Newer
Older
/* Epilogue UI elements */
$epilogueScreen = $('#epilogue-screen');
var epilogueContainer = document.getElementById('epilogue-container');
/* Epilogue selection modal elements */
$epilogueSelectionModal = $('#epilogue-modal'); //the modal box
$epilogueHeader = $('#epilogue-header-text'); //the header text for the epilogue selection box
$epilogueList = $('#epilogue-list'); //the list of epilogues
$epilogueAcceptButton = $('#epilogue-modal-accept-button'); //click this button to go with the chosen ending
var epilogueSelections = []; //references to the epilogue selection UI elements
var winStr = "You've won the game, and possibly made some friends. Who among these players did you become close with?"; //Winning the game, with endings available
var winStrNone = "You've won the game, and possibly made some friends? Unfortunately, none of your competitors are ready for a friend like you.<br>(None of the characters you played with have an ending written.)"; //Player won the game, but none of the characters have an ending written
var lossStr = "Well you lost. And you didn't manage to make any new friends. But you saw some people strip down and show off, and isn't that what life is all about?<br>(You may only view an ending when you win.)"; //Player lost the game. Currently no support for ending scenes when other players win
// Player won the game, but epilogues are disabled.
var winEpiloguesDisabledStr = "You won... but epilogues have been disabled.";
// Player lost the game with epilogues disabled.
var lossEpiloguesDisabledStr = "You lost... but epilogues have been disabled.";
var epilogues = []; //list of epilogue data objects
var chosenEpilogue = null;
var epiloguePlayer = null;
var epilogueSuffix = 0;
// Attach some event listeners
var previousButton = document.getElementById('epilogue-previous');
var nextButton = document.getElementById('epilogue-next');
previousButton.addEventListener('click', function (e) {
document.getElementById('epilogue-restart').addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
showRestartModal();
});
document.getElementById('epilogue-buttons').addEventListener('click', function () {
epilogueContainer.addEventListener('click', function () {
/************************************************************
* Animation class. Used instead of CSS animations for the control over stopping/rewinding/etc.
************************************************************/
function Animation(id, frames, updateFunc, loop, easingFunction, clampFunction, iterations) {
this.id = id;
this.looped = loop === "1" || loop === "true";
this.keyframes = frames;
this.iterations = iterations;
spnati_edit
committed
this.easingFunction = easingFunction || "smooth";
this.clampFunction = clampFunction || "wrap";
for (var i = 0; i < frames.length; i++) {
frames[i].index = i;
frames[i].keyframes = frames;
}
this.duration = frames[frames.length - 1].end;
this.elapsed = 0;
this.updateFunc = updateFunc;
}
Animation.prototype.easingFunctions = {
"linear": function (t) { return t; },
"smooth": function (t) { return 3 * t * t - 2 * t * t * t; },
"ease-in": function (t) { return t * t; },
"ease-out": function (t) { return t * (2 - t); },
"elastic": function (t) { return (.04 - .04 / t) * Math.sin(25 * t) + 1; },
"ease-in-cubic": function (t) { return t * t * t; },
"ease-out-cubic": function (t) { t--; return 1 + t * t * t; },
"ease-in-sin": function (t) { return 1 + Math.sin(Math.PI / 2 * t - Math.PI / 2); },
"ease-out-sin": function (t) { return Math.sin(Math.PI / 2 * t); },
"ease-in-out-cubic": function (t) { return t < .5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1; },
"ease-out-in": function (t) { return t < .5 ? Animation.prototype.easingFunctions["ease-out"](2 * t) * 0.5 : Animation.prototype.easingFunctions["ease-in"](2 * (t - 0.5)) * 0.5 + 0.5; },
"ease-out-in-cubic": function (t) { return t < .5 ? Animation.prototype.easingFunctions["ease-out-cubic"](2 * t) * 0.5 : Animation.prototype.easingFunctions["ease-in-cubic"](2 * (t - 0.5)) * 0.5 + 0.5; },
"bounce": function (t) {
if (t < 0.3636) {
return 7.5625 * t * t;
}
else if (t < 0.7273) {
t -= 0.5455;
return 7.5625 * t * t + 0.75;
}
else if (t < 0.9091) {
t -= 0.8182;
return 7.5625 * t * t + 0.9375;
}
else {
t -= 0.9545;
return 7.5625 * t * t + 0.984375;
}
},
Animation.prototype.isComplete = function () {
if (this.looped) {
return this.iterations > 0 ? life / this.duration >= this.iterations : false;
}
return life >= this.duration;
};
Animation.prototype.update = function (elapsedMs) {
this.elapsed += elapsedMs;
if (!this.updateFunc) { return; }
//determine what keyframes we're between
var last;
if (t < 0) {
return;
spnati_edit
committed
if (this.duration === 0) {
t = 1;
}
else {
var easingFunction = this.easingFunction;
t /= this.duration;
if (this.looped) {
t = clampingFunctions[this.clampFunction](t);
if (this.isComplete()) {
t = 1;
}
}
else {
t = Math.min(1, t);
}
t = this.easingFunctions[easingFunction](t)
t *= this.duration;
spnati_edit
committed
}
for (var i = this.keyframes.length - 1; i >= 0; i--) {
var frame = this.keyframes[i];
if (isNaN(frame.start)) { frame.start = 0; frame.end = 0; }
if (t >= frame.start) {
last = (i > 0 ? this.keyframes[i - 1] : frame);
//normalize the time between frames
var time;
if (frame.end === frame.start) {
time = 1;
}
else {
time = t === 0 ? 0 : (t - frame.start) / (frame.end - frame.start);
}
this.updateFunc(this.id, last, frame, time);
return;
}
}
}
Animation.prototype.halt = function () {
var frame = this.keyframes[this.keyframes.length - 1];
this.updateFunc && this.updateFunc(this.id, frame, frame, 1);
}
/************************************************************
* Linear interpolation
************************************************************/
/************************************************************
* Clamping functions for what to do with values that go outside [0:1] to put them back inside.
************************************************************/
var clampingFunctions = {
"clamp": function (t) { return Math.max(0, Math.min(1, t)); }, //everything after 1 is clamped to 1
"wrap": function (t) { return t % 1.0; }, //passing 1 wraps back to 0 (ex. 1.1 => 0.1)
"mirror": function (t) { t %= 2.0; return t > 1 ? 2 - t : t; }, //bouncing back and forth from 0->1->0 (ex. 1.1 => 0.9, 2.1 => 0.1)
};
/************************************************************
* Interpolation functions for animation movement interpolation
************************************************************/
var interpolationModes = {
"none": function noInterpolation(prop, start, end, t, frames, index) {
return t >= 1 ? end : start;
},
"linear": function linear(prop, start, end, t, frames, index) {
return lerp(start, end, t);
},
"spline": function catmullrom(prop, start, end, t, frames, index) {
var p0 = index > 0 ? frames[index - 1][prop] : start;
var p1 = start;
var p2 = end;
var p3 = index < frames.length - 1 ? frames[index + 1][prop] : end;
if (typeof p0 === "undefined" || isNaN(p0)) {
p0 = start;
}
if (typeof p3 === "undefined" || isNaN(p3)) {
p3 = end;
}
var a = 2 * p1;
var b = p2 - p0;
var c = 2 * p0 - 5 * p1 + 4 * p2 - p3;
var d = -p0 + 3 * p1 - 3 * p2 + p3;
var p = 0.5 * (a + (b * t) + (c * t * t) + (d * t * t * t));
return p;
},
};
/************************************************************
* Converts a px or % value to the equivalent scene value
************************************************************/
function toSceneX(x, scene) {
if (typeof x === "undefined") { return; }
if ($.isNumeric(x)) { return parseInt(x, 10); }
if (x.endsWith("%")) {
return parseInt(x, 10) / 100 * scene.width;
}
else {
return parseInt(x, 10);
}
}
/************************************************************
* Converts a px or % value to the equivalent scene value
************************************************************/
function toSceneY(y, scene) {
if (typeof y === "undefined") { return; }
if ($.isNumeric(y)) { return parseInt(y, 10); }
if (y.endsWith("%")) {
return parseInt(y, 10) / 100 * scene.height;
}
else {
return parseInt(y, 10);
}
/************************************************************
* Return the numerical part of a string s. E.g. "20%" -> 20
************************************************************/
function getNumericalPart(s) {
return parseFloat(s); //apparently I don't actually need to remove the % (or anything else) from the string before I do the conversion
}
/************************************************************
* Return the approriate left: setting so that a text box of the specified width is centred
* Assumes a % width
************************************************************/
function getCenteredPosition(width) {
var w = getNumericalPart(width); //numerical value of the width
var left = 50 - (w / 2); //centre of the text box is at the 50% position
return left + "%";
}
/************************************************************
* Load the Epilogue data for a character
************************************************************/
// This is just a list of all possible conditional attribute names.
var EPILOGUE_CONDITIONAL_ATTRIBUTES = [
'alsoPlaying', 'playerStartingLayers', 'markers',
'not-markers', 'any-markers', 'alsoplaying-markers',
'alsoplaying-not-markers', 'alsoplaying-any-markers'
]
function loadEpilogueData(player) {
if (!player || !player.xml) { //return an empty list if a character doesn't have an XML variable. (Most likely because they're the player.)
return [];
}
metodLD
committed
var playerGender = humanPlayer.gender;
metodLD
committed
//get the XML tree that relates to the epilogue, for the specific player gender
//var epXML = $($.parseXML(xml)).find('epilogue[gender="'+playerGender+'"]'); //use parseXML() so that <image> tags come through properly //IE doesn't like this
metodLD
committed
var epilogues = player.xml.find('epilogue').filter(function (index) {
/* Returning true from this function adds the current epilogue to the list of selectable epilogues.
* Conversely, returning false from this function will make the current epilogue not selectable.
*/
/* 'gender' attribute: the epilogue will only be selectable if the player character has the given gender, or if the epilogue is marked for 'any' gender. */
var epilogue_gender = $(this).attr('gender');
if (epilogue_gender && epilogue_gender !== playerGender && epilogue_gender !== 'any') {
// if the gender doesn't match, don't make this epilogue selectable
return false;
}
var alsoPlaying = $(this).attr('alsoPlaying');
if (alsoPlaying !== undefined && !(players.some(function (p) { return p.id == alsoPlaying; }))) {
return false;
}
var playerStartingLayers = parseInterval($(this).attr('playerStartingLayers'));
if (playerStartingLayers !== undefined && !inInterval(humanPlayer.startingLayers, playerStartingLayers)) {
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
return false;
}
/* 'markers' attribute: the epilogue will only be selectable if the character has ALL markers listed within the attribute set. */
var all_marker_attr = $(this).attr('markers');
if (all_marker_attr
&& !all_marker_attr.trim().split(/\s+/).every(function (marker) {
return checkMarker(marker, player);
})) {
// not every marker set
return false;
}
/* 'not-markers' attribute: the epilogue will only be selectable if the character has NO markers listed within the attribute set. */
var no_marker_attr = $(this).attr('not-markers');
if (no_marker_attr
&& no_marker_attr.trim().split(/\s+/).some(function (marker) {
return checkMarker(marker, player);
})) {
// some disallowed marker set
return false;
}
/* 'any-markers' attribute: the epilogue will only be selectable if the character has at least ONE of the markers listed within the attribute set. */
var any_marker_attr = $(this).attr('any-markers');
if (any_marker_attr
&& !any_marker_attr.trim().split(/\s+/).some(function (marker) {
return checkMarker(marker, player);
})) {
// none of the markers set
return false;
}
/* 'alsoplaying-markers' attribute: this epilogue will only be selectable if ALL markers within the attribute are set for any OTHER characters in the game. */
var alsoplaying_marker_attr = $(this).attr('alsoplaying-markers');
if (alsoplaying_marker_attr
&& !alsoplaying_marker_attr.trim().split(/\s+/).every(function (marker) {
return players.some(function (p) {
return p !== player && checkMarker(marker, p);
});
})) {
// not every marker set by some other character
return false;
}
/* 'alsoplaying-not-markers' attribute: this epilogue will only be selectable if NO markers within the attribute are set for other characters in the game. */
var alsoplaying_not_marker_attr = $(this).attr('alsoplaying-not-markers');
if (alsoplaying_not_marker_attr
&& alsoplaying_not_marker_attr.trim().split(/\s+/).some(function (marker) {
return players.some(function (p) {
return p !== player && checkMarker(marker, p);
});
})) {
// some disallowed marker set by some other character
return false;
}
/* 'alsoplaying-any-markers' attribute: this epilogue will only be selectable if at least one marker within the attribute are set for any OTHER character in the game. */
var alsoplaying_any_marker_attr = $(this).attr('alsoplaying-any-markers');
if (alsoplaying_any_marker_attr
&& !alsoplaying_any_marker_attr.trim().split(/\s+/).some(function (marker) {
return players.some(function (p) {
return p !== player && checkMarker(marker, p);
});
})) {
// none of the markers set by any other player
return false;
}
// if we made it this far the epilogue must be selectable
return true;
}).map(function (i, e) { return parseEpilogue(player, e); }).get();
metodLD
committed

FarawayVision
committed
var animatedProperties = ["x", "y", "rotation", "scalex", "scaley", "skewx", "skewy", "alpha", "src", "zoom", "color"];
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
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
function addDirectiveToScene(scene, directive) {
switch (directive.type) {
case "move":
case "camera":
case "fade":
if (directive.keyframes.length > 1) {
//split the directive into multiple directives so that all the keyframes affect the same properties
//for instance, an animation with [frame1: {x:5, y:2}, frame2: {y:4}, frame3: {x:8, y:10}] would be split into two: [frame1: {x:5}, frame3: {x:8}] and [frame1: {y:2}, frame2: {y:4}, frame3: {y:10}]
//this makes the tweening simple for properties that don't change in intermediate frames, because those intermediate frames are removed
//first split the properties into buckets of frame indices where they appear
var propertyMap = {};
for (var i = 0; i < directive.keyframes.length; i++) {
var frame = directive.keyframes[i];
for (var j = 0; j < animatedProperties.length; j++) {
var property = animatedProperties[j];
if (frame.hasOwnProperty(property) && !Number.isNaN(frame[property])) {
if (!propertyMap[property]) {
propertyMap[property] = [];
}
propertyMap[property].push(i);
}
}
}
//next create directives for each combination of frames
var directives = {};
for (var prop in propertyMap) {
var key = propertyMap[prop].join(',');
var workingDirective = directives[key];
if (!workingDirective) {
//shallow copy the directive
workingDirective = {};
for (var srcProp in directive) {
if (directive.hasOwnProperty(srcProp)) {
workingDirective[srcProp] = directive[srcProp];
}
}
workingDirective.keyframes = [];
directives[key] = workingDirective;
scene.directives.push(workingDirective);
}
var lastStart = 0;
for (var i = 0; i < propertyMap[prop].length; i++) {
var srcFrame = directive.keyframes[propertyMap[prop][i]];
var targetFrame;
if (workingDirective.keyframes.length <= i) {
//shallow copy the frame minus the animatable properties
targetFrame = {};
for (var srcProp in srcFrame) {
if (srcFrame.hasOwnProperty(srcProp)) {
targetFrame[srcProp] = srcFrame[srcProp];
}
}
for (var j = 0; j < animatedProperties.length; j++) {
var property = animatedProperties[j];
delete targetFrame[property];
}
targetFrame.start = lastStart;
workingDirective.keyframes.push(targetFrame);
workingDirective.time = srcFrame.end;
lastStart = srcFrame.end;
}
else {
targetFrame = workingDirective.keyframes[i];
}
targetFrame[prop] = srcFrame[prop];
}
}
}
else {
scene.directives.push(directive);
}
break;
default:
scene.directives.push(directive);
}
}
function parseEpilogue(player, rawEpilogue, galleryEnding) {
//use parseXML() so that <image> tags come through properly
//not using parseXML() because internet explorer doesn't like it
if (!rawEpilogue) {
return;
}
player.markers = player.markers || {}; //ensure markers collection exists in the gallery even though they'll be empty
var $epilogue = $(rawEpilogue);
var title = $epilogue.find("title").html().trim();

FarawayVision
committed
var epilogue = {
title: title,
player: player,
scenes: [],
};
var scenes = epilogue.scenes;
metodLD
committed
//determine what type of epilogue this is and parse accordingly
var isLegacy = $epilogue.children("screen").length > 0;
if (isLegacy) {
parseLegacyEpilogue(player, epilogue, $epilogue);
else if ($epilogue.children("background").length > 0) {
var sceneWidth, sceneHeight;
var rawRatio = $epilogue.attr('ratio');
if (rawRatio) {
rawRatio = rawRatio.split(':');
sceneWidth = parseFloat(rawRatio[0]);
sceneHeight = parseFloat(rawRatio[1]);
parseNotQuiteLegacyEpilogue(player, epilogue, $epilogue, sceneWidth, sceneHeight);
var scene;
$epilogue.children("scene").each(function (index, rawScene) {
var $scene = $(rawScene);
if ($scene.attr("transition")) {
if (scenes.length === 0) {
//add a blank scene to transition from
scene = {
color: $scene.attr("color"),
directives: [],
};
scenes.push(scene);
scene = scenes[scenes.length - 1];
scene.transition = readProperties(rawScene, scene);
}
else {
var width = parseInt($scene.attr("width"), 10);
var height = parseInt($scene.attr("height"), 10);
scene = {
background: $scene.attr("background"),
width: width,
height: height,
aspectRatio: width / height,
zoom: parseFloat($scene.attr("zoom"), 10),
color: $scene.attr("color"),
overlayColor: $scene.attr("overlay"),
overlayAlpha: $scene.attr("overlay-alpha"),
directives: [],
scenes.push(scene);
scene.x = toSceneX($scene.attr("x"), scene);
scene.y = toSceneY($scene.attr("y"), scene);
var directives = scene.directives;
$scene.find("directive").each(function (i, item) {
var totalTime = 0;
var directive = readProperties(item, scene);
directive.keyframes = [];
$(item).find("keyframe").each(function (i2, frame) {
var keyframe = readProperties(frame, scene);
keyframe.ease = keyframe.ease || directive.ease;
keyframe.start = totalTime;
totalTime = Math.max(totalTime, keyframe.time);
keyframe.end = totalTime;
keyframe.interpolation = directive.interpolation || "linear";
directive.keyframes.push(keyframe);
});
if (directive.keyframes.length === 0) {
//if no keyframes were explicitly provided, use the directive itself as a keyframe
directive.start = 0;
directive.end = directive.time;
directive.keyframes.push(directive);
}
else {
directive.time = totalTime;
}
if (directive.marker && !checkMarker(directive.marker, player)) {
directive.type = "skip";
}
directives.push(directive);
});
//if the last scene has a transition, add a dummy scene to the end
if (scenes.length > 0 && scenes[scenes.length - 1].transition) {
scene = {
color: scenes[scenes.length - 1].transition.color,
directives: [],
};
scenes.push(scene);
}
/**
* Parses an old screen-based epilogue and converts it into directive format
*/
function parseLegacyEpilogue(player, epilogue, $xml) {
var scenes = epilogue.scenes;
$xml.find("screen").each(function () {
var $this = $(this);
var image = $this.attr("img").trim();
//create a scene for each screen
var scene = {
directives: [],
background: image,
};
scenes.push(scene);
parseSceneContent(player, scene, $this);
});
/**
* Parses an epilogue that came in the format background > scene > sprite/text and converts it into directive format
*/
function parseNotQuiteLegacyEpilogue(player, epilogue, $xml, sceneWidth, sceneHeight) {
var scenes = epilogue.scenes;
$xml.find('background').each(function () {
var $this = $(this);
var image = $this.attr('img').trim();
if (image.length == 0) {
image = '';
}
//create a directive-based scene for each scene in the background
$this.find('scene').each(function () {
var scene = {
directives: [],
background: image,
width: sceneWidth,
height: sceneHeight,
aspectRatio: sceneWidth / sceneHeight,
};
scenes.push(scene);
parseSceneContent(player, scene, $(this)); //this is intentionally $(this) instead of $this like in parseLegacyEpilogue
});
});
}
function parseSceneContent(player, scene, $scene) {
var directive;
var backgroundTransform = [$scene.attr('background-position-x'), $scene.attr('background-position-y'), $scene.attr('background-zoom') || 100];
scene.x = toSceneX(backgroundTransform[0], scene);
scene.y = toSceneY(backgroundTransform[1], scene);
scene.zoom = parseFloat(backgroundTransform[2]) / 100;
} catch (e) { }
$scene.find('sprite').each(function () {
var x = $(this).find("x").html().trim();
var y = $(this).find("y").html().trim();
var width = $(this).find("width").html().trim();
var src = $(this).find('src').html().trim();
var css = $(this).attr('css');
metodLD
committed
directive = {
type: "sprite",
id: "obj" + (epilogueSuffix++),
x: toSceneX(x, scene),
y: toSceneY(y, scene),
width: width,
src: src,
css: css,
}
scene.directives.push(directive);
});
//get the information for all the text boxes
$scene.find("text").each(function () {
//the text box's position and width
var x = $(this).find("x").html().trim();
var y = $(this).find("y").html().trim();
var w = $(this).find("width").html();
var a = $(this).find("arrow").html();
//the width component is optional. Use a default of 20%.
if (w) {
w = w.trim();
}
if (!w || (w.length <= 0)) {
w = "20%"; //default to text boxes having a width of 20%
}
metodLD
committed
//dialogue bubble arrow
if (a) {
a = a.trim().toLowerCase();
if (a.length >= 1) {
a = "arrow-" + a; //class name for the different arrows. Only use if the writer specified something.
}
} else {
a = "";
}
//automatically centre the text box, if the writer wants that.
metodLD
committed
var text = fixupDialogue($(this).find("content").html().trim()); //the actual content of the text box
metodLD
committed
var css = $(this).attr('css');
directive = {
type: "text",
id: "obj" + (epilogueSuffix++),
x: x,
y: y,
arrow: a,
width: w,
text: text,
css: css,
}
scene.directives.push(directive);
scene.directives.push({ type: "pause" });
/************************************************************
* Read attributes from a source XML object and put them into properties of a JS object
************************************************************/
function readProperties(sourceObj, scene) {
var targetObj = {};
var $obj = $(sourceObj);
$.each(sourceObj.attributes, function (i, attr) {
var name = attr.name.toLowerCase();
var value = attr.value;
targetObj[name] = value;
//properties needing special handling
targetObj.delay = parseFloat(targetObj.delay, 10) * 1000 || 0;
if (targetObj.type !== "text") {
// scene directives
spnati_edit
committed
targetObj.time = parseFloat(targetObj.time, 10) * 1000 || 0;
if (targetObj.alpha) { targetObj.alpha = parseFloat(targetObj.alpha, 10); }
targetObj.zoom = parseFloat(targetObj.zoom, 10);
targetObj.rotation = parseFloat(targetObj.rotation, 10);
targetObj.angle = parseFloat(targetObj.angle, 10) || 0;
if (targetObj.scale) {
targetObj.scalex = targetObj.scaley = targetObj.scale;
}
targetObj.scalex = parseFloat(targetObj.scalex, 10);
targetObj.scaley = parseFloat(targetObj.scaley, 10);
targetObj.skewx = parseFloat(targetObj.skewx, 10);
targetObj.skewy = parseFloat(targetObj.skewy, 10);
if (targetObj.x) { targetObj.x = toSceneX(targetObj.x, scene); }
if (targetObj.y) { targetObj.y = toSceneY(targetObj.y, scene); }
targetObj.iterations = parseInt(targetObj.iterations) || 0;
targetObj.rate = parseFloat(targetObj.rate, 10) || 0;
targetObj.count = parseFloat(targetObj.count, 10) || 0;
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
}
else {
// textboxes
// ensure an ID
var id = targetObj.id;
if (!id) {
targetObj.id = "obj" + (epilogueSuffix++);
}
// text (not from an attribute, so not populated automatically)
targetObj.text = fixupDialogue($obj.html().trim());
var w = targetObj.width;
//the width component is optional. Use a default of 20%.
if (w) {
w = w.trim();
}
if (!w || (w.length <= 0)) {
w = "20%"; //default to text boxes having a width of 20%
}
targetObj.width = w;
//dialogue bubble arrow
var a = targetObj.arrow; if (a) {
a = a.trim().toLowerCase();
if (a.length >= 1) {
a = "arrow-" + a; //class name for the different arrows. Only use if the writer specified something.
}
} else {
a = "";
}
targetObj.arrow = a;
//automatically centre the text box, if the writer wants that.
var x = targetObj.x;
targetObj.x = getCenteredPosition(w);
}
}
return targetObj;
}
/************************************************************
* Add the epilogue to the Epilogue modal
************************************************************/
function addEpilogueEntry(epilogue) {
var num = epilogues.length; //index number of the new epilogue
epilogues.push(epilogue);
metodLD
committed
var nameStr = player.first + " " + player.last;
if (player.first.length <= 0 || player.last.length <= 0) {
nameStr = player.first + player.last; //only use a space if they have both first and last names
}
metodLD
committed
var epilogueTitle = nameStr + ": " + epilogue.title;
var idName = 'epilogue-option-' + num;
var clickAction = "selectEpilogue(" + num + ")";
var unlocked = save.hasEnding(player.id, epilogue.title) ? " unlocked" : "";
metodLD
committed
var htmlStr = '<li id="' + idName + '" class="epilogue-entry' + unlocked + '"><button onclick="' + clickAction + '">' + epilogueTitle + '</button></li>';
metodLD
committed
$epilogueList.append(htmlStr);
epilogueSelections.push($('#' + idName));
}
/************************************************************
* Clear the Epilogue modal
************************************************************/
function clearEpilogueList() {
$epilogueHeader.html('');
$epilogueList.html('');
epilogues = [];
epilogueSelections = [];
/************************************************************
* Cleans up epilogue data
************************************************************/
function clearEpilogue() {
if (epiloguePlayer) {
epiloguePlayer.destroy();
epiloguePlayer = null;
}
}
/************************************************************
* The user has clicked on a button to choose a particular Epilogue
************************************************************/
function selectEpilogue(epNumber) {
chosenEpilogue = epilogues[epNumber]; //select the chosen epilogues
metodLD
committed
for (var i = 0; i < epilogues.length; i++) {
epilogueSelections[i].removeClass("active"); //make sure no other epilogue is selected
}
epilogueSelections[epNumber].addClass("active"); //mark the selected epilogue as selected
$epilogueAcceptButton.prop("disabled", false); //allow the player to accept the epilogue
}
/************************************************************
* Show the modal for the player to choose an Epilogue, or restart the game.
************************************************************/
if (SENTRY_INITIALIZED) {
Sentry.addBreadcrumb({
category: 'ui',
message: 'Showing epilogue modal.',
level: 'info'
});
}
clearEpilogueList(); //remove any already loaded epilogues
chosenEpilogue = null; //reset any currently-chosen epilogue
$epilogueAcceptButton.prop("disabled", true); //don't let the player accept an epilogue until they've chosen one
//whether or not the human player won
var playerWon = !humanPlayer.out;
if (EPILOGUES_ENABLED && playerWon) { //all the epilogues are for when the player wins, so don't allow them to choose one if they lost
//load the epilogue data for each player
players.forEach(function (p) {
loadEpilogueData(p).forEach(addEpilogueEntry);
});
}
//are there any epilogues available for the player to see?
var haveEpilogues = (epilogues.length >= 1); //whether or not there are any epilogues available
$epilogueAcceptButton.css("visibility", haveEpilogues ? "visible" : "hidden");
if (EPILOGUES_ENABLED) {
//decide which header string to show the player. This describes the situation.
var headerStr = '';
if (playerWon) {
headerStr = winStr; //player won, and there are epilogues available
if (!haveEpilogues) {
headerStr = winStrNone; //player won, but none of the NPCs have epilogues
}
} else {
if (playerWon) {
headerStr = winEpiloguesDisabledStr;
} else {
headerStr = lossEpiloguesDisabledStr;
}
}
metodLD
committed
$epilogueHeader.html(headerStr); //set the header string
$epilogueSelectionModal.modal("show");//show the epilogue selection modal
}
/************************************************************
* Start the Epilogue
************************************************************/
function doEpilogue() {
save.addEnding(chosenEpilogue.player.id, chosenEpilogue.title);
if (USAGE_TRACKING) {
var usage_tracking_report = {
'date': (new Date()).toISOString(),
'commit': VERSION_COMMIT,
'type': 'epilogue',
'session': sessionID,
'game': gameID,
'userAgent': navigator.userAgent,
'origin': getReportedOrigin(),
'table': {},
'chosen': {
'id': chosenEpilogue.player.id,
'title': chosenEpilogue.title
}
};
if (SENTRY_INITIALIZED) {
Sentry.addBreadcrumb({
category: 'epilogue',
message: 'Starting '+chosenEpilogue.player.id+' epilogue: '+chosenEpilogue.title,
level: 'info'
});
Sentry.setTag("epilogue_player", chosenEpilogue.player.id);
Sentry.setTag("epilogue", chosenEpilogue.title);
Sentry.setTag("epilogue_gallery", false);
Sentry.setTag("screen", "epilogue");
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);
},
});
}
metodLD
committed
metodLD
committed

ReformCopyright
committed
screenTransition($gameScreen, $epilogueScreen);
/************************************************************
* Starts up an epilogue, pre-fetching all its images before displaying anything in order to handle certain computations that rely on the image sizes
************************************************************/
$("#epilogue-spinner").show();
epiloguePlayer = new EpiloguePlayer(epilogue);
epiloguePlayer.load();
updateEpilogueButtons();
}
function moveEpilogueForward() {
if (epiloguePlayer && epiloguePlayer.loaded) {
epiloguePlayer.advanceDirective();
updateEpilogueButtons();
}
}
function moveEpilogueBack() {
if (epiloguePlayer && epiloguePlayer.loaded) {
epiloguePlayer.revertDirective();
updateEpilogueButtons();
}
}
/************************************************************
************************************************************/
function updateEpilogueButtons() {
if (!epiloguePlayer) {
var $epiloguePrevButton = $('#epilogue-buttons > #epilogue-previous');
var $epilogueNextButton = $('#epilogue-buttons > #epilogue-next');
var $epilogueRestartButton = $('#epilogue-buttons > #epilogue-restart');
$epiloguePrevButton.prop("disabled", !epiloguePlayer.hasPreviousDirectives());
$epilogueNextButton.prop("disabled", !epiloguePlayer.hasMoreDirectives());
$epilogueRestartButton.prop("disabled", epiloguePlayer.hasMoreDirectives());
}
/************************************************************
* Class for playing through an epilogue
************************************************************/