1
0
Fork 0
mirror of https://gitlab.com/Oreolek/salet.git synced 2024-07-02 06:45:06 +03:00

Fixed the prototype variables. Resolves #5.

This commit is contained in:
Alexander Yakovlev 2016-02-08 20:52:12 +07:00
parent 07b994cc7f
commit 43c66af5a6
5 changed files with 275 additions and 268 deletions

View file

@ -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: """

View file

@ -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

View file

@ -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 = "<section id='current-room' data-room='#{@name}' class='room-#{@name}#{classes}'>"
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 += "</section>"
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 += '<div class="pic">'+system.view.pictureTag(@pic.fcall(this, system, f))+'</div>'
# 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 = "<section id='current-room' data-room='#{@name}' class='room-#{@name}#{classes}'>"
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 += "</section>"
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 += '<div class="pic">'+system.view.pictureTag(@pic.fcall(this, system, f))+'</div>'
# 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

View file

@ -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

View file

@ -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