"use strict";
window.html5ks.api = {
init: function () {
var chrs = html5ks.data.characters;
for (var ch in chrs) {
var chr = chrs[ch];
if (chr.name) {
chr.name = this.tag(chr.name);
}
}
},
_fading: {},
set_volume: function (target, fade, channel) {
var audio = html5ks.elements.audio[channel],
step = (target - audio.volume) / (fade * 20);
var nextVol = function () {
// clamp new volume 0-1
audio.volume = Math.min(Math.max(audio.volume + step, 0), 1);
switch (audio.volume) {
case 0:
audio.pause();
/* falls through */
case 1:
clearInterval(this._fading[channel]);
delete this._fading[channel];
return false;
}
return true;
}.bind(this);
if (nextVol()) this._fading[channel] = setInterval(nextVol, 50);
return when.resolve();
},
_loadMedia: function (el, src, types) {
var i = 0;
var _nextType = function () {
for (; i < types.length; i++) {
var type = types[i];
if (el.canPlayType(type[0])) {
el.src = src + "." + type[1];
el.load();
return true;
}
}
};
html5ks.spin(1);
el.oncanplaythrough = function () {
el.oncanplaythrough = null;
el.play();
html5ks.spin(-1);
};
setTimeout(function () {
if (el.src && el.oncanplaythrough) {
console.warn("video not playing after 3 seconds");
el.oncanplaythrough();
}
}, 3000);
el.onerror = function (e) {
if (e.code === e.MEDIA_ERR_SRC_NOT_SUPPORTED) {
if (_nextType(++i)) {
console.warn("browser claimed support for " + types[i-1][0] + " but failed");
} else {
console.error("no media formats supported");
}
} else {
console.error("unknown audio error");
}
};
_nextType();
},
play: function (cmd, args) {
var channel = cmd[1];
this.stop(channel);
var deferred = when.defer(),
audio = html5ks.elements.audio[channel];
if (channel === "music" || channel === "ambient") {
audio.loop = true;
}
html5ks.elements.audio[channel] = audio;
html5ks.store[channel] = args.file;
var src = "dump/", volume;
switch (channel) {
case "music":
var fname = html5ks.data.music[args.file.replace(/ $/, '')];
src += "bgm/" + fname;
volume = html5ks.persistent.musicVolume;
document.getElementById("current-music-track").innerHTML = fname.replace(/_/g, " ");
break;
case "ambient":
case "sound":
src += "sfx/" + html5ks.data.sfx[name];
volume = html5ks.persistent.sfxVolume;
}
audio.addEventListener("playing", function playing() {
audio.removeEventListener("playing", playing, false);
if (args.fadein) {
html5ks.api.set_volume(volume, args.fadein, channel);
}
deferred.resolve();
}, false);
audio.volume = args.fadein ? 0 : volume;
this._loadMedia(audio, src, [
['audio/ogg; codecs="opus"', "opus"],
['audio/ogg; codecs="vorbis"', "ogg"],
['audio/x-m4a', "m4a"],
['audio/aac', "aac"],
['audio/wav; codecs="1"', "wav"]]);
return deferred.promise;
},
stop: function (channel, ignore, fade) {
if (channel === "all") {
return ["music", "sound", "ambient"].forEach(function (channel) {
html5ks.api.stop(channel, ignore, fade);
});
}
var audio = html5ks.elements.audio[channel];
if (this._fading[channel]) {
clearInterval(this._fading[channel]);
}
if (fade) {
this.set_volume(0, fade, channel);
} else {
audio.pause();
}
return when.resolve();
},
movie_cutscene: function (vid_src, skippable) {
var deferred = when.defer(),
video = html5ks.elements.video,
src = "dump/video/" + vid_src;
this.stop("all");
clearInterval(html5ks._nextTimeout);
if (html5ks.api.skip()) {
return deferred.resolve();
}
this._loadMedia(video, src, [
['video/webm; codecs="vp9,opus"', "vp9.webm"],
['video/webm; codecs="vp8,vorbis"', "webm"],
['video/ogg; codecs="theora,vorbis"', "ogv"],
['video/mp4; codecs="avc1.42E01E,mp4a.40.2"']]);
video.style.display = "block";
video.volume = html5ks.persistent.musicVolume;
var done = function () {
video.style.display = "none";
video.onerror = null;
video.src = "";
deferred.resolve();
};
document.addEventListener("keyup", function keyupListener(e) {
document.removeEventListener("keyup", keyupListener, false);
if (e.keyCode === 27) {
done();
e.preventDefault();
}
}, false);
video.onclick = function (e) {
if (e.button === 0) {
if (video.paused) video.play();
else if (skippable) done();
}
};
video.onended = done;
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) {
html5ks.store.status = "scene";
var deferred = when.defer(),
real_target = html5ks.persistent.language + "_" + target.replace(/"/g, ''),
i = 0;
html5ks.fetch('script', real_target).then(function run(l) {
if (l[i++]) {
html5ks.api.runInst(l[i]).then(function(){run(l)}, console.error);
} else {
deferred.resolve();
}
}, deferred.reject);
return deferred.promise;
},
_safeCall: function (n, f, args) {
if (f) return f.apply(this, args);
else {
console.error("no such function " + n);
debugger;
return when.resolve();
}
},
runInst: function (inst) {
console.debug(inst);
var cmd = inst[0].replace(/"/g, '');
return this._safeCall(cmd, this[cmd], inst.slice(1));
},
UserStatement: function (line, parsed) {
var c = parsed[0][0];
return this._safeCall(c, this[c], parsed);
},
With: function () {
return when.resolve();
},
window: function (action) {
var windw = html5ks.elements.window;
switch (action) {
case true:
case "show":
html5ks.store.window = true;
windw.style.display = "block";
break;
case false:
case "hide":
html5ks.store.window = false;
windw.style.display = "none";
break;
}
return when.resolve();
},
Scene: function () {
html5ks.elements.show.innerHTML = "";
return this.Show.apply(this, arguments);
},
_positions: {
left: { xpos: 0.0, xanchor: 0.0, ypos: 1.0, yanchor: 1.0 },
right: { xpos: 1.0, xanchor: 1.0, ypos: 1.0, yanchor: 1.0 },
center: { xpos: 0.5, xanchor: 0.5, ypos: 1.0, yanchor: 1.0 },
truecenter: { xpos: 0.5, xanchor: 0.5, ypos: 0.5, yanchor: 0.5 },
topleft: { xpos: 0.0, xanchor: 0.0, ypos: 0.0, yanchor: 0.0 },
topright: { xpos: 1.0, xanchor: 1.0, ypos: 0.0, yanchor: 0.0 },
top: { xpos: 0.5, xanchor: 0.5, ypos: 0.0, yanchor: 0.0 },
twoleft: { xpos: 0.3, xanchor: 0.5, ypos: 1.0, yanchor: 1.0 },
tworight: { xpos: 0.7, xanchor: 0.5, ypos: 1.0, yanchor: 1.0 },
closeleft: { xpos: 0.25, xanchor: 0.5, ypos: 1.0, yanchor: 1.0 },
closeright: { xpos: 0.75, xanchor: 0.5, ypos: 1.0, yanchor: 1.0 },
twoleftoff: { xpos: 0.32, xanchor: 0.5, ypos: 1.0, yanchor: 1.0 },
tworightoff: { xpos: 0.68, xanchor: 0.5, ypos: 1.0, yanchor: 1.0 },
centeroff: { xpos: 0.52, xanchor: 0.5, ypos: 1.0, yanchor: 1.0 },
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 }
},
Show: function (imspec) {
var deferred = when.defer();
console.log(imspec);
html5ks.fetch("json", "ui_settings").then(function (d) {
var name = imspec[0].join(' ');
var image = d[name];
if (!image) {
switch (imspec[0][0]) {
case 'url':
image = {eval: imspec[0][1]};
break;
case 'bg':
image = {eval: "bgs/" + imspec[0][1] + ".jpg"};
break;
default:
return deferred.resolve();
}
}
console.log(image);
var lookup = document.getElementById(name),
el = lookup || document.createElement("img");
if (!location && !lookup) location = "center";
el.onload = function () {
if (location) {
// calculate position
// we don't actually know how big the image is till we fetch it
var pos = html5ks.api._positions[location];
if (pos) {
el.style.left = pos.xpos * 800 + "px";
el.style.top = pos.ypos * 600 + "px";
el.style.marginLeft = "-" + pos.xanchor * el.width + "px";
el.style.marginTop = "-" + pos.yanchor * el.height + "px";
}
el.style.display = "block";
}
html5ks.spin(-1);
deferred.resolve();
};
el.onerror = function () {
el.parentNode.removeChild(el);
html5ks.spin(-1);
deferred.resolve();
};
var src = image.eval;
if (Modernizr.webp) {
src = src.replace(/\.[a-z]+$/, ".webp");
}
el.id = name;
el.src = "dump/" + src;
html5ks.spin(1);
// prevent FOUIPC (flash of incorrectly placed content)
if (!lookup) {
el.style.display = "none";
html5ks.elements.show.appendChild(el);
}
});
return deferred.promise;
},
hide: function (name) {
var show = html5ks.elements.show.children;
for (var i = show.length - 1; i >= 0; i--) {
if (show[i].id === name) {
html5ks.elements.show.removeChild(show[i]);
}
}
return when.resolve();
},
tag: function (str) {
var tags = [
/&/g, "&",
//g, ">",
/{b}/g, "",
/{\/b}/g, "",
/{s}/g, "",
/{\/s}/g, "",
/{size=(\d*)}/g, "",
/{\/size}/g, "",
/{color=(\d*)}/g, "",
/{\/color}/g, "",
/{w(=\d*\.\d*)?}.*/, "",
/{nw}/, "",
/{fast}/, "",
/\n/g, "
"
];
for (var i = 0; i < tags.length - 1; i += 2) {
str = str.replace(tags[i], tags[i+1]);
}
return str;
},
dlgTag: function (str) {
var text = [];
for (var i = 0; i < str.length; i++) {
switch (str[i]) {
case '{':
var close = str.indexOf('}', i),
tag = str.slice(i, close);
i = close;
if (tag[1] === '/') {
text.push('');
} else {
text.push(tag);
}
break;
default:
text.push(str[i]);
}
}
var br = document.createElement("br");
var span = document.createElement("span");
span.style.visibility = "hidden";
return text.map(function (txt) {
if (txt == '\n') {
return br.cloneNode(false);
} else {
span = span.cloneNode(false);
span.appendChild(document.createTextNode(txt));
return span;
}
});
},
Say: function (who, what, with_, extend) {
var deferred = when.defer(),
chr = html5ks.data.characters[who],
w = /{w=?(\d*\.\d*)?}(.*)/.exec(what);
if (!chr) {
chr = { name: who };
}
chr.what_prefix = chr.what_prefix || "“";
chr.what_suffix = chr.what_suffix || "“";
this._lastchar = chr;
if (!extend && chr.what_prefix) {
what = chr.what_prefix + what;
}
if ((!w || !w[1]) && chr.what_suffix) {
what += chr.what_suffix;
}
var text = this.dlgTag(what),
say, ctc;
if (chr.kind === "nvl") {
say = document.createElement("span");
say.className = "nvl-block";
html5ks.elements.nvlsay.appendChild(say);
ctc = html5ks.elements.nvlctc;
} else {
var who = html5ks.elements.who;
if (!extend) {
who.innerHTML = chr.name;
who.style.color = chr.color || "#ffffff";
}
say = html5ks.elements.say;
if (!extend) say.innerHTML = "";
ctc = html5ks.elements.ctc;
}
text.forEach(say.appendChild.bind(say));
var tm, done;
var ptxt = function (immed) {
var txt = text.shift();
if (typeof txt !== 'undefined') {
txt.removeAttribute("style");
if (immed) return ptxt(immed);
else tm = setTimeout(ptxt, 1000 / html5ks.persistent.textSpeed);
} else {
done = true;
ctc.style.display = "block";
ctc.class = "";
ctc.class = "ctc";
}
};
ptxt();
html5ks.next = function () {
if (!done) {
clearTimeout(tm);
ptxt(true);
} else {
ctc.style.display = "none";
deferred.resolve();
}
html5ks.api._setNextTimeout(what, true);
};
if (html5ks.persistent.textSpeed == 200) {
ptxt(true);
}
this._setNextTimeout(what, false);
return deferred.promise;
},
_setNextTimeout: function (str, done) {
if (done) {
if (this.auto()) {
setTimeout(html5ks.next, str.length + 3.5 * html5ks.persistent.autoModeDelay);
}
} else if (html5ks.api.skip() || str.indexOf("{nw}") > -1) {
html5ks.next();
html5ks._nextTimeout = setTimeout(html5ks.next, 50);
}
},
Pause: function (duration) {
var deferred = when.defer();
setTimeout(function () {
deferred.resolve();
}, duration * 1000);
return deferred.promise;
},
nvl: function (action) {
var nvl = html5ks.elements.nvl;
switch (action) {
case true:
case "show":
html5ks.store.nvl = true;
nvl.style.display = "block";
break;
case false:
case "hide":
html5ks.store.nvl = false;
nvl.style.display = "none";
break;
case "clear":
html5ks.elements.nvlsay.innerHTML = "";
break;
default:
console.error("no such nvl action " + action);
}
return when.resolve();
},
centered: function (text) {
var deferred = when.defer(),
centered = document.getElementById("centered");
centered.innerHTML = this.tag(text);
html5ks._next = function () {
centered.innerHTML = "";
deferred.resolve();
};
this._setNextTimeout(text, false);
this._setNextTimeout(text, true);
return deferred.promise;
},
menu: function (choices) {
var deferred = when.defer();
var menu = html5ks.elements.choices,
frag = document.createDocumentFragment(),
choice = document.createElement("div");
choice.className = "choice button";
for (var i in choices) {
choice.innerHTML = i;
choice.id = choices[i];
frag.appendChild(choice);
choice = choice.cloneNode(false);
}
menu.addEventListener("click", function (e) {
html5ks.elements.choices.innerHTML = "";
deferred.resolve(e.target.id);
}, false);
html5ks.elements.choices.appendChild(frag);
return deferred.promise;
},
auto: function (arg) {
if (arg) {
return this.speed("auto");
} else {
return html5ks.store.speed === "auto";
}
},
skip: function (arg) {
if (arg) {
return this.speed("skip");
} else {
return html5ks.store.speed === "skip";
}
},
speed: function (state) {
switch (state) {
case "skip":
case "auto":
this.speed();
html5ks.store.speed = state;
document.getElementById(state).style.display = "block";
break;
default:
html5ks.store.speed = "";
document.getElementById("auto").style.display = "none";
document.getElementById("skip").style.display = "none";
}
}
};