diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | LICENSE | 4 | ||||
-rw-r--r-- | unrpyc/LICENSE | 17 | ||||
-rw-r--r-- | unrpyc/Makefile | 16 | ||||
-rw-r--r-- | unrpyc/README | 15 | ||||
-rw-r--r-- | unrpyc/decompiler.py | 499 | ||||
-rwxr-xr-x | unrpyc/fix.js | 6 | ||||
-rw-r--r-- | unrpyc/unrpyc.py | 140 | ||||
-rw-r--r-- | www/js/api.js | 121 | ||||
-rw-r--r-- | www/js/html5ks.js | 39 | ||||
-rw-r--r-- | www/js/menu.js | 4 |
11 files changed, 790 insertions, 73 deletions
@@ -1,3 +1,5 @@ +/unrpyc/renpy # ignore 'copyrighted' contents similar to emulators +/www/scripts /www/dump /rpy @@ -1,4 +1,6 @@ Copyright © 2013 Alex Xu (Hello71) All Rights Reserved -I haven't decided on an open-source license; contact me to discuss. +I haven't decided on an open-source license; contact me [0] to discuss. + +[0] mailto:alex.hello71@gmail.com diff --git a/unrpyc/LICENSE b/unrpyc/LICENSE new file mode 100644 index 0000000..8a8ff9b --- /dev/null +++ b/unrpyc/LICENSE @@ -0,0 +1,17 @@ +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. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/unrpyc/Makefile b/unrpyc/Makefile new file mode 100644 index 0000000..86dcac3 --- /dev/null +++ b/unrpyc/Makefile @@ -0,0 +1,16 @@ +all: $(patsubst %.rpyc,%.json,$(wildcard *.rpyc)) + +%.json.o: %.rpyc *.py + python unrpyc.py --clobber $< $@ + +%.json: %.json.o + node fix.js $< $@ + +clean: + rm -f *.json.o *.json + +test: all + jshint --show-non-errors *.json + +.PHONY: all clean test +.PRECIOUS: %.json.o diff --git a/unrpyc/README b/unrpyc/README new file mode 100644 index 0000000..c046fe8 --- /dev/null +++ b/unrpyc/README @@ -0,0 +1,15 @@ +This is a !@#$ed up version of unrpyc to generate sort-of JSON from Ren'Py .rpyc files. + +It's made for Katawa Shoujo script files; trying to use it on anything else will probably break horribly. + +How to use: + +1. Copy Ren'Py distribution into "renpy" folder. +2. Execute "2to3 -w renpy/*.py". +3. Put the rpyc files from the official KS distribution in here. +4. Run "make" with appropriate -j options (yay auto-parallelization) +5. Copy the resulting .json files to /www/scripts. + +If you want to hack on the code, have fun. This is all the documentation you get. + +https://github.com/yuriks/unrpyc diff --git a/unrpyc/decompiler.py b/unrpyc/decompiler.py new file mode 100644 index 0000000..c6046f6 --- /dev/null +++ b/unrpyc/decompiler.py @@ -0,0 +1,499 @@ +# Copyright (c) 2012 Yuri K. Schlesner +# +# 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. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import ast as python_ast +import renpy.ast as ast +import renpy.atl as atl + +DECOMPILE_SCREENS = False +firstLabel = True +warnedATL = False + +def pretty_print_ast(out_file, ast): + out_file.write('{') + for stmt in ast: + print_statement(out_file, stmt, 0) + out_file.write(']}') + +def indent(f, level): + # Print indentation + f.write(' ' * level) + +def print_statement(f, statement, indent_level=0): + indent(f, indent_level) + + func = statement_printer_dict.get(type(statement), print_Unknown) + func(f, statement, indent_level) + +def escape_string(s): + s = s.replace('"', '\\"') + s = s.replace('\n', '\\n') + s = s.replace('\t', '\\t') + return s + +# TODO "choice" and "parallel" blocks are greedily combined +# so we need a "pass" statement to separate them if +# multiple of the same block are immediately after +# each other. +def print_atl(f, atl_block, indent_level): + if not warnedATL: + global warnedATL + warnedATL = True + print("ATL not yet implemented") + return + if not atl_block.statements: + indent(f, indent_level) + f.write('pass\n') + for stmt in atl_block.statements: + indent(f, indent_level) + + if type(stmt) is atl.RawMultipurpose: + # warper + if stmt.warp_function: + f.write("warp %s" % (stmt.warp_function.strip(), )) + f.write(" %s " % (stmt.duration.strip(), )) + elif stmt.warper: + f.write(stmt.warper) + f.write(" %s " % (stmt.duration.strip(), )) + elif stmt.duration.strip() != '0': + f.write('pause') + f.write(" %s" % (stmt.duration.strip(), )) + + # revolution + if stmt.revolution: + f.write("%s " % (stmt.revolution, )) + + # circles + if stmt.circles != "0": + f.write("circles %s " % (stmt.circles.strip(), )) + + # splines + for (name, exprs) in stmt.splines: + f.write("%s " % (name, )) + for expr in exprs: + f.write("knot %s " % (expr.strip(), )) + + # properties + for (k, v) in stmt.properties: + f.write("%s %s " % (k, v.strip())) + + # with + for (expr, with_expr) in stmt.expressions: + f.write("%s " % (expr.strip(), )) + if with_expr: + f.write("with %s " % (with_expr, )) + + f.write("\n") + + elif type(stmt) is atl.RawBlock: + # what does stmt.animation do? + f.write("block:\n") + print_atl(f, stmt, indent_level + 1) + + elif type(stmt) is atl.RawChoice: + first = True + for (chance, block) in stmt.choices: + if first: + first = False + else: + indent(f, indent_level) + + f.write("choice") + if chance != "1.0": + f.write(" %s" % (chance, )) + f.write(":\n") + print_atl(f, block, indent_level + 1) + + elif type(stmt) is atl.RawContainsExpr: + f.write("contains %s\n" % (stmt.expression, )) + + elif type(stmt) is atl.RawEvent: + f.write("event %s\n" % (stmt.name, )) + + elif type(stmt) is atl.RawFunction: + f.write("function %s\n" % (stmt.expr, )) + + elif type(stmt) is atl.RawOn: + first = True + for name, block in list(stmt.handlers.items()): + if first: + first = False + else: + indent(f, indent_level) + + f.write("on %s:\n" % (name, )) + print_atl(f, block, indent_level + 1) + + elif type(stmt) is atl.RawParallel: + first = True + for block in stmt.blocks: + if first: + first = False + else: + indent(f, indent_level) + + f.write("parallel:\n") + print_atl(f, block, indent_level + 1) + + elif type(stmt) is atl.RawRepeat: + f.write("repeat") + if stmt.repeats: + f.write(" %s" % (stmt.repeats, )) # not sure if this is even a string + f.write("\n") + + elif type(stmt) is atl.RawTime: + f.write("time %s\n" % (stmt.time, )) + + else: + f.write("TODO atl.%s\n" % type(stmt).__name__) + +def print_imspec(f, imspec): + if imspec[1] is not None: # Expression + f.write('expression ') + f.write(escape_string(imspec[1])) + else: # Image name + delim = '' + for s in imspec[0]: + f.write(delim + escape_string(s)) + delim = '", "' + + # at + if len(imspec[3]) > 0: + f.write(' at ') + delim = '' + for s in imspec[3]: + f.write(delim + escape_string(s)) + delim = ', ' + + # as + if imspec[2] is not None: + f.write(" as %s" % (escape_string(imspec[2]), )) + + # behind + if len(imspec[6]) > 0: + f.write('", "behind", "') + delim = '' + for s in imspec[6]: + f.write(delim + escape_string(s)) + delim = ', ' + + f.write('"],') + +def print_Label(f, stmt, indent_level): + if firstLabel: + global firstLabel + firstLabel = False + else: + f.write("],\n") + f.write("\"%s\": [\n" % (stmt.name, )) + if stmt.parameters is not None: + print("Error: label params") + + for sub_stmt in stmt.block: + print_statement(f, sub_stmt, indent_level + 1) + +def print_Say(f, stmt, indent_level): + if stmt.who is not None: + f.write('["%s", ' % (escape_string(stmt.who), )) + else: + f.write("[") + f.write("\"%s\"]," % (escape_string(stmt.what), )) + if stmt.with_ is not None: + pass + #f.write(" with %s" % (stmt.with_, )) + f.write('\n') + +def print_Jump(f, stmt, indent_level): + f.write("jump ") + if stmt.expression: + # TODO expression + f.write("expression TODO") + else: + f.write(stmt.target) + f.write('\n') + +def print_Scene(f, stmt, indent_level): + f.write('["scene", "') + print_imspec(f, stmt.imspec) + + # with isn't handled here, but split in several statements + + f.write('\n') + if stmt.atl is not None: + print_atl(f, stmt.atl, indent_level+1) + +def print_With(f, stmt, indent_level): + f.seek(-3, 1) + f.write(', "%s"],\n' % (escape_string(stmt.expr), )) + +def print_Show(f, stmt, indent_level): + f.write('["show", "') + print_imspec(f, stmt.imspec) + + # with isn't handled here, but split in several statements + + if stmt.atl is not None: + f.write('\n') + print_atl(f, stmt.atl, indent_level+1) + else: + f.write('\n') + +def print_Hide(f, stmt, indent_level): + f.write('["hide", "') + print_imspec(f, stmt.imspec) + + # with isn't handled here, but split in several statements + + f.write('\n') + +class PrintRenPython(python_ast.NodeVisitor): + def __init__(self, f): + self.f = f + + def visit_Attribute(self, node): + return '"%s"' % node.attr + + def visit_Call(self, node): + self.f.write('[') + self.f.write(self.visit(node.func)) + self.f.write(', ') + self.f.write(', '.join(map(self.visit, node.args))) + self.f.write('],\n') + + def quote(self, string): + return '"%s"' % string + + def visit_Dict(self, node): + return self.quote(python_ast.dump(node)) + + def visit_Num(self, node): + return str(node.n) + + def visit_Name(self, node): + return self.quote(node.id) + + def visit_Str(self, node): + return self.quote(escape_string(node.s)) + +def print_Python(f, stmt, indent_level, early=False): + code_src = stmt.code.source + + stripped_code = code_src.strip() + + if stripped_code.count('\n') == 0: + stmt = compile(code_src, '<unknown>', 'exec', python_ast.PyCF_ONLY_AST).body[0] + PrintRenPython(f).visit(stmt) + else: + f.write("python") + if early: + f.write(" early") + if stmt.hide: + f.write(" hide") + f.write(":\n") + + for line in code_src.splitlines(True): + indent(f, indent_level + 1) + f.write(line) + +def print_Return(f, stmt, indent_level): + if stmt.expression is not None: + f.write(' "%s",' % (stmt.expression, )) + + f.write('\n') + +def print_UserStatement(f, stmt, indent_level): + f.write('["%s"],\n' % (escape_string(stmt.line).replace(' ', '", "'), )) + +def print_Init(f, stmt, indent_level): + f.write("init") + if stmt.priority != 0: + f.write(" %d" % (stmt.priority, )) + f.write(":\n") + for s in stmt.block: + print_statement(f, s, indent_level + 1) + +def print_Image(f, stmt, indent_level): + f.write("image %s" % (' '.join(stmt. imgname), )) + if stmt.code is not None: + f.write(" = %s\n" % (stmt.code.source, )) + else: + f.write("\n") + print_atl(f, stmt.atl, indent_level + 1) + +def print_Transform(f, stmt, indent_level): + f.write("transform %s" % (stmt.varname, )) + if stmt.parameters is not None: + print_params(f, stmt.parameters) + + f.write("\n") + print_atl(f, stmt.atl, indent_level + 1) + +def print_Menu(f, stmt, indent_level): + f.write('["menu", ') + + first = True + + for item in stmt.items: + indent(f, indent_level + 1) + + if first and item[2] is not None: + first = False + f.write(' {\n') + + # caption + f.write("\"%s\"" % (escape_string(item[0]), )) + + if first and item[2] is None: + first = False + f.write(', {') + + if item[2] is not None: + f.write(':') + for inner_stmt in item[2]: + print_statement(f, inner_stmt, indent_level + 2) + + f.write('}]\n') + +def print_Pass(f, stmt, indent_level): + f.write("pass\n") + +def print_Call(f, stmt, indent_level): + f.write("call ") + if stmt.expression: + f.write("expression %s" % (stmt.label, )) + else: + f.write(stmt.label) + + if stmt.arguments is not None: + if stmt.expression: + f.write("pass ") + print_args(f, stmt.arguments) + + f.write('\n') + +def print_If(f, stmt, indent_level): + f.write("if %s:\n" % (stmt.entries[0][0], )) + for inner_stmt in stmt.entries[0][1]: + print_statement(f, inner_stmt, indent_level + 1) + + if len(stmt.entries) >= 2: + if stmt.entries[-1][0].strip() == 'True': + else_entry = stmt.entries[-1] + elif_entries = stmt.entries[1:-1] + else: + else_entry = None + elif_entries = stmt.entries + + for case in elif_entries: + indent(f, indent_level) + f.write("elif %s:\n" % (case[0], )) + for inner_stmt in case[1]: + print_statement(f, inner_stmt, indent_level + 1) + + if else_entry is not None: + indent(f, indent_level) + f.write("else:\n") + for inner_stmt in else_entry[1]: + print_statement(f, inner_stmt, indent_level + 1) + +def print_EarlyPython(f, stmt, indent_level): + print_Python(f, stmt, indent_level, early=True) + +# TODO extrapos, extrakw? +def print_args(f, arginfo): + if arginfo is None: + return + + f.write("(") + + first = True + for (name, val) in arginfo.arguments: + if first: + first = False + else: + f.write(", ") + + if name is not None: + f.write("%s = " % (name, )) + f.write(val) + + f.write(")") + +# TODO positional? +def print_params(f, paraminfo): + f.write("(") + + first = True + for param in paraminfo.parameters: + if first: + first = False + else: + f.write(", ") + + f.write(param[0]) + + if (param[1] is not None) and ('None' not in param[1]): + f.write(" = %s" % param[1]) + if paraminfo.extrapos: + f.write(", ") + f.write("*%s" % paraminfo.extrapos) + if paraminfo.extrakw: + f.write(", ") + f.write("**%s" % paraminfo.extrakw) + + f.write(")") + +# Print while command, from http://forum.cheatengine.org/viewtopic.php?p=5377683 +def print_While(f, stmt, indent_level): + f.write("while %s:\n" % (stmt.condition, )) + for inner_stmt in stmt.block: + print_statement(f, inner_stmt, indent_level + 1) + +# Print define command, by iAmGhost +def print_Define(f, stmt, indent_level): + f.write("define %s = %s\n" % (stmt.varname, stmt.code.source,)) + + +statement_printer_dict = { + ast.Label: print_Label, + ast.Say: print_Say, + ast.Jump: print_Jump, + ast.Scene: print_Scene, + ast.With: print_With, + ast.Show: print_Show, + ast.Hide: print_Hide, + ast.Python: print_Python, + ast.Return: print_Return, + ast.UserStatement: print_UserStatement, + ast.Init: print_Init, + ast.Image: print_Image, + ast.Transform: print_Transform, + ast.Menu: print_Menu, + ast.Pass: print_Pass, + ast.Call: print_Call, + ast.If: print_If, + ast.While: print_While, + ast.Define: print_Define, + ast.EarlyPython: print_EarlyPython, + } + +def print_Unknown(f, stmt, indent_level): + print(("Unknown AST node: %s" % (type(stmt).__name__, ))) + f.write("<<<UNKNOWN NODE %s>>>\n" % (type(stmt).__name__, )) diff --git a/unrpyc/fix.js b/unrpyc/fix.js new file mode 100755 index 0000000..88a9646 --- /dev/null +++ b/unrpyc/fix.js @@ -0,0 +1,6 @@ +var fs = require('fs'); + +fs.readFile(process.argv[2], function (err, data) { + if (err) throw err; + fs.writeFile(process.argv[3], JSON.stringify(eval('(' + data + ')'))); +}); diff --git a/unrpyc/unrpyc.py b/unrpyc/unrpyc.py new file mode 100644 index 0000000..f423cc8 --- /dev/null +++ b/unrpyc/unrpyc.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python2 + +# Copyright (c) 2012 Yuri K. Schlesner +# +# 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. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import optparse +import os.path +import sys +import pickle as pickle +import codecs +import glob +import itertools +import zlib + +class Dummy: + def record_pycode(self,*args,**kwargs): + return + all_pycode = [] + +def import_renpy(basedir=None): + #import renpy from another location. + if basedir: + sys.path.append(basedir) + global renpy + global decompiler + + # Needed for pickle to read the AST + try: + import renpy + except ImportError: + print("\nFailed at importing renpy. Are you sure that the renpy directory can be found in sys.path or the current working directory?\n") + raise + # try to import as much renpy modules as possible, but some modules might not exist + # in older ren'py versions. + try: import renpy.log + except: pass + try: import renpy.display + except: pass + try: import renpy.object + except: pass + try: + import renpy.game + renpy.game.script = Dummy() + except: pass + try: import renpy.loader + except: pass + try: import renpy.ast + except: pass + try: import renpy.atl + except: pass + try: import renpy.curry + except: pass + try: import renpy.easy + except: pass + try: import renpy.execution + except: pass + try: import renpy.loadsave + except: pass + try: import renpy.parser + except: pass + try: import renpy.python + except: pass + try: import renpy.script + except: pass + try: import renpy.statements + except: pass + try: import renpy.style + except: pass + + import decompiler + if basedir: + sys.path.remove(basedir) + + +def read_ast_from_file(in_file): + raw_contents = zlib.decompress(in_file.read()) + data, stmts = pickle.loads(raw_contents) + return stmts + +def decompile_rpyc(input_filename, out_filename, overwrite=False): + # Output filename is input filename but with .rpy extension + path, ext = os.path.splitext(input_filename) + + print(("Decompiling %s to %s..." % (input_filename, out_filename))) + + if not overwrite and os.path.exists(out_filename): + print("Output file already exists. Pass --clobber to overwrite.") + return False # Don't stop decompiling if one file already exists + + with open(input_filename, 'rb') as in_file: + ast = read_ast_from_file(in_file) + + with codecs.open(out_filename, 'w', encoding='utf-8') as out_file: + decompiler.pretty_print_ast(out_file, ast) + return True + +if __name__ == "__main__": + parser = optparse.OptionParser( + usage="usage: %prog [options] input output", + version="%prog 0.1") + + parser.add_option('-c', '--clobber', action='store_true', dest='clobber', + default=False, help="overwrites existing output files") + + parser.add_option('-b', '--basedir', action='store', dest='basedir', + help="specify the game base directory in which the 'renpy' directory is located") + + options, args = parser.parse_args() + + + if options.basedir: + import_renpy(options.basedir) + else: + import_renpy() + + if len(args) != 2: + parser.print_help(); + parser.error("Incorrect number of arguments: expected 2, got %d." % (len(args))) + + decompile_rpyc(args[0], args[1], options.clobber) +else: + import_renpy() + diff --git a/www/js/api.js b/www/js/api.js index 91a28d7..1cf9fbe 100644 --- a/www/js/api.js +++ b/www/js/api.js @@ -9,55 +9,55 @@ window.html5ks.api = { } } }, - set_volume: function (volume, delay, channel) { + + + set_volume: function (target, delay, channel) { var deferred = when.defer(), audio = html5ks.elements.audio[channel], - chg = volume - audio.volume, - step = chg / (delay * 10); + step = (target - audio.volume) / (delay * 20); var f = setInterval(function () { - var newv = audio.volume + step; - if (newv > 1) { - audio.volume = 1; + // clamp new volume 0-1 + audio.volume = Math.min(Math.max(audio.volume + step, 0), 1); + if (audio.volume === 0 || audio.volume === 1) { clearInterval(f); - } else if (newv < 0) { - audio.volume = 0; - clearInterval(f); - } else { - audio.volume = newv; } - }, 100); + }, 50); deferred.resolve(); return deferred.promise; }, - play: function (channel, name, fade) { + + play: function (channel, name, ignore, fade) { + this.stop(channel); var deferred = when.defer(), - audio = html5ks.elements.audio[channel]; + audio = new Audio(); + if (channel === "music" || channel === "ambient") { + audio.loop = true; + } + html5ks.elements.audio[channel] = audio; audio.src = "dump/" + (channel === "music" ? "bgm/" + html5ks.data.music[name] + ".ogg" : html5ks.data.sfx[name]); audio.load(); audio.volume = fade ? 0 : 1; audio.play(); audio.addEventListener("playing", function playing() { - audio.removeEventListener("playing", playing, false); deferred.resolve(); if (fade) { html5ks.api.set_volume(1, fade, channel); } }, false); audio.addEventListener("error", function error() { - audio.removeEventListener("error", error, false); deferred.reject(this.error); }, false); return deferred.promise; }, - stop: function (channel, fade) { + + stop: function (channel, ignore, fade) { if (channel === "all") { - this.stop("music"); - this.stop("sound"); - return this.stop("ambient"); + this.stop("music", ignore, fade); + this.stop("sound", ignore, fade); + return this.stop("ambient", ignore, fade); } var deferred = when.defer(), - audio = html5ks.elements.audio[channel], - fadeSet = html5ks.persistent.settings.fade; + audio = html5ks.elements.audio[channel]; if (fade) { this.set_volume(0, fade, channel); } else { @@ -67,6 +67,7 @@ window.html5ks.api = { return deferred.promise; }, + movie_cutscene: function (vid_src, skippable) { var deferred = when.defer(), video = html5ks.elements.video, @@ -113,13 +114,16 @@ window.html5ks.api = { }, false); return deferred.promise; }, + act_op: function (this_video) { // strip off extension return this.movie_cutscene(this_video.slice(0,-4)); }, + + iscene: function (target, is_h, is_end) { var deferred = when.defer(), - label = html5ks.data.script[target], + label = html5ks.data.script[html5ks.persistent.settings.language + "_" + target], i = 0; (function run(ret) { if (label[i]) { @@ -132,18 +136,18 @@ window.html5ks.api = { return deferred.promise; }, - with: function (transition, action) { - return this.runInst(action); - }, runInst: function (inst) { - var cmd = inst[0], + var cmd = inst[0].replace(/"/g, ''), args = inst.slice(1); if (html5ks.data.characters[cmd]) { return this.character(cmd, args[0]); } else { if (this[cmd]) { return this[cmd].apply(this, args); + } else if (inst.length === 1) { + console.log("hopefully this is dialogue"); + return this.character("name_only", cmd); } else if (/^[A-Z]/.test(cmd)) { console.log("cmd starts with caps, probably character"); return this.character(cmd, args[0]); @@ -156,6 +160,7 @@ window.html5ks.api = { } }, + window: function (action, transition) { var windw = html5ks.elements.window, deferred = when.defer(); @@ -167,12 +172,14 @@ window.html5ks.api = { deferred.resolve(action); return deferred.promise; }, - // NOT iscene + + scene: function (type, name) { html5ks.elements.show.innerHTML = ""; return this.show.apply(this, arguments); }, + show: function (name, type, location) { var deferred = when.defer(); var lookup = document.getElementById(name), @@ -200,7 +207,7 @@ window.html5ks.api = { bgleft: { xpos: 0.4, xanchor: 0.5, ypos: 1.0, yanchor: 1.0 }, bgright: { xpos: 0.6, xanchor: 0.5, ypos: 1.0, yanchor: 1.0 } }; - var pos = positions[location] || positions["center"]; + var pos = positions[location] || positions.center; // TODO: implement transitions if (pos) { el.style.left = pos.xpos * 800 + "px"; @@ -213,38 +220,37 @@ window.html5ks.api = { deferred.resolve(); }; el.onerror = function () { - // TODO: check if img is really in images.js - deferred.resolve(); + deferred.reject(); }; var nom = name; if (type) { nom = name + "_" + type; } var image = html5ks.data.images[nom]; - if (typeof image == "undefined") { - switch (name) { - case "bg": - image = "bgs/" + type + ".jpg"; - break; - case "url": - name = type; - image = type; - break; - default: - image = "sprites/" + name + "/" + (type && type.indexOf("_close") > -1 ? "close/" : "") + name + "_" + type + ".png"; - } - } - if (typeof image == "string") { - if (image.substring(0,1) == "#") { + switch (typeof image) { + case "string": + el = document.createElement("div"); el.style.backgroundColor = image; el.style.width = "100%"; el.style.height = "100%"; el.src = ""; deferred.resolve(); return deferred.promise; - } else { - image = {image: image}; - } + case "undefined": + switch (name) { + case "bg": + image = "bgs/" + type + ".jpg"; + break; + case "url": + name = type; + image = type; + break; + default: + image = "sprites/" + name + "/" + (type && type.indexOf("_close") > -1 ? "close/" : "") + name + "_" + type + ".png"; + } + } + if (typeof image === "string") { + image = {image: image}; } var src = ""; if (html5ks.persistent.settings.useWebP) { @@ -429,10 +435,10 @@ window.html5ks.api = { menu: function (label) { var deferred = when.defer(), - menu = html5ks.data.script[label], - char = menu[1], - str = menu[2], - choices = menu[3]; + imenu = html5ks.data.script[label], + char = imenu[1], + str = imenu[2], + choices = imenu[3]; this.character(char, str, null, true); var menu = html5ks.elements.choices, frag = document.createDocumentFragment(), @@ -442,14 +448,15 @@ window.html5ks.api = { for (var i in choices) { choice.innerHTML = i; - choice.addEventListener("click", function () { - html5ks.elements.choices.innerHTML = ""; - deferred.resolve(choices[this.innerHTML]); - }, false); frag.appendChild(choice); choice = choice.cloneNode(false); } + menu.addEventListener("click", function (e) { + html5ks.elements.choices.innerHTML = ""; + deferred.resolve(choices[e.target.innerHTML]); + }, false); + html5ks.elements.choices.appendChild(frag); return deferred.promise; } diff --git a/www/js/html5ks.js b/www/js/html5ks.js index 8a174dc..182d08a 100644 --- a/www/js/html5ks.js +++ b/www/js/html5ks.js @@ -1,5 +1,4 @@ "use strict"; -console.log("HELP") window.html5ks = { data: { script: {} @@ -13,12 +12,14 @@ window.html5ks = { skipUnread: false, skipAfterChoices: false, useWebP: null, - fullscreen: true, + fullscreen: false, + scaleAll: true, scaleVideo: true, textSpeed: 0.5, autoModeDelay: 0.2, musicVolume: 1, - sfxVolume: 1 + sfxVolume: 1, + language: "en" } }, store: { @@ -30,8 +31,10 @@ window.html5ks = { }, }, state: {}, + next: function () {}, initElements: function () { this.elements = { + all: document.getElementById("all"), container: document.getElementById("container"), video: document.getElementById("vid"), audio: { @@ -49,8 +52,6 @@ window.html5ks = { choices: document.getElementById("choices"), show: document.getElementById("show") }; - this.elements.audio.music.loop = true; - this.elements.audio.ambient.loop = true; }, load: function () { if (localStorage.persistent) { @@ -62,7 +63,7 @@ window.html5ks = { localStorage.persistent = JSON.stringify(this.persistent); }, scale: function () { - if (html5ks.persistent.settings.fullscreen) { + if (html5ks.persistent.settings.scaleAll) { var newScale = 1; var height = document.documentElement.clientHeight, width = document.documentElement.clientWidth; @@ -95,18 +96,24 @@ window.html5ks = { } } }, - next: function () {}, + fullscreen: function () { + var all = html5ks.elements.all; + if (all.requestFullscreen) { + all.requestFullscreen(); + } else if (all.mozRequestFullScreen) { + all.mozRequestFullScreen(); + } else if (all.webkitRequestFullscreen) { + all.webkitRequestFullscreen(); + } + }, initEvents: function () { window.addEventListener("resize", html5ks.scale, false); this.elements.container.addEventListener("mouseup", function () { html5ks.next(); }, false); - var deselect = function () { - window.getSelection().collapse(this, 0); - }; - document.body.addEventListener("mousemove", deselect, true); - document.body.addEventListener("mouseup", deselect, true); - document.body.addEventListener("keyup", deselect, true); + window.addEventListener("dragstart", function (e) { + e.preventDefault(); + }, false); }, warnUnsupported: function () { if (!html5ks.persistent.settings.gotit) { @@ -141,6 +148,12 @@ window.html5ks = { } this.api.init(); this.menu.init(); + if (this.persistent.settings.fullscreen) { + document.body.addEventListener("click", function onclick() { + this.removeEventListener("click", onclick, false); + html5ks.fullscreen(); + }, false); + } }, winload: function () { if (!this.loaded) { diff --git a/www/js/menu.js b/www/js/menu.js index 4259ce1..43d98c4 100644 --- a/www/js/menu.js +++ b/www/js/menu.js @@ -45,13 +45,13 @@ case "checkbox": option.checked = values[option.id]; option.addEventListener("change", function () { - values[this.id] = this.checked; + values[this.id] = this.checked; }, false); break; case "range": option.value = values[option.id]; option.addEventListener("change", function () { - values[this.id] = this.value; + values[this.id] = this.value; }, false); break; default: |