# I confess that this world model heavily borrows from INSTEAD engine. - A.Y.
obj = require('./obj.coffee')
markdown = require('./markdown.coffee')
cycle = require('./cycle.coffee')
Random = require('./random.js')
languages = require('./localize.coffee')
# Feature detection
hasLocalStorage = () ->
hasStorage = false
try {
hasStorage = ('localStorage' in window) &&
window.localStorage !== null &&
window.localStorage !== undefined;
}
catch (err) {
hasStorage = false
}
return hasStorage
# Assertion
assert = console.assert
way_to = (content, ref) ->
return "#{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
cls = (system) ->
system.clearContent()
system.clearContent("#intro")
update_ways = (ways, name) ->
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].title.fcall(this, 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")
picture_tag = (picture) ->
extension = picture.substr((~-picture.lastIndexOf(".") >>> 0) + 2)
if (extension == "webm")
return """
"""
return ""
class SaletRoom extends undum.Situation
constructor: (spec) ->
undum.Situation.call(this, spec)
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
frequency: 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: (character, 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: (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 and f?
cls(system)
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
current_situation = ""
if not @extendSection
classes = if @classes then ' ' + @classes.join(' ') else ''
situation = document.getElementById('current-situation')
if situation?
situation.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.
current_situation = "
tags are usually bad for replacers system.replaceWith(output, '#'+ref) inserter: (ref) -> content = that.writers[ref].fcall(that, character, system, action) output = markdown(content) system.writeInto(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, character, 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: () => if not @name? console.error("Situation has no name") return this undum.game.situations[@name] = this return this writers: cyclewriter: (character) -> cycle(this.cycle, this.name, character) room = (name, spec) -> spec ?= {} spec.name = name retval = new SaletRoom(spec) retval.register() return retval module.exports = room