Work on text stuff.
Work on tweens.
Beginning audio support.
This commit is contained in:
fire bingo 2018-04-15 18:35:01 -07:00
vanhempi 00be56d0a8
commit a192a0eb0e
10 muutettua tiedostoa jossa 799 lisäystä ja 173 poistoa

Näytä tiedosto

@ -41,11 +41,11 @@
.centered { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; }
#app-container { position: relative; width: 1334; height: 750; }
#app-container { position: relative; width: 1334px; height: 750px; }
#parent-container { display: flex; flex-direction: column; align-items: center; }
#text-container { position: absolute; height: 100%; width: 100%; font-family: 'FOT-RodinNTLGPro'; }
#text-container { position: absolute; left: 0; right: 0; top: 0; bottom: 0; margin: auto; height: 750px; width: 1334px; font-family: 'FOT-RodinNTLGPro'; }
#text-container #title { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; font-size: 20px; transition: opacity 0.3s; }
@ -59,8 +59,10 @@
#dialog-box #character { position: absolute; top: -5px; left: 70px; font-size: 30px; }
#dialog-box #dialog { position: absolute; padding: 60px 30px 0px 70px; box-sizing: border-box; height: 100%; width: 100%; font-size: 30px; }
#dialog-box #dialog { overflow: hidden; position: absolute; padding: 0 30px 0 70px; margin-top: 60px; box-sizing: border-box; height: 120px; width: 100%; font-size: 30px; line-height: 60px; }
#dialog-box #ui-buttons { right: 20px; top: -50px; position: absolute; }
#dialog-box .ui-button { position: relative; height: 85px; }
#dialog-box .ui-button { position: relative; height: 85px; }
#other-controls-container { display: flex; width: 100; justify-content: center; }

99
Js/Audio.js Normal file
Näytä tiedosto

@ -0,0 +1,99 @@
//(Math.exp(x)-1)/(Math.E-1)
//🔊
class bufferLoader {
constructor(context, soundMap, callback) {
this.context = context;
this.soundMap = soundMap;
this.onloadUpdate = callback;
this.bufferList = {};
this.loadCount = 0;
}
load() {
for (let s of this.soundMap) {
this.loadBuffer(s.FileName, s.Label);
}
}
loadBuffer(url, name) {
return new Promise((resolve, reject) => {
try
{
// Load buffer asynchronously
fetch(url)
.then((response) => {
if(response.status !== 200) {
if(this.onloadUpdate) {
this.onloadUpdate((++this.loadCount / this.soundMap.length) * 100);
}
reject(response); return;
}
response.arrayBuffer().then((buffer) => {
// Asynchronously decode the audio file data in request.response
this.context.decodeAudioData(buffer, (buf) => {
if (!buf) {
reject('error decoding file data: ' + url);
}
this.bufferList[name] = buf;
if(this.onloadUpdate) {
this.onloadUpdate((++this.loadCount / this.soundMap.length) * 100);
}
resolve(`${this.loadCount}|${this.soundMap.length}`);
},
function(error) {
console.log(error);
console.log(`url: ${url}, name: ${name}`);
if(this.onloadUpdate) {
this.onloadUpdate((++this.loadCount / this.soundMap.length) * 100);
}
reject(error);
}
);
});
}, (failure) => {
console.log(failure);
reject(failure);
});
} catch(error) {
console.log(error);
reject(error);
}
});
}
}
class audioController {
constructor() {
this.volume = 1.0;
this.muted = false;
this.audioCtx = new (window.AudioContext || window.webkitAudioContext)();
this.gainNode = this.audioCtx.createGain();
this.loader = undefined;
}
changeVolume(vol) {
this.volume = (Math.exp(vol)-1)/(Math.E-1);
if(!this.muted) {
this.gainNode.gain.setValueAtTime(this.volume, this.audioCtx.currentTime);
}
}
mute() {
this.muted = !this.muted;
if(this.muted) {
this.gainNode.gain.setValueAtTime(0, athis.udioCtx.currentTime);
} else {
this.gainNode.gain.setValueAtTime(this.volume, this.audioCtx.currentTime);
}
}
loadSounds(soundMap, callback) {
this.loader = new bufferLoader(this.audioCtx, soundMap, (percent) => {
if (callback) {
callback(percent);
}
});
this.loader.load();
}
}

Näytä tiedosto

@ -1,33 +1,46 @@
'use strict';
var rootUrl = `${window.location.protocol}//${window.location.host}/`;
let rootUrl = `${window.location.protocol}//${window.location.host}/`;
const screenRatio = 9/16;
class commonFunctions {
static getFileText(file) {
return new Promise((resolve, reject) => {
fetch(file)
.then((success) => {
success.text()
.then((text) => {
resolve(text);
try
{
fetch(file)
.then((success) => {
if(success.status !== 200) { reject(success); return; }
success.text()
.then((text) => {
resolve(text);
});
}, (failure) => {
reject(failure);
});
}, (failure) => {
reject(failure);
});
} catch(error) {
reject(error);
}
});
}
static getFileJson(file) {
return new Promise((resolve, reject) => {
fetch(file)
.then((success) => {
success.json()
.then((json) => {
resolve(json);
try
{
fetch(file)
.then((success) => {
if(success.status !== 200) { reject(success); return; }
success.json()
.then((json) => {
resolve(json);
});
}, (failure) => {
reject(failure);
});
}, (failure) => {
reject(failure);
});
} catch(error) {
reject(error);
}
});
}
@ -40,15 +53,41 @@ class commonFunctions {
let split = line.split('\t');
let newEntry = {};
for(let i = 0; i < split.length; ++i) {
let x = split[i];
let x = split[i].trim();
newEntry[headers[i]] = x;
}
return newEntry;
}
}
static lerp(start, end, t) {
static lerp(start, end, t, type = "linear") {
switch(type) {
default:
case "linear":
break;
case "sin":
t = t * (Math.PI / 2);
t = Math.sin(t);
break;
case "fullsin":
t = t * Math.PI;
t = Math.sin(t);
break;
case "fullwait":
t = -(Math.pow((2*t-1), 4)) + 1;
break;
case "square":
t = Math.pow(t, 2);
break;
case "exp":
t = Math.pow(t, 1/4);
break;
case "sqrt":
t = Math.sqrt(t);
break;
}
return (1 - t) * start + t * end;
//-(2*x-1)^4 +1
}
static getColorFromName(name) {
@ -64,10 +103,52 @@ class commonFunctions {
static convertUtageTextTags(text) {
text = text.replace(/<speed.*?>|<\/speed>/g, "");
text = text.replace("\\n", "<br/>")
text = text.replace(/\\n/g, "<br/>")
//rewrite ruby tags to normal html format
let rubyMatches = text.match(/<ruby=.*?<\/ruby>/g);
if(rubyMatches) {
for(let i = 0; i < rubyMatches.length; ++i) {
let m = rubyMatches[i];
let rText = '';
let innerText = '';
let startR = false;
let startI = false;
for(let j = 0; j < m.length; ++j) {
if(m[j] === '<' && j !== 0) { startI = false; }
if(startI) {
innerText+= m[j];
}
if(m[j] === '>') { startR = false; startI = true; }
if(startR) {
rText += m[j];
}
if(m[j] === '=') { startR = true; }
}
text = text.replace(m, `<ruby>${innerText}<rt>${rText}</rt></ruby>`);
}
}
return text;
}
static getNewResolution(baseRes, screenWidth, screenHeight, minusHeight) {
let retval = { width: 0, height: 0 };
if(screenWidth >= screenHeight) {
let newPer = (screenHeight - (minusHeight || 0)) / baseRes.height;
retval.height = baseRes.height * newPer;
retval.width = baseRes.width * newPer;
if(retval.width > screenWidth) {
newPer = screenWidth / baseRes.width;
retval.height = baseRes.height * newPer;
retval.width = baseRes.width * newPer;
}
} else if (screenHeight > screenWidth) {
let newPer = screenWidth / baseRes.width;
retval.height = baseRes.height * newPer;
retval.width = baseRes.width * newPer;
}
return retval;
}
static getAnchorFromCharPivot(pivot) {
let x = 0.5;
let y = 0.5;
@ -77,9 +158,60 @@ class commonFunctions {
x = Number(p.substring(2));
} else if(p.startsWith("y=")) {
y = Number(p.substring(2));
//The y pivot from utage is based from the bottom so it needs to be flipped
y = 1 - y;
}
}
return {x, y};
}
static getPropertiesFromTweenCommand(props) {
var retval = {};
let indexX = props.indexOf("x=");
if(indexX !== -1) {
retval.x = "";
for(let i = indexX + 2; i < props.length; ++i) {
if(props[i] == " ") { break; }
retval.x += props[i];
}
retval.x = Number(retval.x);
}
let indexY = props.indexOf("y=");
if(indexY !== -1) {
retval.y = "";
for(let i = indexY+ 2; i < props.length; ++i) {
if(props[i] == " ") { break; }
retval.y += props[i];
}
retval.y = -Number(retval.y);
}
let indexT = props.indexOf("time=");
if(indexT !== -1) {
retval.time = "";
for(let i = indexT + 5; i < props.length; ++i) {
if(props[i] == " ") { break; }
retval.time += props[i];
}
retval.time = Number(retval.time) * 1000;
}
let indexD = props.indexOf("delay=");
if(indexD !== -1) {
retval.delay = "";
for(let i = indexD + 6; i < props.length; ++i) {
if(props[i] == " ") { break; }
retval.delay += props[i];
}
retval.delay = Number(retval.delay) * 1000;
}
let indexA = props.indexOf("alpha=");
if(indexA !== -1) {
retval.alpha = "";
for(let i = indexA + 6; i < props.length; ++i) {
if(props[i] == " ") { break; }
retval.alpha += props[i];
}
retval.alpha = Number(retval.alpha);
}
return retval;
}
}

Näytä tiedosto

@ -4,18 +4,26 @@ const pixiApp = {
app: new PIXI.Application(baseDimensions),
loader: PIXI.loader
};
const utage = new UtageInfo();
const textFunc = new TextFunctions();
const player = new Player(pixiApp, utage, textFunc);
const audio = new audioController();
const player = new Player(pixiApp, utage, textFunc, audio);
const context = new (window.AudioContext || window.webkitAudioContext)();
const languages = ["eng", "jpn"];
let bodyLoaded = false;
let utageLoaded = false;
let selectedLang = "eng";
let screenw = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
let screenh = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
let screenSizeTimeout = undefined;
function onBodyLoaded() {
bodyLoaded = true;
}
(function startLoad() {
var promises = [
let promises = [
utage.loadUtageSettings()
];
@ -47,10 +55,11 @@ function onAllLoaded(success) {
buildMissionSelectList();
let appContainer = document.getElementById('app-container');
appContainer.appendChild(pixiApp.app.view);
//appContainer.style.cssText = `width: ${baseDimensions.width}; height: ${baseDimensions.height};`;
setTimeout(() => {
document.getElementById('parent-container').style.cssText = "opacity: 1;";
});
onWindowResize();
window.addEventListener("resize", onWindowResize);
}, 0);
}
function buildMissionSelectList() {
@ -63,8 +72,11 @@ function buildMissionSelectList() {
opt.innerText = 'Select Mission';
} else {
let m = utage.missionsList[i];
//if(m.includes('MA1-') || m.includes('MA2-')|| m.includes('MA3-')) {
// continue;
//}
opt.setAttribute('value', m);
opt.innerText = m;
opt.innerText = m.replace('|', ' - ');
}
selectBox.appendChild(opt);
}
@ -74,14 +86,15 @@ function missionChanged(event) {
if(!event || !event.currentTarget || !event.currentTarget.value || event.currentTarget.value === '{Select}') { return; }
let newMission = utage.availableMissions[event.currentTarget.value.split('|')[0]];
var promises = [
utage.parseMissionFile(`${utage.rootDirectory}XDUData/${newMission.Path.replace('Asset/', '').replace('.utage', '')}`),
let promises = [
utage.parseMissionFile(`${utage.rootDirectory}XDUData/${newMission.Path.replace('Asset/', '').replace('.utage', '').replace('.tsv', '_t.tsv')}`),
utage.loadMissionTranslation(`${utage.rootDirectory}XDUData/${newMission.Path.replace('Asset/', '').replace('.utage', '').replace('.tsv', `_translations_${selectedLang}.json`)}`),
player.resetAll()
];
Promise.all(promises)
.then((success) => {
var res = player.playFile()
let res = player.playFile()
.then((success) => {
player.resetAll();
debugger;
@ -94,8 +107,25 @@ function missionChanged(event) {
});
}
function onTextClicked(event) {
event.preventDefault();
event.stopPropagation();
function onMainClick(event) {
player.onMainClick(event);
}
function hideUiClicked(event) {
player.hideUiClicked(event);
}
function onWindowResize(event) {
if(screenSizeTimeout) {
clearTimeout(screenSizeTimeout);
screenSizeTimeout = undefined;
}
screenSizeTimeout = setTimeout(() => {
screenw = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
screenh = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
let topContainerHeight = document.getElementById('other-controls-container').offsetHeight + 6;
let res = commonFunctions.getNewResolution(baseDimensions, screenw, screenh, topContainerHeight);
player.updateResolution(res);
document.getElementById('app-container').style.cssText = `width: ${res.width}px; height: ${res.height}px;`;
}, 400);
}

21
Js/Pixi.min.js vendored Normal file

File diff suppressed because one or more lines are too long

Näytä tiedosto

@ -2,13 +2,20 @@
const baseDimensions = {width: 1334, height: 750};
class Player {
constructor(pixi, utage, text) {
constructor(pixi, utage, text, audio) {
this.pixi = pixi;
this.loader = pixi.loader;
this.utage = utage;
this.text = text;
this.audio = audio;
//consts
this.resolutionScale = 1;
this.baseFps = 60; //I am assuming that PIXI is going to stay as keeping 60fps = delta1.
this.bgLayerName = "背景";
this.defaultCharPattern = 'すまし';
this.backCharTint = 0x808080;
this.titleWaitTime = 1;
this.blackBackSp = undefined;
this.currentCharacters = {};
this.lastCharOffLayer = undefined;
@ -19,12 +26,12 @@ class Player {
this.secondTicker = 1000;
this.waitTime = 0;
this.lerpTargets = [];
this.bgLayerName = "背景";
this.defaultCharPattern = 'すまし';
this.titleWaitTime = 1;
this.manualNext = false;
this.hasMoreText = false;
this.uiHidden = false;
this.center = {x: ((baseDimensions.width / 2) * this.resolutionScale), y: ((baseDimensions.height / 2) * this.resolutionScale) };
this.assetLoadPercent = 0;
this.audioLoadPercent = 0;
}
playFile() {
@ -43,6 +50,8 @@ class Player {
preCheckFilesToGet() {
return new Promise((resolve, reject) => {
let toLoadBgm = {};
let toLoadSe = {};
for(let i = 0; i < utage.currentPlayingFile.length; ++i) {
try {
let c = utage.currentPlayingFile[i];
@ -58,7 +67,7 @@ class Player {
} else if(!this.utage.textureInfo[c.Arg1]) {
console.log(`Failed to get BG: ${c.Arg1}`);
}
break;
break;
//Text
case "":
//Character Text
@ -74,18 +83,58 @@ class Player {
(!this.utage.characterInfo[c.Arg1] || !this.utage.characterInfo[c.Arg1][Arg2])) {
console.log(`Failed to get Character: ${c.Arg1}|${Arg2}`);
}
break;
//These voices arent in the Sound.tsv because fuck you
if(c.Voice) {
let filename = c.Voice;
if(filename.includes(',')) {
let s = filename.split(',');
filename = `${s[0].split('_').join('/')}/${s[1]}`;
}
filename = `${this.utage.rootDirectory}XDUData/Voice/${filename}.opus`;
if(!toLoadSe[c.Voice]) {
toLoadSe[c.Voice] = { Label: c.Voice, FileName: filename };
}
}
break;
case "bgm":
if(this.utage.soundInfo[c.Arg1]) {
if(!toLoadBgm[c.Arg1]) {
toLoadBgm[c.Arg1] = this.utage.soundInfo[c.Arg1];
}
} else {
console.log(`Failed to get BGM: ${c.Arg1}`);
}
break;
case "se":
if(this.utage.soundInfo[c.Arg1]) {
if(!toLoadSe[c.Arg1]) {
toLoadSe[c.Arg1] = this.utage.soundInfo[c.Arg1];
}
} else {
console.log(`Failed to get SE: ${c.Arg1}`);
}
break;
}
} catch (error) {
console.log(error);
continue;
}
}
let audioArray = [];
for(let s of Object.keys(toLoadBgm)) {
audioArray.push(toLoadBgm[s]);
}
for(let s of Object.keys(toLoadSe)) {
audioArray.push(toLoadSe[s]);
}
this.audio.loadSounds(audioArray, (percent) => {
this.onAudioProgress(percent);
});
//Manually load white bg for fading. Can be tinted to change color.
this.loader.add('bg|whiteFade', `${this.utage.rootDirectory}Images/white.png`);
this.loader
.on("progress", (loader, resource) => {
this.onPixiProgress(loader, resource)
this.onPixiProgress(loader, resource);
})
.load(() => {
this.onPixiLoad(resolve, reject);
@ -133,17 +182,34 @@ class Player {
}
onPixiProgress(loader, resource) {
if(loader.progress < 100) {
this.text.titleText(true, `Loading Assets... ${loader.progress.toFixed(0)}%`);
} else {
this.text.titleText(false, '');
}
this.assetLoadPercent = loader.progress;
this.text.titleText(true, `Loading Assets... ${loader.progress.toFixed(0)}%`);
this.onLoadProgressUpdate();
}
onAudioProgress(percent) {
this.audioLoadPercent = percent;
this.onLoadProgressUpdate();
}
onLoadProgressUpdate() {
let totalProgress = (this.audioLoadPercent / 2) + (this.assetLoadPercent / 2);
this.text.titleText(true, `Loading Assets... ${totalProgress.toFixed(0)}%`);
}
onPixiLoad(resolve, reject) {
this.runEvent = true;
this.buildLayerContainers();
resolve();
if(this.audioLoadPercent === 100) {
this.text.titleText(false, '');
setTimeout(() => {
this.runEvent = true;
this.buildLayerContainers();
resolve();
}, 1000);
} else {
setTimeout(() => {
this.onPixiLoad(resolve, reject);
}, 100);
}
}
onPixiTick(delta) {
@ -185,17 +251,18 @@ class Player {
for(let i = 0; i < this.lerpTargets.length; ++i) {
let l = this.lerpTargets[i];
l.curTime += deltaTime;
if(l.curTime < 0) { continue; }
let inter = l.inter || "linear";
let pos = l.curTime / l.time;
if(pos >= 1) {
pos = 1;
if(pos >= 1) {
pos = 1;
toRemove.push(i);
if(l.post === "destroy") {
debugger;
l.object.destroy();
continue;
}
}
let newValue = commonFunctions.lerp(l.initV, l.finalV, pos);
let newValue = commonFunctions.lerp(l.initV, l.finalV, pos, inter);
let split = l.type.split(".");
switch(split.length) {
case 1:
@ -219,24 +286,28 @@ class Player {
processCommand(delta) {
try {
let cur = this.currentCommand;
if(this.checkIfAllOff()) {
this.text.dialogText(false, "");
this.text.characterName(false, "");
} else {
this.text.dialogText(true, "");
this.text.characterName(true, "");
}
//if(this.checkIfAllOff()) {
// this.text.dialogText(false, "");
// this.text.characterName(false, "");
//} else {
// this.text.dialogText(true, "");
// this.text.characterName(true, "");
//}
switch((cur.Command || "").toLowerCase()) {
case "scenetitle01":
this.waitTime = this.titleWaitTime * 1000;
this.text.titleText(true, cur.Text);
var text = cur.English ? (utage.currentTranslation[cur.English] || cur.Text) : cur.Text;
this.text.titleText(true, text);
break;
case "divaeffect":
this.waitTime = 1000//Number(cur.Arg5) * 1000;
this.text.divaText(true, cur.Text);
var text = cur.English ? (utage.currentTranslation[cur.English] || cur.Text) : cur.Text;
this.text.divaText(true, text);
break;
//FadeTo
case "fadeout":
this.text.dialogText(false, "");
this.text.characterName(false, "");
this.waitTime = Number(cur.Arg6) * 1000;
this.layers["bg|whiteFade"].sprite.tint = commonFunctions.getColorFromName(cur.Arg1);
this.lerpTargets.push({type: 'alpha', object: this.layers["bg|whiteFade"].sprite, curTime: 0, time: this.waitTime, finalV: 1, initV: 0});
@ -291,7 +362,8 @@ class Player {
let scale = 1 + (1 - Number(cur.Arg3));
let cont = this.layers["bg|mainparent"].container;
let x = this.center.x + -(Number(cur.Arg1));
let y = this.center.y + -(Number(cur.Arg2));
//y in xdu is flipped
let y = this.center.y - -(Number(cur.Arg2));
if(time) {
this.waitTime = time * 1000;
if(cont.scale.x !== scale) {
@ -307,11 +379,11 @@ class Player {
this.lerpTargets.push({type: 'position.x', object: cont, curTime: 0,
time: this.waitTime, finalV: x, initV: cont.position.x });
}
if(cont.position.y !== y) {
this.lerpTargets.push({type: 'position.y', object: cont, curTime: 0,
time: this.waitTime, finalV: y, initV: cont.position.y });
}
if(cur.Arg6 && cur.Arg6.toLowerCase() === "nowait") {
this.waitTime = 0;
}
@ -322,6 +394,8 @@ class Player {
break;
}
case "characteroff": {
this.text.dialogText(false, "");
this.text.characterName(false, "");
for(let c of Object.keys(this.currentCharacters)) {
if(!this.currentCharacters[c]) { continue; }
let curChar = this.currentCharacters[c];
@ -335,6 +409,7 @@ class Player {
}
}
case "tween":
this.processTween(delta, cur);
break;
case "bgm":
break;
@ -344,6 +419,9 @@ class Player {
break;
case "shake":
break;
case "henshin01_bgmoff":
this.checkPutCharacterScreen(cur, true);
break;
default:
this.processCommandOther(delta);
break;
@ -357,64 +435,174 @@ class Player {
processCommandOther(delta) {
let cur = this.currentCommand;
//Character on screen
checkPutCharacterScreen.apply(this);
this.checkPutCharacterScreen(cur);
//Display text
checkPutText.apply(this);
function checkPutCharacterScreen() {
if(!cur.Command && cur.Arg1 && this.utage.characterInfo[cur.Arg1]) {
if(!cur.Arg2) {
cur.Arg2 = this.defaultCharPattern;
}
let chr = this.utage.characterInfo[cur.Arg1][cur.Arg2];
let lay = undefined;
let chlay = undefined;
if(cur.Arg3) {
lay = this.layers[cur.Arg3];
chlay = this.currentCharacters[cur.Arg3];
if(!lay) { return; }
} else {
lay = this.lastCharOffLayer;
if(!lay) { return; }
cur.Arg3 = lay.info.LayerName;
}
if(this.currentCharacters[cur.Arg3] && this.currentCharacters[cur.Arg3].charName === cur.Arg1) {
return;
}
//If the layer already has a character on it remove it.
if(chlay && (chlay.character.NameText !== chr.NameText || chlay.character.Pattern !== chr.Pattern)) {
this.lerpTargets.push({type: 'alpha', object: chlay.sprite, curTime: 0, time: 100, finalV: 0, initV: 1, post: "destroy" });
this.currentCharacters[cur.Arg3] = undefined;
}
let sprite = new PIXI.Sprite(this.loader.resources[`char|${cur.Arg1}|${cur.Arg2}`].texture);
sprite.scale.set(Number(chr.Scale), Number(chr.Scale));
let anchor = commonFunctions.getAnchorFromCharPivot(chr.Pivot);
sprite.anchor.set(anchor.x, anchor.y);
sprite.alpha = 0;
this.currentCharacters[cur.Arg3] = { layer: lay, character: chr, charName: cur.Arg1, sprite: sprite };
this.lerpTargets.push({type: 'alpha', object: sprite, curTime: 0, time: 100, finalV: 1, initV: 0 });
lay.container.addChild(sprite);
lay.container.visible = true;
}
}
function checkPutText() {
if(!cur.Command && cur.Arg1 && cur.Text) {
if(cur.Arg2 && cur.Arg2.toLowerCase() === "<off>") {
this.text.characterName(true, cur.Arg1);
this.text.dialogText(true, commonFunctions.convertUtageTextTags(cur.Text));
} else {
let charName = "";
for(let c of Object.keys(this.currentCharacters)) {
if(!this.currentCharacters[c]) { continue; }
if(this.currentCharacters[c].charName === cur.Arg1) {
this.text.characterName(true, this.currentCharacters[c].character.NameText);
this.text.dialogText(true, commonFunctions.convertUtageTextTags(cur.Text));
break;
}
this.checkPutText(cur);
}
checkPutCharacterScreen(cur, special = false) {
if((!cur.Command || special) && cur.Arg1 && this.utage.characterInfo[cur.Arg1]) {
let lay = undefined;
let curChar = undefined;
//First check if the character is already on screen
for(let c of Object.keys(this.currentCharacters)) {
if(!this.currentCharacters[c]) { continue; }
if(this.currentCharacters[c].charName === cur.Arg1) {
curChar = this.currentCharacters[c];
lay = this.currentCharacters[c].layer;
if(!cur.Arg3) {
cur.Arg3 = c;
}
}
}
//Sometimes they don't give a pattern so just assume the default.
if(!cur.Arg2 && !curChar) {
cur.Arg2 = this.defaultCharPattern;
//If the character was already on screen use that pattern
} else if (!cur.Arg2 && curChar) {
cur.Arg2 = curChar.character.Pattern;
}
let chr = this.utage.characterInfo[cur.Arg1][cur.Arg2];
//If the script gives us a layer get that layer and if there is a character on it already.
if(cur.Arg3 && !curChar) {
lay = this.layers[cur.Arg3];
curChar = this.currentCharacters[cur.Arg3];
if(!lay) { return; }
//If they didn't give us a layer try to use the last layer a character was removed from.
} else if(!curChar) {
lay = this.lastCharOffLayer;
if(!lay) { return; }
cur.Arg3 = lay.info.LayerName;
}
//If this chracter is already here and not changing patterns don't change anything.
if(curChar && curChar.charName === cur.Arg1 && curChar.character.Pattern === cur.Arg2) {
return;
}
//If the layer already has a different character on it remove it.
if(curChar && (curChar.character.NameText !== chr.NameText || curChar.character.Pattern !== chr.Pattern)) {
this.lerpTargets.push({type: 'alpha', object: curChar.sprite, curTime: 0, time: 100, finalV: 0, initV: 1, post: "destroy" });
this.currentCharacters[cur.Arg3] = undefined;
}
let sprite = new PIXI.Sprite(this.loader.resources[`char|${cur.Arg1}|${cur.Arg2}`].texture);
sprite.scale.set(Number(chr.Scale), Number(chr.Scale));
let anchor = commonFunctions.getAnchorFromCharPivot(chr.Pivot);
sprite.anchor.set(anchor.x, anchor.y);
sprite.alpha = 0;
this.currentCharacters[cur.Arg3] = { layer: lay, character: chr, charName: cur.Arg1, sprite: sprite };
this.lerpTargets.push({type: 'alpha', object: sprite, curTime: 0, time: 100, finalV: 1, initV: 0 });
lay.container.addChild(sprite);
lay.container.visible = true;
}
}
checkPutText(cur) {
if(!cur.Command && cur.Arg1 && cur.Text) {
//If its chracter off screen text
var text = cur.English ? (utage.currentTranslation[cur.English] || cur.Text) : cur.Text;
text = commonFunctions.convertUtageTextTags(text);
if(cur.Arg2 && cur.Arg2.toLowerCase() === "<off>") {
this.text.characterName(true, cur.Arg1);
this.text.dialogText(true, commonFunctions.convertUtageTextTags(text));
} else {
let charName = "";
let found = true;
//Look for the character that is saying the text to get their name
//future note: This might be better to just look for the character in character info if this start failing.
for(let c of Object.keys(this.currentCharacters)) {
if(!this.currentCharacters[c]) { continue; }
if(this.currentCharacters[c].charName === cur.Arg1) {
this.text.characterName(true, this.currentCharacters[c].character.NameText);
this.text.dialogText(true, text);
this.currentCharacters[c].sprite.tint = 0xFFFFFF;
found = true;
continue;
}
//while were here set other characters tint to background shade
if(this.currentCharacters[c].sprite) {
this.currentCharacters[c].sprite.tint = this.backCharTint;
}
}
//If we didnt find the character just dump the text anyways with Arg1 as the name
if(!found) {
this.text.characterName(true, cur.Arg1);
this.text.dialogText(true, text);
}
}
this.manualNext = true;
} else if(!cur.Command && cur.Arg2.toLowerCase() === "<off>" && cur.Text) {
var text = cur.English ? (utage.currentTranslation[cur.English] || cur.Text) : cur.Text;
this.text.characterName(true, "");
this.text.dialogText(true, commonFunctions.convertUtageTextTags(text));
this.manualNext = true;
}
}
processTween(delta, cur) {
this.text.dialogText(false, "");
this.text.characterName(false, "");
let curChar = undefined;
for(let c of Object.keys(this.currentCharacters)) {
if(!this.currentCharacters[c]) { continue; }
if(this.currentCharacters[c].charName === cur.Arg1) {
curChar = this.currentCharacters[c];
this.currentCharacters[c].sprite.tint = 0xFFFFFF;
continue;
}
//while were here set other characters tint to background shade
if(this.currentCharacters[c].sprite) {
this.currentCharacters[c].sprite.tint = this.backCharTint;
}
}
if(!curChar) { return; }
switch(cur.Arg2.toLowerCase()) {
case "moveto": {
let props = commonFunctions.getPropertiesFromTweenCommand(cur.Arg3);
//moveto has a islocal value that im just assuming is true until I run into a case it actually isint.
if(!cur.Arg6 || cur.Arg6 !== "NoWait") {
this.waitTime = props.time + (props.delay || 0);
}
if(props.x) {
if(props.time) {
this.lerpTargets.push({type: 'position.x', object: curChar.sprite, curTime: 0 - (props.delay || 0), time: props.time,
finalV: curChar.sprite.position.x + props.x, initV: curChar.sprite.position.x, inter: 'exp' });
} else {
curChar.sprite.position.x = curChar.sprite.position.x + props.x;
}
}
if(props.y) {
if(props.time) {
this.lerpTargets.push({type: 'position.y', object: curChar.sprite, curTime: 0 - (props.delay || 0), time: props.time,
finalV: curChar.sprite.position.y + props.y, initV: curChar.sprite.position.y, inter: 'exp' });
} else {
curChar.sprite.position.y = curChar.sprite.position.y + props.y;
}
}
break;
}
case "punchposition": {
let props = commonFunctions.getPropertiesFromTweenCommand(cur.Arg3);
if(!props.time) { props.time = 0.5; }
if(!cur.Arg6 || cur.Arg6 !== "NoWait") {
this.waitTime = props.time + (props.delay || 0);
}
if(props.x) {
this.lerpTargets.push({type: 'position.x', object: curChar.sprite, curTime: 0 - (props.delay || 0), time: props.time,
finalV: curChar.sprite.position.x + props.x, initV: curChar.sprite.position.x, inter: 'fullwait' });
}
if(props.y) {
this.lerpTargets.push({type: 'position.y', object: curChar.sprite, curTime: 0 - (props.delay || 0), time: props.time,
finalV: curChar.sprite.position.y + props.y, initV: curChar.sprite.position.y, inter: 'fullwait' });
}
break;
}
case "colorto": {
let props = commonFunctions.getPropertiesFromTweenCommand(cur.Arg3);
if(props.alpha) {
if(props.time) {
} else {
curChar.sprite.alpha = 0;
}
}
this.waitTime = 1000;
}
}
}
@ -439,14 +627,33 @@ class Player {
return true;
}
onMainClick() {
if(this.runEvent && this.manualNext) {
if (!this.hasMoreText) {
this.getNextCommand();
} else {
}
onMainClick(event) {
if(!this.runEvent) {
return
}
event.preventDefault();
event.stopPropagation();
if(this.manualNext && !this.uiHidden) {
if (!this.text.scrollingText) {
this.manualNext = false;
this.waitTime = 0;
} else if(this.text.scrollingText) {
this.text.showDialogFullText();
}
} else if(this.uiHidden) {
this.text.hideUi(true);
this.uiHidden = false;
}
}
hideUiClicked(event) {
if(!this.runEvent) {
return;
}
event.preventDefault();
event.stopPropagation();
this.uiHidden = true;
this.text.hideUi(false);
}
onEndFile() {
@ -466,6 +673,16 @@ class Player {
this.currentCommand = command;
}
updateResolution(res) {
//this.resolutionScale = res.height / baseDimensions.height;
//this.center = {x: ((baseDimensions.width / 2) * this.resolutionScale), y: ((baseDimensions.height / 2) * this.resolutionScale) };
//this.pixi.app.renderer.resolution = res.height / baseDimensions.height;
let newScale = res.height / baseDimensions.height;
this.pixi.app.stage.scale.set(newScale, newScale);
this.pixi.app.renderer.resize(res.width, res.height);
document.getElementById('text-container').style.cssText = `transform: scale(${newScale})`;
}
resetAll() {
return new Promise((resolve, reject) => {
try {
@ -478,9 +695,19 @@ class Player {
}
this.loader.reset();
this.currentCharacters = {};
this.lastCharOffLayer = undefined;
this.layers = {};
this.sprites = {};
this.blackBackSp = undefined;
this.currentCommand = undefined;
this.runEvent = false;
this.secondTicker = 1000;
this.waitTime = 0;
this.lerpTargets = [];
this.manualNext = false;
this.hasMoreText = false;
this.audioLoadPercent = 0;
this.assetLoadPercent = 0;
this.text.resetAll();
resolve();
} catch (error) {

Näytä tiedosto

@ -8,6 +8,10 @@ class TextFunctions {
this.dialogBox = undefined;
this.character = undefined;
this.dialog = undefined;
this.dialogToDisplay = {timeout: undefined, fullText: "", text: "", subsection: "", subindex: -1};
this.textScrollSpeedMs = 40;
this.scrollingText = false;
this.lineHeight = -1;
}
findTextElements() {
@ -17,6 +21,7 @@ class TextFunctions {
this.dialogBox = document.getElementById('dialog-box');
this.character = document.getElementById('character');
this.dialog = document.getElementById('dialog');
this.dialogInner = document.getElementById('dialog-inner');
}
titleText(show, text) {
@ -42,18 +47,90 @@ class TextFunctions {
}
dialogText(show, text) {
if(this.lineHeight == -1) {
this.lineHeight = Number(window.getComputedStyle(this.dialog, null).getPropertyValue("line-height").replace('px', ''));
}
if(text != undefined) {
this.dialog.innerHTML = text;
if(this.dialogToDisplay.timeout) {
clearTimeout(this.dialogToDisplay.timeout);
this.dialogToDisplay.timeout = undefined;
}
if(text === "") {
this.dialogInner.innerHTML = text;
this.scrollingText = false;
} else {
this.dialogToDisplay.text = text;
this.dialogToDisplay.fullText = text;
this.dialogInner.innerHTML = this.dialogToDisplay.text[0];
this.dialogToDisplay.text = this.dialogToDisplay.text.slice(1);
this.scrollingText = true;
this.dialogToDisplay.timeout = setTimeout(putText.bind(this), this.textScrollSpeedMs);
}
}
this.mainUi.style = show ? "opacity: 1;" : "opacity: 0;";
this.dialogBox.style = show ? "opacity: 1;" : "opacity: 0;";
function putText() {
let t = this.getNextTextToPut();
if(!t) { return; }
this.dialogInner.innerHTML += t;
let lHeight = this.lineHeight * 2
if(this.dialogInner.offsetHeight > lHeight) {
this.dialog.scrollTop = this.dialogInner.offsetHeight - lHeight;
}
this.dialogToDisplay.timeout = setTimeout(putText.bind(this), this.textScrollSpeedMs);
}
}
showDialogFullText() {
if(this.dialogToDisplay.timeout) {
clearTimeout(this.dialogToDisplay.timeout);
this.dialogToDisplay.timeout = undefined;
}
this.dialogInner.innerHTML = this.dialogToDisplay.fullText;
let lHeight = this.lineHeight * 2
if(this.dialogInner.offsetHeight > lHeight) {
this.dialog.scrollTop = this.dialogInner.offsetHeight - lHeight;
}
this.scrollingText = false;
}
hideUi(show) {
this.mainUi.style = show ? "opacity: 1;" : "opacity: 0;";
this.dialogBox.style = show ? "opacity: 1;" : "opacity: 0;";
this.character.style = show ? "opacity: 1;" : "opacity: 0;";
}
getNextTextToPut() {
if(!this.dialogToDisplay.text || this.dialogToDisplay.text.length === 0) {
this.scrollingText = false;
return "";
}
let toSlice = 1;
let text = this.dialogToDisplay.text[0];
if(text === '<') {
let match = this.dialogToDisplay.text.match(/<.+>/);
if(match && match[0]); {
debugger;
text = match[0];
toSlice = match[0].length;
if(!text.includes("/")) {
let s = text.split('');
s.splice(1, 0, '/');
text += s.join('');
}
}
}
this.dialogToDisplay.text = this.dialogToDisplay.text.slice(toSlice);
return text;
}
resetAll() {
this.title.innerHTML = '';
this.diva.innerHTML = '';
this.character.innerHTML = '';
this.dialog.innerHTML = '';
this.dialogInner.innerHTML = '';
this.title.style = "opacity: 0;";
this.diva.style = "opacity: 0;";
this.mainUi.style = "opacity: 0;";

Näytä tiedosto

@ -13,11 +13,12 @@ class UtageInfo {
this.paramInfo = {};
this.soundInfo = {};
this.textureInfo = {};
this.currentTranslation = {};
}
loadUtageSettings(resolve, reject) {
return new Promise((resolve, reject) => {
var promises = [
let promises = [
commonFunctions.getFileJson(`${this.rootDirectory}Js/XduMissions.json`),
commonFunctions.getFileText(`${this.rootDirectory}XDUData/Utage/Diva/Settings/Character.tsv`),
commonFunctions.getFileText(`${this.rootDirectory}XDUData/Utage/Diva/Settings/Layer.tsv`),
@ -51,14 +52,14 @@ class UtageInfo {
return new Promise((resolve, reject) => {
commonFunctions.getFileText(file)
.then((success) => {
var lines = success.split("\n");
var headers = [];
let lines = success.split("\n");
let headers = [];
for(let i = 0; i < lines.length; ++i) {
var line = lines[i];
let line = lines[i];
if(i === 0) {
headers = line.split('\t');
headers = line.trim().split('\t');
} else {
var read = commonFunctions.readLine(line, headers);
let read = commonFunctions.readLine(line, headers);
if(read) {
this.currentPlayingFile.push(read);
}
@ -73,18 +74,31 @@ class UtageInfo {
});
}
loadMissionTranslation(file) {
return new Promise((resolve, reject) => {
commonFunctions.getFileJson(file)
.then((success) => {
this.currentTranslation = success;
resolve();
}, (failure) => {
this.currentTranslation = {};
resolve();
});
});
}
//http://madnesslabo.net/utage/?page_id=4521&lang=en
parseCharacterInfo(text) {
var lines = text.split("\n");
var headers = [];
var lastCharName = '';
var lastNameText = '';
let lines = text.split("\n");
let headers = [];
let lastCharName = '';
let lastNameText = '';
for(let i = 0; i < lines.length; ++i) {
var line = lines[i];
let line = lines[i];
if(i === 0) {
headers = line.split('\t');
} else {
var read = commonFunctions.readLine(line, headers);
let read = commonFunctions.readLine(line, headers);
if(read && !read.comment) {
//If character name is empty it means it belongs to the line before it
// so I am grouping the patterns by character name.
@ -111,14 +125,14 @@ class UtageInfo {
//http://madnesslabo.net/utage/?page_id=4518&lang=en
parseLayerInfo(text) {
var lines = text.split("\n");
var headers = [];
let lines = text.split("\n");
let headers = [];
for(let i = 0; i < lines.length; ++i) {
var line = lines[i];
let line = lines[i];
if(i === 0) {
headers = line.split('\t');
} else {
var read = commonFunctions.readLine(line, headers);
let read = commonFunctions.readLine(line, headers);
if(read && read.LayerName) {
this.layerInfo[read.LayerName] = read;
}
@ -128,14 +142,14 @@ class UtageInfo {
//http://madnesslabo.net/utage/?page_id=4514&lang=en
parseLocalizeInfo(text) {
var lines = text.split("\n");
var headers = [];
let lines = text.split("\n");
let headers = [];
for(let i = 0; i < lines.length; ++i) {
var line = lines[i];
let line = lines[i];
if(i === 0) {
headers = line.split('\t');
} else {
var read = commonFunctions.readLine(line, headers);
let read = commonFunctions.readLine(line, headers);
if(read && read.Key) {
this.localizeInfo[read.Key] = read;
}
@ -145,14 +159,14 @@ class UtageInfo {
//http://madnesslabo.net/utage/?page_id=4517&lang=en
parseParamInfo(text) {
var lines = text.split("\n");
var headers = [];
let lines = text.split("\n");
let headers = [];
for(let i = 0; i < lines.length; ++i) {
var line = lines[i];
let line = lines[i];
if(i === 0) {
headers = line.split('\t');
} else {
var read = commonFunctions.readLine(line, headers);
let read = commonFunctions.readLine(line, headers);
if(read && read.Label) {
this.paramInfo[read.Label] = read;
}
@ -162,14 +176,14 @@ class UtageInfo {
//http://madnesslabo.net/utage/?page_id=4519&lang=en
parseSoundInfo(text) {
var lines = text.split("\n");
var headers = [];
let lines = text.split("\n");
let headers = [];
for(let i = 0; i < lines.length; ++i) {
var line = lines[i];
let line = lines[i];
if(i === 0) {
headers = line.split('\t');
} else {
var read = commonFunctions.readLine(line, headers);
let read = commonFunctions.readLine(line, headers);
if(read && read.Label) {
if(read.FileName && read.Type) {
if(!read.FileName.includes('.')) {
@ -178,7 +192,7 @@ class UtageInfo {
switch(read.Type.toLowerCase()) {
case 'se':
if(read.FileName.includes(',')) {
var s = read.FileName.split(',');
let s = read.FileName.split(',');
read.FileName = `${s[0].split('_').join('/')}/${s[1]}`;
}
read.FileName = `${this.rootDirectory}XDUData/Se/${read.FileName}`;
@ -196,14 +210,14 @@ class UtageInfo {
//http://madnesslabo.net/utage/?page_id=4520&lang=en
parseTextureInfo(text) {
var lines = text.split("\n");
var headers = [];
let lines = text.split("\n");
let headers = [];
for(let i = 0; i < lines.length; ++i) {
var line = lines[i];
let line = lines[i];
if(i === 0) {
headers = line.split('\t');
} else {
var read = commonFunctions.readLine(line, headers);
let read = commonFunctions.readLine(line, headers);
if(read && read.Label) {
read.FileName = `${this.rootDirectory}XDUData/BG/${read.FileName}`;
this.textureInfo[read.Label] = read;

21
LICENSE.txt Normal file
Näytä tiedosto

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2017 Matt Boldt
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, andor 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.

Näytä tiedosto

@ -4,36 +4,39 @@
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
<link rel="stylesheet" type="text/css" href="../Css/generic.min.css">
<link rel="stylesheet" type="text/css" href="Css/main.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/4.5.1/pixi.min.js"></script>
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/4.5.1/pixi.min.js"></script> -->
<script src="Js/Pixi.min.js"></script>
<script src="Js/Common.js"></script>
<script src="Js/TextFunctions.js"></script>
<script src="Js/UtageParse.js"></script>
<script src="Js/Audio.js"></script>
<script src="Js/Player.js"></script>
<script src="Js/Main.js"></script>
</head>
<body onload="onBodyLoaded()">
<script src="Js/Main.js"></script>
<div id="loading-container" class="centered">
<h2 id="loading-utage">Loading Utage Data...</h2>
<h2 id="loading-font">Loading Fonts...</h2>
</div>
<div id="parent-container" class="fade-in centered">
<div id="select-mission-container">
<select id="select-mission" onchange="missionChanged(event)"></select>
<div id="other-controls-container">
<div id="volume-control">🔊</div>
<select id="select-mission" onchange="missionChanged(event);"></select>
</div>
<!-- Im doing weird shit so that app container is always the base resolution of XDU (1334, 750).
<!-- Im doing weird shit so that text container is always the base resolution of XDU (1334, 750).
The canvas then resizes as the canvas does and the text-container uses transform scale based off this resolution -->
<div id="app-container">
<div id="text-container" on-click="onTextClick(event)">
<div id="app-container" onclick="onMainClick(event);">
<div id="text-container">
<img id="main-ui-img" src="Images/newui_main.png" style="opacity: 0;"/>
<div id="title" style="opacity: 0;">Title Text</div>
<div id="diva" style="opacity: 0;">Diva Text</div>
<div id="dialog-box" style="opacity: 0;">
<div id="ui-buttons">
<div id="ui-buttons" onclick="hideUiClicked(event)">
<img class="ui-button" src="Images/hide_button.png"/>
</div>
<div id="character">女教師</div>
<div id="character"></div>
<div id="dialog">
「はい今日は前回の続きからはじめます。<br/> 抑揚をつけることで、歌の表現は深まりますが――」
<div id ="dialog-inner"></div>
</div>
</div>
</div>