mirror of
https://gitlab.com/Oreolek/salet.git
synced 2024-07-04 07:45:03 +03:00
Backports from Rachel, new interface.
This commit is contained in:
parent
f1a6d47dea
commit
16b82caf8d
121
Gulpfile.coffee
121
Gulpfile.coffee
|
@ -1,118 +1,131 @@
|
||||||
|
watchify = require('watchify')
|
||||||
|
browserify = require('browserify')
|
||||||
browserSync = require('browser-sync')
|
browserSync = require('browser-sync')
|
||||||
gulp = require('gulp')
|
gulp = require('gulp')
|
||||||
|
source = require('vinyl-source-stream')
|
||||||
|
gutil = require('gulp-util')
|
||||||
|
coffeify = require('coffeeify')
|
||||||
coffee = require("gulp-coffee")
|
coffee = require("gulp-coffee")
|
||||||
sass = require('gulp-sass')
|
sass = require('gulp-sass')
|
||||||
|
uglify = require('gulp-uglify')
|
||||||
|
buffer = require('vinyl-buffer')
|
||||||
zip = require('gulp-zip')
|
zip = require('gulp-zip')
|
||||||
|
_ = require('lodash')
|
||||||
concat = require('gulp-concat')
|
concat = require('gulp-concat')
|
||||||
|
|
||||||
reload = browserSync.reload;
|
reload = browserSync.reload
|
||||||
|
|
||||||
# Copy assets over without touching them
|
html = (target) ->
|
||||||
assets = (target) ->
|
|
||||||
return () ->
|
return () ->
|
||||||
return gulp.src([
|
return gulp.src(['html/index.html','html/en.html'])
|
||||||
'img/*.png',
|
.pipe(gulp.dest(target));
|
||||||
'img/*.jpeg',
|
|
||||||
'img/*.jpg'
|
|
||||||
]).pipe(gulp.dest(target))
|
|
||||||
|
|
||||||
gulp.task('assets', assets('./build'));
|
img = (target) ->
|
||||||
|
return () ->
|
||||||
|
return gulp.src(['img/*.png', 'img/*.jpeg', 'img/*.jpg'])
|
||||||
|
.pipe(gulp.dest(target));
|
||||||
|
|
||||||
gulp.task('sass', function () {
|
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')
|
gulp.src('sass/main.scss')
|
||||||
.pipe(sass().on('error', sass.logError))
|
.pipe(sass({outputStyle: 'compressed'}).on('error', sass.logError))
|
||||||
.pipe(gulp.dest('./build/css'));
|
.pipe(gulp.dest('./build/css'));
|
||||||
|
)
|
||||||
|
|
||||||
|
sources = [
|
||||||
|
'./game/begin.coffee',
|
||||||
|
'./game/story.coffee',
|
||||||
|
'./game/init.coffee',
|
||||||
|
]
|
||||||
|
|
||||||
|
opts = _.assign({}, watchify.args, {
|
||||||
|
entries: ["./build/game/main.coffee"]
|
||||||
|
debug: true
|
||||||
|
transform: [coffeify]
|
||||||
});
|
});
|
||||||
|
bundler = watchify(browserify(opts));
|
||||||
|
|
||||||
gulp.task('html', html('./build'));
|
bundle = () ->
|
||||||
|
return bundler.bundle()
|
||||||
|
.on('error', gutil.log.bind(gutil, 'Browserify Error'))
|
||||||
|
.pipe(source('bundle.js'))
|
||||||
|
.pipe(gulp.dest('./build/game'));
|
||||||
|
|
||||||
gulp.task('concatCoffee', function() {
|
gulp.task('concatCoffee', () ->
|
||||||
return gulp.src([
|
return gulp.src(sources)
|
||||||
'./game/begin.coffee',
|
|
||||||
'./game/story.coffee',
|
|
||||||
'./game/init.coffee',
|
|
||||||
])
|
|
||||||
.pipe(concat('./main.coffee'))
|
.pipe(concat('./main.coffee'))
|
||||||
.pipe(gulp.dest('./build/game'));
|
.pipe(gulp.dest('./build/game'));
|
||||||
});
|
);
|
||||||
|
|
||||||
gulp.task('coffee', ['concatCoffee'])
|
gulp.task('coffee', ['concatCoffee'], bundle);
|
||||||
|
|
||||||
bundler.on('update', coffee);
|
bundler.on('update', bundle);
|
||||||
bundler.on('log', gutil.log); # Output build logs to terminal
|
bundler.on('log', gutil.log);
|
||||||
|
|
||||||
gulp.task('build', ['html', 'img', 'sass', 'coffee'])
|
gulp.task('build', ['html', 'img', 'sass', 'coffee', 'audio'])
|
||||||
|
|
||||||
gulp.task('serve', ['build'], () ->
|
gulp.task('serve', ['build'], () ->
|
||||||
browserSync({
|
browserSync({
|
||||||
server: {
|
server: {
|
||||||
baseDir: 'build'
|
baseDir: 'build'
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
sassListener = () ->
|
sassListener = () ->
|
||||||
reload('./build/css/main.css');
|
reload('./build/css/main.css');
|
||||||
|
|
||||||
gulp.watch(['./html/*.html'], ['html']);
|
gulp.watch(['./html/*.html'], ['html']);
|
||||||
gulp.watch(['./sass/*.scss'], ['sass']);
|
gulp.watch(['./sass/*.scss'], ['sass']);
|
||||||
gulp.watch(['./build/css/main.css'], sassListener);
|
|
||||||
gulp.watch(['./img/*.png', './img/*.jpeg', './img/*.jpg'], ['img']);
|
gulp.watch(['./img/*.png', './img/*.jpeg', './img/*.jpg'], ['img']);
|
||||||
gulp.watch(['./game/*.coffee'], ['coffee']);
|
gulp.watch(['./game/*.coffee'], ['coffee']);
|
||||||
|
|
||||||
|
gulp.watch(['./build/css/main.css'], sassListener);
|
||||||
gulp.watch(
|
gulp.watch(
|
||||||
['./build/game/bundle.js', './build/img/*', './build/index.html'],
|
['./build/game/bundle.js', './build/img/*', './build/index.html'],
|
||||||
browserSync.reload)
|
browserSync.reload);
|
||||||
})
|
)
|
||||||
|
|
||||||
/* Distribution tasks */
|
|
||||||
|
|
||||||
var undumDistBundler = browserify();
|
|
||||||
undumDistBundler.require('undum-commonjs');
|
|
||||||
|
|
||||||
gulp.task('undum-dist', function () {
|
|
||||||
return undumDistBundler.bundle().pipe(source('undum.js'))
|
|
||||||
.pipe(buffer())
|
|
||||||
.pipe(uglify())
|
|
||||||
.pipe(gulp.dest('./dist/game'));
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('html-dist', html('./dist'));
|
gulp.task('html-dist', html('./dist'));
|
||||||
gulp.task('img-dist', img('./dist/img'));
|
gulp.task('img-dist', img('./dist/img'));
|
||||||
gulp.task('legal-dist', function () {
|
gulp.task('audio-dist', audio('./dist/audio'));
|
||||||
|
gulp.task('legal-dist', () ->
|
||||||
return gulp.src(['LICENSE.txt'])
|
return gulp.src(['LICENSE.txt'])
|
||||||
.pipe(gulp.dest("./dist"));
|
.pipe(gulp.dest("./dist"));
|
||||||
});
|
);
|
||||||
|
|
||||||
gulp.task('sass-dist', function () {
|
gulp.task('sass-dist', () ->
|
||||||
return gulp.src('./sass/main.scss')
|
return gulp.src('./sass/main.scss')
|
||||||
.pipe(sass({outputStyle: 'compressed'}))
|
.pipe(sass({outputStyle: 'compressed'}))
|
||||||
.pipe(gulp.dest('./dist/css'));
|
.pipe(gulp.dest('./dist/css'));
|
||||||
});
|
);
|
||||||
|
|
||||||
var distBundler = browserify({
|
distBundler = browserify({
|
||||||
debug: false,
|
debug: false,
|
||||||
entries: ['./build/game/main.coffee'],
|
entries: ['./build/game/main.coffee'],
|
||||||
transform: ['coffeeify']
|
transform: ['coffeeify']
|
||||||
});
|
});
|
||||||
|
|
||||||
distBundler.external('undum-commonjs');
|
gulp.task('coffee-dist', ['concatCoffee'], () ->
|
||||||
|
|
||||||
gulp.task('coffee-dist', ['undum-dist', 'concatCoffee'], function () {
|
|
||||||
return distBundler.bundle()
|
return distBundler.bundle()
|
||||||
.pipe(source('bundle.js'))
|
.pipe(source('bundle.js'))
|
||||||
.pipe(buffer())
|
.pipe(buffer())
|
||||||
.pipe(uglify())
|
.pipe(uglify())
|
||||||
.on('error', gutil.log)
|
.on('error', gutil.log)
|
||||||
.pipe(gulp.dest('./dist/game'));
|
.pipe(gulp.dest('./dist/game'));
|
||||||
});
|
);
|
||||||
|
|
||||||
gulp.task('dist', ['html-dist', 'img-dist', 'sass-dist', 'coffee-dist', 'legal-dist'],
|
gulp.task('dist', ['html-dist', 'img-dist', 'sass-dist', 'coffee-dist', 'audio-dist', 'legal-dist']);
|
||||||
function () {
|
|
||||||
return;
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('zip', ['dist'], function () {
|
gulp.task('zip', ['dist'], () ->
|
||||||
return gulp.src('dist/**')
|
return gulp.src('dist/**')
|
||||||
.pipe(zip('dist.zip'))
|
.pipe(zip('dist.zip'))
|
||||||
.pipe(gulp.dest('.'));
|
.pipe(gulp.dest('.'));
|
||||||
});
|
);
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
A general client-side framework for cybertext interactive fiction games.
|
A general client-side framework for cybertext interactive fiction games.
|
||||||
|
|
||||||
**Salet** is based upon the ideas of [Undum,](https://github.com/idmillington/undum) but is not a direct follower.
|
**Salet** is based upon the ideas of [Undum,](https://github.com/idmillington/undum) but is not a direct follower.
|
||||||
|
It also uses some code from [Raconteur,](https://github.com/sequitur/raconteur) rewritten in CoffeeScript and altered to its own needs.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|
|
@ -1,170 +1,44 @@
|
||||||
situation = require('raconteur')
|
markdown = require('../../lib/markdown.coffee')
|
||||||
|
room = require("../../lib/room.coffee")
|
||||||
|
obj = require('../../lib/obj.coffee')
|
||||||
|
dialogue = require('../../lib/dialogue.coffee')
|
||||||
undum = require('undum-commonjs')
|
undum = require('undum-commonjs')
|
||||||
oneOf = require('raconteur/lib/oneOf.js')
|
oneOf = require('../../lib/oneOf.coffee')
|
||||||
qualities = require('raconteur/lib/qualities.js')
|
|
||||||
$ = require("jquery")
|
$ = require("jquery")
|
||||||
|
require('../../lib/interface.coffee')
|
||||||
|
|
||||||
Array.prototype.oneOf = () ->
|
undum.game.id = "your-game-id-here"
|
||||||
oneOf.apply(null, this)
|
|
||||||
|
|
||||||
md = require('markdown-it')
|
|
||||||
markdown = new md({
|
|
||||||
typographer: true,
|
|
||||||
html: true
|
|
||||||
})
|
|
||||||
|
|
||||||
shortid = require('shortid')
|
|
||||||
# you have to alter linkRe in Undum core to use that.
|
|
||||||
# Undum doesn't allow using uppercase letters in situation names by default.
|
|
||||||
|
|
||||||
undum.game.id = "6a909a4-586a-4089-bd18-26da684d1c8d"
|
|
||||||
undum.game.version = "1.0"
|
undum.game.version = "1.0"
|
||||||
|
|
||||||
a = require('raconteur/lib/elements.js').a
|
###
|
||||||
way_to = (content, ref) -> a(content).class('way').ref(ref)
|
Element helpers. There is no real need to build monsters like a().id("hello")
|
||||||
textlink = (content, ref) -> a(content).once().writer(ref)
|
because you won't use them as is. It does not make sense in context, the
|
||||||
actlink = (content, ref) -> a(content).once().action(ref)
|
author has Markdown and all utilities to *forget* about the markup.
|
||||||
textcycle = (content, ref) -> a(content).replacer(ref).class("cycle").id(ref).toString()
|
###
|
||||||
|
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>"
|
||||||
|
textcycle = (content, ref) ->
|
||||||
|
return "<a href='./_replacer_#{ref}' class='cycle' id='#{ref}'>#{content}</a>"
|
||||||
|
|
||||||
# usage: writemd( system, "Text to write")
|
# usage: writemd( system, "Text to write")
|
||||||
writemd = (system, text) ->
|
writemd = (system, text) ->
|
||||||
if typeof text is Function
|
text = markdown(text)
|
||||||
text = text()
|
|
||||||
text = markdown.render(text)
|
|
||||||
system.write(text)
|
system.write(text)
|
||||||
|
|
||||||
preparemd = (text, mark) ->
|
# The first room of the game.
|
||||||
if typeof text is Function
|
# For accessibility reasons the text is provided in HTML, not here.
|
||||||
text = text()
|
room "start",
|
||||||
text = markdown.render(text)
|
|
||||||
if mark?
|
|
||||||
text = """
|
|
||||||
<div class="#{mark}">
|
|
||||||
#{text}
|
|
||||||
</div>
|
|
||||||
"""
|
|
||||||
return text
|
|
||||||
|
|
||||||
money = (character, system, amount) ->
|
|
||||||
system.setQuality("money", character.qualities.money + amount)
|
|
||||||
|
|
||||||
code_can_input = (character) ->
|
|
||||||
return character.sandbox.code.length < 8
|
|
||||||
|
|
||||||
code_print = (character) ->
|
|
||||||
mask = 8 - character.sandbox.code.length
|
|
||||||
retval = character.sandbox.code
|
|
||||||
if mask > 0
|
|
||||||
for i in [1..mask]
|
|
||||||
retval += "_"
|
|
||||||
return retval
|
|
||||||
|
|
||||||
code_input = (character, digit) ->
|
|
||||||
if code_can_input(character)
|
|
||||||
character.sandbox.code = character.sandbox.code + digit
|
|
||||||
|
|
||||||
code_reset = (character) ->
|
|
||||||
character.sandbox.code = ""
|
|
||||||
|
|
||||||
code_check = (character, system) ->
|
|
||||||
if character.sandbox.code.length >= 8
|
|
||||||
# There is an Undum.. let's call it a feature
|
|
||||||
# that prevents the player from entering "3112".
|
|
||||||
# You see, you can't select the situation 1 when you are
|
|
||||||
# already in this situation, so you can't input 1 twice.
|
|
||||||
if character.sandbox.code == "01012017"
|
|
||||||
character.sandbox.box_opened = 1
|
|
||||||
if character.sandbox.knows_the_code == 0
|
|
||||||
writemd(system, """
|
|
||||||
Is he an extraordinary puzzle cracker or was it a sheer luck, but Ronald manages to *guess* the code.
|
|
||||||
""")
|
|
||||||
else
|
|
||||||
writemd(system, """
|
|
||||||
New Year 2017.
|
|
||||||
L. Y. must be Leonard Yakovlev, a famous painter.
|
|
||||||
Some tabloids tried to connect him with Ana but it seemed like a weak link.
|
|
||||||
|
|
||||||
By that logic, his sketch is worth more than all the cash here.
|
|
||||||
Ronald thinks about it, but decides to "let the woman have her memories".
|
|
||||||
""")
|
|
||||||
writemd(system, """
|
|
||||||
Something clicks and box opens.
|
|
||||||
|
|
||||||
The phone is slick, black and light in Ronald's hand.
|
|
||||||
It springs to life, humming with purpose.
|
|
||||||
The screen plays an animation: "LOADING..."
|
|
||||||
|
|
||||||
Ronald has no other business here.
|
|
||||||
It's time to go.
|
|
||||||
""")
|
|
||||||
system.doLink("bedroom")
|
|
||||||
else
|
|
||||||
writemd(system, "Something clicks and the display resets, but the box stays locked.")
|
|
||||||
if character.sandbox.code == "000000"
|
|
||||||
writemd(system, "Of course, Ronald didn't hope it would be that easy.")
|
|
||||||
|
|
||||||
character.sandbox.code = ""
|
|
||||||
|
|
||||||
update_ways = (ways) ->
|
|
||||||
content = ""
|
|
||||||
for way in ways
|
|
||||||
if undum.game.situations[way]?
|
|
||||||
content += way_to(undum.game.situations[way].title, way)
|
|
||||||
$("#ways").html(content)
|
|
||||||
|
|
||||||
situation "start",
|
|
||||||
content: """
|
content: """
|
||||||
Peter had so much trouble sleeping he had to drown his pills in at least an hour of thoughts.
|
""",
|
||||||
|
choices: "#start"
|
||||||
A violent ringing of the bell awakened him.
|
|
||||||
He rose from the bed, grumbling:
|
|
||||||
“Crazy neighbors and their guests. It must be three o'clock!”
|
|
||||||
|
|
||||||
The visitor entered the hallway.
|
|
||||||
It was him ringing the bell, but he was not going to meet Peter.
|
|
||||||
In fact, he wasn't looking for meeting anybody here.
|
|
||||||
|
|
||||||
Fourth floor, apartment 406.
|
|
||||||
There, he tried two keys.
|
|
||||||
The second of them fitted the lock.
|
|
||||||
|
|
||||||
Burglary is a curious line of employment.
|
|
||||||
Befittedly, Ronald Chernoff was very curious about a black phone behind the door of apartment 406 in a wooden box on a small table no farther than two meters from the bed.
|
|
||||||
A gift, a prototype, a valuable treasure left by Anastacia Kozlowa when she fled the country.
|
|
||||||
Of course, one had to be reasonably au fait with her *Instagram* to notice that.
|
|
||||||
|
|
||||||
Peter opened his door to find an empty silent corridor.
|
|
||||||
He went to the neighbor's door and met a closed door.
|
|
||||||
Ronald was working inside, quietly walking around the apartment.
|
|
||||||
He began the inspection from [the living room.](living-room)
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
# This function needs to go after the start room.
|
||||||
is_visited = (situation) ->
|
is_visited = (situation) ->
|
||||||
situations = undum.game.situations[situation]
|
place = undum.game.situations[situation]
|
||||||
if situations
|
if place
|
||||||
return Boolean situations.visited
|
return Boolean place.visited
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
# N-th level examine function
|
|
||||||
level = (text, mark) ->
|
|
||||||
$("#content .#{mark}").fadeOut()
|
|
||||||
return preparemd(text, mark)
|
|
||||||
|
|
||||||
lvl1 = (text) ->
|
|
||||||
$("#content .lvl2").fadeOut()
|
|
||||||
$("#content .lvl3").fadeOut()
|
|
||||||
$("#content .lvl4").fadeOut()
|
|
||||||
level(text, "lvl1")
|
|
||||||
|
|
||||||
lvl2 = (text) ->
|
|
||||||
$("#content .lvl3").fadeOut()
|
|
||||||
$("#content .lvl4").fadeOut()
|
|
||||||
level(text, "lvl2")
|
|
||||||
|
|
||||||
lvl3 = (text) ->
|
|
||||||
$("#content .lvl4").fadeOut()
|
|
||||||
level(text, "lvl3")
|
|
||||||
|
|
||||||
lvl4 = (text) ->
|
|
||||||
level(text, "lvl4")
|
|
||||||
|
|
|
@ -1,14 +1,6 @@
|
||||||
# This is where you initialize your game.
|
# This is where you initialize your game.
|
||||||
# All code in this file comes last, so the game is almost ready by this point.
|
# All code in this file comes last, so the game is almost ready by this point.
|
||||||
|
|
||||||
player "Player"
|
|
||||||
money: 0
|
|
||||||
status: "Good"
|
|
||||||
|
|
||||||
undum.game.init = (character, system) ->
|
undum.game.init = (character, system) ->
|
||||||
$("#ways").on("click", "a", (event) ->
|
|
||||||
event.preventDefault()
|
|
||||||
undum.processClick($(this).attr("href"))
|
|
||||||
)
|
|
||||||
|
|
||||||
window.onload = undum.begin
|
window.onload = undum.begin
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
# Your game goes here
|
||||||
|
dialogue "Option 1", "start", "secretary", """
|
||||||
|
No spoilers!
|
||||||
|
"""
|
||||||
|
|
||||||
|
dialogue "Option 2", "start", "secretary", """
|
||||||
|
No spoilers!
|
||||||
|
"""
|
||||||
|
|
||||||
|
room "university-start",
|
||||||
|
tags: ["secretary"]
|
||||||
|
ways: ["supermarket"]
|
||||||
|
optionText: "Leave the University"
|
||||||
|
before: () ->
|
||||||
|
document.getElementById("intro").innerHTML = ""
|
||||||
|
document.getElementById("content").innerHTML = ""
|
||||||
|
undum.game.situations["supermarket"].destination()
|
||||||
|
"""
|
||||||
|
You leave the University.
|
||||||
|
"""
|
||||||
|
content: """
|
||||||
|
Okay, now to the supermarket.
|
||||||
|
"""
|
||||||
|
|
||||||
|
room "supermarket",
|
||||||
|
content: """
|
||||||
|
A trendy supermarket.
|
||||||
|
"""
|
|
@ -2,16 +2,33 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>Salet tutorial</title>
|
<title>Salet showcase</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<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 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">
|
<link rel="stylesheet" href="css/main.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="page" class="container">
|
<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="content_wrapper" class="row">
|
||||||
<div id="content">
|
<div id="intro" class="content">
|
||||||
<h1>Salet tutorial</h1>
|
<section>
|
||||||
|
<p>Intro here.</p>
|
||||||
|
|
||||||
|
<noscript>You need to turn on Javascript to play this game.</noscript>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<div id="content" class="content">
|
||||||
</div>
|
</div>
|
||||||
<a name="end_of_content"></a>
|
<a name="end_of_content"></a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -20,22 +37,31 @@
|
||||||
<h2>Other rooms</h2>
|
<h2>Other rooms</h2>
|
||||||
<div id="ways"></div>
|
<div id="ways"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class='buttons'>
|
<div class="menu">
|
||||||
<button id="undo">Undo</button>
|
<ul class="nav nav-pills">
|
||||||
<button id="save">Save</button>
|
<li class="nav-item">
|
||||||
<button id="load">Load</button>
|
<span class="nav-link disabled" id="inventory">Inventory</span>
|
||||||
<button id="erase">Restart</button>
|
</li>
|
||||||
|
<li class="nav-item dropdown">
|
||||||
|
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">System</a>
|
||||||
|
<div class="dropdown-menu">
|
||||||
|
<span class="dropdown-item disabled" id="save">Save</span>
|
||||||
|
<span class="dropdown-item" id="erase">Restart</span>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<span class="dropdown-item disabled" id="undo">Undo (non-fuctioning)</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div><!-- end of tools -->
|
</div> <!-- End of div.tools_wrapper -->
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div id="legal">
|
<div id="legal">
|
||||||
<div id="footleft">
|
<div id="footleft">
|
||||||
<!-- Author credit goes here -->
|
<p>The game was written by <em>(you should put your name here)</em></p>
|
||||||
<p>The game was written by <b><a href="http://en.oreolek.ru/" target="_blank">Oreolek.</a></b></p>
|
<p>Approximate play time: not measured.</p>
|
||||||
<!-- It's a good gesture to specify how long is your game. -->
|
<p>Written using <a href="http://git.oreolek.ru/oreolek/salet" target="_blank">Salet</a>.</p>
|
||||||
<p>Approximate play time: five minutes.</p>
|
<p>Betatesting credit: none yet</p>
|
||||||
<p>Written using <a href="http://git.oreolek.ru/oreolek/salet" target="_blank">Salet.</a>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="footright">
|
<div id="footright">
|
||||||
<a href="./LICENSE.txt"><img src="img/mit.png" alt="This program is licensed under MIT license."></a>
|
<a href="./LICENSE.txt"><img src="img/mit.png" alt="This program is licensed under MIT license."></a>
|
||||||
|
@ -44,6 +70,27 @@
|
||||||
</div>
|
</div>
|
||||||
</div> <!-- End of div.page -->
|
</div> <!-- End of div.page -->
|
||||||
|
|
||||||
<script type="text/javascript" src="game/main.js"></script>
|
<div id="ui_library">
|
||||||
|
<div id="quality" class="quality">
|
||||||
|
<span class="name" data-attr="name"></span>
|
||||||
|
<span class="value" data-attr="value"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="quality_group" class="quality_group">
|
||||||
|
<h2 data-attr="title"></h2>
|
||||||
|
<div class="qualities_in_group">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr id="turn_separator">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="content_library"></div>
|
||||||
|
<!-- CDN JS Libraries -->
|
||||||
|
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/marked/0.3.5/marked.min.js"></script>
|
||||||
|
<script type="text/javascript" src="//code.jquery.com/jquery-2.2.0.min.js"></script>
|
||||||
|
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="game/bundle.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
33
lib/dialogue.coffee
Normal file
33
lib/dialogue.coffee
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
room = require("./room.coffee")
|
||||||
|
|
||||||
|
randomid = () ->
|
||||||
|
alphabet = "abcdefghijklmnopqrstuvwxyz0123456789" # see the dreaded linkRe expression in Undum
|
||||||
|
rndstr = []
|
||||||
|
for i in [1..10]
|
||||||
|
rndstr.push alphabet.charAt(Math.floor(Math.random() * alphabet.length))
|
||||||
|
return rndstr.join('').toString()
|
||||||
|
|
||||||
|
###
|
||||||
|
A dialogue shortcut.
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
dialogue "Point out a thing in her purse (mildly)", "start", "mild", """
|
||||||
|
Point out a thing in her purse (mildly)
|
||||||
|
""", "character.sandbox.mild = true"
|
||||||
|
###
|
||||||
|
dialogue = (title, startTag, endTag, text, effect) ->
|
||||||
|
retval = room(randomid(), {
|
||||||
|
optionText: title
|
||||||
|
content: text
|
||||||
|
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
|
14
lib/interface.coffee
Normal file
14
lib/interface.coffee
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
###
|
||||||
|
Salet interface configuration.
|
||||||
|
###
|
||||||
|
$ = require("jquery")
|
||||||
|
|
||||||
|
$(document).ready(() ->
|
||||||
|
$("#ways").on("click", "a", (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
undum.processClick($(this).attr("href"))
|
||||||
|
)
|
||||||
|
$("#inventory").on("click", "a", (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
)
|
||||||
|
)
|
32
lib/markdown.coffee
Normal file
32
lib/markdown.coffee
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
###
|
||||||
|
Indent normalization. Removes tabs AND spaces from every line beginning.
|
||||||
|
Implies that you don't mix up your tabs and spaces.
|
||||||
|
Copyright 2015 Bruno Dias
|
||||||
|
###
|
||||||
|
normaliseTabs = (text) ->
|
||||||
|
lines = text.split('\n');
|
||||||
|
indents = lines
|
||||||
|
.filter((l) => l != '')
|
||||||
|
.map((l) => l.match(/^\s+/))
|
||||||
|
.map((m) ->
|
||||||
|
if (m == null)
|
||||||
|
return ''
|
||||||
|
return m[0]
|
||||||
|
)
|
||||||
|
smallestIndent = indents.reduce((max, curr) ->
|
||||||
|
if (curr.length < max.length)
|
||||||
|
return curr
|
||||||
|
return max
|
||||||
|
)
|
||||||
|
return lines.map((l) ->
|
||||||
|
return l.replace(new RegExp('^' + smallestIndent), '')
|
||||||
|
).join('\n')
|
||||||
|
|
||||||
|
markdown = (text) ->
|
||||||
|
if typeof text is Function
|
||||||
|
text = text()
|
||||||
|
return marked(normaliseTabs(text), {
|
||||||
|
smartypants: true
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = markdown
|
33
lib/obj.coffee
Normal file
33
lib/obj.coffee
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
markdown = require('./markdown.coffee')
|
||||||
|
undum = require('undum-commonjs')
|
||||||
|
$ = require("jquery")
|
||||||
|
objlink = (content, ref) ->
|
||||||
|
return "<a href='./_act_#{ref}'>#{content}</a>"
|
||||||
|
|
||||||
|
class RaconteurObj
|
||||||
|
constructor: (spec) ->
|
||||||
|
for key, value of spec
|
||||||
|
this[key] ?= value
|
||||||
|
level: 0
|
||||||
|
look: (character, system, f) ->
|
||||||
|
if @dsc
|
||||||
|
text = markdown(@dsc.fcall(this, character, system, f))
|
||||||
|
text = "<span class='look lvl#{@level}'>" + text + "</span>"
|
||||||
|
window.name = @name
|
||||||
|
text = text.replace /([\s^])\{\{(\w+)\}\}([\s$])/g, (str, p1, p2, p3) ->
|
||||||
|
name = window.name
|
||||||
|
window.name = undefined
|
||||||
|
return p1+objlink(p2, name)+p3
|
||||||
|
return text
|
||||||
|
take: () -> "You take the #{@name}." # taking to inventory
|
||||||
|
act: () -> "You don't find anything extraordinary about the #{@name}." # object action
|
||||||
|
dsc: () -> "You see a {{#{@name}}} here." # object description
|
||||||
|
put: (room) ->
|
||||||
|
@level = 0 # this is scenery
|
||||||
|
undum.game.situations[room].objects[@name] = this
|
||||||
|
|
||||||
|
obj = (name, spec) ->
|
||||||
|
spec ?= {}
|
||||||
|
spec.name = name
|
||||||
|
return new RaconteurObj(spec)
|
||||||
|
module.exports = obj
|
170
lib/oneOf.coffee
Normal file
170
lib/oneOf.coffee
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
###
|
||||||
|
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)
|
||||||
|
|
||||||
|
module.exports = oneOf;
|
166
lib/room.coffee
Normal file
166
lib/room.coffee
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
# I confess that this world model heavily borrows from INSTEAD engine. - A.Y.
|
||||||
|
|
||||||
|
undum = require('undum-commonjs')
|
||||||
|
RaconteurSituation = require('./situation.coffee')
|
||||||
|
obj = require('./obj.coffee')
|
||||||
|
markdown = require('./markdown.coffee')
|
||||||
|
$ = require("jquery")
|
||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
})
|
||||||
|
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
|
||||||
|
return this
|
||||||
|
objects: []
|
||||||
|
distance: Infinity # distance to the destination
|
||||||
|
|
||||||
|
###
|
||||||
|
I call SaletRoom.exit every time the player exits to another room.
|
||||||
|
###
|
||||||
|
exit: (character, system, to) ->
|
||||||
|
###
|
||||||
|
Undum calls Situation.enter 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.
|
||||||
|
###
|
||||||
|
enter: (character, system, f) ->
|
||||||
|
#system.clearContent()
|
||||||
|
if f != @name and f?
|
||||||
|
@visited++
|
||||||
|
undum.game.situations[f].exit(character, system, @name)
|
||||||
|
|
||||||
|
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?
|
||||||
|
print(@before.fcall(this, character, system, f))
|
||||||
|
|
||||||
|
if @look
|
||||||
|
@look character, system, f
|
||||||
|
|
||||||
|
if f != @name and @after?
|
||||||
|
print(@after.fcall(this, character, system, f))
|
||||||
|
|
||||||
|
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) ->
|
||||||
|
# default Raconteur action
|
||||||
|
if (action.match(/^_(writer|replacer|inserter)_.+$/))
|
||||||
|
return RaconteurSituation.prototype.act.call(this, character, system, f)
|
||||||
|
|
||||||
|
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.")
|
||||||
|
|
||||||
|
# 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
|
101
lib/situation.coffee
Normal file
101
lib/situation.coffee
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
###
|
||||||
|
This file is built on top of Raconteur.
|
||||||
|
Raconteur is copyright (c) 2015 Bruno Dias
|
||||||
|
This file is copyright (c) 2016 Alexander Yakovlev
|
||||||
|
|
||||||
|
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.
|
||||||
|
###
|
||||||
|
|
||||||
|
undum = require('undum-commonjs')
|
||||||
|
markdown = require('./markdown.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(); when called on a String, it only returns the
|
||||||
|
string itself, discarding any input.
|
||||||
|
###
|
||||||
|
|
||||||
|
Function.prototype.fcall = Function.prototype.call;
|
||||||
|
String.prototype.fcall = () -> return this
|
||||||
|
|
||||||
|
#Adds the "fade" class to a htmlString.
|
||||||
|
String.prototype.fade = () ->
|
||||||
|
return this.classList.add("fade")
|
||||||
|
|
||||||
|
###
|
||||||
|
The prototype RaconteurSituation is the basic spec for situations
|
||||||
|
created with Raconteur. It should be able to handle any use case for Undum.
|
||||||
|
This prototype is fairly complex; see the API documentation.
|
||||||
|
###
|
||||||
|
|
||||||
|
RaconteurSituation = (spec) ->
|
||||||
|
if RaconteurSituation.arguments.length == 0
|
||||||
|
return
|
||||||
|
undum.Situation.call(this, spec)
|
||||||
|
|
||||||
|
for key, value of spec
|
||||||
|
this[key] ?= value
|
||||||
|
|
||||||
|
@visited = 0
|
||||||
|
return this
|
||||||
|
RaconteurSituation.inherits(undum.Situation)
|
||||||
|
|
||||||
|
###
|
||||||
|
Situation.prototype.act() is called by Undum whenever an action link
|
||||||
|
(Ie, a link that doesn't point at another situation or an external URL) is
|
||||||
|
clicked.
|
||||||
|
|
||||||
|
Raconteur's version of act() is set up to implement commonly used
|
||||||
|
functionality: "writer" links, "replacer" links, "inserter" links, and
|
||||||
|
generic "action" links that call functions which access the underlying
|
||||||
|
Undum API.
|
||||||
|
###
|
||||||
|
|
||||||
|
RaconteurSituation.prototype.act = (character, system, action) ->
|
||||||
|
actionClass = action.match(/^_(\w+)_(.+)$/)
|
||||||
|
that = this
|
||||||
|
|
||||||
|
responses = {
|
||||||
|
writer: (ref) ->
|
||||||
|
content = @writers[ref].fcall(that, character, system, action)
|
||||||
|
output = markdown(content).fade()
|
||||||
|
system.writeInto(output, '#current-situation')
|
||||||
|
replacer: (ref) ->
|
||||||
|
content = @writers[ref].fcall(that, character, system, action)
|
||||||
|
output = markdown(content).fade()
|
||||||
|
system.replaceWith(output, '#'+ref)
|
||||||
|
inserter: (ref) ->
|
||||||
|
content = @writers[ref].fcall(that, character, system, action)
|
||||||
|
output = markdown(content).fade()
|
||||||
|
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}");
|
||||||
|
|
||||||
|
RaconteurSituation.prototype.register = () ->
|
||||||
|
if not @name?
|
||||||
|
console.error("Situation has no name")
|
||||||
|
return this
|
||||||
|
undum.game.situations[@name] = this
|
||||||
|
return this
|
||||||
|
|
||||||
|
module.exports = RaconteurSituation
|
16
package.json
16
package.json
|
@ -1,15 +1,25 @@
|
||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"undum-commonjs": "git://github.com/oreolek/undum-commonjs#commonjs",
|
||||||
|
"jquery": "^2.1.3"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"babelify": "^6.0.2",
|
||||||
|
"browser-sync": "^2.6.0",
|
||||||
|
"browserify": "^9.0.8",
|
||||||
|
"browserify-shim": "^3.8.8",
|
||||||
|
"coffeeify": "^1.0.0",
|
||||||
"gulp": "^3.8.11",
|
"gulp": "^3.8.11",
|
||||||
"gulp-uglify": "^1.2.0",
|
"gulp-uglify": "^1.2.0",
|
||||||
"gulp-sass": "^2.1.1",
|
|
||||||
"gulp-coffee": "^2.3.1",
|
"gulp-coffee": "^2.3.1",
|
||||||
|
"gulp-util": "^3.0.4",
|
||||||
"gulp-zip": "^3.0.2",
|
"gulp-zip": "^3.0.2",
|
||||||
"gulp-shell": "^0.5.1",
|
|
||||||
"gulp-concat": "^2.6.0",
|
"gulp-concat": "^2.6.0",
|
||||||
"browser-sync": "^2.11.0"
|
"gulp-sass": "^2.1.1",
|
||||||
|
"lodash": "^3.6.0",
|
||||||
|
"vinyl-buffer": "^1.0.0",
|
||||||
|
"vinyl-source-stream": "^1.1.0",
|
||||||
|
"watchify": "^3.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ $body-bg: #F1EED9;
|
||||||
$body-color: #58351A;
|
$body-color: #58351A;
|
||||||
$link-color: #382313;
|
$link-color: #382313;
|
||||||
$btn-bg: #C33601;
|
$btn-bg: #C33601;
|
||||||
$btn-color: $body-color;
|
$btn-color: lighten($btn-bg, 50%);
|
||||||
$secondary-bg: #F1EED9;
|
$secondary-bg: #F1EED9;
|
||||||
|
|
||||||
$waycolor: $link-color;
|
$waycolor: $link-color;
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 08031d6a76337e9e6d0c7aa3d034b8006e26e705
|
Subproject commit 643bd8eaeb7a2a692fec3add22a3b61eff0fb62c
|
|
@ -9,7 +9,11 @@
|
||||||
@import "bootstrap/scss/type";
|
@import "bootstrap/scss/type";
|
||||||
@import "bootstrap/scss/images";
|
@import "bootstrap/scss/images";
|
||||||
@import "bootstrap/scss/grid";
|
@import "bootstrap/scss/grid";
|
||||||
@import "bootstrap/scss/buttons";
|
//@import "bootstrap/scss/buttons";
|
||||||
|
|
||||||
|
@import "bootstrap/scss/animation";
|
||||||
|
@import "bootstrap/scss/dropdown";
|
||||||
|
@import "bootstrap/scss/nav";
|
||||||
@import "bootstrap/scss/responsive-embed";
|
@import "bootstrap/scss/responsive-embed";
|
||||||
@import "bootstrap/scss/utilities";
|
@import "bootstrap/scss/utilities";
|
||||||
|
|
||||||
|
@ -18,22 +22,67 @@ body {
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
background: $body-bg;
|
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);
|
||||||
|
}
|
||||||
|
cursor: pointer; // Until we click to start.
|
||||||
|
.label {
|
||||||
|
overflow: hidden;
|
||||||
|
margin: auto;
|
||||||
|
max-width: 18em;
|
||||||
|
position: relative;
|
||||||
|
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 {
|
#tools_wrapper {
|
||||||
display: none; // Shown by Javascript
|
|
||||||
.ways {
|
.ways {
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
// @include col(4, 5);
|
@include col(8, 9);
|
||||||
@include col(9, 10);
|
|
||||||
@media (min-width: breakpoint-min(sm)) {
|
@media (min-width: breakpoint-min(sm)) {
|
||||||
@include make-col-offset(1);
|
@include make-col-offset(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.buttons {
|
.destination {
|
||||||
@include col(1, 2);
|
font-weight: bold;
|
||||||
button {
|
}
|
||||||
@extend .btn;
|
.menu {
|
||||||
@include button-variant($btn-color, $btn-bg, $btn-color);
|
@include col(3, 4);
|
||||||
margin-bottom: 1em;
|
span {
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,7 +90,7 @@ body {
|
||||||
background: $text_background;
|
background: $text_background;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
#content {
|
.content {
|
||||||
@include col(10, 12);
|
@include col(10, 12);
|
||||||
@media (min-width: breakpoint-min(sm)) {
|
@media (min-width: breakpoint-min(sm)) {
|
||||||
@include make-col-offset(1);
|
@include make-col-offset(1);
|
||||||
|
@ -97,7 +146,6 @@ body {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
color: darken($body-color, 10%);
|
color: darken($body-color, 10%);
|
||||||
font-size: smaller;
|
font-size: smaller;
|
||||||
display: none; // Shown by Javascript
|
|
||||||
#footleft {
|
#footleft {
|
||||||
@include make-col();
|
@include make-col();
|
||||||
@media (min-width: breakpoint-min(sm)) {
|
@media (min-width: breakpoint-min(sm)) {
|
||||||
|
@ -120,6 +168,11 @@ body {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#content_library,
|
||||||
|
#ui_library {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.way {
|
.way {
|
||||||
color: $waycolor;
|
color: $waycolor;
|
||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
|
@ -128,6 +181,20 @@ body {
|
||||||
color: darkgreen;
|
color: darkgreen;
|
||||||
border-bottom: darkgreen dashed 1px;
|
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 {
|
hr {
|
||||||
width: 50%;
|
width: 50%;
|
||||||
border-color: $body-color;
|
border-color: $body-color;
|
||||||
|
|
Loading…
Reference in a new issue