mirror of
https://gitlab.com/Oreolek/black_phone.git
synced 2024-04-25 21:59:42 +03:00
Salet conversion WIP
This commit is contained in:
parent
0d2f825662
commit
f497b78e27
131
Gulpfile.coffee
Normal file
131
Gulpfile.coffee
Normal file
|
@ -0,0 +1,131 @@
|
|||
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')
|
||||
_ = require('lodash')
|
||||
concat = require('gulp-concat')
|
||||
|
||||
reload = browserSync.reload
|
||||
|
||||
html = (target) ->
|
||||
return () ->
|
||||
return gulp.src(['html/index.html','html/en.html'])
|
||||
.pipe(gulp.dest(target));
|
||||
|
||||
img = (target) ->
|
||||
return () ->
|
||||
return gulp.src(['img/*.png', 'img/*.jpeg', 'img/*.jpg'])
|
||||
.pipe(gulp.dest(target));
|
||||
|
||||
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'));
|
||||
)
|
||||
|
||||
sources = [
|
||||
'./game/begin.coffee',
|
||||
'./game/story.coffee',
|
||||
'./game/end.coffee',
|
||||
]
|
||||
|
||||
opts = _.assign({}, watchify.args, {
|
||||
entries: ["./build/game/main.coffee"]
|
||||
debug: true
|
||||
transform: [coffeify]
|
||||
});
|
||||
bundler = watchify(browserify(opts))
|
||||
|
||||
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(sources)
|
||||
.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(['./lib/*.coffee', './lib/*.js', './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('.'));
|
||||
);
|
184
Gulpfile.js
184
Gulpfile.js
|
@ -1,184 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
/* Imports */
|
||||
var 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'),
|
||||
sass = require('gulp-sass'),
|
||||
minifyCSS = require('gulp-minify-css'),
|
||||
uglify = require('gulp-uglify'),
|
||||
buffer = require('vinyl-buffer'),
|
||||
zip = require('gulp-zip'),
|
||||
_ = require('lodash'),
|
||||
concat = require('gulp-concat');
|
||||
|
||||
var reload = browserSync.reload;
|
||||
|
||||
/* Tasks */
|
||||
|
||||
/* Trivial file copies */
|
||||
|
||||
function html (target) {
|
||||
return function () {
|
||||
return gulp.src(['html/index.html','html/en.html'])
|
||||
.pipe(gulp.dest(target));
|
||||
};
|
||||
}
|
||||
|
||||
function img (target) {
|
||||
return function () {
|
||||
return gulp.src(['img/*.png', 'img/*.jpeg', 'img/*.jpg'])
|
||||
.pipe(gulp.dest(target));
|
||||
};
|
||||
}
|
||||
|
||||
function audio (target) {
|
||||
return function () {
|
||||
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'));
|
||||
|
||||
/* Less */
|
||||
|
||||
gulp.task('sass', function () {
|
||||
gulp.src('sass/main.scss')
|
||||
.pipe(sass().on('error', sass.logError))
|
||||
.pipe(gulp.dest('./build/css'));
|
||||
});
|
||||
|
||||
/* Bundle libraries */
|
||||
|
||||
var undumBundler = browserify({debug: true});
|
||||
undumBundler.require('undum-commonjs');
|
||||
|
||||
gulp.task('buildUndum', function () {
|
||||
return undumBundler.bundle().pipe(source('undum.js')).pipe(gulp.dest('./build/game'));
|
||||
});
|
||||
|
||||
/* Generate JavaScript with browser sync. */
|
||||
|
||||
var sources = [
|
||||
'./game/begin.coffee',
|
||||
'./game/story.coffee',
|
||||
'./game/end.coffee',
|
||||
]
|
||||
|
||||
var opts = _.assign({}, watchify.args, {
|
||||
entries: ["./build/game/main.coffee"],
|
||||
debug: true,
|
||||
transform: [coffeify]
|
||||
});
|
||||
var bundler = watchify(browserify(opts));
|
||||
bundler.external('undum-commonjs');
|
||||
|
||||
function 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() {
|
||||
return gulp.src(sources)
|
||||
.pipe(concat('./main.coffee'))
|
||||
.pipe(gulp.dest('./build/game'));
|
||||
});
|
||||
|
||||
// `gulp coffee` will generate bundle
|
||||
gulp.task('coffee', ['buildUndum', 'concatCoffee'], bundle);
|
||||
|
||||
bundler.on('update', bundle); // Re-bundle on dep updates
|
||||
bundler.on('log', gutil.log); // Output build logs to terminal
|
||||
|
||||
/* Make a development build */
|
||||
|
||||
gulp.task('build', ['html', 'img', 'sass', 'coffee', 'audio'], function () {
|
||||
|
||||
});
|
||||
|
||||
/* Start a development server */
|
||||
|
||||
gulp.task('serve', ['build'], function () {
|
||||
browserSync({
|
||||
server: {
|
||||
baseDir: 'build'
|
||||
}
|
||||
});
|
||||
|
||||
var sassListener = function () {
|
||||
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);
|
||||
});
|
||||
|
||||
/* 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('img-dist', img('./dist/img'));
|
||||
gulp.task('audio-dist', audio('./dist/audio'));
|
||||
gulp.task('legal-dist', function () {
|
||||
return gulp.src(['LICENSE.txt'])
|
||||
.pipe(gulp.dest("./dist"));
|
||||
});
|
||||
|
||||
gulp.task('sass-dist', function () {
|
||||
return gulp.src('./sass/main.scss')
|
||||
.pipe(sass({outputStyle: 'compressed'}))
|
||||
.pipe(gulp.dest('./dist/css'));
|
||||
});
|
||||
|
||||
var distBundler = browserify({
|
||||
debug: false,
|
||||
entries: ['./build/game/main.coffee'],
|
||||
transform: ['coffeeify']
|
||||
});
|
||||
|
||||
distBundler.external('undum-commonjs');
|
||||
|
||||
gulp.task('coffee-dist', ['undum-dist', 'concatCoffee'], function () {
|
||||
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'],
|
||||
function () {
|
||||
return;
|
||||
});
|
||||
|
||||
gulp.task('zip', ['dist'], function () {
|
||||
return gulp.src('dist/**')
|
||||
.pipe(zip('dist.zip'))
|
||||
.pipe(gulp.dest('.'));
|
||||
});
|
|
@ -1,4 +1,6 @@
|
|||
Copyright (c) 2015-2016 Alexander Yakovlev, https://oreolek.ru/
|
||||
Copyright (c) 2016 Alexander Yakovlev, https://oreolek.ru/
|
||||
Raconteur is copyright (c) 2015 Bruno Dias, released under the similar license terms.
|
||||
Undum is copyright (c) 2009-2015 Ian Millington, released under the similar license terms.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
|
|
|
@ -2,54 +2,32 @@
|
|||
# This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
# To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0
|
||||
|
||||
situation = require('raconteur')
|
||||
undum = require('undum-commonjs')
|
||||
oneOf = require('raconteur/lib/oneOf.js')
|
||||
qualities = require('raconteur/lib/qualities.js')
|
||||
$ = require("jquery")
|
||||
|
||||
Array.prototype.oneOf = () ->
|
||||
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.
|
||||
markdown = require('../../lib/markdown.coffee')
|
||||
room = require("../../lib/room.coffee")
|
||||
obj = require('../../lib/obj.coffee')
|
||||
dialogue = require('../../lib/dialogue.coffee')
|
||||
oneOf = require('../../lib/oneOf.coffee')
|
||||
require('../../lib/interface.coffee')
|
||||
undum = require('../../lib/undum.js')
|
||||
|
||||
undum.game.id = "6a9909a4-586a-4089-bd18-26da684d1c8d"
|
||||
undum.game.version = "1.0"
|
||||
undum.game.version = "2.0"
|
||||
|
||||
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")
|
||||
|
||||
a = require('raconteur/lib/elements.js').a
|
||||
way_to = (content, ref) -> a(content).class('way').ref(ref)
|
||||
textlink = (content, ref) -> a(content).once().writer(ref)
|
||||
actlink = (content, ref) -> a(content).once().action(ref)
|
||||
textcycle = (content, ref) -> a(content).replacer(ref).class("cycle").id(ref).toString()
|
||||
# usage: writemd( system, "Text to write")
|
||||
writemd = (system, text) ->
|
||||
if typeof text is Function
|
||||
text = text()
|
||||
text = markdown.render(text)
|
||||
text = markdown(text)
|
||||
system.write(text)
|
||||
|
||||
preparemd = (text, mark) ->
|
||||
if typeof text is Function
|
||||
text = text()
|
||||
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)
|
||||
money = (character, amount) ->
|
||||
character.sandbox.money = character.sandbox.money + amount
|
||||
|
||||
code_can_input = (character) ->
|
||||
return character.sandbox.code.length < 8
|
||||
|
@ -108,40 +86,12 @@ code_check = (character, system) ->
|
|||
|
||||
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: """
|
||||
Peter had so much trouble sleeping he had to drown his pills in at least an hour of thoughts.
|
||||
|
||||
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 Anastasia Kozlowa when she fled the country.
|
||||
Of course, one had to be reasonably au fait with her *Instagram* to notice that.
|
||||
|
||||
room "start",
|
||||
dsc: """
|
||||
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>
|
||||
He began the inspection from #{way_to('the living room.', 'living-room')}
|
||||
"""
|
||||
|
||||
is_visited = (situation) ->
|
||||
|
@ -153,7 +103,7 @@ is_visited = (situation) ->
|
|||
# N-th level examine function
|
||||
level = (text, mark) ->
|
||||
$("#content .#{mark}").fadeOut()
|
||||
return preparemd(text, mark)
|
||||
return markdown(text, mark)
|
||||
|
||||
lvl1 = (text) ->
|
||||
$("#content .lvl2").fadeOut()
|
||||
|
|
|
@ -1,24 +1,8 @@
|
|||
qualities
|
||||
general:
|
||||
money: qualities.integer('Money'),
|
||||
|
||||
undum.game.init = (character, system) ->
|
||||
$("#ways").on("click", "a", (event) ->
|
||||
event.preventDefault()
|
||||
undum.processClick($(this).attr("href"))
|
||||
)
|
||||
_paq.push(['setCustomDimension', 1, false])
|
||||
# If you use only once() links you can use this "hack":
|
||||
document.onmousedown = (e) ->
|
||||
e.target.click()
|
||||
# It makes every click slightly faster because the game responds after the user presses the mouse button,
|
||||
# not after he presses and releases it.
|
||||
#
|
||||
# Another thing to bear in mind: this game is not a typical Undum game.
|
||||
# It deliberately repeats the situations, so you can repeat "once" links once you re-enter the situation.
|
||||
# So this game has no need in repeatable links at all and this hack is useful.
|
||||
|
||||
character.sandbox.view_smash = 1
|
||||
character.sandbox.money = 0
|
||||
character.sandbox.code = ""
|
||||
character.sandbox.knows_the_code = 0
|
||||
character.sandbox.box_opened = 0
|
||||
|
|
|
@ -1,44 +1,40 @@
|
|||
situation "living-room",
|
||||
room "living-room",
|
||||
title: "Living room"
|
||||
before: () ->
|
||||
if not $(".ways h2").is(':visible')
|
||||
$(".ways h2").fadeIn()
|
||||
update_ways(this.ways)
|
||||
audio = document.getElementById("bgsound")
|
||||
audio.currentTime=0
|
||||
audio.volume = 0.5
|
||||
audio.play()
|
||||
enter: (character, system, from) ->
|
||||
if (from == "start")
|
||||
audio = document.getElementById("bgsound")
|
||||
audio.currentTime=0
|
||||
audio.volume = 0.5
|
||||
audio.play()
|
||||
ways: ["bedroom", "kitchen", "balcony"]
|
||||
content: """
|
||||
Ronald is standing in a dark room with a big #{textlink("window.", "window")}
|
||||
|
||||
#{textlink("The walls", "walls")} are covered with a dingy rose wallpaper.
|
||||
|
||||
dsc: """
|
||||
#{textlink("A book stand", "bookcase")} is hanging above #{textlink("a television set.", "tv")}
|
||||
|
||||
Oh, and #{actlink("the door Ronald came into", "door")} the apartment is there, too.
|
||||
"""
|
||||
actions:
|
||||
door: (character, system) ->
|
||||
if character.sandbox.box_opened == 0
|
||||
writemd(system, lvl1("Ronald has a job here. It's still on."))
|
||||
else
|
||||
system.doLink("exitdoor")
|
||||
writers:
|
||||
walls: (character, system) ->
|
||||
lvl1("""
|
||||
objects:
|
||||
window: obj "window",
|
||||
act: """
|
||||
The moon is full today.
|
||||
It illuminates the apartment, makes the things stand out in some weird angles.
|
||||
"""
|
||||
dsc: "Ronald is standing in a dark room with a big {{window}}"
|
||||
walls: obj "walls",
|
||||
dsc: "{{The walls}} are covered with a dingy rose wallpaper."
|
||||
act: """
|
||||
There are colorful photographs on the walls.
|
||||
A wooden house in a forest.
|
||||
A village on a mountaintop.
|
||||
A family sitting around a fire.
|
||||
A sunset burning in a vast ocean.
|
||||
A black monolith standing on sand dunes.
|
||||
""")
|
||||
window: (character, system) ->
|
||||
lvl1("""
|
||||
The moon is full today.
|
||||
It illuminates the apartment, makes the things stand out in some weird angles.
|
||||
""")
|
||||
"""
|
||||
door: obj "door",
|
||||
dsc: "Oh, and {{the door Ronald came into}} the apartment is there, too."
|
||||
act: (character, system) ->
|
||||
if character.sandbox.box_opened == 0
|
||||
writemd(system, lvl1("Ronald has a job here. It's still on."))
|
||||
else
|
||||
system.doLink("exitdoor")
|
||||
writers:
|
||||
bookcase: (character, system) ->
|
||||
lvl1("""
|
||||
Either Anastasia has a very conflicting taste in books, or she has no taste at all. Let's see...
|
||||
|
@ -88,7 +84,7 @@ situation "living-room",
|
|||
No need to read it, not a bit.
|
||||
""")
|
||||
else
|
||||
money(character, system, 20000)
|
||||
money(character, 20000)
|
||||
lvl2("""
|
||||
Nietsche's four-part novel about The Man, The Superman and everything in-between.
|
||||
It's surprisingly worn down.
|
||||
|
@ -117,12 +113,10 @@ situation "living-room",
|
|||
An expensive 40-something inch TV standing on a stylish black stand. The room looks kinda small for that monster.
|
||||
""")
|
||||
|
||||
situation "bedroom",
|
||||
before: () ->
|
||||
update_ways(this.ways)
|
||||
room "bedroom",
|
||||
title: "Bedroom"
|
||||
ways: ["living-room", "kitchen", "bathroom"]
|
||||
content: (character, system) ->
|
||||
dsc: (character, system) ->
|
||||
return """
|
||||
The bedroom is spacious; its walls are lavender green, almost white in the moonlight.
|
||||
|
||||
|
@ -188,7 +182,7 @@ situation "bedroom",
|
|||
""")
|
||||
else
|
||||
character.sandbox.seen_coat = 1
|
||||
money(character, system, 4000)
|
||||
money(character, 4000)
|
||||
return lvl2("""
|
||||
A warm coat.. hey, what's this?
|
||||
One of the pockets is loaded with cash!
|
||||
|
@ -264,7 +258,7 @@ situation "bedroom",
|
|||
""")
|
||||
money: (character, system) ->
|
||||
character.sandbox.seen_safe = 1
|
||||
money(character, system, 50000)
|
||||
money(character, 50000)
|
||||
lvl4("""
|
||||
It's a big cash.
|
||||
Odd that she didn't take this when she left.
|
||||
|
@ -278,12 +272,10 @@ situation "bedroom",
|
|||
The sketch is signed: *"L. Y. - 2017"*
|
||||
""")
|
||||
|
||||
situation "kitchen",
|
||||
before: () ->
|
||||
update_ways(this.ways)
|
||||
room "kitchen",
|
||||
title: "Kitchen"
|
||||
ways: ["living-room", "bedroom"]
|
||||
content: """
|
||||
dsc: """
|
||||
The white, perfectly clean kitchen could be called spartan: #{textlink("a fridge,", "fridge")} a microwave and #{textlink("a big table", "table")} where one can eat whatever she "cooked" that way.
|
||||
"""
|
||||
writers:
|
||||
|
@ -318,22 +310,19 @@ situation "kitchen",
|
|||
He's sure it's recent (`24.03.2018`) and it's about something-something QUANTUM AUDIO.. armement?
|
||||
""")
|
||||
|
||||
situation "bathroom",
|
||||
room "bathroom",
|
||||
before: (character,system) ->
|
||||
writemd(system,"Ronald doesn't want to search the bathroom. It's too private a room to enter.")
|
||||
index = undum.game.situations["bedroom"].ways.indexOf("bathroom")
|
||||
undum.game.situations["bedroom"].ways.splice(index, 1)
|
||||
update_ways(undum.game.situations["bedroom"].ways)
|
||||
return false
|
||||
title: "Bathroom"
|
||||
ways: ["bedroom"]
|
||||
|
||||
situation "balcony",
|
||||
before: () ->
|
||||
update_ways(this.ways)
|
||||
room "balcony",
|
||||
title: "Balcony"
|
||||
ways: ["living-room"]
|
||||
content: """
|
||||
dsc: """
|
||||
A small glazed-in empty balcony.
|
||||
It's an amazing night.
|
||||
The whole town is lit by moonlight, standing perfectly still.
|
||||
|
@ -359,12 +348,10 @@ situation "balcony",
|
|||
*L. Y.*
|
||||
""")
|
||||
|
||||
situation "box",
|
||||
before: () ->
|
||||
update_ways(this.ways)
|
||||
room "box",
|
||||
ways: ["bedroom"]
|
||||
choices: "#box"
|
||||
content: (character, system) ->
|
||||
dsc: (character, system) ->
|
||||
return """
|
||||
It's a red wood, very expensive.
|
||||
And this box is locked with a digital code key.
|
||||
|
@ -379,8 +366,7 @@ situation "box",
|
|||
}
|
||||
"""
|
||||
|
||||
# no need to call update_ways, it's the same location
|
||||
situation "smash",
|
||||
room "smash",
|
||||
canView: (character) ->
|
||||
character.sandbox.view_smash == 1
|
||||
optionText: "Smash the box"
|
||||
|
@ -388,10 +374,10 @@ situation "smash",
|
|||
character.sandbox.view_smash = 0
|
||||
choices: "#box"
|
||||
tags: ["box"]
|
||||
content: "Ronald still needs the phone in this box. A very high-tech fragile phone. Smashing isn't an option."
|
||||
dsc: "Ronald still needs the phone in this box. A very high-tech fragile phone. Smashing isn't an option."
|
||||
|
||||
safe_button = (number) ->
|
||||
situation "put#{number}",
|
||||
room "put#{number}",
|
||||
choices: "#box"
|
||||
tags: ["box"]
|
||||
optionText: "Enter #{number}"
|
||||
|
@ -401,7 +387,7 @@ safe_button = (number) ->
|
|||
code_can_input(character)
|
||||
after: (character, system) ->
|
||||
code_check(character, system)
|
||||
content: (character) -> """
|
||||
dsc: (character) -> """
|
||||
Ronald presses button #{number}. The display is #{code_print(character)} now.
|
||||
"""
|
||||
|
||||
|
@ -410,22 +396,20 @@ safe_button(2)
|
|||
safe_button(7)
|
||||
safe_button(0)
|
||||
|
||||
situation "reset",
|
||||
room "reset",
|
||||
choices: "#box"
|
||||
tags: ["box"]
|
||||
optionText: "Reset the display"
|
||||
before: (character) ->
|
||||
code_reset(character)
|
||||
content: """
|
||||
dsc: """
|
||||
Ronald presses Backspace until the display is empty.
|
||||
"""
|
||||
|
||||
situation "exitdoor",
|
||||
before: () ->
|
||||
update_ways(this.ways)
|
||||
room "exitdoor",
|
||||
ways: ["living-room"]
|
||||
choices: "#door"
|
||||
content: """
|
||||
dsc: """
|
||||
Ronald is ready to go.
|
||||
Maybe he's satisfied with his explorations or just wants to finish this.
|
||||
But then a new problem arrives.
|
||||
|
@ -433,15 +417,14 @@ situation "exitdoor",
|
|||
Someone's shadow is under the doorframe.
|
||||
"""
|
||||
|
||||
situation "finale",
|
||||
room "finale",
|
||||
before: () ->
|
||||
_paq.push(['setCustomDimension', 1, true])
|
||||
$("#tools_wrapper").hide()
|
||||
update_ways(this.ways)
|
||||
optionText: "Use the Phone"
|
||||
tags: ["door"]
|
||||
ways: []
|
||||
content: (character, system) -> """
|
||||
dsc: (character, system) -> """
|
||||
"LOADING... 100%"
|
||||
|
||||
Ronald opens the door and presses his finger to the phone screen.
|
||||
|
@ -460,8 +443,8 @@ situation "finale",
|
|||
|
||||
“Well, that was a good night.”
|
||||
|
||||
#{if character.qualities.money > 0
|
||||
"The pocket is heavy with #{character.qualities.money} rubles and the phone."
|
||||
#{if character.sandbox.money > 0
|
||||
"The pocket is heavy with #{character.sandbox.money} rubles and the phone."
|
||||
else
|
||||
"The phone is heavy in the pocket."
|
||||
}
|
||||
|
|
|
@ -16,24 +16,37 @@
|
|||
<noscript>
|
||||
<p class="noscript_message">This game requires Javascript.</p>
|
||||
</noscript>
|
||||
<p class="click_message">click to begin</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="content_wrapper" class="row">
|
||||
<div id="content">
|
||||
<div id="intro" class="content">
|
||||
<section>
|
||||
<p>Peter had so much trouble sleeping he had to drown his pills in at least an hour of thoughts.</p>
|
||||
|
||||
<p>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!”</p>
|
||||
|
||||
<p>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.</p>
|
||||
|
||||
<p>Fourth floor, apartment 406.
|
||||
There, he tried two keys.
|
||||
The second of them fitted the lock.</p>
|
||||
|
||||
<p>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 Anastasia Kozlowa when she fled the country.
|
||||
Of course, one had to be reasonably au fait with her <em>Instagram</em> to notice that.</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 id="character_panel" class="tools">
|
||||
<div id="character">
|
||||
<div id="character_text">
|
||||
<div id="character_text_content"></div>
|
||||
</div>
|
||||
<!-- <div id="qualities"></div> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class='ways'>
|
||||
<h2>Other rooms</h2>
|
||||
<div id="ways"></div>
|
||||
|
@ -46,9 +59,8 @@
|
|||
<div class="row">
|
||||
<div id="legal">
|
||||
<div id="footleft">
|
||||
<p>The game was written by <b><a href="http://en.oreolek.ru/" target="_blank">Oreolek.</a></b></p>
|
||||
<p>The game was written by <a href="http://en.oreolek.ru/" target="_blank">Oreolek</a> using <a href="http://git.oreolek.ru/oreolek/salet" target="_blank">Salet.</a></p>
|
||||
<p>Approximate play time: five minutes.</p>
|
||||
<p>Written using <a href="http://undum.com" target="_blank">Undum</a> and <a href="http://sequitur.github.io/raconteur/" target="_blank">Raconteur</a>.</p>
|
||||
<p>Betatesting credit: <a href="https://verityvirtue.wordpress.com/">Verity Virtue</a></p>
|
||||
</div>
|
||||
<div id="footright">
|
||||
|
@ -75,7 +87,10 @@
|
|||
|
||||
<div id="content_library"></div>
|
||||
<audio id="bgsound" preload="auto"><source src="audio/bgr.mp3" type='audio/mpeg; codecs="mp3"'></audio>
|
||||
<script type="text/javascript" src="game/undum.js"></script>
|
||||
<!-- 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>
|
||||
<!-- Game code -->
|
||||
<script type="text/javascript" src="game/bundle.js"></script>
|
||||
<!-- Piwik -->
|
||||
<script type="text/javascript">
|
||||
|
|
19
lib/cycle.coffee
Normal file
19
lib/cycle.coffee
Normal file
|
@ -0,0 +1,19 @@
|
|||
# Cycling interface.
|
||||
# Rooms: cycle through this.cycle_gallery
|
||||
# Objects: cycle through this.cycle_gallery
|
||||
|
||||
cyclelink = (content) ->
|
||||
return "<a href='./_replacer_cyclewriter' class='cycle' id='cyclewriter'>#{content}</a>"
|
||||
|
||||
cycle = (responses, name, character) ->
|
||||
if typeof responses == "function"
|
||||
responses = responses()
|
||||
character.sandbox.cycle_index ?= [] # initialize with empty array
|
||||
character.sandbox.cycle_index[name] ?= 0 # initialize with 0
|
||||
response = responses[character.sandbox.cycle_index[name]]
|
||||
character.sandbox.cycle_index[name]++
|
||||
if character.sandbox.cycle_index[name] == responses.length
|
||||
character.sandbox.cycle_index[name] = 0
|
||||
return cyclelink(response)
|
||||
|
||||
module.exports = cycle
|
34
lib/dialogue.coffee
Normal file
34
lib/dialogue.coffee
Normal file
|
@ -0,0 +1,34 @@
|
|||
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
|
||||
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
|
16
lib/interface.coffee
Normal file
16
lib/interface.coffee
Normal file
|
@ -0,0 +1,16 @@
|
|||
###
|
||||
Salet interface configuration.
|
||||
###
|
||||
undum = require('./undum.js')
|
||||
$(document).ready(() ->
|
||||
$("#ways").on("click", "a", (event) ->
|
||||
event.preventDefault()
|
||||
undum.processClick($(this).attr("href"))
|
||||
)
|
||||
$("#inventory").on("click", "a", (event) ->
|
||||
event.preventDefault()
|
||||
)
|
||||
$("#load").on("click", "a", (event) ->
|
||||
window.location.reload()
|
||||
)
|
||||
)
|
60
lib/localize.coffee
Normal file
60
lib/localize.coffee
Normal file
|
@ -0,0 +1,60 @@
|
|||
# Internationalization support
|
||||
|
||||
languages = {}
|
||||
|
||||
# Default Messages
|
||||
|
||||
en = {
|
||||
terrible: "terrible",
|
||||
poor: "poor",
|
||||
mediocre: "mediocre",
|
||||
fair: "fair",
|
||||
good: "good",
|
||||
great: "great",
|
||||
superb: "superb",
|
||||
yes: "yes",
|
||||
no: "no",
|
||||
choice: "Choice {number}",
|
||||
no_group_definition: "Couldn't find a group definition for {id}.",
|
||||
link_not_valid: "The link '{link}' doesn't appear to be valid.",
|
||||
link_no_action: "A link with a situation of '.', must have an action.",
|
||||
unknown_situation: "You can't move to an unknown situation: {id}.",
|
||||
existing_situation: "You can't override situation {id} in HTML.",
|
||||
erase_message: "This will permanently delete this character and immediately return you to the start of the game. Are you sure?",
|
||||
no_current_situation: "I can't display, because we don't have a current situation.",
|
||||
no_local_storage: "No local storage available.",
|
||||
random_seed_error: "You must provide a valid random seed.",
|
||||
random_error: "Initialize the Random with a non-empty seed before use.",
|
||||
dice_string_error: "Couldn't interpret your dice string: '{string}'."
|
||||
}
|
||||
|
||||
# Set this data as both the default fallback language, and the english preferred language.
|
||||
languages[""] = en
|
||||
languages["en"] = en
|
||||
|
||||
languageCodes = Object.keys(languages)
|
||||
|
||||
localize = (languageCode, message) ->
|
||||
for thisCode in languageCodes
|
||||
localized = languages[languageCode][message]
|
||||
if localized
|
||||
return localized
|
||||
return message
|
||||
|
||||
# API
|
||||
String.prototype.l = (args) ->
|
||||
# Get lang attribute from html tag.
|
||||
lang = document.getElementsByTagName("html")[0].getAttribute("lang") || ""
|
||||
|
||||
# Find the localized form.
|
||||
localized = localize(lang, this)
|
||||
|
||||
# Merge in any replacement content.
|
||||
if args
|
||||
for name in args
|
||||
localized = localized.replace(
|
||||
new RegExp("\\{"+name+"\\}"), args[name]
|
||||
)
|
||||
return localized
|
||||
|
||||
module.exports = languages;
|
36
lib/markdown.coffee
Normal file
36
lib/markdown.coffee
Normal file
|
@ -0,0 +1,36 @@
|
|||
###
|
||||
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) ->
|
||||
unless text?
|
||||
return ""
|
||||
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) ->
|
||||
unless text?
|
||||
return ""
|
||||
if typeof text is Function
|
||||
text = text()
|
||||
return marked(normaliseTabs(text), {
|
||||
smartypants: true
|
||||
})
|
||||
|
||||
module.exports = markdown
|
50
lib/obj.coffee
Normal file
50
lib/obj.coffee
Normal file
|
@ -0,0 +1,50 @@
|
|||
markdown = require('./markdown.coffee')
|
||||
undum = require('./undum.js')
|
||||
objlink = (content, ref) ->
|
||||
return "<a href='./_act_#{ref}' class='once'>#{content}</a>"
|
||||
|
||||
Array::remove = (e) -> @[t..t] = [] if (t = @indexOf(e)) > -1
|
||||
|
||||
parsedsc = (text, name) ->
|
||||
window.objname = name
|
||||
text = text.replace /\{\{(.+)\}\}/g, (str, p1) ->
|
||||
name = window.objname
|
||||
window.objname = undefined
|
||||
return objlink(p1, name)
|
||||
return text
|
||||
|
||||
# An object class.
|
||||
# An object cannot be in several locations at once, you must clone the variable.
|
||||
class SaletObj
|
||||
constructor: (spec) ->
|
||||
unless spec.name?
|
||||
console.error("Trying to create an object with no name")
|
||||
return null
|
||||
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>"
|
||||
# replace braces {{}} with link to _act_
|
||||
return parsedsc(text, @name)
|
||||
takeable: false
|
||||
take: (character, system) => "You take the #{@name}." # taking to inventory
|
||||
act: (character, system) => "You don't find anything extraordinary about the #{@name}." # object action
|
||||
dsc: (character, system) => "You see a {{#{@name}}} here." # object description
|
||||
inv: (character, system) => "It's a {{#{@name}.}}" # inventory description
|
||||
location: ""
|
||||
put: (location) =>
|
||||
@level = 0 # this is scenery
|
||||
if undum.game.situations[location]?
|
||||
undum.game.situations[location].take(this)
|
||||
@location = location
|
||||
delete: () =>
|
||||
undum.game.situations[@location].objects.remove(this)
|
||||
|
||||
obj = (name, spec) ->
|
||||
spec ?= {}
|
||||
spec.name = name
|
||||
return new SaletObj(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;
|
207
lib/random.js
Normal file
207
lib/random.js
Normal file
|
@ -0,0 +1,207 @@
|
|||
// 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;
|
277
lib/room.coffee
Normal file
277
lib/room.coffee
Normal file
|
@ -0,0 +1,277 @@
|
|||
# I confess that this world model heavily borrows from INSTEAD engine. - A.Y.
|
||||
|
||||
undum = require('./undum.js')
|
||||
obj = require('./obj.coffee')
|
||||
markdown = require('./markdown.coffee')
|
||||
cycle = require('./cycle.coffee')
|
||||
|
||||
way_to = (content, ref) ->
|
||||
return "<a href='#{ref}' class='way' id='waylink-#{ref}'>#{content}</a>"
|
||||
|
||||
# jQuery was confused by this point where's the context so I did it vanilla-way
|
||||
print = (content) ->
|
||||
if content == ""
|
||||
return
|
||||
if typeof content == "function"
|
||||
content = content()
|
||||
block = document.getElementById("current-situation")
|
||||
if block
|
||||
block.innerHTML = block.innerHTML + markdown(content)
|
||||
else
|
||||
console.error("No current situation found.")
|
||||
|
||||
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 """
|
||||
<video src="#{picture}" controls>
|
||||
Your browser does not support the video tag for some reason.
|
||||
You won't be able to view this video in this browser.
|
||||
</video>
|
||||
"""
|
||||
return "<img class='img-responsive' src='#{picture}' alt='Room illustration'>"
|
||||
|
||||
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
|
||||
|
||||
dsc: false # room description
|
||||
extendSection: false
|
||||
distance: Infinity # distance to the destination
|
||||
clear: true # clear the screen on entering the room?
|
||||
|
||||
###
|
||||
I call SaletRoom.exit every time the player exits to another room.
|
||||
Unlike @after this gets called after the section is closed.
|
||||
It's a styling difference.
|
||||
###
|
||||
exit: (character, system, to) =>
|
||||
return true
|
||||
|
||||
###
|
||||
I call SaletRoom.enter every time the player enters this room but before the section is opened.
|
||||
Unlike @before this gets called before the current section is opened.
|
||||
It's a styling difference.
|
||||
|
||||
The upstream Undum version does not allow you to redefine @enter function easily but allows custom @exit one.
|
||||
It was renamed as @entering to achieve API consistency.
|
||||
###
|
||||
enter: (character, system, from) =>
|
||||
return true
|
||||
|
||||
###
|
||||
Salet's Undum version calls Situation.entering every time a situation is entered, and
|
||||
passes it three arguments; The character object, the system object,
|
||||
and a string referencing the previous situation, or null if there is
|
||||
none (ie, for the starting situation).
|
||||
|
||||
My version of `enter` splits the location description from the effects.
|
||||
Also if f == this.name (we're in the same location) the `before` and `after` callbacks are ignored.
|
||||
###
|
||||
entering: (character, system, f) =>
|
||||
if @clear 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 = "<section id='current-situation' data-situation='#{@name}' class='situation-#{@name}#{classes}'>"
|
||||
|
||||
if f != @name and @before?
|
||||
current_situation += markdown(@before.fcall(this, character, system, f))
|
||||
|
||||
current_situation += @look character, system, f
|
||||
|
||||
if f != @name and @after?
|
||||
current_situation += markdown(@after.fcall(this, character, system, f))
|
||||
|
||||
if not @extendSection
|
||||
current_situation += "</section>"
|
||||
|
||||
system.write(current_situation)
|
||||
|
||||
if @choices
|
||||
system.writeChoices(system.getSituationIdChoices(@choices, @minChoices, @maxChoices))
|
||||
|
||||
###
|
||||
An internal function to get the room's description and the descriptions of
|
||||
every object in this room.
|
||||
###
|
||||
look: (character, system, f) =>
|
||||
update_ways(@ways, @name)
|
||||
retval = ""
|
||||
|
||||
if @pic
|
||||
retval += '<div class="pic">'+picture_tag(@pic.fcall(this, character, system, f))+'</div>'
|
||||
|
||||
# Print the room description
|
||||
if @dsc
|
||||
retval += markdown(@dsc.fcall(this, character, system, f))
|
||||
|
||||
for name, thing of @objects
|
||||
retval += thing.look()
|
||||
|
||||
return retval
|
||||
|
||||
###
|
||||
Puts an object in this room.
|
||||
###
|
||||
take: (thing) =>
|
||||
@objects[thing.name] = thing
|
||||
# BUG: for some really weird reason if the call is made in init function or
|
||||
# during the initialization, this ALSO puts the thing in the start room.
|
||||
undum.game.situations["start"].objects = {}
|
||||
|
||||
drop: (name) =>
|
||||
delete @objects[name]
|
||||
|
||||
###
|
||||
Object action. A function or a string which comes when you click on the object link.
|
||||
You could interpret this as an EXAMINE verb or USE one, it's your call.
|
||||
###
|
||||
act: (character, system, action) =>
|
||||
if (link = action.match(/^_(act|cycle)_(.+)$/)) #object action
|
||||
for name, thing of @objects
|
||||
if name == link[2]
|
||||
if link[1] == "act"
|
||||
# If it's takeable, the player can take this object.
|
||||
# If not, we check the "act" function.
|
||||
if thing.takeable
|
||||
character.sandbox.inventory.push thing
|
||||
@drop name
|
||||
cls(system)
|
||||
@entering.fcall(this, character, system, @name)
|
||||
return print(thing.take.fcall(thing, character, system))
|
||||
if thing.act
|
||||
return print(thing.act.fcall(thing, character, system))
|
||||
elseif link[1] == "cycle"
|
||||
# TODO object cyclewriter
|
||||
# the loop is done but no return came - match not found
|
||||
console.error("Could not find #{link[1]} in current room.")
|
||||
|
||||
# we're done with objects, now check the regular actions
|
||||
actionClass = action.match(/^_(\w+)_(.+)$/)
|
||||
that = this
|
||||
|
||||
responses = {
|
||||
writer: (ref) ->
|
||||
content = that.writers[ref].fcall(that, character, system, action)
|
||||
output = markdown(content)
|
||||
system.writeInto(output, '#current-situation')
|
||||
replacer: (ref) ->
|
||||
content = that.writers[ref].fcall(that, character, system, action)
|
||||
output = "<span>"+content+"</span>" # <p> 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
|
1617
lib/undum.js
Normal file
1617
lib/undum.js
Normal file
File diff suppressed because it is too large
Load diff
13
package.json
13
package.json
|
@ -1,9 +1,5 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"undum": "git://github.com/oreolek/undum-commonjs#commonjs",
|
||||
"raconteur": "git://github.com/sequitur/raconteur.git#stable",
|
||||
"jquery": "^2.1.3",
|
||||
"markdown-it": "^4.1.0"
|
||||
},
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
|
@ -13,16 +9,15 @@
|
|||
"browserify-shim": "^3.8.8",
|
||||
"coffeeify": "^1.0.0",
|
||||
"gulp": "^3.8.11",
|
||||
"gulp-gzip": "^1.1.0",
|
||||
"gulp-less": "^3.0.2",
|
||||
"gulp-minify-css": "^1.0.0",
|
||||
"gulp-uglify": "^1.2.0",
|
||||
"gulp-coffee": "^2.3.1",
|
||||
"gulp-util": "^3.0.4",
|
||||
"gulp-zip": "^3.0.2",
|
||||
"gulp-concat": "^2.6.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",
|
||||
"shortid": "^2.2.4"
|
||||
"watchify": "^3.1.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,13 +51,6 @@ body {
|
|||
margin-bottom: 1em;
|
||||
}
|
||||
}
|
||||
.click_message {
|
||||
display: none;
|
||||
font-size: 0.9em;
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
color: #987;
|
||||
}
|
||||
.noscript_message {
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
@ -70,24 +63,8 @@ body {
|
|||
}
|
||||
}
|
||||
#tools_wrapper {
|
||||
display: none; // Shown by Javascript
|
||||
/*
|
||||
.tools {
|
||||
background: $secondary-bg;
|
||||
border-radius: 5px;
|
||||
padding: 0.5em;
|
||||
@include col(4, 5);
|
||||
@media (min-width: breakpoint-min(sm)) {
|
||||
@include make-col-offset(1);
|
||||
}
|
||||
}
|
||||
*/
|
||||
.ways {
|
||||
h2 {
|
||||
display: none; // Shown by Javascript
|
||||
}
|
||||
padding: 0.5em;
|
||||
// @include col(4, 5);
|
||||
@include col(9, 10);
|
||||
@media (min-width: breakpoint-min(sm)) {
|
||||
@include make-col-offset(1);
|
||||
|
@ -103,11 +80,10 @@ body {
|
|||
}
|
||||
}
|
||||
#content_wrapper {
|
||||
display: none; // Shown by Javascript
|
||||
background: $text_background;
|
||||
border-radius: 5px;
|
||||
}
|
||||
#content {
|
||||
.content {
|
||||
@include col(10, 12);
|
||||
@media (min-width: breakpoint-min(sm)) {
|
||||
@include make-col-offset(1);
|
||||
|
@ -163,7 +139,6 @@ body {
|
|||
margin-top: 1em;
|
||||
color: darken($body-color, 10%);
|
||||
font-size: smaller;
|
||||
display: none; // Shown by Javascript
|
||||
#footleft {
|
||||
@include make-col();
|
||||
@media (min-width: breakpoint-min(sm)) {
|
||||
|
|
Loading…
Reference in a new issue