diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..3c6bd60 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +Js/Pixi.min.js diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..c92e89c --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,29 @@ +{ + "env": { + "browser": true, + "es6": true + }, + "extends": "eslint:recommended", + "globals": { + "Atomics": "readonly", + "SharedArrayBuffer": "readonly", + "PIXI": "readonly", + "UtageInfo": "readonly", + "TextFunctions": "readonly", + "Player": "readonly", + "Shaders": "readonly", + "baseDimensions": "readonly", + "commonFunctions": "readonly", + "audioController": "readonly" + }, + "parserOptions": { + "ecmaVersion": 2018, + "sourceType": "module" + }, + "rules": { + "no-console": "off", + "no-unused-vars": [2, { "vars": "local", "argsIgnorePattern": "^(success|event|resource|delta|reject)$"}], + "no-empty": ["error", { "allowEmptyCatch": true }], + "no-mixed-spaces-and-tabs": "off" + } +} diff --git a/.gitignore b/.gitignore index 65a42c1..60674bd 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,9 @@ / web.config /Js/Typed +/Js/[Pp]ixi.js +/Js/[Pp]ixi.js.map +/Js/[Pp]ixi.min.js.map /node_modules /Js/XduPlayer.js /Js/XduPlayer.min.js.map @@ -9,3 +12,4 @@ web.config /Dist /Css/main.min.css /Js/XduPlayer.min.js +/**/.*.swp \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..2ce17f3 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,8 @@ +[submodule "Js/Translations"] + path = Js/Translations + url = https://git.poweris.moe/yttt-xdu/xdutranslations.git + branch = master +[submodule "CustomData"] + path = CustomData + url = https://git.poweris.moe/yttt-xdu/customdata.git + branch = . diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d5b8181 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,50 @@ +## V1.1.0 (2018-06-08) + +Release for Bridal Gears. +Added various sound effects for some hit effects so there is just nothing when they happen. +Updated Pixi JS to 4.8.1. +Support for custom missions. +Made translations a submodule. +Various fixes to xdu functions. + +## V1.2.0 (2018-06-13) + +Added title tag that updates with current mission. +Added null check for pixi sprite calculateverticies because it sometimes makes a texture invalid when it shouldn't be. This is kinda a hack and should be investigated deeper in the future. +DPI is now taken into account when resizing. +Added credits to mission modal. +Added ability to go fullscreen. +Missions JSON moved to translations submodule so missions can be added/enabled without disturbing main project. + +## V1.2.1 (2018-12-29) + +Custom asset support +Russian language support +Fix translation fallback + +## V1.2.2 (2019-01-20) + +Add language url parameter +Fix noise\_disappearance commands + +## V1.3.0 (2019-05-13) + +Macro support +DivaMovie +Changed metadata to include quests +Per-language quest enabling +Sort scenes into quests to reduce clutter +Preserve ColorTo tinting properly +urlparam: questSceneMstId + +## V1.3.1 (2019-06-09) + +Fix MoveCamera macro + +## V1.4.0 (2019-09-29) + +Updated Pixi.js to v5 + +## V1.5.0 (2020-07-31) + +XDU Global support diff --git a/Css/generic.min.css b/Css/generic.min.css index fb2a566..08dcf65 100644 --- a/Css/generic.min.css +++ b/Css/generic.min.css @@ -1 +1 @@ -html{font-size:16px;font-family:arial;background-color:#393939;color:rgba(255,255,255,.7)}html body{border-color:rgba(255,255,255,.2)}html body a{color:#0096cf}html body a:not([href]){text-decoration:underline;cursor:pointer}html body .hover-span{color:#0096cf}.fade-in{opacity:0;transition:opacity .5s} \ No newline at end of file +html{font-size:16px;font-family:arial;background-color:#393939;color:rgba(255,255,255,.7)}html body{border-color:rgba(255,255,255,.2)}html body a{color:#0096cf}html body a:not([href]){text-decoration:underline;cursor:pointer}html body .hover-span{color:#0096cf}.fade-in{opacity:0;transition:opacity .5s}.centered{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);text-align:center} \ No newline at end of file diff --git a/Css/main.css b/Css/main.css index c0c3655..779762e 100644 --- a/Css/main.css +++ b/Css/main.css @@ -35,10 +35,20 @@ } @font-face { - font-family: SourceCodePro-Regular; - src: url(../Fonts/SourceCodePro-Regular.woff2) format('woff'); + font-family: Orbitron-Medium; + src: url(../Fonts/Orbitron Medium.woff2) format('woff'); } +@font-face { + font-family: PTSans; + src: url(../Fonts/PTSans.woff2) format('woff'); +} + +/*@font-face { + font-family: SourceSansVariable-Roman; + src: url(../Fonts/SourceSansVariable-Roman.woff2) format('woff'); +}*/ + @-webkit-keyframes smallbounce { from { transform: translate(0, 5px); } to { transform: translate(0, -5px); } @@ -52,9 +62,11 @@ to { transform: translate(0, -5px); } } -body { margin: 0; } +html { height: 100%; } -.centered { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; } +body { margin: 0; height: 100%; } + +.flex-grow { flex-grow: 1; } .hidden { opacity: 0; } @@ -62,9 +74,19 @@ body { margin: 0; } #app-container { position: relative; width: 1334px; height: 750px; display: flex; justify-content: center; align-items: center; } -#parent-container { display: flex; flex-direction: column; align-items: center; } +#parent-container { height: 100%; display: flex; flex-direction: column; align-items: center; } -#text-container { position: absolute; margin: auto; height: 750px; width: 1334px; font-family: 'FOT-RodinNTLGPro'; } +#text-container { color: white; position: absolute; margin: auto; height: 750px; width: 1334px; font-family: 'FOT-RodinNTLGPro'; } + +#text-container.eng { font-family: 'FOT-RodinNTLGPro'; } + +#text-container.jpn { font-family: 'FOT-RodinNTLGPro'; } + +#text-container.rus { font-family: 'PTSans'; } + +#text-container.cze { font-family: 'PTSans'; } + +#text-container #fullscreen-button { position: absolute; top: 0.5rem; left: 0.5rem; font-size: 30px; line-height: 30px; opacity: 0.35; z-index: 11; } #text-container #title { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; font-size: 20px; transition: opacity 0.3s; cursor: default; user-select: none; } @@ -74,7 +96,7 @@ body { margin: 0; } #text-container #main-ui-img { width: 100%; height:100%; transition: opacity 0.1s; } -#dialog-box { color: white; font-weight: bold; text-shadow: 1px 1px 6px black; transition: opacity 0.1s; } +#dialog-box { font-weight: bold; text-shadow: 1px 1px 6px black; transition: opacity 0.1s; } #dialog-box #character { position: absolute; left: 70px; font-size: 30px; cursor: default; user-select: none; } @@ -100,11 +122,13 @@ body { margin: 0; } #dialog-scroll img { height: 35px; } -#title-container { margin-bottom: 2px; } +#title-container { padding: 2px 0; } -#other-controls-container { display: flex; width: 550px; justify-content: center; align-items: center; z-index: 10; } +#other-controls-container { padding-bottom: 4px; display: flex; width: 100%; justify-content: center; align-items: center; z-index: 10; } -#select-mission { min-width: 0; } +#select-quest { min-width: 0; width: 25% } + +#select-scene { min-width: 0; width: 25% } #select-language { margin-left: 10px; } @@ -116,7 +140,7 @@ body { margin: 0; } #modal-container { position: fixed; z-index: 16; top: 50%; left: 50%; transform: translate(-50%, -50%); } -.modal { background-color: #393939; color: #F0F0F0; height: 400px; width: 350px; position: relative; margin: auto; display: flex; flex-direction: column; align-items: center; box-shadow: 0px 0px 20px 3px #070707; border-radius: 7px; box-sizing: border-box; padding: 10px; } +.modal { background-color: #393939; color: #F0F0F0; height: 425px; width: 350px; position: relative; margin: auto; display: flex; flex-direction: column; align-items: center; box-shadow: 0px 0px 20px 3px #070707; border-radius: 7px; box-sizing: border-box; padding: 10px; } #mission-modal { } @@ -124,15 +148,21 @@ body { margin: 0; } #mission-modal #mission-icon { display: none; margin: 10px 0 10px 0; object-fit: contain; } -#mission-modal span { max-width: 100%; word-break: break-word; max-height: 120px; overflow: auto;} +#mission-modal #mission-summary { max-width: 100%; word-break: break-word; margin-bottom: 5px; min-height: 0; overflow-y: auto; } #mission-modal .mission-title { font-weight: bold; text-align: center; } -#mission-modal #mission-ids { margin-top: auto; width: 100%; display: flex; flex-direction: column; align-items: center; } +#mission-modal #mission-ids { margin: 5px 0 5px 0; width: 100%; display: flex; flex-direction: column; align-items: center; min-height: 21px; } #mission-modal #mission-ids div { display: flex; align-items: center; } -#modal-buttons { bottom: 0; margin-top: auto; width: 100%; display: flex; justify-content: space-between; } +#modal-buttons { bottom: 0; margin-top: auto; width: 100%; display: flex; justify-content: space-between; min-height: 21px; } + +#mission-modal .follow-links { margin-top: auto; display: flex; flex-direction: column; text-align: center; width:100%; } + +#mission-modal .follow-links .follow-links-header { font-weight: bold; } + +#mission-modal .follow-links .follow-links-links { display: flex; flex-direction: row; justify-content: space-around; margin-top: 5px; } @media screen and (max-width: 812px) { #modal-container { top: 0; bottom: 0; left: 0; right: 0; transform: none; } @@ -147,4 +177,4 @@ body { margin: 0; } @media screen and (max-height: 600px) { #mission-modal #mission-detail { display: none; } #mission-modal #mission-icon { display: block; max-height: 20%; margin: 10px 0 10px 0; object-fit: contain; } -} \ No newline at end of file +} diff --git a/CustomData b/CustomData new file mode 160000 index 0000000..e92af8f --- /dev/null +++ b/CustomData @@ -0,0 +1 @@ +Subproject commit e92af8f0d0c0d4246e44dd0b63508f9e460fe3a8 diff --git a/Js/Audio.js b/Js/Audio.js index 2aff8ad..bb1c33b 100644 --- a/Js/Audio.js +++ b/Js/Audio.js @@ -1,5 +1,4 @@ //(Math.exp(x)-1)/(Math.E-1) -//🔊 class bufferLoader { constructor(context, soundMap, callback) { @@ -90,7 +89,7 @@ class audioController { if(!this.loader.bufferList[sound]) { return; } - let source = this.audioCtx.createBufferSource() + let source = this.audioCtx.createBufferSource(); this.sources[sound] = source; source.buffer = this.loader.bufferList[sound]; source.loop = false; @@ -153,12 +152,18 @@ class audioController { } loadSounds(soundMap, callback) { - this.loader = new bufferLoader(this.audioCtx, soundMap, (percent) => { + if(!soundMap || soundMap.length === 0) { if (callback) { - callback(percent); + callback(100); } - }); - this.loader.load(); + } else { + this.loader = new bufferLoader(this.audioCtx, soundMap, (percent) => { + if (callback) { + callback(percent); + } + }); + this.loader.load(); + } } resetAll() { @@ -176,4 +181,4 @@ class audioController { } this.sources = {}; } -} \ No newline at end of file +} diff --git a/Js/Common.js b/Js/Common.js index c3f152b..31f73fb 100644 --- a/Js/Common.js +++ b/Js/Common.js @@ -4,6 +4,11 @@ let rootUrl = `${window.location.protocol}//${window.location.host}/`; const baseDimensions = {width: 1334, height: 750}; const screenRatio = 9/16; +const CUSTOM = { + custom: 'Custom', + stock: 'Stock' +} + class commonFunctions { static getFileText(file) { return new Promise((resolve, reject) => { @@ -158,11 +163,12 @@ class commonFunctions { let color = ''; name = name.substring(1); if(name.length === 8) { - color = name.slice(0, 6); - alpha = name.slice(6, 8); + color = parseInt(name.slice(0, 6), 16); + alpha = parseInt(name.slice(6, 8), 16) / 255; + } else { + color = parseInt(name, 16); + alpha = 1; } - color = parseInt(color, 16); - alpha = parseInt(alpha, 16) / 255; return { color, alpha }; } else { switch(name.toLowerCase()) { @@ -184,6 +190,15 @@ class commonFunctions { return [r/255, g/255, b/255]; } + static componentToHex(c) { + var hex = parseInt(c).toString(16); + return hex.length == 1 ? "0" + hex : hex; + } + + static rgbToHex(rgb) { + return `#${this.componentToHex(rgb[0] * 255)}${this.componentToHex(rgb[1]* 255)}${this.componentToHex(rgb[2]* 255)}`; + } + static convertUtageTextTags(text) { text = text.replace(/|<\/speed>/g, ""); text = text.replace(/\\n/g, "
") @@ -317,6 +332,14 @@ class commonFunctions { } retval.speed = Number(retval.speed); } + let indexC = props.indexOf("color="); + if(indexC !== -1) { + retval.color = ""; + for(let i = indexC + 6; i < props.length; ++i) { + if(props[i] == " ") { break; } + retval.color += props[i]; + } + } return retval; } -} \ No newline at end of file +} diff --git a/Js/Main.js b/Js/Main.js index 1280765..7ba61db 100644 --- a/Js/Main.js +++ b/Js/Main.js @@ -2,7 +2,7 @@ const pixiApp = { app: new PIXI.Application(baseDimensions), - loader: PIXI.loader + loader: PIXI.Loader.shared }; const utage = new UtageInfo(); @@ -10,25 +10,40 @@ const shaders = new Shaders(); const textFunc = new TextFunctions(); let audio = undefined; //Cant create a audio context without user input. const player = new Player(pixiApp, utage, textFunc, audio, shaders); -const languages = ["eng", "jpn"]; +const languages = ["eng", "jpn", "rus", "cze", "enm", "kor", "zho"]; +const version = "YameteTomete XDUPlayer V1.5.0"; let bodyLoaded = false; let utageLoaded = false; let languagesLoaded = false; let selectedLang = "eng"; -let currentMission = undefined; -let currentMissionMst = 0; -let currentMissionIndex = 0; -let currentMissionList = []; +let currentScene = {}; +let currentSceneId = ""; +let scenePlaylist = []; +let currentPart = ""; +let partPlaylist = []; let urlParams = {}; let screenw = Math.max(document.documentElement.clientWidth, window.innerWidth || 0); let screenh = Math.max(document.documentElement.clientHeight, window.innerHeight || 0); let screenSizeTimeout = undefined; let isMuted = false; let volume = 0.5; -let prevMission = '{Select}'; +let fullScreen = false; +let prevScene = '{Select}'; +let prevQuest = '{Select}'; + +const emoji = { + LoudSound: String.fromCodePoint(0x1f50a), + Mute: String.fromCodePoint(0x1f507), + HeavyPlusSign: String.fromCodePoint(0x2795) +}; function onBodyLoaded() { bodyLoaded = true; + document.getElementById("title-tag").innerText = version; + document.addEventListener('webkitfullscreenchange', onFullScreenChange, false); + document.addEventListener('mozfullscreenchange', onFullScreenChange, false); + document.addEventListener('fullscreenchange', onFullScreenChange, false); + document.addEventListener('MSFullscreenChange', onFullScreenChange, false); } (function startLoad() { @@ -47,6 +62,7 @@ function onBodyLoaded() { (function checkIsLoaded() { if(bodyLoaded) { document.getElementById('loading-font').style.cssText = "display: none;"; + checkQueryParameters(); loadLocalStorage(); } if(utageLoaded && languagesLoaded) { @@ -62,7 +78,8 @@ function onBodyLoaded() { function onAllLoaded(success) { textFunc.findTextElements(); - buildMissionSelectList(); + buildQuestSelectList(); + buildSceneSelectList(); buildLanguageList(); let appContainer = document.getElementById('app-container'); appContainer.appendChild(pixiApp.app.view); @@ -88,15 +105,16 @@ function loadLocalStorage() { audio.mute(isMuted); } if(isMuted) { - document.getElementById('mute-button').innerText = "🔇"; + document.getElementById('mute-button').innerText = emoji.Mute; } else { - document.getElementById('mute-button').innerText = "🔊"; + document.getElementById('mute-button').innerText = emoji.LoudSound; } //language - let lang = localStorage.getItem('language') || "eng"; + let lang = urlParams['lang'] || localStorage.getItem('language') || "eng"; if(languages.includes(lang)) { selectedLang = lang; } + document.getElementById('text-container').className = selectedLang; utage.setTranslationLanguage(selectedLang, '') .then((success) => { languagesLoaded = true; @@ -109,29 +127,79 @@ function loadLocalStorage() { } } -function buildMissionSelectList() { - let selectBox = document.getElementById('select-mission'); - selectBox.innerHTML = ''; - for(let i = -1; i < utage.missionsList.length; ++i) { - let opt = document.createElement('option'); - if(i === -1) { +function buildQuestSelectList() { + let questBox = document.getElementById('select-quest'); + questBox.innerHTML = ''; + for (let i = -1; i < utage.questList.length; ++i) { + let opt = document.createElement('option') + if (i === -1) { opt.setAttribute('value', '{Select}'); - opt.innerText = 'Select Mission'; + opt.innerText = 'Select Event'; } else { - let m = utage.missionsList[i]; - //Only allowing 3.5 right now - //if(!(m.MstId >= 104001 && m.MstId <= 104008)) { - if(m.MstId !== 202070) { + let q = utage.questList[i]; + let cust = q.IsCustom ? CUSTOM.custom : CUSTOM.stock; + let name = q.Name; + let tl_key = utage.questTranslations[cust][q.QuestMstId]; + if (!tl_key) { + console.log("Failed to build quest list: missing translations"); continue; } - opt.setAttribute('value', m.MstId); - let name = m.Name; - if(utage.missionTranslations[m.MstId]) { - name = utage.missionTranslations[m.MstId].Name || name; + if (!tl_key.Enabled && !utage.quests[cust][q.QuestMstId].Scenes.some((s) => { return utage.sceneTranslations[cust][s].Enabled === true })) { + continue; } + name = tl_key.Name || name; + opt.setAttribute('value', `${cust}|${q.QuestMstId}`); opt.innerText = name; } - selectBox.appendChild(opt); + questBox.appendChild(opt); + } +} + +function buildSceneSelectList() { + let sceneBox = document.getElementById('select-scene'); + let questBox = document.getElementById('select-quest'); + sceneBox.innerHTML = ''; + + let opt = document.createElement('option'); + opt.setAttribute('value', '{Select}'); + opt.innerText = "Select Scene"; + + if (questBox.value === '{Select}') { + sceneBox.appendChild(opt); + sceneBox.setAttribute("disabled", "true"); + return; + } else { + sceneBox.removeAttribute("disabled"); + } + + let cust = questBox.value.split("|")[0]; + let questMstId = questBox.value.split("|")[1]; + + for (let i = -2; i < utage.quests[cust][questMstId].Scenes.length; ++i) { + let opt = document.createElement('option'); + if (i === -2) { + opt.setAttribute('value', '{Select}'); + opt.innerText = 'Select Scene'; + } else if (i === -1) { + opt.setAttribute('value', '{All}'); + opt.innerText = 'Play All'; + } else { + let questSceneMstId = utage.quests[cust][questMstId].Scenes[i]; + let s = utage.scenes[cust][questSceneMstId]; + opt.setAttribute('value', `${cust}|${questSceneMstId}`); + let name = s.Name; + let tl_key = utage.sceneTranslations[cust][questSceneMstId]; + if (!tl_key) { + console.log("Failed to build scene list: missing translations"); + continue; + } + if (!tl_key.Enabled) { + continue; + } + name = tl_key.Name || name; + opt.innerText = name; + } + sceneBox.appendChild(opt); } } @@ -149,47 +217,118 @@ function buildLanguageList() { function checkQueryParameters() { urlParams = commonFunctions.readQueryParameters(); - if(urlParams['mstid'] && urlParams['id'] && utage.groupedMissions[urlParams['mstid']] && utage.groupedMissions[urlParams['mstid']].Missions[urlParams['id']]) { - document.getElementById('play-from-query').style.cssText = "position: fixed; z-index: 15; text-align: center; top: 50%; left: 50%; display: block;"; + if (languagesLoaded) { + let cust; + + if (urlParams['custom'] && urlParams['custom'] === "1") { + cust = CUSTOM.custom; + } else { + cust = CUSTOM.stock; + } + + let playable = (urlParams['questscenemstid'] && + utage.scenes[cust][urlParams['questscenemstid']] && + utage.sceneTranslations[cust][urlParams['questscenemstid']] && + utage.sceneTranslations[cust][urlParams['questscenemstid']].Enabled); + + if(playable) { + document.getElementById('play-from-query').style.cssText = "position: fixed; z-index: 15; text-align: center; top: 50%; left: 50%; display: block;"; + } } } function playFromQuery(event) { - missionChanged(urlParams['mstid'], urlParams['id']); + let cust; + if (urlParams['custom'] && urlParams['custom'] === "1") { + cust = CUSTOM.custom; + } else { + cust = CUSTOM.stock; + } + sceneSet(urlParams['questscenemstid'], cust); document.getElementById('play-from-query').style.cssText = "display: none;"; } -function missionDropDownChanged(event) { +function questDropDownChanged(event) { + if(!event || !event.currentTarget || !event.currentTarget.value) { return; } + buildSceneSelectList(); +} + +function sceneDropDownChanged(event) { if(!event || !event.currentTarget || !event.currentTarget.value || event.currentTarget.value === '{Select}') { return; } + + if (event.currentTarget.value === '{All}') { + let quest = document.getElementById("select-quest"); + let cust = quest.value.split("|")[0]; + let questMstId = quest.value.split("|")[1]; + let scene = utage.quests[cust][questMstId].Scenes; + resetPlaylist(); + for (const s of scene) { + let tl_key = utage.sceneTranslations[cust][s]; + if (tl_key) { + if (utage.sceneTranslations[cust][s].Enabled) { + utage.scenes[cust][s]['QuestSceneMstId'] = s; + scenePlaylist.push(utage.scenes[cust][s]); + } + } + } + playNext(); + return; + } + let cont = document.getElementById("modal-container"); - let misId = event.currentTarget.value; - let mis = utage.groupedMissions[misId]; - if(!mis) { console.log(`Mission ${misId} not found`); return; } - let name = mis.Name; - let summary = mis.SummaryText; - if(utage.missionTranslations[mis.MstId]) { - name = utage.missionTranslations[mis.MstId].Name || name; - summary = utage.missionTranslations[mis.MstId].SummaryText || summary; + + let cust = event.currentTarget.value.split("|")[0]; + let questSceneMstId = event.currentTarget.value.split("|")[1]; + + let scene = utage.scenes[cust][questSceneMstId]; + if(!scene) { console.log(`Scene ${questSceneMstId} not found`); return; } + + let name = scene.Name; + let summary = scene.SummaryText; + let image = questSceneMstId; + if ("Image" in scene) { + image = scene.Image; } + let credits = ""; + let tl_key = utage.sceneTranslations[cust][questSceneMstId]; + + if(tl_key) { + name = tl_key.Name || name; + summary = tl_key.SummaryText || summary; + credits = tl_key.Credits || credits; + } + if(!credits) { + if(selectedLang === "eng") { + credits = "YameteTomete"; + } else { + credits = "None"; + } + } + let chapterSelect = '
Chapter Select:
'; cont.innerHTML = ` `; document.getElementById("click-catcher").style.cssText = 'display: flex;'; @@ -198,10 +337,18 @@ function missionDropDownChanged(event) { function closeMissionModal(event, wasStarted) { if(!wasStarted) { - document.getElementById('select-mission').value = prevMission; + document.getElementById('select-scene').value = prevScene; + document.getElementById('select-quest').value = prevQuest; } else { - prevMission = document.getElementById('select-mission').value; + prevScene = document.getElementById('select-scene').value; + prevQuest = document.getElementById('select-quest').value; } + if (prevScene === '{Select}') { + document.getElementById('select-scene').setAttribute("disabled", "true"); + } else { + document.getElementById('select-scene').removeAttribute("disabled"); + } + closeModal(event); } @@ -212,50 +359,85 @@ function closeModal(event) { cont.innerHTML = ''; } -function missionChanged(mstId, value) { - let mst = utage.groupedMissions[mstId]; - let name = mst.Name; - if(utage.missionTranslations[mstId]) { - name = utage.missionTranslations[mstId].Name || name; +function sceneSet(questSceneMstId, cust) { + resetPlaylist(); + let part = document.getElementById('ChapterSelect'); + utage.scenes[cust][questSceneMstId]['QuestSceneMstId'] = questSceneMstId; + if (!part || part.value === '{All}') { + scenePlaylist.push(utage.scenes[cust][questSceneMstId]); + } else { + currentScene = utage.scenes[cust][questSceneMstId]; + try { + partPlaylist.push.apply(partPlaylist, currentScene.Parts.slice(currentScene.Parts.indexOf(part.value))); + } catch (error) { + console.log(error); + return; + } } - if(!value) { - value = document.getElementById("ChapterSelect").value; + + playNext(); +} + +function playNext() { + if (!partPlaylist.length) { + if (!scenePlaylist.length) { + resetPlaylist(); + return; // we're probably done + } + currentScene = scenePlaylist.shift(); + partPlaylist = currentScene.Parts.slice(); } + + partChanged(partPlaylist.shift()); +} + +function partChanged(part) { + let cust = currentScene.IsCustom ? CUSTOM.custom : CUSTOM.stock; + let name = currentScene.Name; + let tl_key = utage.sceneTranslations[cust][currentScene.QuestSceneMstId]; + + if(tl_key) { + name = tl_key.Name || name; + } + if(!audio) { audio = new audioController(utage); audio.changeVolume(volume); audio.mute(isMuted); player.audio = audio; } + player.resetAll() .then((success) => { - let newMission = mst.Missions[value]; - checkMissionList(mst.Missions, value); - currentMission = newMission; - currentMissionMst = mstId; - let promises = [ - utage.parseMissionFile(`${utage.rootDirectory}XDUData/${newMission.Path.replace('Asset/', '').replace('.utage', '').replace('.tsv', '_t.tsv')}`), - utage.loadMissionTranslation(`${utage.rootDirectory}Js/Translations/Missions/${currentMission.Path.replace('Asset/Utage/', '').replace('Scenario/', '').replace('.utage', '').replace('.tsv', `_translations_${selectedLang}.json`)}`) - ]; + if (scenePlaylist.length || partPlaylist.length) { + document.getElementById("skip-button").style.cssText = "display: inline-block;"; + } else { + document.getElementById("skip-button").style.cssText = "display: none;"; + } + let promises = []; + if(currentScene.IsCustom) { + promises.push(utage.parseMissionFile(`${utage.rootDirectory}CustomData/Utage/${currentScene.Folder}/Scenario/${part}_t.tsv`)); + promises.push(utage.loadMissionTranslation(`${utage.rootDirectory}Js/Translations/MissionsCustom/${currentScene.Folder}/${part}_translations_${selectedLang}.json`)); + } else { + promises.push(utage.parseMissionFile(`${utage.rootDirectory}XDUData/Utage/${currentScene.Folder}/Scenario/${part}_t.tsv`)); + promises.push(utage.loadMissionTranslation(`${utage.rootDirectory}Js/Translations/Missions/${currentScene.Folder}/${part}_translations_${selectedLang}.json`)); + } closeMissionModal(undefined, true); Promise.all(promises) .then((success) => { - document.getElementById("playing-title").innerText = `${name} (${value})`; + document.getElementById("playing-title").innerText = `${name} (${part})`; + document.getElementById("title-tag").innerText = name; + currentPart = part; player.playFile() .then((success) => { - if(currentMissionIndex !== currentMissionList.length - 1) { - missionChanged(currentMissionMst, mst.Missions[currentMissionList[currentMissionIndex+1]].Id) - } else { - player.resetAll(); - resetMissions(); - } + playNext(); }, (failure) => { player.resetAll(); - resetMissions(); + resetPlaylist(); console.log(failure); }); }, (failure) => { - resetMissions(); + resetPlaylist(); console.log(failure); }); }, (failure) => { @@ -267,37 +449,32 @@ function languageChanged(event) { if(!event || !event.currentTarget || !event.currentTarget.value || event.currentTarget.value === '{Select}' || !languages.includes(event.currentTarget.value)) { return; } selectedLang = event.currentTarget.value; let missionPath = ''; - if(currentMission) { - missionPath = `${utage.rootDirectory}Js/Translations/Missions/${currentMission.Path.replace('Asset/Utage/', '').replace('Scenario/', '').replace('.utage', '').replace('.tsv', `_translations_${selectedLang}.json`)}`; - } - utage.setTranslationLanguage(selectedLang, missionPath); -} - -function checkMissionList(missions, currentvalue) { - currentMissionList = []; - let i = 0; - for(var m of Object.keys(missions)) { - currentMissionList.push(m); - if(m === currentvalue) { - currentMissionIndex = i; + if(currentPart) { + if (currentScene.IsCustom) { + missionPath = `${utage.rootDirectory}Js/Translations/CustomMissions/${currentScene.Folder}/${currentPart}_translations_${selectedLang}.json`; + } else { + missionPath = `${utage.rootDirectory}Js/Translations/Missions/${currentScene.Folder}/${currentPart}_translations_${selectedLang}.json`; } - ++i; - } - if(currentMissionIndex + 1 === currentMissionList.length) { - document.getElementById("skip-button").style.cssText = "display: none;"; - } else { - document.getElementById("skip-button").style.cssText = "display: inline-block;"; } + utage.setTranslationLanguage(selectedLang, missionPath) + .then((success) => { + document.getElementById('text-container').className = selectedLang; + buildQuestSelectList(); + buildSceneSelectList(); + localStorage.setItem('language', selectedLang); + }); } -function resetMissions() { - currentMissionIndex = 0; - currentMissionList = []; - currentMission = undefined; - currentMissionMst = 0; +function resetPlaylist() { + currentScene = {}; + scenePlaylist = []; + currentPart = ""; + partPlaylist = []; document.getElementById("skip-button").style.cssText = "display: inline-block;"; document.getElementById("playing-title").innerText = 'None'; - document.getElementById('select-mission').value = '{Select}'; + document.getElementById("title-tag").innerText = version; + document.getElementById("select-quest").value = '{Select}'; + buildSceneSelectList(); } function onMainClick(event) { @@ -311,10 +488,10 @@ function hideUiClicked(event) { function skipClicked(event) { if(player.uiHidden) { player.hideUiClicked(event); - } else if(player.runEvent && currentMissionIndex !== currentMissionList.length - 1) { + } else if(player.runEvent) { event.preventDefault(); event.stopPropagation(); - missionChanged(currentMissionMst, utage.groupedMissions[currentMissionMst].Missions[currentMissionList[currentMissionIndex+1]].Id); + playNext(); } } @@ -331,7 +508,11 @@ function dialogScrollDown(event) { } function onBodyKey(event) { - if(event.code.toLowerCase() === "space") { + if(event.code.toLowerCase() === "arrowdown") { + dialogScrollDown(event) + } else if(event.code.toLowerCase() === "arrowup") { + dialogScrollUp(event); + } else if(event.code.toLowerCase() === "space") { event.preventDefault(); event.stopPropagation(); player.onMainClick(event); @@ -342,7 +523,7 @@ function openHelpModal(event) { let cont = document.getElementById("modal-container"); cont.innerHTML = `