Newer
Older
/********************************************************************************
metodLD
committed
This file contains the variables and functions that forms the core of the game.
Anything that is needed game-wide is kept here.
********************************************************************************/
/**********************************************************************
* Game Wide Constants
**********************************************************************/
/* General Constants */
var EPILOGUES_ENABLED = true;
var EPILOGUE_BADGES_ENABLED = true;
var ALT_COSTUMES_ENABLED = false;

FarawayVision
committed
var FORCE_ALT_COSTUME = null;
var BASE_FONT_SIZE = 14;
var BASE_SCREEN_WIDTH = 100;
var USAGE_TRACKING_ENDPOINT = 'https://spnati.faraway-vision.io/usage/report';
var BUG_REPORTING_ENDPOINT = 'https://spnati.faraway-vision.io/usage/bug_report';
var CURRENT_VERSION = undefined;
var VERSION_COMMIT = undefined;
/* Game Wide Constants */
var HUMAN_PLAYER = 0;
/* Directory Constants */
var IMG = 'img/';

ReformCopyright
committed
var backgroundImage;
/*var OPP = 'opponents/';
#The "OPP" folder abbreviation was used to slightly shorten a few lines in spniSelect that looked for opponents in the opponents folder.
#Now that opponents can be specified in any folder, this is no longer required.*/
/* Gender Images */
var MALE_SYMBOL = IMG + 'male.png';
var FEMALE_SYMBOL = IMG + 'female.png';
var includedOpponentStatuses = {};

FarawayVision
committed
var alternateCostumeSets = {};
var versionInfo = null;
/* game table */
var tableOpacity = 1;
$gameTable = $('#game-table');
/* useful variables */
var BLANK_PLAYER_IMAGE = "opponents/blank.png";
/* player array */

ReformCopyright
committed
var players = Array(5);

ReformCopyright
committed
/* Current timeout ID, so we can cancel it when restarting the game in order to avoid trouble. */
var timeoutID;
/**********************************************************************
* Game Wide Global Variables
**********************************************************************/
var table = new Table();
var jsErrors = [];
var sessionID = '';
/**********************************************************************
* Screens & Modals
**********************************************************************/
/* Screens */
$titleScreen = $('#title-screen');
$selectScreen = $('#main-select-screen');
$individualSelectScreen = $('#individual-select-screen');
$groupSelectScreen = $('#group-select-screen');
$gameScreen = $('#game-screen');
$epilogueScreen = $('#epilogue-screen');
metodLD
committed
$galleryScreen = $('#gallery-screen');
var allScreens = [$warningScreen, $titleScreen, $selectScreen, $individualSelectScreen, $groupSelectScreen, $gameScreen, $epilogueScreen, $galleryScreen];
/* Modals */
$searchModal = $('#search-modal');
$groupSearchModal = $('#group-search-modal');
$versionModal = $('#version-modal');
$gameSettingsModal = $('#game-settings-modal');
$bugReportModal = $('#bug-report-modal');
$usageTrackingModal = $('#usage-reporting-modal');
$playerTagsModal = $('#player-tags-modal');
/* Screen State */
$previousScreen = null;
/* CSS rules for arrow offsets */
var bubbleArrowOffsetRules;
/********************************************************************************
* Game Wide Utility Functions
********************************************************************************/
function getReportedOrigin () {
var origin = window.location.origin;
if (origin.toLowerCase().startsWith('file:')) {
return '<local filesystem origin>';
} else {
return origin;
}
}
/* Gathers most of the generic information for an error report. */
function compileBaseErrorReport(userDesc, bugType) {
var tableReports = [];
for (let i=1;i<players.length;i++) {
if (players[i]) {
playerData = {
'id': players[i].id,
'slot': i,
'stage': players[i].stage,
'timeInStage': players[i].timeInStage,
'markers': players[i].markers
}
if (players[i].chosenState) {
playerData.currentLine = players[i].chosenState.rawDialogue;
playerData.currentImage = players[i].folder + players[i].chosenState.image;
tableReports[i-1] = playerData;
} else {
tableReports[i-1] = null;
}
}
var circumstances = {
'userAgent': navigator.userAgent,
'origin': getReportedOrigin(),
'currentRound': currentRound,
'currentTurn': currentTurn,
'previousLoser': previousLoser,
'recentLoser': recentLoser,
'gameOver': gameOver,
'visibleScreens': [],
'rollback': inRollback()
if (inRollback()) {
circumstances.gamePhase = rolledBackGamePhase[0];
} else {
circumstances.gamePhase = gamePhase[0];
}
for (let i=0;i<allScreens.length;i++) {
if (allScreens[i].css('display') !== 'none') {
circumstances.visibleScreens.push(allScreens[i].attr('id'));
}
}
var bugCharacter = null;
if (bugType.startsWith('character')) {
bugCharacter = bugType.split(':', 2)[1];
bugType = 'character';
}
return {
'date': (new Date()).toISOString(),
'commit': VERSION_COMMIT,
'type': bugType,
'character': bugCharacter,
'circumstances': circumstances,
'table': tableReports,
'player': {
'gender': players[HUMAN_PLAYER].gender,
'size': players[HUMAN_PLAYER].size,
},
'jsErrors': jsErrors,
};
}
window.addEventListener('error', function (ev) {
jsErrors.push({
'date': (new Date()).toISOString(),
'type': ev.error.name,
'message': ev.message,
'filename': ev.filename,
'lineno': ev.lineno,
'stack': ev.error.stack
});
if (USAGE_TRACKING) {
var report = compileBaseErrorReport('Automatically generated after Javascript error.', 'auto');
$.ajax({
url: BUG_REPORTING_ENDPOINT,
method: 'POST',
data: JSON.stringify(report),
contentType: 'application/json',
error: function (jqXHR, status, err) {
console.error("Could not send bug report - error "+status+": "+err);
},
});
}
});
/* Fetch a possibly compressed file.
* Attempts to fetch a compressed version of the file first,
* then fetches the uncompressed version of the file if that isn't found.
*/
function fetchCompressedURL(baseUrl, successCb, errorCb) {
/*
* The usual Jquery AJAX request function doesn't play nice with
* the binary-encoded data we'll get here, so we do the XHR manually.
* (I would use fetch() were it not for compatibility issues.)
*/
var req = new XMLHttpRequest();
req.open('GET', baseUrl+'.gz', true);
req.responseType = 'arraybuffer';
req.onload = function(ev) {
if (req.status < 400 && req.response) {
var data = new Uint8Array(req.response);
var decompressed = pako.inflate(data, { to: 'string' });
successCb(decompressed);
} else if (req.status === 404) {
$.ajax({
type: "GET",
url: baseUrl,
dataType: "text",
success: successCb,
error: errorCb,
});
} else {
errorCb();
}
}
req.onerror = function(err) {
$.ajax({
type: "GET",
url: baseUrl,
dataType: "text",
success: successCb,
error: errorCb,
});
}
/**********************************************************************
***** Player Object Specification *****
**********************************************************************/
/************************************************************
* Creates and returns a new player object based on the
* supplied information.
*
* folder (string), the path to their folder
* first (string), their first name.
* last (string), their last name.
* labels (string or XML element), what's shown on screen and what other players refer to them as.
* Can vary by stage.
* size (string): Their level of endowment
* intelligence (string or XML element), the name of their AI algorithm.
* Can vary by stage.
* gender (constant), their gender.
* clothing (array of Clothing objects), their clothing.
* stamina (integer), time until forfeit is finished (initial timer value).
* state (array of PlayerState objects), their sequential states.
* xml (jQuery object), the player's loaded behaviour.xml file.
* metaXml (jQuery object), the player's loaded meta.xml file.
************************************************************/
function Player (id) {
this.id = id;
this.folder = 'opponents/'+id+'/';

FarawayVision
committed
this.base_folder = 'opponents/'+id+'/';
this.first = '';
this.last = '';
this.labels = undefined;
this.size = eSize.MEDIUM;
this.intelligence = eIntelligence.AVERAGE;
this.gender = eGender.MALE;
this.tags = this.baseTags = [];
this.xml = null;
this.metaXml = null;
/*******************************************************************
* Sets initial values of state variables used by targetStatus,
* targetStartingLayers etc. adccording to wardrobe.
*******************************************************************/
Player.prototype.initClothingStatus = function () {
this.startingLayers = this.clothing.length;
this.exposed = { upper: true, lower: true };
for (var position in this.exposed) {
if (this.clothing.some(function(c) {
return (c.type == IMPORTANT_ARTICLE || c.type == MAJOR_ARTICLE)
&& (c.position == position || c.position == FULL_ARTICLE);
})) {
this.exposed[position] = false;
};
}
this.mostlyClothed = this.decent = !(this.exposed.upper || this.exposed.lower)
&& this.clothing.some(function(c) {
return c.type == MAJOR_ARTICLE
&& [UPPER_ARTICLE, LOWER_ARTICLE, FULL_ARTICLE].indexOf(c.position) >= 0;
});
}
/*******************************************************************
* (Re)Initialize the player properties that change during a game
*******************************************************************/
this.forfeit = "";
this.stage = this.current = this.consecutiveLosses = 0;
this.timeInStage = -1;
this.markers = {};
/* Load in the legacy "start" lines, and also
* initialize player.chosenState to the first listed line.
* This may be overridden by later updateBehaviour calls if
* the player has (new-style) selected or game start case lines.
*/
/* Initialize reaction handling state. */
this.volatileMatches = [];
this.bestVolatileMatch = null;
this.currentTarget = null;
this.currentPriority = -1;
this.stateLockCount = 0;
this.stateCommitted = false;
this.xml.children('start').children('state').each(function () {
allStates.push(new State($(this)));
});
this.chosenState = this.allStates[0];
if (!this.chosenState) {
/* If the opponent does not have legacy start lines then select
* a new-style selected line immediately.
* Prevents a crash triggered by selected, unselecting, and re-selecting
* an opponent with no legacy starting lines.
*/
this.updateBehaviour(SELECTED);
}
this.commitBehaviourUpdate();
AmonKurath
committed
var appearance = this.default_costume;
if (ALT_COSTUMES_ENABLED && this.alt_costume) {
appearance = this.alt_costume;
}
AmonKurath
committed
this.labels = appearance.labels;
this.folders = appearance.folders;
this.labelOverridden = false;
AmonKurath
committed
/* Find and grab the wardrobe tag */
/* find and create all of their clothing */
$wardrobe.find('clothing').each(function () {
var formalName = $(this).attr('formalName');
var genericName = $(this).attr('genericName') || $(this).attr('lowercase');
var type = $(this).attr('type');
var position = $(this).attr('position');
var plural = ['true', 'yes'].indexOf($(this).attr('plural')) >= 0;
var newClothing = createNewClothing(formalName, genericName, type, position, null, plural, 0);
this.stageChangeUpdate();
Player.prototype.getIntelligence = function () {
return this.intelligence; // Opponent uses getByStage()
};

ReformCopyright
committed
/* These shouldn't do anything for the human player, but exist as empty functions
to make it easier to iterate over the entire players[] array. */
Player.prototype.updateLabel = function () { }
Player.prototype.updateFolder = function () { }

ReformCopyright
committed
Player.prototype.updateBehaviour = function() { }
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
/* Compute the Player's tags list from their baseTags list. */
Player.prototype.updateTags = function () {
var tags = [this.id];
var stage = this.stage || 0;
if (this.alt_costume && this.alt_costume.id) {
tags.push(this.alt_costume.id);
}
this.baseTags.forEach(function (tag_desc) {
if (typeof(tag_desc) === 'string') {
tags.push(tag_desc);
return;
}
if (!tag_desc.tag) return;
var tag = tag_desc.tag;
var from = parseInt(tag_desc.from, 10);
var to = parseInt(tag_desc.to, 10);
if (isNaN(to)) to = Number.POSITIVE_INFINITY;
if (isNaN(from)) from = 0;
if (stage >= from && stage <= to) {
tags.push(tag);
}
});
this.tags = expandTagsList(tags);
}
Player.prototype.stageChangeUpdate = function () {
this.updateLabel();
this.updateFolder();
this.updateTags();
}
Player.prototype.addTag = function(tag) {
this.baseTags.push(canonicalizeTag(tag));
}
Player.prototype.removeTag = function(tag) {
tag = canonicalizeTag(tag);
this.baseTags = this.baseTags.filter(function (t) {
if (typeof(t) === 'string') { return t !== tag };
if (!t.tag) return false;
return t.tag !== tag;
});
}

ReformCopyright
committed
Player.prototype.hasTag = function(tag) {
return tag && this.tags && this.tags.indexOf(canonicalizeTag(tag)) >= 0;
};
/*****************************************************************************
* Subclass of Player for AI-controlled players.
****************************************************************************/
function Opponent (id, $metaXml, status, releaseNumber) {
this.id = id;
this.folder = 'opponents/'+id+'/';

FarawayVision
committed
this.base_folder = 'opponents/'+id+'/';
this.status = status;
this.first = $metaXml.find('first').text();
this.last = $metaXml.find('last').text();
this.label = $metaXml.find('label').text();
this.image = $metaXml.find('pic').text();
this.gender = $metaXml.find('gender').text();
this.height = $metaXml.find('height').text();
this.source = $metaXml.find('from').text();
this.artist = $metaXml.find('artist').text();
this.writer = $metaXml.find('writer').text();

ReformCopyright
committed
this.description = fixupDialogue($metaXml.find('description').html());

ReformCopyright
committed
this.endings = $metaXml.find('epilogue');
this.ending = this.endings.length > 0 || $metaXml.find('has_ending').text() === "true";
this.layers = parseInt($metaXml.find('layers').text(), 10);
this.scale = Number($metaXml.find('scale').text()) || 100.0;
this.release = parseInt(releaseNumber, 10) || Number.POSITIVE_INFINITY;
this.selected_costume = null;
this.alt_costume = null;
this.default_costume = null;

FarawayVision
committed
this.labelOverridden = false;
/* baseTags stores tags that will be later used in resetState to build the
* opponent's true tags list. It does not store implied tags.
*
* The tags list stores the fully-expanded list of tags for the opponent,
* including implied tags.
*/
this.baseTags = $metaXml.find('tags').children().map(function() { return canonicalizeTag($(this).text()); }).get();
this.updateTags();
/* Attempt to preload this opponent's picture for selection. */
new Image().src = 'opponents/'+id+'/'+this.image;

FarawayVision
committed
this.alternate_costumes = [];
this.selection_image = this.folder + this.image;
$metaXml.find('alternates').find('costume').each(function (i, elem) {
var set = $(elem).attr('set') || 'offline';
if (alternateCostumeSets['all'] || alternateCostumeSets[set]) {
var costume_descriptor = {
'folder': $(elem).attr('folder'),
'label': $(elem).text(),
'image': $(elem).attr('img'),
'set': set
};
if (set === FORCE_ALT_COSTUME) {
this.selection_image = costume_descriptor['folder'] + costume_descriptor['image'];
this.selectAlternateCostume(costume_descriptor);

FarawayVision
committed
}
this.alternate_costumes.push(costume_descriptor);
}
}.bind(this)).get();
}
Opponent.prototype = Object.create(Player.prototype);
Opponent.prototype.constructor = Opponent;

ReformCopyright
committed
Opponent.prototype.clone = function() {
var clone = Object.create(Opponent.prototype);
/* This should be deep enough for our purposes. */
for (var prop in this) {
if (this[prop] instanceof Array) {
clone[prop] = this[prop].slice();
} else {
clone[prop] = this[prop];
}
}
return clone;
}

ReformCopyright
committed
Opponent.prototype.isLoaded = function() {
return this.xml != undefined;
}
Opponent.prototype.onSelected = function(individual) {
this.resetState();

FarawayVision
committed
console.log(this.slot+": ");
console.log(this);
if (individual) {
updateAllBehaviours(this.slot, SELECTED, [[OPPONENT_SELECTED]]);
} else {
this.updateBehaviour(SELECTED);
this.commitBehaviourUpdate();
}
updateSelectionVisuals();

ReformCopyright
committed
Opponent.prototype.updateLabel = function () {

FarawayVision
committed
if (this.labels && !this.labelOverridden) this.label = this.getByStage(this.labels);

ReformCopyright
committed
}
Opponent.prototype.updateFolder = function () {
if (this.folders) this.folder = this.getByStage(this.folders);
}

ReformCopyright
committed
Opponent.prototype.getByStage = function (arr, stage) {

ReformCopyright
committed
if (typeof(arr) === "string") {
return arr;
}
if (stage === undefined) stage = this.stage;

ReformCopyright
committed
var bestFitStage = -1;
var bestFit = null;
for (var i = 0; i < arr.length; i++) {
var startStage = arr[i].getAttribute('stage');
startStage = parseInt(startStage, 10) || 0;
if (startStage > bestFitStage && startStage <= stage) {

ReformCopyright
committed
bestFit = $(arr[i]).text();
bestFitStage = startStage;
}
}
return bestFit;
};
Opponent.prototype.selectAlternateCostume = function (costumeDesc) {
this.selected_costume = null;
this.selection_image = this.base_folder + this.image;
} else {
this.selected_costume = costumeDesc.folder;
this.selection_image = costumeDesc.folder + costumeDesc.image;
}

ReformCopyright
committed
Opponent.prototype.getIntelligence = function () {
return this.getByStage(this.intelligence) || eIntelligence.AVERAGE;
};
Opponent.prototype.loadAlternateCostume = function (individual) {
if (this.alt_costume) {
if (this.alt_costume.folder != this.selected_costume) {
this.unloadAlternateCostume();
} else {
return;
}
}

FarawayVision
committed
console.log("Loading alternate costume: "+this.selected_costume);

FarawayVision
committed
url: this.selected_costume+'costume.xml',
dataType: "text",
success: function (xml) {
var $xml = $(xml);
AmonKurath
committed

FarawayVision
committed
id: $xml.find('id').text(),
folders: $xml.find('folder'),
wardrobe: $xml.find('wardrobe')
};
var poses = $xml.find('poses');
var poseDefs = {};
$(poses).find('pose').each(function (i, elem) {
var def = new PoseDefinition($(elem), this);
poseDefs[def.id] = def;
}.bind(this));
this.alt_costume.poses = poseDefs;
AmonKurath
committed
var costumeTags = this.default_costume.tags.slice();
var tagMods = $xml.find('tags');
if (tagMods) {
var newTags = [];
tagMods.find('tag').each(function (idx, elem) {
var $elem = $(elem);
var tag = canonicalizeTag(tag);
var fromStage = $elem.attr('from');
var toStage = $elem.attr('to');
// Remove previous declarations for this tag
costumeTags = costumeTags.filter(function (t) { return t.tag !== tag; });
if (removed.toLowerCase() !== 'true') {
newTags.push({'tag': tag, 'from': fromStage, 'to': toStage});
Array.prototype.push.apply(costumeTags, newTags);
}
this.alt_costume.tags = costumeTags;
this.onSelected(individual);
}.bind(this),
error: function () {
console.error("Failed to load alternate costume: "+this.selected_costume);
},
})
}
Opponent.prototype.unloadAlternateCostume = function () {
if (!this.alt_costume) {
return;
}
this.selectAlternateCostume(null);
/************************************************************
* Loads and parses the start of the behaviour XML file of the
* given opponent.
*
* The onLoadFinished parameter must be a function capable of
* receiving a new player object and a slot number.
************************************************************/
Opponent.prototype.loadBehaviour = function (slot, individual) {
if (this.selected_costume) {
this.loadAlternateCostume();
} else {
this.onSelected(individual);
AmonKurath
committed
fetchCompressedURL(
'opponents/' + this.id + "/behaviour.xml",
/* Success callback.
* 'this' is bound to the Opponent object.
*/
function(xml) {
this.xml = $xml;
this.size = $xml.find('size').text();
this.stamina = Number($xml.find('timer').text());
this.intelligence = $xml.find('intelligence');

FarawayVision
committed
/* The gender listed in meta.xml and behaviour.xml might differ
* (for example with gender-revealing characters)
* So assume behaviour.xml holds the 'definitive' starting gender
* for the character.
*/
var startGender = $xml.find('gender').text();
if (startGender) {
this.gender = startGender;
}
labels: $xml.find('label'),
tags: null,
folders: this.folder,
wardrobe: $xml.find('wardrobe')
};
var poses = $xml.find('poses');
var poseDefs = {};
$(poses).find('pose').each(function (i, elem) {
poseDefs[def.id] = def;
}.bind(this));
AmonKurath
committed
var tagsArray = [];
if (typeof tags !== typeof undefined && tags !== false) {
tagsArray = $(tags).find('tag').map(function () {
return {
'tag': canonicalizeTag($(this).text()),
'from': $(this).attr('from'),
'to': $(this).attr('to'),
}
}).get();
this.default_costume.tags = tagsArray;
$xml.find('case[target]>state, case[alsoPlaying]>state').each(function() {
var $case = $(this.parentNode);
['target', 'alsoPlaying'].forEach(function(attr) {
var id = $case.attr(attr);
if (id) {
if (!(id in targetedLines)) { targetedLines[id] = { count: 0, seen: {} }; }
if (!(this.textContent in targetedLines[id].seen)) {
targetedLines[id].seen[this.textContent] = true;
targetedLines[id].count++;
}
}
}, this);
});
AmonKurath
committed
if (this.selected_costume) {
return this.loadAlternateCostume();

FarawayVision
committed
return this.onSelected(individual);
/* Error callback. */
function(err) {
console.log("Failed reading \""+this.id+"\" behaviour.xml");

ReformCopyright
committed
delete players[this.slot];
Player.prototype.getImagesForStage = function (stage) {
if(!this.xml) return [];
var imageSet = {};
var folder = this.folders ? this.getByStage(this.folders, stage) : this.folder;
var selector = (stage == -1 ? 'start, stage[id=1] case[tag=game_start]'
: 'stage[id='+stage+'] case');

FarawayVision
committed
this.xml.find(selector).each(function () {
var target = $(this).attr('target'), alsoPlaying = $(this).attr('alsoPlaying'),
filter = canonicalizeTag($(this).attr('filter'));
// Skip cases requiring a character that isn't present
if ((target === undefined || players.some(function(p) { return p.id === target; }))
&& (alsoPlaying === undefined || players.some(function(p) { return p.id === alsoPlaying; }))

ReformCopyright
committed
&& (filter === undefined || players.some(function(p) { return p.hasTag(filter); })))
$(this).children('state').each(function (i, e) {
var poseName = $(e).attr('img');
if (!poseName) return;
if (poseName.startsWith('custom:')) {
var key = poseName.split(':', 2)[1];
var pose = advPoses[key];
if (pose) pose.getUsedImages().forEach(function (img) {
imageSet[img] = true;
});
} else {
imageSet[folder+poseName] = true;
}
});
return Object.keys(imageSet);
};
Player.prototype.preloadStageImages = function (stage) {
this.getImagesForStage(stage)
.forEach(function(fn) { new Image().src = fn; }, this );
};
/**********************************************************************
***** Overarching Game Flow Functions *****
**********************************************************************/
/************************************************************
* Loads the initial content of the game.
************************************************************/
function initialSetup () {
/* start by creating the human player object */
var humanPlayer = new Player('human'); //createNewPlayer("human", "", "", "", eGender.MALE, eSize.MEDIUM, eIntelligence.AVERAGE, 20, undefined, [], null);
players[HUMAN_PLAYER].slot = HUMAN_PLAYER;
/* enable table opacity */
tableOpacity = 1;
$gameTable.css({opacity:1});
/* load the all content */
loadTitleScreen();
selectTitleCandy();
loadVersionInfo();
/* Make sure that the config file is loaded before processing the
opponent list, so that includedOpponentStatuses is populated. */
loadConfigFile().always(loadSelectScreen);
save.loadCookie();
/* Generate a random session ID. */
sessionID = generateRandomID();
/* Construct a CSS rule for every combination of arrow direction, screen, and pseudo-element */
bubbleArrowOffsetRules = [];
for (var i = 1; i <= 4; i++) {
var pair = [];
[["up", "down"], ["left", "right"]].forEach(function(p) {
var index = document.styleSheets[2].cssRules.length;
var rule = p.map(function(d) {
return ["select", "game"].map(function(s) {
return ["before", "after"].map(function(r) {
return '#'+s+'-bubble-'+i+'>.dialogue-bubble.arrow-'+d+'::'+r;
}).join(', ');
}).join(', ');
}).join(', ') + ' {}';
document.styleSheets[2].insertRule(rule, index);
pair.push(document.styleSheets[2].cssRules[index]);
});
bubbleArrowOffsetRules.push(pair);
}
function loadVersionInfo () {
$('.substitute-version').text('Unknown Version');
return $.ajax({
type: "GET",
url: "version-info.xml",
dataType: "text",
success: function(xml) {
versionInfo = $(xml);
CURRENT_VERSION = versionInfo.find('current').attr('version');
$('.substitute-version').text('v'+CURRENT_VERSION);
console.log("Running SPNATI version "+CURRENT_VERSION);
}
});
}
return $.ajax({
type: "GET",
url: "config.xml",
dataType: "text",
var _epilogues = $(xml).find('epilogues').text();
if(_epilogues.toLowerCase() === 'false') {
EPILOGUES_ENABLED = false;
console.log("Epilogues are disabled.");
$("#title-gallery-edge").hide();
} else {
console.log("Epilogues are enabled.");
EPILOGUES_ENABLED = true;
}
var _epilogues_unlocked = $(xml).find('epilogues-unlocked').text().trim();
if (_epilogues_unlocked.toLowerCase() === 'true') {
EPILOGUES_UNLOCKED = true;
console.error('All epilogues unlocked in config file. You better be using this for development only and not cheating!');
} else {
EPILOGUES_UNLOCKED = false;
var _epilogue_badges = $(xml).find('epilogue_badges').text();
if(_epilogue_badges.toLowerCase() === 'false') {
EPILOGUE_BADGES_ENABLED = false;
console.log("Epilogue badges are disabled.");
} else {
console.log("Epilogue badges are enabled.");
EPILOGUE_BADGES_ENABLED = true;
}
var _debug = $(xml).find('debug').text();
if (_debug === "true") {
DEBUG = true;
console.log("Debugging is enabled");
}
else {
DEBUG = false;
console.log("Debugging is disabled");
}
var _game_commit = $(xml).find('commit').text();
if (_game_commit) {
VERSION_COMMIT = _game_commit;
console.log("Running SPNATI commit "+VERSION_COMMIT+'.');
} else {
console.log("Could not find currently deployed Git commit!");
}
AmonKurath
committed
var _alts = $(xml).find('alternate-costumes').text();
AmonKurath
committed
if(_alts === "true") {
ALT_COSTUMES_ENABLED = true;
console.log("Alternate costumes enabled");

FarawayVision
committed
FORCE_ALT_COSTUME = $(xml).find('force-alternate-costume').text();
if (FORCE_ALT_COSTUME) {
console.log("Forcing alternate costume set: "+FORCE_ALT_COSTUME);
alternateCostumeSets[FORCE_ALT_COSTUME] = true;
} else {
$(xml).find('alternate-costume-sets').each(function () {
var set = $(this).text();
alternateCostumeSets[set] = true;
if (set === 'all') {
console.log("Including all alternate costume sets");
} else {
console.log("Including alternate costume set: "+set);
}
});
}
} else {
ALT_COSTUMES_ENABLED = false;
console.log("Alternate costumes disabled");
}
AmonKurath
committed
$(xml).find('include-status').each(function() {
includedOpponentStatuses[$(this).text()] = true;
console.log("Including", $(this).text(), "opponents");
});