1
0
Fork 0
mirror of https://github.com/Oreolek/raconteur.git synced 2024-06-26 03:30:47 +03:00
This commit is contained in:
Bruno Dias 2015-04-14 01:04:10 -03:00
parent f51ae68f57
commit 103c2cbddf

View file

@ -9,14 +9,36 @@ var undum = require('undum-commonjs'),
which coalesce as a DSL for defining Undum stories.
----------------------------------------------------------------------------*/
/* ---------------------------------------------------------------------------
situation.js
Raconteur's core, defines a new Situation prototype.
*/
/* ---------------------------------------------------------------------------
Helper functions
----------------------------------------------------------------------------*/
/*
Normalises the whitespace on a string.
1. Disregard empty lines
2. Find the indent (leading whitespace) of each line
3. Figure out the bottom indentation level (Ie, smallest indent). An empty
string is a valid "0 indentation"
4. Strip that much indentation out of each line, so that the bottom
level is now 0 indentation.
This is done so that multiline strings in code can be indented along with
(And in fact one level deeper than) the surrounding code, for programmer
convenience, without all of the code being parsed by markdown-it as a giant
<pre> block.
Note that tabs and spaces are both counted as one character, which is too
bad for the guy mixing them.
*/
String.prototype.normaliseTabs = function () {
var lines = this.split('\n');
var indents = lines
@ -39,13 +61,14 @@ String.prototype.normaliseTabs = function () {
/*
Many properties in Raconteur can be either a String, or a function that
takes some objects from the game state (character, system, and the current
situation) and returns a String. Or in Haskell terms:
situation) and returns a String. Or in Haskellese:
String | (CharacterObject -> SystemObject -> SituationString -> String)
fcall() is added to the prototypes of both String and Function to handle
these situations. When called on a Function, it's an alias for
Function#call(); when called on a String, it only returns the string itself,
discarding any input.
fcall() (by analogy with fmap) is added to the prototypes of both String and
Function to handle these situations. When called on a Function, it's an
alias for Function#call(); when called on a String, it only returns the
string itself, discarding any input.
*/
Function.prototype.fcall = Function.prototype.call;
@ -63,6 +86,8 @@ var markdown = new md({
/*
Ensures a string is a HTML string, by wrapping it in span tags.
FIXME: This is not necessarily the best approach.
*/
String.prototype.spanWrap = function () {
@ -71,6 +96,8 @@ String.prototype.spanWrap = function () {
/*
Adds the "fade" class to a htmlString.
FIXME: Currently this is an undocumented feature.
*/
String.prototype.fade = function () {
@ -81,55 +108,14 @@ String.prototype.fade = function () {
The prototype RaconteurSituation is the basic spec for situations
created with Raconteur. It should be able to handle any use case for Undum.
Properties:
(In addition to properties inherited from undum.Situation)
actions :: {key: (character, system, from) -> null}
An object containing definitions for actions, which are called when an
action without a special marker (writer, inserter, replacer) is called
when the situation is current, usually by clicking an action link.
after :: (character, system, from) -> null
A function that is called right after printing the content of the
situation. Useful for housekeeping tasks (Such as changing character
stats) or implementing custom behaviour in general.
before :: (character, system, from) -> null
Similar to after, but called first
choices :: [String]
A list of situation names and/or tags that can be listed as choices for
this situation. That list will be further filtered by CanView and
CanChoose.
content :: markdownString | (character, system, from) -> markdownString
The main content of the situation, printed when the situation is entered.
visited :: Number
Defaults to 0. Incremented every time the situation is entered.
writers :: {key: markdownString | (character, system, from) -> markdownString}
An object containing definitions for special actions called by inserter,
writer, and replacer links. Note that the content of writer links will be
interpreted as a regular markdownString, while the content of replacer
and inserter links, on the assumption that it's meant to be written into
an existing paragraph, will be interpreted as a inline markdown.
This prototype is fairly complex; see the API documentation.
*/
var RaconteurSituation = function (spec) {
undum.Situation.call(this, spec);
// Add all properties of the spec to the object, indiscriminately.
// Add all own properties of the spec to the object, indiscriminately.
Object.keys(spec).forEach( key => {
if (this[key] === undefined) {
this[key] = spec[key];
@ -185,53 +171,43 @@ RaconteurSituation.prototype.enter = function (character, system, f) {
RaconteurSituation.prototype.act = function (character, system, action) {
var actionClass,
self = this;
that = this;
var responses = {
writer: function (ref) {
var beforeOpts = undefined;
if (self.writers[ref] === undefined) {
throw new Error("Tried to call undefined writer:" + ref);
}
let content = that.writers[ref].fcall(that, character, system, action);
let output = markdown.render(content).fade();
if ($('.options').length) {
system.writeBefore(
markdown.render(
self.writers[ref].fcall(self, character, system, action))
.fade(), '.options');
// Write before the options list if one is currently shown.
system.writeBefore(output, '.options');
} else {
system.write(
markdown.render(
self.writers[ref].fcall(self, character, system, action)
).fade());
system.write(output);
}
},
replacer: function (ref) {
if (self.writers[ref] === undefined) {
throw new Error("Tried to call undefined replacer:" + ref);
}
system.replaceWith(
markdown.renderInline(
self.writers[ref].fcall(self, character, system, action)
).spanWrap().fade(), `#${ref}`);
let content = that.writers[ref].fcall(that, character, system, action);
let output = markdown.renderInline(content).fade();
system.replaceWith(output, `#${ref}`);
},
inserter: function (ref) {
if (self.writers[ref] === undefined) {
throw new Error("Tried to call undefined inserter:" + ref);
}
system.writeInto(
markdown.renderInline(
self.writers[ref].fcall(self, character, system, action)
).spanWrap().fade(), `#${ref}`);
let content = that.writers[ref].fcall(that, character, system, action);
let output = markdown.renderInline(content).fade();
system.writeInto(output, `#${ref}`);
}
};
if (actionClass = action.match(/^_(\w+)_(.+)$/)) {
responses[actionClass[1]](actionClass[2]);
} else if (self.actions.hasOwnProperty(action)) {
self.actions[action].call(self, character, system, action);
// Matched a special action class
let [responder, ref] = [actionClass[1], actionClass[2]]
if(!that.writers.hasOwnProperty(actionClass[2])) {
throw new Error(`Tried to call undefined writer: ${action}`);
}
responses[responder](ref);
} else if (that.actions.hasOwnProperty(action)) {
that.actions[action].call(that, character, system, action);
} else {
throw new Err(`Action "${action}" attempted with no corresponding` +
'action in current situation.');
throw new Error(`Tried to call undefined action: ${action}`);
}
};
@ -241,6 +217,7 @@ module.exports = function (name, spec) {
return (undum.game.situations[name] = new RaconteurSituation(spec));
};
/* Hack. Needed to ensure a single global "Undum" object. FIXME. */
module.exports.exportUndum = function () {
if (!global.undum) {
global.undum = undum;