diff --git a/game/story.coffee b/game/story.coffee
index 4e74486..8acaa61 100644
--- a/game/story.coffee
+++ b/game/story.coffee
@@ -8,10 +8,11 @@ room "world", salet,
You're in a large room carved inside a giant milky rock mountain.
The floor and walls are littered with signs and signatures of the previous visitors.
"""
- objects:
- well: obj "well",
+ objects: [
+ obj "well",
dsc: "A steep narrow {{well}} proceeds upward."
act: "There is only one passage out. See the „Other rooms“ block popped up? Click it."
+ ]
room "plaza", salet,
title: (from) ->
@@ -29,8 +30,8 @@ room "plaza", salet,
"""
else
"You quickly find the central plaza."
- objects:
- policeman: obj "policeman",
+ objects: [
+ obj "policeman",
dsc: "There is a policeman nearby. You could ask him {{for directions.}}"
act: (salet) ->
if salet.character.has_mark?
@@ -40,9 +41,10 @@ room "plaza", salet,
"""
“Here, let me mark it on your map.”
"""
- people: obj "people",
+ obj "people",
dsc: "There are {{people shouting}} nearby."
act: 'Just some weirdos shouting "Viva la Cthulhu!". Typical.'
+ ]
room "shop", salet,
title: "The Shop"
@@ -61,13 +63,15 @@ room "lair", salet,
dsc: """
The Lair of Yog-Sothoth is a very *n'gai* cave, full of *buggs-shoggogs* and *n'ghaa ng'aa*.
"""
- objects:
- bugg: obj "bugg",
+ ways: ["shop"]
+ objects: [
+ obj "bugg",
dsc: "You see a particularly beautiful slimy {{bugg.}}"
takeable: false
act: (salet) =>
- salet.here().drop(@name)
+ salet.rooms[salet.current].drop(@name)
return "You eat the bugg mass. Delicious and raw. Perhaps it's a good lair to live in."
+ ]
dialogue "Yes", salet, "merchant", "merchant", """
Yes.
@@ -81,25 +85,25 @@ room "shop-inside", salet,
dsc: """
The insides are painted pastel white, honouring The Great Milk Spill of 1985.
"""
- objects:
+ objects: [
merchant: obj "merchant",
dsc: "A {{merchant}} eyes you warily."
takeable: false
act: (system) =>
salet.processClick("merchdialogue")
return ""
+ ]
###
I want to be able to do this but I can't because I'm lost in all the `this` and @objects and `new`.
The chain of calls is very weird and this puts an object IN EVERY ROOM for God's sake.
I need someone smarter than me to fix this.
-
-lamp = obj "lamp",
- dsc: "You see a {{lamp.}}"
- takeable: true
-lamp.put("shop-inside")
###
+lamp = obj "lamp", salet,
+ takeable: true
+lamp.put(salet, "shop-inside")
+
room "merchdialogue", salet,
choices: "#merchant",
dsc: """
diff --git a/lib/obj.coffee b/lib/obj.coffee
index 5443135..872da0a 100644
--- a/lib/obj.coffee
+++ b/lib/obj.coffee
@@ -20,32 +20,34 @@ class SaletObj
unless spec.name?
console.error("Trying to create an object with no name")
return null
+
+ @level = 0 # if > 0 it's hidden
+ @order = 0 # you can use this to sort the descriptions
+ @look = (system, f) =>
+ if @dsc and @dsc != ""
+ text = markdown(@dsc.fcall(this, system, f).toString())
+ text = system.view.wrapLevel(text, @level)
+ # replace braces {{}} with link to _act_
+ return parsedsc(text, @name)
+ @takeable = false
+ @take = (system) => "You take the #{@name}." # taking to inventory
+ @act = (system) => "You don't find anything extraordinary about the #{@name}." # object action
+ @dsc = (system) => "You see a {{#{@name}}} here." # object description
+ @inv = (system) => "It's a {{#{@name}.}}" # inventory description
+ @location = ""
+ @put = (salet, location) =>
+ @level = 0 # this is scenery
+ if salet.rooms[location]?
+ @location = location
+ salet.rooms[location].take(this)
+ @delete = (salet, location = false) =>
+ if location == false
+ location = @location
+ salet.rooms[location].drop(this)
+
for key, value of spec
this[key] = value
- level: 0 # if > 0 it's hidden
- order: 0 # you can use this to sort the descriptions
- look: (system, f) =>
- if @dsc and @dsc != ""
- text = markdown(@dsc.fcall(this, system, f).toString())
- text = system.view.wrapLevel(text, @level)
- # replace braces {{}} with link to _act_
- return parsedsc(text, @name)
- takeable: false
- take: (system) => "You take the #{@name}." # taking to inventory
- act: (system) => "You don't find anything extraordinary about the #{@name}." # object action
- dsc: (system) => "You see a {{#{@name}}} here." # object description
- inv: (system) => "It's a {{#{@name}.}}" # inventory description
- location: ""
- put: (location) =>
- @level = 0 # this is scenery
- if salet.rooms[location]?
- salet.rooms[location].take(this)
- @location = location
- delete: (location = false) =>
- if location == false
- location = @location
- salet.rooms[location].drop(this)
-
+
obj = (name, spec) ->
spec ?= {}
spec.name = name
diff --git a/lib/room.coffee b/lib/room.coffee
index 43e75d6..86d3ad1 100644
--- a/lib/room.coffee
+++ b/lib/room.coffee
@@ -19,239 +19,236 @@ Array::remove = (e) -> @[t..t] = [] if (t = @indexOf(e)) > -1
class SaletRoom
constructor: (spec) ->
+ @visited = 0
+ @title = "Room"
+ @objects = {}
+ @canView = true
+ @canChoose = true
+ @priority = 1
+ @displayOrder = 1
+ @tags = []
+ @choices = ""
+ @optionText = "Choice"
+
+ # room illustration image, VN-style. Can be a GIF or WEBM. Can be a function.
+ @pic = false
+ @dsc = false # room description
+ @extendSection = false
+ @distance = Infinity # distance to the destination
+ @clear = true # clear the screen on entering the room?
+ @entering = (system, from) =>
+ ###
+ I call SaletRoom.exit every time the player exits to another room.
+ Unlike @after this gets called after the section is closed.
+ It's a styling difference.
+ ###
+ @exit = (system, to) =>
+ return true
+
+ ###
+ I call SaletRoom.enter every time the player enters this room but before the section is opened.
+ Unlike @before this gets called before the current section is opened.
+ It's a styling difference.
+
+ The upstream Undum version does not allow you to redefine @enter function easily but allows custom @exit one.
+ It was renamed as @entering to achieve API consistency.
+ ###
+ @enter = (system, from) =>
+ return true
+
+ ###
+ Salet's Undum version calls Situation.entering every time a situation is entered, and
+ passes it three arguments; The character object, the system object,
+ and a string referencing the previous situation, or null if there is
+ none (ie, for the starting situation).
+
+ My version of `enter` splits the location description from the effects.
+ Also if f == this.name (we're in the same location) the `before` and `after` callbacks are ignored.
+ ###
+ @entering = (system, f) =>
+ if @clear and f?
+ system.view.clearContent()
+
+ if f != @name and f?
+ @visited++
+ if system.rooms[f].exit?
+ system.rooms[f].exit system, @name
+
+ if @enter
+ @enter system, f
+
+ room_content = ""
+ if not @extendSection
+ classes = if @classes then ' ' + @classes.join(' ') else ''
+ room = document.getElementById('current-room')
+ if room?
+ room.removeAttribute('id')
+ # Javascript DOM manipulation functions like jQuery's append() or document.createElement
+ # don't work like a typical printLn - they create *DOM nodes*.
+ # You can't leave an unclosed tag just like that. So we have to buffer the output.
+ room_content = ""
+
+ if f != @name and @before?
+ room_content += markdown(@before.fcall(this, system, f))
+
+ room_content += @look system, f
+
+ if f != @name and @after?
+ room_content += markdown(@after.fcall(this, system, f))
+
+ if not @extendSection
+ room_content += ""
+
+ system.view.write(room_content)
+
+ if @choices
+ system.view.writeChoices(system, system.getSituationIdChoices(@choices, @maxChoices))
+
+ if system.autosave
+ system.saveGame()
+
+ ###
+ An internal function to get the room's description and the descriptions of
+ every object in this room.
+ ###
+ @look = (system, f) =>
+ system.view.updateWays(system, @ways, @name)
+ retval = ""
+
+ if @pic
+ retval += '
'+system.view.pictureTag(@pic.fcall(this, system, f))+'
'
+
+ # Print the room description
+ if @dsc and @dsc != ""
+ dsc = @dsc.fcall(this, system, f).toString()
+ retval += markdown(dsc)
+
+ objDescriptions = []
+ for thing in @objects
+ if thing.name and typeof(thing.look) == "function" and thing.level == 0 and thing.look(system, f)
+ objDescriptions.push ({
+ order: thing.order,
+ content: thing.look(system, f)
+ })
+
+ objDescriptions.sort((a, b) ->
+ return a.order - b.order
+ )
+
+ for description in objDescriptions
+ retval += description.content
+
+ return retval
+
+ ###
+ Puts an object in this room.
+ ###
+ @take = (thing) =>
+ @objects[thing.name] = thing
+
+ @drop = (name) =>
+ for thing in @objects
+ if thing.name == name
+ index = @objects.indexOf(thing)
+ @objects.splice(index, 1)
+
+ ###
+ Object action. A function or a string which comes when you click on the object link.
+ You could interpret this as an EXAMINE verb or USE one, it's your call.
+ ###
+ @act = (system, action) =>
+ if (link = action.match(/^_(act|cycle)_(.+)$/)) #object action
+ for thing in @objects
+ if thing.name == link[2]
+ if link[1] == "act"
+ # If it's takeable, the player can take this object.
+ # If not, we check the "act" function.
+ if thing.takeable
+ system.character.inventory.push thing
+ @drop name
+ system.view.clearContent()
+ @entering.fcall(this, system, @name)
+ return system.view.write(thing.take.fcall(thing, system).toString())
+ if thing.act
+ system.view.changeLevel(thing.level)
+ return system.view.write(
+ system.view.wrapLevel(
+ thing.act.fcall(thing, system).toString(),
+ thing.level
+ )
+ )
+ # the loop is done but no return came - match not found
+ console.error("Could not find #{link[2]} in current room.")
+
+ # we're done with objects, now check the regular actions
+ actionClass = action.match(/^_(\w+)_(.+)$/)
+ that = this
+
+ responses = {
+ writer: (ref) ->
+ content = that.writers[ref].fcall(that, system, action)
+ output = markdown(content)
+ system.view.write(output)
+ replacer: (ref) ->
+ content = that.writers[ref].fcall(that, system, action)
+ system.view.replace(content, '#'+ref)
+ inserter: (ref) ->
+ content = that.writers[ref].fcall(that, system, action)
+ output = markdown(content)
+ system.view.write(output, '#'+ref)
+ }
+
+ if (actionClass)
+ # Matched a special action class
+ [responder, ref] = [actionClass[1], actionClass[2]]
+
+ if(!@writers.hasOwnProperty(actionClass[2]))
+ throw new Error("Tried to call undefined writer: #{action}");
+ responses[responder](ref);
+ else if (@actions.hasOwnProperty(action))
+ @actions[action].call(this, system, action);
+ else
+ throw new Error("Tried to call undefined action: #{action}");
+
+ # Marks every room in the game with distance to this room
+ @destination = () =>
+ @distance = 0
+
+ candidates = [this]
+ while candidates.length > 0
+ current_room = candidates.shift()
+ if current_room.ways
+ for node in current_room.ways
+ if node.distance == Infinity
+ node.distance = current_room.distance + 1
+ candidates.push(node)
+
+ @register = (salet) =>
+ if not @name?
+ console.error("Situation has no name")
+ return this
+ salet.rooms[@name] = this
+ return this
+
+ @writers = {
+ cyclewriter: (salet) =>
+ responses = @cycle
+ if typeof responses == "function"
+ responses = responses()
+ cycleIndex = window.localStorage.getItem("cycleIndex")
+ cycleIndex ?= 0
+ response = responses[cycleIndex]
+ cycleIndex++
+ if cycleIndex == responses.length
+ cycleIndex = 0
+ window.localStorage.setItem("cycleIndex", cycleIndex)
+ return salet.view.cycleLink(response)
+ }
+
for index, value of spec
this[index] = value
return this
- visited: 0
- title: "Room"
- objects: {}
-
- # room illustration image, VN-style. Can be a GIF or WEBM. Can be a function.
- pic: false
-
- canView: true
- canChoose: true
- priority: 1
- displayOrder: 1
- tags: []
- choices: ""
- optionText: "Choice"
-
- dsc: false # room description
- extendSection: false
- distance: Infinity # distance to the destination
- clear: true # clear the screen on entering the room?
-
- entering: (system, from) =>
-
- ###
- I call SaletRoom.exit every time the player exits to another room.
- Unlike @after this gets called after the section is closed.
- It's a styling difference.
- ###
- exit: (system, to) =>
- return true
-
- ###
- I call SaletRoom.enter every time the player enters this room but before the section is opened.
- Unlike @before this gets called before the current section is opened.
- It's a styling difference.
-
- The upstream Undum version does not allow you to redefine @enter function easily but allows custom @exit one.
- It was renamed as @entering to achieve API consistency.
- ###
- enter: (system, from) =>
- return true
-
- ###
- Salet's Undum version calls Situation.entering every time a situation is entered, and
- passes it three arguments; The character object, the system object,
- and a string referencing the previous situation, or null if there is
- none (ie, for the starting situation).
-
- My version of `enter` splits the location description from the effects.
- Also if f == this.name (we're in the same location) the `before` and `after` callbacks are ignored.
- ###
- entering: (system, f) =>
- if @clear and f?
- system.view.clearContent()
-
- if f != @name and f?
- @visited++
- if system.rooms[f].exit?
- system.rooms[f].exit system, @name
-
- if @enter
- @enter system, f
-
- room_content = ""
- if not @extendSection
- classes = if @classes then ' ' + @classes.join(' ') else ''
- room = document.getElementById('current-room')
- if room?
- room.removeAttribute('id')
- # Javascript DOM manipulation functions like jQuery's append() or document.createElement
- # don't work like a typical printLn - they create *DOM nodes*.
- # You can't leave an unclosed tag just like that. So we have to buffer the output.
- room_content = ""
-
- if f != @name and @before?
- room_content += markdown(@before.fcall(this, system, f))
-
- room_content += @look system, f
-
- if f != @name and @after?
- room_content += markdown(@after.fcall(this, system, f))
-
- if not @extendSection
- room_content += ""
-
- system.view.write(room_content)
-
- if @choices
- system.view.writeChoices(system, system.getSituationIdChoices(@choices, @maxChoices))
-
- if system.autosave
- system.saveGame()
-
- ###
- An internal function to get the room's description and the descriptions of
- every object in this room.
- ###
- look: (system, f) =>
- system.view.updateWays(system, @ways, @name)
- retval = ""
-
- if @pic
- retval += ''+system.view.pictureTag(@pic.fcall(this, system, f))+'
'
-
- # Print the room description
- if @dsc and @dsc != ""
- dsc = @dsc.fcall(this, system, f).toString()
- retval += markdown(dsc)
-
- objDescriptions = []
- for thing in @objects
- console.log thing
- if thing.name and typeof(thing.look) == "function" and thing.level == 0 and thing.look(system, f)
- objDescriptions.push ({
- order: thing.order,
- content: thing.look(system, f)
- })
-
- objDescriptions.sort((a, b) ->
- return a.order - b.order
- )
-
- for description in objDescriptions
- retval += description.content
-
- return retval
-
- ###
- Puts an object in this room.
- ###
- take: (thing) =>
- @objects[thing.name] = thing
- # BUG: for some really weird reason if the call is made in init function or
- # during the initialization, this ALSO puts the thing in the start room.
- salet.rooms["start"].objects = {}
-
- drop: (name) =>
- delete @objects[name]
-
- ###
- Object action. A function or a string which comes when you click on the object link.
- You could interpret this as an EXAMINE verb or USE one, it's your call.
- ###
- act: (system, action) =>
- if (link = action.match(/^_(act|cycle)_(.+)$/)) #object action
- for thing in @objects
- if thing.name == link[2]
- if link[1] == "act"
- # If it's takeable, the player can take this object.
- # If not, we check the "act" function.
- if thing.takeable
- system.character.inventory.push thing
- @drop name
- system.view.clearContent()
- @entering.fcall(this, system, @name)
- return system.view.write(thing.take.fcall(thing, system).toString())
- if thing.act
- system.view.changeLevel(thing.level)
- return system.view.write(
- system.view.wrapLevel(
- thing.act.fcall(thing, system).toString(),
- thing.level
- )
- )
- # the loop is done but no return came - match not found
- console.error("Could not find #{link[2]} in current room.")
-
- # we're done with objects, now check the regular actions
- actionClass = action.match(/^_(\w+)_(.+)$/)
- that = this
-
- responses = {
- writer: (ref) ->
- content = that.writers[ref].fcall(that, system, action)
- output = markdown(content)
- system.view.write(output)
- replacer: (ref) ->
- content = that.writers[ref].fcall(that, system, action)
- system.view.replace(content, '#'+ref)
- inserter: (ref) ->
- content = that.writers[ref].fcall(that, system, action)
- output = markdown(content)
- system.view.write(output, '#'+ref)
- }
-
- if (actionClass)
- # Matched a special action class
- [responder, ref] = [actionClass[1], actionClass[2]]
-
- if(!@writers.hasOwnProperty(actionClass[2]))
- throw new Error("Tried to call undefined writer: #{action}");
- responses[responder](ref);
- else if (@actions.hasOwnProperty(action))
- @actions[action].call(this, system, action);
- else
- throw new Error("Tried to call undefined action: #{action}");
-
- # Marks every room in the game with distance to this room
- destination: () =>
- @distance = 0
-
- candidates = [this]
- while candidates.length > 0
- current_room = candidates.shift()
- if current_room.ways
- for node in current_room.ways
- if node.distance == Infinity
- node.distance = current_room.distance + 1
- candidates.push(node)
-
- register: (salet) =>
- if not @name?
- console.error("Situation has no name")
- return this
- salet.rooms[@name] = this
- return this
-
- writers:
- cyclewriter: (salet) =>
- responses = @cycle
- if typeof responses == "function"
- responses = responses()
- cycleIndex = window.localStorage.getItem("cycleIndex")
- cycleIndex ?= 0
- response = responses[cycleIndex]
- cycleIndex++
- if cycleIndex == responses.length
- cycleIndex = 0
- window.localStorage.setItem("cycleIndex", cycleIndex)
- return salet.view.cycleLink(response)
-
+
room = (name, salet, spec) ->
spec ?= {}
spec.name = name
diff --git a/lib/salet.coffee b/lib/salet.coffee
index d3fe645..b405533 100644
--- a/lib/salet.coffee
+++ b/lib/salet.coffee
@@ -23,6 +23,8 @@ class Character
###
This is the control structure, it has minimal amount of data and
this data is volatile anyway (as in, it won't get saved).
+
+There is only one instance of this class.
###
class Salet
# REDEFINE THIS IN YOUR GAME
diff --git a/lib/view.coffee b/lib/view.coffee
index 952995d..b24dcf2 100644
--- a/lib/view.coffee
+++ b/lib/view.coffee
@@ -9,6 +9,8 @@ You don't need to call this module from the game directly.
The abstraction goal here is to provide the author with a freedom to style his
game as he wants to. The save and erase buttons are not necessary buttons,
but they could be something else entirely. (That's why IDs are hardcoded.)
+
+There is only one instance of this class, and it's stored as `salet.view`.
###
assert = (msg, assertion) -> console.assert assertion, msg