1
0
Fork 0
mirror of https://gitlab.com/Oreolek/salet.git synced 2024-07-07 09:14:24 +03:00
salet/lib/room.coffee
Alexander Yakovlev 828962b1ff Documentation and API
Made enter() a redefinable function.
Updated Gulpfile to watch the libs.
Fixed a couple of bugs in Raconteur CoffeeScript port.
2016-01-15 15:10:12 +07:00

200 lines
6.5 KiB
CoffeeScript

# I confess that this world model heavily borrows from INSTEAD engine. - A.Y.
undum = require('./undum.js')
RaconteurSituation = require('./situation.coffee')
obj = require('./obj.coffee')
markdown = require('./markdown.coffee')
way_to = (content, ref) ->
return "<a href='#{ref}' class='way' id='waylink-#{ref}'>#{content}</a>"
# jQuery was confused by this point where's the context so I did it vanilla-way
print = (content) ->
if typeof content == "function"
content = content()
block = document.getElementById("current-situation")
if block
block.innerHTML = block.innerHTML + markdown(content)
else #the game is not initialized yet. This is dangerous and will not augment any links.
block = document.getElementById("content")
block.innerHTML = markdown(content)
Array::remove = (e) -> @[t..t] = [] if (t = @indexOf(e)) > -1
addClass = (element, className) ->
if (element.classList)
element.classList.add(className)
else
element.className += ' ' + className
# Function to return the current room.
# Works because our `enter()` function sets the `data-situation` attribute.
here = () ->
return undum.game.situations[document.getElementById("current-situation").getAttribute("data-situation")]
update_ways = (ways) ->
content = ""
distances = []
if ways
document.querySelector(".ways h2").style.display = "block"
for way in ways
if undum.game.situations[way]?
title = undum.game.situations[way].name
content += way_to(title, way)
distances.push({
key: way
distance: undum.game.situations[way].distance
})
else
document.querySelector(".ways h2").style.display = "none"
document.getElementById("ways").innerHTML = content
min = Infinity
min_key = []
for node in distances
if node.distance < min
min = node.distance
min_key = [node.key]
if node.distance == min
min_key.push(node.key)
if min < Infinity
for node in min_key
addClass(document.getElementById("waylink-#{node}"), "destination")
class SaletRoom extends RaconteurSituation
constructor: (spec) ->
RaconteurSituation.call(this, spec)
if spec.objects?
@objects = spec.objects
if spec.exit?
@exit = spec.exit
if spec.enter?
@enter = spec.enter
if spec.clear?
@clear = spec.clear
if spec.writers?
@writers = spec.writers
return this
objects: []
distance: Infinity # distance to the destination
clear: true # clear the screen on entering the room?
###
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: (character, 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: (character, 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: (character, system, f) ->
if @clear
system.clearContent()
if f != @name and f?
@visited++
if undum.game.situations[f].exit?
undum.game.situations[f].exit(character, system, @name)
if @enter
@enter character, system, f
if not @extendSection
classes = if @classes then ' ' + @classes.join(' ') else ''
situation = document.getElementById('current-situation')
if situation?
situation.setAttribute('id', undefined)
system.write("<section id='current-situation' data-situation='#{@name}' class='situation-#{@name}#{classes}'>")
if f != @name and @before?
content = @before.fcall(this, character, system, f)
if content
print(content)
if @look
@look character, system, f
if f != @name and @after?
content = @after.fcall(this, character, system, f)
if content
print(content)
if not @extendSection
system.write("</section>")
if @choices
system.writeChoices(system.getSituationIdChoices(@choices, @minChoices, @maxChoices))
look: (character, system, f) ->
update_ways(@ways)
# Print the room description
if @content
system.write(markdown(@content.fcall(this, character, system, f)))
if @objects? then for thing in @objects
system.write thing.look()
###
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: (character, system, action) ->
if (link = action.match(/^_act_(.+)$/)) #object action
for thing in @objects
if thing.name == link[1]
# We check the "take" function. If it exists, the player can take this object.
# If not, we check the "act" function.
if thing.take
@objects.remove(thing)
character.sandbox.inventory.push thing
@enter(character, system, @name)
return print(thing.take.fcall(thing, character, system))
if thing.act
return print(thing.act.fcall(thing, character, system))
# the loop is done but no return came - match not found
console.error("Could not find #{link[1]} in current room.")
# default Raconteur action
return RaconteurSituation.prototype.act.call(this, character, system, 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)
room = (name, spec) ->
if spec
spec.name = name
retval = new SaletRoom(spec)
return retval.register()
module.exports = room