mirror of
https://gitlab.com/Oreolek/salet-module.git
synced 2024-07-05 08:14:23 +03:00
More untested conversions WIP
This commit is contained in:
parent
e54585cdbc
commit
c48c1ff11e
430
lib/salet.coffee
430
lib/salet.coffee
|
@ -20,7 +20,7 @@ parseFn = (str) ->
|
||||||
#{str}
|
#{str}
|
||||||
#})
|
#})
|
||||||
"""
|
"""
|
||||||
return eval(fstr);
|
return eval(fstr)
|
||||||
|
|
||||||
# Scrolls the top of the screen to the specified point
|
# Scrolls the top of the screen to the specified point
|
||||||
scrollTopTo = (value) ->
|
scrollTopTo = (value) ->
|
||||||
|
@ -63,143 +63,6 @@ augmentLinks = (content) ->
|
||||||
)
|
)
|
||||||
return output
|
return output
|
||||||
|
|
||||||
/* Erases the character in local storage. This is permanent!
|
|
||||||
/* To restart the game afterwards, we perform a simple page refresh.
|
|
||||||
/* This guarantees authors don't have to care about "tainting" the
|
|
||||||
/* game state across save/erase cycles, meaning that character.sandbox
|
|
||||||
/* no longer has to be the end-all be-all repository of game state. */
|
|
||||||
var doErase = function(force) {
|
|
||||||
var saveId = getSaveId();
|
|
||||||
if (localStorage.getItem(saveId)) {
|
|
||||||
if (force || confirm("erase_message".l())) {
|
|
||||||
localStorage.removeItem(saveId);
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Find and return a list of ids for all situations with the given tag. */
|
|
||||||
var getSituationIdsWithTag = function(tag) {
|
|
||||||
var result = [];
|
|
||||||
for (var situationId in game.situations) {
|
|
||||||
var situation = game.situations[situationId];
|
|
||||||
|
|
||||||
for (var i = 0; i < situation.tags.length; ++i) {
|
|
||||||
if (situation.tags[i] == tag) {
|
|
||||||
result.push(situationId);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Clear the current game output and start again. */
|
|
||||||
var startGame = function() {
|
|
||||||
progress.seed = new Date().toString();
|
|
||||||
|
|
||||||
character = new Character();
|
|
||||||
system.rnd = new Random(progress.seed);
|
|
||||||
progress.sequence = [{link:game.start, when:0}];
|
|
||||||
|
|
||||||
// Empty the display
|
|
||||||
$("#content").empty();
|
|
||||||
|
|
||||||
// Start the game
|
|
||||||
startTime = new Date().getTime() * 0.001;
|
|
||||||
system.time = 0;
|
|
||||||
if (game.init) game.init(character, system);
|
|
||||||
|
|
||||||
// Do the first state.
|
|
||||||
doTransitionTo(game.start);
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Saves the character to local storage. */
|
|
||||||
var saveGame = function() {
|
|
||||||
// Store when we're saving the game, to avoid exploits where a
|
|
||||||
// player loads their file to gain extra time.
|
|
||||||
var now = (new Date()).getTime() * 0.001;
|
|
||||||
progress.saveTime = now - startTime;
|
|
||||||
|
|
||||||
// Save the game.
|
|
||||||
localStorage.setItem(getSaveId(), JSON.stringify(progress));
|
|
||||||
|
|
||||||
// Switch the button highlights.
|
|
||||||
$("#erase").removeClass('disabled');
|
|
||||||
$("#load").removeClass('disabled');
|
|
||||||
$("#save").addClass('disabled');
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Loads the game from the given data */
|
|
||||||
var loadGame = function(characterData) {
|
|
||||||
progress = characterData;
|
|
||||||
|
|
||||||
character = new Character();
|
|
||||||
system.rnd = new Random(progress.seed);
|
|
||||||
|
|
||||||
// Empty the display
|
|
||||||
$("#content").empty();
|
|
||||||
$("#intro").empty();
|
|
||||||
|
|
||||||
// Now play through the actions so far:
|
|
||||||
if (game.init) game.init(character, system);
|
|
||||||
|
|
||||||
// Run through all the player's history.
|
|
||||||
interactive = false;
|
|
||||||
for (var i = 0; i < progress.sequence.length; i++) {
|
|
||||||
var step = progress.sequence[i];
|
|
||||||
// The action must be done at the recorded time.
|
|
||||||
system.time = step.when;
|
|
||||||
processLink(step.link);
|
|
||||||
}
|
|
||||||
interactive = true;
|
|
||||||
|
|
||||||
// Reverse engineer the start time.
|
|
||||||
var now = new Date().getTime() * 0.001;
|
|
||||||
startTime = now - progress.saveTime;
|
|
||||||
};
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
// Setup
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
|
|
||||||
var begin = function () {
|
|
||||||
/* Set up the game when everything is loaded. */
|
|
||||||
|
|
||||||
// Handle storage.
|
|
||||||
if (hasLocalStorage()) {
|
|
||||||
var erase = $("#erase").click(function() {
|
|
||||||
doErase();
|
|
||||||
});
|
|
||||||
var save = $("#save").click(saveGame);
|
|
||||||
|
|
||||||
var storedCharacter = localStorage.getItem(getSaveId());
|
|
||||||
if (storedCharacter) {
|
|
||||||
try {
|
|
||||||
loadGame(JSON.parse(storedCharacter));
|
|
||||||
save.addClass('disabled')
|
|
||||||
erase.removeClass('disabled')
|
|
||||||
} catch(err) {
|
|
||||||
doErase(true);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
startGame();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
startGame();
|
|
||||||
}
|
|
||||||
// Any point that an option list appears, its options are its
|
|
||||||
// first links.
|
|
||||||
$("body").on('click', "ul.options li, #menu li", function(event) {
|
|
||||||
// Make option clicks pass through to their first link.
|
|
||||||
var link = $("a", this);
|
|
||||||
if (link.length > 0) {
|
|
||||||
$(link.get(0)).click();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
###
|
###
|
||||||
This is the control structure, it has minimal amount of data and
|
This is the control structure, it has minimal amount of data and
|
||||||
this data is volatile anyway (as in, it won't get saved).
|
this data is volatile anyway (as in, it won't get saved).
|
||||||
|
@ -587,116 +450,215 @@ Salet = {
|
||||||
@enableSaving()
|
@enableSaving()
|
||||||
|
|
||||||
disableSaving: () ->
|
disableSaving: () ->
|
||||||
|
$("#save").addClass('disabled');
|
||||||
enableSaving: () ->
|
enableSaving: () ->
|
||||||
$("#save").removeClass('disabled');
|
$("#save").removeClass('disabled');
|
||||||
|
|
||||||
/* This gets called to actually do the work of processing a code.
|
###
|
||||||
* When one doLink is called (or a link is clicked), this may set call
|
This gets called to actually do the work of processing a code.
|
||||||
* code that further calls doLink, and so on. This method processes
|
When one doLink is called (or a link is clicked), this may set call
|
||||||
* each one, and processLink manages this.
|
code that further calls doLink, and so on. This method processes
|
||||||
*/
|
each one, and processLink manages this.
|
||||||
var processOneLink = function(code) {
|
###
|
||||||
var match = code.match(linkRe);
|
processOneLink: = (code) ->
|
||||||
assert(match, "link_not_valid".l({link:code}));
|
match = code.match(linkRe)
|
||||||
|
assert(match, "link_not_valid".l({link:code}))
|
||||||
|
|
||||||
var situation = match[1];
|
situation = match[1]
|
||||||
var action = match[3];
|
action = match[3]
|
||||||
|
|
||||||
// Change the situation
|
# Change the situation
|
||||||
if (situation !== '.') {
|
if (situation !== '.')
|
||||||
if (situation !== current) {
|
if (situation !== current)
|
||||||
doTransitionTo(situation);
|
doTransitionTo(situation)
|
||||||
}
|
else
|
||||||
} else {
|
# We should have an action if we have no situation change.
|
||||||
// We should have an action if we have no situation change.
|
assert(action, "link_no_action".l())
|
||||||
assert(action, "link_no_action".l());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Carry out the action
|
# Carry out the action
|
||||||
if (action) {
|
if (action)
|
||||||
situation = getCurrentSituation();
|
situation = getCurrentSituation()
|
||||||
if (situation) {
|
if (situation)
|
||||||
if (game.beforeAction) {
|
if (game.beforeAction)
|
||||||
// Try the global act handler, and see if we need
|
# Try the global act handler, and see if we need
|
||||||
// to notify the situation.
|
# to notify the situation.
|
||||||
var consumed = game.beforeAction(
|
consumed = game.beforeAction(
|
||||||
character, system, current, action
|
character, system, current, action
|
||||||
);
|
)
|
||||||
if (consumed !== true) {
|
if (consumed != true)
|
||||||
situation.act(character, system, action);
|
situation.act(character, system, action)
|
||||||
}
|
else
|
||||||
} else {
|
# We have no global act handler, always notify the situation.
|
||||||
// We have no global act handler, always notify
|
situation.act(character, system, action)
|
||||||
// the situation.
|
|
||||||
situation.act(character, system, action);
|
|
||||||
}
|
|
||||||
if (game.afterAction) {
|
|
||||||
game.afterAction(character, system, current, action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/* This gets called when the user clicks a link to carry out an
|
if (game.afterAction)
|
||||||
* action. */
|
game.afterAction(character, system, current, action)
|
||||||
var processClick = function(code) {
|
|
||||||
var now = (new Date()).getTime() * 0.001;
|
|
||||||
system.time = now - startTime;
|
|
||||||
progress.sequence.push({link:code, when:system.time});
|
|
||||||
processLink(code);
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Transitions between situations. */
|
# This gets called when the user clicks a link to carry out an action.
|
||||||
var doTransitionTo = function(newSituationId) {
|
processClick: (code) ->
|
||||||
var oldSituationId = current;
|
now = (new Date()).getTime() * 0.001
|
||||||
var oldSituation = getCurrentSituation();
|
system.time = now - startTime
|
||||||
var newSituation = game.situations[newSituationId];
|
progress.sequence.push({link:code, when:system.time})
|
||||||
|
processLink(code)
|
||||||
|
|
||||||
assert(newSituation, "unknown_situation".l({id:newSituationId}));
|
# Transitions between situations.
|
||||||
|
doTransitionTo: (newSituationId) ->
|
||||||
|
oldSituationId = current
|
||||||
|
oldSituation = getCurrentSituation()
|
||||||
|
newSituation = game.situations[newSituationId]
|
||||||
|
|
||||||
// We might not have an old situation if this is the start of
|
assert(newSituation, "unknown_situation".l({id:newSituationId}))
|
||||||
// the game.
|
|
||||||
if (oldSituation) {
|
|
||||||
if (game.exit) {
|
|
||||||
game.exit(character, system, oldSituationId, newSituationId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove links and transient sections.
|
# We might not have an old situation if this is the start of the game.
|
||||||
$('#content a').each(function(index, element) {
|
if (oldSituation)
|
||||||
var a = $(element);
|
if (game.exit)
|
||||||
if (a.hasClass('sticky') || a.attr("href").match(/[?&]sticky[=&]?/))
|
game.exit(character, system, oldSituationId, newSituationId);
|
||||||
return;
|
|
||||||
a.replaceWith($("<span>").addClass("ex_link").html(a.html()));
|
|
||||||
});
|
|
||||||
var contentToHide = $('#content .transient, #content ul.options');
|
|
||||||
contentToHide.add($("#content a").filter(function(){
|
|
||||||
return $(this).attr("href").match(/[?&]transient[=&]?/);
|
|
||||||
}));
|
|
||||||
if (interactive) {
|
|
||||||
contentToHide.
|
|
||||||
animate({opacity: 0}, 350).
|
|
||||||
slideUp(500, function() {
|
|
||||||
$(this).remove();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
contentToHide.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move the character.
|
# Remove links and transient sections.
|
||||||
current = newSituationId;
|
$('#content a').each((index, element) ->
|
||||||
|
a = $(element);
|
||||||
|
if (a.hasClass('sticky') || a.attr("href").match(/[?&]sticky[=&]?/))
|
||||||
|
return;
|
||||||
|
a.replaceWith($("<span>").addClass("ex_link").html(a.html()))
|
||||||
|
)
|
||||||
|
contentToHide = $('#content .transient, #content ul.options')
|
||||||
|
contentToHide.add($("#content a").filter(() ->
|
||||||
|
return $(this).attr("href").match(/[?&]transient[=&]?/)
|
||||||
|
))
|
||||||
|
if (interactive)
|
||||||
|
contentToHide.animate({opacity: 0}, 350).
|
||||||
|
slideUp(500, () ->
|
||||||
|
$(this).remove()
|
||||||
|
)
|
||||||
|
else
|
||||||
|
contentToHide.remove()
|
||||||
|
|
||||||
// Notify the incoming situation.
|
# Move the character.
|
||||||
if (game.enter) {
|
current = newSituationId
|
||||||
game.enter(character, system, oldSituationId, newSituationId);
|
|
||||||
}
|
|
||||||
newSituation.entering(character, system, oldSituationId);
|
|
||||||
|
|
||||||
// additional hook for when the situation text has already been printed
|
# Notify the incoming situation.
|
||||||
if (game.afterEnter) {
|
if (game.enter)
|
||||||
game.afterEnter(character, system, oldSituationId, newSituationId);
|
game.enter(character, system, oldSituationId, newSituationId)
|
||||||
}
|
newSituation.entering(character, system, oldSituationId)
|
||||||
};
|
|
||||||
|
# additional hook for when the situation text has already been printed
|
||||||
|
if (game.afterEnter)
|
||||||
|
game.afterEnter(character, system, oldSituationId, newSituationId)
|
||||||
|
|
||||||
|
###
|
||||||
|
Erases the character in local storage. This is permanent!
|
||||||
|
To restart the game afterwards, we perform a simple page refresh.
|
||||||
|
This guarantees authors don't have to care about "tainting" the
|
||||||
|
game state across save/erase cycles, meaning that character.sandbox
|
||||||
|
no longer has to be the end-all be-all repository of game state. */
|
||||||
|
###
|
||||||
|
erase_save: (force = false) =>
|
||||||
|
save_id = @getSaveId() # save slot
|
||||||
|
if (localStorage.getItem(saveId) and (force or confirm("erase_message".l())))
|
||||||
|
localStorage.removeItem(saveId)
|
||||||
|
window.location.reload()
|
||||||
|
|
||||||
|
# Find and return a list of ids for all situations with the given tag.
|
||||||
|
getSituationIdsWithTag: (tag) =>
|
||||||
|
result = []
|
||||||
|
for (situationId, situation of @situations)
|
||||||
|
for (i = 0; i < situation.tags.length; ++i)
|
||||||
|
if (situation.tags[i] == tag)
|
||||||
|
result.push(situationId)
|
||||||
|
break
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Saves the character to local storage.
|
||||||
|
saveGame: () ->
|
||||||
|
# Store when we're saving the game, to avoid exploits where a
|
||||||
|
# player loads their file to gain extra time.
|
||||||
|
now = (new Date()).getTime() * 0.001
|
||||||
|
progress.saveTime = now - startTime
|
||||||
|
|
||||||
|
# Save the game.
|
||||||
|
localStorage.setItem(getSaveId(), JSON.stringify(progress))
|
||||||
|
|
||||||
|
# Switch the button highlights.
|
||||||
|
$("#erase").removeClass('disabled')
|
||||||
|
$("#load").removeClass('disabled')
|
||||||
|
@disableSaving()
|
||||||
|
|
||||||
|
# Loads the game from the given data
|
||||||
|
loadGame = (characterData) ->
|
||||||
|
progress = characterData
|
||||||
|
|
||||||
|
character = new Character()
|
||||||
|
system.rnd = new Random(progress.seed)
|
||||||
|
|
||||||
|
# Empty the display
|
||||||
|
$("#content").empty()
|
||||||
|
$("#intro").empty()
|
||||||
|
|
||||||
|
# Now play through the actions so far:
|
||||||
|
if (game.init)
|
||||||
|
game.init(character, system)
|
||||||
|
|
||||||
|
# Run through all the player's history.
|
||||||
|
interactive = false
|
||||||
|
for (i = 0; i < progress.sequence.length; i++)
|
||||||
|
step = progress.sequence[i]
|
||||||
|
# The action must be done at the recorded time.
|
||||||
|
system.time = step.when
|
||||||
|
processLink(step.link)
|
||||||
|
interactive = true
|
||||||
|
|
||||||
|
# Reverse engineer the start time.
|
||||||
|
now = new Date().getTime() * 0.001
|
||||||
|
startTime = now - progress.saveTime
|
||||||
|
|
||||||
|
game_begin: () ->
|
||||||
|
# Handle storage.
|
||||||
|
storedCharacter = false
|
||||||
|
if (@hasLocalStorage())
|
||||||
|
storedCharacter = localStorage.getItem(@getSaveId())
|
||||||
|
|
||||||
|
if (storedCharacter)
|
||||||
|
try
|
||||||
|
@loadGame(JSON.parse(storedCharacter))
|
||||||
|
@disableSaving()
|
||||||
|
$("#erase").removeClass('disabled')
|
||||||
|
catch(err)
|
||||||
|
@erase_save(true)
|
||||||
|
else
|
||||||
|
progress.seed = new Date().toString()
|
||||||
|
|
||||||
|
character = new Character()
|
||||||
|
system.rnd = new Random(progress.seed)
|
||||||
|
progress.sequence = [{link:game.start, when:0}]
|
||||||
|
|
||||||
|
# Empty the display
|
||||||
|
$("#content").empty()
|
||||||
|
|
||||||
|
# Start the game
|
||||||
|
startTime = new Date().getTime() * 0.001
|
||||||
|
system.time = 0
|
||||||
|
if (game.init)
|
||||||
|
game.init(character, system)
|
||||||
|
|
||||||
|
# Do the first state.
|
||||||
|
doTransitionTo(game.start);
|
||||||
|
|
||||||
|
# Any point that an option list appears, its options are its first links.
|
||||||
|
$("body").on('click', "ul.options li, #menu li", (event) ->
|
||||||
|
# Make option clicks pass through to their first link.
|
||||||
|
link = $("a", this)
|
||||||
|
if (link.length > 0)
|
||||||
|
$(link.get(0)).click()
|
||||||
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Set up the game when everything is loaded.
|
||||||
|
$(document).ready(() ->
|
||||||
|
salet = new Salet
|
||||||
|
if (salet.hasLocalStorage())
|
||||||
|
$("#erase").click(salet.erase_save) # is Salet defined here?
|
||||||
|
$("#save").click(salet.saveGame)
|
||||||
|
|
||||||
|
salet.game_begin()
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in a new issue