From ddae440f61dfb7dc18e30d573cf63c2abfed2f57 Mon Sep 17 00:00:00 2001 From: Cay Henning Date: Thu, 14 Dec 2023 14:29:02 -0500 Subject: [PATCH 01/57] Add conditional question text --- src/creator.html | 153 ++++++++++++- .../creator-assets/controllers.coffee | 2 + src/src-assets/creator-assets/creator.scss | 205 +++++++++++++++++- .../creator-assets/directives.coffee | 134 +++++++++++- src/src-assets/creator-assets/services.coffee | 106 ++++++++- src/src-assets/player-assets/player.coffee | 90 ++++---- 6 files changed, 623 insertions(+), 67 deletions(-) diff --git a/src/creator.html b/src/creator.html index b1aa5076..45a6e622 100644 --- a/src/creator.html +++ b/src/creator.html @@ -543,12 +543,17 @@

End Point

'align-top' : editedNode.media.align == 'top', 'align-bottom' : editedNode.media.align == 'bottom','expanded' : (displayNodeCreation == NARR || displayNodeCreation == END), 'tiny' : displayNodeCreation == HOTSPOT}" > - + +
@@ -748,7 +753,7 @@

Required Items for Answer

Player must not have this item.

- +
@@ -771,7 +776,7 @@

Required Items for Answer

- +
@@ -1019,7 +1024,7 @@

Required Items for Hotspot

- +
@@ -1042,7 +1047,7 @@

Required Items for Hotspot

- +
@@ -1294,7 +1299,7 @@

Required Items for Answer

- +
@@ -1317,7 +1322,7 @@

Required Items for Answer

- +
@@ -1544,6 +1549,138 @@

Take Player Items

+ +

Conditional Question

+

Conditional Question

+

Conditional Question

+

Conditional Narrative

+

Conditional End Text

+

Instructions: Choose to display different text based on the number of times the player has visited this destination.

+

Instructions: Choose to display different text based on the number of times the player has visited this destination and/or what items they have in their inventory. Note that if a player meets the requirements for more than one, then it will display the one with the most required items.

+

{{invalidRequiredVisits}}

+ +
+ + +
+
p { + padding: 1em; + + color: $gray-darker; + font-style: italic; + + &.invalid-quantity-message { + padding: 0; + } + + &.hidden { + padding: 0; + display: none; + } + } + + > ul { + padding: 1em; + list-style-type: none; + margin: 0px; + padding: 0px; + + display: flex; + flex-direction: column; + flex-grow: 1; + grid-gap: 10px; + + max-height:300px; + overflow-y: auto; + + > li { + width: 99%; + padding: 5px; + box-sizing: border-box; + border-radius: 5px; + display: flex; + flex-direction: row; + gap: 10px; + + textarea { + flex-grow: 1; + } + + .question-options { + display: flex; + flex-direction: row; + text-align: center; + grid-gap: 10px; + color: $gray-darker; + + .required-number-of-visits { + display: flex; + flex-direction: column; + width: 155px; + justify-content: center; + align-items: center; + + input { + height: 2em; + width: 75px; + } + } + + .require-item-btn { + // width: 100%; + height: 100%; + position: relative; + display: flex; + flex-direction: column-reverse; + justify-content: center; + align-items: center; + grid-gap: 5px; + border-color: $white; + font-size: 14px; + max-width: 115px; + + &#hotspot-require-item-btn { + height: auto; + } + + div { + border-radius: 5px; + border: 1px dashed $gray-lighter; + position: relative; + height: 50px; + width: 50px; + display: flex; + align-items: center; + justify-content: center; + } + + &.requires-items { + div { + background-color: $gray-lighter; + } + } + + .arrow-bottom { + position: absolute; + top: -12px; + left: 30%; + } + + .arrow-right { + position: absolute; + right: -17px; + top: 12px; + + border-top: 13px solid transparent; + border-left: 10px solid $gray-darker; + border-bottom: 13px solid transparent; + } + } + } + + input { + padding: 5px 10px; + + border: solid 1px #cccccc; + border-radius: 3px; + } + + &:hover { + background: $color-selection-hover; + } + + button.question-node-select, button.add-item { + color: $white; + height: fit-content; + } + + .question-items { + width: 100%; + display: flex; + flex-direction: row; + gap: 10px; + box-sizing: border-box; + + background: $gray-lighter; + border: solid 1px #cccccc; + border-radius: 5px; + padding: 10px; + + p { + color: $gray; + font-size: 0.8em; + } + + button:hover { + background: $red; + color: $white; + } + } + } + } + + .end-buttons { + display: flex; + flex-direction: row; + justify-content: space-between; + padding: 1em; + } +} + import-type-selection { position: fixed; left: 50%; @@ -3646,7 +3840,6 @@ import-type-selection { z-index: $z-index-modal; width: $misc-popup-width; - margin: 5px 0px 0px -300px; border-radius: 10px; background: $color-primary; diff --git a/src/src-assets/creator-assets/directives.coffee b/src/src-assets/creator-assets/directives.coffee index 6e592f51..68f23960 100644 --- a/src/src-assets/creator-assets/directives.coffee +++ b/src/src-assets/creator-assets/directives.coffee @@ -2026,9 +2026,9 @@ angular.module "Adventure" $scope.questionPlaceholder = "Enter a conclusion here for this decision tree or path." # Update the node's properties when the associated input models change - $scope.$watch "question", (newVal, oldVal) -> + $scope.$watch "questions", (newVal, oldVal) -> if newVal isnt null and $scope.editedNode - $scope.editedNode.question = newVal + $scope.editedNode.questions = $scope.questions $scope.$watch "answers", ((newVal, oldVal) -> if newVal isnt null and $scope.editedNode @@ -2094,6 +2094,104 @@ angular.module "Adventure" if(hotspot) hotspot.style.zIndex = 100; + $scope.toggleQuestionsEditor = () -> + $scope.showQuestions = !$scope.showQuestions + if $scope.editedNode.questions.length <= 0 + $scope.newQuestion() + + $scope.toggleQuestionRequiredItemsModal = (question) -> + $scope.showDropdown = false + # Same question, close the modal + if $scope.currentQuestion is question + $scope.currentQuestion = null + # Different question + else + $scope.currentQuestion = question + + # If current question is not null, open modal + if $scope.currentQuestion + $scope.showQuestionRequiredItems = true + # Else close the modal + else + $scope.showQuestionRequiredItems = false + + if $scope.showQuestionRequiredItems + # Close node items modal + $scope.showItemSelection = false + + # Remove error message + $scope.invalidQuantity = null + # Save the original item count in case of invalid input + if question.requiredItems + for item in question.requiredItems + item.tempMinCount = item.minCount + item.tempMaxCount = item.maxCount + + if !$scope.showAdvancedOptions + # Show advanced options on start only if advanced options are enabled + if !item.uncappedMax + $scope.showAdvancedOptions = true + else + $scope.showAdvancedOptions = false + + # Add items not already being used to the items available for selection + $scope.availableItems = [] + for item in $scope.inventoryItems + do (item) -> + used = false + if question.requiredItems + for i in question.requiredItems + if item.id is i.id + used = true + if ! used + $scope.availableItems.push(item) + + $scope.selectedItem = $scope.availableItems[0] + + # $scope.addRequiredItemToQuestion = (item, question) -> + # newItem = { + # id: item.id + # minCount: 1 + # maxCount: 1 + # tempMinCount: 1 + # tempMaxCount: 1 + # uncappedMax: true + # range: "" + # } + # # Add item to question's required items + # question.requiredItems.push(newItem) + # # Remove item from available items + # $scope.availableItems.splice($scope.availableItems.indexOf(item), 1) + # # Reset selected item + # $scope.selectedItem = $scope.availableItems[0] + # $scope.showDropdown = false + + # $scope.removeRequiredItemFromQuestion = (item, question) -> + # # Remove item from question's required items + # question.requiredItems.splice(question.requiredItems.indexOf(item), 1) + # # Add item back to available items + # item.count = 1 + # for parentItem in $scope.inventoryItems + # if item.id is parentItem.id + # $scope.availableItems.push(parentItem) + # # Reset selected item + # $scope.selectedItem = $scope.availableItems[0] + + $scope.newQuestion = () -> + # Create new question + newQuestion = + id: treeSrv.generateAnswerHash() + text: "" + requiredItems: [] + requiredVisits: if $scope.editedNode.questions.length > 0 then $scope.editedNode.questions[$scope.editedNode.questions.length - 1].requiredVisits + 1 else 0 + + # Add new answer to answers array + $scope.editedNode.questions.push newQuestion + + $scope.removeQuestion = () -> + # Remove question from questions array + $scope.editedNode.questions.splice($scope.editedNode.questions.indexOf($scope.currentQuestion), 1) + $scope.toggleNodeItemsModal = () -> $scope.showDropdown = false # Close node items modal @@ -2203,7 +2301,7 @@ angular.module "Adventure" if item.tempMinCount > -1 $scope.invalidQuantity = null if (item.tempMinCount > item.tempMaxCount && !item.uncappedMax) - if advanced + if advanced $scope.invalidMaxQuantity = "Invalid range. Max quantity must be greater than or equal to min quantity." $scope.showAdvancedOptions = true if (item.tempMaxCount > -1) @@ -2219,7 +2317,7 @@ angular.module "Adventure" $scope.invalidMaxQuantity = null else if item.tempMinCount == 0 and !advanced # if setting minimum to 0 in basic options, means player must have none of item - item.minCount = 0 + item.minCount = 0 item.maxCount = 0 item.tempMaxCount = 0 item.uncappedMax = false @@ -2305,12 +2403,12 @@ angular.module "Adventure" $scope.selectedItem = item $scope.showItemManagerDialog = false - # Add a required item to answer - $scope.addRequiredItemToAnswer = (item, answer) -> + # Add a required item to object + $scope.addRequiredItemToObject = (item, object) -> if item - answer.requiredItems = answer.requiredItems || [] + object.requiredItems = object.requiredItems || [] - # for i in answer.requiredItems when i.id is item.id + # for i in object.requiredItems when i.id is item.id # i.count += 1 # return @@ -2325,7 +2423,7 @@ angular.module "Adventure" uncappedMax: true range: "" } - answer.requiredItems.push(newItem) + object.requiredItems.push(newItem) # Remove item from the available items dropdown index = $scope.availableItems.indexOf(item) @@ -2335,8 +2433,8 @@ angular.module "Adventure" # Hide Dropdown $scope.showDropdown = false - $scope.removeRequiredItemFromAnswer = (item, answer) -> - answer.requiredItems.splice answer.requiredItems.indexOf(item), 1 + $scope.removeRequiredItemFromObject = (item, object) -> + object.requiredItems.splice object.requiredItems.indexOf(item), 1 # Add item to the available items dropdown item.count = 1 @@ -3139,6 +3237,20 @@ angular.module "Adventure" # children()[1] references the answer text input box row.children()[1].focus() ), 100 + + # Listen for when a new question is added + $scope.$on "editedNode.questions.added", (evt) -> + $timeout (() -> + # Auto-scroll to the bottom + $element[0].scrollTop = $element[0].scrollHeight + + # Find the answer input box and focus it + list = angular.element($element.children()[0]).children() + if list.length > 0 + row = angular.element list[list.length - 1] + # children()[1] references the answer text input box + row.children()[1].focus() + ), 100 ] # The validation dialog is linked to two validation events: diff --git a/src/src-assets/creator-assets/services.coffee b/src/src-assets/creator-assets/services.coffee index a1294b36..fab176aa 100644 --- a/src/src-assets/creator-assets/services.coffee +++ b/src/src-assets/creator-assets/services.coffee @@ -528,6 +528,14 @@ angular.module "Adventure" answer.requiredItems.splice index, 1 break + if tree.questions + for question in tree.questions + if question.requiredItems + for item, index in question.requiredItems + if item.id is deletedItem.id + question.requiredItems.splice index, 1 + break + if !tree.contents then return i = 0 @@ -604,10 +612,51 @@ angular.module "Adventure" items: questionItemData answers: [] - question = - text: if tree.question then tree.question else "" + angular.forEach tree.questions, (question, index) -> + requiredItemsData = [] - itemData.questions.push question + if question.requiredItems + for i in question.requiredItems + do (i) -> + # Format properties for pre-existing items without said properties + + if i.minCount > -1 + minCount = i.minCount + else if i.tempMinCount > -1 + minCount = i.tempMinCount + else if i.count + minCount = i.count + else + # If minCount isn't set, set it to 1 + minCount = 1 + + if i.maxCount > -1 + maxCount = i.maxCount + else if i.tempMaxCount > -1 + maxCount = i.tempMaxCount + else if i.count + maxCount = i.count + else + # If maxCount isn't set, set it to minCount + maxCount = minCount + + uncappedMax = if (i.uncappedMax isnt null) then i.uncappedMax else false + + formattedItem = + id: i.id + range: "" + minCount: minCount + maxCount: maxCount + uncappedMax: uncappedMax + + requiredItemsData.push formattedItem + itemQuestionData = + text: question.text + options: + requiredItems: requiredItemsData + requiredVisits: question.requiredVisits || 0 + + itemData.questions.push itemQuestionData if tree.media itemData.options.asset = @@ -731,8 +780,6 @@ angular.module "Adventure" type: item.options.type contents: [] - if item.questions[0].text then node.question = item.questions[0].text - if item.options.asset if item.options.asset.type is 'image' node.media = @@ -761,6 +808,55 @@ angular.module "Adventure" if item.options.hasLinkToSelf then node.hasLinkToSelf = true if item.options.pendingTarget then node.pendingTarget = item.options.pendingTarget + angular.forEach item.questions, (question, index) -> + unless node.questions then node.questions = [] + + requiredItemsData = [] + if question.options.requiredItems + for i in question.options.requiredItems + do (i) -> + # Format properties for pre-existing items without said properties + + if i.minCount > -1 + minCount = i.minCount + else if i.tempMinCount > -1 + minCount = i.tempMinCount + else if i.count + minCount = i.count + else + # If minCount isn't set, set it to 1 + minCount = 1 + + if i.maxCount > -1 + maxCount = i.maxCount + else if i.tempMaxCount > -1 + maxCount = i.tempMaxCount + else if i.count + maxCount = i.count + else + # If maxCount isn't set, set it to minCount + maxCount = minCount + + uncappedMax = if (i.uncappedMax isnt null) then i.uncappedMax else false + + formattedItem = + id: i.id + range: "" + minCount: minCount + maxCount: maxCount + uncappedMax: uncappedMax + + + requiredItemsData.push formattedItem + + nodeQuestion = + text: question.text + id: generateAnswerHash() + requiredItems: requiredItemsData + requiredVisits: question.options.requiredVisits + + node.questions.push nodeQuestion + angular.forEach item.answers, (answer, index) -> unless node.answers then node.answers = [] diff --git a/src/src-assets/player-assets/player.coffee b/src/src-assets/player-assets/player.coffee index f398154d..6cdb8ca3 100644 --- a/src/src-assets/player-assets/player.coffee +++ b/src/src-assets/player-assets/player.coffee @@ -83,7 +83,7 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) $scope.setLightboxZoom = (val) -> $scope.lightboxZoom = val - $scope.visitedNodes = [] + $scope.visitedNodes = {} # Object containing properties for the hotspot label that appears on mouseover $scope.hotspotLabelTarget = @@ -103,36 +103,6 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) if questionId is 0 q_data = $scope.qset.items[0] - unless q_data.options.asset then $scope.layout = "text-only" - else if q_data.questions[0].text != "" then $scope.layout = q_data.options.asset.align - else $scope.layout = "image-only" - - # If the question text contains a string that doesn't pass angular's $sanitize check, it'll fail to display anything - # Instead, parse in advance, catch the error, and warn the user that the text was nasty - try - # Run question text thru pre-sanitize routine because $sanitize is fickle about certain characters like >, < - presanitized = q_data.questions[0].text - for k, v of PRESANITIZE_CHARACTERS - presanitized = presanitized.replace k, v - $sanitize presanitized - - catch error - q_data.questions[0].text = "*Question text removed due to malformed or dangerous HTML content*" - - # Note: Micromarkdown is still adding a mystery newline or carriage return character to the beginning of most parsed strings (but not generated tags??) - if presanitized.length then parsedQuestion = micromarkdown.parse(presanitized) else parsedQuestion = "" - - # hyperlinks are automatically converted into tags, except it loads content within the iframe. To circumvent this, need to dynamically add target="_blank" attribute to all generated URLs - parsedQuestion = addTargetToHrefs parsedQuestion - - $scope.question = - text : parsedQuestion, # questions MUST be an array, always 1 index w/ single text property. MMD converts markdown formatting into proper markdown syntax - layout: $scope.layout, - type : q_data.options.type, - id : q_data.options.id - materiaId: q_data.id - options: q_data.options - # Remove new item alerts for i in $scope.inventory i.new = false @@ -146,13 +116,13 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) $scope.inventoryUpdateMessage = "" # Add items to player's inventory - if $scope.question.options.items and $scope.question.options.items[0] + if q_data.options.items and q_data.options.items[0] $scope.showInventoryBtn = true # Format items - if $scope.question.options.items - for q_i in $scope.question.options.items + if q_data.options.items + for q_i in q_data.options.items do (q_i) -> item = id: q_i.id @@ -166,7 +136,7 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) hasItem = false # Check if item is first visit only and player has visited this node before - if ($scope.visitedNodes.some((n) => n is $scope.question.id) and q_i.firstVisitOnly) + if ($scope.visitedNodes[q_data.id] and q_i.firstVisitOnly) # Move to next item else # Inventory update @@ -220,6 +190,52 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) $scope.inventoryUpdateMessage = "Updates to inventory: " + addedItemsMessage + removedItemsMessage + # Get question based on inventory and number of visits + presanitized = "" + mostItems = 0 + for q in q_data.questions + if q.options.requiredItems + missingItems = $scope.checkInventory(q.options.requiredItems) + if (missingItems.length > 0) + continue + else if (mostItems < q.options.requiredItems.length) + mostItems = q.options.requiredItems.length + else + continue + if q.options.requiredVisits != null + if $scope.visitedNodes[q_data.id] < q.options.requiredVisits + continue + # If the question text contains a string that doesn't pass angular's $sanitize check, it'll fail to display anything + # Instead, parse in advance, catch the error, and warn the user that the text was nasty + try + # Run question text thru pre-sanitize routine because $sanitize is fickle about certain characters like >, < + presanitized = q.text + for k, v of PRESANITIZE_CHARACTERS + presanitized = presanitized.replace k, v + $sanitize presanitized + + catch error + q.text = "*Question text removed due to malformed or dangerous HTML content*" + + unless q_data.options.asset then $scope.layout = "text-only" + else if presanitized != "" then $scope.layout = q_data.options.asset.align + else $scope.layout = "image-only" + + + # Note: Micromarkdown is still adding a mystery newline or carriage return character to the beginning of most parsed strings (but not generated tags??) + if presanitized.length then parsedQuestion = micromarkdown.parse(presanitized) else parsedQuestion = "" + + # hyperlinks are automatically converted into tags, except it loads content within the iframe. To circumvent this, need to dynamically add target="_blank" attribute to all generated URLs + parsedQuestion = addTargetToHrefs parsedQuestion + + $scope.question = + text : parsedQuestion, # questions MUST be an array, always 1 index w/ single text property. MMD converts markdown formatting into proper markdown syntax + layout: $scope.layout, + type : q_data.options.type, + id : q_data.options.id + materiaId: q_data.id + options: q_data.options + $scope.answers = [] if q_data.answers @@ -319,7 +335,7 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) else handleEmptyNode() # Should hopefully only happen on preview, when empty nodes are allowed - $scope.visitedNodes.push(q_data.options.id) + $scope.visitedNodes[q_data.id] = ($scope.visitedNodes[q_data.id] || 0) + 1 $scope.dismissUpdates = () -> $scope.inventoryUpdate = false @@ -531,7 +547,7 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) link = null if $scope.question.type is $scope.END link = -1 - Materia.Score.submitFinalScoreFromClient q_data.options.id, q_data.questions[0].text, q_data.options.finalScore + Materia.Score.submitFinalScoreFromClient q_data.options.id, $scope.question.text, q_data.options.finalScore else link = q_data.answers[0].options.link From ce36a9fc0ff6a3e37cd7ff7aed992e9e466b9398 Mon Sep 17 00:00:00 2001 From: Cay Henning Date: Thu, 14 Dec 2023 14:41:25 -0500 Subject: [PATCH 02/57] Hide updates on tab in player --- src/player.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/player.html b/src/player.html index b353a8ee..5111465a 100644 --- a/src/player.html +++ b/src/player.html @@ -138,7 +138,8 @@

{{title}}

focus-manager aria-live="assertive" tabindex="0" - aria-label="Question: {{question.text}}, {{inventoryUpdateMessage}}"> + aria-label="Question: {{question.text}}, {{inventoryUpdateMessage}}" + ng-blur="dismissUpdates()">

From d38ede7ec0c7db509be1d592fa9955e5b736b590 Mon Sep 17 00:00:00 2001 From: Cay Henning Date: Thu, 14 Dec 2023 15:39:02 -0500 Subject: [PATCH 03/57] Update creator's guide --- ..._widget_adventure_conditional_questions.png | Bin 0 -> 103545 bytes ...t_adventure_conditional_questions_items.png | Bin 0 -> 128877 bytes src/_guides/creator.md | 17 +++++++++++++++++ 3 files changed, 17 insertions(+) create mode 100644 src/_guides/assets/create_widget_adventure_conditional_questions.png create mode 100644 src/_guides/assets/create_widget_adventure_conditional_questions_items.png diff --git a/src/_guides/assets/create_widget_adventure_conditional_questions.png b/src/_guides/assets/create_widget_adventure_conditional_questions.png new file mode 100644 index 0000000000000000000000000000000000000000..f126bfd4c2e68312cbf638a326173f0d69759815 GIT binary patch literal 103545 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 PkHBGrIQtF7((U#i7oT-! literal 0 HcmV?d00001 diff --git a/src/_guides/assets/create_widget_adventure_conditional_questions_items.png b/src/_guides/assets/create_widget_adventure_conditional_questions_items.png new file mode 100644 index 0000000000000000000000000000000000000000..4f58431ffe623207b2cd8a9cee7098a21b342972 GIT binary patch literal 128877 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
&bEJET@+KQR!-S^|}NYnzGHGVIjh1P2A3>GXI@ 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

XWg#%b2Ak(GelsBGFQ)(e2}R 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 z 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 literal 0 HcmV?d00001 diff --git a/src/_guides/creator.md b/src/_guides/creator.md index 2257c2a5..9a14e5b2 100644 --- a/src/_guides/creator.md +++ b/src/_guides/creator.md @@ -173,6 +173,16 @@ For any destination type besides Hotspot, selecting the **Add Media** button cha If you choose to upload an image, the image uploader will be displayed, where you can upload images from your computer, or select an image you've previously uploaded to Materia. Once you've selected an image or video, you can use the **Swap with Question** button below the media to switch the arrangement of the image and question text. Narrative and End Point destinations allow you to choose between vertical as well as horizontal arrangements. Select **Change Media** to choose a different image or video. +### (Advanced) Conditional Text Based on Number of Visits +You have the option of creating different question text each time a player visits a destination. Click on the "Advanced Question Editor" at the top right of the node creation screen to open the editor. + +![conditional questions](assets/create_widget_adventure_conditional_questions.png "conditional questions") + +1. Add question text +2. Change required number of visits + +It will always display the question text with the most required number of visits met. For example, if a player has visited the node 2 times, it will display the second question above. If a player has visited the node 6 times, it will display the third question above. + ## Inventory System ## Optionally, you can choose to use the inventory system. The inventory system allows you to give the player items or remove items from the player's inventory for each destination they visit. @@ -283,6 +293,13 @@ The advanced options allow you to set the required item amount to be within a sp 2. Set the maximum number the player can have of this item to select this answer. 3. (Default) Set the maximum to have no cap. +### (Advanced) Conditional Question Text Based on Items +Once you have created items, an option will appear in the Advanced Question Editor to require items for each question text. You can require players to have certain items in their inventory in order to see different questions by clicking on the "Edit Required Items" button. This menu will operate similarly to the required items for answers from above. + +![conditional questions](assets/create_widget_adventure_conditional_questions_items.png "conditional questions") + +It will always display the question text with the most requirements met. For example, if the first question requires the player to have a hat, but the second question requires the player to have a hat AND a vinyl, it will display the second question if the player has both. + ### Example Tree Using Inventory System ### ![adventure tree with items](assets/create_widget_adventure_tree_with_items.png "adventure tree with items") From 7c3f9b6a2280636fcd10e5d718269bf8e0066c29 Mon Sep 17 00:00:00 2001 From: Cay Henning Date: Mon, 18 Dec 2023 15:27:33 -0600 Subject: [PATCH 04/57] Fix unreachable destinations checker --- .../creator-assets/controllers.coffee | 47 ++++--- src/src-assets/creator-assets/services.coffee | 122 ++++++++++-------- 2 files changed, 94 insertions(+), 75 deletions(-) diff --git a/src/src-assets/creator-assets/controllers.coffee b/src/src-assets/creator-assets/controllers.coffee index 0388789a..18be7735 100644 --- a/src/src-assets/creator-assets/controllers.coffee +++ b/src/src-assets/creator-assets/controllers.coffee @@ -299,30 +299,29 @@ angular.module "Adventure" validation = treeSrv.validateTreeOnSave $scope.treeData # Check if there are any unreachable destinations if the inventory system is enabled - # if ($scope.inventoryItems.length > 0) - # visitedNodes = new Map() - # unvisitedNodes = new Map() - # inventory = new Map() - # unreachableDestinations = treeSrv.findUnreachableDestinations $scope.treeData, $scope.treeData, visitedNodes, unvisitedNodes, inventory - - # if (unreachableDestinations.size > 0) - # # Get all nodes that are not in reachableDestinations - # # create error at each node that is not in reachableDestinations - # $scope.validation.errors = [] - # unreachableDestinations = Array.from(unreachableDestinations.values()) - - # for node in unreachableDestinations - # nodeId = if node.options then node.options.id or node.id else node.id - # if (node.parentId == -1) - # # start node id is 0 - # nodeId = 0 - # $scope.validation.errors.push({ - # type: "unreachable_destination", - # node: nodeId, - # message: "Destination " + treeSrv.integerToLetters(nodeId) + " is unreachable!" - # }) - # $rootScope.$broadcast "validation.error" - # return Materia.CreatorCore.cancelSave '' + if ($scope.inventoryItems.length > 0) + visitedNodes = new Map() + unvisitedNodes = new Map() + inventory = new Map() + unreachableDestinations = treeSrv.findUnreachableDestinations $scope.treeData, $scope.treeData, visitedNodes, unvisitedNodes, inventory + if (unreachableDestinations.size > 0) + # Get all nodes that are not in reachableDestinations + # create error at each node that is not in reachableDestinations + $scope.validation.errors = [] + unreachableDestinations = Array.from(unreachableDestinations.values()) + + for node in unreachableDestinations + nodeId = if node.options then node.options.id or node.id else node.id + if (node.parentId == -1) + # start node id is 0 + nodeId = 0 + $scope.validation.errors.push({ + type: "unreachable_destination", + node: nodeId, + message: "Destination " + treeSrv.integerToLetters(nodeId) + " is unreachable!" + }) + $rootScope.$broadcast "validation.error" + return Materia.CreatorCore.cancelSave '' else validation = [] diff --git a/src/src-assets/creator-assets/services.coffee b/src/src-assets/creator-assets/services.coffee index 2b95ef2a..2b8b858c 100644 --- a/src/src-assets/creator-assets/services.coffee +++ b/src/src-assets/creator-assets/services.coffee @@ -155,66 +155,86 @@ angular.module "Adventure" if (! requiredItems) return [] angular.forEach requiredItems, (item) -> - hasItemInInventory = false - playerItem = inventory.get(item.id) - if playerItem - hasItemInInventory = true + meetsMin = false + meetsMax = false + playerItemCount = inventory.get(item.id) + if playerItemCount # Check if player has more than the min - if playerItem.count >= item.minCount + if playerItemCount >= item.minCount || item.minCount is 0 + meetsMin = true # Check if player has less than the max - if playerItem.count <= item.maxCount or item.uncappedMax - return true - return false - # Check if player doesn't have item but there is no minimum - if ! hasItemInInventory and item.minCount is 0 - hasRequiredItem = true - if ! hasRequiredItem + if playerItemCount <= item.maxCount or item.uncappedMax + meetsMax = true + if !meetsMin or !meetsMax missingItems.push(item.id) return missingItems + # Returns all nodes that can be reached from a given node using breadth-first search + findUnreachableDestinations = (tree, startNode) -> + visitedNodes = new Map() + unvisitedNodes = new Map() + node = { + ...startNode, + parent: "Start", # parent node's name + inventory: new Map(), # inventory up to this node + visitedNodes: new Map() # chain of nodes visited to get to this node + } + queue = [node] + + while queue.length > 0 + node = queue.shift() + + # If the node has already been visited some number of times, skip it + if visitedNodes.get(node.id) && visitedNodes.get(node.id) > Math.max(50, node.answerLinks.length) + continue + + # If the node is an end node, skip it + if node.type is "end" + continue + + # Increment the number of times the node has been visited + visitedNodes.set(node.id, (visitedNodes.get(node.id) or 0) + 1) + + + # Add items to inventory + if node.items + for item in node.items + # Check if item is first visit only and player has visited this node before + if (node.visitedNodes.get(node.id) >= 1 and item.firstVisitOnly) + # Move to next item + continue + else + if item.takeAll + node.inventory.set(item.id, 0) + else + node.inventory.set(item.id, (node.inventory.get(item.id) || 0) + item.count) - # Returns all nodes that can be reached from a given node - findUnreachableDestinations = (tree, node, visitedNodes, unvisitedNodes, inventory) -> - # If the node has already been visited some number of times, return to parent - # This is to prevent infinite loops - if visitedNodes.get(node.id) > Math.max(50, node.answerLinks.length) - return unvisitedNodes - - # Increment the number of times the node has been visited - visitedNodes.set(node.id, (visitedNodes.get(node.id) || 0) + 1) + node.visitedNodes.set(node.id, (node.visitedNodes.get(node.id) || 0) + 1) - # If the node is an end node, return to parent - if node.type is "end" - return unvisitedNodes + if node.answers + for answer in node.answers + targetNode = findNode(tree, answer.target) - # Add items to inventory - if node.items - for item in node.items - # Check if item is first visit only and player has visited this node before - if (visitedNodes.get(node.id) >= 1 and item.firstVisitOnly) - # Move to next item - continue - else - if item.takeAll - inventory.set(item.id, 0) + if checkInventory(answer.requiredItems, node.inventory).length > 0 && visitedNodes.get(targetNode.id) is undefined + # if the player doesn't have the required items and this node hasn't been visited on a different path, add it to unvisitedNodes + unvisitedNodes.set(targetNode.id, targetNode) else - inventory.set(item.id, (inventory.get(item.id) || 0) + item.count) - - # Search answers - if node.answers - for answer in node.answers - targetNode = findNode(tree, answer.target) - if checkInventory(answer.requiredItems, inventory).length > 0 - # If the player doesn't have the required items, add it to unvisitedNodes - unvisitedNodes.set(targetNode.id, targetNode) - else - # If the player has the required items, add it to visitedNodes - if (unvisitedNodes.get(targetNode.id)) - unvisitedNodes.remove(targetNode.id) - visitedNodes.set(targetNode.id, (visitedNodes.get(targetNode.id) || 0) + 1) - unvisitedNodes = new Map([...unvisitedNodes, ...findUnreachableDestinations(tree, targetNode, visitedNodes, unvisitedNodes, inventory)]) - - return unvisitedNodes + # if the player has the required items, add it to visitedNodes + if (unvisitedNodes.get(targetNode.id)) then unvisitedNodes.delete(targetNode.id) + + # increment the number of times the node has been visited + visitedNodes.set(targetNode.id, (visitedNodes.get(targetNode.id) || 0) + 1) + + # add the node to the queue with new inventory and visited node stores + addInventory = { + ...targetNode, + parent: node.name, + inventory: new Map(node.inventory), + visitedNodes: new Map(node.visitedNodes) + } + queue.push(addInventory) + + unvisitedNodes # Recursive function for adding a node in between a given parent and child, essentially splitting an existing link From ecba592b354837e1a9c9db73331f957a0ec2a598 Mon Sep 17 00:00:00 2001 From: Cay Henning Date: Mon, 18 Dec 2023 15:44:06 -0600 Subject: [PATCH 05/57] remove testing stuff --- src/src-assets/creator-assets/services.coffee | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/src-assets/creator-assets/services.coffee b/src/src-assets/creator-assets/services.coffee index 2b8b858c..65ec3d98 100644 --- a/src/src-assets/creator-assets/services.coffee +++ b/src/src-assets/creator-assets/services.coffee @@ -214,7 +214,6 @@ angular.module "Adventure" if node.answers for answer in node.answers targetNode = findNode(tree, answer.target) - if checkInventory(answer.requiredItems, node.inventory).length > 0 && visitedNodes.get(targetNode.id) is undefined # if the player doesn't have the required items and this node hasn't been visited on a different path, add it to unvisitedNodes unvisitedNodes.set(targetNode.id, targetNode) @@ -825,7 +824,7 @@ angular.module "Adventure" unless node.questions then node.questions = [] requiredItemsData = [] - if question.options.requiredItems + if question.options && question.options.requiredItems for i in question.options.requiredItems do (i) -> # Format properties for pre-existing items without said properties @@ -866,7 +865,7 @@ angular.module "Adventure" text: question.text id: generateAnswerHash() requiredItems: requiredItemsData - requiredVisits: question.options.requiredVisits + requiredVisits: if question.options then question.options.requiredVisits else -1 node.questions.push nodeQuestion if item.options.customLabel then node.customLabel = item.options.customLabel From 2165dcf6a21ce8005baa2ed4b4e30959f47868e5 Mon Sep 17 00:00:00 2001 From: Cay Henning Date: Mon, 18 Dec 2023 16:03:51 -0600 Subject: [PATCH 06/57] Reposition advanced question button and remove unnecessary continue statement --- src/creator.html | 7 ++++--- src/src-assets/creator-assets/creator.scss | 7 ++----- src/src-assets/creator-assets/services.coffee | 2 +- src/src-assets/player-assets/player.coffee | 4 +--- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/creator.html b/src/creator.html index 2d7c4317..aa72fc2e 100644 --- a/src/creator.html +++ b/src/creator.html @@ -569,9 +569,6 @@

'tiny' : displayNodeCreation == HOTSPOT}" ng-model="editedNode.questions[0].text" placeholder="{{questionPlaceholder}}"> -

@@ -1559,6 +1556,10 @@

Take Player Items

-->
+ + -
diff --git a/src/src-assets/creator-assets/creator.scss b/src/src-assets/creator-assets/creator.scss index 278e6f57..49b5923f 100644 --- a/src/src-assets/creator-assets/creator.scss +++ b/src/src-assets/creator-assets/creator.scss @@ -1134,136 +1134,6 @@ required-items-tooltip { } } -/** For node item editor sections only **/ -.item-selection { - - .item-instructions { - font-size: 12px; - font-style: italic; - color: $gray-darker; - } - - /** Show modal on screen widths < 1000px **/ - @media screen and (max-width: $item-panel-max-width) { - &.item-panel { - display: none; - } - } - /** Show two panels on screen widths >= 1000px **/ - @media screen and (min-width: $item-panel-max-width) { - &.item-modal { - display: none; - } - } - .basic-options { - display: flex; - flex-direction: row; - justify-content: space-between; - width: 100%; - align-items: center; - } - - .advanced-option { - display: flex; - align-items: center; - flex-direction: row; - flex-wrap: wrap; - gap: 15px; - padding: 10px 10px 10px 0; - - input { - width: auto; - height: 1em; - } - - button.hide-answer-button { - display: flex; - flex-direction: row; - align-items: center; - color: $gray-darker; - gap: 20px; - padding: 20px; - background-color: $white; - - img { - width: 25px; - height: 25px; - } - - &:hover { - background-color: $gray-lighter; - } - - - &.hide-answer { - background-color: $gray-lighter; - } - - } - } - - .item-options-container { - display: flex; - gap: 5px; - flex-wrap: wrap; - justify-content: end; - flex-direction: column; - padding: 0 10px; - align-content:end; - min-width: 150px; - max-width: 150px; - - &.flex-start { - justify-content: start; - align-content:start; - } - - .item-minimum-quantity, .item-quantity { - display: flex; - flex-direction: row; - grid-gap: 10px; - align-items: center; - - input { - width: 70px; - box-sizing: border-box; - - &.uncapped-max-option, &.no-minimum-option { - width: auto; - } - } - - .ng-invalid-pattern { - border: $red solid 1px; - } - } - - .item-quantity-options { - gap: 10px; - display: flex; - flex-direction: column; - } - - .quantity-explanation { - font-style: italic; - padding: 10px 0; - } - - .greyed-out { - color: $gray-darker; - font-style: italic; - } - } - - &.item-panel { - .advanced-option { - justify-content: flex-start; - font-size: 14px; - margin-top: 12px; - } - } -} - /** For both node item editors and inventory manager **/ .item-selection, item-manager { opacity: 0; @@ -1550,10 +1420,6 @@ required-items-tooltip { align-self: center; justify-content: center; - &.small-name { - max-width: 100px; - } - .item-name { font-weight: bold; } @@ -1949,6 +1815,137 @@ item-icon-selector { } + +/** For node item editor sections only **/ +.item-selection { + + .item-instructions { + font-size: 12px; + font-style: italic; + color: $gray-darker; + } + + /** Show modal on screen widths < 1000px **/ + @media screen and (max-width: $item-panel-max-width) { + &.item-panel { + display: none !important; + } + } + /** Show two panels on screen widths >= 1000px **/ + @media screen and (min-width: $item-panel-max-width) { + &.item-modal:not(.question-required) { + display: none !important; + } + } + .basic-options { + display: flex; + flex-direction: row; + justify-content: space-between; + width: 100%; + align-items: center; + } + + .advanced-option { + display: flex; + align-items: center; + flex-direction: row; + flex-wrap: wrap; + gap: 15px; + padding: 10px 10px 10px 0; + + input { + width: auto; + height: 1em; + } + + button.hide-answer-button { + display: flex; + flex-direction: row; + align-items: center; + color: $gray-darker; + gap: 20px; + padding: 20px; + background-color: $white; + + img { + width: 25px; + height: 25px; + } + + &:hover { + background-color: $gray-lighter; + } + + + &.hide-answer { + background-color: $gray-lighter; + } + + } + } + + .item-options-container { + display: flex; + gap: 5px; + flex-wrap: wrap; + justify-content: end; + flex-direction: column; + padding: 0 10px; + align-content:end; + min-width: 150px; + max-width: 150px; + + &.flex-start { + justify-content: start; + align-content:start; + } + + .item-minimum-quantity, .item-quantity { + display: flex; + flex-direction: row; + grid-gap: 10px; + align-items: center; + + input { + width: 70px; + box-sizing: border-box; + + &.uncapped-max-option, &.no-minimum-option { + width: auto; + } + } + + .ng-invalid-pattern { + border: $red solid 1px; + } + } + + .item-quantity-options { + gap: 10px; + display: flex; + flex-direction: column; + } + + .quantity-explanation { + font-style: italic; + padding: 10px 0; + } + + .greyed-out { + color: $gray-darker; + font-style: italic; + } + } + + &.item-panel { + .advanced-option { + justify-content: flex-start; + font-size: 14px; + margin-top: 12px; + } + } +} + /* For give-item and required-item modals and panels */ .item-container { display: flex; @@ -2557,28 +2554,6 @@ node-creation-selection-dialog { textarea { resize: none; } - - .panel-right { - display: none; - } - - .item-selection { - /** Show modal on screen widths < 1000px **/ - @media screen and (max-width: 1400px) { - &.item-panel { - display: none; - } - } - /** Show two panels on screen widths >= 1000px **/ - @media screen and (min-width: 1400px) { - &.item-panel { - display: none; - } - &.item-modal.show { - display: flex; - } - } - } } h2 { diff --git a/src/src-assets/creator-assets/directives.coffee b/src/src-assets/creator-assets/directives.coffee index ecb1834b..7b0fd19d 100644 --- a/src/src-assets/creator-assets/directives.coffee +++ b/src/src-assets/creator-assets/directives.coffee @@ -390,7 +390,7 @@ angular.module "Adventure" midpoint = pathNode.getPointAtLength(pathNode.getTotalLength()/2) midX = midpoint.x midY = midpoint.y - + # compute the position of the lock icon along the path quarterpoint = pathNode.getPointAtLength(pathNode.getTotalLength()/4) links[index].lock.x = quarterpoint.x @@ -2185,35 +2185,6 @@ angular.module "Adventure" $scope.selectedItem = $scope.availableItems[0] - # $scope.addRequiredItemToQuestion = (item, question) -> - # newItem = { - # id: item.id - # minCount: 1 - # maxCount: 1 - # tempMinCount: 1 - # tempMaxCount: 1 - # uncappedMax: true - # range: "" - # } - # # Add item to question's required items - # question.requiredItems.push(newItem) - # # Remove item from available items - # $scope.availableItems.splice($scope.availableItems.indexOf(item), 1) - # # Reset selected item - # $scope.selectedItem = $scope.availableItems[0] - # $scope.showDropdown = false - - # $scope.removeRequiredItemFromQuestion = (item, question) -> - # # Remove item from question's required items - # question.requiredItems.splice(question.requiredItems.indexOf(item), 1) - # # Add item back to available items - # item.count = 1 - # for parentItem in $scope.inventoryItems - # if item.id is parentItem.id - # $scope.availableItems.push(parentItem) - # # Reset selected item - # $scope.selectedItem = $scope.availableItems[0] - $scope.newQuestion = () -> # Create new question newQuestion = diff --git a/src/src-assets/player-assets/player.coffee b/src/src-assets/player-assets/player.coffee index 1a5fc40b..248083e7 100644 --- a/src/src-assets/player-assets/player.coffee +++ b/src/src-assets/player-assets/player.coffee @@ -136,6 +136,7 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) count: q_i.count || 1 takeAll: q_i.takeAll || false firstVisitOnly: q_i.firstVisitOnly || false + time: Date.now() $scope.questionItems.push item for q_i in $scope.questionItems @@ -200,16 +201,27 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) # Get question based on inventory and number of visits presanitized = "" mostItems = 0 + mostRecentItem = 0 for q in q_data.questions - if q.options.requiredItems + if q.options.requiredVisits + if $scope.visitedNodes[q_data.id] < q.options.requiredVisits + # If the player hasn't visited this node enough times, skip this question + continue + if q.options.requiredItems && q.options.requiredItems[0] missingItems = $scope.checkInventory(q.options.requiredItems) if (missingItems.length > 0) + # If the player doesn't have the required items, skip this question continue else if (mostItems < q.options.requiredItems.length) + # Choose the question with the most required items mostItems = q.options.requiredItems.length - if q.options.requiredVisits - if $scope.visitedNodes[q_data.id] < q.options.requiredVisits - continue + else + # Choose the question with the most recent item + recentItem = $scope.getMostRecentItem($scope.inventory, q.options.requiredItems) + if (recentItem > mostRecentItem) + mostRecentItem = recentItem + else + continue # If the question text contains a string that doesn't pass angular's $sanitize check, it'll fail to display anything # Instead, parse in advance, catch the error, and warn the user that the text was nasty try @@ -342,6 +354,15 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) $scope.visitedNodes[q_data.id] = ($scope.visitedNodes[q_data.id] || 0) + 1 + $scope.getMostRecentItem = (inventory, requiredItems) -> + mostRecentItem = 0 + for i in inventory + for r in requiredItems + if i.id is r.id + if i.time > mostRecentItem + mostRecentItem = i.time + mostRecentItem + $scope.dismissUpdates = () -> $scope.inventoryUpdate = false document.getElementById("inventory-update").setAttribute("inert", true) From b586860801689178ffe1d25b8a2d2ce970c20340 Mon Sep 17 00:00:00 2001 From: Cay Henning Date: Tue, 19 Dec 2023 08:59:03 -0600 Subject: [PATCH 08/57] Fix choosing question with most recent item --- src/src-assets/player-assets/player.coffee | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/src-assets/player-assets/player.coffee b/src/src-assets/player-assets/player.coffee index 248083e7..0254716c 100644 --- a/src/src-assets/player-assets/player.coffee +++ b/src/src-assets/player-assets/player.coffee @@ -212,15 +212,18 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) if (missingItems.length > 0) # If the player doesn't have the required items, skip this question continue - else if (mostItems < q.options.requiredItems.length) - # Choose the question with the most required items - mostItems = q.options.requiredItems.length else - # Choose the question with the most recent item + keep = false recentItem = $scope.getMostRecentItem($scope.inventory, q.options.requiredItems) - if (recentItem > mostRecentItem) + if (recentItem >= mostRecentItem) + # Choose the question with the most recent item mostRecentItem = recentItem - else + keep = true + if (mostItems < q.options.requiredItems.length) + # Choose the question with the most required items + mostItems = q.options.requiredItems.length + keep = true + if (!keep) continue # If the question text contains a string that doesn't pass angular's $sanitize check, it'll fail to display anything # Instead, parse in advance, catch the error, and warn the user that the text was nasty @@ -357,6 +360,8 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) $scope.getMostRecentItem = (inventory, requiredItems) -> mostRecentItem = 0 for i in inventory + console.log($scope.itemSelection[$scope.getItemIndex(i.id)]) + console.log(i) for r in requiredItems if i.id is r.id if i.time > mostRecentItem From 933ca5f3f4a34cd7177f780db71aedf61f6eec30 Mon Sep 17 00:00:00 2001 From: Cay Henning Date: Tue, 19 Dec 2023 09:42:52 -0600 Subject: [PATCH 09/57] Fix multiple questions for new widgets --- src/src-assets/creator-assets/directives.coffee | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/src-assets/creator-assets/directives.coffee b/src/src-assets/creator-assets/directives.coffee index 7b0fd19d..e14d8346 100644 --- a/src/src-assets/creator-assets/directives.coffee +++ b/src/src-assets/creator-assets/directives.coffee @@ -1980,6 +1980,8 @@ angular.module "Adventure" # Initialize the node edit screen with the node's info. If info doesn't exist yet, init properties if $scope.editedNode.question then $scope.question = $scope.editedNode.question else $scope.question = null + if $scope.editedNode.questions then $scope.questions = $scope.editedNode.questions + else $scope.questions = [] # Reset size of question box document.querySelector(".question-box").style.width = null; @@ -2193,7 +2195,7 @@ angular.module "Adventure" requiredItems: [] requiredVisits: if $scope.editedNode.questions.length > 0 then $scope.editedNode.questions[$scope.editedNode.questions.length - 1].requiredVisits + 1 else 0 - # Add new answer to answers array + # Add new answer to questions array $scope.editedNode.questions.push newQuestion $scope.removeQuestion = () -> From 31b8254d18889e7f18e6a0fc5476beded8487bdb Mon Sep 17 00:00:00 2001 From: Cay Henning Date: Tue, 19 Dec 2023 14:07:54 -0600 Subject: [PATCH 10/57] Fix player skipping base question on first visit and null reference --- src/src-assets/player-assets/player.coffee | 43 +++++++++++----------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/src/src-assets/player-assets/player.coffee b/src/src-assets/player-assets/player.coffee index 0254716c..a5cb2b23 100644 --- a/src/src-assets/player-assets/player.coffee +++ b/src/src-assets/player-assets/player.coffee @@ -203,28 +203,29 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) mostItems = 0 mostRecentItem = 0 for q in q_data.questions - if q.options.requiredVisits - if $scope.visitedNodes[q_data.id] < q.options.requiredVisits - # If the player hasn't visited this node enough times, skip this question - continue - if q.options.requiredItems && q.options.requiredItems[0] - missingItems = $scope.checkInventory(q.options.requiredItems) - if (missingItems.length > 0) - # If the player doesn't have the required items, skip this question - continue - else - keep = false - recentItem = $scope.getMostRecentItem($scope.inventory, q.options.requiredItems) - if (recentItem >= mostRecentItem) - # Choose the question with the most recent item - mostRecentItem = recentItem - keep = true - if (mostItems < q.options.requiredItems.length) - # Choose the question with the most required items - mostItems = q.options.requiredItems.length - keep = true - if (!keep) + if q.options + if q.options.requiredVisits != undefined + if $scope.visitedNodes[q_data.id] < q.options.requiredVisits || (q.options.requiredVisits > 0 && $scope.visitedNodes[q_data.id] == undefined) + # If the player hasn't visited this node enough times, skip this question + continue + if q.options.requiredItems && q.options.requiredItems[0] + missingItems = $scope.checkInventory(q.options.requiredItems) + if (missingItems.length > 0) + # If the player doesn't have the required items, skip this question continue + else + keep = false + recentItem = $scope.getMostRecentItem($scope.inventory, q.options.requiredItems) + if (recentItem >= mostRecentItem) + # Choose the question with the most recent item + mostRecentItem = recentItem + keep = true + if (mostItems < q.options.requiredItems.length) + # Choose the question with the most required items + mostItems = q.options.requiredItems.length + keep = true + if (!keep) + continue # If the question text contains a string that doesn't pass angular's $sanitize check, it'll fail to display anything # Instead, parse in advance, catch the error, and warn the user that the text was nasty try From 342293a1b3a8179f3007664e9ea22f2160adc98d Mon Sep 17 00:00:00 2001 From: Cay Henning Date: Fri, 5 Jan 2024 10:38:16 -0500 Subject: [PATCH 11/57] Fix question deletion, implement default/conditional question selection, fix modal toggling, alter button text based on node type, update qset --- src/creator.html | 8 +- src/src-assets/creator-assets/creator.scss | 2 +- .../creator-assets/directives.coffee | 32 +++-- src/src-assets/creator-assets/services.coffee | 119 ++++++++++-------- src/src-assets/player-assets/player.coffee | 44 +++++-- 5 files changed, 127 insertions(+), 78 deletions(-) diff --git a/src/creator.html b/src/creator.html index e434c1f5..98f6c5d2 100644 --- a/src/creator.html +++ b/src/creator.html @@ -1577,11 +1577,11 @@

Conditional Question

Conditional Question

Conditional Narrative

Conditional End Text

-

Instructions: Choose to display different text based on the number of times the player has visited this destination.

-

Instructions: Choose to display different text based on the number of times the player has visited this destination and/or what items they have in their inventory. Note that if a player meets the requirements for more than one, then it will display the one with the most required items.

+

Choose to display different narratives based on the number of times the player has visited this destination.

+

Choose to display different narratives based on the number of times the player has visited this destination AND/OR what items they have in their inventory. Note that if a player meets the requirements for more than one, then it will display the one with the most required items and visits.

{{invalidRequiredVisits}}

    -
  • +
  • @@ -1696,7 +1696,7 @@

    Required Items for Question

    + ng-click="newQuestion()">Add {{displayNodeCreation == NARR ? 'Narrative' : (displayNodeCreation == END ? 'End Text' : 'Question')}}
    diff --git a/src/install.yaml b/src/install.yaml index a2259951..05f6eb8f 100755 --- a/src/install.yaml +++ b/src/install.yaml @@ -19,6 +19,7 @@ files: score: is_scorable: Yes score_module: Adventure + score_screen: scoreScreen.html meta_data: features: - Customizable diff --git a/src/scoreScreen.html b/src/scoreScreen.html index 414261c7..b3b39937 100644 --- a/src/scoreScreen.html +++ b/src/scoreScreen.html @@ -1,6 +1,6 @@ - + Adventure Score Screen @@ -9,7 +9,7 @@ - + @@ -21,70 +21,59 @@ -
    -
    -

    FINAL INVENTORY

    -
    -
    -
    - - {{itemSelection[getItemIndex(item)].name}} ({{item.count}}) -
    -
    -
    -
    - -

    - {{itemSelection[getItemIndex(selectedItem)].name}} -

    -
    -

    Description

    -

    {{itemSelection[getItemIndex(selectedItem)].description ? itemSelection[getItemIndex(selectedItem)].description : (itemSelection[getItemIndex(selectedItem)].icon.alt ? itemSelection[getItemIndex(selectedItem)].icon.alt : 'No description')}}

    -

    Count

    -

    {{itemSelection[getItemIndex(selectedItem)].count}}

    -
    -
    -
    -

    RESPONSES:

    -
    -
    -
    -
    The Question
    -
    Your Response
    -
    -
    -
    -
    -

    {{$index + 1}}

    -
    -
    {{row.question}}
    -
    -

    {{row.answer}}

    -
    -

    {{row.feedback}}

    -
    -
    -
    -
    -

    Items Gained

    -
    -
    -

    + {{item.count}}

    - -

    {{itemSelection[getItemIndex(item)].name}}

    -
    -
    -

    Items Lost

    -
    -
    -

    - {{-1*item.count}}

    - -

    {{itemSelection[getItemIndex(item)].name}}

    -
    -
    -
    -
    -
    +
    + +

    Review Your Journey

    + +
    +
    + + multiple choice icon + + + short answer icon + + + hotspot icon + + + narrative icon + + + end point icon + +
    + {{row.question}} +
    +
    + {{row.answer}} +
    +
    +

    {{ row.text }}

    +
    +

    {{ row.score }}%

    +
    +
    +
    +
    +
    + + {{ getItemById(item.id).name }} + ({{ item.count }}) +
    +
    + + {{ getItemById(item.id).name }} + ({{ item.count }}) +
    +
    +
    +
    +
    diff --git a/src/src-assets/creator-assets/creator.scss b/src/src-assets/creator-assets/creator.scss index 7745305b..499dd18c 100644 --- a/src/src-assets/creator-assets/creator.scss +++ b/src/src-assets/creator-assets/creator.scss @@ -1443,6 +1443,11 @@ required-items-tooltip { min-width: 30px; max-width: 30px; padding: 0; + + &.wide { + max-width: none; + padding: 5px 10px; + } } } diff --git a/src/src-assets/creator-assets/directives.coffee b/src/src-assets/creator-assets/directives.coffee index 5370b8a8..2851627f 100644 --- a/src/src-assets/creator-assets/directives.coffee +++ b/src/src-assets/creator-assets/directives.coffee @@ -996,6 +996,7 @@ angular.module "Adventure" $scope.showItemIconSelector = true # Make sure Item Editor is open too $scope.editingIndex = index + $scope.currentItem = $scope.inventoryItems[index] $scope.handleIconClick = (icon) -> diff --git a/src/src-assets/player-assets/player.coffee b/src/src-assets/player-assets/player.coffee index a9e404e2..2c9e7f9f 100644 --- a/src/src-assets/player-assets/player.coffee +++ b/src/src-assets/player-assets/player.coffee @@ -486,7 +486,7 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) # Do stuff when the user submits something in the SA answer box $scope.handleShortAnswerInput = -> - response = $scope.response + response = originalResponse = $scope.response $scope.response = "" # Outer loop - loop through every answer set (index 0 is always [All Other Answers] ) @@ -498,7 +498,6 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) # Loop through each match to see if it matches the recorded response for j in [0...$scope.q_data.answers[i].options.matches.length] - # TODO make matching algo more robust match = $scope.q_data.answers[i].options.matches[j] @@ -547,7 +546,7 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) for answer in $scope.q_data.answers if answer.options.isDefault - $scope.selectedAnswer = response + $scope.selectedAnswer = originalResponse _logProgress() # Log the response link = ~~answer.options.link @@ -631,8 +630,7 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) # Submit the user's response to the logs _logProgress = -> - if $scope.selectedAnswer isnt null # TODO is this check required?? - Materia.Score.submitQuestionForScoring $scope.question.materiaId, $scope.selectedAnswer + Materia.Score.submitQuestionForScoring $scope.question.materiaId, $scope.selectedAnswer _end = -> if $scope.scoringDisabled diff --git a/src/src-assets/score-assets/score.coffee b/src/src-assets/score-assets/score.coffee index 263e5748..7b065ae9 100644 --- a/src/src-assets/score-assets/score.coffee +++ b/src/src-assets/score-assets/score.coffee @@ -1,83 +1,68 @@ angular.module('AdventureScorescreen', ['ngSanitize']) -## CONTROLLER ## +.controller 'AdventureScoreCtrl', ['$scope','$sanitize', '$sce', '$timeout', ($scope, $sanitize, $sce, $timeout) -> -## UNFINISHED ## -.controller 'AdventureScoreCtrl', ['$scope','$sanitize', '$sce', ($scope, $sanitize, $sce) -> - - materiaCallbacks = {} + _getHeight = () -> + Math.ceil(parseFloat(window.getComputedStyle(document.querySelector('html')).height)) $scope.inventory = [] $scope.responses = [] $scope.itemSelection = [] $scope.customTable = false - $scope.setSelectedItem = (item) -> - $scope.selectedItem = item - - $scope.getItemIndex = (item) -> - if (item) - for i, index in $scope.itemSelection - if i.id is item.id - return index - $scope.getQuestion = (qset, id) -> for i in qset.items if i.id is id return i return -1 - $scope.createInventoryFromResponses = (qset, responses) -> - inventory = [] + $scope.getItemById = (id) -> + for item in $scope.inventory + if item.id == id then return item + null - for r, index in responses - for responseItem in $scope.getQuestion(qset, r.data[1]).options.items - itemPresent = false - for item in inventory - if item.id is responseItem.id - item.count += responseItem.count - itemPresent = true - if !itemPresent - inventory.push(responseItem) - - return inventory + $scope.getItemUrl = (id) -> + for item in $scope.inventory + if item.id == id && item.icon then return item.icon.url $scope.createTable = (qset, scoreTable) -> table = [] for response in scoreTable - items = $scope.getQuestion(qset, response.data[1]).options.items - row = - question: response.data[0] - answer: response.data[2] - feedback: response.feedback - items: $scope.getQuestion(qset, response.data[1]).options.items - gainedItems: if items.some((i) => i.count > 0) then true else false - lostItems: if items.some((i) => i.count < 0) then true else false - table.push(row) + if response.type == 'SCORE_FINAL_FROM_CLIENT' + row = + text: response.data[0] + score: response.data[1] + type: 'end' + table.push row + else + question = $scope.getQuestion qset, response.id + console.log(question) + items = question.options.items + row = + question: response.data[0] + answer: response.data[1] + type: question.options.type + feedback: response.feedback + items: question.options.items + gainedItems: if items.some((i) => i.count > 0) then true else false + lostItems: if items.some((i) => i.count < 0) then true else false + table.push row return table + $scope.start = (instance, qset, scoreTable, isPreview, qsetVersion) -> + $scope.update(qset, scoreTable) - $scope.toggleInventoryDrawer = () -> - $scope.showInventory = !$scope.showInventory - - materiaCallbacks.start = (instance, qset, scoreTable, isPreview, qsetVersion) -> + $scope.update = (qset, scoreTable) -> $scope.$apply -> - # console.log(instance) - # console.log(qset) - # console.log(scoreTable) - # $scope.inventory = $scope.createInventoryFromResponses(qset, scoreTable) - # $scope.itemSelection = qset.options.inventoryItems || [] - # $scope.table = $scope.createTable(qset, scoreTable) - # console.log($scope.inventory) - # console.log($scope.itemSelection) - # console.log($scope.table) - - $scope.customTable = false - - # Materia.ScoreCore.hideResultsTable() - - return Materia.ScoreCore.start materiaCallbacks - -] - -angular.bootstrap(document, ['AdventureScorescreen']) \ No newline at end of file + $scope.table = $scope.createTable(qset, scoreTable) + $scope.inventory = if qset.options and qset.options.inventoryItems then qset.options.inventoryItems else [] + console.log $scope.table + console.log $scope.inventory + + Materia.ScoreCore.setHeight(_getHeight()) + + Materia.ScoreCore.hideResultsTable() + + Materia.ScoreCore.start $scope + +] \ No newline at end of file diff --git a/src/src-assets/score-assets/score.scss b/src/src-assets/score-assets/score.scss index 9808bf1e..e702b34e 100644 --- a/src/src-assets/score-assets/score.scss +++ b/src/src-assets/score-assets/score.scss @@ -21,6 +21,12 @@ $red-lightest: #ffd7d7; $yellow: #fef6b1; $shadow-dialog: 0px 3px 3px $gray; +$color-mc: #60b973; +$color-sa: #6ea0aa; +$color-hotspot: #ba83c7; +$color-narr: #ffc656; +$color-end: $red; + .arrow-top { border-left: 10px solid transparent; border-right: 10px solid transparent; @@ -29,234 +35,342 @@ $shadow-dialog: 0px 3px 3px $gray; img { - width: 35px; - height: 35px; + width: 35px; + height: 35px; } body { - font-family: 'Lato', arial, serif; - color: $gray-darkest; + font-family: 'Lato', arial, serif; + color: $gray-darkest; div.content-frame { width: 100%; + padding: 0.5em 0; - .inventory { - width: 750px; - height: 100%; - margin: auto; - border-radius: 10px; - box-shadow: 0px 3px 5px 0px #333; - - .inventory-header { - font-size: 17px; - text-transform: uppercase; - font-weight: bold; - padding: 15px; - background-color: $gray-lightest; - margin: 0; - margin-top: 10px; - border-top-left-radius: 10px; - border-top-right-radius: 10px; - display: flex; - flex-direction: row; - align-items: center; - justify-content: space-between; - - &:hover { - cursor: pointer; - } - - &.closed { - border-radius: 10px; - } - } - .inventory-content { - display: flex; - flex-direction: row; - align-items: stretch; - height: 100%; - background-color: $gray-lighter; - - border-bottom-left-radius: 10px; - border-bottom-right-radius: 10px; - - .inventory-items { - display: flex; - flex-direction: row; - flex-wrap: wrap; - align-content: flex-start; - flex-grow:2; - height: 247px; - overflow: auto; - - div.item { - padding: 15px; - margin: 15px; - border-radius: 20px; - max-height: 25px; - - position: relative; - - display: flex; - flex-direction: row; - align-items: center; - grid-gap: 10px; - - border: 2px solid $gray-lightest; - - img { - width: 35px; - height: 35px; - object-fit: cover; - } - } - - div.item:hover { - box-shadow: $shadow-dialog; - cursor: pointer; - } - } - - .item-description { - flex-grow: 1; - min-width: 200px; - border-bottom-right-radius: 10px; - margin: 10px; - - h4 { - margin: 0 0 10px 0; - padding-left: 10px; - border-bottom: 1px solid $gray; - font-size: 14px; - padding: 10px; - } - - p { - margin-top: 0; - font-size: 12px; - } - - .item-header { - display: flex; - flex-direction: row; - grid-gap: 20px; - align-items: center; - - background: $gray-lightest; - margin: 0; - padding: 10px; - border-radius: 10px; - - img { - width: 35px; - height: 35px; - object-fit: cover; - } - - h3 { - margin: 0; - font-size: 17px; - } - } - - p { - padding: 0 10px; - } - } - } - } - - .responses-heading { - text-align: center; - color: $gray-lightest; - font-size: 17px; - } - - .table { - border-radius: 10px; - margin: auto; - width: 750px; - text-align: center; - - .table-header { - background-color: $gray-lightest; - border-top-left-radius: 10px; - border-top-right-radius: 10px; - padding: 10px 0; - } - - .table-header, .row-details { - display: grid; - grid-template-columns: 200px 300px 250px; - } - - .row-details { - background-color: $gray-lighter; - box-shadow: 0px 10px 5px 0px $gray-darkest; - - .row-number { - display: flex; - justify-content: center; - align-content: center; - align-items: center; - - p { - border: 1px $gray solid; - border-radius: 50%; - width: 35px; - height: 35px; - display: flex; - justify-content: center; - align-items: center; - } - } - - .row-number, .row-question, .row-answer { - border: 1px solid $gray; - padding: 10px; - } - } - - .row-feedback { - margin: 0 25px; - background-color: $yellow; - padding: 10px; - border-radius: 10px; - - p { - margin: 0; - } - } - - .row-items { - margin: 0 35px 10px 35px; - background-color: $gray; - padding: 10px; - box-shadow: 0px 10px 5px 0px $gray-darkest; - - h4 { - margin: 0; - } - - .row-items-gained, .row-items-lost, .row-item { - display: flex; - flex-direction: row; - gap: 40px; - align-items: center; - flex-wrap: wrap; - justify-content: center; - background-color: $gray; - border-radius: 10px; - - } - - .row-item { - gap: 5px; - } - } - - } - } + border-radius: 5px; + background: #fff; + h2 { + text-align: center; + color: $gray-darkest; + } + + .inventory { + width: 750px; + height: 100%; + margin: auto; + border-radius: 10px; + box-shadow: 0px 3px 5px 0px #333; + + .inventory-header { + font-size: 17px; + text-transform: uppercase; + font-weight: bold; + padding: 15px; + background-color: $gray-lightest; + margin: 0; + margin-top: 10px; + border-top-left-radius: 10px; + border-top-right-radius: 10px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + + &:hover { + cursor: pointer; + } + + &.closed { + border-radius: 10px; + } + } + .inventory-content { + display: flex; + flex-direction: row; + align-items: stretch; + height: 100%; + background-color: $gray-lighter; + + border-bottom-left-radius: 10px; + border-bottom-right-radius: 10px; + + .inventory-items { + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-content: flex-start; + flex-grow:2; + height: 247px; + overflow: auto; + + div.item { + padding: 15px; + margin: 15px; + border-radius: 20px; + max-height: 25px; + + position: relative; + + display: flex; + flex-direction: row; + align-items: center; + grid-gap: 10px; + + border: 2px solid $gray-lightest; + img { + width: 35px; + height: 35px; + object-fit: cover; + } + } + + div.item:hover { + box-shadow: $shadow-dialog; + cursor: pointer; + } + } + + .item-description { + flex-grow: 1; + min-width: 200px; + border-bottom-right-radius: 10px; + margin: 10px; + + h4 { + margin: 0 0 10px 0; + padding-left: 10px; + border-bottom: 1px solid $gray; + font-size: 14px; + padding: 10px; + } + + p { + margin-top: 0; + font-size: 12px; + } + + .item-header { + display: flex; + flex-direction: row; + grid-gap: 20px; + align-items: center; + + background: $gray-lightest; + margin: 0; + padding: 10px; + border-radius: 10px; + + img { + width: 35px; + height: 35px; + object-fit: cover; + } + + h3 { + margin: 0; + font-size: 17px; + } + } + + p { + padding: 0 10px; + } + } + } + } + + section.timeline { + + color: #000; + + div.destination { + position: relative; + margin: 5px 16px 10px 32px; + padding: 10px 15px 15px 32px; + + background: $white; + border: solid 1px $gray-lightest; + border-left: solid 18px $gray-lightest; + + box-shadow: 4px 4px 6px #ccc; + + &:after { + content: ''; + position: absolute; + z-index: 0; + left: 12px; + bottom: calc(-2em - 1px); + display: block; + height: 2em; + width: 1px; + border-left: solid 1px $gray; + } + + &.end { + margin-top: 30px; + + &:before { + content: 'Where Did You End Up?'; + position: absolute; + top: -18px; + left: 0px; + width: 100%; + text-align: center; + + font-weight: 700; + font-size: 0.8em; + } + + &:after { + display: none; + } + + .end-row-score { + + &:before { + content: 'Your Score:'; + display: block; + font-size: 12px; + font-style: italic; + color: $gray-darker; + } + + display: inline-block; + + p { + margin: 0.20em 0; + font-size: 2em; + font-weight: 700; + } + + } + } + + span.icon { + position: absolute; + top: 50%; + left: 0px; + display: block; + + width: 36px; + height: 36px; + margin-top: -18px; + margin-left: -30px; + + background: $white; + border-radius: 9px; + + &.mc { + border: solid 2px $color-mc; + } + + &.sa { + border: solid 2px $color-sa; + } + + &.hotspot { + border: solid 2px $color-hotspot; + } + + &.narr { + border: solid 2px $color-narr; + } + + &.end { + border: solid 2px $color-end; + } + + // box-shadow: #999 1px 1px 3px; + } + + div.question { + position: relative; + margin: 24px 0; + + &:before { + content: 'Question:'; + position: absolute; + top: -18px; + left: 2px; + + font-size: 12px; + font-style: italic; + + color: $gray-darker; + } + + &.narrative { + &:before { + content: 'Text:'; + } + } + } + + div.response { + position: relative; + margin-top: 12px; + + &:before { + content: 'You Responded:'; + position: absolute; + top: -18px; + left: 2px; + + font-size: 12px; + font-style: italic; + + color: $gray-darker; + } + } + + div.items { + padding-top: 0.5em; + border-top: solid 1px $gray-lightest; + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + gap: 0.5em; + + .item { + display: inline-block; + + &:before { + display: block; + font-size: 12px; + font-style: italic; + color: $gray-darker; + } + + &.item-received:before { + content: 'Received:'; + } + + &.item-taken:before { + content: 'Taken:'; + } + + .received, .taken { + display: flex; + flex-direction: row; + align-items: center; + gap: 0.5em; + + .item-icon { + width: auto; + height: 1em; + } + + .quantity { + color: $gray; + font-size: 12px; + } + } + } + } + } + } + } } \ No newline at end of file From 46b6c29ea36e8dd067a81651a8205cd0926d46b2 Mon Sep 17 00:00:00 2001 From: Corey Peterson Date: Tue, 20 Feb 2024 16:33:12 -0500 Subject: [PATCH 13/57] Enhanced support for hotspot display in score screens --- src/_score/score_module.php | 1 + src/scoreScreen.html | 43 ++++++++++++++++++ src/src-assets/player-assets/player.coffee | 14 +++--- src/src-assets/score-assets/score.coffee | 13 ++++++ src/src-assets/score-assets/score.scss | 53 ++++++++++++++++++++++ 5 files changed, 118 insertions(+), 6 deletions(-) diff --git a/src/_score/score_module.php b/src/_score/score_module.php index ae4f2b1c..e4025463 100755 --- a/src/_score/score_module.php +++ b/src/_score/score_module.php @@ -82,6 +82,7 @@ protected function details_for_question_answered($log) 'data' => [ $this->get_ss_question($log, $q), $this->get_ss_answer($log, $q), + $log->value ], 'data_style' => ['question', 'response'], 'score' => $score, diff --git a/src/scoreScreen.html b/src/scoreScreen.html index b3b39937..8225c394 100644 --- a/src/scoreScreen.html +++ b/src/scoreScreen.html @@ -48,6 +48,49 @@

    Review Your Journey

    {{row.answer}}
    +
    +

    Historical play records don't provide enough information to display the hotspot selection associated with this answer choice.

    +
    +
    + + + + + + + + + + +

    {{ row.text }}

    diff --git a/src/src-assets/player-assets/player.coffee b/src/src-assets/player-assets/player.coffee index 2c9e7f9f..3355a63e 100644 --- a/src/src-assets/player-assets/player.coffee +++ b/src/src-assets/player-assets/player.coffee @@ -458,6 +458,7 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) if link is -1 then return _end() $scope.selectedAnswer = $scope.q_data.answers[index].text + selectedAnswerId = $scope.q_data.answers[index].id requiredItems = $scope.answers[index].requiredItems || $scope.answers[index].options.requiredItems @@ -473,9 +474,10 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) $scope.hotspotLabelTarget.show = false $scope.hotspotLabelTarget.x = null $scope.hotspotLabelTarget.y = null - - # record the answer - _logProgress() + _logProgress(selectedAnswerId) + else + # record the answer + _logProgress() if $scope.q_data.answers[index].options.feedback $scope.feedback = $scope.q_data.answers[index].options.feedback @@ -628,9 +630,9 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) # $scope.link = $scope.question.options.parentId # Submit the user's response to the logs - _logProgress = -> - - Materia.Score.submitQuestionForScoring $scope.question.materiaId, $scope.selectedAnswer + _logProgress = (answerId = undefined) -> + if answerId != undefined then Materia.Score.submitQuestionForScoring $scope.question.materiaId, $scope.selectedAnswer, answerId + else Materia.Score.submitQuestionForScoring $scope.question.materiaId, $scope.selectedAnswer _end = -> if $scope.scoringDisabled diff --git a/src/src-assets/score-assets/score.coffee b/src/src-assets/score-assets/score.coffee index 7b065ae9..5f34a8a4 100644 --- a/src/src-assets/score-assets/score.coffee +++ b/src/src-assets/score-assets/score.coffee @@ -10,6 +10,10 @@ angular.module('AdventureScorescreen', ['ngSanitize']) $scope.itemSelection = [] $scope.customTable = false + getAnswerById = (question, id) -> + for answer in question.answers + if answer.id is id then return answer + $scope.getQuestion = (qset, id) -> for i in qset.items if i.id is id @@ -46,6 +50,15 @@ angular.module('AdventureScorescreen', ['ngSanitize']) items: question.options.items gainedItems: if items.some((i) => i.count > 0) then true else false lostItems: if items.some((i) => i.count < 0) then true else false + + if question.options.type is 'hotspot' + + if response.data[2] + answer = getAnswerById(question, response.data[2]) + if answer + row.svg = answer.options.svg + row.image = question.options.asset.url + table.push row return table diff --git a/src/src-assets/score-assets/score.scss b/src/src-assets/score-assets/score.scss index e702b34e..f7eae63c 100644 --- a/src/src-assets/score-assets/score.scss +++ b/src/src-assets/score-assets/score.scss @@ -208,6 +208,59 @@ body { border-left: solid 1px $gray; } + &.hotspot { + + .hotspot-content { + position: relative; + width: 698px; + height: 400px; + + margin: -0.75em; + + text-align: center; + + transform: scale(0.75); + + &:before { + content: 'Your hotspot selection:'; + position: absolute; + top: -1.5em; + left: 50%; + width: 400px; + margin-left: -200px; + font-style: italic; + color: $gray-darker; + } + + svg { + position: absolute; + z-index: 10; + display: block; + left: 0; + top: 0; + width: 698px; + height: 400px; + } + + img { + position: relative; + display: block; + width: auto; + height: 400px; + + margin-left: auto; + margin-right: auto; + } + } + + .hotspot-no-svg-content { + margin: 1em 0 0.5em 0; + color: $gray-darker; + font-style: italic; + font-size: 12px; + } + } + &.end { margin-top: 30px; From 5456fb3672430c30a7c1ef905aa42a782f05c8f0 Mon Sep 17 00:00:00 2001 From: Corey Peterson Date: Thu, 22 Feb 2024 11:05:27 -0500 Subject: [PATCH 14/57] Added logic to simulate item acquisition/removal more accurately in score screen --- src/src-assets/score-assets/score.coffee | 85 ++++++++++++++++++++---- 1 file changed, 72 insertions(+), 13 deletions(-) diff --git a/src/src-assets/score-assets/score.coffee b/src/src-assets/score-assets/score.coffee index 5f34a8a4..8065f44f 100644 --- a/src/src-assets/score-assets/score.coffee +++ b/src/src-assets/score-assets/score.coffee @@ -2,18 +2,78 @@ angular.module('AdventureScorescreen', ['ngSanitize']) .controller 'AdventureScoreCtrl', ['$scope','$sanitize', '$sce', '$timeout', ($scope, $sanitize, $sce, $timeout) -> - _getHeight = () -> - Math.ceil(parseFloat(window.getComputedStyle(document.querySelector('html')).height)) - - $scope.inventory = [] $scope.responses = [] $scope.itemSelection = [] $scope.customTable = false - getAnswerById = (question, id) -> + _visitedNodes = [] + _qsetItems = [] + _currentInventory = [] + + _getHeight = () -> + Math.ceil(parseFloat(window.getComputedStyle(document.querySelector('html')).height)) + + _getAnswerById = (question, id) -> for answer in question.answers if answer.id is id then return answer + # in order to accurately simulate the items received and taken, we have to recreate item handling logic in the score screen + # simply using the options.items value for each question would not accurately report items taken and received + # if certain factors are at play, like the takeAll and firstVisitOnly flags + _manageItemDelta = (question) -> + + items = [] + + for item in question.options.items + if item.firstVisitOnly and _visitedNodes.includes(question.options.id) then continue + + # positive delta? Add the item to the inventory, or increase the count if it's in there already + if item.count > 0 + + previouslyExists = false + # if the item type is already in the inventory, increase the count + for inventoryItem in _currentInventory + if inventoryItem.id is item.id + inventoryItem.count += item.count + previouslyExists = true + break + + # add the item to the inventory, since it wasn't there already + if !previouslyExists then _currentInventory.push angular.copy item + + items.push item + + # negative delta? remove it from the inventory, if present + else if item.count < 0 + + for inventoryItem in _currentInventory + if !inventoryItem then continue + + if inventoryItem.id is item.id + itemRemoved = angular.copy item + # takeAll ignores the count value - remove the item from the inventory altogether + # instead of using item.count, the delta is whatever the current inventory value is, zeroed out + if item.takeAll + itemRemoved.count = inventoryItem.count * -1 + + _currentInventory.splice(_currentInventory.indexOf(inventoryItem), 1) + + else + # inventoryItem will persist because the quantity removed is less than the total + if inventoryItem.count > item.count * -1 + itemRemoved.count = item.count + inventoryItem.count += item.count + else + # remove the item completely, and the quantity removed will be some value less than item.count + itemRemoved.count = (inventoryItem.count % item.count) * -1 + _currentInventory.splice(_currentInventory.indexOf(inventoryItem), 1) + + items.push itemRemoved + + _visitedNodes.push question.options.id + if _currentInventory[0] then console.log 'the current cash count is ' + _currentInventory[0].count + return items + $scope.getQuestion = (qset, id) -> for i in qset.items if i.id is id @@ -21,12 +81,12 @@ angular.module('AdventureScorescreen', ['ngSanitize']) return -1 $scope.getItemById = (id) -> - for item in $scope.inventory + for item in _qsetItems if item.id == id then return item null $scope.getItemUrl = (id) -> - for item in $scope.inventory + for item in _qsetItems if item.id == id && item.icon then return item.icon.url $scope.createTable = (qset, scoreTable) -> @@ -40,26 +100,26 @@ angular.module('AdventureScorescreen', ['ngSanitize']) table.push row else question = $scope.getQuestion qset, response.id - console.log(question) items = question.options.items row = question: response.data[0] answer: response.data[1] type: question.options.type feedback: response.feedback - items: question.options.items + items: _manageItemDelta question gainedItems: if items.some((i) => i.count > 0) then true else false lostItems: if items.some((i) => i.count < 0) then true else false if question.options.type is 'hotspot' if response.data[2] - answer = getAnswerById(question, response.data[2]) + answer = _getAnswerById(question, response.data[2]) if answer row.svg = answer.options.svg row.image = question.options.asset.url table.push row + return table $scope.start = (instance, qset, scoreTable, isPreview, qsetVersion) -> @@ -68,9 +128,8 @@ angular.module('AdventureScorescreen', ['ngSanitize']) $scope.update = (qset, scoreTable) -> $scope.$apply -> $scope.table = $scope.createTable(qset, scoreTable) - $scope.inventory = if qset.options and qset.options.inventoryItems then qset.options.inventoryItems else [] - console.log $scope.table - console.log $scope.inventory + _currentInventory = [] + _qsetItems = if qset.options and qset.options.inventoryItems then qset.options.inventoryItems else [] Materia.ScoreCore.setHeight(_getHeight()) From 1504578181f1a916f0eb4d461b2a5941215d5d09 Mon Sep 17 00:00:00 2001 From: Corey Peterson Date: Wed, 28 Feb 2024 13:58:41 -0500 Subject: [PATCH 15/57] tweaks to conditional questions UI. Support for conditional question text mostly working in score screen. --- src/creator.html | 8 ++-- src/src-assets/creator-assets/creator.scss | 45 ++++++++++++------ src/src-assets/score-assets/score.coffee | 55 ++++++++++++++++++++-- 3 files changed, 85 insertions(+), 23 deletions(-) diff --git a/src/creator.html b/src/creator.html index 916cc03b..dc402d73 100644 --- a/src/creator.html +++ b/src/creator.html @@ -1572,13 +1572,14 @@

    Take Player Items

    -

    Conditional Question

    +

    Conditional Question Text

    Conditional Question

    Conditional Question

    Conditional Narrative

    Conditional End Text

    -

    Choose to display different narratives based on the number of times the player has visited this destination.

    -

    Choose to display different narratives based on the number of times the player has visited this destination AND/OR what items they have in their inventory. Note that if a player meets the requirements for more than one, then it will display the one with the most required items and visits.

    +

    Question text can be overridden based on the number of times the player has visited this destination.

    +

    Question text can be overridden based on the number of times the player has visited this destination AND/OR what items they have in their inventory. Note that if a player meets the requirements for more than one, then it will display the one with the most required items and visits.

    +

    Conditional questions are evaluated from top to bottom. If multiple question override requirements are met, the first question to meet the requirements will be selected.

    {{invalidRequiredVisits}}

    • @@ -1595,7 +1596,6 @@

      Conditional End Text

      -
      {{question.requiredItems[0] ? 'Edit Required Items' : 'No Required Items'}} diff --git a/src/src-assets/creator-assets/creator.scss b/src/src-assets/creator-assets/creator.scss index 499dd18c..0f5c4f99 100644 --- a/src/src-assets/creator-assets/creator.scss +++ b/src/src-assets/creator-assets/creator.scss @@ -1182,7 +1182,7 @@ required-items-tooltip { } &.sub-heading { - justify-content: start; + justify-content: flex-start; h4 { margin: 1em 0; @@ -1249,7 +1249,7 @@ required-items-tooltip { .row { display: flex; flex-direction: row; - justify-content: end; + justify-content: flex-end; } img { @@ -1322,7 +1322,7 @@ required-items-tooltip { .blank-selection { width: 100%; height: 2em; - justify-content: end; + justify-content: flex-end; background-color: $gray-lighter; border-radius: 10px; @@ -1478,7 +1478,7 @@ required-items-tooltip { .advanced-option-bar { display: flex; - align-items: end; + align-items: flex-end; flex-direction: row; justify-content: space-between; margin-top: 10px; @@ -1893,16 +1893,16 @@ item-icon-selector { display: flex; gap: 5px; flex-wrap: wrap; - justify-content: end; + justify-content: flex-end; flex-direction: column; padding: 0 10px; - align-content:end; + align-content: flex-end; min-width: 150px; max-width: 150px; &.flex-start { - justify-content: start; - align-content:start; + justify-content: flex-start; + align-content: flex-start; } .item-minimum-quantity, .item-quantity { @@ -2584,8 +2584,7 @@ node-creation-selection-dialog { } .question-box { - width: $edit-popup-standard-question-width; // 80% - flex-grow: 2; + width: 100%; max-height: 300px; box-sizing: border-box; height: 100%; @@ -2658,8 +2657,8 @@ node-creation-selection-dialog { .question-media-box { display: flex; flex-direction: row; + justify-content: space-between; align-items: stretch; - // flex-grow: 1; margin: $edit-popup-padding; grid-gap: $edit-popup-padding; @@ -2703,6 +2702,10 @@ node-creation-selection-dialog { &.tiny { margin: 5px; } + + .question-editor { + flex-grow: 2; + } } .media-box { @@ -3811,10 +3814,18 @@ advanced-question-editor { justify-content: space-between; > p { - padding: 1em; + margin: 0.5em 1em; color: $gray-darker; - font-style: italic; + font-size: 0.9em; + + &:nth-child(2) { + margin-top: 1em; + } + + &:nth-child(3) { + margin-bottom: 1em; + } &.invalid-quantity-message { padding: 0; @@ -3842,7 +3853,7 @@ advanced-question-editor { > li { width: 99%; - padding: 5px; + padding: 5px 20px; box-sizing: border-box; border-radius: 5px; display: flex; @@ -3867,6 +3878,12 @@ advanced-question-editor { justify-content: center; align-items: center; + label { + margin-bottom: 1em; + font-size: 0.8em; + font-weight: bold; + } + input { height: 2em; width: 75px; diff --git a/src/src-assets/score-assets/score.coffee b/src/src-assets/score-assets/score.coffee index 8065f44f..b2d20103 100644 --- a/src/src-assets/score-assets/score.coffee +++ b/src/src-assets/score-assets/score.coffee @@ -13,10 +13,50 @@ angular.module('AdventureScorescreen', ['ngSanitize']) _getHeight = () -> Math.ceil(parseFloat(window.getComputedStyle(document.querySelector('html')).height)) + _getNodeVisitCount = (id) -> + + for node in _visitedNodes + if node.id is id then return node.count + + return 0 + + _getItemInInventory = (id) -> + for item in _currentInventory + if item.id is id then return item + + return null + _getAnswerById = (question, id) -> for answer in question.answers if answer.id is id then return answer + _manageConditionalQuestion = (question, response) -> + if question.options.additionalQuestions + + for option in question.options.additionalQuestions + + match = true + + # meets required visits check? + if option.requiredVisits > 0 + if _getNodeVisitCount(question.options.id) < option.requiredVisits then match = false + + # does the contextual question require items? + if option.requiredItems.length > 0 + + for item in option.requiredItems + inventoryItem = _getItemInInventory item.id + if inventoryItem != null + # user currently has the item by id, check if count is out of bounds + if inventoryItem.count < item.minCount or (inventoryItem.count > item.maxCount and item.uncappedMax is false) then match = false + else match = false # user does not have the required item + + + if match is true then return option.text + + return response + + # in order to accurately simulate the items received and taken, we have to recreate item handling logic in the score screen # simply using the options.items value for each question would not accurately report items taken and received # if certain factors are at play, like the takeAll and firstVisitOnly flags @@ -25,7 +65,8 @@ angular.module('AdventureScorescreen', ['ngSanitize']) items = [] for item in question.options.items - if item.firstVisitOnly and _visitedNodes.includes(question.options.id) then continue + # if item.firstVisitOnly and _visitedNodes.includes(question.options.id) then continue + if item.firstVisitOnly and _getNodeVisitCount(question.options.id) > 0 then continue # positive delta? Add the item to the inventory, or increase the count if it's in there already if item.count > 0 @@ -70,8 +111,12 @@ angular.module('AdventureScorescreen', ['ngSanitize']) items.push itemRemoved - _visitedNodes.push question.options.id - if _currentInventory[0] then console.log 'the current cash count is ' + _currentInventory[0].count + if _getNodeVisitCount(question.options.id) > 0 + for node in _visitedNodes + if node.id is question.options.id then node.count++ + else _visitedNodes.push + id: question.options.id + count: 1 return items $scope.getQuestion = (qset, id) -> @@ -94,7 +139,7 @@ angular.module('AdventureScorescreen', ['ngSanitize']) for response in scoreTable if response.type == 'SCORE_FINAL_FROM_CLIENT' row = - text: response.data[0] + text: response.data[0] # needs to work with conditional questions score: response.data[1] type: 'end' table.push row @@ -102,7 +147,7 @@ angular.module('AdventureScorescreen', ['ngSanitize']) question = $scope.getQuestion qset, response.id items = question.options.items row = - question: response.data[0] + question: _manageConditionalQuestion question, response.data[0] answer: response.data[1] type: question.options.type feedback: response.feedback From 10272fd1baf4600139a3d8583095cc0d1604c5f3 Mon Sep 17 00:00:00 2001 From: Cay Henning Date: Wed, 13 Mar 2024 09:00:54 -0400 Subject: [PATCH 16/57] Reword advanced question editor --- src/creator.html | 9 ++++++--- src/src-assets/creator-assets/creator.scss | 15 ++++----------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/creator.html b/src/creator.html index 98f6c5d2..37dde746 100644 --- a/src/creator.html +++ b/src/creator.html @@ -1577,9 +1577,12 @@

      Conditional Question

      Conditional Question

      Conditional Narrative

      Conditional End Text

      -

      Choose to display different narratives based on the number of times the player has visited this destination.

      -

      Choose to display different narratives based on the number of times the player has visited this destination AND/OR what items they have in their inventory. Note that if a player meets the requirements for more than one, then it will display the one with the most required items and visits.

      -

      {{invalidRequiredVisits}}

      +
      +

      Choose to display different narratives based on the number of times the player has visited this destination.

      +

      Choose to display different narratives based on the player's number of visits to this destination and/or what items they have in their inventory.

      +

      If a player meets the requirements for more than one, then it will choose the one they most recently met the requirements for.

      +

      {{invalidRequiredVisits}}

      +
      • + ng-click="newQuestion()">Add {{displayNodeCreation == NARR ? 'Narrative Text' : (displayNodeCreation == END ? 'End Text' : 'Question Text')}} -
        -
        +
        +
        Add Item
        -
        - - -
        - - +
        +
        + + +
        +
        + + +
        +
        -
      • @@ -1116,239 +1114,7 @@

        Required Items for Hotspot

    -
-
- -
-
-
-

Items for {{integerToLetters(editedNode.id)}}

- -
- Select an item via the drop-down, then choose whether the item will be given to the player or taken from them. -

{{invalidQuantity}}

-

{{invalidMaxQuantity}}

-
-
-
-
-
-
- - -
-

Give Player Items

-
-
-
    -
  • -
    -
    - -
    -
    -
    - {{inventoryItems[getItemIndex(item)].name}} -
    -
    -
    - -
    - Quantity - -
    -
    - -
    -
    - - -
    -
  • -
-
-
-
-
-
- - -
-

Take Player Items

-
-
-
    -
  • -
    -
    - -
    -
    -
    - {{inventoryItems[getItemIndex(item)].name}} -
    -
    -
    -
    - Quantity - -
    -
    - -
    -
    -
    - - -
    -
    - - -
    -
    -
  • -
-
-
-
-
-
-
- -
-
-
- - {{inventoryItems[getItemIndex(selectedItem)].name}} -
- -
-
-
- - {{item.name}} -
-
-
- - -
-
- - -
-
-
- -
-
-
-

Required Items for Answer

- -
-

The player must have these items in their inventory to select this answer.

-

{{invalidQuantity}}

-

{{invalidMaxQuantity}}

-
-
    -
  • -
    - -
    -
    -
    - {{inventoryItems[getItemIndex(item)].name}} -
    -
    -
    -
    -
    - - - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -

    Player is not required to have any amount of this item.

    -

    Player must have 0 or up to {{item.tempMaxCount}} of this item.

    -

    Player must have a minimum of {{item.tempMinCount}} of this item

    -

    Player must have between {{item.tempMinCount}} and {{item.tempMaxCount}} of this item.

    -

    Player must have exactly {{item.tempMinCount}} of this item.

    -

    Player must not have this item.

    -
    -
    -
    - -
  • -
-
-
-
-
- -
-
-
- - {{inventoryItems[getItemIndex(selectedItem)].name}} -
- -
-
-
- - {{item.name}} -
-
-
- -
-
-
- - -
-
-
+
diff --git a/src/player.html b/src/player.html index 9c780a8d..3d5ee740 100644 --- a/src/player.html +++ b/src/player.html @@ -217,7 +217,7 @@

{{title}}

data-link="{{answer.link}}" tabindex="0"> {{ answer.text.length > 0 ? answer.text : 'No answer text provided' }} -
+
{{itemSelection[getItemIndex(item.id)].name}} ({{item.range}}) diff --git a/src/src-assets/creator-assets/creator.scss b/src/src-assets/creator-assets/creator.scss index df680250..5d3fb740 100644 --- a/src/src-assets/creator-assets/creator.scss +++ b/src/src-assets/creator-assets/creator.scss @@ -1489,7 +1489,7 @@ required-items-tooltip { align-items: center; flex-direction: row; justify-content: center; - gap: 15px; + gap: 0.5em; padding: 0 10px; input { @@ -1727,11 +1727,10 @@ item-icon-selector { &.required-item-modal.show { display: flex; position: fixed; - height: 63% !important; - width: 56%; - top: 120px; - left: -200px; - right: 0; + top: 15%; + height: 75%; + width: 65%; + max-width: 850px; margin: auto; .arrow-right { @@ -1832,14 +1831,17 @@ item-icon-selector { /** Show modal on screen widths < 1000px **/ @media screen and (max-width: $item-panel-max-width) { - &.item-panel { - display: none !important; - } + // &.item-panel { + // display: none !important; + // } } /** Show two panels on screen widths >= 1000px **/ @media screen and (min-width: $item-panel-max-width) { &.item-modal:not(.question-required) { - display: none !important; + + // height: 100% !important; + // max-height: 100%; + // min-width: 300px; } } .basic-options { @@ -1855,7 +1857,7 @@ item-icon-selector { align-items: center; flex-direction: row; flex-wrap: wrap; - gap: 15px; + gap: 0.5em; padding: 10px 10px 10px 0; input { @@ -1863,6 +1865,16 @@ item-icon-selector { height: 1em; } + .advanced-option-selection { + display: flex; + gap: 0.25em; + align-items: center; + } + + label { + margin-right: 0.5em; + } + button.hide-answer-button { display: flex; flex-direction: row; @@ -2412,10 +2424,9 @@ node-creation-selection-dialog { flex-direction: column; justify-content: space-between; - .node-creation-panels { + .node-creation-content { display: flex; flex-direction: column; - flex-grow: 1; max-height: 80%; padding: 10px; overflow: auto; @@ -2798,6 +2809,7 @@ node-creation-selection-dialog { .hide-answer-tooltip { position: absolute; + z-index: $z-index-modal-dialog + 10; top: 45px; width: 100px; background: $blue; diff --git a/src/src-assets/creator-assets/directives.coffee b/src/src-assets/creator-assets/directives.coffee index 2851627f..d3c66bbe 100644 --- a/src/src-assets/creator-assets/directives.coffee +++ b/src/src-assets/creator-assets/directives.coffee @@ -2497,7 +2497,7 @@ angular.module "Adventure" id: treeSrv.generateAnswerHash() requiredItems: [] hideAnswer: false - # hideRequiredItems: false + hideRequiredItems: false # only used for MC nodes! # Add a matches and case sensitivity property to the answer object if it's a short answer question. if $scope.editedNode.type is $scope.SHORTANS diff --git a/src/src-assets/creator-assets/services.coffee b/src/src-assets/creator-assets/services.coffee index bcee1c89..83be59ca 100644 --- a/src/src-assets/creator-assets/services.coffee +++ b/src/src-assets/creator-assets/services.coffee @@ -752,7 +752,7 @@ angular.module "Adventure" feedback: answer.feedback requiredItems: requiredItemsData hideAnswer: answer.hideAnswer or false - # hideRequiredItems: answer.hideRequiredItems or false + hideRequiredItems: answer.hideRequiredItems or false switch tree.type when "shortanswer" @@ -939,7 +939,7 @@ angular.module "Adventure" id: generateAnswerHash() requiredItems: requiredItemsData hideAnswer: answer.options.hideAnswer or false - # hideRequiredItems: answer.options.hideRequiredItems or false + hideRequiredItems: answer.options.hideRequiredItems or false switch item.options.type when "shortanswer" diff --git a/src/src-assets/player-assets/player.coffee b/src/src-assets/player-assets/player.coffee index 746b4b29..fed553d5 100644 --- a/src/src-assets/player-assets/player.coffee +++ b/src/src-assets/player-assets/player.coffee @@ -343,7 +343,7 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) options : q_data.answers[i].options requiredItems: requiredItems hideAnswer: q_data.answers[i].options.hideAnswer || false - # hideRequiredItems: q_data.answers[i].options.hideRequiredItems || false + hideRequiredItems: q_data.answers[i].options.hideRequiredItems || false if answer.requiredItems[0] $scope.showInventoryBtn = true diff --git a/src/src-assets/player-assets/player.scss b/src/src-assets/player-assets/player.scss index 1ff8ff8c..9891c34f 100644 --- a/src/src-assets/player-assets/player.scss +++ b/src/src-assets/player-assets/player.scss @@ -528,10 +528,10 @@ h1 { display: flex; align-items: center; flex-direction: row; - flex-wrap: wrap; + flex-wrap: nowrap; width: 100%; margin-bottom:10px; - // padding-left: 20px; + padding-right: 0.5em; color:#000000; @@ -576,8 +576,6 @@ h1 { } .answer-text { - max-width: calc(100% - 34px); - &.no-text-provided { font-style: italic; color: #696969; @@ -585,8 +583,10 @@ h1 { } .answer-required-items { + min-width: 120px; font-size: 0.8em; margin-left: auto; + padding-left: 0.5em; &:before { padding: 0 0 0.25em 0; @@ -600,6 +600,7 @@ h1 { display: flex; align-items: center; flex-direction: row; + flex-wrap: wrap; min-width: 75px; max-width: 200px; From e5b9371117829304acb37f011f869a4db3c05b2d Mon Sep 17 00:00:00 2001 From: Corey Peterson Date: Tue, 26 Mar 2024 17:32:16 -0400 Subject: [PATCH 22/57] Addl tweaks to responsive node-creation layout, unselectable answer option styling --- src/src-assets/creator-assets/creator.scss | 39 +++++++++++++--------- src/src-assets/player-assets/player.coffee | 3 +- src/src-assets/player-assets/player.scss | 1 + 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/src-assets/creator-assets/creator.scss b/src/src-assets/creator-assets/creator.scss index 5d3fb740..233f30f7 100644 --- a/src/src-assets/creator-assets/creator.scss +++ b/src/src-assets/creator-assets/creator.scss @@ -2427,6 +2427,7 @@ node-creation-selection-dialog { .node-creation-content { display: flex; flex-direction: column; + flex-grow: 2; max-height: 80%; padding: 10px; overflow: auto; @@ -2451,26 +2452,26 @@ node-creation-selection-dialog { } } - .panel-right { - padding: 10px; - max-width: 550px; + // .panel-right { + // padding: 10px; + // max-width: 550px; - @media screen and (max-width: $item-panel-max-width) { - & { - display: none; - padding: 0; - } - } - } + // @media screen and (max-width: $item-panel-max-width) { + // & { + // display: none; + // padding: 0; + // } + // } + // } } - @media screen and (min-width: $node-creation-panels-width) { - .node-creation-panels { - flex-direction: row; - } - } + // @media screen and (min-width: $node-creation-panels-width) { + // .node-creation-panels { + // flex-direction: row; + // } + // } .answer-required-items { display: flex; @@ -2567,6 +2568,14 @@ node-creation-selection-dialog { &.hotspot { max-height: 650px; + .node-creation-content { + flex-direction: row; + + @media screen and (max-width: $node-creation-panels-width) { + flex-direction: column; + } + } + textarea { resize: none; } diff --git a/src/src-assets/player-assets/player.coffee b/src/src-assets/player-assets/player.coffee index fed553d5..bea3699a 100644 --- a/src/src-assets/player-assets/player.coffee +++ b/src/src-assets/player-assets/player.coffee @@ -123,7 +123,8 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) $scope.inventoryUpdateMessage = "" # Add items to player's inventory - if q_data.options.items and q_data.options.items[0] + # if q_data.options.items and q_data.options.items[0] + if $scope.inventory.length > 0 $scope.showInventoryBtn = true diff --git a/src/src-assets/player-assets/player.scss b/src/src-assets/player-assets/player.scss index 9891c34f..c5101ca8 100644 --- a/src/src-assets/player-assets/player.scss +++ b/src/src-assets/player-assets/player.scss @@ -569,6 +569,7 @@ h1 { &.nonselectable { cursor: default; font-style: italic; + color: $gray; .missing-item { color: $red-lighter; From 0544694c4bbb5ca14d8d6051d728c6e62a319706 Mon Sep 17 00:00:00 2001 From: Corey Peterson Date: Wed, 27 Mar 2024 10:46:53 -0400 Subject: [PATCH 23/57] Added text for no items in player inventory. Added placeholder text in player when question text is empty. --- src/player.html | 4 ++++ src/src-assets/player-assets/player.coffee | 2 +- src/src-assets/player-assets/player.scss | 15 +++++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/player.html b/src/player.html index 3d5ee740..3e5cfec4 100644 --- a/src/player.html +++ b/src/player.html @@ -72,6 +72,10 @@

Inventory Items

+
+

You don't have any items yet.

+

Once you receive an item, it'll appear here.

+
NEW! diff --git a/src/src-assets/player-assets/player.coffee b/src/src-assets/player-assets/player.coffee index bea3699a..0a3620a8 100644 --- a/src/src-assets/player-assets/player.coffee +++ b/src/src-assets/player-assets/player.coffee @@ -265,7 +265,7 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) # Note: Micromarkdown is still adding a mystery newline or carriage return character to the beginning of most parsed strings (but not generated tags??) - if presanitized.length then parsedQuestion = micromarkdown.parse(presanitized) else parsedQuestion = "" + if presanitized.length then parsedQuestion = micromarkdown.parse(presanitized) else parsedQuestion = "No question text provided." # hyperlinks are automatically converted into tags, except it loads content within the iframe. To circumvent this, need to dynamically add target="_blank" attribute to all generated URLs parsedQuestion = addTargetToHrefs parsedQuestion diff --git a/src/src-assets/player-assets/player.scss b/src/src-assets/player-assets/player.scss index c5101ca8..577f0f1f 100644 --- a/src/src-assets/player-assets/player.scss +++ b/src/src-assets/player-assets/player.scss @@ -763,6 +763,21 @@ h1 { height: 100%; overflow: auto; + .no-items-yet { + width: 90%; + margin: 1em 5%; + + h3 { + text-align: center; + color: $gray-darker; + } + + p { + text-align: center; + color: $gray; + } + } + .inventory-items { display: flex; flex-direction: row; From 891c27d8f1760ab59e7e438b5711e6d6181f6b71 Mon Sep 17 00:00:00 2001 From: Corey Peterson Date: Wed, 27 Mar 2024 16:47:43 -0400 Subject: [PATCH 24/57] Style fixes. Bug fix with provisioning items in player. --- src/creator.html | 5 ++--- src/player.html | 2 +- src/src-assets/creator-assets/creator.scss | 21 +++++++++++++++------ src/src-assets/player-assets/player.coffee | 10 ++-------- 4 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/creator.html b/src/creator.html index 7ee83fb0..df28ca5c 100644 --- a/src/creator.html +++ b/src/creator.html @@ -710,8 +710,7 @@

-
+

Required Items for Answer

@@ -797,7 +796,7 @@

Required Items for Answer

- +
diff --git a/src/player.html b/src/player.html index 3e5cfec4..3397c62b 100644 --- a/src/player.html +++ b/src/player.html @@ -222,7 +222,7 @@

{{title}}

tabindex="0"> {{ answer.text.length > 0 ? answer.text : 'No answer text provided' }}
- + {{itemSelection[getItemIndex(item.id)].name}} ({{item.range}}) diff --git a/src/src-assets/creator-assets/creator.scss b/src/src-assets/creator-assets/creator.scss index 233f30f7..a3483299 100644 --- a/src/src-assets/creator-assets/creator.scss +++ b/src/src-assets/creator-assets/creator.scss @@ -1040,10 +1040,6 @@ debug-qset-loader, debug-qset-generator { } } -.invalid-quantity-message { - color: $red; -} - toast { position: absolute; z-index: $z-index-very-important; @@ -1150,13 +1146,18 @@ required-items-tooltip { opacity: 1; overflow: visible; transition: opacity 0.3s ease; - padding: 5px 25px 25px 25px; + padding: 5px 25px 20px 25px; z-index: $z-index-modal-dialog; box-sizing: border-box; border-radius: 2px; box-shadow: 0px 0px 5px #888; } + .invalid-quantity-message { + font-weight: bold; + color: $red; + } + .item-header { border-bottom: 1px solid $gray-lighter; @@ -1728,10 +1729,11 @@ item-icon-selector { display: flex; position: fixed; top: 15%; - height: 75%; + height: 65%; width: 65%; max-width: 850px; margin: auto; + padding-bottom: 5px; .arrow-right { position: absolute; @@ -1827,6 +1829,11 @@ item-icon-selector { font-size: 12px; font-style: italic; color: $gray-darker; + + &.invalid-quantity-message { + color: $red; + font-weight: bold; + } } /** Show modal on screen widths < 1000px **/ @@ -3851,6 +3858,8 @@ advanced-question-editor { &.invalid-quantity-message { padding: 0; + font-weight: bold; + color: $red; } &.hidden { diff --git a/src/src-assets/player-assets/player.coffee b/src/src-assets/player-assets/player.coffee index 0a3620a8..6e1963ba 100644 --- a/src/src-assets/player-assets/player.coffee +++ b/src/src-assets/player-assets/player.coffee @@ -124,9 +124,7 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) # Add items to player's inventory # if q_data.options.items and q_data.options.items[0] - if $scope.inventory.length > 0 - - $scope.showInventoryBtn = true + if q_data.options.items and q_data.options.items[0] # Format items if q_data.options.items @@ -180,6 +178,7 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) $scope.inventory = $scope.inventory.filter((p_i) -> p_i.count > 0) if ($scope.removedItems[0] || $scope.addedItems[0]) + if !$scope.showInventoryBtn then $scope.showInventoryBtn = true $scope.inventoryUpdate = true document.getElementById("inventory-update").removeAttribute("inert") @@ -429,11 +428,6 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) return true return false - $scope.presentInInventory = (id, inventory) -> - for item in inventory - if item.id == id then return true - return false - # Checks to see if player inventory contains all required items # Returns array of missing items $scope.checkInventory = (requiredItems) -> From 11bf30a8cf54df660ba692c56f99d157749829e6 Mon Sep 17 00:00:00 2001 From: Corey Peterson Date: Wed, 10 Apr 2024 15:48:18 -0400 Subject: [PATCH 25/57] qset compatibility fixes --- src/scoreScreen.html | 2 +- src/src-assets/score-assets/app.coffee | 2 +- src/src-assets/score-assets/score.coffee | 12 +++++++++--- src/src-assets/score-assets/score.scss | 9 +++++++++ webpack.config.js | 3 ++- 5 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/scoreScreen.html b/src/scoreScreen.html index 85a20446..d0acfd24 100644 --- a/src/scoreScreen.html +++ b/src/scoreScreen.html @@ -1,6 +1,6 @@ - + Adventure Score Screen diff --git a/src/src-assets/score-assets/app.coffee b/src/src-assets/score-assets/app.coffee index 482f5aa1..04b9ada3 100644 --- a/src/src-assets/score-assets/app.coffee +++ b/src/src-assets/score-assets/app.coffee @@ -1,4 +1,4 @@ # Create an angular module to import the animation module and house our controller ## UNFINISHED ## -angular.module "AdventureScoresceen", ["ngSanitize"] \ No newline at end of file +angular.module "Adventure", ["ngSanitize"] \ No newline at end of file diff --git a/src/src-assets/score-assets/score.coffee b/src/src-assets/score-assets/score.coffee index 5ac939a8..a1b806cc 100644 --- a/src/src-assets/score-assets/score.coffee +++ b/src/src-assets/score-assets/score.coffee @@ -1,6 +1,6 @@ -angular.module('AdventureScorescreen', ['ngSanitize']) +angular.module('Adventure', ['ngSanitize']) -.controller 'AdventureScoreCtrl', ['$scope','$sanitize', '$sce', '$timeout', ($scope, $sanitize, $sce, $timeout) -> +.controller 'AdventureScoreCtrl', ['$scope','$sanitize', '$sce', '$timeout', 'legacyQsetSrv', ($scope, $sanitize, $sce, $timeout, legacyQsetSrv) -> $scope.responses = [] $scope.itemSelection = [] @@ -93,6 +93,8 @@ angular.module('AdventureScorescreen', ['ngSanitize']) _manageItemDelta = (question) -> items = [] + + if !question.options.items then return items for item in question.options.items if item.firstVisitOnly and _getNodeVisitCount(question.options.id) > 0 then continue @@ -185,7 +187,7 @@ angular.module('AdventureScorescreen', ['ngSanitize']) $scope.showOlderQsetWarning = response.older_qset else question = _getQuestion qset, response.id - items = question.options.items + items = if question.options.items then question.options.items else [] row = question: _manageConditionalQuestion question, response.data[0] answer: response.data[1] @@ -212,6 +214,10 @@ angular.module('AdventureScorescreen', ['ngSanitize']) $scope.update(qset, scoreTable) $scope.update = (qset, scoreTable) -> + + # if a legacy qset - convert it first + if qset.items[0].items then qset = JSON.parse legacyQsetSrv.convertOldQset qset + $scope.$apply -> $scope.table = $scope.createTable(qset, scoreTable) _currentInventory = [] diff --git a/src/src-assets/score-assets/score.scss b/src/src-assets/score-assets/score.scss index f7eae63c..8c457288 100644 --- a/src/src-assets/score-assets/score.scss +++ b/src/src-assets/score-assets/score.scss @@ -55,6 +55,15 @@ body { color: $gray-darkest; } + .older-qset-warning { + display: block; + width: 80%; + margin: 1em auto 1.5em auto; + text-align: center; + font-size: 0.9em; + font-style: italic; + } + .inventory { width: 750px; height: 100%; diff --git a/webpack.config.js b/webpack.config.js index 1b5567d3..bc9ca2f0 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -49,7 +49,8 @@ const entries = { srcPath+"scoreScreen.html", srcPath+"src-assets/score-assets/score.scss", srcPath+"src-assets/score-assets/app.coffee", - srcPath+"src-assets/score-assets/score.coffee" + srcPath+"src-assets/score-assets/score.coffee", + srcPath+"src-assets/legacyQsetSrv.coffee" ] } From 2bf84a3700cae89ef8fdd35d4c49361f20b346a0 Mon Sep 17 00:00:00 2001 From: Corey Peterson Date: Thu, 11 Apr 2024 11:04:30 -0400 Subject: [PATCH 26/57] Better handling of blank preview nodes on score screen --- src/_score/score_module.php | 3 ++- src/scoreScreen.html | 7 +++++-- src/src-assets/score-assets/score.coffee | 2 +- src/src-assets/score-assets/score.scss | 10 ++++++++++ 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/_score/score_module.php b/src/_score/score_module.php index e2c4ad27..56447791 100755 --- a/src/_score/score_module.php +++ b/src/_score/score_module.php @@ -146,7 +146,8 @@ protected function get_score_details() 'symbol' => '%', 'graphic' => 'score', 'display_score' => false, - 'older_qset' => $log->item_id == 0 ? true : false + 'older_qset' => $log->item_id == 0 && $log->text != 'Blank Destination! Be sure to edit or remove this node before publishing.' ? true : false, + 'blank_node' => $log->item_id == 0 && $log->text == 'Blank Destination! Be sure to edit or remove this node before publishing.' ? true : false, ]; break; } diff --git a/src/scoreScreen.html b/src/scoreScreen.html index d0acfd24..a195402d 100644 --- a/src/scoreScreen.html +++ b/src/scoreScreen.html @@ -46,6 +46,9 @@

Review Your Journey

end point icon + + empty node icon +
{{row.question}}
@@ -95,9 +98,9 @@

Review Your Journey

-
+

{{ row.text }}

-
+

{{ row.score }}%

diff --git a/src/src-assets/score-assets/score.coffee b/src/src-assets/score-assets/score.coffee index a1b806cc..dcd24518 100644 --- a/src/src-assets/score-assets/score.coffee +++ b/src/src-assets/score-assets/score.coffee @@ -181,7 +181,7 @@ angular.module('Adventure', ['ngSanitize']) row = text: _manageConditionalQuestion question, response.data[0] # needs to work with conditional questions score: response.data[1] - type: 'end' + type: if response.blank_node then 'blank' else 'end' table.push row $scope.showOlderQsetWarning = response.older_qset diff --git a/src/src-assets/score-assets/score.scss b/src/src-assets/score-assets/score.scss index 8c457288..1f5c4060 100644 --- a/src/src-assets/score-assets/score.scss +++ b/src/src-assets/score-assets/score.scss @@ -310,6 +310,12 @@ body { } } + &.blank { + &:after { + display: none; + } + } + span.icon { position: absolute; top: 50%; @@ -344,6 +350,10 @@ body { border: solid 2px $color-end; } + &.blank { + border: solid 2px $gray; + } + // box-shadow: #999 1px 1px 3px; } From 2b05e505eb91898a7ebfee9b99b66c06d1e24287 Mon Sep 17 00:00:00 2001 From: Corey Peterson Date: Thu, 11 Apr 2024 13:57:26 -0400 Subject: [PATCH 27/57] Adds backwards compatibility fallback --- src/src-assets/score-assets/score.coffee | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/src-assets/score-assets/score.coffee b/src/src-assets/score-assets/score.coffee index dcd24518..2b5a35bf 100644 --- a/src/src-assets/score-assets/score.coffee +++ b/src/src-assets/score-assets/score.coffee @@ -201,9 +201,13 @@ angular.module('Adventure', ['ngSanitize']) if response.data[2] answer = _getAnswerById(question, response.data[2]) + + # older qsets won't contain an asset.url value + image = if question.options.asset.url then question.options.asset.url else Materia.ScoreCore.getMediaUrl question.options.asset.id + if answer row.svg = answer.options.svg - row.image = question.options.asset.url + row.image = image _counter++ table.push row From 71567dee591be6378453e4e6517d793d436431d1 Mon Sep 17 00:00:00 2001 From: Corey Peterson Date: Wed, 24 Apr 2024 13:52:27 -0400 Subject: [PATCH 28/57] Adjustments to required items display in player --- src/src-assets/player-assets/player.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/src-assets/player-assets/player.scss b/src/src-assets/player-assets/player.scss index 577f0f1f..610ba591 100644 --- a/src/src-assets/player-assets/player.scss +++ b/src/src-assets/player-assets/player.scss @@ -602,11 +602,11 @@ h1 { align-items: center; flex-direction: row; flex-wrap: wrap; - min-width: 75px; - max-width: 200px; + width: 180px; font-style: normal; font-size: 0.9em; + line-height: 1.5em; padding: 0.25em; img { From 6080e6fd73eb8fd470a29b47cfbe3d7fe10f250c Mon Sep 17 00:00:00 2001 From: Corey Peterson Date: Tue, 7 May 2024 12:45:36 -0400 Subject: [PATCH 29/57] adds micromarkdown support to score screen --- src/scoreScreen.html | 3 +-- src/src-assets/score-assets/score.coffee | 5 +++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/scoreScreen.html b/src/scoreScreen.html index a195402d..51f1b47e 100644 --- a/src/scoreScreen.html +++ b/src/scoreScreen.html @@ -49,8 +49,7 @@

Review Your Journey

empty node icon -
- {{row.question}} +
{{row.answer}} diff --git a/src/src-assets/score-assets/score.coffee b/src/src-assets/score-assets/score.coffee index 2b5a35bf..16bed93d 100644 --- a/src/src-assets/score-assets/score.coffee +++ b/src/src-assets/score-assets/score.coffee @@ -84,6 +84,7 @@ angular.module('Adventure', ['ngSanitize']) # all checks are met, this is the one if match is true then selected = option.text + if selected.length then selected = micromarkdown.parse(selected) else selected = 'No question text provided.' return selected @@ -214,6 +215,10 @@ angular.module('Adventure', ['ngSanitize']) return table + # $scope.mmd_parse = (text) -> + # console.log 'haha butts' + # return micromarkdown.parse text + $scope.start = (instance, qset, scoreTable, isPreview, qsetVersion) -> $scope.update(qset, scoreTable) From 690815b1a9f594f531e50b9740ee85bd75dde317 Mon Sep 17 00:00:00 2001 From: Corey Peterson Date: Tue, 7 May 2024 12:45:53 -0400 Subject: [PATCH 30/57] Adds partial string detection for short answer responses --- src/creator.html | 28 +++++++--- src/src-assets/creator-assets/creator.scss | 53 +++++++++++++++---- .../creator-assets/directives.coffee | 4 ++ src/src-assets/creator-assets/services.coffee | 2 + src/src-assets/player-assets/player.coffee | 3 +- 5 files changed, 72 insertions(+), 18 deletions(-) diff --git a/src/creator.html b/src/creator.html index 7b1817ce..05e4abfc 100644 --- a/src/creator.html +++ b/src/creator.html @@ -678,13 +678,27 @@

ng-repeat="match in answer.matches" ng-click="removeAnswerMatch($index, $parent.$index)">{{match}}

-
- - - - +
+

Matching Options

+

The response entered by the user will be compared to each possible answer provided above. By default, matches are not case sensitive and whitespace is removed.

+
    +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
+
Date: Tue, 7 May 2024 12:59:42 -0400 Subject: [PATCH 31/57] Make question selection intuitive and clear --- src/creator.html | 15 +++-- src/src-assets/creator-assets/creator.scss | 18 ++++++ src/src-assets/player-assets/player.coffee | 75 ++++++++++++++++------ 3 files changed, 81 insertions(+), 27 deletions(-) diff --git a/src/creator.html b/src/creator.html index 7b1817ce..306dc775 100644 --- a/src/creator.html +++ b/src/creator.html @@ -1342,13 +1342,14 @@

Conditional Question

Conditional Question

Conditional Narrative Text

Conditional End Text

-
-

Choose to display different question text based on the number of times the player has visited this destination.

-
-
-

Choose to display different question text based on the player's number of visits to this destination and/or what items they have in their inventory.

-

Note that if more than one contextual question's requirements are met, the question with the most required items and visits will be selected.

-
+
+

Choose to display different narratives based on the number of times the player has visited this destination.

+

Choose to display different narratives based on the player's number of visits to this destination and/or what items they have in their inventory.

+

+ How the widget chooses which one to display: If a player meets the requirements for more than one question, then it will choose the one that has not been shown yet. And if there is more than one question that hasn't been shown yet, it will choose the one with the most required items and visits. +

+

{{invalidRequiredVisits}}

+

{{invalidRequiredVisits}}

  • diff --git a/src/src-assets/creator-assets/creator.scss b/src/src-assets/creator-assets/creator.scss index a3483299..d383313c 100644 --- a/src/src-assets/creator-assets/creator.scss +++ b/src/src-assets/creator-assets/creator.scss @@ -3841,6 +3841,24 @@ advanced-question-editor { flex-direction: column; justify-content: space-between; + .question-instructions { + padding: 1em; + color: $gray-darker; + } + + #question-instructions-subtext { + padding: 0.75em; + margin-top: 1em; + background-color: $gray-lightest; + font-size: 0.9em; + font-style: italic; + border-radius: 5px; + } + + .hidden { + display: none; + } + > section { margin: 1em 1em 0 1em; diff --git a/src/src-assets/player-assets/player.coffee b/src/src-assets/player-assets/player.coffee index 6e1963ba..46bac028 100644 --- a/src/src-assets/player-assets/player.coffee +++ b/src/src-assets/player-assets/player.coffee @@ -34,6 +34,8 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) $scope.customInternalScoreMessage = "" # custom "internal score screen" message, if blank then use default $scope.inventory = [] $scope.itemSelection = [] + $scope.additionalQuestions = [] + $scope.lastSelectedQuestion = null $scope.missingRequiredItems = [] $scope.missingRequiredItemsAltText = "" @@ -202,61 +204,94 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) presanitized = "" mostItems = 0 mostRecentItem = 0 - # Load default question - selected_question = q_data.questions[0] + mostVisited = 0 + # Track which questions have the most required items and visits, respectively + questionWithMostItems = null + questionWithMostVisits = null # If conditional question matches, use it instead if q_data.options.additionalQuestions for q in q_data.options.additionalQuestions + keepMostVisited = false + keepMostItems = false if q.requiredVisits != undefined + # If the player hasn't visited this node enough times, skip this question if $scope.visitedNodes[q_data.id] < q.requiredVisits || (q.requiredVisits > 0 && $scope.visitedNodes[q_data.id] == undefined) - # If the player hasn't visited this node enough times, skip this question continue + # Keep the question with the most required visits + # We don't set questionWithMostVisits here because it also needs to have the required items + if mostVisited < q.requiredVisits + mostVisited = q.requiredVisits + keepMostVisited = true + # Check if player has required items if q.requiredItems && q.requiredItems[0] + # If the player doesn't have the required items, skip this question missingItems = $scope.checkInventory(q.requiredItems) if (missingItems.length > 0) - # If the player doesn't have the required items, skip this question continue else - keep = false recentItem = $scope.getMostRecentItem($scope.inventory, q.requiredItems) + # Keep the question with the most recent item if (recentItem >= mostRecentItem) - # Choose the question with the most recent item mostRecentItem = recentItem - keep = true - if (mostItems < q.requiredItems.length) - # Choose the question with the most required items + keepMostItems = true + # Keep the question with the most required items + else if (mostItems < q.requiredItems.length) mostItems = q.requiredItems.length - keep = true - if (!keep) - continue - else if mostRecentItem > 0 || mostItems > 0 - # If we've already chosen a more selective question, skip this one - continue - selected_question = q + keepMostItems = true + # If the question meets the visits and items requirements and has the most required items and visits, save it + if keepMostVisited + questionWithMostVisits = q + if keepMostItems + questionWithMostItems = q + + # Load default question + selectedQuestion = q_data.questions[0] + # Make the decision on which question to display + # If both questions are not null and both have been played, just go with whichever was played last + console.log(questionWithMostItems, questionWithMostVisits, $scope.additionalQuestions.indexOf(questionWithMostItems), $scope.additionalQuestions.indexOf(questionWithMostVisits)) + console.log($scope.lastSelectedQuestion) + if questionWithMostItems and questionWithMostVisits and $scope.additionalQuestions.indexOf(questionWithMostItems) > -1 and $scope.additionalQuestions.indexOf(questionWithMostVisits) > -1 + if $scope.lastSelectedQuestion == questionWithMostItems + selectedQuestion = questionWithMostItems + else if $scope.lastSelectedQuestion == questionWithMostVisits + selectedQuestion = questionWithMostVisits + else + selectedQuestion = questionWithMostItems + + # Question with most items takes precedence + else if questionWithMostItems and questionWithMostVisits and $scope.additionalQuestions.indexOf(questionWithMostVisits) > -1 + selectedQuestion = questionWithMostItems + if $scope.additionalQuestions.indexOf(questionWithMostItems) is -1 then $scope.additionalQuestions.push questionWithMostItems + else if questionWithMostVisits + selectedQuestion = questionWithMostVisits + if $scope.additionalQuestions.indexOf(questionWithMostVisits) is -1 then $scope.additionalQuestions.push questionWithMostVisits + + + $scope.lastSelectedQuestion = selectedQuestion # If the question text contains a string that doesn't pass angular's $sanitize check, it'll fail to display anything # Instead, parse in advance, catch the error, and warn the user that the text was nasty try # Run question text thru pre-sanitize routine because $sanitize is fickle about certain characters like >, < - presanitized = selected_question.text + presanitized = selectedQuestion.text for k, v of PRESANITIZE_CHARACTERS presanitized = presanitized.replace k, v $sanitize presanitized catch error - selected_question.text = "*Question text removed due to malformed or dangerous HTML content*" + selectedQuestion.text = "*Question text removed due to malformed or dangerous HTML content*" else # If the question text contains a string that doesn't pass angular's $sanitize check, it'll fail to display anything # Instead, parse in advance, catch the error, and warn the user that the text was nasty try # Run question text thru pre-sanitize routine because $sanitize is fickle about certain characters like >, < - presanitized = selected_question.text + presanitized = selectedQuestion.text for k, v of PRESANITIZE_CHARACTERS presanitized = presanitized.replace k, v $sanitize presanitized catch error - selected_question.text = "*Question text removed due to malformed or dangerous HTML content*" + selectedQuestion.text = "*Question text removed due to malformed or dangerous HTML content*" unless q_data.options.asset then $scope.layout = "text-only" else if presanitized != "" then $scope.layout = q_data.options.asset.align From 8a6471a64dd001f163ecca78718c73011e52a92c Mon Sep 17 00:00:00 2001 From: Cay Henning Date: Tue, 7 May 2024 13:04:00 -0400 Subject: [PATCH 32/57] Remove logs --- src/src-assets/player-assets/player.coffee | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/src-assets/player-assets/player.coffee b/src/src-assets/player-assets/player.coffee index 4c755091..0d57b234 100644 --- a/src/src-assets/player-assets/player.coffee +++ b/src/src-assets/player-assets/player.coffee @@ -34,7 +34,7 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) $scope.customInternalScoreMessage = "" # custom "internal score screen" message, if blank then use default $scope.inventory = [] $scope.itemSelection = [] - $scope.additionalQuestions = [] + $scope.shownQuestions = [] $scope.lastSelectedQuestion = null $scope.missingRequiredItems = [] @@ -209,8 +209,8 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) questionWithMostItems = null questionWithMostVisits = null # If conditional question matches, use it instead - if q_data.options.additionalQuestions - for q in q_data.options.additionalQuestions + if q_data.options.shownQuestions + for q in q_data.options.shownQuestions keepMostVisited = false keepMostItems = false if q.requiredVisits != undefined @@ -248,9 +248,7 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) selectedQuestion = q_data.questions[0] # Make the decision on which question to display # If both questions are not null and both have been played, just go with whichever was played last - console.log(questionWithMostItems, questionWithMostVisits, $scope.additionalQuestions.indexOf(questionWithMostItems), $scope.additionalQuestions.indexOf(questionWithMostVisits)) - console.log($scope.lastSelectedQuestion) - if questionWithMostItems and questionWithMostVisits and $scope.additionalQuestions.indexOf(questionWithMostItems) > -1 and $scope.additionalQuestions.indexOf(questionWithMostVisits) > -1 + if questionWithMostItems and questionWithMostVisits and $scope.shownQuestions.indexOf(questionWithMostItems) > -1 and $scope.shownQuestions.indexOf(questionWithMostVisits) > -1 if $scope.lastSelectedQuestion == questionWithMostItems selectedQuestion = questionWithMostItems else if $scope.lastSelectedQuestion == questionWithMostVisits @@ -259,12 +257,14 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) selectedQuestion = questionWithMostItems # Question with most items takes precedence - else if questionWithMostItems and questionWithMostVisits and $scope.additionalQuestions.indexOf(questionWithMostVisits) > -1 + else if questionWithMostItems and questionWithMostVisits and $scope.shownQuestions.indexOf(questionWithMostVisits) > -1 selectedQuestion = questionWithMostItems - if $scope.additionalQuestions.indexOf(questionWithMostItems) is -1 then $scope.additionalQuestions.push questionWithMostItems + # Add question to previously shown questions if it's not already there + if $scope.shownQuestions.indexOf(questionWithMostItems) is -1 then $scope.shownQuestions.push questionWithMostItems else if questionWithMostVisits selectedQuestion = questionWithMostVisits - if $scope.additionalQuestions.indexOf(questionWithMostVisits) is -1 then $scope.additionalQuestions.push questionWithMostVisits + # Add question to previously shown questions if it's not already there + if $scope.shownQuestions.indexOf(questionWithMostVisits) is -1 then $scope.shownQuestions.push questionWithMostVisits $scope.lastSelectedQuestion = selectedQuestion From c3f700366155c815e2e3618a4b9da0ae280c0b03 Mon Sep 17 00:00:00 2001 From: Cay Henning Date: Tue, 7 May 2024 13:41:31 -0400 Subject: [PATCH 33/57] Fix rename --- src/src-assets/player-assets/player.coffee | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/src-assets/player-assets/player.coffee b/src/src-assets/player-assets/player.coffee index 0d57b234..a29b0c55 100644 --- a/src/src-assets/player-assets/player.coffee +++ b/src/src-assets/player-assets/player.coffee @@ -208,9 +208,13 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) # Track which questions have the most required items and visits, respectively questionWithMostItems = null questionWithMostVisits = null + + # Load default question + selectedQuestion = q_data.questions[0] + # If conditional question matches, use it instead - if q_data.options.shownQuestions - for q in q_data.options.shownQuestions + if q_data.options.additionalQuestions + for q in q_data.options.additionalQuestions keepMostVisited = false keepMostItems = false if q.requiredVisits != undefined @@ -219,7 +223,7 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) continue # Keep the question with the most required visits # We don't set questionWithMostVisits here because it also needs to have the required items - if mostVisited < q.requiredVisits + if mostVisited <= q.requiredVisits mostVisited = q.requiredVisits keepMostVisited = true # Check if player has required items @@ -244,8 +248,6 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) if keepMostItems questionWithMostItems = q - # Load default question - selectedQuestion = q_data.questions[0] # Make the decision on which question to display # If both questions are not null and both have been played, just go with whichever was played last if questionWithMostItems and questionWithMostVisits and $scope.shownQuestions.indexOf(questionWithMostItems) > -1 and $scope.shownQuestions.indexOf(questionWithMostVisits) > -1 From cc0eae7e5fe61b5fa8f830d0d567f9fb4e4c2bef Mon Sep 17 00:00:00 2001 From: Cay Henning Date: Tue, 7 May 2024 14:48:54 -0400 Subject: [PATCH 34/57] Fix required item range in player --- src/player.html | 1 - src/src-assets/player-assets/player.coffee | 32 +++++++++++++--------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/player.html b/src/player.html index 3397c62b..260dbede 100644 --- a/src/player.html +++ b/src/player.html @@ -124,7 +124,6 @@

    Count

    aria-label="Welcome to Adventure. Now playing: {{title}}" aria-describedby="tutorial-content" tutorial-focus-manager> -
-
-

{{ row.text }}

+

{{ row.score }}%

From 8205917d6fcddd368fa765127b1b3f7cad4030a0 Mon Sep 17 00:00:00 2001 From: Cay Henning Date: Tue, 7 May 2024 15:33:31 -0400 Subject: [PATCH 36/57] Fix auto scroll in advanced question editor --- src/creator.html | 4 ++-- src/src-assets/creator-assets/directives.coffee | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/creator.html b/src/creator.html index 9a04bb3c..4236a402 100644 --- a/src/creator.html +++ b/src/creator.html @@ -698,7 +698,7 @@

Matching Options

- +
Conditional End Text

{{invalidRequiredVisits}}

{{invalidRequiredVisits}}

-
    +

Matching Options

-

The response entered by the user will be compared to each possible answer provided above. By default, matches are not case sensitive and whitespace is removed.

+

The response entered by the user will be compared to each possible answer provided above. By default, matches are not case-sensitive. Whitespace will always be ignored.

  • ! letter.match(/\W/g)).join() - - matchFrom = $scope.newMatch.trim() - matchFrom = matchFrom.split('').filter((letter) -> ! letter.match(/\W/)).join() + matchTo = matchTo.replace(/\s/g, '') + matchFrom = matchFrom.replace(/\s/g, '') matchErrorMessage = "This match already exists!" if (! $scope.answers[i].characterSensitive) # If matches DO NOT have same special characters, customize toast message if ! (matchTo.toLowerCase().localeCompare(matchFrom.toLowerCase()) is 0) - matchErrorMessage += " Make whitespace and character sensitive to add match." + matchErrorMessage += " Make character sensitive to add match." matchTo = matchTo.split('').filter((letter) -> letter.match(/\w/)).join() From 3577450d56e18026870e3c30ae9e3a30107ccff2 Mon Sep 17 00:00:00 2001 From: Corey Peterson Date: Fri, 10 May 2024 16:28:38 -0400 Subject: [PATCH 38/57] Fixed variable assignment associated with prior commit --- src/src-assets/creator-assets/directives.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/src-assets/creator-assets/directives.coffee b/src/src-assets/creator-assets/directives.coffee index b5056334..b32a4b7a 100644 --- a/src/src-assets/creator-assets/directives.coffee +++ b/src/src-assets/creator-assets/directives.coffee @@ -2651,8 +2651,8 @@ angular.module "Adventure" while j < $scope.answers[i].matches.length # Remove whitespace - matchTo = matchTo.replace(/\s/g, '') - matchFrom = matchFrom.replace(/\s/g, '') + matchTo = $scope.answers[i].matches[j].replace(/\s/g, '') + matchFrom = $scope.newMatch.replace(/\s/g, '') matchErrorMessage = "This match already exists!" From f20072ecc3a505df3c23cb110995b421c865a907 Mon Sep 17 00:00:00 2001 From: Cay Henning Date: Mon, 13 May 2024 09:51:43 -0500 Subject: [PATCH 39/57] Fix storage of last selected question/shown questions and make question selection a service --- src/src-assets/player-assets/player.coffee | 130 +++--------------- src/src-assets/score-assets/score.coffee | 24 ++-- .../services/inventoryService.coffee | 105 ++++++++++++++ .../{ => services}/legacyQsetSrv.coffee | 0 webpack.config.js | 8 +- 5 files changed, 141 insertions(+), 126 deletions(-) create mode 100644 src/src-assets/services/inventoryService.coffee rename src/src-assets/{ => services}/legacyQsetSrv.coffee (100%) diff --git a/src/src-assets/player-assets/player.coffee b/src/src-assets/player-assets/player.coffee index 2189c88d..8f2af062 100644 --- a/src/src-assets/player-assets/player.coffee +++ b/src/src-assets/player-assets/player.coffee @@ -1,7 +1,7 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) ## CONTROLLER ## -.controller 'AdventureController', ['$scope','$rootScope','legacyQsetSrv','$sanitize', '$sce', '$timeout', ($scope, $rootScope, legacyQsetSrv, $sanitize, $sce, $timeout) -> +.controller 'AdventureController', ['$scope','$rootScope', 'inventoryService', 'legacyQsetSrv','$sanitize', '$sce', '$timeout', ($scope, $rootScope, inventoryService, legacyQsetSrv, $sanitize, $sce, $timeout) -> $scope.BLANK = "blank" $scope.MC = "mc" @@ -34,8 +34,8 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) $scope.customInternalScoreMessage = "" # custom "internal score screen" message, if blank then use default $scope.inventory = [] $scope.itemSelection = [] - $scope.shownQuestions = [] - $scope.lastSelectedQuestion = null + $scope.shownQuestions = {} # track which questions have been shown for each node + $scope.lastSelectedQuestion = {} # track the last selected question for each node $scope.missingRequiredItems = [] $scope.missingRequiredItemsAltText = "" @@ -202,87 +202,28 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) # Get question based on inventory and number of visits presanitized = "" - mostItems = 0 - mostRecentItem = 0 - mostVisited = 0 - # Track which questions have the most required items and visits, respectively - questionWithMostItems = null - questionWithMostVisits = null # Load default question selectedQuestion = q_data.questions[0] # If conditional question matches, use it instead if q_data.options.additionalQuestions - for q in q_data.options.additionalQuestions - keepMostVisited = false - keepMostItems = false - if q.requiredVisits != undefined - # If the player hasn't visited this node enough times, skip this question - if $scope.visitedNodes[q_data.id] < q.requiredVisits || (q.requiredVisits > 0 && $scope.visitedNodes[q_data.id] == undefined) - continue - # Keep the question with the most required visits - # We don't set questionWithMostVisits here because it also needs to have the required items - if mostVisited <= q.requiredVisits - mostVisited = q.requiredVisits - keepMostVisited = true - # Check if player has required items - if q.requiredItems && q.requiredItems[0] - # If the player doesn't have the required items, skip this question - missingItems = $scope.checkInventory(q.requiredItems) - if (missingItems.length > 0) - continue - else - recentItem = $scope.getMostRecentItem($scope.inventory, q.requiredItems) - # Keep the question with the most recent item - if (recentItem >= mostRecentItem) - mostRecentItem = recentItem - keepMostItems = true - # Keep the question with the most required items - else if (mostItems < q.requiredItems.length) - mostItems = q.requiredItems.length - keepMostItems = true - # If the question meets the visits and items requirements and has the most required items and visits, save it - if keepMostVisited - questionWithMostVisits = q - if keepMostItems - questionWithMostItems = q - - # Make the decision on which question to display - # If both questions are not null and both have been played, just go with whichever was played last - if questionWithMostItems and questionWithMostVisits and $scope.shownQuestions.indexOf(questionWithMostItems) > -1 and $scope.shownQuestions.indexOf(questionWithMostVisits) > -1 - if $scope.lastSelectedQuestion == questionWithMostItems - selectedQuestion = questionWithMostItems - else if $scope.lastSelectedQuestion == questionWithMostVisits - selectedQuestion = questionWithMostVisits - else - selectedQuestion = questionWithMostItems + shownQuestions = [] + lastSelectedQuestion = null + if q_data.id of $scope.shownQuestions then shownQuestions = $scope.shownQuestions[q_data.id] + if q_data.id of $scope.lastSelectedQuestion then lastSelectedQuestion = $scope.lastSelectedQuestion[q_data.id] - # Question with most items takes precedence - else if questionWithMostItems and questionWithMostVisits and $scope.shownQuestions.indexOf(questionWithMostVisits) > -1 - selectedQuestion = questionWithMostItems - # Add question to previously shown questions if it's not already there - if $scope.shownQuestions.indexOf(questionWithMostItems) is -1 then $scope.shownQuestions.push questionWithMostItems - else if questionWithMostVisits - selectedQuestion = questionWithMostVisits - # Add question to previously shown questions if it's not already there - if $scope.shownQuestions.indexOf(questionWithMostVisits) is -1 then $scope.shownQuestions.push questionWithMostVisits + selectedQuestion = inventoryService.selectQuestion(q_data, $scope.inventory, $scope.visitedNodes, shownQuestions, lastSelectedQuestion, selectedQuestion) + if selectedQuestion + # Update last selected question + $scope.lastSelectedQuestion[q_data.id] = selectedQuestion - $scope.lastSelectedQuestion = selectedQuestion - - # If the question text contains a string that doesn't pass angular's $sanitize check, it'll fail to display anything - # Instead, parse in advance, catch the error, and warn the user that the text was nasty - try - # Run question text thru pre-sanitize routine because $sanitize is fickle about certain characters like >, < - presanitized = selectedQuestion.text - for k, v of PRESANITIZE_CHARACTERS - presanitized = presanitized.replace k, v - $sanitize presanitized + # Mark the selected question as shown + if q_data.id of $scope.shownQuestions + if $scope.shownQuestions[q_data.id].indexOf(selectedQuestion) is -1 then $scope.shownQuestions[q_data.id].push selectedQuestion + else $scope.shownQuestions[q_data.id] = [selectedQuestion] - catch error - selectedQuestion.text = "*Question text removed due to malformed or dangerous HTML content*" - else # If the question text contains a string that doesn't pass angular's $sanitize check, it'll fail to display anything # Instead, parse in advance, catch the error, and warn the user that the text was nasty try @@ -403,15 +344,6 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) $scope.visitedNodes[q_data.id] = ($scope.visitedNodes[q_data.id] || 0) + 1 - $scope.getMostRecentItem = (inventory, requiredItems) -> - mostRecentItem = 0 - for i in inventory - for r in requiredItems - if i.id is r.id - if i.time > mostRecentItem - mostRecentItem = i.time - mostRecentItem - $scope.dismissUpdates = () -> $scope.inventoryUpdate = false document.getElementById("inventory-update").setAttribute("inert", true) @@ -453,30 +385,6 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) return true return false - # Checks to see if player inventory contains all required items - # Returns array of missing items - $scope.checkInventory = (requiredItems) -> - missingItems = [] - if (! requiredItems) - return [] - angular.forEach requiredItems, (item) -> - hasItemInInventory = false - hasRequiredItem = $scope.inventory.some (playerItem) -> - if playerItem.id is item.id - hasItemInInventory = true - # Check if player has more than the min - if playerItem.count >= item.minCount - # Check if player has less than the max - if playerItem.count <= item.maxCount or item.uncappedMax - return true - return false - # Check if player doesn't have item but there is no minimum - if ! hasItemInInventory and item.minCount is 0 - hasRequiredItem = true - if ! hasRequiredItem - missingItems.push item - return missingItems - # Handles selection of MC answer choices and transitional buttons (narrative and end screen) $scope.handleAnswerSelection = (link, index) -> # link to -1 indicates the widget should advance to the score screen @@ -488,7 +396,7 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) # answers[index] will be inaccurate if answers are randomized !!! requiredItems = getAnswerByIndex(index).requiredItems || [] - $scope.missingRequiredItems = $scope.checkInventory(requiredItems) + $scope.missingRequiredItems = inventoryService.checkInventory(requiredItems) if $scope.missingRequiredItems[0] $scope.missingRequiredItemsAltText = $scope.missingRequiredItems.map((item) -> "#{$scope.itemSelection[$scope.getItemIndex(item.id)].name} (amount: #{requiredItems.find((el) -> el.id is item.id).range});") @@ -548,10 +456,10 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) if ($scope.q_data.answers[i].options.partialMatches and response.includes(match)) or match is response requiredItems = $scope.q_data.answers[i].options.requiredItems || $scope.q_data.answers[i].requiredItems - missingItems = $scope.checkInventory(requiredItems) + missingItems = inventoryService.checkInventory(requiredItems) requiredItems = $scope.q_data.answers[i].options.requiredItems || $scope.q_data.answers[i].requiredItems - $scope.missingRequiredItems = $scope.checkInventory(requiredItems) + $scope.missingRequiredItems = inventoryService.checkInventory(requiredItems) if $scope.missingRequiredItems[0] $scope.missingRequiredItemsAltText = missingItems.map((item) -> "#{$scope.itemSelection[$scope.getItemIndex(item.id)].name} (amount: #{requiredItems.find((el) -> el.id is item.id).range});") @@ -920,7 +828,7 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) if answer.text then $scope.hotspotLabelTarget.text = answer.text else return false - $scope.hotspotLabelTarget.ariaLabel = answer.text + (if $scope.checkInventory(answer.requiredItems).length > 0 then ' Cannot select. ' else ' ') + $scope.hotspotLabelTarget.ariaLabel = answer.text + (if inventoryService.checkInventory(answer.requiredItems).length > 0 then ' Cannot select. ' else ' ') requiredItemString = answer.requiredItems.map((item) -> $scope.itemSelection[$scope.getItemIndex(item.id)].name + ' (amount: ' + item.range + ')').join(', ') $scope.hotspotLabelTarget.ariaLabel += (if (answer.requiredItems && answer.requiredItems.length > 0) then ('Required Items: ' + requiredItemString) else '') diff --git a/src/src-assets/score-assets/score.coffee b/src/src-assets/score-assets/score.coffee index 16bed93d..4b0129ac 100644 --- a/src/src-assets/score-assets/score.coffee +++ b/src/src-assets/score-assets/score.coffee @@ -1,6 +1,6 @@ angular.module('Adventure', ['ngSanitize']) -.controller 'AdventureScoreCtrl', ['$scope','$sanitize', '$sce', '$timeout', 'legacyQsetSrv', ($scope, $sanitize, $sce, $timeout, legacyQsetSrv) -> +.controller 'AdventureScoreCtrl', ['$scope','$sanitize', '$sce', '$timeout', 'inventoryService', 'legacyQsetSrv', ($scope, $sanitize, $sce, $timeout, inventoryService, legacyQsetSrv) -> $scope.responses = [] $scope.itemSelection = [] @@ -20,7 +20,7 @@ angular.module('Adventure', ['ngSanitize']) for node in _visitedNodes if node.id is id then return node.count - + return 0 _getItemInInventory = (id) -> @@ -96,7 +96,7 @@ angular.module('Adventure', ['ngSanitize']) items = [] if !question.options.items then return items - + for item in question.options.items if item.firstVisitOnly and _getNodeVisitCount(question.options.id) > 0 then continue @@ -111,7 +111,7 @@ angular.module('Adventure', ['ngSanitize']) inventoryItem.recency = _counter previouslyExists = true break - + # add the item to the inventory, since it wasn't there already if !previouslyExists itemCopy = angular.copy item @@ -119,7 +119,7 @@ angular.module('Adventure', ['ngSanitize']) _currentInventory.push angular.copy itemCopy items.push item - + # negative delta? remove it from the inventory, if present else if item.count < 0 @@ -132,9 +132,9 @@ angular.module('Adventure', ['ngSanitize']) # instead of using item.count, the delta is whatever the current inventory value is, zeroed out if item.takeAll itemRemoved.count = inventoryItem.count * -1 - + _currentInventory.splice(_currentInventory.indexOf(inventoryItem), 1) - + else # inventoryItem will persist because the quantity removed is less than the total if inventoryItem.count > item.count * -1 @@ -186,7 +186,7 @@ angular.module('Adventure', ['ngSanitize']) table.push row $scope.showOlderQsetWarning = response.older_qset - else + else question = _getQuestion qset, response.id items = if question.options.items then question.options.items else [] row = @@ -209,7 +209,7 @@ angular.module('Adventure', ['ngSanitize']) if answer row.svg = answer.options.svg row.image = image - + _counter++ table.push row @@ -226,14 +226,14 @@ angular.module('Adventure', ['ngSanitize']) # if a legacy qset - convert it first if qset.items[0].items then qset = JSON.parse legacyQsetSrv.convertOldQset qset - + $scope.$apply -> $scope.table = $scope.createTable(qset, scoreTable) _currentInventory = [] _qsetItems = if qset.options and qset.options.inventoryItems then qset.options.inventoryItems else [] - + Materia.ScoreCore.setHeight(_getHeight()) - + Materia.ScoreCore.hideResultsTable() Materia.ScoreCore.start $scope diff --git a/src/src-assets/services/inventoryService.coffee b/src/src-assets/services/inventoryService.coffee new file mode 100644 index 00000000..5a803d71 --- /dev/null +++ b/src/src-assets/services/inventoryService.coffee @@ -0,0 +1,105 @@ +angular.module("Adventure") +.service "inventoryService", [() -> + + # Check if player has required items in their inventory + checkInventory = (inventory, requiredItems) -> + missingItems = [] + if (! requiredItems) + return [] + angular.forEach requiredItems, (item) -> + hasItemInInventory = false + hasRequiredItem = inventory.some (playerItem) -> + if playerItem.id is item.id + hasItemInInventory = true + # Check if player has more than the min + if playerItem.count >= item.minCount + # Check if player has less than the max + if playerItem.count <= item.maxCount or item.uncappedMax + return true + return false + # Check if player doesn't have item but there is no minimum + if ! hasItemInInventory and item.minCount is 0 + hasRequiredItem = true + if ! hasRequiredItem + missingItems.push item + return missingItems + + # Get the most recently acquired item in the player's inventory + getMostRecentItem = (inventory, requiredItems) -> + mostRecentItem = 0 + for i in inventory + for r in requiredItems + if i.id is r.id + if i.time > mostRecentItem + mostRecentItem = i.time + mostRecentItem + + # Select the next question to display based on the player's inventory and visited nodes + selectQuestion = (q_data, inventory, visitedNodes, shownQuestions, lastSelectedQuestion, defaultQuestion) -> + mostItems = 0 + mostRecentItem = 0 + mostVisited = 0 + # Track which questions have the most required items and visits, respectively + questionWithMostItems = null + questionWithMostVisits = null + questionWithMostRecent = null + + for q in q_data.options.additionalQuestions + keepMostVisited = false + keepMostItems = false + keepMostRecent = false + if q.requiredVisits != undefined + # If the player hasn't visited this node enough times, skip this question + if visitedNodes[q_data.id] < q.requiredVisits || (q.requiredVisits > 0 && visitedNodes[q_data.id] == undefined) + continue + # Keep the question with the most required visits + # We don't set questionWithMostVisits here because it also needs to have the required items + if mostVisited <= q.requiredVisits + mostVisited = q.requiredVisits + keepMostVisited = true + # Check if player has required items + if q.requiredItems && q.requiredItems[0] + # If the player doesn't have the required items, skip this question + missingItems = checkInventory(inventory, q.requiredItems) + if (missingItems.length > 0) + # This erases the most visited question as well + continue + else + recentItem = getMostRecentItem(inventory, q.requiredItems) + # Keep the question with the most recent item + if (recentItem >= mostRecentItem) + mostRecentItem = recentItem + keepMostRecent = true + # Keep the question with the most required items + else if (mostItems < q.requiredItems.length) + mostItems = q.requiredItems.length + keepMostItems = true + # If the question meets the visits and items requirements and has the most required items and visits, save it + if keepMostVisited + questionWithMostVisits = q + if keepMostItems + questionWithMostItems = q + if keepMostRecent + questionWithMostRecent = q + + # Make the decision on which question to display + # Question with most recent item takes precedence, then most items, then most visited + if questionWithMostRecent and shownQuestions.indexOf(questionWithMostRecent) < 0 + selectedQuestion = questionWithMostRecent + else if questionWithMostItems and shownQuestions.indexOf(questionWithMostItems) < 0 + selectedQuestion = questionWithMostItems + else if questionWithMostVisits and shownQuestions.indexOf(questionWithMostVisits) < 0 + selectedQuestion = questionWithMostVisits + # If none of the above conditions are met, just go with the last selected question + else if lastSelectedQuestion + # Technically, the last selected question should not be null at this point if the above conditions are false + selectedQuestion = lastSelectedQuestion + else + selectedQuestion = defaultQuestion + + return selectedQuestion + + selectQuestion : selectQuestion + checkInventory : checkInventory + getMostRecentItem : getMostRecentItem +] \ No newline at end of file diff --git a/src/src-assets/legacyQsetSrv.coffee b/src/src-assets/services/legacyQsetSrv.coffee similarity index 100% rename from src/src-assets/legacyQsetSrv.coffee rename to src/src-assets/services/legacyQsetSrv.coffee diff --git a/webpack.config.js b/webpack.config.js index bc9ca2f0..73c34127 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -33,7 +33,8 @@ const entries = { srcPath+"src-assets/player-assets/player.scss", srcPath+"src-assets/player-assets/app.coffee", srcPath+"src-assets/player-assets/player.coffee", - srcPath+"src-assets/legacyQsetSrv.coffee" + srcPath+"src-assets/services/inventoryService.coffee", + srcPath+"src-assets/services/legacyQsetSrv.coffee" ], "creator": [ @@ -43,14 +44,15 @@ const entries = { srcPath+"src-assets/creator-assets/services.coffee", srcPath+"src-assets/creator-assets/controllers.coffee", srcPath+"src-assets/creator-assets/directives.coffee", - srcPath+"src-assets/legacyQsetSrv.coffee" + srcPath+"src-assets/services/legacyQsetSrv.coffee" ], "scoreScreen": [ srcPath+"scoreScreen.html", srcPath+"src-assets/score-assets/score.scss", srcPath+"src-assets/score-assets/app.coffee", srcPath+"src-assets/score-assets/score.coffee", - srcPath+"src-assets/legacyQsetSrv.coffee" + srcPath+"src-assets/services/inventoryService.coffee", + srcPath+"src-assets/services/legacyQsetSrv.coffee" ] } From 926c0cb6f6ce19075a3c15a2ee24271f1499dcfa Mon Sep 17 00:00:00 2001 From: Cay Henning Date: Tue, 14 May 2024 11:01:03 -0500 Subject: [PATCH 40/57] Add conditional question service to score screen --- src/src-assets/player-assets/player.coffee | 23 +----- src/src-assets/score-assets/score.coffee | 74 ++++--------------- .../services/inventoryService.coffee | 70 ++++++++++++++---- 3 files changed, 71 insertions(+), 96 deletions(-) diff --git a/src/src-assets/player-assets/player.coffee b/src/src-assets/player-assets/player.coffee index 8f2af062..7390296a 100644 --- a/src/src-assets/player-assets/player.coffee +++ b/src/src-assets/player-assets/player.coffee @@ -34,8 +34,6 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) $scope.customInternalScoreMessage = "" # custom "internal score screen" message, if blank then use default $scope.inventory = [] $scope.itemSelection = [] - $scope.shownQuestions = {} # track which questions have been shown for each node - $scope.lastSelectedQuestion = {} # track the last selected question for each node $scope.missingRequiredItems = [] $scope.missingRequiredItemsAltText = "" @@ -92,8 +90,6 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) $scope.setLightboxZoom = (val) -> $scope.lightboxZoom = val - $scope.visitedNodes = {} - # Object containing properties for the hotspot label that appears on mouseover $scope.hotspotLabelTarget = text: null @@ -145,7 +141,7 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) hasItem = false # Check if item is first visit only and player has visited this node before - if ($scope.visitedNodes[q_data.id] and q_i.firstVisitOnly) + if (inventoryService.getNodeVisitedCount(q_data) > 0 and q_i.firstVisitOnly) # Move to next item else # Inventory update @@ -208,22 +204,9 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) # If conditional question matches, use it instead if q_data.options.additionalQuestions - shownQuestions = [] - lastSelectedQuestion = null - if q_data.id of $scope.shownQuestions then shownQuestions = $scope.shownQuestions[q_data.id] - if q_data.id of $scope.lastSelectedQuestion then lastSelectedQuestion = $scope.lastSelectedQuestion[q_data.id] - - selectedQuestion = inventoryService.selectQuestion(q_data, $scope.inventory, $scope.visitedNodes, shownQuestions, lastSelectedQuestion, selectedQuestion) + selectedQuestion = inventoryService.selectQuestion(q_data, $scope.inventory, inventoryService.visitedNodes) if selectedQuestion - # Update last selected question - $scope.lastSelectedQuestion[q_data.id] = selectedQuestion - - # Mark the selected question as shown - if q_data.id of $scope.shownQuestions - if $scope.shownQuestions[q_data.id].indexOf(selectedQuestion) is -1 then $scope.shownQuestions[q_data.id].push selectedQuestion - else $scope.shownQuestions[q_data.id] = [selectedQuestion] - # If the question text contains a string that doesn't pass angular's $sanitize check, it'll fail to display anything # Instead, parse in advance, catch the error, and warn the user that the text was nasty try @@ -342,7 +325,7 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) else handleEmptyNode() # Should hopefully only happen on preview, when empty nodes are allowed - $scope.visitedNodes[q_data.id] = ($scope.visitedNodes[q_data.id] || 0) + 1 + inventoryService.addNodeToVisited(q_data) $scope.dismissUpdates = () -> $scope.inventoryUpdate = false diff --git a/src/src-assets/score-assets/score.coffee b/src/src-assets/score-assets/score.coffee index 4b0129ac..829e891a 100644 --- a/src/src-assets/score-assets/score.coffee +++ b/src/src-assets/score-assets/score.coffee @@ -7,7 +7,6 @@ angular.module('Adventure', ['ngSanitize']) $scope.customTable = false $scope.showOlderQsetWarning = false - _visitedNodes = [] _qsetItems = [] _currentInventory = [] @@ -16,13 +15,6 @@ angular.module('Adventure', ['ngSanitize']) _getHeight = () -> Math.ceil(parseFloat(window.getComputedStyle(document.querySelector('html')).height)) - _getNodeVisitCount = (id) -> - - for node in _visitedNodes - if node.id is id then return node.count - - return 0 - _getItemInInventory = (id) -> for item in _currentInventory if item.id is id then return item @@ -44,50 +36,6 @@ angular.module('Adventure', ['ngSanitize']) for answer in question.answers if answer.id is id then return answer - _manageConditionalQuestion = (question, response) -> - if !question then return response - selected = response - if question.options.additionalQuestions - - mostRecentItem = 0 - mostItems = 0 - - for option in question.options.additionalQuestions - - match = true - - # meets required visits check? - if option.requiredVisits > 0 - if _getNodeVisitCount(question.options.id) < option.requiredVisits then match = false - - # does the contextual question require items? - if option.requiredItems.length > 0 - # ensure all required items are accounted for - for item in option.requiredItems - inventoryItem = _getItemInInventory item.id - if inventoryItem != null - # user currently has the item by id, check if count is out of bounds - if inventoryItem.count < item.minCount or (inventoryItem.count > item.maxCount and item.uncappedMax is false) then match = false - else match = false # user does not have the required item - - # next, verify whether the question requires the most recently acquired item - itemRecency = _getMostRecentItem option.requiredItems - if itemRecency < mostRecentItem then match = false - else mostRecentItem = itemRecency - # verify whether the question requires the most items - if option.requiredItems.length < mostItems then match = false - else mostItems = option.requiredItems.length - - # no items required but another conditional question DID require them; therefore, it was more selective and will be chosen - else if mostRecentItem > 0 or mostItems > 0 then match = false - - # all checks are met, this is the one - if match is true then selected = option.text - - if selected.length then selected = micromarkdown.parse(selected) else selected = 'No question text provided.' - return selected - - # in order to accurately simulate the items received and taken, we have to recreate item handling logic in the score screen # simply using the options.items value for each question would not accurately report items taken and received # if certain factors are at play, like the takeAll and firstVisitOnly flags @@ -98,7 +46,7 @@ angular.module('Adventure', ['ngSanitize']) if !question.options.items then return items for item in question.options.items - if item.firstVisitOnly and _getNodeVisitCount(question.options.id) > 0 then continue + if item.firstVisitOnly and inventoryService.getNodeVisitedCount() > 0 then continue # positive delta? Add the item to the inventory, or increase the count if it's in there already if item.count > 0 @@ -147,12 +95,8 @@ angular.module('Adventure', ['ngSanitize']) items.push itemRemoved - if _getNodeVisitCount(question.options.id) > 0 - for node in _visitedNodes - if node.id is question.options.id then node.count++ - else _visitedNodes.push - id: question.options.id - count: 1 + inventoryService.addNodeToVisited question + return items _getQuestion = (qset, id) -> @@ -179,8 +123,12 @@ angular.module('Adventure', ['ngSanitize']) for response in scoreTable if response.type == 'SCORE_FINAL_FROM_CLIENT' question = _getQuestionByNodeId qset, response.node_id + rowQuestion = response.data[0] + if question.options.additionalQuestions + rowQuestion = inventoryService.selectQuestion question, _currentInventory, inventoryService.visitedNodes row = - text: _manageConditionalQuestion question, response.data[0] # needs to work with conditional questions + text: rowQuestion.text + # text: _manageConditionalQuestion question, response.data[0] # needs to work with conditional questions score: response.data[1] type: if response.blank_node then 'blank' else 'end' table.push row @@ -189,8 +137,12 @@ angular.module('Adventure', ['ngSanitize']) else question = _getQuestion qset, response.id items = if question.options.items then question.options.items else [] + rowQuestion = response.data[0] + if question.options.additionalQuestions and question.options.additionalQuestions.length > 0 + rowQuestion = inventoryService.selectQuestion question, _currentInventory, inventoryService.visitedNodes row = - question: _manageConditionalQuestion question, response.data[0] + question: rowQuestion.text + # question: _manageConditionalQuestion question, response.data[0] answer: response.data[1] type: question.options.type feedback: response.feedback diff --git a/src/src-assets/services/inventoryService.coffee b/src/src-assets/services/inventoryService.coffee index 5a803d71..934fa0b0 100644 --- a/src/src-assets/services/inventoryService.coffee +++ b/src/src-assets/services/inventoryService.coffee @@ -1,8 +1,17 @@ +# Holds the conditional question logic +# Ideally, this would hold all of the inventory logic to be shared between the player and score screen +# Which is currently being handled by both separately + angular.module("Adventure") .service "inventoryService", [() -> + self = this + + self.shownQuestions = {} # track which questions have been shown for each node + self.lastSelectedQuestion = {} # track the last selected question for each node + self.visitedNodes = {} # track which nodes have been visited and how many times # Check if player has required items in their inventory - checkInventory = (inventory, requiredItems) -> + self.checkInventory = (inventory, requiredItems) -> missingItems = [] if (! requiredItems) return [] @@ -25,7 +34,7 @@ angular.module("Adventure") return missingItems # Get the most recently acquired item in the player's inventory - getMostRecentItem = (inventory, requiredItems) -> + self.getMostRecentItem = (inventory, requiredItems) -> mostRecentItem = 0 for i in inventory for r in requiredItems @@ -35,7 +44,13 @@ angular.module("Adventure") mostRecentItem # Select the next question to display based on the player's inventory and visited nodes - selectQuestion = (q_data, inventory, visitedNodes, shownQuestions, lastSelectedQuestion, defaultQuestion) -> + self.selectQuestion = (q_data, inventory, visitedNodes) -> + _shownQuestions = [] + _lastSelectedQuestion = null + + if self.shownQuestions[q_data.id]? then _shownQuestions = self.shownQuestions[q_data.id] + if self.lastSelectedQuestion[q_data.id]? then _lastSelectedQuestion = self.lastSelectedQuestion[q_data.id] + mostItems = 0 mostRecentItem = 0 mostVisited = 0 @@ -44,6 +59,8 @@ angular.module("Adventure") questionWithMostVisits = null questionWithMostRecent = null + selectedQuestion = q_data.questions[0] + for q in q_data.options.additionalQuestions keepMostVisited = false keepMostItems = false @@ -60,12 +77,12 @@ angular.module("Adventure") # Check if player has required items if q.requiredItems && q.requiredItems[0] # If the player doesn't have the required items, skip this question - missingItems = checkInventory(inventory, q.requiredItems) + missingItems = self.checkInventory(inventory, q.requiredItems) if (missingItems.length > 0) # This erases the most visited question as well continue else - recentItem = getMostRecentItem(inventory, q.requiredItems) + recentItem = self.getMostRecentItem(inventory, q.requiredItems) # Keep the question with the most recent item if (recentItem >= mostRecentItem) mostRecentItem = recentItem @@ -84,22 +101,45 @@ angular.module("Adventure") # Make the decision on which question to display # Question with most recent item takes precedence, then most items, then most visited - if questionWithMostRecent and shownQuestions.indexOf(questionWithMostRecent) < 0 + if questionWithMostRecent and _shownQuestions.indexOf(questionWithMostRecent) < 0 selectedQuestion = questionWithMostRecent - else if questionWithMostItems and shownQuestions.indexOf(questionWithMostItems) < 0 + else if questionWithMostItems and _shownQuestions.indexOf(questionWithMostItems) < 0 selectedQuestion = questionWithMostItems - else if questionWithMostVisits and shownQuestions.indexOf(questionWithMostVisits) < 0 + else if questionWithMostVisits and _shownQuestions.indexOf(questionWithMostVisits) < 0 selectedQuestion = questionWithMostVisits # If none of the above conditions are met, just go with the last selected question - else if lastSelectedQuestion + else if _lastSelectedQuestion # Technically, the last selected question should not be null at this point if the above conditions are false - selectedQuestion = lastSelectedQuestion - else - selectedQuestion = defaultQuestion + selectedQuestion = _lastSelectedQuestion + + # Update last selected question + self.lastSelectedQuestion[q_data.id] = selectedQuestion + + # Mark the selected question as shown + if self.shownQuestions[q_data.id]? + if self.shownQuestions[q_data.id].indexOf(selectedQuestion) is -1 then self.shownQuestions[q_data.id].push selectedQuestion + else self.shownQuestions[q_data.id] = [selectedQuestion] return selectedQuestion - selectQuestion : selectQuestion - checkInventory : checkInventory - getMostRecentItem : getMostRecentItem + self.addNodeToVisited = (node) -> + if self.visitedNodes[node.id]? + self.visitedNodes[node.id]++ + else + self.visitedNodes[node.id] = 1 + + self.getNodeVisitedCount = (node) -> + if self.visitedNodes[node.id]? + return self.visitedNodes[node.id] + else + return 0 + + selectQuestion : self.selectQuestion + checkInventory : self.checkInventory + getMostRecentItem : self.getMostRecentItem + shownQuestions : self.shownQuestions + lastSelectedQuestion : self.lastSelectedQuestion + visitedNodes : self.visitedNodes + addNodeToVisited : self.addNodeToVisited + getNodeVisitedCount : self.getNodeVisitedCount ] \ No newline at end of file From e4cc9315a0ec23eb02dcb87db56ce57c677a4d8f Mon Sep 17 00:00:00 2001 From: Cay Henning Date: Tue, 14 May 2024 11:19:29 -0500 Subject: [PATCH 41/57] Change how recency is calculated --- src/src-assets/player-assets/player.coffee | 3 ++- src/src-assets/score-assets/score.coffee | 8 +++----- src/src-assets/services/inventoryService.coffee | 13 ++++++++----- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/src-assets/player-assets/player.coffee b/src/src-assets/player-assets/player.coffee index 7390296a..8ea23f2a 100644 --- a/src/src-assets/player-assets/player.coffee +++ b/src/src-assets/player-assets/player.coffee @@ -133,8 +133,9 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) count: q_i.count || 1 takeAll: q_i.takeAll || false firstVisitOnly: q_i.firstVisitOnly || false - time: Date.now() + recency: inventoryService.recencyCounter $scope.questionItems.push item + inventoryService.recencyCounter++ for q_i in $scope.questionItems do (q_i) -> diff --git a/src/src-assets/score-assets/score.coffee b/src/src-assets/score-assets/score.coffee index 829e891a..b78da9cf 100644 --- a/src/src-assets/score-assets/score.coffee +++ b/src/src-assets/score-assets/score.coffee @@ -10,8 +10,6 @@ angular.module('Adventure', ['ngSanitize']) _qsetItems = [] _currentInventory = [] - _counter = 0 - _getHeight = () -> Math.ceil(parseFloat(window.getComputedStyle(document.querySelector('html')).height)) @@ -56,14 +54,14 @@ angular.module('Adventure', ['ngSanitize']) for inventoryItem in _currentInventory if inventoryItem.id is item.id inventoryItem.count += item.count - inventoryItem.recency = _counter + inventoryItem.recency = inventoryService.recencyCounter previouslyExists = true break # add the item to the inventory, since it wasn't there already if !previouslyExists itemCopy = angular.copy item - itemCopy.recency = _counter + itemCopy.recency = inventoryService.recencyCounter _currentInventory.push angular.copy itemCopy items.push item @@ -162,7 +160,7 @@ angular.module('Adventure', ['ngSanitize']) row.svg = answer.options.svg row.image = image - _counter++ + inventoryService.recencyCounter++ table.push row return table diff --git a/src/src-assets/services/inventoryService.coffee b/src/src-assets/services/inventoryService.coffee index 934fa0b0..a80abc2c 100644 --- a/src/src-assets/services/inventoryService.coffee +++ b/src/src-assets/services/inventoryService.coffee @@ -9,6 +9,7 @@ angular.module("Adventure") self.shownQuestions = {} # track which questions have been shown for each node self.lastSelectedQuestion = {} # track the last selected question for each node self.visitedNodes = {} # track which nodes have been visited and how many times + self.recencyCounter = 0 # Check if player has required items in their inventory self.checkInventory = (inventory, requiredItems) -> @@ -39,8 +40,8 @@ angular.module("Adventure") for i in inventory for r in requiredItems if i.id is r.id - if i.time > mostRecentItem - mostRecentItem = i.time + if i.recency > mostRecentItem + mostRecentItem = i.recency mostRecentItem # Select the next question to display based on the player's inventory and visited nodes @@ -134,12 +135,14 @@ angular.module("Adventure") else return 0 - selectQuestion : self.selectQuestion - checkInventory : self.checkInventory - getMostRecentItem : self.getMostRecentItem shownQuestions : self.shownQuestions lastSelectedQuestion : self.lastSelectedQuestion visitedNodes : self.visitedNodes + recencyCounter : self.recencyCounter + + selectQuestion : self.selectQuestion + checkInventory : self.checkInventory + getMostRecentItem : self.getMostRecentItem addNodeToVisited : self.addNodeToVisited getNodeVisitedCount : self.getNodeVisitedCount ] \ No newline at end of file From ba3125cf4d0bf31f468a248e34bc8ba55aef3c3c Mon Sep 17 00:00:00 2001 From: Cay Henning Date: Tue, 14 May 2024 11:21:10 -0500 Subject: [PATCH 42/57] Remove recency function from score screen --- src/src-assets/score-assets/score.coffee | 11 ----------- src/src-assets/services/inventoryService.coffee | 1 + 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/src/src-assets/score-assets/score.coffee b/src/src-assets/score-assets/score.coffee index b78da9cf..e8d0a7ed 100644 --- a/src/src-assets/score-assets/score.coffee +++ b/src/src-assets/score-assets/score.coffee @@ -19,17 +19,6 @@ angular.module('Adventure', ['ngSanitize']) return null - # recency is determined by the order in which a node a visited - # unlike the player, which determines recency by time - _getMostRecentItem = (requiredItems) -> - mostRecentItem = 0 - for i in _currentInventory - for r in requiredItems - if i.id is r.id - if i.recency > mostRecentItem - mostRecentItem = i.recency - mostRecentItem - _getAnswerById = (question, id) -> for answer in question.answers if answer.id is id then return answer diff --git a/src/src-assets/services/inventoryService.coffee b/src/src-assets/services/inventoryService.coffee index a80abc2c..9a2dce68 100644 --- a/src/src-assets/services/inventoryService.coffee +++ b/src/src-assets/services/inventoryService.coffee @@ -35,6 +35,7 @@ angular.module("Adventure") return missingItems # Get the most recently acquired item in the player's inventory + # recency is determined by the order in which a node is visited self.getMostRecentItem = (inventory, requiredItems) -> mostRecentItem = 0 for i in inventory From 6b0bdcae51a11170fb100889b8b07fa0066297bb Mon Sep 17 00:00:00 2001 From: Cay Henning Date: Wed, 15 May 2024 11:20:56 -0500 Subject: [PATCH 43/57] Fix non-conditional question responses in scorescreen --- src/src-assets/score-assets/score.coffee | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/src-assets/score-assets/score.coffee b/src/src-assets/score-assets/score.coffee index e8d0a7ed..e5a4b767 100644 --- a/src/src-assets/score-assets/score.coffee +++ b/src/src-assets/score-assets/score.coffee @@ -113,8 +113,9 @@ angular.module('Adventure', ['ngSanitize']) rowQuestion = response.data[0] if question.options.additionalQuestions rowQuestion = inventoryService.selectQuestion question, _currentInventory, inventoryService.visitedNodes + rowQuestion = rowQuestion.text row = - text: rowQuestion.text + text: rowQuestion # text: _manageConditionalQuestion question, response.data[0] # needs to work with conditional questions score: response.data[1] type: if response.blank_node then 'blank' else 'end' @@ -127,8 +128,9 @@ angular.module('Adventure', ['ngSanitize']) rowQuestion = response.data[0] if question.options.additionalQuestions and question.options.additionalQuestions.length > 0 rowQuestion = inventoryService.selectQuestion question, _currentInventory, inventoryService.visitedNodes + rowQuestion = rowQuestion.text row = - question: rowQuestion.text + question: rowQuestion # question: _manageConditionalQuestion question, response.data[0] answer: response.data[1] type: question.options.type From 573381bac595cd1ae072539fdf112ccf9dafd0d4 Mon Sep 17 00:00:00 2001 From: Cay Henning Date: Wed, 15 May 2024 11:31:31 -0500 Subject: [PATCH 44/57] Fix calls to inventoryService in player --- src/src-assets/player-assets/player.coffee | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/src-assets/player-assets/player.coffee b/src/src-assets/player-assets/player.coffee index 8ea23f2a..caed15d2 100644 --- a/src/src-assets/player-assets/player.coffee +++ b/src/src-assets/player-assets/player.coffee @@ -380,7 +380,7 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) # answers[index] will be inaccurate if answers are randomized !!! requiredItems = getAnswerByIndex(index).requiredItems || [] - $scope.missingRequiredItems = inventoryService.checkInventory(requiredItems) + $scope.missingRequiredItems = inventoryService.checkInventory($scope.inventory, requiredItems) if $scope.missingRequiredItems[0] $scope.missingRequiredItemsAltText = $scope.missingRequiredItems.map((item) -> "#{$scope.itemSelection[$scope.getItemIndex(item.id)].name} (amount: #{requiredItems.find((el) -> el.id is item.id).range});") @@ -440,10 +440,10 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) if ($scope.q_data.answers[i].options.partialMatches and response.includes(match)) or match is response requiredItems = $scope.q_data.answers[i].options.requiredItems || $scope.q_data.answers[i].requiredItems - missingItems = inventoryService.checkInventory(requiredItems) + missingItems = inventoryService.checkInventory($scope.inventory, requiredItems) requiredItems = $scope.q_data.answers[i].options.requiredItems || $scope.q_data.answers[i].requiredItems - $scope.missingRequiredItems = inventoryService.checkInventory(requiredItems) + $scope.missingRequiredItems = inventoryService.checkInventory($scope.inventory, requiredItems) if $scope.missingRequiredItems[0] $scope.missingRequiredItemsAltText = missingItems.map((item) -> "#{$scope.itemSelection[$scope.getItemIndex(item.id)].name} (amount: #{requiredItems.find((el) -> el.id is item.id).range});") @@ -798,7 +798,7 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) $attrs.$set "style", style ] -.directive "labelManager", ['$timeout', ($timeout) -> +.directive "labelManager", ['$timeout', 'inventoryService', ($timeout, inventoryService) -> restrict: "A", link: ($scope, $element, $attrs) -> @@ -812,7 +812,7 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) if answer.text then $scope.hotspotLabelTarget.text = answer.text else return false - $scope.hotspotLabelTarget.ariaLabel = answer.text + (if inventoryService.checkInventory(answer.requiredItems).length > 0 then ' Cannot select. ' else ' ') + $scope.hotspotLabelTarget.ariaLabel = answer.text + (if inventoryService.checkInventory($scope.inventory, answer.requiredItems).length > 0 then ' Cannot select. ' else ' ') requiredItemString = answer.requiredItems.map((item) -> $scope.itemSelection[$scope.getItemIndex(item.id)].name + ' (amount: ' + item.range + ')').join(', ') $scope.hotspotLabelTarget.ariaLabel += (if (answer.requiredItems && answer.requiredItems.length > 0) then ('Required Items: ' + requiredItemString) else '') From 4f1a68bcbf5dc0523ed6c198ef3f7b024795266e Mon Sep 17 00:00:00 2001 From: Corey Peterson Date: Tue, 21 May 2024 14:30:58 -0400 Subject: [PATCH 45/57] Fixed missing score value in score screen. Updated MWDK to 3.0.2. --- package.json | 2 +- src/scoreScreen.html | 3 ++- yarn.lock | 18 +++++++++--------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index eceb249a..a3cf0537 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ }, "version": "2.4.0", "dependencies": { - "materia-widget-development-kit": "^3.0.1", + "materia-widget-development-kit": "^3.0.2", "micromarkdown": "^0.3.0" }, "scripts": { diff --git a/src/scoreScreen.html b/src/scoreScreen.html index e59bfae0..5b7ead2b 100644 --- a/src/scoreScreen.html +++ b/src/scoreScreen.html @@ -97,7 +97,8 @@

    Review Your Journey

-
+
+

{{ row.score }}%

diff --git a/yarn.lock b/yarn.lock index d7ea813e..1860057e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4541,15 +4541,15 @@ make-fetch-happen@^11.0.0, make-fetch-happen@^11.0.1, make-fetch-happen@^11.1.1: socks-proxy-agent "^7.0.0" ssri "^10.0.0" -materia-widget-dependencies@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/materia-widget-dependencies/-/materia-widget-dependencies-0.2.0.tgz#ebec53a9c10991bacf8b8ea9d6dfba5cd40ed912" - integrity sha512-eqab8DdPD0tzEicJWfgc/BJko/MSZarECmXENNwt4NUr757HCQJl7M+q70eXYO84Ssk3kHySTELgOTLES/FODg== +materia-widget-dependencies@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/materia-widget-dependencies/-/materia-widget-dependencies-0.3.0.tgz#b95b8b99f48118f599bc034f6d4ddeb5e75af722" + integrity sha512-+atVBgczOu3/mcn7pDYaS0AlDkBZ3ttDLi5vXxpAOGi9U6n7RPTxxrZGv5yqkcRJC+b3j+mXlwxPY96hazSIIg== -materia-widget-development-kit@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/materia-widget-development-kit/-/materia-widget-development-kit-3.0.1.tgz#16b48010d68c52a2382f117e628b714c5a2be47d" - integrity sha512-qVd9yzFPleu+FAYvboiA+EGvbP1e2GsVvhl4IG/YIxNXCvEtP7jZcCtpqXVY9p8piUPBAmxXY5E/NAS06iXRIw== +materia-widget-development-kit@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/materia-widget-development-kit/-/materia-widget-development-kit-3.0.2.tgz#fb446da09348391f8bd06b68eeb5058597e42ed8" + integrity sha512-PPAmooObAGX8oWoHVBSJznpW5uroQleRo+YYdnmHFBoGvsSLG9NKJiV3jsvVEyh8VHEfXMLSmWXoBar5bgm4KQ== dependencies: "@babel/core" "^7.22.17" "@babel/preset-env" "^7.20.2" @@ -4572,7 +4572,7 @@ materia-widget-development-kit@^3.0.1: hbs "^4.2.0" html-loader "^4.2.0" html-webpack-plugin "^5.5.3" - materia-widget-dependencies "0.2.0" + materia-widget-dependencies "0.3.0" mini-css-extract-plugin "^2.7.2" npm-check-updates "^16.10.9" path "^0.12.7" From a54d5f8ba80d81121a74fda2bb7417e621d528ba Mon Sep 17 00:00:00 2001 From: Corey Peterson Date: Wed, 22 May 2024 14:46:58 -0400 Subject: [PATCH 46/57] Significant tweaks to short answer matching. Added most significant match logic. --- src/creator.html | 2 +- src/src-assets/player-assets/player.coffee | 64 +++++++++++++++------- 2 files changed, 45 insertions(+), 21 deletions(-) diff --git a/src/creator.html b/src/creator.html index b56dc016..401b09f4 100644 --- a/src/creator.html +++ b/src/creator.html @@ -695,7 +695,7 @@

Matching Options

  • - +
  • diff --git a/src/src-assets/player-assets/player.coffee b/src/src-assets/player-assets/player.coffee index caed15d2..501f8e78 100644 --- a/src/src-assets/player-assets/player.coffee +++ b/src/src-assets/player-assets/player.coffee @@ -411,6 +411,12 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) response = originalResponse = $scope.response $scope.response = "" + console.log $scope.response + console.log originalResponse + + matches = [] + selectedMatch = null + # Outer loop - loop through every answer set (index 0 is always [All Other Answers] ) for i in [0...$scope.q_data.answers.length] @@ -438,32 +444,39 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) match = match.toLowerCase() response = response.toLowerCase() - if ($scope.q_data.answers[i].options.partialMatches and response.includes(match)) or match is response - requiredItems = $scope.q_data.answers[i].options.requiredItems || $scope.q_data.answers[i].requiredItems - missingItems = inventoryService.checkInventory($scope.inventory, requiredItems) + if ($scope.q_data.answers[i].options.partialMatches and response.includes(match)) or match is response then matches.push { text: match, index: i, requiresExact: !$scope.q_data.answers[i].options.partialMatches } - requiredItems = $scope.q_data.answers[i].options.requiredItems || $scope.q_data.answers[i].requiredItems - $scope.missingRequiredItems = inventoryService.checkInventory($scope.inventory, requiredItems) + # determine the selected match via most significant match criteria (if multiple matches were identified) + if matches.length > 1 then selectedMatch = _mostSignificantMatch response, matches + else if matches.length is 1 then selectedMatch = matches[0] - if $scope.missingRequiredItems[0] - $scope.missingRequiredItemsAltText = missingItems.map((item) -> "#{$scope.itemSelection[$scope.getItemIndex(item.id)].name} (amount: #{requiredItems.find((el) -> el.id is item.id).range});") - # Add range value to required items - $scope.missingRequiredItems.map((item) -> _assignRange item) - $scope.next = null - return + if selectedMatch + matchIndex = selectedMatch.index + requiredItems = $scope.q_data.answers[matchIndex].options.requiredItems || $scope.q_data.answers[matchIndex].requiredItems + missingItems = inventoryService.checkInventory($scope.inventory, requiredItems) - link = ~~$scope.q_data.answers[i].options.link # is parsing required? + requiredItems = $scope.q_data.answers[matchIndex].options.requiredItems || $scope.q_data.answers[matchIndex].requiredItems + $scope.missingRequiredItems = inventoryService.checkInventory($scope.inventory, requiredItems) - $scope.selectedAnswer = $scope.q_data.answers[i].options.matches[j] - _logProgress() + if $scope.missingRequiredItems[0] + $scope.missingRequiredItemsAltText = missingItems.map((item) -> "#{$scope.itemSelection[$scope.getItemIndex(item.id)].name} (amount: #{requiredItems.find((el) -> el.id is item.id).range});") + # Add range value to required items + $scope.missingRequiredItems.map((item) -> _assignRange item) + $scope.next = null + return - if $scope.q_data.answers[i].options and $scope.q_data.answers[i].options.feedback - $scope.feedback = $scope.q_data.answers[i].options.feedback - $scope.next = link - else - manageQuestionScreen link + link = ~~$scope.q_data.answers[matchIndex].options.link # is parsing required? + + $scope.selectedAnswer = originalResponse + _logProgress() + + if $scope.q_data.answers[matchIndex].options and $scope.q_data.answers[matchIndex].options.feedback + $scope.feedback = $scope.q_data.answers[matchIndex].options.feedback + $scope.next = link + else + manageQuestionScreen link - return true + return true # Fallback in case the user response doesn't match anything. Have to match the link associated with [All Other Answers] for answer in $scope.q_data.answers @@ -555,6 +568,17 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) # $scope.question.text = "[Destination requires item(s): [#{itemArray.toString(', ')}]]" # $scope.link = $scope.question.options.parentId + _mostSignificantMatch = (response, matches) -> + mostSignificantMatch = { text: '' } + for option in matches + # exact match against an answer that requires exact matches takes top priority + if option.requiresExact then return mostSignificantMatch = option + # exact match against an answer that allows partial matches + else if option.text == response then return mostSignificantMatch = option + # fuzzy match of highest complexity (longest string length) + else if option.text.length > mostSignificantMatch.text.length then mostSignificantMatch = option + return mostSignificantMatch + # Submit the user's response to the logs _logProgress = (answerId = undefined) -> if answerId != undefined then Materia.Score.submitQuestionForScoring $scope.question.materiaId, $scope.selectedAnswer, answerId From 50064b0194d846bc39b496b26407241e9891b483 Mon Sep 17 00:00:00 2001 From: Corey Peterson Date: Wed, 22 May 2024 16:52:20 -0400 Subject: [PATCH 47/57] Removes console logs --- src/src-assets/player-assets/player.coffee | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/src-assets/player-assets/player.coffee b/src/src-assets/player-assets/player.coffee index 501f8e78..2512464e 100644 --- a/src/src-assets/player-assets/player.coffee +++ b/src/src-assets/player-assets/player.coffee @@ -411,9 +411,6 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) response = originalResponse = $scope.response $scope.response = "" - console.log $scope.response - console.log originalResponse - matches = [] selectedMatch = null From ae813e2a2c2dce6e4596bdcfbdcaad0e400fe73e Mon Sep 17 00:00:00 2001 From: Corey Peterson Date: Thu, 23 May 2024 15:59:55 -0400 Subject: [PATCH 48/57] Adds checkInventory helper function available to scope in player --- src/src-assets/player-assets/player.coffee | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/src-assets/player-assets/player.coffee b/src/src-assets/player-assets/player.coffee index 2512464e..b17f8757 100644 --- a/src/src-assets/player-assets/player.coffee +++ b/src/src-assets/player-assets/player.coffee @@ -369,6 +369,9 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) return true return false + $scope.checkInventory = (items) -> + return inventoryService.checkInventory $scope.inventory, items + # Handles selection of MC answer choices and transitional buttons (narrative and end screen) $scope.handleAnswerSelection = (link, index) -> # link to -1 indicates the widget should advance to the score screen From df9d18b703a00b58bd52bb9ce0c5e67456588c8a Mon Sep 17 00:00:00 2001 From: Corey Peterson Date: Fri, 24 May 2024 14:46:14 -0400 Subject: [PATCH 49/57] Fixed missing param in nodeVisitedCount reference in score screen. Rearranged conditional question computation to occur prior to item delta in player. --- src/src-assets/player-assets/player.coffee | 91 ++++++++++++---------- src/src-assets/score-assets/score.coffee | 2 +- 2 files changed, 51 insertions(+), 42 deletions(-) diff --git a/src/src-assets/player-assets/player.coffee b/src/src-assets/player-assets/player.coffee index b17f8757..e3a62fb1 100644 --- a/src/src-assets/player-assets/player.coffee +++ b/src/src-assets/player-assets/player.coffee @@ -108,6 +108,51 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) if questionId is 0 q_data = $scope.qset.items[0] + # ****************************** condition question selection, question parsing and sanitizing **************************** + + # Get question based on inventory and number of visits + presanitized = "" + + # Load default question + selectedQuestion = q_data.questions[0] + + # If conditional question matches, use it instead + if q_data.options.additionalQuestions + selectedQuestion = inventoryService.selectQuestion(q_data, $scope.inventory, inventoryService.visitedNodes) + + if selectedQuestion + # If the question text contains a string that doesn't pass angular's $sanitize check, it'll fail to display anything + # Instead, parse in advance, catch the error, and warn the user that the text was nasty + try + # Run question text thru pre-sanitize routine because $sanitize is fickle about certain characters like >, < + presanitized = selectedQuestion.text + for k, v of PRESANITIZE_CHARACTERS + presanitized = presanitized.replace k, v + $sanitize presanitized + + catch error + selectedQuestion.text = "*Question text removed due to malformed or dangerous HTML content*" + + unless q_data.options.asset then $scope.layout = "text-only" + else if presanitized != "" then $scope.layout = q_data.options.asset.align + else $scope.layout = "image-only" + + # Note: Micromarkdown is still adding a mystery newline or carriage return character to the beginning of most parsed strings (but not generated tags??) + if presanitized.length then parsedQuestion = micromarkdown.parse(presanitized) else parsedQuestion = "No question text provided." + + # hyperlinks are automatically converted into tags, except it loads content within the iframe. To circumvent this, need to dynamically add target="_blank" attribute to all generated URLs + parsedQuestion = addTargetToHrefs parsedQuestion + + $scope.question = + text : parsedQuestion, # questions MUST be an array, always 1 index w/ single text property. MMD converts markdown formatting into proper markdown syntax + layout: $scope.layout, + type : q_data.options.type, + id : q_data.options.id + materiaId: q_data.id + options: q_data.options + + # ******************************************* inventory item management ************************************** + # Remove new item alerts for i in $scope.inventory i.new = false @@ -197,47 +242,7 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) $scope.inventoryUpdateMessage = "Updates to inventory: " + addedItemsMessage + removedItemsMessage - # Get question based on inventory and number of visits - presanitized = "" - - # Load default question - selectedQuestion = q_data.questions[0] - - # If conditional question matches, use it instead - if q_data.options.additionalQuestions - selectedQuestion = inventoryService.selectQuestion(q_data, $scope.inventory, inventoryService.visitedNodes) - - if selectedQuestion - # If the question text contains a string that doesn't pass angular's $sanitize check, it'll fail to display anything - # Instead, parse in advance, catch the error, and warn the user that the text was nasty - try - # Run question text thru pre-sanitize routine because $sanitize is fickle about certain characters like >, < - presanitized = selectedQuestion.text - for k, v of PRESANITIZE_CHARACTERS - presanitized = presanitized.replace k, v - $sanitize presanitized - - catch error - selectedQuestion.text = "*Question text removed due to malformed or dangerous HTML content*" - - unless q_data.options.asset then $scope.layout = "text-only" - else if presanitized != "" then $scope.layout = q_data.options.asset.align - else $scope.layout = "image-only" - - - # Note: Micromarkdown is still adding a mystery newline or carriage return character to the beginning of most parsed strings (but not generated tags??) - if presanitized.length then parsedQuestion = micromarkdown.parse(presanitized) else parsedQuestion = "No question text provided." - - # hyperlinks are automatically converted into tags, except it loads content within the iframe. To circumvent this, need to dynamically add target="_blank" attribute to all generated URLs - parsedQuestion = addTargetToHrefs parsedQuestion - - $scope.question = - text : parsedQuestion, # questions MUST be an array, always 1 index w/ single text property. MMD converts markdown formatting into proper markdown syntax - layout: $scope.layout, - type : q_data.options.type, - id : q_data.options.id - materiaId: q_data.id - options: q_data.options + # *************************************** answer generation ****************************************** $scope.answers = [] @@ -305,6 +310,8 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) $scope.q_data = q_data + # ************************************ layout formatting ******************************** + # TODO Add back in with Layout support # check if question has an associated asset (for now, just an image) # if $scope.question.type is $scope.HOTSPOT then $scope.question.layout = LAYOUT_VERT_TEXT @@ -316,6 +323,8 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) else $scope.question.video = $sce.trustAsResourceUrl($scope.question.options.asset.url) + # ************************************ node type specific follow-ups **************************** + switch q_data.options.type when $scope.OVER then _end() # Creator doesn't pass a value like this back yet / technically this shouldn't be called - the end call is made is _handleAnswerSelection when $scope.NARR, $scope.END then handleTransitional q_data diff --git a/src/src-assets/score-assets/score.coffee b/src/src-assets/score-assets/score.coffee index e5a4b767..dea3e218 100644 --- a/src/src-assets/score-assets/score.coffee +++ b/src/src-assets/score-assets/score.coffee @@ -33,7 +33,7 @@ angular.module('Adventure', ['ngSanitize']) if !question.options.items then return items for item in question.options.items - if item.firstVisitOnly and inventoryService.getNodeVisitedCount() > 0 then continue + if item.firstVisitOnly and inventoryService.getNodeVisitedCount(question) > 0 then continue # positive delta? Add the item to the inventory, or increase the count if it's in there already if item.count > 0 From 13052875e2d1ddef08057074aa54faa00b7d07cb Mon Sep 17 00:00:00 2001 From: Corey Peterson Date: Wed, 29 May 2024 13:19:57 -0400 Subject: [PATCH 50/57] Temporarily disabled unreachable nodes validation pending re-review --- .../creator-assets/controllers.coffee | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/src-assets/creator-assets/controllers.coffee b/src/src-assets/creator-assets/controllers.coffee index 18be7735..b6998f3e 100644 --- a/src/src-assets/creator-assets/controllers.coffee +++ b/src/src-assets/creator-assets/controllers.coffee @@ -298,30 +298,30 @@ angular.module "Adventure" # Check for errors validation = treeSrv.validateTreeOnSave $scope.treeData - # Check if there are any unreachable destinations if the inventory system is enabled - if ($scope.inventoryItems.length > 0) - visitedNodes = new Map() - unvisitedNodes = new Map() - inventory = new Map() - unreachableDestinations = treeSrv.findUnreachableDestinations $scope.treeData, $scope.treeData, visitedNodes, unvisitedNodes, inventory - if (unreachableDestinations.size > 0) - # Get all nodes that are not in reachableDestinations - # create error at each node that is not in reachableDestinations - $scope.validation.errors = [] - unreachableDestinations = Array.from(unreachableDestinations.values()) - - for node in unreachableDestinations - nodeId = if node.options then node.options.id or node.id else node.id - if (node.parentId == -1) - # start node id is 0 - nodeId = 0 - $scope.validation.errors.push({ - type: "unreachable_destination", - node: nodeId, - message: "Destination " + treeSrv.integerToLetters(nodeId) + " is unreachable!" - }) - $rootScope.$broadcast "validation.error" - return Materia.CreatorCore.cancelSave '' + # # Check if there are any unreachable destinations if the inventory system is enabled + # if ($scope.inventoryItems.length > 0) + # visitedNodes = new Map() + # unvisitedNodes = new Map() + # inventory = new Map() + # unreachableDestinations = treeSrv.findUnreachableDestinations $scope.treeData, $scope.treeData, visitedNodes, unvisitedNodes, inventory + # if (unreachableDestinations.size > 0) + # # Get all nodes that are not in reachableDestinations + # # create error at each node that is not in reachableDestinations + # $scope.validation.errors = [] + # unreachableDestinations = Array.from(unreachableDestinations.values()) + + # for node in unreachableDestinations + # nodeId = if node.options then node.options.id or node.id else node.id + # if (node.parentId == -1) + # # start node id is 0 + # nodeId = 0 + # $scope.validation.errors.push({ + # type: "unreachable_destination", + # node: nodeId, + # message: "Destination " + treeSrv.integerToLetters(nodeId) + " is unreachable!" + # }) + # $rootScope.$broadcast "validation.error" + # return Materia.CreatorCore.cancelSave '' else validation = [] From 8672367599a7018b46fa7598bfbb21edd5949d22 Mon Sep 17 00:00:00 2001 From: Corey Peterson Date: Wed, 12 Jun 2024 16:46:18 -0400 Subject: [PATCH 51/57] Adjusts condi question help text wording. Fixes blank node rendering in score screen. --- src/creator.html | 2 +- src/src-assets/score-assets/score.coffee | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/creator.html b/src/creator.html index 401b09f4..68143ace 100644 --- a/src/creator.html +++ b/src/creator.html @@ -1360,7 +1360,7 @@

    Conditional End Text

    Choose to display different narratives based on the number of times the player has visited this destination.

    Choose to display different narratives based on the player's number of visits to this destination and/or what items they have in their inventory.

    - How the widget chooses which one to display: If a player meets the requirements for more than one question, then it will choose the one that has not been shown yet. And if there is more than one question that hasn't been shown yet, it will choose the one with the most required items and visits. + How the widget chooses which option to display: If a player meets the requirements for more than one option, the option that has not yet been displayed will be chosen. If more than one option meeting the criteria hasn't been displayed yet, the option with the most required items and visits will be selected.

    {{invalidRequiredVisits}}

    diff --git a/src/src-assets/score-assets/score.coffee b/src/src-assets/score-assets/score.coffee index dea3e218..b580584a 100644 --- a/src/src-assets/score-assets/score.coffee +++ b/src/src-assets/score-assets/score.coffee @@ -111,14 +111,16 @@ angular.module('Adventure', ['ngSanitize']) if response.type == 'SCORE_FINAL_FROM_CLIENT' question = _getQuestionByNodeId qset, response.node_id rowQuestion = response.data[0] - if question.options.additionalQuestions + + if question.options.additionalQuestions and question.options.additionalQuestions.length > 0 rowQuestion = inventoryService.selectQuestion question, _currentInventory, inventoryService.visitedNodes rowQuestion = rowQuestion.text + row = text: rowQuestion - # text: _manageConditionalQuestion question, response.data[0] # needs to work with conditional questions score: response.data[1] type: if response.blank_node then 'blank' else 'end' + table.push row $scope.showOlderQsetWarning = response.older_qset @@ -126,12 +128,13 @@ angular.module('Adventure', ['ngSanitize']) question = _getQuestion qset, response.id items = if question.options.items then question.options.items else [] rowQuestion = response.data[0] + if question.options.additionalQuestions and question.options.additionalQuestions.length > 0 rowQuestion = inventoryService.selectQuestion question, _currentInventory, inventoryService.visitedNodes rowQuestion = rowQuestion.text + row = question: rowQuestion - # question: _manageConditionalQuestion question, response.data[0] answer: response.data[1] type: question.options.type feedback: response.feedback From a3c9148bf2dfb07dfc0535340615e977e49462b7 Mon Sep 17 00:00:00 2001 From: Corey Peterson Date: Thu, 13 Jun 2024 10:16:52 -0400 Subject: [PATCH 52/57] fix to short answer matches not correctly recycling response value for each check --- src/src-assets/player-assets/player.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/src/src-assets/player-assets/player.coffee b/src/src-assets/player-assets/player.coffee index e3a62fb1..a0bcb984 100644 --- a/src/src-assets/player-assets/player.coffee +++ b/src/src-assets/player-assets/player.coffee @@ -436,6 +436,7 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) for j in [0...$scope.q_data.answers[i].options.matches.length] match = $scope.q_data.answers[i].options.matches[j] + response = originalResponse # Remove whitespace match = match.trim().split('').filter((letter) -> letter.match(/\w/)).join() From b1fd586763abd9049e70c6cb2930151eb6c19975 Mon Sep 17 00:00:00 2001 From: Corey Peterson Date: Thu, 13 Jun 2024 15:15:45 -0400 Subject: [PATCH 53/57] Fixed short answer match whitespace trim to retain symbols --- src/src-assets/player-assets/player.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/src-assets/player-assets/player.coffee b/src/src-assets/player-assets/player.coffee index a0bcb984..42562f15 100644 --- a/src/src-assets/player-assets/player.coffee +++ b/src/src-assets/player-assets/player.coffee @@ -439,8 +439,8 @@ angular.module('Adventure', ['ngAria', 'ngSanitize']) response = originalResponse # Remove whitespace - match = match.trim().split('').filter((letter) -> letter.match(/\w/)).join() - response = response.trim().split('').filter((letter) -> letter.match(/\w/)).join() + match = match.replace(/\s/g, '') + response = response.replace(/\s/g, '') # If matches are not character sensitive if (! $scope.q_data.answers[i].options.characterSensitive) From 86e2b14b40f911cda5c1647dcd6a205a8e4dcc42 Mon Sep 17 00:00:00 2001 From: Corey Peterson Date: Tue, 18 Jun 2024 14:04:34 -0400 Subject: [PATCH 54/57] Reworks bridge node rendering. Bridge nodes are always visible, styles adjusted, added compute logic to move them along paths to avoid occlusion --- src/src-assets/creator-assets/creator.scss | 19 +++-- .../creator-assets/directives.coffee | 69 ++++++++++++++++--- 2 files changed, 66 insertions(+), 22 deletions(-) diff --git a/src/src-assets/creator-assets/creator.scss b/src/src-assets/creator-assets/creator.scss index 8b3c398b..91f8a4e9 100644 --- a/src/src-assets/creator-assets/creator.scss +++ b/src/src-assets/creator-assets/creator.scss @@ -510,29 +510,26 @@ g.node { g.bridge { - opacity: 0; - cursor: pointer; -webkit-transition: opacity 0.5s; transition: opacity 0.5s; - circle { - - // -webkit-transition: opacity 0.5s; - // transition: opacity 0.5s; + circle.node-dot { + fill: $gray; } path { - // opacity: 0; + opacity: 0; fill: $white; + + transition: opacity 0.5s; } &:hover { - // circle { - // opacity: 0.8; - // } - opacity: 0.8; + path { + opacity: 1; + } } } diff --git a/src/src-assets/creator-assets/directives.coffee b/src/src-assets/creator-assets/directives.coffee index fb200c20..8213dd1f 100644 --- a/src/src-assets/creator-assets/directives.coffee +++ b/src/src-assets/creator-assets/directives.coffee @@ -117,6 +117,22 @@ angular.module "Adventure" $scope.previewNodeMode = false $scope.render treeSrv.get() + + # helper function to determine the closest pixel distance between one node and the set of all other nodes in the tree + # used with bridge nodes (in-between nodes) to determine whether the "add an in-between node" option is too close to another one + _getDistanceToClosestAdjacentNode = (nodes, target) -> + closestNode = null + closestDistance = Infinity + + for node in nodes + if node != target + distance = Math.sqrt(Math.pow(node.x - target.x, 2) + Math.pow(node.y - target.y, 2)) + if distance < closestDistance + closestNode = node + closestDistance = distance + + closestDistance + $scope.render = (data) -> unless data? then return false @@ -243,6 +259,9 @@ angular.module "Adventure" source = link.source target = link.target + # don't create bridge nodes if either the source or target is blank + if source.type is "blank" or target.type is "blank" then return + # Disable bridge nodes on loopbacks if link.specialCase is "loopBack" then return else @@ -390,7 +409,7 @@ angular.module "Adventure" midpoint = pathNode.getPointAtLength(pathNode.getTotalLength()/2) midX = midpoint.x midY = midpoint.y - + # compute the position of the lock icon along the path quarterpoint = pathNode.getPointAtLength(pathNode.getTotalLength()/4) links[index].lock.x = quarterpoint.x @@ -399,9 +418,22 @@ angular.module "Adventure" # Now find the associated bridge node using the bridgeNodeIndex flag on the given link # And update its X,Y coordinates for the new midpoint location nodeIndex = links[index].bridgeNodeIndex + nodes[nodeIndex].x = midX nodes[nodeIndex].y = midY + # compute distance to the closest node + distance = _getDistanceToClosestAdjacentNode nodes, nodes[nodeIndex] + + # if the distance is too close, nudge the bridge node along the link path + while distance < 36 + midpointOffset = pathNode.getPointAtLength((pathNode.getTotalLength()/2) + distance * 2) + + nodes[nodeIndex].x = midpointOffset.x + nodes[nodeIndex].y = midpointOffset.y + + # re-compute distance in case nudging the bridge node moved it too close to a different node + distance = _getDistanceToClosestAdjacentNode nodes, nodes[nodeIndex] else if links[index].specialCase and links[index].specialCase is "loopBack" @@ -531,9 +563,15 @@ angular.module "Adventure" $scope.onHover {data: d} # Animation effects on node mouseover - d3.select(this).select("circle") - .transition() - .attr("r", 30) + # expansion radius dependent on node type (bridge or regular node) + if d.type is 'bridge' + d3.select(this).select("circle") + .transition() + .attr("r", 16) + else + d3.select(this).select("circle") + .transition() + .attr("r", 30) ) .on("mouseout", (d, i) -> @@ -541,10 +579,15 @@ angular.module "Adventure" $scope.onHoverOut {data: d} - # Animation effects on node mouseout - d3.select(this).select("circle") - .transition() - .attr("r", 20) + # Animation effects on node mouseover + if d.type is 'bridge' + d3.select(this).select("circle") + .transition() + .attr("r", 3) + else + d3.select(this).select("circle") + .transition() + .attr("r", 20) ) .on("click", (d, i) -> $scope.nodeClick {data: d} # when clicked, we return all of the node's data @@ -585,9 +628,13 @@ angular.module "Adventure" nodeGroup.append("svg:circle") .attr("class", "node-dot") .attr("r", (d) -> - return 20 # sets size of node bubbles + # sets initial node radius. normal nodes are 20px; bridge nodes are 3 + if d.type is 'bridge' then return 3 + else return 20 + ) + .attr("filter", (d) -> + if d.type isnt 'bridge' then return "url(#dropshadow)" ) - .attr("filter", "url(#dropshadow)") # Icons displayed inside the node circles nodeGroup.append("svg:image") @@ -728,7 +775,7 @@ angular.module "Adventure" # The "+" symbol displayed on bridge nodes (the pseudo-nodes between nodes you can click to add an in-between node) nodeGroup.append("path") - .attr("d", "M -3,12 L -3,3 L -12,3 L -12,-3 L -3,-3 L -3,-12 L 3,-12 L 3,-3 L 12,-3 L 12,3 L 3,3 L 3,12 Z") + .attr("d", "M -0.5,3 L -0.5,0.5 L -3,0.5 L -3,-0.5 L -0.5,-0.5 L -0.5,-3 L 0.5,-3 L 0.5,-0.5 L 3,-0.5 L 3,0.5 L 0.5,0.5 L 0.5,3 Z") .attr("visibility", (d) -> if d.type isnt "bridge" then return "hidden" ) From 88c5e1769315e0507842d87f5271185c0454c01a Mon Sep 17 00:00:00 2001 From: Corey Peterson Date: Tue, 18 Jun 2024 15:40:23 -0400 Subject: [PATCH 55/57] Fix for meltdown caused by adding in-between nodes along existing node links --- src/src-assets/creator-assets/directives.coffee | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/src-assets/creator-assets/directives.coffee b/src/src-assets/creator-assets/directives.coffee index 8213dd1f..ac52aebb 100644 --- a/src/src-assets/creator-assets/directives.coffee +++ b/src/src-assets/creator-assets/directives.coffee @@ -409,16 +409,22 @@ angular.module "Adventure" midpoint = pathNode.getPointAtLength(pathNode.getTotalLength()/2) midX = midpoint.x midY = midpoint.y + + if links[index].lock + # compute the position of the lock icon along the path + quarterpoint = pathNode.getPointAtLength(pathNode.getTotalLength()/4) + links[index].lock.x = quarterpoint.x + links[index].lock.y = quarterpoint.y - # compute the position of the lock icon along the path - quarterpoint = pathNode.getPointAtLength(pathNode.getTotalLength()/4) - links[index].lock.x = quarterpoint.x - links[index].lock.y = quarterpoint.y + # don't perform any midpoint computations if the bridgeNodeIndex isn't present in the link: links with a blank source or target no longer include them + unless links[index].bridgeNodeIndex then return # Now find the associated bridge node using the bridgeNodeIndex flag on the given link # And update its X,Y coordinates for the new midpoint location nodeIndex = links[index].bridgeNodeIndex + console.log links[index] + nodes[nodeIndex].x = midX nodes[nodeIndex].y = midY From 71f6c2646eae96178d2b951746712cf9185aebe5 Mon Sep 17 00:00:00 2001 From: Corey Peterson Date: Tue, 18 Jun 2024 15:42:03 -0400 Subject: [PATCH 56/57] final score minimum value now 0, not 1 --- src/creator.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/creator.html b/src/creator.html index 68143ace..645e0b68 100644 --- a/src/creator.html +++ b/src/creator.html @@ -1163,7 +1163,7 @@

    Required Items for Hotspot

    ng-class="{'invalid' : !finalScoreForm.$valid}" ng-model="finalScore" placeholder="100" - min="1" + min="0" max="100" /> % From 74245640ca2612e61889c93523b1a75911225132 Mon Sep 17 00:00:00 2001 From: Corey Peterson Date: Tue, 18 Jun 2024 15:48:48 -0400 Subject: [PATCH 57/57] Removed console log. Minor button tweaks. --- src/creator.html | 2 +- src/src-assets/creator-assets/creator.scss | 5 ++++- src/src-assets/creator-assets/directives.coffee | 2 -- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/creator.html b/src/creator.html index 645e0b68..48d343a4 100644 --- a/src/creator.html +++ b/src/creator.html @@ -472,7 +472,7 @@

    Just a sec!

    Careful! Deleting this destination will delete all child destinations, as well as its associated parent's answer. Are you sure?

    + ng-click="deleteNode()">Do It
    diff --git a/src/src-assets/creator-assets/creator.scss b/src/src-assets/creator-assets/creator.scss index 91f8a4e9..47c7062a 100644 --- a/src/src-assets/creator-assets/creator.scss +++ b/src/src-assets/creator-assets/creator.scss @@ -3237,9 +3237,12 @@ node-creation-selection-dialog { } button { + color: $white; + background: $color-accent; + &.managed { color: $white; - background: $color-accent; + background: $color-accent-highlight; } } } diff --git a/src/src-assets/creator-assets/directives.coffee b/src/src-assets/creator-assets/directives.coffee index ac52aebb..b14d78ba 100644 --- a/src/src-assets/creator-assets/directives.coffee +++ b/src/src-assets/creator-assets/directives.coffee @@ -423,8 +423,6 @@ angular.module "Adventure" # And update its X,Y coordinates for the new midpoint location nodeIndex = links[index].bridgeNodeIndex - console.log links[index] - nodes[nodeIndex].x = midX nodes[nodeIndex].y = midY