diff --git a/src/scoreScreen.html b/src/scoreScreen.html
index 414261c7..5b7ead2b 100644
--- a/src/scoreScreen.html
+++ b/src/scoreScreen.html
@@ -9,7 +9,7 @@
-
-
-
-
-
-
- {{itemSelection[getItemIndex(item)].name}} ({{item.count}})
-
-
-
-
-
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:
-
-
-
-
-
-
Items Gained
-
-
-
+ {{item.count}}
-
-
{{itemSelection[getItemIndex(item)].name}}
-
-
-
Items Lost
-
-
-
- {{-1*item.count}}
-
-
{{itemSelection[getItemIndex(item)].name}}
-
-
-
-
-
+
+
+
Review Your Journey
+
+
+ This play is from an earlier version of Adventure. Certain destinations you visited may not be displayed here.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{row.answer}}
+
+
+
Historical play records don't provide enough information to display the hotspot selection associated with this answer choice.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ getItemById(item.id).name }}
+
({{ item.count }})
+
+
+
+ {{ getItemById(item.id).name }}
+
({{ item.count }})
+
+
+
+
+
diff --git a/src/src-assets/creator-assets/controllers.coffee b/src/src-assets/creator-assets/controllers.coffee
index 9118d658..b6998f3e 100644
--- a/src/src-assets/creator-assets/controllers.coffee
+++ b/src/src-assets/creator-assets/controllers.coffee
@@ -228,6 +228,8 @@ angular.module "Adventure"
$scope.showItemManager = false
$scope.showItemIconSelector = false
$scope.editingIcons = false
+ $scope.showQuestionRequiredItems = false
+ $scope.showQuestions = false
$scope.showCustomNodeLabelEditor = false
$scope.resetNewNodeManager()
@@ -296,13 +298,12 @@ angular.module "Adventure"
# Check for errors
validation = treeSrv.validateTreeOnSave $scope.treeData
- # Check if there are any unreachable destinations if the inventory system is enabled
+ # # 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
diff --git a/src/src-assets/creator-assets/creator.scss b/src/src-assets/creator-assets/creator.scss
index c2c79322..47c7062a 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;
+ }
}
}
@@ -1130,136 +1127,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;
@@ -1276,13 +1143,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;
@@ -1308,7 +1180,7 @@ required-items-tooltip {
}
&.sub-heading {
- justify-content: start;
+ justify-content: flex-start;
h4 {
margin: 1em 0;
@@ -1333,10 +1205,6 @@ required-items-tooltip {
.item-instructions {
margin: 0 0 10px 0;
}
-
- .invalid-quantity-message {
- color: $red;
- }
}
ul li, .options-bar {
@@ -1379,7 +1247,7 @@ required-items-tooltip {
.row {
display: flex;
flex-direction: row;
- justify-content: end;
+ justify-content: flex-end;
}
img {
@@ -1452,7 +1320,7 @@ required-items-tooltip {
.blank-selection {
width: 100%;
height: 2em;
- justify-content: end;
+ justify-content: flex-end;
background-color: $gray-lighter;
border-radius: 10px;
@@ -1550,10 +1418,6 @@ required-items-tooltip {
align-self: center;
justify-content: center;
- &.small-name {
- max-width: 100px;
- }
-
.item-name {
font-weight: bold;
}
@@ -1577,6 +1441,11 @@ required-items-tooltip {
min-width: 30px;
max-width: 30px;
padding: 0;
+
+ &.wide {
+ max-width: none;
+ padding: 5px 10px;
+ }
}
}
@@ -1607,7 +1476,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;
@@ -1618,7 +1487,7 @@ required-items-tooltip {
align-items: center;
flex-direction: row;
justify-content: center;
- gap: 15px;
+ gap: 0.5em;
padding: 0 10px;
input {
@@ -1844,19 +1713,24 @@ item-icon-selector {
}
.item-modal {
- display: flex;
flex-direction: column;
justify-content: space-between;
background-color: $color-primary;
+ display: none;
+
+ &.show {
+ display: flex;
+ }
&.required-item-modal.show {
+ display: flex;
position: fixed;
- height: 63% !important;
- width: 56%;
- top: 120px;
- left: -200px;
- right: 0;
+ top: 15%;
+ height: 65%;
+ width: 65%;
+ max-width: 850px;
margin: auto;
+ padding-bottom: 5px;
.arrow-right {
position: absolute;
@@ -1944,6 +1818,155 @@ item-icon-selector {
}
+
+/** For node item editor sections only **/
+.item-selection {
+
+ .item-instructions {
+ font-size: 12px;
+ font-style: italic;
+ color: $gray-darker;
+
+ &.invalid-quantity-message {
+ color: $red;
+ font-weight: bold;
+ }
+ }
+
+ /** 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) {
+
+ // height: 100% !important;
+ // max-height: 100%;
+ // min-width: 300px;
+ }
+ }
+ .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: 0.5em;
+ padding: 10px 10px 10px 0;
+
+ input {
+ width: auto;
+ 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;
+ 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: flex-end;
+ flex-direction: column;
+ padding: 0 10px;
+ align-content: flex-end;
+ min-width: 150px;
+ max-width: 150px;
+
+ &.flex-start {
+ justify-content: flex-start;
+ align-content: flex-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;
@@ -2405,10 +2428,10 @@ 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;
+ flex-grow: 2;
max-height: 80%;
padding: 10px;
overflow: auto;
@@ -2433,26 +2456,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;
@@ -2462,8 +2485,9 @@ node-creation-selection-dialog {
}
.require-item-btn {
- // width: 100%;
height: 100%;
+ max-height: 140px;
+ min-width: 90px;
position: relative;
display: flex;
flex-direction: column-reverse;
@@ -2549,30 +2573,16 @@ node-creation-selection-dialog {
&.hotspot {
max-height: 650px;
- textarea {
- resize: none;
- }
+ .node-creation-content {
+ flex-direction: row;
- .panel-right {
- display: none;
+ @media screen and (max-width: $node-creation-panels-width) {
+ flex-direction: column;
+ }
}
- .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;
- }
- }
+ textarea {
+ resize: none;
}
}
@@ -2599,8 +2609,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%;
@@ -2651,6 +2660,10 @@ node-creation-selection-dialog {
}
}
+ #advanced-question-btn {
+ margin-right: 20px;
+ }
+
#big-upload-button {
text-align: center;
height:fit-content;
@@ -2669,8 +2682,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;
@@ -2714,6 +2727,10 @@ node-creation-selection-dialog {
&.tiny {
margin: 5px;
}
+
+ .question-editor {
+ flex-grow: 2;
+ }
}
.media-box {
@@ -2806,6 +2823,7 @@ node-creation-selection-dialog {
.hide-answer-tooltip {
position: absolute;
+ z-index: $z-index-modal-dialog + 10;
top: 45px;
width: 100px;
background: $blue;
@@ -2830,17 +2848,46 @@ node-creation-selection-dialog {
flex-grow: 1;
justify-content: space-between;
- .match-options {
+ .match-options-container {
display: flex;
- flex-direction: row;
- align-items: center;
- font-size: 0.8em;
- color: $gray-darker;
+ flex-direction: column;
+ padding: 0.25em 1em 0.25em 0.5em;
+ background: $color-primary;
+ border-radius: 5px;
- #characterSensitive {
- margin-left: 20px;
+ h4 {
+ margin: 0.25em 0;
+ font-size: 0.9em;
+ }
+
+ p {
+ margin: 0 0 .5em 0;
+ font-size: 0.8em;
+ }
+
+ .match-options {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ padding-left: 1em;
+ font-size: 0.8em;
+ color: $gray-darker;
+
+ list-style-type: none;
+
+ li {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ margin: 0;
+ input {
+ margin: 0 0.5em;
+ }
+ }
}
}
+
+
}
.add-match-box {
@@ -2893,6 +2940,7 @@ node-creation-selection-dialog {
align-items: center;
grid-gap: 10px;
height: 100%;
+ max-height: 140px;
padding: 10px;
border: 1px solid $color-accent;
@@ -2982,7 +3030,12 @@ node-creation-selection-dialog {
&.short-answer-mode {
+ .answer-options {
+ align-items: flex-start;
+ }
+
.possible-answers-box {
+ position: relative;
background: $gray-lighter;
border: solid 1px #cccccc;
border-radius: 5px;
@@ -3184,9 +3237,12 @@ node-creation-selection-dialog {
}
button {
+ color: $white;
+ background: $color-accent;
+
&.managed {
color: $white;
- background: $color-accent;
+ background: $color-accent-highlight;
}
}
}
@@ -3289,7 +3345,7 @@ node-creation-selection-dialog {
img {
position: relative;
-
+
z-index: $z-index-modal + 1;
max-width: 100%;
height: 400px;
@@ -3803,6 +3859,222 @@ validation-dialog {
}
}
+advanced-question-editor {
+ position: absolute;
+ z-index: $z-index-modal-dialog;
+ left: 5%;
+ top: 10%;
+
+ width: 90%;
+ max-width: 1280px;
+ max-height: 75%;
+ min-height: 300px;
+ border-radius: 10px;
+ background: $color-primary;
+ box-shadow: 0px 0px 10px #888888;
+
+ display: flex;
+ 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;
+
+ color: $gray-darker;
+ font-size: 0.9em;
+
+ p {
+ display: block;
+ margin: 0 0 1em 0;
+
+ span.highlight {
+ font-weight: bold;
+ }
+ }
+
+ &.invalid-quantity-message {
+ padding: 0;
+ font-weight: bold;
+ color: $red;
+ }
+
+ &.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 20px;
+ 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;
+
+ label {
+ margin-bottom: 1em;
+ font-size: 0.8em;
+ font-weight: bold;
+ }
+
+ input {
+ height: 2em;
+ width: 75px;
+ }
+ }
+
+ .require-item-btn {
+ 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%;
@@ -3810,7 +4082,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 8460e423..b14d78ba 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,18 +409,35 @@ 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
+
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 +567,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 +583,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 +632,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 +779,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"
)
@@ -996,6 +1047,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) ->
@@ -1557,6 +1609,7 @@ angular.module "Adventure"
answer.matches = []
answer.caseSensitive = false
answer.characterSensitive = false
+ answer.partialMatches = false
# Create the new node associated with the [Unmatched Response] answer
newDefaultId = $scope.addNode $scope.nodeTools.target, $scope.BLANK
@@ -1573,6 +1626,7 @@ angular.module "Adventure"
isDefault: true
caseSensitive: false
characterSensitive: false
+ partialMatches: false
# The new answer has to take the 0 index spot in the answers array
node.answers.splice 0, 0, newDefault
@@ -1980,6 +2034,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;
@@ -2063,9 +2119,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
@@ -2131,16 +2187,100 @@ angular.module "Adventure"
if(hotspot)
hotspot.style.zIndex = 100;
+ $scope.closeModals = () ->
+ $scope.showItemSelection = false
+ $scope.showRequiredItems = false
+ $scope.showQuestionRequiredItems = false
+ $scope.showQuestions = false
+
+ $scope.toggleQuestionsEditor = () ->
+ if $scope.showQuestions
+ $scope.showQuestions = false
+ return
+ # Close all modals
+ $scope.closeModals()
+ # Reopen this modal
+ $scope.showQuestions = true
+ if $scope.editedNode.questions.length <= 0
+ $scope.newQuestion()
+
+ $scope.toggleQuestionRequiredItemsModal = (question) ->
+ $scope.showDropdown = false
+ # Same question, close the modal
+ if $scope.currentQuestion is question and $scope.showQuestionRequiredItems
+ $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.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 questions array
+ $scope.editedNode.questions.push newQuestion
+
+ # Inform the auto-scroll-and-focus directive that a new question is added
+ $rootScope.$broadcast "editedNode.questions.added"
+
+ $scope.removeQuestion = (index) ->
+ # Remove question from questions array
+ $scope.editedNode.questions.splice(index, 1)
+
$scope.toggleNodeItemsModal = () ->
$scope.showDropdown = false
# Close node items modal
if $scope.showItemSelection is true
$scope.showItemSelection = false
return
+ # Close modals
+ $scope.closeModals()
# Open node items modal
$scope.showItemSelection = true
- # Close required items modal
- $scope.toggleRequiredItemsModal(null)
# Show advanced options on start only if advanced options are enabled
if !$scope.showAdvancedOptions
@@ -2176,7 +2316,7 @@ angular.module "Adventure"
$scope.toggleRequiredItemsModal = (answer = null) ->
$scope.showDropdown = false
# Same answer, close the modal
- if $scope.currentAnswer is answer
+ if $scope.currentAnswer is answer and $scope.showRequiredItems
$scope.currentAnswer = null
# Different answer
else
@@ -2190,8 +2330,10 @@ angular.module "Adventure"
$scope.showRequiredItems = false
if $scope.showRequiredItems
- # Close node items modal
- $scope.showItemSelection = false
+ # Close all modals
+ $scope.closeModals()
+ # Reopen modal
+ $scope.showRequiredItems = true
if document.querySelector('hotspot-answer-manager')
document.querySelector('hotspot-answer-manager').style.zIndex = 100;
@@ -2342,12 +2484,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
@@ -2362,7 +2504,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)
@@ -2372,8 +2514,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
@@ -2411,13 +2553,15 @@ 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
newAnswer.matches = []
newAnswer.caseSensitive = false
newAnswer.characterSensitive = false
+ newAnswer.partialMatches = false
+
$scope.answers.push newAnswer
# Refresh all answerLinks references as some have changed
@@ -2561,18 +2705,15 @@ angular.module "Adventure"
while j < $scope.answers[i].matches.length
# Remove whitespace
- matchTo = $scope.answers[i].matches[j].trim()
- matchTo = matchTo.split('').filter((letter) -> ! letter.match(/\W/g)).join()
-
- matchFrom = $scope.newMatch.trim()
- matchFrom = matchFrom.split('').filter((letter) -> ! letter.match(/\W/)).join()
+ matchTo = $scope.answers[i].matches[j].replace(/\s/g, '')
+ matchFrom = $scope.newMatch.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()
@@ -3178,6 +3319,26 @@ angular.module "Adventure"
), 100
]
+.directive "autoScrollAndSelectQuestion", ['$timeout', ($timeout) ->
+ restrict: "A",
+ link: ($scope,$element, $attrs) ->
+
+ # 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:
# - when the qset is loaded and a problem is detected (node doesn't exist)
# - when the widget is published and problems are detected
diff --git a/src/src-assets/creator-assets/services.coffee b/src/src-assets/creator-assets/services.coffee
index 220eb6b7..dc738263 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)])
+ # 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)
- return unvisitedNodes
+ unvisitedNodes
# Recursive function for adding a node in between a given parent and child, essentially splitting an existing link
@@ -519,6 +539,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
@@ -592,13 +620,63 @@ angular.module "Adventure"
parentId: tree.parentId
type: tree.type
redirectId: tree.redirectId
- items: questionItemData
+ items: questionItemData,
+ additionalQuestions: []
answers: []
- question =
- text: if tree.question then tree.question else ""
+ # Load default question
+ if tree.questions and tree.questions[0]
+ itemData.questions.push tree.questions[0]
+ else
+ itemData.questions.push
+ text: ""
- itemData.questions.push question
+ # Load conditional questions
+ angular.forEach tree.questions, (question, index) ->
+ if (index < 1) then return
+ requiredItemsData = []
+
+ 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
+ requiredItems: requiredItemsData
+ requiredVisits: question.requiredVisits || 0
+
+ itemData.options.additionalQuestions.push itemQuestionData
if tree.media
itemData.options.asset =
@@ -674,13 +752,14 @@ 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"
itemAnswerData.options.matches = answer.matches
itemAnswerData.options.caseSensitive = answer.caseSensitive
itemAnswerData.options.characterSensitive = answer.characterSensitive
+ itemAnswerData.options.partialMatches = answer.partialMatches or false
if answer.isDefault then itemAnswerData.options.isDefault = true
when "hotspot"
@@ -724,8 +803,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 =
@@ -754,6 +831,61 @@ angular.module "Adventure"
if item.options.hasLinkToSelf then node.hasLinkToSelf = true
if item.options.pendingTarget then node.pendingTarget = item.options.pendingTarget
+ if item.questions
+ node.questions = item.questions
+ else
+ node.questions = []
+
+ if item.options.additionalQuestions
+ angular.forEach item.options.additionalQuestions, (question, index) ->
+ unless node.questions then node.questions = []
+
+ requiredItemsData = []
+ 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
+
+ nodeQuestion =
+ text: question.text
+ id: generateAnswerHash()
+ requiredItems: requiredItemsData
+ requiredVisits: question.requiredVisits || 0
+
+ node.questions.push nodeQuestion
+
if item.options.customLabel then node.customLabel = item.options.customLabel
angular.forEach item.answers, (answer, index) ->
@@ -808,13 +940,14 @@ 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"
nodeAnswer.matches = answer.options.matches
nodeAnswer.caseSensitive = answer.options.caseSensitive
nodeAnswer.characterSensitive = answer.options.characterSensitive
+ nodeAnswer.partialMatches = answer.options.partialMatches or false
if answer.options.isDefault then nodeAnswer.isDefault = true
diff --git a/src/src-assets/player-assets/player.coffee b/src/src-assets/player-assets/player.coffee
index 6d306bf5..42562f15 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"
@@ -90,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
@@ -110,24 +108,37 @@ 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"
+ # ****************************** 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
- # 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
+ selectedQuestion.text = "*Question text removed due to malformed or dangerous HTML content*"
- catch error
- q_data.questions[0].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 = ""
+ 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
@@ -140,6 +151,8 @@ angular.module('Adventure', ['ngAria', 'ngSanitize'])
materiaId: q_data.id
options: q_data.options
+ # ******************************************* inventory item management **************************************
+
# Remove new item alerts
for i in $scope.inventory
i.new = false
@@ -153,27 +166,28 @@ angular.module('Adventure', ['ngAria', 'ngSanitize'])
$scope.inventoryUpdateMessage = ""
# Add items to player's inventory
- if $scope.question.options.items and $scope.question.options.items[0]
-
- $scope.showInventoryBtn = true
+ # if q_data.options.items and q_data.options.items[0]
+ if q_data.options.items and q_data.options.items[0]
# 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
count: q_i.count || 1
takeAll: q_i.takeAll || false
firstVisitOnly: q_i.firstVisitOnly || false
+ recency: inventoryService.recencyCounter
$scope.questionItems.push item
+ inventoryService.recencyCounter++
for q_i in $scope.questionItems
do (q_i) ->
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 (inventoryService.getNodeVisitedCount(q_data) > 0 and q_i.firstVisitOnly)
# Move to next item
else
# Inventory update
@@ -208,6 +222,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")
@@ -227,6 +242,8 @@ angular.module('Adventure', ['ngAria', 'ngSanitize'])
$scope.inventoryUpdateMessage = "Updates to inventory: " + addedItemsMessage + removedItemsMessage
+ # *************************************** answer generation ******************************************
+
$scope.answers = []
if q_data.answers
@@ -270,19 +287,7 @@ angular.module('Adventure', ['ngAria', 'ngSanitize'])
uncappedMax: uncappedMax
# Format range for pre-existing items without the range property
- if item.range is ""
- if item.uncappedMax and item.minCount is 0
- item.range = "any amount"
- else if item.minCount is 0 and item.maxCount is 0
- item.range = "none"
- else if item.uncappedMax
- item.range = "at least #{item.minCount}"
- else if item.minCount is 0
- item.range = "no more than #{item.maxCount}"
- else if item.minCount is item.maxCount
- item.range = "#{item.minCount}"
- else
- item.range = "#{item.minCount} to #{item.maxCount}"
+ _assignRange item
requiredItems.push item
@@ -293,7 +298,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
@@ -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
@@ -326,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)
+ inventoryService.addNodeToVisited(q_data)
$scope.dismissUpdates = () ->
$scope.inventoryUpdate = false
@@ -369,29 +378,8 @@ 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
+ $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) ->
@@ -399,13 +387,17 @@ 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
+ # answers[index] will be inaccurate if answers are randomized !!!
+ requiredItems = getAnswerByIndex(index).requiredItems || []
- $scope.missingRequiredItems = $scope.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});")
+ # Add range value to required items
+ $scope.missingRequiredItems.map((item) -> _assignRange item)
$scope.next = null
return
@@ -414,9 +406,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
@@ -427,9 +420,12 @@ 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 = ""
+ 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]
@@ -439,13 +435,12 @@ 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]
+ 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)
@@ -459,36 +454,45 @@ angular.module('Adventure', ['ngAria', 'ngSanitize'])
match = match.toLowerCase()
response = response.toLowerCase()
- if match is response
- requiredItems = $scope.q_data.answers[i].options.requiredItems || $scope.q_data.answers[i].requiredItems
- missingItems = $scope.checkInventory(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 = $scope.checkInventory(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});")
- $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()
- return true
+ 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
# 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
if answer.options.isDefault
- $scope.selectedAnswer = response
+ $scope.selectedAnswer = originalResponse
_logProgress() # Log the response
link = ~~answer.options.link
@@ -515,6 +519,11 @@ angular.module('Adventure', ['ngAria', 'ngSanitize'])
$scope.missingRequiredItems = []
$scope.missingRequiredItemsAltText = ""
+ getAnswerByIndex = (index) ->
+ for answer in $scope.answers
+ if answer.index == index then return answer
+ return null
+
handleMultipleChoice = (q_data) ->
$scope.type = $scope.MC
@@ -548,7 +557,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
@@ -569,11 +578,21 @@ angular.module('Adventure', ['ngAria', 'ngSanitize'])
# $scope.question.text = "[Destination requires item(s): [#{itemArray.toString(', ')}]]"
# $scope.link = $scope.question.options.parentId
- # Submit the user's response to the logs
- _logProgress = ->
+ _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
- if $scope.selectedAnswer isnt null # TODO is this check required??
- Materia.Score.submitQuestionForScoring $scope.question.materiaId, $scope.selectedAnswer
+ # Submit the user's response to the logs
+ _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
@@ -599,6 +618,20 @@ angular.module('Adventure', ['ngAria', 'ngSanitize'])
if $scope.qset.items[n].options.items and $scope.qset.items[n].options.items[0] then return true
false
+ _assignRange = (item) ->
+ if item.uncappedMax and item.minCount is 0
+ item.range = "any amount"
+ else if item.minCount is 0 and item.maxCount is 0
+ item.range = "none"
+ else if item.uncappedMax
+ item.range = "at least #{item.minCount}"
+ else if item.minCount is 0
+ item.range = "no more than #{item.maxCount}"
+ else if item.minCount is item.maxCount
+ item.range = "#{item.minCount}"
+ else
+ item.range = "#{item.minCount} to #{item.maxCount}"
+
# Small script that inserts " target="_blank" " into a hrefs, preventing hyperlinks from displaying within the iframe.
addTargetToHrefs = (string) ->
@@ -799,7 +832,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) ->
@@ -813,7 +846,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($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 '')
diff --git a/src/src-assets/player-assets/player.scss b/src/src-assets/player-assets/player.scss
index 122d2144..722c47ab 100644
--- a/src/src-assets/player-assets/player.scss
+++ b/src/src-assets/player-assets/player.scss
@@ -530,10 +530,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;
@@ -571,6 +571,7 @@ h1 {
&.nonselectable {
cursor: default;
font-style: italic;
+ color: $gray;
.missing-item {
color: $red-lighter;
@@ -578,8 +579,6 @@ h1 {
}
.answer-text {
- max-width: calc(100% - 34px);
-
&.no-text-provided {
font-style: italic;
color: #696969;
@@ -587,31 +586,30 @@ h1 {
}
.answer-required-items {
+ min-width: 120px;
font-size: 0.8em;
margin-left: auto;
+ padding-left: 0.5em;
&:before {
- margin-left: 10px;
- padding: 5px;
- content: "Requires items: ";
+ padding: 0 0 0.25em 0;
+ content: "Requires items:";
font-size: 0.8em;
- font-style: italic;
+ font-style: normal;
+ color: $gray;
}
.required-item {
display: flex;
align-items: center;
flex-direction: row;
- min-width: 75px;
- max-width: 200px;
+ flex-wrap: wrap;
+ width: 180px;
- margin: 5px 5px 5px 0;
- padding: 5px;
-
- background: $color-primary;
- border: solid 1px $gray-lighter;
- // background: $gray-lightest;
- border-radius: 3px;
+ font-style: normal;
+ font-size: 0.9em;
+ line-height: 1.5em;
+ padding: 0.25em;
img {
width: 24px;
@@ -767,6 +765,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;
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 263e5748..b580584a 100644
--- a/src/src-assets/score-assets/score.coffee
+++ b/src/src-assets/score-assets/score.coffee
@@ -1,83 +1,185 @@
-angular.module('AdventureScorescreen', ['ngSanitize'])
+angular.module('Adventure', ['ngSanitize'])
-## CONTROLLER ##
+.controller 'AdventureScoreCtrl', ['$scope','$sanitize', '$sce', '$timeout', 'inventoryService', 'legacyQsetSrv', ($scope, $sanitize, $sce, $timeout, inventoryService, legacyQsetSrv) ->
-## UNFINISHED ##
-.controller 'AdventureScoreCtrl', ['$scope','$sanitize', '$sce', ($scope, $sanitize, $sce) ->
-
- materiaCallbacks = {}
-
- $scope.inventory = []
$scope.responses = []
$scope.itemSelection = []
$scope.customTable = false
+ $scope.showOlderQsetWarning = false
+
+ _qsetItems = []
+ _currentInventory = []
+
+ _getHeight = () ->
+ Math.ceil(parseFloat(window.getComputedStyle(document.querySelector('html')).height))
+
+ _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
+
+ # 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 = []
+
+ if !question.options.items then return items
+
+ for item in question.options.items
+ 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
+
+ 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
+ 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 = inventoryService.recencyCounter
+ _currentInventory.push angular.copy itemCopy
+
+ 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)
- $scope.setSelectedItem = (item) ->
- $scope.selectedItem = item
+ 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)
- $scope.getItemIndex = (item) ->
- if (item)
- for i, index in $scope.itemSelection
- if i.id is item.id
- return index
+ items.push itemRemoved
- $scope.getQuestion = (qset, id) ->
+ inventoryService.addNodeToVisited question
+
+ return items
+
+ _getQuestion = (qset, id) ->
for i in qset.items
- if i.id is id
- return i
- return -1
+ if i.id is id then return i
+ return null
- $scope.createInventoryFromResponses = (qset, responses) ->
- inventory = []
+ _getQuestionByNodeId = (qset, nodeId) ->
+ for i in qset.items
+ if i.options.id is parseInt(nodeId) then return i
+ return 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)
+ $scope.getItemById = (id) ->
+ for item in _qsetItems
+ if item.id == id then return item
+ null
- return inventory
+ $scope.getItemUrl = (id) ->
+ for item in _qsetItems
+ 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'
+ question = _getQuestionByNodeId qset, response.node_id
+ 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 =
+ text: rowQuestion
+ score: response.data[1]
+ type: if response.blank_node then 'blank' else 'end'
+
+ table.push row
+
+ $scope.showOlderQsetWarning = response.older_qset
+ 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
+ rowQuestion = rowQuestion.text
+
+ row =
+ question: rowQuestion
+ answer: response.data[1]
+ type: question.options.type
+ feedback: response.feedback
+ 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])
+
+ # 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 = image
+
+ inventoryService.recencyCounter++
+ table.push row
+
return table
+ # $scope.mmd_parse = (text) ->
+ # console.log 'haha butts'
+ # return micromarkdown.parse text
- $scope.toggleInventoryDrawer = () ->
- $scope.showInventory = !$scope.showInventory
+ $scope.start = (instance, qset, scoreTable, isPreview, qsetVersion) ->
+ $scope.update(qset, scoreTable)
- materiaCallbacks.start = (instance, qset, scoreTable, isPreview, qsetVersion) ->
- $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.update = (qset, scoreTable) ->
- $scope.customTable = false
+ # 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.hideResultsTable()
+ Materia.ScoreCore.setHeight(_getHeight())
- return Materia.ScoreCore.start materiaCallbacks
+ Materia.ScoreCore.hideResultsTable()
-]
+ Materia.ScoreCore.start $scope
-angular.bootstrap(document, ['AdventureScorescreen'])
\ No newline at end of file
+]
\ 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..1f5c4060 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,414 @@ $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;
+ }
+
+ .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%;
+ 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;
+ }
+
+ &.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;
+
+ &: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;
+ }
+
+ }
+ }
+
+ &.blank {
+ &:after {
+ display: none;
+ }
+ }
+
+ 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;
+ }
+
+ &.blank {
+ border: solid 2px $gray;
+ }
+
+ // 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
diff --git a/src/src-assets/services/inventoryService.coffee b/src/src-assets/services/inventoryService.coffee
new file mode 100644
index 00000000..9a2dce68
--- /dev/null
+++ b/src/src-assets/services/inventoryService.coffee
@@ -0,0 +1,149 @@
+# 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
+ self.recencyCounter = 0
+
+ # Check if player has required items in their inventory
+ self.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
+ # recency is determined by the order in which a node is visited
+ self.getMostRecentItem = (inventory, requiredItems) ->
+ mostRecentItem = 0
+ for i in inventory
+ for r in requiredItems
+ if i.id is r.id
+ if i.recency > mostRecentItem
+ mostRecentItem = i.recency
+ mostRecentItem
+
+ # Select the next question to display based on the player's inventory and visited nodes
+ 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
+ # Track which questions have the most required items and visits, respectively
+ questionWithMostItems = null
+ questionWithMostVisits = null
+ questionWithMostRecent = null
+
+ selectedQuestion = q_data.questions[0]
+
+ 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 = self.checkInventory(inventory, q.requiredItems)
+ if (missingItems.length > 0)
+ # This erases the most visited question as well
+ continue
+ else
+ recentItem = self.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
+
+ # 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
+
+ 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
+
+ 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
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 1b5567d3..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,13 +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/score-assets/score.coffee",
+ srcPath+"src-assets/services/inventoryService.coffee",
+ srcPath+"src-assets/services/legacyQsetSrv.coffee"
]
}
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"