Skip to content
Snippets Groups Projects
spniSelect.js 54.2 KiB
Newer Older
  • Learn to ignore specific revisions
  •  ************************************************************/
    function backToSelect () {
        /* switch screens */
    	screenTransition($individualSelectScreen, $selectScreen);
    	screenTransition($groupSelectScreen, $selectScreen);
    }
    
    /************************************************************
    
     * The player clicked on the start game button on the main
    
     * select screen.
     ************************************************************/
    function advanceSelectScreen () {
    
        gameID = generateRandomID();
    
    
        if (USAGE_TRACKING) {
    
            var usage_tracking_report = {
    
                'date': (new Date()).toISOString(),
    
    			'commit': VERSION_COMMIT,
    
                'type': 'start_game',
    
                'session': sessionID,
    
                'game': gameID,
    
                'userAgent': navigator.userAgent,
    
                'origin': getReportedOrigin(),
    
                'table': {},
    			'tags': players[HUMAN_PLAYER].tags
    
            for (let i=1;i<5;i++) {
                if (players[i]) {
    
                    usage_tracking_report.table[i] = players[i].id;
    
            $.ajax({
                url: USAGE_TRACKING_ENDPOINT,
                method: 'POST',
    
                data: JSON.stringify(usage_tracking_report),
    
                contentType: 'application/json',
                error: function (jqXHR, status, err) {
                    console.error("Could not send usage tracking report - error "+status+": "+err);
                },
            });
        }
    
        players.forEach(function(player) {
            player.preloadStageImages(0);
        });
    
        advanceToNextScreen($selectScreen);
    }
    
    /************************************************************
     * The player clicked on the back button on the main select
     * screen.
     ************************************************************/
    function backSelectScreen () {
    	screenTransition($selectScreen, $titleScreen);
    }
    
    
    /* The player selected an alternate costume for an opponent.
     * `slot` is the 1-based opponent slot affected.
     * `inGroup` is true if the affected opponent is on the group selection screen.
     */
    function altCostumeSelected(slot, inGroup) {
    	var costumeSelector = (inGroup ? $groupCostumeSelectors[slot-1] : $individualCostumeSelectors[slot-1]);
    	var selectImage = (inGroup ? $groupImages[slot-1] : $individualImages[slot-1]);
    	var opponent = (inGroup ? selectableGroups[groupSelectScreen][groupPage[groupSelectScreen]].opponents[slot-1] : shownIndividuals[slot-1]);
    	
    	var selectedCostume = costumeSelector.val();
    	
    	var costumeDesc = undefined;
    	if (selectedCostume.length > 0) {
    		for (let i=0;i<opponent.alternate_costumes.length;i++) {
    
    			if (opponent.alternate_costumes[i].folder === selectedCostume) {
    
    				costumeDesc = opponent.alternate_costumes[i];
    				break;
    			}
    		}
    	}
    	
    
        opponent.selectAlternateCostume(costumeDesc);
        selectImage.attr('src', opponent.selection_image);
    
    /**********************************************************************
     *****                     Display Functions                      *****
     **********************************************************************/
    
    /************************************************************
     * Displays all of the current players on the main select
     * screen.
     ************************************************************/
    function updateSelectionVisuals () {
        /* update all opponents */
        for (var i = 1; i < players.length; i++) {
    
    FarawayVision's avatar
    FarawayVision committed
    		mainSelectDisplays[i-1].update(players[i]);
    
        /* Check to see if all opponents are loaded. */
    
        var filled = 0, loaded = 0;
        players.forEach(function(p, idx) {
            if (idx > 0) {
                filled++;
    
    FarawayVision's avatar
    FarawayVision committed
                if (p.isLoaded()) {
    
        /* if enough opponents are selected, and all those are loaded, then enable progression */
        $selectMainButton.attr('disabled', filled < 2 || loaded < filled);
    
        /* if all slots are taken, disable fill buttons */
        $selectRandomButtons.attr('disabled', filled >= 4);
    
    Joseph Kantel's avatar
    Joseph Kantel committed
        /* if no opponents are loaded, disable remove all button */
    
        $selectRemoveAllButton.attr('disabled', filled <= 0 || loaded < filled);
    
        /* Disable buttons while loading is going on */
        $selectRandomTableButton.attr('disabled', loaded < filled);
        $groupButton.attr('disabled', loaded < filled);
    
        /* Update suggestions images. */
        var current_player_count = countLoadedOpponents();
    
        if (current_player_count >= 3) {
    
            var suggested_opponents = loadedOpponents.filter(function(opp) {
                /* hide selected opponents */
                if (players.some(function(p) { return p && p.id == opp.id; })) {
                    return false;
                }
    
            /* sort opponents */
            suggested_opponents.sort(sortOpponentsByMostTargeted());
    
            var suggestion_idx = 0;
            for (var i=1;i<players.length;i++) {
    
                if (players[i] === undefined) {
                    updateSuggestions(i-1, suggested_opponents, suggestion_idx);
    
                    $selectSuggestions[i-1].show();
                    suggestion_idx += 4;
                } else {
                    $selectSuggestions[i-1].hide();
                }
            }
        } else {
            for (var i=0;i<4;i++) {
                $selectSuggestions[i].hide();
            }
        }
    
    
    
    /************************************************************
     * This is the callback for the group clicked rows, it
     * updates information on the group screen.
     ************************************************************/
    function updateGroupScreen (playerObject) {
        /* find a spot to store this player */
        for (var i = 0; i < storedGroup.length; i++) {
            if (!storedGroup[i]) {
                storedGroup[i] = playerObject;
                $groupLabels[i+1].html(playerObject.label);
                break;
            }
        }
    
    	/* enable the button */
    	$groupButton.attr('disabled', false);
    }
    
    /************************************************************
     * Hides the table on the single selection screen.
     ************************************************************/
    function hideSelectionTable() {
        mainSelectHidden = !mainSelectHidden;
        if (mainSelectHidden) {
            $selectTable.hide();
        }
        else {
            $selectTable.show();
        }
    }
    
    /************************************************************
     * Hides the table on the single selection screen.
     ************************************************************/
    function hideSingleSelectionTable() {
        singleSelectHidden = !singleSelectHidden;
        if (singleSelectHidden) {
            $individualSelectTable.hide();
        }
        else {
            $individualSelectTable.show();
        }
    }
    
    /************************************************************
    
     * Hides the table on the single group screen.
    
     ************************************************************/
    function hideGroupSelectionTable() {
        groupSelectHidden = !groupSelectHidden;
        if (groupSelectHidden) {
            $groupSelectTable.hide();
        }
        else {
            $groupSelectTable.show();
        }
    }
    
    function openSearchModal() {
    
    }
    
    function closeSearchModal() {
    
        // perform the search and sort logic
        updateSelectableOpponents();
    
        // update
        updateIndividualSelectScreen();
    
        updateIndividualCountStats();
    
    function clearSearch() {
        $searchName.val(null);
        $searchTag.val(null);
        $searchSource.val(null);
        closeSearchModal();
    }
    
    
    function changeSearchGender(gender) {
        chosenGender = gender;
        setActiveOption($searchGenderOptions, gender);
    
    function openGroupSearchModal() {
        $groupSearchModal.modal('show');
    }
    
    function closeGroupSearchModal() {
        // perform the search and sort logic
        updateSelectableGroups(groupSelectScreen);
    
        // update
        updateGroupSelectScreen();
        updateGroupCountStats();
    }
    
    function clearGroupSearch() {
        $groupSearchName.val(null);
        $groupSearchGroupName.val(null);
        $groupSearchTag.val(null);
        $groupSearchSource.val(null);
        closeGroupSearchModal();
    }
    
    function changeGroupSearchGender(gender) {
        chosenGroupGender = gender;
        setActiveOption($groupSearchGenderOptions, gender);
    }
    
    
    
    /************************************************************
     * Sorting Functions
     ************************************************************/
    
    
     * Callback for Arrays.sort to sort an array of objects by the given field.
     * Prefixing "-" to a field will cause the sort to be done in reverse.
     * Examples:
     *   // sorts myArr by each element's first name (A-Z)
    
     *   myArr.sort(sortOpponentsByField("first"));
    
     *   // sorts myArr by each element's last name (Z-A)
    
     *   myArr.sort(sortOpponentsByField("-last"));
    
     */
    function sortOpponentsByField(field) {
        // check for prefix
        var order = 1; // 1 = forward, -1 = reversed
    
        if (field[0] === "-") {
    
            order = -1;
            field = field.substr(1);
        }
    
        return function(opp1, opp2) {
            var compare = 0;
            if (opp1[field] < opp2[field]) {
                compare = -1;
            }
            else if (opp1[field] > opp2[field]) {
                compare = 1;
            }
            return order * compare;
        }
    }
    
    /**
     * Callback for Arrays.sort to sort an array of objects over multiple given fields.
     * Prefixing "-" to a field will cause the sort to be done in reverse.
     * This should allow more flexibility in the sorting order.
     * Example:
    
     *   // sorts myArr by each element's number of layers (low to high),
    
     *   // and for elements whose layers are equivalent, sort them by first name (Z-A)
    
     *   myArr.sort(sortOpponentsByMultipleFields("layers", "-first"));
    
     */
    function sortOpponentsByMultipleFields() {
        var fields = arguments; // retrieve the args passed in
        return function(opp1, opp2) {
            var i = 0;
            var compare = 0;
            // if both elements have the same field, check the next ones
            while (compare === 0 && i < fields.length) {
                compare = sortOpponentsByField(fields[i])(opp1, opp2);
                i++;
            }
            return compare;
        }
    }
    
    /**
     * Special Callback for Arrays.sort to sort an array of opponents on
     * the total number of lines targeting them the currently selected
     * opponents have.
     */
    function sortOpponentsByMostTargeted() {
    	return function(opp1, opp2) {
    		counts = [opp1, opp2].map(function(opp) {
    			return players.reduce(function(sum, p) {
    
    				if (p && p.targetedLines && opp.id in p.targetedLines) {
    
    					sum += p.targetedLines[opp.id].count;
    				}
    				return sum;
    			}, 0);
    		});
    		if (counts[0] > counts[1]) return -1;
    		if (counts[0] < counts[1]) return 1;
    		return 0;
    	}
    }
    
    
    function setSortingMode(mode) {
        sortingMode = mode;
        $("#sort-dropdown-selection").html(sortingMode); // change the dropdown text to the selected option
        individualPage = 0; // reset the page number
    }
    
    
    /** Event handler for the sort dropdown options. Fires when user clicks on a dropdown item. */
    $sortingOptionsItems.on("click", function(e) {
    
        setSortingMode($(this).find('a').html());
    
    /************************************************************
     * Word wrapping Functions
     ************************************************************/
    
    /**
    
     * Inserts a fixed-size HTML element with the specified text to allow the content
     * to be either word-wrapped (if the text is long and spaces are present)
    
     * or word-broken (if text is long and no spaces are present).
     */
    function wordWrapHtml(text) {
    
        text = text || "&nbsp;";
    
        return "<table class=\"wrap-text\"><tr><td>" + text + "</td></tr></table>";
    
    
    /************************************************************
     * Dynamic dialogue and image counting functions
     ************************************************************/
    
    
    /** Event handler for the individual selection screen credits button. */
    
    $individualCreditsButton.on('click', function(e) {
        updateIndividualCountStats()
    
    /** Event handler for the group selection screen credits button. */
    
    $groupCreditsButton.on('click', function(e) {
        updateGroupCountStats();
    
     * Loads and displays the number of unique dialogue lines and the number of pose images
    
     * into the character's player object for those currently on the selection screen.
     * Only loads if the unique line count or image count is not known.
     */
    
    function updateOpponentCountStats(opponentArr, uiElements) {
    
        opponentArr.forEach(function(opp, idx) {
    
            // load behaviour file if line/image count is not known
    
            if (opp && (opp.uniqueLineCount === undefined || opp.posesImageCount === undefined)) {
    
                uiElements.countBoxes[idx].css("visibility", "visible");
    
                // retrieve line and image counts
    
                if (DEBUG) {
                    console.log("[LineImageCount] Fetching counts for " + opp.label + " in slot " + idx);
    
                var countsPromise = new Promise(function (resolve, reject) {
                    fetchCompressedURL(
                        opp.folder + 'behaviour.xml',
                        resolve, reject
                    );
                });
    
                countsPromise.then(countLinesImages).then(function(response) {
                    opp.uniqueLineCount = response.numUniqueLines;
                    opp.posesImageCount = response.numPoses;
    
                    // show line and image counts
    
                    if (DEBUG) {
                        console.log("[LineImageCount] Loaded " + opp.label + " from behaviour: " +
                          opp.uniqueLineCount + " lines, " + opp.posesImageCount + " images");
    
                    uiElements.lineLabels[idx].html(opp.uniqueLineCount);
                    uiElements.poseLabels[idx].html(opp.posesImageCount);
    
                // this character's counts were previously loaded
    
                    if (DEBUG) {
                        console.log("[LineImageCount] Loaded previous count for " + opp.label + ": " +
                          opp.uniqueLineCount + " lines, " + opp.posesImageCount + " images)");
    
                    uiElements.countBoxes[idx].css("visibility", "visible");
                    uiElements.lineLabels[idx].html(opp.uniqueLineCount);
                    uiElements.poseLabels[idx].html(opp.posesImageCount);
    
                    // there is no character in the slot
    
                    uiElements.countBoxes[idx].css("visibility", "hidden");
                    uiElements.lineLabels[idx].html("");
                    uiElements.poseLabels[idx].html("");
    
    /** Dialogue/image count update function for the individual selection screen. */
    function updateIndividualCountStats() {
        if (individualCreditsShown) {
    
            var individualUIElements = {
                countBoxes : $individualCountBoxes,
                lineLabels : $individualLineCountLabels,
                poseLabels : $individualPoseCountLabels
            };
            updateOpponentCountStats(shownIndividuals, individualUIElements);
    
        }
    }
    
    /** Dialogue/image count update function for the group selection screen. */
    function updateGroupCountStats() {
        if (groupCreditsShown) {
    
            var groupUIElements = {
                countBoxes : $groupCountBoxes,
                lineLabels : $groupLineCountLabels,
                poseLabels : $groupPoseCountLabels
            };
            updateOpponentCountStats(shownGroup, groupUIElements);
    
    /**
     * Callback to parse the number of lines of dialogue and number of images
    
     * given a character's behaviour XML. Returns the counts as an object with
    
     * properties numTotalLines, numUniqueLines, and numPoses.
     */
    
    function countLinesImages(xml) {
    
        // parse all lines of dialogue and all images
    
    	var numTotalLines = 0;
    	var numUniqueDialogueLines = 0;
    	var numUniqueUsedPoses = 0;
        var lines = {};
        var poses = {};
    
        $(xml).find('state').each(function(idx, data) {
    
    		numTotalLines++;
    		// count only unique lines of dialogue
    		if (lines[data.textContent.trim()] === undefined) numUniqueDialogueLines++;
            lines[data.textContent.trim()] = 1;
    		// count unique number of poses used in dialogue
    		// note that this number may differ from actual image count if some images
    		// are never used, or if images that don't exist are used in the dialogue
    		if (poses[data.getAttribute("img")] === undefined) numUniqueUsedPoses++;
            poses[data.getAttribute("img")] = 1;
    
            numUniqueLines : numUniqueDialogueLines,
    
            numPoses : numUniqueUsedPoses