mirror of
https://gitlab.com/Oreolek/salet.git
synced 2024-07-07 01:04:25 +03:00
Proper variables and putting things
This commit is contained in:
parent
43c66af5a6
commit
2b0f714ac0
|
@ -4,10 +4,10 @@ dialogue = require('../../lib/dialogue.coffee')
|
||||||
oneOf = require('../../lib/oneOf.coffee')
|
oneOf = require('../../lib/oneOf.coffee')
|
||||||
Salet = require('../../lib/salet.coffee')
|
Salet = require('../../lib/salet.coffee')
|
||||||
|
|
||||||
salet = new Salet
|
salet = Salet({
|
||||||
salet.view.init(salet)
|
game_id: "your-game-id-here"
|
||||||
salet.game_id = "your-game-id-here"
|
game_version: "1.0"
|
||||||
salet.game_version = "1.0"
|
})
|
||||||
$(document).ready(() ->
|
$(document).ready(() ->
|
||||||
salet.beginGame()
|
salet.beginGame()
|
||||||
)
|
)
|
||||||
|
|
|
@ -86,10 +86,10 @@ room "shop-inside", salet,
|
||||||
The insides are painted pastel white, honouring The Great Milk Spill of 1985.
|
The insides are painted pastel white, honouring The Great Milk Spill of 1985.
|
||||||
"""
|
"""
|
||||||
objects: [
|
objects: [
|
||||||
merchant: obj "merchant",
|
obj "merchant",
|
||||||
dsc: "A {{merchant}} eyes you warily."
|
dsc: "A {{merchant}} eyes you warily."
|
||||||
takeable: false
|
takeable: false
|
||||||
act: (system) =>
|
act: (salet) =>
|
||||||
salet.processClick("merchdialogue")
|
salet.processClick("merchdialogue")
|
||||||
return ""
|
return ""
|
||||||
]
|
]
|
||||||
|
@ -100,7 +100,7 @@ The chain of calls is very weird and this puts an object IN EVERY ROOM for God's
|
||||||
I need someone smarter than me to fix this.
|
I need someone smarter than me to fix this.
|
||||||
###
|
###
|
||||||
|
|
||||||
lamp = obj "lamp", salet,
|
lamp = obj "lamp",
|
||||||
takeable: true
|
takeable: true
|
||||||
lamp.put(salet, "shop-inside")
|
lamp.put(salet, "shop-inside")
|
||||||
|
|
||||||
|
|
21
lib/character.coffee
Normal file
21
lib/character.coffee
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
class Character
|
||||||
|
constructor: (spec) ->
|
||||||
|
@inventory = []
|
||||||
|
|
||||||
|
@take = (thing) =>
|
||||||
|
@inventory.push thing
|
||||||
|
@drop = (thing) =>
|
||||||
|
for i in @inventory
|
||||||
|
if i.name == thing
|
||||||
|
index = @objects.indexOf(thing)
|
||||||
|
@inventory.splice(index, 1)
|
||||||
|
|
||||||
|
for index, value of spec
|
||||||
|
this[index] = value
|
||||||
|
return this
|
||||||
|
|
||||||
|
character = (spec) ->
|
||||||
|
spec ?= {}
|
||||||
|
return( new Character(spec) )
|
||||||
|
|
||||||
|
module.exports = character
|
|
@ -40,6 +40,8 @@ class SaletObj
|
||||||
if salet.rooms[location]?
|
if salet.rooms[location]?
|
||||||
@location = location
|
@location = location
|
||||||
salet.rooms[location].take(this)
|
salet.rooms[location].take(this)
|
||||||
|
else
|
||||||
|
console.log("Could not find location #{location} for an object #{@name}")
|
||||||
@delete = (salet, location = false) =>
|
@delete = (salet, location = false) =>
|
||||||
if location == false
|
if location == false
|
||||||
location = @location
|
location = @location
|
||||||
|
|
|
@ -144,7 +144,7 @@ class SaletRoom
|
||||||
Puts an object in this room.
|
Puts an object in this room.
|
||||||
###
|
###
|
||||||
@take = (thing) =>
|
@take = (thing) =>
|
||||||
@objects[thing.name] = thing
|
@objects.push(thing)
|
||||||
|
|
||||||
@drop = (name) =>
|
@drop = (name) =>
|
||||||
for thing in @objects
|
for thing in @objects
|
||||||
|
@ -164,7 +164,8 @@ class SaletRoom
|
||||||
# If it's takeable, the player can take this object.
|
# If it's takeable, the player can take this object.
|
||||||
# If not, we check the "act" function.
|
# If not, we check the "act" function.
|
||||||
if thing.takeable
|
if thing.takeable
|
||||||
system.character.inventory.push thing
|
console.log system
|
||||||
|
system.character.take(thing)
|
||||||
@drop name
|
@drop name
|
||||||
system.view.clearContent()
|
system.view.clearContent()
|
||||||
@entering.fcall(this, system, @name)
|
@entering.fcall(this, system, @name)
|
||||||
|
|
705
lib/salet.coffee
705
lib/salet.coffee
|
@ -1,6 +1,7 @@
|
||||||
markdown = require('./markdown.coffee')
|
markdown = require('./markdown.coffee')
|
||||||
SaletView = require('./view.coffee')
|
SaletView = require('./view.coffee')
|
||||||
Random = require('./random.js')
|
Random = require('./random.js')
|
||||||
|
character = require('./character.coffee')
|
||||||
languages = require('./localize.coffee')
|
languages = require('./localize.coffee')
|
||||||
|
|
||||||
###
|
###
|
||||||
|
@ -17,9 +18,6 @@ String.prototype.fcall = () ->
|
||||||
|
|
||||||
assert = (msg, assertion) -> console.assert assertion, msg
|
assert = (msg, assertion) -> console.assert assertion, msg
|
||||||
|
|
||||||
class Character
|
|
||||||
inventory: []
|
|
||||||
|
|
||||||
###
|
###
|
||||||
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).
|
||||||
|
@ -27,421 +25,420 @@ this data is volatile anyway (as in, it won't get saved).
|
||||||
There is only one instance of this class.
|
There is only one instance of this class.
|
||||||
###
|
###
|
||||||
class Salet
|
class Salet
|
||||||
# REDEFINE THIS IN YOUR GAME
|
constructor: (spec) ->
|
||||||
game_id: null
|
@character = character()
|
||||||
game_version: "1.0"
|
|
||||||
autosave: true
|
|
||||||
|
|
||||||
rnd: null
|
# REDEFINE THIS IN YOUR GAME
|
||||||
time: 0
|
@game_id = null
|
||||||
|
@game_version = "1.0"
|
||||||
|
@autosave = true
|
||||||
|
|
||||||
# Corresponding room names to room objects.
|
@rnd = null
|
||||||
rooms: {}
|
@time = 0
|
||||||
|
|
||||||
# The unique id of the starting room.
|
# Corresponding room names to room objects.
|
||||||
start: "start"
|
@rooms = {}
|
||||||
|
|
||||||
# Regular expression to catch every link action.
|
# The unique id of the starting room.
|
||||||
# Salet's default is a general URL-safe expression.
|
@start = "start"
|
||||||
linkRe: /^([0-9A-Za-z_-]+|\.)(\/([0-9A-Za-z_-]+))?$/
|
|
||||||
|
|
||||||
character: new Character
|
# Regular expression to catch every link action.
|
||||||
|
# Salet's default is a general URL-safe expression.
|
||||||
|
@linkRe = /^([0-9A-Za-z_-]+|\.)(\/([0-9A-Za-z_-]+))?$/
|
||||||
|
|
||||||
###
|
###
|
||||||
This function is called at the start of the game. It is
|
This function is called at the start of the game. It is
|
||||||
normally overridden to provide initial character creation
|
normally overridden to provide initial character creation
|
||||||
(setting initial quality values, setting the
|
(setting initial quality values, setting the
|
||||||
character-text. This is optional, however, as set-up
|
character-text. This is optional, however, as set-up
|
||||||
processing could also be done by the first situation's
|
processing could also be done by the first situation's
|
||||||
enter function.
|
enter function.
|
||||||
###
|
###
|
||||||
init: () ->
|
@init = () ->
|
||||||
|
|
||||||
###
|
###
|
||||||
This function is called before entering any new
|
This function is called before entering any new
|
||||||
situation. It is called before the corresponding situation
|
situation. It is called before the corresponding situation
|
||||||
has its `enter` method called.
|
has its `enter` method called.
|
||||||
###
|
###
|
||||||
enter: (oldSituationId, newSituationId) ->
|
@enter = (oldSituationId, newSituationId) ->
|
||||||
|
|
||||||
###
|
###
|
||||||
Hook for when the situation has already been carried out
|
Hook for when the situation has already been carried out
|
||||||
and printed.
|
and printed.
|
||||||
###
|
###
|
||||||
afterEnter: (oldSituationId, newSituationId) ->
|
@afterEnter = (oldSituationId, newSituationId) ->
|
||||||
|
|
||||||
###
|
###
|
||||||
This function is called before carrying out any action in
|
This function is called before carrying out any action in
|
||||||
any situation. It is called before the corresponding
|
any situation. It is called before the corresponding
|
||||||
situation has its `act` method called.
|
situation has its `act` method called.
|
||||||
|
|
||||||
If the function returns true, then it is indicating that it
|
If the function returns true, then it is indicating that it
|
||||||
has consumed the action, and the action will not be passed
|
has consumed the action, and the action will not be passed
|
||||||
on to the situation. Note that this is the only one of
|
on to the situation. Note that this is the only one of
|
||||||
these global handlers that can consume the event.
|
these global handlers that can consume the event.
|
||||||
###
|
###
|
||||||
beforeAction: (situationId, actionId) ->
|
@beforeAction = (situationId, actionId) ->
|
||||||
|
|
||||||
###
|
###
|
||||||
This function is called after carrying out any action in
|
This function is called after carrying out any action in
|
||||||
any situation. It is called after the corresponding
|
any situation. It is called after the corresponding
|
||||||
situation has its `act` method called.
|
situation has its `act` method called.
|
||||||
###
|
###
|
||||||
afterAction: (situationId, actionId) ->
|
@afterAction = (situationId, actionId) ->
|
||||||
|
|
||||||
###
|
###
|
||||||
This function is called after leaving any situation. It is
|
This function is called after leaving any situation. It is
|
||||||
called after the corresponding situation has its `exit`
|
called after the corresponding situation has its `exit`
|
||||||
method called.
|
method called.
|
||||||
###
|
###
|
||||||
exit: (oldSituationId, newSituationId) ->
|
@exit = (oldSituationId, newSituationId) ->
|
||||||
|
|
||||||
###
|
###
|
||||||
Returns a list of situation ids to choose from, given a set of
|
Returns a list of situation ids to choose from, given a set of
|
||||||
specifications.
|
specifications.
|
||||||
|
|
||||||
This function is a complex and powerful way of compiling
|
This function is a complex and powerful way of compiling
|
||||||
implicit situation choices. You give it a list of situation ids
|
implicit situation choices. You give it a list of situation ids
|
||||||
and situation tags (if a single id or tag is needed just that
|
and situation tags (if a single id or tag is needed just that
|
||||||
string can be given, it doesn't need to be wrapped in a
|
string can be given, it doesn't need to be wrapped in a
|
||||||
list). Tags should be prefixed with a hash # to differentiate
|
list). Tags should be prefixed with a hash # to differentiate
|
||||||
them from situation ids. The function then considers all
|
them from situation ids. The function then considers all
|
||||||
matching situations in descending priority order, calling their
|
matching situations in descending priority order, calling their
|
||||||
canView functions and filtering out any that should not be
|
canView functions and filtering out any that should not be
|
||||||
shown, given the current state. Without additional parameters
|
shown, given the current state. Without additional parameters
|
||||||
the function returns a list of the situation ids at the highest
|
the function returns a list of the situation ids at the highest
|
||||||
level of priority that has any valid results. So, for example,
|
level of priority that has any valid results. So, for example,
|
||||||
if a tag #places matches three situations, one with priority 2,
|
if a tag #places matches three situations, one with priority 2,
|
||||||
and two with priority 3, and all of them can be viewed in the
|
and two with priority 3, and all of them can be viewed in the
|
||||||
current context, then only the two with priority 3 will be
|
current context, then only the two with priority 3 will be
|
||||||
returned. This allows you to have high-priority situations that
|
returned. This allows you to have high-priority situations that
|
||||||
trump any lower situations when they are valid, such as
|
trump any lower situations when they are valid, such as
|
||||||
situations that force the player to go to one destination if
|
situations that force the player to go to one destination if
|
||||||
the player is out of money, for example.
|
the player is out of money, for example.
|
||||||
|
|
||||||
If a maxChoices value is given, then the function will not
|
If a maxChoices value is given, then the function will not
|
||||||
return any more than the given number of results. If there are
|
return any more than the given number of results. If there are
|
||||||
more than this number of results possible, then the highest
|
more than this number of results possible, then the highest
|
||||||
priority resuls will be guaranteed to be returned, but the
|
priority resuls will be guaranteed to be returned, but the
|
||||||
lowest priority group will have to fight it out for the
|
lowest priority group will have to fight it out for the
|
||||||
remaining places. In this case, a random sample is chosen,
|
remaining places.
|
||||||
taking into account the frequency of each situation. So a
|
|
||||||
situation with a frequency of 100 will be chosen 100 times more
|
|
||||||
often than a situation with a frequency of 1, if there is one
|
|
||||||
space available. Often these frequencies have to be taken as a
|
|
||||||
guideline, and the actual probabilities will only be
|
|
||||||
approximate. Consider three situations with frequencies of 1,
|
|
||||||
1, 100, competing for two spaces. The 100-frequency situation
|
|
||||||
will be chosen almost every time, but for the other space, one
|
|
||||||
of the 1-frequency situations must be chosen. So the actual
|
|
||||||
probabilities will be roughly 50%, 50%, 100%. When selecting
|
|
||||||
more than one result, frequencies can only be a guide.
|
|
||||||
|
|
||||||
Before this function returns its result, it sorts the
|
Before this function returns its result, it sorts the
|
||||||
situations in increasing order of their displayOrder values.
|
situations in increasing order of their displayOrder values.
|
||||||
###
|
###
|
||||||
getSituationIdChoices: (listOfOrOneIdsOrTags, maxChoices) =>
|
@getSituationIdChoices = (listOfOrOneIdsOrTags, maxChoices) =>
|
||||||
datum = null
|
datum = null
|
||||||
i = 0
|
i = 0
|
||||||
|
|
||||||
# First check if we have a single string for the id or tag.
|
# First check if we have a single string for the id or tag.
|
||||||
if (typeof(listOfOrOneIdsOrTags) == 'string')
|
if (typeof(listOfOrOneIdsOrTags) == 'string')
|
||||||
listOfOrOneIdsOrTags = [listOfOrOneIdsOrTags]
|
listOfOrOneIdsOrTags = [listOfOrOneIdsOrTags]
|
||||||
|
|
||||||
# First we build a list of all candidate ids.
|
# First we build a list of all candidate ids.
|
||||||
allIds = []
|
allIds = []
|
||||||
for tagOrId in listOfOrOneIdsOrTags
|
for tagOrId in listOfOrOneIdsOrTags
|
||||||
if (tagOrId.substr(0, 1) == '#')
|
if (tagOrId.substr(0, 1) == '#')
|
||||||
ids = @getRoomsTagged(tagOrId.substr(1))
|
ids = @getRoomsTagged(tagOrId.substr(1))
|
||||||
for id in ids
|
for id in ids
|
||||||
allIds.push(id)
|
allIds.push(id)
|
||||||
else #it's an id, not a tag
|
else #it's an id, not a tag
|
||||||
allIds.push(tagOrId)
|
allIds.push(tagOrId)
|
||||||
|
|
||||||
#Filter out anything that can't be viewed right now.
|
#Filter out anything that can't be viewed right now.
|
||||||
currentRoom = @getCurrentRoom()
|
currentRoom = @getCurrentRoom()
|
||||||
viewableRoomData = []
|
viewableRoomData = []
|
||||||
for roomId in allIds
|
for roomId in allIds
|
||||||
room = @rooms[roomId]
|
room = @rooms[roomId]
|
||||||
assert(room, "unknown_situation".l({id:roomId}))
|
assert(room, "unknown_situation".l({id:roomId}))
|
||||||
|
|
||||||
if (room.canView.fcall(this, currentRoom))
|
if (room.canView.fcall(this, currentRoom))
|
||||||
viewableRoomData.push({
|
viewableRoomData.push({
|
||||||
priority: room.priority
|
priority: room.priority
|
||||||
id: roomId
|
id: roomId
|
||||||
displayOrder: room.displayOrder
|
displayOrder: room.displayOrder
|
||||||
|
})
|
||||||
|
|
||||||
|
# Then we sort in descending priority order.
|
||||||
|
viewableRoomData.sort((a, b) ->
|
||||||
|
return b.priority - a.priority
|
||||||
|
)
|
||||||
|
|
||||||
|
committed = []
|
||||||
|
|
||||||
|
# if we need to filter out the results
|
||||||
|
if (maxChoices? && viewableRoomData.length > maxChoices)
|
||||||
|
viewableRoomData = viewableRoomData[-maxChoices..]
|
||||||
|
|
||||||
|
for candidateRoom in viewableRoomData
|
||||||
|
committed.push({
|
||||||
|
id: candidateRoom.id
|
||||||
|
displayOrder: candidateRoom.displayOrder
|
||||||
})
|
})
|
||||||
|
|
||||||
# Then we sort in descending priority order.
|
# Now sort in ascending display order.
|
||||||
viewableRoomData.sort((a, b) ->
|
committed.sort((a, b) ->
|
||||||
return b.priority - a.priority
|
return a.displayOrder - b.displayOrder
|
||||||
)
|
)
|
||||||
|
|
||||||
committed = []
|
# And return as a list of ids only.
|
||||||
|
result = []
|
||||||
|
for i in committed
|
||||||
|
result.push(i.id)
|
||||||
|
|
||||||
# if we need to filter out the results
|
return result
|
||||||
if (maxChoices? && viewableRoomData.length > maxChoices)
|
|
||||||
viewableRoomData = viewableRoomData[-maxChoices..]
|
|
||||||
|
|
||||||
for candidateRoom in viewableRoomData
|
# This is the data on the player's progress that gets saved.
|
||||||
committed.push({
|
@progress = {
|
||||||
id: candidateRoom.id
|
# A random seed string, used internally to make random
|
||||||
displayOrder: candidateRoom.displayOrder
|
# sequences predictable.
|
||||||
})
|
seed: null
|
||||||
|
# Keeps track of the links clicked, and when.
|
||||||
|
sequence: [],
|
||||||
|
# The time when the progress was saved.
|
||||||
|
saveTime: null
|
||||||
|
}
|
||||||
|
|
||||||
# Now sort in ascending display order.
|
# The Id of the current room the player is in.
|
||||||
committed.sort((a, b) ->
|
@current = null
|
||||||
return a.displayOrder - b.displayOrder
|
|
||||||
)
|
|
||||||
|
|
||||||
# And return as a list of ids only.
|
# Tracks whether we're in interactive mode or batch mode.
|
||||||
result = []
|
@interactive = true
|
||||||
for i in committed
|
|
||||||
result.push(i.id)
|
|
||||||
|
|
||||||
return result
|
# The system time when the game was initialized.
|
||||||
|
@startTime = null
|
||||||
|
|
||||||
# This is the data on the player's progress that gets saved.
|
# The stack of links, resulting from the last action, still be to resolved.
|
||||||
progress: {
|
@linkStack = null
|
||||||
# A random seed string, used internally to make random
|
|
||||||
# sequences predictable.
|
|
||||||
seed: null
|
|
||||||
# Keeps track of the links clicked, and when.
|
|
||||||
sequence: [],
|
|
||||||
# The time when the progress was saved.
|
|
||||||
saveTime: null
|
|
||||||
}
|
|
||||||
|
|
||||||
# The Id of the current situation the player is in.
|
@getCurrentRoom = () =>
|
||||||
current: null;
|
if (@current)
|
||||||
|
return @rooms[@current]
|
||||||
|
return null
|
||||||
|
|
||||||
# Tracks whether we're in interactive mode or batch mode.
|
# Gets the unique id used to identify saved games.
|
||||||
interactive: true
|
@getSaveId = (slot = "") =>
|
||||||
|
return 'salet_'+@game_id+'_'+@game_version#+'_'+slot
|
||||||
|
|
||||||
# The system time when the game was initialized.
|
# This gets called when a link needs to be followed, regardless
|
||||||
startTime: null
|
# of whether it was user action that initiated it.
|
||||||
|
@processLink = (code) =>
|
||||||
|
# Check if we should do this now, or if processing is already underway.
|
||||||
|
if @linkStack != null
|
||||||
|
@linkStack.push(code)
|
||||||
|
return
|
||||||
|
|
||||||
# The stack of links, resulting from the last action, still be to resolved.
|
@view.mark_all_links_old
|
||||||
linkStack: null
|
|
||||||
|
|
||||||
getCurrentRoom: () =>
|
# We're processing, so make the stack available.
|
||||||
if (@current)
|
@linkStack = []
|
||||||
return @rooms[@current]
|
|
||||||
return null
|
|
||||||
|
|
||||||
# Gets the unique id used to identify saved games.
|
# Handle each link in turn.
|
||||||
getSaveId: (slot = "") =>
|
@processOneLink(code);
|
||||||
return 'salet_'+@game_id+'_'+@game_version#+'_'+slot
|
while (@linkStack.length > 0)
|
||||||
|
code = @linkStack.shift()
|
||||||
|
@processOneLink(code)
|
||||||
|
|
||||||
# This gets called when a link needs to be followed, regardless
|
# We're done, so remove the stack to prevent future pushes.
|
||||||
# of whether it was user action that initiated it.
|
@linkStack = null;
|
||||||
processLink: (code) =>
|
|
||||||
# Check if we should do this now, or if processing is already underway.
|
|
||||||
if @linkStack != null
|
|
||||||
@linkStack.push(code)
|
|
||||||
return
|
|
||||||
|
|
||||||
@view.mark_all_links_old
|
# Scroll to the top of the new content.
|
||||||
|
@view.endOutputTransaction()
|
||||||
|
|
||||||
# We're processing, so make the stack available.
|
# We're able to save, if we weren't already.
|
||||||
@linkStack = []
|
@view.enableSaving()
|
||||||
|
|
||||||
# Handle each link in turn.
|
@goTo = (roomId) =>
|
||||||
@processOneLink(code);
|
return @processLink(roomId)
|
||||||
while (@linkStack.length > 0)
|
|
||||||
code = @linkStack.shift()
|
|
||||||
@processOneLink(code)
|
|
||||||
|
|
||||||
# We're done, so remove the stack to prevent future pushes.
|
###
|
||||||
@linkStack = null;
|
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
|
||||||
|
code that further calls doLink, and so on. This method processes
|
||||||
|
each one, and processLink manages this.
|
||||||
|
###
|
||||||
|
@processOneLink = (code) =>
|
||||||
|
match = code.match(@linkRe)
|
||||||
|
assert(match, "link_not_valid".l({link:code}))
|
||||||
|
|
||||||
# Scroll to the top of the new content.
|
situation = match[1]
|
||||||
@view.endOutputTransaction()
|
action = match[3]
|
||||||
|
|
||||||
# We're able to save, if we weren't already.
|
# Change the situation
|
||||||
@view.enableSaving()
|
if situation != '.' and situation != @current_room
|
||||||
|
@doTransitionTo(situation)
|
||||||
|
else
|
||||||
|
# We should have an action if we have no situation change.
|
||||||
|
assert(action, "link_no_action".l())
|
||||||
|
|
||||||
goTo: (roomId) =>
|
# Carry out the action
|
||||||
return @processLink(roomId)
|
if (action)
|
||||||
|
room = @getCurrentRoom()
|
||||||
|
if (room and @beforeAction)
|
||||||
|
# Try the global act handler
|
||||||
|
consumed = @beforeAction(room, action)
|
||||||
|
if (consumed != true)
|
||||||
|
room.act(this, action)
|
||||||
|
|
||||||
###
|
if (@afterAction)
|
||||||
This gets called to actually do the work of processing a code.
|
@afterAction(this, room, action)
|
||||||
When one doLink is called (or a link is clicked), this may set call
|
|
||||||
code that further calls doLink, and so on. This method processes
|
|
||||||
each one, and processLink manages this.
|
|
||||||
###
|
|
||||||
processOneLink: (code) =>
|
|
||||||
match = code.match(@linkRe)
|
|
||||||
assert(match, "link_not_valid".l({link:code}))
|
|
||||||
|
|
||||||
situation = match[1]
|
# This gets called when the user clicks a link to carry out an action.
|
||||||
action = match[3]
|
@processClick = (code) =>
|
||||||
|
now = (new Date()).getTime() * 0.001
|
||||||
|
@time = now - @startTime
|
||||||
|
@progress.sequence.push({link:code, when:@time})
|
||||||
|
@processLink(code)
|
||||||
|
|
||||||
# Change the situation
|
# Transition between rooms.
|
||||||
if situation != '.' and situation != @current_room
|
@doTransitionTo = (newRoomId) =>
|
||||||
@doTransitionTo(situation)
|
oldRoomId = @current
|
||||||
else
|
oldRoom = @getCurrentRoom()
|
||||||
# We should have an action if we have no situation change.
|
newRoom = @rooms[newRoomId]
|
||||||
assert(action, "link_no_action".l())
|
|
||||||
|
|
||||||
# Carry out the action
|
assert(newRoom, "unknown_situation".l({id:newRoomId}))
|
||||||
if (action)
|
|
||||||
room = @getCurrentRoom()
|
|
||||||
if (room and @beforeAction)
|
|
||||||
# Try the global act handler
|
|
||||||
consumed = @beforeAction(room, action)
|
|
||||||
if (consumed != true)
|
|
||||||
room.act(this, action)
|
|
||||||
|
|
||||||
if (@afterAction)
|
# We might not have an old situation if this is the start of the game.
|
||||||
@afterAction(this, room, action)
|
if (oldRoom and @exit)
|
||||||
|
@exit(oldRoomId, newRoomId)
|
||||||
|
|
||||||
# This gets called when the user clicks a link to carry out an action.
|
@current = newRoomId
|
||||||
processClick: (code) =>
|
|
||||||
now = (new Date()).getTime() * 0.001
|
|
||||||
@time = now - @startTime
|
|
||||||
@progress.sequence.push({link:code, when:@time})
|
|
||||||
@processLink(code)
|
|
||||||
|
|
||||||
# Transitions between situations.
|
# Remove links and transient sections.
|
||||||
doTransitionTo: (newRoomId) =>
|
@view.removeTransient(@interactive)
|
||||||
oldRoomId = @current
|
|
||||||
oldRoom = @getCurrentRoom()
|
|
||||||
newRoom = @rooms[newRoomId]
|
|
||||||
|
|
||||||
assert(newRoom, "unknown_situation".l({id:newRoomId}))
|
# Notify the incoming situation.
|
||||||
|
if (@enter)
|
||||||
|
@enter(oldRoomId, newRoomId)
|
||||||
|
newRoom.entering(this, oldRoomId)
|
||||||
|
|
||||||
# We might not have an old situation if this is the start of the game.
|
# additional hook for when the situation text has already been printed
|
||||||
if (oldRoom and @exit)
|
if (@afterEnter)
|
||||||
@exit(oldRoomId, newRoomId)
|
@afterEnter(oldRoomId, newRoomId)
|
||||||
|
|
||||||
@current = newRoomId
|
###
|
||||||
|
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.
|
||||||
|
###
|
||||||
|
@eraseSave = (force = false) =>
|
||||||
|
saveId = @getSaveId() # save slot
|
||||||
|
if (localStorage.getItem(saveId) and (force or confirm("erase_message".l())))
|
||||||
|
localStorage.removeItem(saveId)
|
||||||
|
window.location.reload()
|
||||||
|
|
||||||
# Remove links and transient sections.
|
# Find and return a list of ids for all situations with the given tag.
|
||||||
@view.removeTransient(@interactive)
|
@getRoomsTagged = (tag) =>
|
||||||
|
result = []
|
||||||
|
for id, room of @rooms
|
||||||
|
for i in room.tags
|
||||||
|
if (i == tag)
|
||||||
|
result.push(id)
|
||||||
|
break
|
||||||
|
return result
|
||||||
|
|
||||||
# Notify the incoming situation.
|
# Saves the character and the walking history to local storage.
|
||||||
if (@enter)
|
@saveGame = () =>
|
||||||
@enter(oldRoomId, newRoomId)
|
# Store when we're saving the game, to avoid exploits where a
|
||||||
newRoom.entering(this, oldRoomId)
|
# player loads their file to gain extra time.
|
||||||
|
now = (new Date()).getTime() * 0.001
|
||||||
|
@progress.saveTime = now - @startTime
|
||||||
|
|
||||||
# additional hook for when the situation text has already been printed
|
# Save the game.
|
||||||
if (@afterEnter)
|
window.localStorage.setItem(@getSaveId(), JSON.stringify({
|
||||||
@afterEnter(oldRoomId, newRoomId)
|
progress: @progress,
|
||||||
|
character: @character
|
||||||
|
}))
|
||||||
|
|
||||||
###
|
# Switch the button highlights.
|
||||||
Erases the character in local storage. This is permanent!
|
@view.disableSaving()
|
||||||
To restart the game afterwards, we perform a simple page refresh.
|
@view.enableErasing()
|
||||||
This guarantees authors don't have to care about "tainting" the
|
@view.enableLoading()
|
||||||
game state across save/erase cycles, meaning that character.sandbox
|
|
||||||
no longer has to be the end-all be-all repository of game state.
|
|
||||||
###
|
|
||||||
eraseSave: (force = false) =>
|
|
||||||
saveId = @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.
|
# Loads the game from the given data
|
||||||
getRoomsTagged: (tag) =>
|
@loadGame = (saveFile) =>
|
||||||
result = []
|
@progress = saveFile.progress
|
||||||
for id, room of @rooms
|
@character = saveFile.character
|
||||||
for i in room.tags
|
|
||||||
if (i == tag)
|
|
||||||
result.push(id)
|
|
||||||
break
|
|
||||||
return result
|
|
||||||
|
|
||||||
# Saves the character and the walking history 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.
|
|
||||||
window.localStorage.setItem(@getSaveId(), JSON.stringify({
|
|
||||||
progress: @progress,
|
|
||||||
character: @character
|
|
||||||
}))
|
|
||||||
|
|
||||||
# Switch the button highlights.
|
|
||||||
@view.disableSaving()
|
|
||||||
@view.enableErasing()
|
|
||||||
@view.enableLoading()
|
|
||||||
|
|
||||||
# Loads the game from the given data
|
|
||||||
loadGame: (saveFile) =>
|
|
||||||
@progress = saveFile.progress
|
|
||||||
@character = saveFile.character
|
|
||||||
|
|
||||||
@rnd = new Random(@progress.seed)
|
|
||||||
|
|
||||||
# Don't load the save if it's an autosave at the first room (start).
|
|
||||||
# We don't need to clear the screen.
|
|
||||||
if saveFile.progress? and saveFile.progress.sequence.length > 1
|
|
||||||
@view.clearContent()
|
|
||||||
|
|
||||||
# Now play through the actions so far:
|
|
||||||
if (@init)
|
|
||||||
@init()
|
|
||||||
|
|
||||||
# Run through all the player's history.
|
|
||||||
interactive = false
|
|
||||||
for step in @progress.sequence
|
|
||||||
# The action must be done at the recorded time.
|
|
||||||
@time = step.when
|
|
||||||
@processLink(step.link)
|
|
||||||
interactive = true
|
|
||||||
|
|
||||||
# Reverse engineer the start time.
|
|
||||||
now = new Date().getTime() * 0.001
|
|
||||||
startTime = now - @progress.saveTime
|
|
||||||
|
|
||||||
view: new SaletView
|
|
||||||
|
|
||||||
beginGame: () =>
|
|
||||||
@view.fixClicks()
|
|
||||||
|
|
||||||
# Handle storage.
|
|
||||||
saveFile = false
|
|
||||||
if (@view.hasLocalStorage())
|
|
||||||
saveFile = localStorage.getItem(@getSaveId())
|
|
||||||
|
|
||||||
if (saveFile)
|
|
||||||
try
|
|
||||||
@loadGame(JSON.parse(saveFile))
|
|
||||||
@view.disableSaving()
|
|
||||||
@view.enableErasing()
|
|
||||||
catch err
|
|
||||||
console.log "There was an error loading your save. The save is deleted."
|
|
||||||
console.error err
|
|
||||||
@eraseSave(true)
|
|
||||||
else
|
|
||||||
@progress.seed = new Date().toString()
|
|
||||||
|
|
||||||
character = new Character()
|
|
||||||
@rnd = new Random(@progress.seed)
|
@rnd = new Random(@progress.seed)
|
||||||
@progress.sequence = [{link:@start, when:0}]
|
|
||||||
|
|
||||||
# Start the game
|
# Don't load the save if it's an autosave at the first room (start).
|
||||||
@startTime = new Date().getTime() * 0.001
|
# We don't need to clear the screen.
|
||||||
|
if saveFile.progress? and saveFile.progress.sequence.length > 1
|
||||||
|
@view.clearContent()
|
||||||
|
|
||||||
|
# Now play through the actions so far:
|
||||||
if (@init)
|
if (@init)
|
||||||
@init(character)
|
@init()
|
||||||
|
|
||||||
# Do the first state.
|
# Run through all the player's history.
|
||||||
@doTransitionTo(@start)
|
interactive = false
|
||||||
|
for step in @progress.sequence
|
||||||
|
# The action must be done at the recorded time.
|
||||||
|
@time = step.when
|
||||||
|
@processLink(step.link)
|
||||||
|
interactive = true
|
||||||
|
|
||||||
getRoom: (name) =>
|
# Reverse engineer the start time.
|
||||||
return @rooms[name]
|
now = new Date().getTime() * 0.001
|
||||||
|
startTime = now - @progress.saveTime
|
||||||
|
|
||||||
# Just an alias for getCurrentRoom
|
@view = new SaletView
|
||||||
here: () => @getCurrentRoom()
|
|
||||||
|
|
||||||
isVisited: (name) =>
|
@beginGame = () =>
|
||||||
place = @getRoom(name)
|
@view.fixClicks()
|
||||||
if place
|
|
||||||
return Boolean place.visited
|
|
||||||
return 0
|
|
||||||
|
|
||||||
module.exports = Salet
|
# Handle storage.
|
||||||
|
saveFile = false
|
||||||
|
if (@view.hasLocalStorage())
|
||||||
|
saveFile = localStorage.getItem(@getSaveId())
|
||||||
|
|
||||||
|
if (saveFile)
|
||||||
|
try
|
||||||
|
@loadGame(JSON.parse(saveFile))
|
||||||
|
@view.disableSaving()
|
||||||
|
@view.enableErasing()
|
||||||
|
catch err
|
||||||
|
console.log "There was an error loading your save. The save is deleted."
|
||||||
|
console.error err
|
||||||
|
@eraseSave(true)
|
||||||
|
else
|
||||||
|
@progress.seed = new Date().toString()
|
||||||
|
|
||||||
|
character = new Character()
|
||||||
|
@rnd = new Random(@progress.seed)
|
||||||
|
@progress.sequence = [{link:@start, when:0}]
|
||||||
|
|
||||||
|
# Start the game
|
||||||
|
@startTime = new Date().getTime() * 0.001
|
||||||
|
if (@init)
|
||||||
|
@init(character)
|
||||||
|
|
||||||
|
# Do the first state.
|
||||||
|
@doTransitionTo(@start)
|
||||||
|
|
||||||
|
@getRoom = (name) => @rooms[name]
|
||||||
|
|
||||||
|
# Just an alias for getCurrentRoom
|
||||||
|
@here = () => @getCurrentRoom()
|
||||||
|
|
||||||
|
@isVisited = (name) =>
|
||||||
|
place = @getRoom(name)
|
||||||
|
if place
|
||||||
|
return Boolean place.visited
|
||||||
|
return 0
|
||||||
|
|
||||||
|
for index, value of spec
|
||||||
|
this[index] = value
|
||||||
|
return this
|
||||||
|
|
||||||
|
salet = (spec) ->
|
||||||
|
spec ?= {}
|
||||||
|
retval = new Salet(spec)
|
||||||
|
retval.view.init(retval)
|
||||||
|
return retval
|
||||||
|
|
||||||
|
module.exports = salet
|
||||||
|
|
|
@ -271,6 +271,7 @@ class SaletView
|
||||||
key: way
|
key: way
|
||||||
distance: salet.rooms[way].distance
|
distance: salet.rooms[way].distance
|
||||||
})
|
})
|
||||||
|
document.querySelector(".ways h2").style.display = "block"
|
||||||
else
|
else
|
||||||
document.querySelector(".ways h2").style.display = "none"
|
document.querySelector(".ways h2").style.display = "none"
|
||||||
document.getElementById("ways").innerHTML = content
|
document.getElementById("ways").innerHTML = content
|
||||||
|
|
Loading…
Reference in a new issue