0
0
Fork 0
mirror of https://gitlab.com/Oreolek/salet-module.git synced 2024-06-26 03:50:49 +03:00

NPM module (WIP)

This commit is contained in:
Alexander Yakovlev 2016-09-09 17:18:53 +07:00
parent bda0b84260
commit bcf84bf95b
21 changed files with 41 additions and 1105 deletions

4
.gitignore vendored
View file

@ -1,4 +1,2 @@
node_modules
build
dist
dist.zip
lib

31
Cakefile Normal file
View file

@ -0,0 +1,31 @@
fs = require 'fs'
{exec,spawn} = require 'child_process'
util = require 'util'
sources = [
'markdown.coffee'
'view.coffee'
'unit.coffee'
'character.coffee'
'room.coffee'
'localize.coffee'
'salet.coffee'
]
sourcestring = ""
for source in sources
sourcestring += 'src/'+source+' '
task 'watch', 'Watch source files and build changes', ->
watch = spawn "coffee", ['-c', '-w', '--no-header', '-j', 'lib/index.js', sourcestring]
watch.stdout.on 'data', (data) -> console.log data.toString().trim()
task 'build', 'Compile all CoffeeScript files', ->
# prepare lib directory
if not fs.existsSync 'lib'
fs.mkdirSync 'lib'
# run coffee-script compile
exec "coffee -c --no-header -j lib/index.js #{sourcestring}", (err, stdout, stderr) ->
if err
util.log err
process.exit 1 # abort npm packaging
util.log "Compiled CoffeeScript."

View file

@ -1,130 +0,0 @@
watchify = require('watchify')
browserify = require('browserify')
browserSync = require('browser-sync')
gulp = require('gulp')
source = require('vinyl-source-stream')
gutil = require('gulp-util')
coffeify = require('coffeeify')
coffee = require("gulp-coffee")
sass = require('gulp-sass')
uglify = require('gulp-uglify')
buffer = require('vinyl-buffer')
zip = require('gulp-zip')
concat = require('gulp-concat')
reload = browserSync.reload
html = (target) ->
return () ->
return gulp.src(['html/index.html']).pipe(gulp.dest(target))
# Images
img = (target) ->
return () ->
return gulp.src(['img/*.png', 'img/*.jpeg', 'img/*.jpg']).pipe(gulp.dest(target))
# Audio assets
audio = (target) ->
return () ->
return gulp.src(['audio/*.mp3']).pipe(gulp.dest(target))
gulp.task('html', html('./build'))
gulp.task('img', img('./build/img'))
gulp.task('audio', audio('./build/audio'))
gulp.task('sass', () ->
gulp.src('sass/main.scss')
.pipe(sass({outputStyle: 'compressed'}).on('error', sass.logError))
.pipe(gulp.dest('./build/css'))
)
bundler = watchify(browserify({
entries: ["./build/game/main.coffee"]
debug: true
transform: [coffeify]
}))
bundle = () ->
return bundler.bundle()
.on('error', gutil.log.bind(gutil, 'Browserify Error'))
.pipe(source('bundle.js'))
.pipe(gulp.dest('./build/game'))
gulp.task('concatCoffee', () ->
return gulp.src([
'./game/begin.coffee',
'./game/story.coffee',
]).pipe(concat('./main.coffee')).pipe(gulp.dest('./build/game'))
)
gulp.task('coffee', ['concatCoffee'], bundle)
bundler.on('update', bundle)
bundler.on('log', gutil.log)
gulp.task('build', ['html', 'img', 'sass', 'coffee', 'audio'])
gulp.task('serve', ['build'], () ->
browserSync({
server: {
baseDir: 'build'
}
})
sassListener = () ->
reload('./build/css/main.css')
gulp.watch(['./html/*.html'], ['html'])
gulp.watch(['./sass/*.scss'], ['sass'])
gulp.watch(['./img/*.png', './img/*.jpeg', './img/*.jpg'], ['img'])
gulp.watch(['./game/*.coffee'], ['coffee']);
gulp.watch(['./build/css/main.css'], sassListener)
gulp.watch(
['./build/game/bundle.js', './build/img/*', './build/index.html'],
browserSync.reload)
)
gulp.task('html-dist', html('./dist'))
gulp.task('img-dist', img('./dist/img'))
gulp.task('audio-dist', audio('./dist/audio'))
gulp.task('legal-dist', () ->
return gulp.src(['LICENSE.txt'])
.pipe(gulp.dest("./dist"))
)
gulp.task('sass-dist', () ->
return gulp.src('./sass/main.scss')
.pipe(sass({outputStyle: 'compressed'}))
.pipe(gulp.dest('./dist/css'))
)
distBundler = browserify({
debug: false,
entries: ['./build/game/main.coffee'],
transform: ['coffeeify']
})
gulp.task('coffee-dist', ['concatCoffee'], () ->
return distBundler.bundle()
.pipe(source('bundle.js'))
.pipe(buffer())
.pipe(uglify())
.on('error', gutil.log)
.pipe(gulp.dest('./dist/game'))
)
gulp.task('dist', [
'html-dist',
'img-dist',
'sass-dist',
'coffee-dist',
'audio-dist',
'legal-dist'
])
gulp.task('zip', ['dist'], () ->
return gulp.src('dist/**')
.pipe(zip('dist.zip'))
.pipe(gulp.dest('.'))
)

View file

@ -1,62 +0,0 @@
room = require("../../lib/room.coffee")
unit = require('../../lib/unit.coffee')
dialogue = require('../../lib/util/dialogue.coffee')
phrase = require('../../lib/util/phrase.coffee')
oneOf = require('../../lib/oneOf.coffee')
Salet = require('../../lib/salet.coffee')
salet = Salet({
game_id: "your-game-id-here"
game_version: "1.2"
})
$(document).ready(() ->
window.addEventListener('popstate', (event) ->
salet.goBack()
)
salet.beginGame()
)
###
Element helpers. There is no real need to build monsters like a().id("hello")
because you won't use them as is. It does not make sense in context, the
author has Markdown and all utilities to *forget* about the markup.
###
way_to = (content, ref) ->
return "<a href='#{ref}' class='way'>#{content}</a>"
textlink = (content, ref) ->
return "<a href='./_writer_#{ref}' class='once'>#{content}</a>"
actlink = (content, ref) ->
return "<a href='./#{ref}' class='once'>#{content}</a>"
# The first room of the game.
# For accessibility reasons the text is provided in HTML, not here.
room "start", salet,
enter: (salet) ->
salet.character.bought_lamp = false
dsc: """
""",
choices: "#start"
# This is a special inventory room.
# The inventory button is a regular link to this room.
# You may alter these as much as you like or scrap it along with the button.
room "inventory", salet,
canSave: false # saving the game here is forbidden. Aautosaving too.
enter: () ->
$("#inventory").hide()
exit: () ->
$("#inventory").show()
dsc: (salet) ->
if salet.character.inventory.length == 0
text = "You are carrying nothing."
else
text = "You are carrying:\n\n"
for thing in salet.character.inventory
text += "* #{salet.character.listinv(thing.name)}\n"
return text+"\n\n"+"""
<div class="center"><a href="./exit"><button class="btn btn-lg btn-outline-primary">Go back</button></a></div>
"""
actions:
exit: (salet) ->
return salet.goBack()

View file

@ -1,124 +0,0 @@
room "world", salet,
tags: ["start"],
optionText: "Enter the world",
ways: ["plaza"]
dsc: """
### Rhinestone Room
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.
"""
units: [
unit "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) ->
if from == "world"
return "Upwards"
else
return "Town plaza"
cycle: ["quirky", "distinct", "kooky", "crazy", "quaint"]
ways: ["shop"]
before: (system, from) ->
if from == 'world'
"""
You climb up the well and come out to a central plaza of a #{system.view.cycleLink("quaint")} little town.
A plaque nearby says it's the town of *Innsmouth,* wherever that is.
"""
else
"You quickly find the central plaza."
units: [
unit "policeman",
dsc: "There is a policeman nearby. You could ask him {{for directions.}}"
act: (salet) ->
if salet.character.has_mark?
return "You already talked to him, no need to bug the man twice."
salet.character.has_mark ?= true
salet.getRoom("lair").destination()
"""
Here, let me mark it on your map.
"""
unit "people",
dsc: "There are {{people shouting}} nearby."
act: 'Just some weirdos shouting "Viva la Cthulhu!". Typical.'
]
room "shop", salet,
title: "The Shop"
#pic: "http://loremflickr.com/640/300/room,shop"
ways: ["plaza", "shop-inside", "lair"]
dsc: """
Being the only shop in town, this trendy establishment did not need a name.
It's an open question why it had one, especially because its name was "Hung Crossing".
You are standing in front of a picturesque sign. It's cold here.
"""
room "lair", salet,
title: "The Lair"
before: "Finding The Lair is easy. Leaving it is impossible. Your game ends here."
dsc: """
The Lair of Yog-Sothoth is a very *n'gai* cave, full of *buggs-shoggogs* and *n'ghaa ng'aa*.
"""
ways: ["shop"]
units: [
unit "bugg",
dsc: "You see a particularly beautiful slimy {{bugg.}}"
takeable: false
act: (salet) =>
salet.rooms[salet.current].drop("bugg")
return "You eat the bugg mass. Delicious and raw. Perhaps it's a good lair to live in."
]
phrase "Yes", salet, "merchant", """
Yes.
"""
dialogue "No", salet, "merchant", "merchant", """
No.
"""
room "sell-lamp", salet,
ways: ["shop"]
tags: ["merchant"]
choices: ["#merchant"]
optionText: "May I buy this lamp?"
title: "Talking with merchant"
canView: (salet) ->
return salet.character.has("lamp") and salet.character.bought_lamp == false
enter: (salet) ->
salet.character.bought_lamp = true
dsc: """
"That'll be 30 pieces of your time."
You quickly pay the price and take the lamp as a rightful owner.
"""
room "shop-inside", salet,
ways: ["shop"]
tags: ["merchant"]
optionText: "End the conversation"
title: "Inside the Shop"
dsc: """
The insides are painted pastel white, honouring The Great Milk Spill of 1985.
"""
units: [
unit "merchant",
dsc: "A {{merchant}} eyes you warily."
takeable: false
act: (salet) =>
salet.processClick("merchdialogue")
return ""
]
lamp = unit "lamp",
takeable: true
lamp.put(salet, "shop-inside")
# The dialogue entry point has to be a room, in order to have an ID to go to.
room "merchdialogue", salet,
choices: "#merchant",
dsc: """
Nice day, isn't it?
"""

View file

@ -1,85 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Salet showcase</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href='https://fonts.googleapis.com/css?family=PT+Sans:400,400italic|PT+Sans+Caption' rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="css/main.css">
</head>
<body>
<div id="page" class="container">
<div class="row">
<div id="title" class="title">
<div class="label">
<h1>Salet</h1>
<h2>A general cybertext IF engine</h2>
<noscript>
<p class="noscript_message">This game requires Javascript.</p>
</noscript>
</div>
</div>
</div>
<div id="content_wrapper" class="row">
<div id="intro" class="content">
<section>
<!-- For the accessibility reasons, you really should begin your game here.
Write a short intro the player will read while the Javascript is loading and executing.
It has to be HTML but that shouldn't be a BIG problem if you're reading this.
On the other hand this means the first intro passages will be non-interactive. -->
<p><em>Salet</em> is an offspring of Undum and Raconteur.</p>
<p>The project is still a work-in-progress. This "game" will show you some of the new features.</p>
<p>It's supposed to be relatively painless for the author without sacrificing the reader's experience.
For example, the game still loads while you're reading this.</p>
<p><em>Salet</em> is <em>not</em> a library or a framework you can slap something on.
It's more a game you can hack into your game. (Like Undum, yeah.)</p>
<p>There are no either cool editor or clear instructions.
You'll have to copy the source code and edit the CoffeeScript files in the <code>game</code> folder.
Then <em>compile</em> them.</p>
<p>This is neither a technical documentation nor UI tutorial, this is a <em>game.</em>
You're supposed to note all the cool and curious features yourself.
If you want some technical info, there are the <a href="http://git.oreolek.ru/oreolek/salet">source code</a> and a <a href="http://git.oreolek.ru/oreolek/salet/wikis/home">wiki.</a></p>
<p>I'm just here to point out that you are playing this in the web browser, online.
And you don't need to learn <em>The Complete Javascript, Volumes I-III</em> to write in this style.</p>
<p>So let me show you... The World.</p>
<noscript>You need to turn on Javascript to play this game.</noscript>
</section>
</div>
<div id="content" class="content">
</div>
<a name="end_of_content"></a>
</div>
<div id="tools_wrapper" class="row">
<div class='ways'>
<small class="text-muted" id="ways_hint">Click these links to explore other rooms</small>
<ul class="nav nav-pills" id="ways">
</ul>
</div>
</div> <!-- End of div.tools_wrapper -->
<div id="legal" class="row">
<div id="footleft">
<p>
If you want to know how this game was made, check out <a
href="https://git.oreolek.ru/oreolek/salet" target="_blank">the
source code.</a>
</p>
</div>
<div id="footright">
<p id='buttons'>
<a href="inventory"><button id="inventory" class="btn btn-outline-primary">Inventory</button></a>
<button id="erase" class="btn btn-outline-danger">Restart</button>
</p>
</div>
</div>
</div> <!-- End of div.page -->
<!-- CDN JS Libraries -->
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/marked/0.3.5/marked.min.js" integrity="sha384-QXBtGc4014gU26HdCwzgy8TVO+FHSSE4+EvPPiSTpdE9w0KyJy1ocfiIbBl1HLq7" crossorigin="anonymous"></script>
<script type="text/javascript" src="//code.jquery.com/jquery-3.0.0.min.js"
integrity="sha384-THPy051/pYDQGanwU6poAc/hOdQxjnOEXzbT+OuUAFqNqFjL+4IGLBgCJC3ZOShY" crossorigin="anonymous"></script>
<script type="text/javascript" src="game/bundle.js"></script>
</body>
</html>

View file

@ -1,172 +0,0 @@
###
oneOf.js
Copyright (c) 2015 Bruno Dias
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
###
###
Undularity Tools
Those functions are not a core part of Undularity, but provide some
general functionality that relates to adaptive text generation.
This is provided partly as a helper to less technical users, and as
a convenience for authors.
###
# Monkey patching
###
Shuffles an array. It can use Undum's random number generator implementation,
so it expects a System.rnd object to be passed into it. If one isn't
supplied, it will use Math.Random instead.
This is an implementation of the Fischer-Yates (Knuth) shuffle.
Returns the shuffled array.
###
Array.prototype.shuffle = (system) ->
rng = if system then system.rnd.random else Math.random
# slice() clones the array. Object members are copied by reference, beware.
newArr = this.slice()
m = newArr.length
while (m)
i = Math.floor(rng() * m--)
t = newArr[m]
newArr[m] = newArr[i]
newArr[i] = t
return newArr
###
oneOf()
Takes an array and returns an object with several methods. Each method
returns an iterator which iterates over the array in a specific way:
inOrder()
Returns the array items in order.
cycling()
Returns the array items in order, cycling back to the first item when
it runs out.
stopping()
Returns the array items in order, then repeats the last item when it
runs out.
randomly()
Returns the array items at random. Takes a system object, for consistent
randomness. Will never return the same item twice in a row.
trulyAtRandom()
Returns the array items purely at random. Takes a system object, for
consistent randomness.
inRandomOrder()
Returns the array items in a random order. Takes a system object, for
consistent randomness.
###
###
Takes a function and gives it a toString() property that calls itself and
returns its value, allowing for ambiguous use of the closure object
as a text snippet.
Returns the modified function.
###
stringish = (callback) ->
callback.toString = () ->
return '' + this.call()
return callback
oneOf = (ary...) ->
if ary.length == 0
throw new Error(
"tried to create a oneOf iterator with a 0-length array");
return {
inOrder: () ->
i = 0
return stringish(() ->
if i >= ary.length
return null
return ary[i++]
)
cycling: () ->
i = 0
return stringish(() ->
if (i >= ary.length)
i = 0
return ary[i++]
)
stopping: () ->
i = 0
return stringish(() ->
if (i >= ary.length)
i = ary.length - 1
return ary[i++]
)
randomly: (system) ->
rng = if system then system.rnd.random else Math.random
last = null
if (ary.length<2)
throw new Error("attempted to make randomly() iterator with a 1-length array")
return stringish( () ->
i = null
offset = null
if not last?
i = Math.floor(rng() * ary.length)
else
###
Let offset be a random number between 1 and the length of the
array, minus one. We jump offset items ahead on the array,
wrapping around to the beginning. This gives us a random item
other than the one we just chose.
###
offset = Math.floor(rng() * (ary.length -1) + 1);
i = (last + offset) % ary.length;
last = i
return ary[i]
)
trulyAtRandom: (system) ->
rng = if system then system.rnd.random else Math.random
return stringish(() ->
return ary[Math.floor(rng() * ary.length)];
)
inRandomOrder: (system) ->
shuffled = ary.shuffle(system)
i = 0
return stringish(() ->
if (i >= ary.length)
i = 0
return shuffled[i++]
)
}
Array.prototype.oneOf = () ->
oneOf.apply(null, this)
String.prototype.oneOf = () ->
return this
module.exports = oneOf;

View file

@ -1,207 +0,0 @@
// Random Number generation based on seedrandom.js code by David Bau.
// Copyright 2010 David Bau, all rights reserved.
//
// Redistribution and use in source and binary forms, with or
// without modification, are permitted provided that the following
// conditions are met:
//
// 1. Redistributions of source code must retain the above
// copyright notice, this list of conditions and the
// following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the
// following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of this module nor the names of its
// contributors may be used to endorse or promote products
// derived from this software without specific prior written
// permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
// CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
var width = 256;
var chunks = 6;
var significanceExponent = 52;
var startdenom = Math.pow(width, chunks);
var significance = Math.pow(2, significanceExponent);
var overflow = significance * 2;
var Random = (function () {
var Random = function(seed) {
this.random = null;
if (!seed) throw {
name: "RandomSeedError",
message: "random_seed_error".l()
};
var key = [];
mixkey(seed, key);
var arc4 = new ARC4(key);
this.random = function() {
var n = arc4.g(chunks);
var d = startdenom;
var x = 0;
while (n < significance) {
n = (n + x) * width;
d *= width;
x = arc4.g(1);
}
while (n >= overflow) {
n /= 2;
d /= 2;
x >>>= 1;
}
return (n + x) / d;
};
};
// Helper type.
var ARC4 = function(key) {
var t, u, me = this, keylen = key.length;
var i = 0, j = me.i = me.j = me.m = 0;
me.S = [];
me.c = [];
if (!keylen) { key = [keylen++]; }
while (i < width) { me.S[i] = i++; }
for (i = 0; i < width; i++) {
t = me.S[i];
j = lowbits(j + t + key[i % keylen]);
u = me.S[j];
me.S[i] = u;
me.S[j] = t;
}
me.g = function getnext(count) {
var s = me.S;
var i = lowbits(me.i + 1); var t = s[i];
var j = lowbits(me.j + t); var u = s[j];
s[i] = u;
s[j] = t;
var r = s[lowbits(t + u)];
while (--count) {
i = lowbits(i + 1); t = s[i];
j = lowbits(j + t); u = s[j];
s[i] = u;
s[j] = t;
r = r * width + s[lowbits(t + u)];
}
me.i = i;
me.j = j;
return r;
};
me.g(width);
};
// Helper functions.
var mixkey = function(seed, key) {
seed += '';
var smear = 0;
for (var j = 0; j < seed.length; j++) {
var lb = lowbits(j);
smear ^= key[lb];
key[lb] = lowbits(smear*19 + seed.charCodeAt(j));
}
seed = '';
for (j in key) {
seed += String.fromCharCode(key[j]);
}
return seed;
};
var lowbits = function(n) {
return n & (width - 1);
};
return Random;
})();
/* Returns a random floating point number between zero and
* one. NB: The prototype implementation below just throws an
* error, it will be overridden in each Random object when the
* seed has been correctly configured. */
Random.prototype.random = function() {
throw {
name:"RandomError",
message: "random_error".l()
};
};
/* Returns an integer between the given min and max values,
* inclusive. */
Random.prototype.randomInt = function(min, max) {
return min + Math.floor((max-min+1)*this.random());
};
/* Returns the result of rolling n dice with dx sides, and adding
* plus. */
Random.prototype.dice = function(n, dx, plus) {
var result = 0;
for (var i = 0; i < n; i++) {
result += this.randomInt(1, dx);
}
if (plus) result += plus;
return result;
};
/* Returns the result of rolling n averaging dice (i.e. 6 sided dice
* with sides 2,3,3,4,4,5). And adding plus. */
Random.prototype.aveDice = (function() {
var mapping = [2,3,3,4,4,5];
return function(n, plus) {
var result = 0;
for (var i = 0; i < n; i++) {
result += mapping[this.randomInt(0, 5)];
}
if (plus) result += plus;
return result;
};
})();
/* Returns a dice-roll result from the given string dice
* specification. The specification should be of the form xdy+z,
* where the x component and z component are optional. This rolls
* x dice of with y sides, and adds z to the result, the z
* component can also be negative: xdy-z. The y component can be
* either a number of sides, or can be the special values 'F', for
* a fudge die (with 3 sides, +,0,-), '%' for a 100 sided die, or
* 'A' for an averaging die (with sides 2,3,3,4,4,5).
*/
Random.prototype.diceString = (function () {
var diceRe = /^([1-9][0-9]*)?d([%FA]|[1-9][0-9]*)([-+][1-9][0-9]*)?$/;
return function(def) {
var match = def.match(diceRe);
if (!match) {
throw new Error(
"dice_string_error".l({string:def})
);
}
var num = match[1]?parseInt(match[1], 10):1;
var sides;
var bonus = match[3]?parseInt(match[3], 10):0;
switch (match[2]) {
case 'A':
return this.aveDice(num, bonus);
case 'F':
sides = 3;
bonus -= num*2;
break;
case '%':
sides = 100;
break;
default:
sides = parseInt(match[2], 10);
break;
}
return this.dice(num, sides, bonus);
};
})();
module.exports = Random;

View file

@ -1,2 +0,0 @@
These functions are not used by Salet core.
But you can `require` them in your game and use thusly.

View file

@ -1,26 +0,0 @@
room = require("../room.coffee")
###
A dialogue shortcut.
Usage:
dialogue "Point out a thing in her purse (mildly)", "start", "mild", """
Point out a thing in her purse (mildly)
""", "character.mild = true"
###
dialogue = (title, salet, startTag, endTag, text, effect) ->
retval = room("dialogue_"+Object.keys(salet.rooms).length, salet, {
optionText: title
dsc: text
clear: false # backlog is useful in dialogues
choices: "#"+endTag
})
if typeof(startTag) == "string"
retval.tags = [startTag]
else if typeof(startTag) == "object"
retval.tags = startTag
if effect?
retval.before = (character, system) ->
eval(effect)
return retval
module.exports = dialogue

View file

@ -1,29 +0,0 @@
room = require("../room.coffee")
###
A phrase shortcut.
Usage:
phrase "Point out a thing in her purse (mildly)", "start", """
Point out a thing in her purse (mildly)
""", "character.sandbox.mild = true"
@param title phrase Phrase (question)
@param salet Salet core
@param string tag tag marking viewing condition
@param string text Response
@param string effect an optional parameter, eval'd code
###
phrase = (title, salet, tag, text, effect) ->
retval = room("phrase_"+salet.rooms.length, salet, {
optionText: title
dsc: text
clear: false # backlog is useful in dialogues
choices: "#"+tag
tags: [tag]
})
if effect?
retval.before = (character, system) ->
eval(effect)
return retval
module.exports = phrase

View file

@ -1,19 +0,0 @@
@mixin halfcolumn() {
@include make-col();
@media (min-width: breakpoint-min(sm)) {
@include make-col-span(6);
}
@media (max-width: breakpoint-max(xs)) {
@include make-col-span(12);
}
}
@mixin col($sm-width, $xs-width) {
@include make-col();
@media (min-width: breakpoint-min(sm)) {
@include make-col-span($sm-width);
}
@media (max-width: breakpoint-max(xs)) {
@include make-col-span($xs-width);
}
}

View file

@ -1,13 +0,0 @@
$font-family-sans-serif: 'PT Sans','Open Sans',"Helvetica Neue", Helvetica, Arial, sans-serif;
$headings-font-family: "PT Sans Caption",$font-family-sans-serif;
$font-family-base: $font-family-sans-serif;
$body-bg: #F1EED9;
$body-color: #58351A;
$link-color: #382313;
$btn-bg: #C33601;
$btn-color: lighten($btn-bg, 50%);
$secondary-bg: #F1EED9;
$waycolor: $link-color;
$text_background: $body-bg; // can be btn-bg

View file

@ -1,209 +0,0 @@
@import "mixins";
@import "variables";
// Bootstrap v4 stripped core
@import "../node_modules/bootstrap/scss/variables";
@import "../node_modules/bootstrap/scss/mixins";
@import "../node_modules/bootstrap/scss/normalize";
@import "../node_modules/bootstrap/scss/print";
@import "../node_modules/bootstrap/scss/reboot";
@import "../node_modules/bootstrap/scss/type";
@import "../node_modules/bootstrap/scss/images";
@import "../node_modules/bootstrap/scss/grid";
@import "../node_modules/bootstrap/scss/buttons";
@import "../node_modules/bootstrap/scss/nav";
@import "../node_modules/bootstrap/scss/responsive-embed";
@import "../node_modules/bootstrap/scss/utilities";
body {
overflow-y: scroll;
overflow-x: hidden;
background: $body-bg;
}
// The title block
.title {
margin-top: 2em;
@include col(10,12);
@media (min-width: breakpoint-min(sm)) {
@include make-col-offset(1);
}
.label {
margin: 1em auto;
max-width: 18em;
text-align: center;
}
.subtitle {
font-size: smaller;
color: #aaa;
}
h1,
h2,
h3 {
text-shadow: rgba(255,255,255,0.5) 2px 2px 2px,
rgba(0,0,0,0.1) -1px -1px 2px;
}
h2 {
font-size: 1.5rem;
}
.warnings {
font-size: small;
font-style: italic;
p {
margin-bottom: 1em;
}
}
.noscript_message {
left: 0;
right: 0;
bottom: 0;
position: absolute;
font-size: 0.9em;
font-style: italic;
text-align: center;
color: #943;
}
}
#tools_wrapper {
.ways {
padding: 0.5em;
@include col(8, 9);
@media (min-width: breakpoint-min(sm)) {
@include make-col-offset(1);
}
}
.destination {
font-weight: bold;
}
.menu {
@include col(3, 4);
span {
cursor: pointer;
}
}
}
#content_wrapper {
background: $text_background;
border-radius: 5px;
}
.content {
@include col(10, 12);
@media (min-width: breakpoint-min(sm)) {
@include make-col-offset(1);
}
.pic {
text-align: center;
margin-bottom: 1em;
}
p {
hyphens: auto;
}
padding: 1em;
ul {
margin: 0;
padding: 0 0 0 1em;
}
ul.options {
padding: 0;
text-align: center;
margin-top: 0.5em;
margin-bottom: 0.7em;
list-style-type: none;
border-radius: 4px;
li {
padding: 0.5em;
}
li:hover {
cursor: pointer;
}
li:last-child {
border-bottom: none;
}
}
section {
border-top: 1px dashed #bbb;
}
.situation-start {
border-top: none;
}
img.right {
float: right;
margin: 1.1em 0 1.1em 1.1em;
}
img.left {
float: left;
margin: 1.1em 1.1em 1.1em 0;
}
h3 {
text-align: center;
}
}
#legal {
margin-top: 1em;
color: darken($body-color, 10%);
font-size: smaller;
#footleft {
@media (min-width: breakpoint-min(sm)) {
@include make-col-offset(2);
@include make-col(5);
}
@media (max-width: breakpoint-max(xs)) {
@include make-col(12);
}
}
#footright {
text-align: right;
@media (min-width: breakpoint-min(sm)) {
@include make-col(3);
@include make-col-offset(2);
}
@media (max-width: breakpoint-max(xs)) {
@include make-col(12);
margin-bottom: 1em;
}
}
}
.way {
color: $waycolor;
margin-right: 1em;
}
.cycle {
color: darkgreen;
border-bottom: darkgreen dashed 1px;
}
ul.options {
border: 1px solid #876;
li {
border-bottom: 1px solid #876;
}
li:hover {
background-color: rgba(153,136,119,0.2);
}
}
#legal {
.muted {
color: grey;
}
}
hr {
width: 50%;
border-color: $body-color;
}
// fade-in animation
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.fadeIn {
animation-name: fadeIn;
}
.center {
text-align: center;
}

View file

@ -36,5 +36,3 @@ class Character
character = (spec) ->
spec ?= {}
return( new Character(spec) )
module.exports = character

View file

@ -58,5 +58,3 @@ String.prototype.l = (args) ->
new RegExp("\\{"+name+"\\}"), args[name]
)
return localized
module.exports = languages;

View file

@ -35,5 +35,3 @@ markdown = (text) ->
return marked(normaliseTabs(text), {
smartypants: true
})
module.exports = markdown

View file

@ -1,6 +1,3 @@
unit = require('./unit.coffee')
markdown = require('./markdown.coffee')
assert = (msg, assertion) -> console.assert assertion, msg
Function.prototype.fcall = Function.prototype.call
@ -255,5 +252,3 @@ room = (name, salet, spec) ->
spec ?= {}
spec.name = name
return new SaletRoom(spec).register(salet)
module.exports = room

View file

@ -1,9 +1,3 @@
markdown = require('./markdown.coffee')
SaletView = require('./view.coffee')
Random = require('./random.js')
Character = require('./character.coffee')
require('./localize.coffee')
###
fcall() (by analogy with fmap) is added to the prototypes of both String and
Function. When called on a Function, it's an alias for Function#call();
@ -36,6 +30,8 @@ class Salet
@rnd = null
@time = 0
@languages = languages
# Corresponding room names to room objects.
@rooms = {}
@ -457,4 +453,10 @@ salet = (spec) ->
retval.view.init(retval)
return retval
module.exports = salet
module.exports = {
salet: salet
room: room
unit: unit
markdown: markdown
character: character
}

View file

@ -1,5 +1,3 @@
markdown = require('./markdown.coffee')
#require('./salet.coffee')
unitlink = (content, ref) ->
return "<a href='./_act_#{ref}' class='once'>#{content}</a>"
@ -53,4 +51,3 @@ unit = (name, spec) ->
spec ?= {}
spec.name = name
return new SaletUnit(spec)
module.exports = unit

View file

@ -1,4 +1,3 @@
markdown = require('./markdown.coffee')
###
Salet interface configuration.
In a typical MVC structure, this is the View.
@ -301,5 +300,3 @@ class SaletView
cycleLink: (content) ->
return "<a href='./_replacer_cyclewriter' class='cycle' id='cyclewriter'>#{content}</a>"
module.exports = SaletView