mirror of
https://github.com/an-anime-team/an-anime-game-launcher.git
synced 2025-01-01 22:47:19 +03:00
Added game pre-downloading feature
- decreased splash window size - added `Game.getLatestData()` result caching, so basically every `Game` and `Voice` method and field now caches most of the data - Made `Game.predownloadUpdate()` method - Made `Game.isUpdatePredownloaded()` method - Made `Voice.predownloadUpdate()` method - Made `Voice.isUpdatePredownloaded()` method - fixed `Cache.get()` method work - now launcher window hides when you launch the game - added `pre_download_game` API field type definition - added game pre-downloading button
This commit is contained in:
parent
7bfb3ab0b5
commit
d2d690a114
14 changed files with 376 additions and 52 deletions
|
@ -168,7 +168,7 @@ This is our current roadmap goals. You can find older ones [here](ROADMAP.md)
|
||||||
* <s>Debugger</s>
|
* <s>Debugger</s>
|
||||||
* <s>Splash screen</s>
|
* <s>Splash screen</s>
|
||||||
* <s>Theming system</s>
|
* <s>Theming system</s>
|
||||||
* Game pre-installation
|
* <s>Game pre-installation</s>
|
||||||
* Launcher auto-updates
|
* Launcher auto-updates
|
||||||
* Statistics window
|
* Statistics window
|
||||||
* Chengelog window
|
* Chengelog window
|
||||||
|
|
BIN
src/assets/images/cloud-download.png
Normal file
BIN
src/assets/images/cloud-download.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
|
@ -5,9 +5,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
import Gear from '/src/assets/images/gear.png';
|
|
||||||
import GearActive from '/src/assets/images/gear-active.png';
|
|
||||||
|
|
||||||
import Window from './ts/neutralino/Window';
|
import Window from './ts/neutralino/Window';
|
||||||
|
|
||||||
import Launcher from './ts/Launcher';
|
import Launcher from './ts/Launcher';
|
||||||
|
@ -15,11 +12,15 @@
|
||||||
import Game from './ts/Game';
|
import Game from './ts/Game';
|
||||||
import Background from './ts/launcher/Background';
|
import Background from './ts/launcher/Background';
|
||||||
|
|
||||||
|
import Gear from './assets/images/gear.png';
|
||||||
|
import GearActive from './assets/images/gear-active.png';
|
||||||
|
import Download from './assets/images/cloud-download.png';
|
||||||
|
|
||||||
Neutralino.events.on('ready', () => {
|
Neutralino.events.on('ready', () => {
|
||||||
Window.open('splash', {
|
Window.open('splash', {
|
||||||
title: 'Splash',
|
title: 'Splash',
|
||||||
width: 400,
|
width: 300,
|
||||||
height: 500,
|
height: 400,
|
||||||
borderless: true,
|
borderless: true,
|
||||||
exitProcessOnClose: false
|
exitProcessOnClose: false
|
||||||
});
|
});
|
||||||
|
@ -82,5 +83,9 @@
|
||||||
<img src={GearActive} class="active" alt="Settings">
|
<img src={GearActive} class="active" alt="Settings">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<button class="button hint--left hint--small" aria-label="Pre-download the game" id="predownload">
|
||||||
|
<img src={Download} alt="Download" />
|
||||||
|
</button>
|
||||||
|
|
||||||
<button class="button" id="launch">Launch</button>
|
<button class="button" id="launch">Launch</button>
|
||||||
</main>
|
</main>
|
||||||
|
|
|
@ -35,16 +35,32 @@
|
||||||
background-color: #7284b6
|
background-color: #7284b6
|
||||||
|
|
||||||
#launch
|
#launch
|
||||||
|
position: absolute
|
||||||
|
|
||||||
width: 238px
|
width: 238px
|
||||||
height: 64px
|
height: 64px
|
||||||
|
|
||||||
font-size: 22px
|
|
||||||
|
|
||||||
position: absolute
|
|
||||||
|
|
||||||
right: 128px
|
right: 128px
|
||||||
bottom: 64px
|
bottom: 64px
|
||||||
|
|
||||||
|
font-size: 22px
|
||||||
|
|
||||||
|
#predownload
|
||||||
|
position: absolute
|
||||||
|
display: none
|
||||||
|
|
||||||
|
width: 52px
|
||||||
|
height: 52px
|
||||||
|
|
||||||
|
right: 386px
|
||||||
|
bottom: 70px
|
||||||
|
|
||||||
|
border-radius: 32px
|
||||||
|
|
||||||
|
img
|
||||||
|
width: 60%
|
||||||
|
margin: auto
|
||||||
|
|
||||||
#settings
|
#settings
|
||||||
width: 76px
|
width: 76px
|
||||||
height: 76px
|
height: 76px
|
||||||
|
|
|
@ -7,11 +7,13 @@
|
||||||
background-color: map.get($theme-map, "background")
|
background-color: map.get($theme-map, "background")
|
||||||
|
|
||||||
div
|
div
|
||||||
width: 60%
|
width: 80%
|
||||||
margin: 56px auto 0 auto
|
margin: 42px auto 0
|
||||||
|
|
||||||
img
|
img
|
||||||
width: 100%
|
width: 80%
|
||||||
|
display: block
|
||||||
|
margin: 0 auto 16px auto
|
||||||
image-rendering: optimizeQuality
|
image-rendering: optimizeQuality
|
||||||
|
|
||||||
h2, p
|
h2, p
|
||||||
|
|
|
@ -11,6 +11,8 @@ import AbstractInstaller from './core/AbstractInstaller';
|
||||||
import Domain from './core/Domain';
|
import Domain from './core/Domain';
|
||||||
import promisify from './core/promisify';
|
import promisify from './core/promisify';
|
||||||
import Debug, { DebugThread } from './core/Debug';
|
import Debug, { DebugThread } from './core/Debug';
|
||||||
|
import Downloader, { Stream as DownloadingStream } from './core/Downloader';
|
||||||
|
import Cache from './core/Cache';
|
||||||
|
|
||||||
declare const Neutralino;
|
declare const Neutralino;
|
||||||
|
|
||||||
|
@ -67,12 +69,24 @@ export default class Game
|
||||||
|
|
||||||
if (response.ok)
|
if (response.ok)
|
||||||
{
|
{
|
||||||
const json: ServerResponse = JSON.parse(await response.body());
|
const cache = await Cache.get('Game.getLatestData.ServerResponse');
|
||||||
|
|
||||||
if (json.message == 'OK')
|
if (cache && !cache.expired)
|
||||||
resolve(json.data);
|
resolve(cache.value as Data);
|
||||||
|
|
||||||
else reject(new Error(`${constants.placeholders.uppercase.company}'s versions server responds with an error: [${json.retcode}] ${json.message}`));
|
else
|
||||||
|
{
|
||||||
|
const json: ServerResponse = JSON.parse(await response.body());
|
||||||
|
|
||||||
|
if (json.message == 'OK')
|
||||||
|
{
|
||||||
|
Cache.set('Game.getLatestData.ServerResponse', json.data, 24 * 3600);
|
||||||
|
|
||||||
|
resolve(json.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
else reject(new Error(`${constants.placeholders.uppercase.company}'s versions server responds with an error: [${json.retcode}] ${json.message}`));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else reject(new Error(`${constants.placeholders.uppercase.company}'s versions server is unreachable`));
|
else reject(new Error(`${constants.placeholders.uppercase.company}'s versions server is unreachable`));
|
||||||
|
@ -141,16 +155,19 @@ export default class Game
|
||||||
/**
|
/**
|
||||||
* Get the game installation stream
|
* Get the game installation stream
|
||||||
*
|
*
|
||||||
|
* @param version current game version to download difference from
|
||||||
|
*
|
||||||
* @returns null if the version can't be found
|
* @returns null if the version can't be found
|
||||||
* @returns Error if company's servers are unreachable or they responded with an error
|
* @returns Error if company's servers are unreachable or they responded with an error
|
||||||
*/
|
*/
|
||||||
public static update(version: string|null = null): Promise<Stream|null>
|
public static update(version: string|null = null): Promise<Stream|null>
|
||||||
{
|
{
|
||||||
Debug.log(
|
Debug.log({
|
||||||
version !== null ?
|
function: 'Game.update',
|
||||||
`Updating the game from the ${version} version` :
|
message: version !== null ?
|
||||||
'Installing the game'
|
`Updating the game from the ${version} version` :
|
||||||
);
|
'Installing the game'
|
||||||
|
});
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
(version === null ? this.latest : this.getDiff(version))
|
(version === null ? this.latest : this.getDiff(version))
|
||||||
|
@ -159,6 +176,60 @@ export default class Game
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pre-download the game update
|
||||||
|
*
|
||||||
|
* @param version current game version to download difference from
|
||||||
|
*
|
||||||
|
* @returns null if the game pre-downloading is not available. Otherwise - downloading stream
|
||||||
|
* @returns Error if company's servers are unreachable or they responded with an error
|
||||||
|
*/
|
||||||
|
public static predownloadUpdate(version: string|null = null): Promise<DownloadingStream|null>
|
||||||
|
{
|
||||||
|
const debugThread = new DebugThread('Game.predownloadUpdate', 'Predownloading game data...')
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.getLatestData()
|
||||||
|
.then((data) => {
|
||||||
|
if (data.pre_download_game)
|
||||||
|
{
|
||||||
|
let path = data.pre_download_game.latest.path;
|
||||||
|
|
||||||
|
if (version !== null)
|
||||||
|
for (const diff of data.pre_download_game.diffs)
|
||||||
|
if (diff.version == version)
|
||||||
|
{
|
||||||
|
path = diff.path;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
debugThread.log(`Downloading update from the path: ${path}`);
|
||||||
|
|
||||||
|
constants.paths.launcherDir.then((dir) => {
|
||||||
|
Downloader.download(path, `${dir}/game-predownloaded.zip`)
|
||||||
|
.then((stream) => resolve(stream));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
else resolve(null);
|
||||||
|
})
|
||||||
|
.catch((error) => resolve(error));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the update was downloaded or not
|
||||||
|
*/
|
||||||
|
public static isUpdatePredownloaded(): Promise<boolean>
|
||||||
|
{
|
||||||
|
return new Promise(async (resolve) => {
|
||||||
|
Neutralino.filesystem.getStats(`${await constants.paths.launcherDir}/game-predownloaded.zip`)
|
||||||
|
.then(() => resolve(true))
|
||||||
|
.catch(() => resolve(false));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the telemetry servers are disabled
|
* Check if the telemetry servers are disabled
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -5,7 +5,8 @@ import constants from './Constants';
|
||||||
import Game from './Game';
|
import Game from './Game';
|
||||||
import AbstractInstaller from './core/AbstractInstaller';
|
import AbstractInstaller from './core/AbstractInstaller';
|
||||||
import Configs from './Configs';
|
import Configs from './Configs';
|
||||||
import Debug from './core/Debug';
|
import Debug, { DebugThread } from './core/Debug';
|
||||||
|
import Downloader, { Stream as DownloadingStream } from './core/Downloader';
|
||||||
|
|
||||||
declare const Neutralino;
|
declare const Neutralino;
|
||||||
|
|
||||||
|
@ -118,13 +119,14 @@ export default class Voice
|
||||||
* @returns null if the language or the version can't be found
|
* @returns null if the language or the version can't be found
|
||||||
* @returns rejects Error object if company's servers are unreachable or they responded with an error
|
* @returns rejects Error object if company's servers are unreachable or they responded with an error
|
||||||
*/
|
*/
|
||||||
public static update(lang: string|null = null, version: string|null = null): Promise<Stream|null>
|
public static update(lang: string, version: string|null = null): Promise<Stream|null>
|
||||||
{
|
{
|
||||||
Debug.log(
|
Debug.log({
|
||||||
version !== null ?
|
function: 'Voice.update',
|
||||||
`Updating the voice package from the ${version} version` :
|
message: version !== null ?
|
||||||
'Installing the voice package'
|
`Updating the voice package from the ${version} version` :
|
||||||
);
|
'Installing the voice package'
|
||||||
|
});
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
(version === null ? this.latest : this.getDiff(version))
|
(version === null ? this.latest : this.getDiff(version))
|
||||||
|
@ -142,6 +144,65 @@ export default class Voice
|
||||||
.catch((error) => reject(error));
|
.catch((error) => reject(error));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pre-download the game's voice update
|
||||||
|
*
|
||||||
|
* @param version current game version to download difference from
|
||||||
|
*
|
||||||
|
* @returns null if the game pre-downloading is not available or the language wasn't found. Otherwise - downloading stream
|
||||||
|
* @returns Error if company's servers are unreachable or they responded with an error
|
||||||
|
*/
|
||||||
|
public static predownloadUpdate(lang: string, version: string|null = null): Promise<DownloadingStream|null>
|
||||||
|
{
|
||||||
|
const debugThread = new DebugThread('Voice.predownloadUpdate', 'Predownloading game voice data...')
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
Game.getLatestData()
|
||||||
|
.then((data) => {
|
||||||
|
if (data.pre_download_game)
|
||||||
|
{
|
||||||
|
let voicePack = data.pre_download_game.latest.voice_packs.filter(voice => voice.language === lang);
|
||||||
|
|
||||||
|
if (version !== null)
|
||||||
|
for (const diff of data.pre_download_game.diffs)
|
||||||
|
if (diff.version == version)
|
||||||
|
{
|
||||||
|
voicePack = diff.voice_packs.filter(voice => voice.language === lang);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (voicePack.length === 1)
|
||||||
|
{
|
||||||
|
debugThread.log(`Downloading update from the path: ${voicePack[0].path}`);
|
||||||
|
|
||||||
|
constants.paths.launcherDir.then((dir) => {
|
||||||
|
Downloader.download(voicePack[0].path, `${dir}/voice-${lang}-predownloaded.zip`)
|
||||||
|
.then((stream) => resolve(stream));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
else resolve(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
else resolve(null);
|
||||||
|
})
|
||||||
|
.catch((error) => resolve(error));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the update was downloaded or not
|
||||||
|
*/
|
||||||
|
public static isUpdatePredownloaded(lang: string): Promise<boolean>
|
||||||
|
{
|
||||||
|
return new Promise(async (resolve) => {
|
||||||
|
Neutralino.filesystem.getStats(`${await constants.paths.launcherDir}/voice-${lang}-predownloaded.zip`)
|
||||||
|
.then(() => resolve(true))
|
||||||
|
.catch(() => resolve(false));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Stream };
|
export { Stream };
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import constants from '../Constants';
|
import constants from '../Constants';
|
||||||
|
import Debug from './Debug';
|
||||||
|
|
||||||
type Record = {
|
type Record = {
|
||||||
expired: boolean;
|
expired: boolean;
|
||||||
|
@ -9,6 +10,10 @@ declare const Neutralino;
|
||||||
|
|
||||||
export default class Cache
|
export default class Cache
|
||||||
{
|
{
|
||||||
|
// Locally stored cache to not to access
|
||||||
|
// cache.json file every time we want to find something
|
||||||
|
protected static cache: object|null = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get cached value
|
* Get cached value
|
||||||
*
|
*
|
||||||
|
@ -17,19 +22,44 @@ export default class Cache
|
||||||
public static get(name: string): Promise<Record|null>
|
public static get(name: string): Promise<Record|null>
|
||||||
{
|
{
|
||||||
return new Promise(async (resolve) => {
|
return new Promise(async (resolve) => {
|
||||||
Neutralino.filesystem.readFile(await constants.paths.cache)
|
if (this.cache !== null && this.cache[name] !== undefined)
|
||||||
.then((cache) => {
|
{
|
||||||
cache = JSON.parse(cache);
|
Debug.log({
|
||||||
|
function: 'Cache.get',
|
||||||
|
message: [
|
||||||
|
`Resolved ${this.cache[name].expired ? 'expired' : 'unexpired'} hot cache record`,
|
||||||
|
`[name] ${name}`,
|
||||||
|
`[value]: ${this.cache[name].value}`
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
if (cache[name] === undefined)
|
resolve(this.cache[name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
else Neutralino.filesystem.readFile(await constants.paths.cache)
|
||||||
|
.then((cache) => {
|
||||||
|
this.cache = JSON.parse(cache);
|
||||||
|
|
||||||
|
if (this.cache![name] === undefined)
|
||||||
resolve(null);
|
resolve(null);
|
||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
resolve({
|
const output = {
|
||||||
expired: cache[name].ttl !== null ? Date.now() > cache[name].ttl * 1000 : false,
|
expired: this.cache![name].ttl !== null ? Date.now() > this.cache![name].ttl * 1000 : false,
|
||||||
value: JSON.parse(atob(cache[name].value))
|
value: this.cache![name].value
|
||||||
|
};
|
||||||
|
|
||||||
|
Debug.log({
|
||||||
|
function: 'Cache.get',
|
||||||
|
message: [
|
||||||
|
`Resolved ${output.expired ? 'expired' : 'unexpired'} cache`,
|
||||||
|
`[name] ${name}`,
|
||||||
|
`[value]: ${JSON.stringify(output.value)}`
|
||||||
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
resolve(output);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => resolve(null));
|
.catch(() => resolve(null));
|
||||||
|
@ -49,19 +79,40 @@ export default class Cache
|
||||||
{
|
{
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
constants.paths.cache.then((cacheFile) => {
|
constants.paths.cache.then((cacheFile) => {
|
||||||
let cache = {};
|
if (this.cache === null)
|
||||||
|
{
|
||||||
|
Neutralino.filesystem.readFile(cacheFile)
|
||||||
|
.then((cacheRaw) =>
|
||||||
|
{
|
||||||
|
this.cache = JSON.parse(cacheRaw);
|
||||||
|
|
||||||
Neutralino.filesystem.readFile(cacheFile)
|
writeCache();
|
||||||
.then((cacheRaw) => cache = JSON.parse(cacheRaw))
|
})
|
||||||
.catch(() => {});
|
.catch(() => {
|
||||||
|
this.cache = {};
|
||||||
|
|
||||||
cache[name] = {
|
writeCache();
|
||||||
ttl: ttl !== null ? Math.round(Date.now() / 1000) + ttl : null,
|
});
|
||||||
value: btoa(JSON.stringify(value))
|
}
|
||||||
|
|
||||||
|
const writeCache = () => {
|
||||||
|
Debug.log({
|
||||||
|
function: 'Cache.set',
|
||||||
|
message: [
|
||||||
|
'Caching data:',
|
||||||
|
`[ttl] ${ttl}`,
|
||||||
|
`[value] ${JSON.stringify(value)}`
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
this.cache![name] = {
|
||||||
|
ttl: ttl !== null ? Math.round(Date.now() / 1000) + ttl : null,
|
||||||
|
value: value
|
||||||
|
};
|
||||||
|
|
||||||
|
Neutralino.filesystem.writeFile(cacheFile, JSON.stringify(this.cache))
|
||||||
|
.then(() => resolve());
|
||||||
};
|
};
|
||||||
|
|
||||||
Neutralino.filesystem.writeFile(cacheFile, JSON.stringify(cache))
|
|
||||||
.then(() => resolve());
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ export default class State
|
||||||
public launcher: Launcher;
|
public launcher: Launcher;
|
||||||
|
|
||||||
public launchButton: HTMLElement;
|
public launchButton: HTMLElement;
|
||||||
|
public predownloadButton: HTMLElement;
|
||||||
|
|
||||||
protected _state: LauncherState = 'game-launch-available';
|
protected _state: LauncherState = 'game-launch-available';
|
||||||
|
|
||||||
|
@ -34,6 +35,7 @@ export default class State
|
||||||
this.launcher = launcher;
|
this.launcher = launcher;
|
||||||
|
|
||||||
this.launchButton = <HTMLElement>document.getElementById('launch');
|
this.launchButton = <HTMLElement>document.getElementById('launch');
|
||||||
|
this.predownloadButton = <HTMLElement>document.getElementById('predownload');
|
||||||
|
|
||||||
this.launchButton.onclick = () => {
|
this.launchButton.onclick = () => {
|
||||||
if (this.events[this._state])
|
if (this.events[this._state])
|
||||||
|
@ -42,14 +44,30 @@ export default class State
|
||||||
|
|
||||||
this.events[this._state].then((event) => {
|
this.events[this._state].then((event) => {
|
||||||
event.default(this.launcher).then(() => {
|
event.default(this.launcher).then(() => {
|
||||||
this.launchButton.style['display'] = 'block';
|
this.update().then(() => {
|
||||||
|
this.launchButton.style['display'] = 'block';
|
||||||
this.update();
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.predownloadButton.onclick = () => {
|
||||||
|
this.launchButton.style['display'] = 'none';
|
||||||
|
this.predownloadButton.style['display'] = 'none';
|
||||||
|
|
||||||
|
const module = this._state === 'game-pre-installation-available' ?
|
||||||
|
'Predownload' : 'PredownloadVoice';
|
||||||
|
|
||||||
|
import(`./states/${module}`).then((module) => {
|
||||||
|
module.default(this.launcher).then(() => {
|
||||||
|
this.update().then(() => {
|
||||||
|
this.launchButton.style['display'] = 'block';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
this.update().then(() => {
|
this.update().then(() => {
|
||||||
Neutralino.storage.setData('launcherLoaded', 'aboba');
|
Neutralino.storage.setData('launcherLoaded', 'aboba');
|
||||||
|
|
||||||
|
@ -73,6 +91,7 @@ export default class State
|
||||||
this._state = state;
|
this._state = state;
|
||||||
|
|
||||||
this.launcher.progressBar!.hide();
|
this.launcher.progressBar!.hide();
|
||||||
|
this.predownloadButton.style['display'] = 'none';
|
||||||
|
|
||||||
switch(state)
|
switch(state)
|
||||||
{
|
{
|
||||||
|
@ -81,6 +100,14 @@ export default class State
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'game-pre-installation-available':
|
||||||
|
case 'game-voice-pre-installation-available':
|
||||||
|
this.launchButton.textContent = 'Launch';
|
||||||
|
|
||||||
|
this.predownloadButton.style['display'] = 'block';
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
case 'game-installation-available':
|
case 'game-installation-available':
|
||||||
this.launchButton.textContent = 'Install';
|
this.launchButton.textContent = 'Install';
|
||||||
|
|
||||||
|
@ -125,14 +152,14 @@ export default class State
|
||||||
let state: LauncherState;
|
let state: LauncherState;
|
||||||
|
|
||||||
const gameCurrent = await Game.current;
|
const gameCurrent = await Game.current;
|
||||||
const gameLatest = (await Game.latest).version;
|
const gameLatest = await Game.getLatestData();
|
||||||
const patch = await Patch.latest;
|
const patch = await Patch.latest;
|
||||||
const voiceData = await Voice.current;
|
const voiceData = await Voice.current;
|
||||||
|
|
||||||
if (gameCurrent === null)
|
if (gameCurrent === null)
|
||||||
state = 'game-installation-available';
|
state = 'game-installation-available';
|
||||||
|
|
||||||
else if (gameCurrent != gameLatest)
|
else if (gameCurrent != gameLatest.game.latest.version)
|
||||||
state = 'game-update-available';
|
state = 'game-update-available';
|
||||||
|
|
||||||
// TODO: update this thing if the user selected another voice language
|
// TODO: update this thing if the user selected another voice language
|
||||||
|
@ -146,6 +173,12 @@ export default class State
|
||||||
'test-patch-available' : 'patch-available');
|
'test-patch-available' : 'patch-available');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else if (gameLatest.pre_download_game && !await Game.isUpdatePredownloaded())
|
||||||
|
state = 'game-pre-installation-available';
|
||||||
|
|
||||||
|
else if (gameLatest.pre_download_game && !await Voice.isUpdatePredownloaded(await Voice.selected))
|
||||||
|
state = 'game-voice-pre-installation-available';
|
||||||
|
|
||||||
else state = 'game-launch-available';
|
else state = 'game-launch-available';
|
||||||
|
|
||||||
this.set(state);
|
this.set(state);
|
||||||
|
|
|
@ -5,6 +5,7 @@ import Notifications from '../../core/Notifications';
|
||||||
import Runners from '../../core/Runners';
|
import Runners from '../../core/Runners';
|
||||||
import Game from '../../Game';
|
import Game from '../../Game';
|
||||||
import Process from '../../neutralino/Process';
|
import Process from '../../neutralino/Process';
|
||||||
|
import Window from '../../neutralino/Window';
|
||||||
|
|
||||||
declare const Neutralino;
|
declare const Neutralino;
|
||||||
|
|
||||||
|
@ -32,6 +33,8 @@ export default (): Promise<void> => {
|
||||||
// Otherwise run the game
|
// Otherwise run the game
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
Window.current.hide();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Selecting wine executable
|
* Selecting wine executable
|
||||||
*/
|
*/
|
||||||
|
@ -139,6 +142,8 @@ export default (): Promise<void> => {
|
||||||
process.finish(() => {
|
process.finish(() => {
|
||||||
const stopTime = Date.now();
|
const stopTime = Date.now();
|
||||||
|
|
||||||
|
Window.current.show();
|
||||||
|
|
||||||
// todo
|
// todo
|
||||||
|
|
||||||
resolve();
|
resolve();
|
||||||
|
|
44
src/ts/launcher/states/Predownload.ts
Normal file
44
src/ts/launcher/states/Predownload.ts
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import type Launcher from '../../Launcher';
|
||||||
|
|
||||||
|
import Game from '../../Game';
|
||||||
|
import Prefix from '../../core/Prefix';
|
||||||
|
|
||||||
|
export default (launcher: Launcher): Promise<void> => {
|
||||||
|
return new Promise(async (resolve) => {
|
||||||
|
Prefix.exists().then((exists) => {
|
||||||
|
if (!exists)
|
||||||
|
{
|
||||||
|
import('./CreatePrefix').then((module) => {
|
||||||
|
module.default(launcher).then(() => updateGame());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateGame = async () => {
|
||||||
|
const prevGameVersion = await Game.current;
|
||||||
|
|
||||||
|
Game.predownloadUpdate(prevGameVersion).then((stream) => {
|
||||||
|
launcher.progressBar?.init({
|
||||||
|
label: 'Downloading game...',
|
||||||
|
showSpeed: true,
|
||||||
|
showEta: true,
|
||||||
|
showPercents: true,
|
||||||
|
showTotals: true
|
||||||
|
});
|
||||||
|
|
||||||
|
stream?.start(() => launcher.progressBar?.show());
|
||||||
|
|
||||||
|
stream?.progress((current: number, total: number, difference: number) => {
|
||||||
|
launcher.progressBar?.update(current, total, difference);
|
||||||
|
});
|
||||||
|
|
||||||
|
stream?.finish(() => {
|
||||||
|
// Predownload voice package when the game itself has been downloaded
|
||||||
|
import('./PredownloadVoice').then((module) => {
|
||||||
|
module.default(launcher, prevGameVersion).then(() => resolve());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
29
src/ts/launcher/states/PredownloadVoice.ts
Normal file
29
src/ts/launcher/states/PredownloadVoice.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import type Launcher from '../../Launcher';
|
||||||
|
|
||||||
|
import Voice from '../../Voice';
|
||||||
|
|
||||||
|
export default (launcher: Launcher, prevGameVersion: string|null = null): Promise<void> => {
|
||||||
|
return new Promise(async (resolve) => {
|
||||||
|
Voice.predownloadUpdate(await Voice.selected, prevGameVersion).then((stream) => {
|
||||||
|
launcher.progressBar?.init({
|
||||||
|
label: 'Downloading voice package...',
|
||||||
|
showSpeed: true,
|
||||||
|
showEta: true,
|
||||||
|
showPercents: true,
|
||||||
|
showTotals: true
|
||||||
|
});
|
||||||
|
|
||||||
|
stream?.start(() => launcher.progressBar?.show());
|
||||||
|
|
||||||
|
stream?.progress((current: number, total: number, difference: number) => {
|
||||||
|
launcher.progressBar?.update(current, total, difference);
|
||||||
|
});
|
||||||
|
|
||||||
|
stream?.finish(() => {
|
||||||
|
launcher.progressBar?.hide();
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
7
src/ts/types/GameData.d.ts
vendored
7
src/ts/types/GameData.d.ts
vendored
|
@ -52,12 +52,17 @@ type DeprecatedPackage = {
|
||||||
md5: string;
|
md5: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PreDownloadGame = {
|
||||||
|
latest: Latest;
|
||||||
|
diffs: Diff[];
|
||||||
|
};
|
||||||
|
|
||||||
type Data = {
|
type Data = {
|
||||||
game: Game;
|
game: Game;
|
||||||
plugin: Plugin;
|
plugin: Plugin;
|
||||||
web_url: string;
|
web_url: string;
|
||||||
force_update?: any;
|
force_update?: any;
|
||||||
pre_download_game?: any;
|
pre_download_game?: PreDownloadGame;
|
||||||
deprecated_packages: DeprecatedPackage[];
|
deprecated_packages: DeprecatedPackage[];
|
||||||
sdk?: any;
|
sdk?: any;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,8 @@ type LauncherState =
|
||||||
| 'game-installation-available'
|
| 'game-installation-available'
|
||||||
| 'game-update-available'
|
| 'game-update-available'
|
||||||
| 'game-voice-update-required'
|
| 'game-voice-update-required'
|
||||||
|
| 'game-pre-installation-available'
|
||||||
|
| 'game-voice-pre-installation-available'
|
||||||
| 'game-launch-available';
|
| 'game-launch-available';
|
||||||
|
|
||||||
export type { LauncherState };
|
export type { LauncherState };
|
Loading…
Reference in a new issue