parent
bba29b8a8b
commit
1ebfafd8de
@ -1,230 +1,230 @@
|
|||||||
import { makeOffscreenBuffer } from "./buffer_utils";
|
import { makeOffscreenBuffer } from "./buffer_utils";
|
||||||
import { AtlasSprite, BaseSprite, RegularSprite, SpriteAtlasLink } from "./sprites";
|
import { AtlasSprite, BaseSprite, RegularSprite, SpriteAtlasLink } from "./sprites";
|
||||||
import { cachebust } from "./cachebust";
|
import { cachebust } from "./cachebust";
|
||||||
import { createLogger } from "./logging";
|
import { createLogger } from "./logging";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import("../application").Application} Application
|
* @typedef {import("../application").Application} Application
|
||||||
* @typedef {import("./atlas_definitions").AtlasDefinition} AtlasDefinition;
|
* @typedef {import("./atlas_definitions").AtlasDefinition} AtlasDefinition;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const logger = createLogger("loader");
|
const logger = createLogger("loader");
|
||||||
|
|
||||||
const missingSpriteIds = {};
|
const missingSpriteIds = {};
|
||||||
|
|
||||||
class LoaderImpl {
|
class LoaderImpl {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.app = null;
|
this.app = null;
|
||||||
|
|
||||||
/** @type {Map<string, BaseSprite>} */
|
/** @type {Map<string, BaseSprite>} */
|
||||||
this.sprites = new Map();
|
this.sprites = new Map();
|
||||||
|
|
||||||
this.rawImages = [];
|
this.rawImages = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Application} app
|
* @param {Application} app
|
||||||
*/
|
*/
|
||||||
linkAppAfterBoot(app) {
|
linkAppAfterBoot(app) {
|
||||||
this.app = app;
|
this.app = app;
|
||||||
this.makeSpriteNotFoundCanvas();
|
this.makeSpriteNotFoundCanvas();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches a given sprite from the cache
|
* Fetches a given sprite from the cache
|
||||||
* @param {string} key
|
* @param {string} key
|
||||||
* @returns {BaseSprite}
|
* @returns {BaseSprite}
|
||||||
*/
|
*/
|
||||||
getSpriteInternal(key) {
|
getSpriteInternal(key) {
|
||||||
const sprite = this.sprites.get(key);
|
const sprite = this.sprites.get(key);
|
||||||
if (!sprite) {
|
if (!sprite) {
|
||||||
if (!missingSpriteIds[key]) {
|
if (!missingSpriteIds[key]) {
|
||||||
// Only show error once
|
// Only show error once
|
||||||
missingSpriteIds[key] = true;
|
missingSpriteIds[key] = true;
|
||||||
logger.error("Sprite '" + key + "' not found!");
|
logger.error("Sprite '" + key + "' not found!");
|
||||||
}
|
}
|
||||||
return this.spriteNotFoundSprite;
|
return this.spriteNotFoundSprite;
|
||||||
}
|
}
|
||||||
return sprite;
|
return sprite;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an atlas sprite from the cache
|
* Returns an atlas sprite from the cache
|
||||||
* @param {string} key
|
* @param {string} key
|
||||||
* @returns {AtlasSprite}
|
* @returns {AtlasSprite}
|
||||||
*/
|
*/
|
||||||
getSprite(key) {
|
getSprite(key) {
|
||||||
const sprite = this.getSpriteInternal(key);
|
const sprite = this.getSpriteInternal(key);
|
||||||
assert(sprite instanceof AtlasSprite || sprite === this.spriteNotFoundSprite, "Not an atlas sprite");
|
assert(sprite instanceof AtlasSprite || sprite === this.spriteNotFoundSprite, "Not an atlas sprite");
|
||||||
return /** @type {AtlasSprite} */ (sprite);
|
return /** @type {AtlasSprite} */ (sprite);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a regular sprite from the cache
|
* Returns a regular sprite from the cache
|
||||||
* @param {string} key
|
* @param {string} key
|
||||||
* @returns {RegularSprite}
|
* @returns {RegularSprite}
|
||||||
*/
|
*/
|
||||||
getRegularSprite(key) {
|
getRegularSprite(key) {
|
||||||
const sprite = this.getSpriteInternal(key);
|
const sprite = this.getSpriteInternal(key);
|
||||||
assert(
|
assert(
|
||||||
sprite instanceof RegularSprite || sprite === this.spriteNotFoundSprite,
|
sprite instanceof RegularSprite || sprite === this.spriteNotFoundSprite,
|
||||||
"Not a regular sprite"
|
"Not a regular sprite"
|
||||||
);
|
);
|
||||||
return /** @type {RegularSprite} */ (sprite);
|
return /** @type {RegularSprite} */ (sprite);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {string} key
|
* @param {string} key
|
||||||
* @returns {Promise<HTMLImageElement|null>}
|
* @returns {Promise<HTMLImageElement|null>}
|
||||||
*/
|
*/
|
||||||
internalPreloadImage(key) {
|
internalPreloadImage(key) {
|
||||||
const url = cachebust("res/" + key);
|
const url = cachebust("res/" + key);
|
||||||
const image = new Image();
|
const image = new Image();
|
||||||
|
|
||||||
let triesSoFar = 0;
|
let triesSoFar = 0;
|
||||||
|
|
||||||
return Promise.race([
|
return Promise.race([
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
setTimeout(reject, G_IS_DEV ? 500 : 10000);
|
setTimeout(reject, G_IS_DEV ? 500 : 10000);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
new Promise(resolve => {
|
new Promise(resolve => {
|
||||||
image.onload = () => {
|
image.onload = () => {
|
||||||
image.onerror = null;
|
image.onerror = null;
|
||||||
image.onload = null;
|
image.onload = null;
|
||||||
|
|
||||||
if (typeof image.decode === "function") {
|
if (typeof image.decode === "function") {
|
||||||
// SAFARI: Image.decode() fails on safari with svgs -> we dont want to fail
|
// SAFARI: Image.decode() fails on safari with svgs -> we dont want to fail
|
||||||
// on that
|
// on that
|
||||||
// FIREFOX: Decode never returns if the image is in cache, so call it in background
|
// FIREFOX: Decode never returns if the image is in cache, so call it in background
|
||||||
image.decode().then(
|
image.decode().then(
|
||||||
() => null,
|
() => null,
|
||||||
() => null
|
() => null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
resolve(image);
|
resolve(image);
|
||||||
};
|
};
|
||||||
|
|
||||||
image.onerror = reason => {
|
image.onerror = reason => {
|
||||||
logger.warn("Failed to load '" + url + "':", reason);
|
logger.warn("Failed to load '" + url + "':", reason);
|
||||||
if (++triesSoFar < 4) {
|
if (++triesSoFar < 4) {
|
||||||
logger.log("Retrying to load image from", url);
|
logger.log("Retrying to load image from", url);
|
||||||
image.src = url + "?try=" + triesSoFar;
|
image.src = url + "?try=" + triesSoFar;
|
||||||
} else {
|
} else {
|
||||||
logger.warn("Failed to load", url, "after", triesSoFar, "tries with reason", reason);
|
logger.warn("Failed to load", url, "after", triesSoFar, "tries with reason", reason);
|
||||||
image.onerror = null;
|
image.onerror = null;
|
||||||
image.onload = null;
|
image.onload = null;
|
||||||
resolve(null);
|
resolve(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
image.src = url;
|
image.src = url;
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Preloads a sprite
|
* Preloads a sprite
|
||||||
* @param {string} key
|
* @param {string} key
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
preloadCSSSprite(key) {
|
preloadCSSSprite(key) {
|
||||||
return this.internalPreloadImage(key).then(image => {
|
return this.internalPreloadImage(key).then(image => {
|
||||||
if (key.indexOf("game_misc") >= 0) {
|
if (key.indexOf("game_misc") >= 0) {
|
||||||
// Allow access to regular sprites
|
// Allow access to regular sprites
|
||||||
this.sprites.set(key, new RegularSprite(image, image.width, image.height));
|
this.sprites.set(key, new RegularSprite(image, image.width, image.height));
|
||||||
}
|
}
|
||||||
this.rawImages.push(image);
|
this.rawImages.push(image);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Preloads an atlas
|
* Preloads an atlas
|
||||||
* @param {AtlasDefinition} atlas
|
* @param {AtlasDefinition} atlas
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
preloadAtlas(atlas) {
|
preloadAtlas(atlas) {
|
||||||
return this.internalPreloadImage(atlas.getFullSourcePath()).then(image => {
|
return this.internalPreloadImage(atlas.getFullSourcePath()).then(image => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
image.label = atlas.sourceFileName;
|
image.label = atlas.sourceFileName;
|
||||||
return this.internalParseAtlas(atlas, image);
|
return this.internalParseAtlas(atlas, image);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {AtlasDefinition} atlas
|
* @param {AtlasDefinition} atlas
|
||||||
* @param {HTMLImageElement} loadedImage
|
* @param {HTMLImageElement} loadedImage
|
||||||
*/
|
*/
|
||||||
internalParseAtlas({ meta: { scale }, sourceData }, loadedImage) {
|
internalParseAtlas({ meta: { scale }, sourceData }, loadedImage) {
|
||||||
this.rawImages.push(loadedImage);
|
this.rawImages.push(loadedImage);
|
||||||
|
|
||||||
for (const spriteName in sourceData) {
|
for (const spriteName in sourceData) {
|
||||||
const { frame, sourceSize, spriteSourceSize } = sourceData[spriteName];
|
const { frame, sourceSize, spriteSourceSize } = sourceData[spriteName];
|
||||||
|
|
||||||
let sprite = /** @type {AtlasSprite} */ (this.sprites.get(spriteName));
|
let sprite = /** @type {AtlasSprite} */ (this.sprites.get(spriteName));
|
||||||
|
|
||||||
if (!sprite) {
|
if (!sprite) {
|
||||||
sprite = new AtlasSprite(spriteName);
|
sprite = new AtlasSprite(spriteName);
|
||||||
this.sprites.set(spriteName, sprite);
|
this.sprites.set(spriteName, sprite);
|
||||||
}
|
}
|
||||||
|
|
||||||
const link = new SpriteAtlasLink({
|
const link = new SpriteAtlasLink({
|
||||||
packedX: frame.x,
|
packedX: frame.x,
|
||||||
packedY: frame.y,
|
packedY: frame.y,
|
||||||
packedW: frame.w,
|
packedW: frame.w,
|
||||||
packedH: frame.h,
|
packedH: frame.h,
|
||||||
packOffsetX: spriteSourceSize.x,
|
packOffsetX: spriteSourceSize.x,
|
||||||
packOffsetY: spriteSourceSize.y,
|
packOffsetY: spriteSourceSize.y,
|
||||||
atlas: loadedImage,
|
atlas: loadedImage,
|
||||||
w: sourceSize.w,
|
w: sourceSize.w,
|
||||||
h: sourceSize.h,
|
h: sourceSize.h,
|
||||||
});
|
});
|
||||||
sprite.linksByResolution[scale] = link;
|
sprite.linksByResolution[scale] = link;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes the canvas which shows the question mark, shown when a sprite was not found
|
* Makes the canvas which shows the question mark, shown when a sprite was not found
|
||||||
*/
|
*/
|
||||||
makeSpriteNotFoundCanvas() {
|
makeSpriteNotFoundCanvas() {
|
||||||
const dims = 128;
|
const dims = 128;
|
||||||
|
|
||||||
const [canvas, context] = makeOffscreenBuffer(dims, dims, {
|
const [canvas, context] = makeOffscreenBuffer(dims, dims, {
|
||||||
smooth: false,
|
smooth: false,
|
||||||
label: "not-found-sprite",
|
label: "not-found-sprite",
|
||||||
});
|
});
|
||||||
context.fillStyle = "#f77";
|
context.fillStyle = "#f77";
|
||||||
context.fillRect(0, 0, dims, dims);
|
context.fillRect(0, 0, dims, dims);
|
||||||
|
|
||||||
context.textAlign = "center";
|
context.textAlign = "center";
|
||||||
context.textBaseline = "middle";
|
context.textBaseline = "middle";
|
||||||
context.fillStyle = "#eee";
|
context.fillStyle = "#eee";
|
||||||
context.font = "25px Arial";
|
context.font = "25px Arial";
|
||||||
context.fillText("???", dims / 2, dims / 2);
|
context.fillText("???", dims / 2, dims / 2);
|
||||||
|
|
||||||
// TODO: Not sure why this is set here
|
// TODO: Not sure why this is set here
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
canvas.src = "not-found";
|
canvas.src = "not-found";
|
||||||
|
|
||||||
const sprite = new AtlasSprite("not-found");
|
const sprite = new AtlasSprite("not-found");
|
||||||
["0.1", "0.25", "0.5", "0.75", "1"].forEach(resolution => {
|
["0.1", "0.25", "0.5", "0.75", "1"].forEach(resolution => {
|
||||||
sprite.linksByResolution[resolution] = new SpriteAtlasLink({
|
sprite.linksByResolution[resolution] = new SpriteAtlasLink({
|
||||||
packedX: 0,
|
packedX: 0,
|
||||||
packedY: 0,
|
packedY: 0,
|
||||||
w: dims,
|
w: dims,
|
||||||
h: dims,
|
h: dims,
|
||||||
packOffsetX: 0,
|
packOffsetX: 0,
|
||||||
packOffsetY: 0,
|
packOffsetY: 0,
|
||||||
packedW: dims,
|
packedW: dims,
|
||||||
packedH: dims,
|
packedH: dims,
|
||||||
atlas: canvas,
|
atlas: canvas,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.spriteNotFoundSprite = sprite;
|
this.spriteNotFoundSprite = sprite;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Loader = new LoaderImpl();
|
export const Loader = new LoaderImpl();
|
||||||
|
@ -1,83 +1,92 @@
|
|||||||
/* typehints:start */
|
/* typehints:start */
|
||||||
import { MetaBuilding } from "./meta_building";
|
import { MetaBuilding } from "./meta_building";
|
||||||
import { AtlasSprite } from "../core/sprites";
|
import { AtlasSprite } from "../core/sprites";
|
||||||
import { Vector } from "../core/vector";
|
import { Vector } from "../core/vector";
|
||||||
/* typehints:end */
|
/* typehints:end */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {{
|
* @typedef {{
|
||||||
* metaClass: typeof MetaBuilding,
|
* metaClass: typeof MetaBuilding,
|
||||||
* metaInstance?: MetaBuilding,
|
* metaInstance?: MetaBuilding,
|
||||||
* variant?: string,
|
* variant?: string,
|
||||||
* rotationVariant?: number,
|
* rotationVariant?: number,
|
||||||
* tileSize?: Vector,
|
* tileSize?: Vector,
|
||||||
* sprite?: AtlasSprite,
|
* sprite?: AtlasSprite,
|
||||||
* blueprintSprite?: AtlasSprite,
|
* blueprintSprite?: AtlasSprite,
|
||||||
* silhouetteColor?: string
|
* silhouetteColor?: string
|
||||||
* }} BuildingVariantIdentifier
|
* }} BuildingVariantIdentifier
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores a lookup table for all building variants (for better performance)
|
* Stores a lookup table for all building variants (for better performance)
|
||||||
* @type {Object<number, BuildingVariantIdentifier>}
|
* @type {Object<number, BuildingVariantIdentifier>}
|
||||||
*/
|
*/
|
||||||
export const gBuildingVariants = {
|
export const gBuildingVariants = {
|
||||||
// Set later
|
// Set later
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a new variant
|
* Mapping from 'metaBuildingId/variant/rotationVariant' to building code
|
||||||
* @param {number} id
|
* @type {Map<string, number>}
|
||||||
* @param {typeof MetaBuilding} meta
|
*/
|
||||||
* @param {string} variant
|
const variantsCache = new Map();
|
||||||
* @param {number} rotationVariant
|
|
||||||
*/
|
/**
|
||||||
export function registerBuildingVariant(
|
* Registers a new variant
|
||||||
id,
|
* @param {number} code
|
||||||
meta,
|
* @param {typeof MetaBuilding} meta
|
||||||
variant = "default" /* FIXME: Circular dependency, actually its defaultBuildingVariant */,
|
* @param {string} variant
|
||||||
rotationVariant = 0
|
* @param {number} rotationVariant
|
||||||
) {
|
*/
|
||||||
assert(!gBuildingVariants[id], "Duplicate id: " + id);
|
export function registerBuildingVariant(
|
||||||
gBuildingVariants[id] = {
|
code,
|
||||||
metaClass: meta,
|
meta,
|
||||||
variant,
|
variant = "default" /* FIXME: Circular dependency, actually its defaultBuildingVariant */,
|
||||||
rotationVariant,
|
rotationVariant = 0
|
||||||
// @ts-ignore
|
) {
|
||||||
tileSize: new meta().getDimensions(variant),
|
assert(!gBuildingVariants[code], "Duplicate id: " + code);
|
||||||
};
|
gBuildingVariants[code] = {
|
||||||
}
|
metaClass: meta,
|
||||||
|
variant,
|
||||||
/**
|
rotationVariant,
|
||||||
*
|
// @ts-ignore
|
||||||
* @param {number} code
|
tileSize: new meta().getDimensions(variant),
|
||||||
* @returns {BuildingVariantIdentifier}
|
};
|
||||||
*/
|
}
|
||||||
export function getBuildingDataFromCode(code) {
|
|
||||||
assert(gBuildingVariants[code], "Invalid building code: " + code);
|
/**
|
||||||
return gBuildingVariants[code];
|
*
|
||||||
}
|
* @param {number} code
|
||||||
|
* @returns {BuildingVariantIdentifier}
|
||||||
/**
|
*/
|
||||||
* Finds the code for a given variant
|
export function getBuildingDataFromCode(code) {
|
||||||
* @param {MetaBuilding} metaBuilding
|
assert(gBuildingVariants[code], "Invalid building code: " + code);
|
||||||
* @param {string} variant
|
return gBuildingVariants[code];
|
||||||
* @param {number} rotationVariant
|
}
|
||||||
*/
|
|
||||||
export function getCodeFromBuildingData(metaBuilding, variant, rotationVariant) {
|
/**
|
||||||
for (const key in gBuildingVariants) {
|
* Builds the cache for the codes
|
||||||
const data = gBuildingVariants[key];
|
*/
|
||||||
if (
|
export function buildBuildingCodeCache() {
|
||||||
data.metaInstance.getId() === metaBuilding.getId() &&
|
for (const code in gBuildingVariants) {
|
||||||
data.variant === variant &&
|
const data = gBuildingVariants[code];
|
||||||
data.rotationVariant === rotationVariant
|
const hash = data.metaInstance.getId() + "/" + data.variant + "/" + data.rotationVariant;
|
||||||
) {
|
variantsCache.set(hash, +code);
|
||||||
return +key;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
assertAlways(
|
/**
|
||||||
false,
|
* Finds the code for a given variant
|
||||||
"Building not found by data: " + metaBuilding.getId() + " / " + variant + " / " + rotationVariant
|
* @param {MetaBuilding} metaBuilding
|
||||||
);
|
* @param {string} variant
|
||||||
return 0;
|
* @param {number} rotationVariant
|
||||||
}
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
export function getCodeFromBuildingData(metaBuilding, variant, rotationVariant) {
|
||||||
|
const hash = metaBuilding.getId() + "/" + variant + "/" + rotationVariant;
|
||||||
|
const result = variantsCache.get(hash);
|
||||||
|
if (G_IS_DEV) {
|
||||||
|
assertAlways(!!result, "Building not found by data: " + hash);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
@ -1,56 +1,55 @@
|
|||||||
import { BaseHUDPart } from "../base_hud_part";
|
import { makeDiv } from "../../../core/utils";
|
||||||
import { makeDiv } from "../../../core/utils";
|
import { T } from "../../../translations";
|
||||||
import { T } from "../../../translations";
|
import { BaseHUDPart } from "../base_hud_part";
|
||||||
import { IS_DEMO } from "../../../core/config";
|
|
||||||
|
/** @enum {string} */
|
||||||
/** @enum {string} */
|
export const enumNotificationType = {
|
||||||
export const enumNotificationType = {
|
saved: "saved",
|
||||||
saved: "saved",
|
upgrade: "upgrade",
|
||||||
upgrade: "upgrade",
|
success: "success",
|
||||||
success: "success",
|
};
|
||||||
};
|
|
||||||
|
const notificationDuration = 3;
|
||||||
const notificationDuration = 3;
|
|
||||||
|
export class HUDNotifications extends BaseHUDPart {
|
||||||
export class HUDNotifications extends BaseHUDPart {
|
createElements(parent) {
|
||||||
createElements(parent) {
|
this.element = makeDiv(parent, "ingame_HUD_Notifications", [], ``);
|
||||||
this.element = makeDiv(parent, "ingame_HUD_Notifications", [], ``);
|
}
|
||||||
}
|
|
||||||
|
initialize() {
|
||||||
initialize() {
|
this.root.hud.signals.notification.add(this.onNotification, this);
|
||||||
this.root.hud.signals.notification.add(this.onNotification, this);
|
|
||||||
|
/** @type {Array<{ element: HTMLElement, expireAt: number}>} */
|
||||||
/** @type {Array<{ element: HTMLElement, expireAt: number}>} */
|
this.notificationElements = [];
|
||||||
this.notificationElements = [];
|
|
||||||
|
// Automatic notifications
|
||||||
// Automatic notifications
|
this.root.signals.gameSaved.add(() =>
|
||||||
this.root.signals.gameSaved.add(() =>
|
this.onNotification(T.ingame.notifications.gameSaved, enumNotificationType.saved)
|
||||||
this.onNotification(T.ingame.notifications.gameSaved, enumNotificationType.saved)
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
/**
|
* @param {string} message
|
||||||
* @param {string} message
|
* @param {enumNotificationType} type
|
||||||
* @param {enumNotificationType} type
|
*/
|
||||||
*/
|
onNotification(message, type) {
|
||||||
onNotification(message, type) {
|
const element = makeDiv(this.element, null, ["notification", "type-" + type], message);
|
||||||
const element = makeDiv(this.element, null, ["notification", "type-" + type], message);
|
element.setAttribute("data-icon", "icons/notification_" + type + ".png");
|
||||||
element.setAttribute("data-icon", "icons/notification_" + type + ".png");
|
|
||||||
|
this.notificationElements.push({
|
||||||
this.notificationElements.push({
|
element,
|
||||||
element,
|
expireAt: this.root.time.realtimeNow() + notificationDuration,
|
||||||
expireAt: this.root.time.realtimeNow() + notificationDuration,
|
});
|
||||||
});
|
}
|
||||||
}
|
|
||||||
|
update() {
|
||||||
update() {
|
const now = this.root.time.realtimeNow();
|
||||||
const now = this.root.time.realtimeNow();
|
for (let i = 0; i < this.notificationElements.length; ++i) {
|
||||||
for (let i = 0; i < this.notificationElements.length; ++i) {
|
const handle = this.notificationElements[i];
|
||||||
const handle = this.notificationElements[i];
|
if (handle.expireAt <= now) {
|
||||||
if (handle.expireAt <= now) {
|
handle.element.remove();
|
||||||
handle.element.remove();
|
this.notificationElements.splice(i, 1);
|
||||||
this.notificationElements.splice(i, 1);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,236 +1,236 @@
|
|||||||
import { globalConfig } from "../core/config";
|
import { globalConfig } from "../core/config";
|
||||||
import { Vector } from "../core/vector";
|
import { Vector } from "../core/vector";
|
||||||
import { BasicSerializableObject, types } from "../savegame/serialization";
|
import { BasicSerializableObject, types } from "../savegame/serialization";
|
||||||
import { BaseItem } from "./base_item";
|
import { BaseItem } from "./base_item";
|
||||||
import { Entity } from "./entity";
|
import { Entity } from "./entity";
|
||||||
import { MapChunkView } from "./map_chunk_view";
|
import { MapChunkView } from "./map_chunk_view";
|
||||||
import { GameRoot } from "./root";
|
import { GameRoot } from "./root";
|
||||||
|
|
||||||
export class BaseMap extends BasicSerializableObject {
|
export class BaseMap extends BasicSerializableObject {
|
||||||
static getId() {
|
static getId() {
|
||||||
return "Map";
|
return "Map";
|
||||||
}
|
}
|
||||||
|
|
||||||
static getSchema() {
|
static getSchema() {
|
||||||
return {
|
return {
|
||||||
seed: types.uint,
|
seed: types.uint,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {GameRoot} root
|
* @param {GameRoot} root
|
||||||
*/
|
*/
|
||||||
constructor(root) {
|
constructor(root) {
|
||||||
super();
|
super();
|
||||||
this.root = root;
|
this.root = root;
|
||||||
|
|
||||||
this.seed = 0;
|
this.seed = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mapping of 'X|Y' to chunk
|
* Mapping of 'X|Y' to chunk
|
||||||
* @type {Map<string, MapChunkView>} */
|
* @type {Map<string, MapChunkView>} */
|
||||||
this.chunksById = new Map();
|
this.chunksById = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the given chunk by index
|
* Returns the given chunk by index
|
||||||
* @param {number} chunkX
|
* @param {number} chunkX
|
||||||
* @param {number} chunkY
|
* @param {number} chunkY
|
||||||
*/
|
*/
|
||||||
getChunk(chunkX, chunkY, createIfNotExistent = false) {
|
getChunk(chunkX, chunkY, createIfNotExistent = false) {
|
||||||
const chunkIdentifier = chunkX + "|" + chunkY;
|
const chunkIdentifier = chunkX + "|" + chunkY;
|
||||||
let storedChunk;
|
let storedChunk;
|
||||||
|
|
||||||
if ((storedChunk = this.chunksById.get(chunkIdentifier))) {
|
if ((storedChunk = this.chunksById.get(chunkIdentifier))) {
|
||||||
return storedChunk;
|
return storedChunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (createIfNotExistent) {
|
if (createIfNotExistent) {
|
||||||
const instance = new MapChunkView(this.root, chunkX, chunkY);
|
const instance = new MapChunkView(this.root, chunkX, chunkY);
|
||||||
this.chunksById.set(chunkIdentifier, instance);
|
this.chunksById.set(chunkIdentifier, instance);
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets or creates a new chunk if not existent for the given tile
|
* Gets or creates a new chunk if not existent for the given tile
|
||||||
* @param {number} tileX
|
* @param {number} tileX
|
||||||
* @param {number} tileY
|
* @param {number} tileY
|
||||||
* @returns {MapChunkView}
|
* @returns {MapChunkView}
|
||||||
*/
|
*/
|
||||||
getOrCreateChunkAtTile(tileX, tileY) {
|
getOrCreateChunkAtTile(tileX, tileY) {
|
||||||
const chunkX = Math.floor(tileX / globalConfig.mapChunkSize);
|
const chunkX = Math.floor(tileX / globalConfig.mapChunkSize);
|
||||||
const chunkY = Math.floor(tileY / globalConfig.mapChunkSize);
|
const chunkY = Math.floor(tileY / globalConfig.mapChunkSize);
|
||||||
return this.getChunk(chunkX, chunkY, true);
|
return this.getChunk(chunkX, chunkY, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a chunk if not existent for the given tile
|
* Gets a chunk if not existent for the given tile
|
||||||
* @param {number} tileX
|
* @param {number} tileX
|
||||||
* @param {number} tileY
|
* @param {number} tileY
|
||||||
* @returns {MapChunkView?}
|
* @returns {MapChunkView?}
|
||||||
*/
|
*/
|
||||||
getChunkAtTileOrNull(tileX, tileY) {
|
getChunkAtTileOrNull(tileX, tileY) {
|
||||||
const chunkX = Math.floor(tileX / globalConfig.mapChunkSize);
|
const chunkX = Math.floor(tileX / globalConfig.mapChunkSize);
|
||||||
const chunkY = Math.floor(tileY / globalConfig.mapChunkSize);
|
const chunkY = Math.floor(tileY / globalConfig.mapChunkSize);
|
||||||
return this.getChunk(chunkX, chunkY, false);
|
return this.getChunk(chunkX, chunkY, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a given tile is within the map bounds
|
* Checks if a given tile is within the map bounds
|
||||||
* @param {Vector} tile
|
* @param {Vector} tile
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
isValidTile(tile) {
|
isValidTile(tile) {
|
||||||
if (G_IS_DEV) {
|
if (G_IS_DEV) {
|
||||||
assert(tile instanceof Vector, "tile is not a vector");
|
assert(tile instanceof Vector, "tile is not a vector");
|
||||||
}
|
}
|
||||||
return Number.isInteger(tile.x) && Number.isInteger(tile.y);
|
return Number.isInteger(tile.x) && Number.isInteger(tile.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the tile content of a given tile
|
* Returns the tile content of a given tile
|
||||||
* @param {Vector} tile
|
* @param {Vector} tile
|
||||||
* @param {Layer} layer
|
* @param {Layer} layer
|
||||||
* @returns {Entity} Entity or null
|
* @returns {Entity} Entity or null
|
||||||
*/
|
*/
|
||||||
getTileContent(tile, layer) {
|
getTileContent(tile, layer) {
|
||||||
if (G_IS_DEV) {
|
if (G_IS_DEV) {
|
||||||
this.internalCheckTile(tile);
|
this.internalCheckTile(tile);
|
||||||
}
|
}
|
||||||
const chunk = this.getChunkAtTileOrNull(tile.x, tile.y);
|
const chunk = this.getChunkAtTileOrNull(tile.x, tile.y);
|
||||||
return chunk && chunk.getLayerContentFromWorldCoords(tile.x, tile.y, layer);
|
return chunk && chunk.getLayerContentFromWorldCoords(tile.x, tile.y, layer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the lower layers content of the given tile
|
* Returns the lower layers content of the given tile
|
||||||
* @param {number} x
|
* @param {number} x
|
||||||
* @param {number} y
|
* @param {number} y
|
||||||
* @returns {BaseItem=}
|
* @returns {BaseItem=}
|
||||||
*/
|
*/
|
||||||
getLowerLayerContentXY(x, y) {
|
getLowerLayerContentXY(x, y) {
|
||||||
return this.getOrCreateChunkAtTile(x, y).getLowerLayerFromWorldCoords(x, y);
|
return this.getOrCreateChunkAtTile(x, y).getLowerLayerFromWorldCoords(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the tile content of a given tile
|
* Returns the tile content of a given tile
|
||||||
* @param {number} x
|
* @param {number} x
|
||||||
* @param {number} y
|
* @param {number} y
|
||||||
* @param {Layer} layer
|
* @param {Layer} layer
|
||||||
* @returns {Entity} Entity or null
|
* @returns {Entity} Entity or null
|
||||||
*/
|
*/
|
||||||
getLayerContentXY(x, y, layer) {
|
getLayerContentXY(x, y, layer) {
|
||||||
const chunk = this.getChunkAtTileOrNull(x, y);
|
const chunk = this.getChunkAtTileOrNull(x, y);
|
||||||
return chunk && chunk.getLayerContentFromWorldCoords(x, y, layer);
|
return chunk && chunk.getLayerContentFromWorldCoords(x, y, layer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the tile contents of a given tile
|
* Returns the tile contents of a given tile
|
||||||
* @param {number} x
|
* @param {number} x
|
||||||
* @param {number} y
|
* @param {number} y
|
||||||
* @returns {Array<Entity>} Entity or null
|
* @returns {Array<Entity>} Entity or null
|
||||||
*/
|
*/
|
||||||
getLayersContentsMultipleXY(x, y) {
|
getLayersContentsMultipleXY(x, y) {
|
||||||
const chunk = this.getChunkAtTileOrNull(x, y);
|
const chunk = this.getChunkAtTileOrNull(x, y);
|
||||||
if (!chunk) {
|
if (!chunk) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return chunk.getLayersContentsMultipleFromWorldCoords(x, y);
|
return chunk.getLayersContentsMultipleFromWorldCoords(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the tile is used
|
* Checks if the tile is used
|
||||||
* @param {Vector} tile
|
* @param {Vector} tile
|
||||||
* @param {Layer} layer
|
* @param {Layer} layer
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
isTileUsed(tile, layer) {
|
isTileUsed(tile, layer) {
|
||||||
if (G_IS_DEV) {
|
if (G_IS_DEV) {
|
||||||
this.internalCheckTile(tile);
|
this.internalCheckTile(tile);
|
||||||
}
|
}
|
||||||
const chunk = this.getChunkAtTileOrNull(tile.x, tile.y);
|
const chunk = this.getChunkAtTileOrNull(tile.x, tile.y);
|
||||||
return chunk && chunk.getLayerContentFromWorldCoords(tile.x, tile.y, layer) != null;
|
return chunk && chunk.getLayerContentFromWorldCoords(tile.x, tile.y, layer) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the tile is used
|
* Checks if the tile is used
|
||||||
* @param {number} x
|
* @param {number} x
|
||||||
* @param {number} y
|
* @param {number} y
|
||||||
* @param {Layer} layer
|
* @param {Layer} layer
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
isTileUsedXY(x, y, layer) {
|
isTileUsedXY(x, y, layer) {
|
||||||
const chunk = this.getChunkAtTileOrNull(x, y);
|
const chunk = this.getChunkAtTileOrNull(x, y);
|
||||||
return chunk && chunk.getLayerContentFromWorldCoords(x, y, layer) != null;
|
return chunk && chunk.getLayerContentFromWorldCoords(x, y, layer) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the tiles content
|
* Sets the tiles content
|
||||||
* @param {Vector} tile
|
* @param {Vector} tile
|
||||||
* @param {Entity} entity
|
* @param {Entity} entity
|
||||||
*/
|
*/
|
||||||
setTileContent(tile, entity) {
|
setTileContent(tile, entity) {
|
||||||
if (G_IS_DEV) {
|
if (G_IS_DEV) {
|
||||||
this.internalCheckTile(tile);
|
this.internalCheckTile(tile);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.getOrCreateChunkAtTile(tile.x, tile.y).setLayerContentFromWorldCords(
|
this.getOrCreateChunkAtTile(tile.x, tile.y).setLayerContentFromWorldCords(
|
||||||
tile.x,
|
tile.x,
|
||||||
tile.y,
|
tile.y,
|
||||||
entity,
|
entity,
|
||||||
entity.layer
|
entity.layer
|
||||||
);
|
);
|
||||||
|
|
||||||
const staticComponent = entity.components.StaticMapEntity;
|
const staticComponent = entity.components.StaticMapEntity;
|
||||||
assert(staticComponent, "Can only place static map entities in tiles");
|
assert(staticComponent, "Can only place static map entities in tiles");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Places an entity with the StaticMapEntity component
|
* Places an entity with the StaticMapEntity component
|
||||||
* @param {Entity} entity
|
* @param {Entity} entity
|
||||||
*/
|
*/
|
||||||
placeStaticEntity(entity) {
|
placeStaticEntity(entity) {
|
||||||
assert(entity.components.StaticMapEntity, "Entity is not static");
|
assert(entity.components.StaticMapEntity, "Entity is not static");
|
||||||
const staticComp = entity.components.StaticMapEntity;
|
const staticComp = entity.components.StaticMapEntity;
|
||||||
const rect = staticComp.getTileSpaceBounds();
|
const rect = staticComp.getTileSpaceBounds();
|
||||||
for (let dx = 0; dx < rect.w; ++dx) {
|
for (let dx = 0; dx < rect.w; ++dx) {
|
||||||
for (let dy = 0; dy < rect.h; ++dy) {
|
for (let dy = 0; dy < rect.h; ++dy) {
|
||||||
const x = rect.x + dx;
|
const x = rect.x + dx;
|
||||||
const y = rect.y + dy;
|
const y = rect.y + dy;
|
||||||
this.getOrCreateChunkAtTile(x, y).setLayerContentFromWorldCords(x, y, entity, entity.layer);
|
this.getOrCreateChunkAtTile(x, y).setLayerContentFromWorldCords(x, y, entity, entity.layer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes an entity with the StaticMapEntity component
|
* Removes an entity with the StaticMapEntity component
|
||||||
* @param {Entity} entity
|
* @param {Entity} entity
|
||||||
*/
|
*/
|
||||||
removeStaticEntity(entity) {
|
removeStaticEntity(entity) {
|
||||||
assert(entity.components.StaticMapEntity, "Entity is not static");
|
assert(entity.components.StaticMapEntity, "Entity is not static");
|
||||||
const staticComp = entity.components.StaticMapEntity;
|
const staticComp = entity.components.StaticMapEntity;
|
||||||
const rect = staticComp.getTileSpaceBounds();
|
const rect = staticComp.getTileSpaceBounds();
|
||||||
for (let dx = 0; dx < rect.w; ++dx) {
|
for (let dx = 0; dx < rect.w; ++dx) {
|
||||||
for (let dy = 0; dy < rect.h; ++dy) {
|
for (let dy = 0; dy < rect.h; ++dy) {
|
||||||
const x = rect.x + dx;
|
const x = rect.x + dx;
|
||||||
const y = rect.y + dy;
|
const y = rect.y + dy;
|
||||||
this.getOrCreateChunkAtTile(x, y).setLayerContentFromWorldCords(x, y, null, entity.layer);
|
this.getOrCreateChunkAtTile(x, y).setLayerContentFromWorldCords(x, y, null, entity.layer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal
|
// Internal
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks a given tile for validty
|
* Checks a given tile for validty
|
||||||
* @param {Vector} tile
|
* @param {Vector} tile
|
||||||
*/
|
*/
|
||||||
internalCheckTile(tile) {
|
internalCheckTile(tile) {
|
||||||
assert(tile instanceof Vector, "tile is not a vector: " + tile);
|
assert(tile instanceof Vector, "tile is not a vector: " + tile);
|
||||||
assert(tile.x % 1 === 0, "Tile X is not a valid integer: " + tile.x);
|
assert(tile.x % 1 === 0, "Tile X is not a valid integer: " + tile.x);
|
||||||
assert(tile.y % 1 === 0, "Tile Y is not a valid integer: " + tile.y);
|
assert(tile.y % 1 === 0, "Tile Y is not a valid integer: " + tile.y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,101 +1,101 @@
|
|||||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||||
import { StorageComponent } from "../components/storage";
|
import { StorageComponent } from "../components/storage";
|
||||||
import { DrawParameters } from "../../core/draw_parameters";
|
import { DrawParameters } from "../../core/draw_parameters";
|
||||||
import { formatBigNumber, lerp } from "../../core/utils";
|
import { formatBigNumber, lerp } from "../../core/utils";
|
||||||
import { Loader } from "../../core/loader";
|
import { Loader } from "../../core/loader";
|
||||||
import { BOOL_TRUE_SINGLETON, BOOL_FALSE_SINGLETON } from "../items/boolean_item";
|
import { BOOL_TRUE_SINGLETON, BOOL_FALSE_SINGLETON } from "../items/boolean_item";
|
||||||
import { MapChunkView } from "../map_chunk_view";
|
import { MapChunkView } from "../map_chunk_view";
|
||||||
|
|
||||||
export class StorageSystem extends GameSystemWithFilter {
|
export class StorageSystem extends GameSystemWithFilter {
|
||||||
constructor(root) {
|
constructor(root) {
|
||||||
super(root, [StorageComponent]);
|
super(root, [StorageComponent]);
|
||||||
|
|
||||||
this.storageOverlaySprite = Loader.getSprite("sprites/misc/storage_overlay.png");
|
this.storageOverlaySprite = Loader.getSprite("sprites/misc/storage_overlay.png");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores which uids were already drawn to avoid drawing entities twice
|
* Stores which uids were already drawn to avoid drawing entities twice
|
||||||
* @type {Set<number>}
|
* @type {Set<number>}
|
||||||
*/
|
*/
|
||||||
this.drawnUids = new Set();
|
this.drawnUids = new Set();
|
||||||
|
|
||||||
this.root.signals.gameFrameStarted.add(this.clearDrawnUids, this);
|
this.root.signals.gameFrameStarted.add(this.clearDrawnUids, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
clearDrawnUids() {
|
clearDrawnUids() {
|
||||||
this.drawnUids.clear();
|
this.drawnUids.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
for (let i = 0; i < this.allEntities.length; ++i) {
|
for (let i = 0; i < this.allEntities.length; ++i) {
|
||||||
const entity = this.allEntities[i];
|
const entity = this.allEntities[i];
|
||||||
const storageComp = entity.components.Storage;
|
const storageComp = entity.components.Storage;
|
||||||
const pinsComp = entity.components.WiredPins;
|
const pinsComp = entity.components.WiredPins;
|
||||||
|
|
||||||
// Eject from storage
|
// Eject from storage
|
||||||
if (storageComp.storedItem && storageComp.storedCount > 0) {
|
if (storageComp.storedItem && storageComp.storedCount > 0) {
|
||||||
const ejectorComp = entity.components.ItemEjector;
|
const ejectorComp = entity.components.ItemEjector;
|
||||||
|
|
||||||
const nextSlot = ejectorComp.getFirstFreeSlot();
|
const nextSlot = ejectorComp.getFirstFreeSlot();
|
||||||
if (nextSlot !== null) {
|
if (nextSlot !== null) {
|
||||||
if (ejectorComp.tryEject(nextSlot, storageComp.storedItem)) {
|
if (ejectorComp.tryEject(nextSlot, storageComp.storedItem)) {
|
||||||
storageComp.storedCount--;
|
storageComp.storedCount--;
|
||||||
|
|
||||||
if (storageComp.storedCount === 0) {
|
if (storageComp.storedCount === 0) {
|
||||||
storageComp.storedItem = null;
|
storageComp.storedItem = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let targetAlpha = storageComp.storedCount > 0 ? 1 : 0;
|
let targetAlpha = storageComp.storedCount > 0 ? 1 : 0;
|
||||||
storageComp.overlayOpacity = lerp(storageComp.overlayOpacity, targetAlpha, 0.05);
|
storageComp.overlayOpacity = lerp(storageComp.overlayOpacity, targetAlpha, 0.05);
|
||||||
|
|
||||||
pinsComp.slots[0].value = storageComp.storedItem;
|
pinsComp.slots[0].value = storageComp.storedItem;
|
||||||
pinsComp.slots[1].value = storageComp.getIsFull() ? BOOL_TRUE_SINGLETON : BOOL_FALSE_SINGLETON;
|
pinsComp.slots[1].value = storageComp.getIsFull() ? BOOL_TRUE_SINGLETON : BOOL_FALSE_SINGLETON;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {DrawParameters} parameters
|
* @param {DrawParameters} parameters
|
||||||
* @param {MapChunkView} chunk
|
* @param {MapChunkView} chunk
|
||||||
*/
|
*/
|
||||||
drawChunk(parameters, chunk) {
|
drawChunk(parameters, chunk) {
|
||||||
const contents = chunk.containedEntitiesByLayer.regular;
|
const contents = chunk.containedEntitiesByLayer.regular;
|
||||||
for (let i = 0; i < contents.length; ++i) {
|
for (let i = 0; i < contents.length; ++i) {
|
||||||
const entity = contents[i];
|
const entity = contents[i];
|
||||||
const storageComp = entity.components.Storage;
|
const storageComp = entity.components.Storage;
|
||||||
if (!storageComp) {
|
if (!storageComp) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const storedItem = storageComp.storedItem;
|
const storedItem = storageComp.storedItem;
|
||||||
if (!storedItem) {
|
if (!storedItem) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.drawnUids.has(entity.uid)) {
|
if (this.drawnUids.has(entity.uid)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.drawnUids.add(entity.uid);
|
this.drawnUids.add(entity.uid);
|
||||||
|
|
||||||
const staticComp = entity.components.StaticMapEntity;
|
const staticComp = entity.components.StaticMapEntity;
|
||||||
|
|
||||||
const context = parameters.context;
|
const context = parameters.context;
|
||||||
context.globalAlpha = storageComp.overlayOpacity;
|
context.globalAlpha = storageComp.overlayOpacity;
|
||||||
const center = staticComp.getTileSpaceBounds().getCenter().toWorldSpace();
|
const center = staticComp.getTileSpaceBounds().getCenter().toWorldSpace();
|
||||||
storedItem.drawItemCenteredClipped(center.x, center.y, parameters, 30);
|
storedItem.drawItemCenteredClipped(center.x, center.y, parameters, 30);
|
||||||
|
|
||||||
this.storageOverlaySprite.drawCached(parameters, center.x - 15, center.y + 15, 30, 15);
|
this.storageOverlaySprite.drawCached(parameters, center.x - 15, center.y + 15, 30, 15);
|
||||||
|
|
||||||
if (parameters.visibleRect.containsCircle(center.x, center.y + 25, 20)) {
|
if (parameters.visibleRect.containsCircle(center.x, center.y + 25, 20)) {
|
||||||
context.font = "bold 10px GameFont";
|
context.font = "bold 10px GameFont";
|
||||||
context.textAlign = "center";
|
context.textAlign = "center";
|
||||||
context.fillStyle = "#64666e";
|
context.fillStyle = "#64666e";
|
||||||
context.fillText(formatBigNumber(storageComp.storedCount), center.x, center.y + 25.5);
|
context.fillText(formatBigNumber(storageComp.storedCount), center.x, center.y + 25.5);
|
||||||
context.textAlign = "left";
|
context.textAlign = "left";
|
||||||
}
|
}
|
||||||
context.globalAlpha = 1;
|
context.globalAlpha = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,438 +1,458 @@
|
|||||||
import { APPLICATION_ERROR_OCCURED } from "../core/error_handler";
|
import { APPLICATION_ERROR_OCCURED } from "../core/error_handler";
|
||||||
import { GameState } from "../core/game_state";
|
import { GameState } from "../core/game_state";
|
||||||
import { logSection, createLogger } from "../core/logging";
|
import { logSection, createLogger } from "../core/logging";
|
||||||
import { waitNextFrame } from "../core/utils";
|
import { waitNextFrame } from "../core/utils";
|
||||||
import { globalConfig } from "../core/config";
|
import { globalConfig } from "../core/config";
|
||||||
import { GameLoadingOverlay } from "../game/game_loading_overlay";
|
import { GameLoadingOverlay } from "../game/game_loading_overlay";
|
||||||
import { KeyActionMapper } from "../game/key_action_mapper";
|
import { KeyActionMapper } from "../game/key_action_mapper";
|
||||||
import { Savegame } from "../savegame/savegame";
|
import { Savegame } from "../savegame/savegame";
|
||||||
import { GameCore } from "../game/core";
|
import { GameCore } from "../game/core";
|
||||||
import { MUSIC } from "../platform/sound";
|
import { MUSIC } from "../platform/sound";
|
||||||
|
|
||||||
const logger = createLogger("state/ingame");
|
const logger = createLogger("state/ingame");
|
||||||
|
|
||||||
// Different sub-states
|
// Different sub-states
|
||||||
const stages = {
|
const stages = {
|
||||||
s3_createCore: "🌈 3: Create core",
|
s3_createCore: "🌈 3: Create core",
|
||||||
s4_A_initEmptyGame: "🌈 4/A: Init empty game",
|
s4_A_initEmptyGame: "🌈 4/A: Init empty game",
|
||||||
s4_B_resumeGame: "🌈 4/B: Resume game",
|
s4_B_resumeGame: "🌈 4/B: Resume game",
|
||||||
|
|
||||||
s5_firstUpdate: "🌈 5: First game update",
|
s5_firstUpdate: "🌈 5: First game update",
|
||||||
s6_postLoadHook: "🌈 6: Post load hook",
|
s6_postLoadHook: "🌈 6: Post load hook",
|
||||||
s7_warmup: "🌈 7: Warmup",
|
s7_warmup: "🌈 7: Warmup",
|
||||||
|
|
||||||
s10_gameRunning: "🌈 10: Game finally running",
|
s10_gameRunning: "🌈 10: Game finally running",
|
||||||
|
|
||||||
leaving: "🌈 Saving, then leaving the game",
|
leaving: "🌈 Saving, then leaving the game",
|
||||||
destroyed: "🌈 DESTROYED: Core is empty and waits for state leave",
|
destroyed: "🌈 DESTROYED: Core is empty and waits for state leave",
|
||||||
initFailed: "🌈 ERROR: Initialization failed!",
|
initFailed: "🌈 ERROR: Initialization failed!",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const gameCreationAction = {
|
export const gameCreationAction = {
|
||||||
new: "new-game",
|
new: "new-game",
|
||||||
resume: "resume-game",
|
resume: "resume-game",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Typehints
|
// Typehints
|
||||||
export class GameCreationPayload {
|
export class GameCreationPayload {
|
||||||
constructor() {
|
constructor() {
|
||||||
/** @type {boolean|undefined} */
|
/** @type {boolean|undefined} */
|
||||||
this.fastEnter;
|
this.fastEnter;
|
||||||
|
|
||||||
/** @type {Savegame} */
|
/** @type {Savegame} */
|
||||||
this.savegame;
|
this.savegame;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class InGameState extends GameState {
|
export class InGameState extends GameState {
|
||||||
constructor() {
|
constructor() {
|
||||||
super("InGameState");
|
super("InGameState");
|
||||||
|
|
||||||
/** @type {GameCreationPayload} */
|
/** @type {GameCreationPayload} */
|
||||||
this.creationPayload = null;
|
this.creationPayload = null;
|
||||||
|
|
||||||
// Stores current stage
|
// Stores current stage
|
||||||
this.stage = "";
|
this.stage = "";
|
||||||
|
|
||||||
/** @type {GameCore} */
|
/** @type {GameCore} */
|
||||||
this.core = null;
|
this.core = null;
|
||||||
|
|
||||||
/** @type {KeyActionMapper} */
|
/** @type {KeyActionMapper} */
|
||||||
this.keyActionMapper = null;
|
this.keyActionMapper = null;
|
||||||
|
|
||||||
/** @type {GameLoadingOverlay} */
|
/** @type {GameLoadingOverlay} */
|
||||||
this.loadingOverlay = null;
|
this.loadingOverlay = null;
|
||||||
|
|
||||||
/** @type {Savegame} */
|
/** @type {Savegame} */
|
||||||
this.savegame;
|
this.savegame = null;
|
||||||
|
|
||||||
this.boundInputFilter = this.filterInput.bind(this);
|
this.boundInputFilter = this.filterInput.bind(this);
|
||||||
}
|
|
||||||
|
/**
|
||||||
/**
|
* Whether we are currently saving the game
|
||||||
* Switches the game into another sub-state
|
* @TODO: This doesn't realy fit here
|
||||||
* @param {string} stage
|
*/
|
||||||
*/
|
this.currentSavePromise = null;
|
||||||
switchStage(stage) {
|
}
|
||||||
assert(stage, "Got empty stage");
|
|
||||||
if (stage !== this.stage) {
|
/**
|
||||||
this.stage = stage;
|
* Switches the game into another sub-state
|
||||||
logger.log(this.stage);
|
* @param {string} stage
|
||||||
return true;
|
*/
|
||||||
} else {
|
switchStage(stage) {
|
||||||
// log(this, "Re entering", stage);
|
assert(stage, "Got empty stage");
|
||||||
return false;
|
if (stage !== this.stage) {
|
||||||
}
|
this.stage = stage;
|
||||||
}
|
logger.log(this.stage);
|
||||||
|
return true;
|
||||||
// GameState implementation
|
} else {
|
||||||
getInnerHTML() {
|
// log(this, "Re entering", stage);
|
||||||
return "";
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
getThemeMusic() {
|
|
||||||
return MUSIC.theme;
|
// GameState implementation
|
||||||
}
|
getInnerHTML() {
|
||||||
|
return "";
|
||||||
onBeforeExit() {
|
}
|
||||||
// logger.log("Saving before quitting");
|
|
||||||
// return this.doSave().then(() => {
|
getThemeMusic() {
|
||||||
// logger.log(this, "Successfully saved");
|
return MUSIC.theme;
|
||||||
// // this.stageDestroyed();
|
}
|
||||||
// });
|
|
||||||
}
|
onBeforeExit() {
|
||||||
|
// logger.log("Saving before quitting");
|
||||||
onAppPause() {
|
// return this.doSave().then(() => {
|
||||||
// if (this.stage === stages.s10_gameRunning) {
|
// logger.log(this, "Successfully saved");
|
||||||
// logger.log("Saving because app got paused");
|
// // this.stageDestroyed();
|
||||||
// this.doSave();
|
// });
|
||||||
// }
|
}
|
||||||
}
|
|
||||||
|
onAppPause() {
|
||||||
getHasFadeIn() {
|
// if (this.stage === stages.s10_gameRunning) {
|
||||||
return false;
|
// logger.log("Saving because app got paused");
|
||||||
}
|
// this.doSave();
|
||||||
|
// }
|
||||||
getPauseOnFocusLost() {
|
}
|
||||||
return false;
|
|
||||||
}
|
getHasFadeIn() {
|
||||||
|
return false;
|
||||||
getHasUnloadConfirmation() {
|
}
|
||||||
return true;
|
|
||||||
}
|
getPauseOnFocusLost() {
|
||||||
|
return false;
|
||||||
onLeave() {
|
}
|
||||||
if (this.core) {
|
|
||||||
this.stageDestroyed();
|
getHasUnloadConfirmation() {
|
||||||
}
|
return true;
|
||||||
this.app.inputMgr.dismountFilter(this.boundInputFilter);
|
}
|
||||||
}
|
|
||||||
|
onLeave() {
|
||||||
onResized(w, h) {
|
if (this.core) {
|
||||||
super.onResized(w, h);
|
this.stageDestroyed();
|
||||||
if (this.stage === stages.s10_gameRunning) {
|
}
|
||||||
this.core.resize(w, h);
|
this.app.inputMgr.dismountFilter(this.boundInputFilter);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
onResized(w, h) {
|
||||||
// ---- End of GameState implementation
|
super.onResized(w, h);
|
||||||
|
if (this.stage === stages.s10_gameRunning) {
|
||||||
/**
|
this.core.resize(w, h);
|
||||||
* Goes back to the menu state
|
}
|
||||||
*/
|
}
|
||||||
goBackToMenu() {
|
|
||||||
this.saveThenGoToState("MainMenuState");
|
// ---- End of GameState implementation
|
||||||
}
|
|
||||||
|
/**
|
||||||
/**
|
* Goes back to the menu state
|
||||||
* Goes back to the settings state
|
*/
|
||||||
*/
|
goBackToMenu() {
|
||||||
goToSettings() {
|
this.saveThenGoToState("MainMenuState");
|
||||||
this.saveThenGoToState("SettingsState", {
|
}
|
||||||
backToStateId: this.key,
|
|
||||||
backToStatePayload: this.creationPayload,
|
/**
|
||||||
});
|
* Goes back to the settings state
|
||||||
}
|
*/
|
||||||
|
goToSettings() {
|
||||||
/**
|
this.saveThenGoToState("SettingsState", {
|
||||||
* Goes back to the settings state
|
backToStateId: this.key,
|
||||||
*/
|
backToStatePayload: this.creationPayload,
|
||||||
goToKeybindings() {
|
});
|
||||||
this.saveThenGoToState("KeybindingsState", {
|
}
|
||||||
backToStateId: this.key,
|
|
||||||
backToStatePayload: this.creationPayload,
|
/**
|
||||||
});
|
* Goes back to the settings state
|
||||||
}
|
*/
|
||||||
|
goToKeybindings() {
|
||||||
/**
|
this.saveThenGoToState("KeybindingsState", {
|
||||||
* Moves to a state outside of the game
|
backToStateId: this.key,
|
||||||
* @param {string} stateId
|
backToStatePayload: this.creationPayload,
|
||||||
* @param {any=} payload
|
});
|
||||||
*/
|
}
|
||||||
saveThenGoToState(stateId, payload) {
|
|
||||||
if (this.stage === stages.leaving || this.stage === stages.destroyed) {
|
/**
|
||||||
logger.warn(
|
* Moves to a state outside of the game
|
||||||
"Tried to leave game twice or during destroy:",
|
* @param {string} stateId
|
||||||
this.stage,
|
* @param {any=} payload
|
||||||
"(attempted to move to",
|
*/
|
||||||
stateId,
|
saveThenGoToState(stateId, payload) {
|
||||||
")"
|
if (this.stage === stages.leaving || this.stage === stages.destroyed) {
|
||||||
);
|
logger.warn(
|
||||||
return;
|
"Tried to leave game twice or during destroy:",
|
||||||
}
|
this.stage,
|
||||||
this.stageLeavingGame();
|
"(attempted to move to",
|
||||||
this.doSave().then(() => {
|
stateId,
|
||||||
this.stageDestroyed();
|
")"
|
||||||
this.moveToState(stateId, payload);
|
);
|
||||||
});
|
return;
|
||||||
}
|
}
|
||||||
|
this.stageLeavingGame();
|
||||||
onBackButton() {
|
this.doSave().then(() => {
|
||||||
// do nothing
|
this.stageDestroyed();
|
||||||
}
|
this.moveToState(stateId, payload);
|
||||||
|
});
|
||||||
/**
|
}
|
||||||
* Called when the game somehow failed to initialize. Resets everything to basic state and
|
|
||||||
* then goes to the main menu, showing the error
|
onBackButton() {
|
||||||
* @param {string} err
|
// do nothing
|
||||||
*/
|
}
|
||||||
onInitializationFailure(err) {
|
|
||||||
if (this.switchStage(stages.initFailed)) {
|
/**
|
||||||
logger.error("Init failure:", err);
|
* Called when the game somehow failed to initialize. Resets everything to basic state and
|
||||||
this.stageDestroyed();
|
* then goes to the main menu, showing the error
|
||||||
this.moveToState("MainMenuState", { loadError: err });
|
* @param {string} err
|
||||||
}
|
*/
|
||||||
}
|
onInitializationFailure(err) {
|
||||||
|
if (this.switchStage(stages.initFailed)) {
|
||||||
// STAGES
|
logger.error("Init failure:", err);
|
||||||
|
this.stageDestroyed();
|
||||||
/**
|
this.moveToState("MainMenuState", { loadError: err });
|
||||||
* Creates the game core instance, and thus the root
|
}
|
||||||
*/
|
}
|
||||||
stage3CreateCore() {
|
|
||||||
if (this.switchStage(stages.s3_createCore)) {
|
// STAGES
|
||||||
logger.log("Creating new game core");
|
|
||||||
this.core = new GameCore(this.app);
|
/**
|
||||||
|
* Creates the game core instance, and thus the root
|
||||||
this.core.initializeRoot(this, this.savegame);
|
*/
|
||||||
|
stage3CreateCore() {
|
||||||
if (this.savegame.hasGameDump()) {
|
if (this.switchStage(stages.s3_createCore)) {
|
||||||
this.stage4bResumeGame();
|
logger.log("Creating new game core");
|
||||||
} else {
|
this.core = new GameCore(this.app);
|
||||||
this.app.gameAnalytics.handleGameStarted();
|
|
||||||
this.stage4aInitEmptyGame();
|
this.core.initializeRoot(this, this.savegame);
|
||||||
}
|
|
||||||
}
|
if (this.savegame.hasGameDump()) {
|
||||||
}
|
this.stage4bResumeGame();
|
||||||
|
} else {
|
||||||
/**
|
this.app.gameAnalytics.handleGameStarted();
|
||||||
* Initializes a new empty game
|
this.stage4aInitEmptyGame();
|
||||||
*/
|
}
|
||||||
stage4aInitEmptyGame() {
|
}
|
||||||
if (this.switchStage(stages.s4_A_initEmptyGame)) {
|
}
|
||||||
this.core.initNewGame();
|
|
||||||
this.stage5FirstUpdate();
|
/**
|
||||||
}
|
* Initializes a new empty game
|
||||||
}
|
*/
|
||||||
|
stage4aInitEmptyGame() {
|
||||||
/**
|
if (this.switchStage(stages.s4_A_initEmptyGame)) {
|
||||||
* Resumes an existing game
|
this.core.initNewGame();
|
||||||
*/
|
this.stage5FirstUpdate();
|
||||||
stage4bResumeGame() {
|
}
|
||||||
if (this.switchStage(stages.s4_B_resumeGame)) {
|
}
|
||||||
if (!this.core.initExistingGame()) {
|
|
||||||
this.onInitializationFailure("Savegame is corrupt and can not be restored.");
|
/**
|
||||||
return;
|
* Resumes an existing game
|
||||||
}
|
*/
|
||||||
this.app.gameAnalytics.handleGameResumed();
|
stage4bResumeGame() {
|
||||||
this.stage5FirstUpdate();
|
if (this.switchStage(stages.s4_B_resumeGame)) {
|
||||||
}
|
if (!this.core.initExistingGame()) {
|
||||||
}
|
this.onInitializationFailure("Savegame is corrupt and can not be restored.");
|
||||||
|
return;
|
||||||
/**
|
}
|
||||||
* Performs the first game update on the game which initializes most caches
|
this.app.gameAnalytics.handleGameResumed();
|
||||||
*/
|
this.stage5FirstUpdate();
|
||||||
stage5FirstUpdate() {
|
}
|
||||||
if (this.switchStage(stages.s5_firstUpdate)) {
|
}
|
||||||
this.core.root.logicInitialized = true;
|
|
||||||
this.core.updateLogic();
|
/**
|
||||||
this.stage6PostLoadHook();
|
* Performs the first game update on the game which initializes most caches
|
||||||
}
|
*/
|
||||||
}
|
stage5FirstUpdate() {
|
||||||
|
if (this.switchStage(stages.s5_firstUpdate)) {
|
||||||
/**
|
this.core.root.logicInitialized = true;
|
||||||
* Call the post load hook, this means that we have loaded the game, and all systems
|
this.core.updateLogic();
|
||||||
* can operate and start to work now.
|
this.stage6PostLoadHook();
|
||||||
*/
|
}
|
||||||
stage6PostLoadHook() {
|
}
|
||||||
if (this.switchStage(stages.s6_postLoadHook)) {
|
|
||||||
logger.log("Post load hook");
|
/**
|
||||||
this.core.postLoadHook();
|
* Call the post load hook, this means that we have loaded the game, and all systems
|
||||||
this.stage7Warmup();
|
* can operate and start to work now.
|
||||||
}
|
*/
|
||||||
}
|
stage6PostLoadHook() {
|
||||||
|
if (this.switchStage(stages.s6_postLoadHook)) {
|
||||||
/**
|
logger.log("Post load hook");
|
||||||
* This makes the game idle and draw for a while, because we run most code this way
|
this.core.postLoadHook();
|
||||||
* the V8 engine can already start to optimize it. Also this makes sure the resources
|
this.stage7Warmup();
|
||||||
* are in the VRAM and we have a smooth experience once we start.
|
}
|
||||||
*/
|
}
|
||||||
stage7Warmup() {
|
|
||||||
if (this.switchStage(stages.s7_warmup)) {
|
/**
|
||||||
if (G_IS_DEV && globalConfig.debug.noArtificialDelays) {
|
* This makes the game idle and draw for a while, because we run most code this way
|
||||||
this.warmupTimeSeconds = 0.05;
|
* the V8 engine can already start to optimize it. Also this makes sure the resources
|
||||||
} else {
|
* are in the VRAM and we have a smooth experience once we start.
|
||||||
if (this.creationPayload.fastEnter) {
|
*/
|
||||||
this.warmupTimeSeconds = globalConfig.warmupTimeSecondsFast;
|
stage7Warmup() {
|
||||||
} else {
|
if (this.switchStage(stages.s7_warmup)) {
|
||||||
this.warmupTimeSeconds = globalConfig.warmupTimeSecondsRegular;
|
if (G_IS_DEV && globalConfig.debug.noArtificialDelays) {
|
||||||
}
|
this.warmupTimeSeconds = 0.05;
|
||||||
}
|
} else {
|
||||||
}
|
if (this.creationPayload.fastEnter) {
|
||||||
}
|
this.warmupTimeSeconds = globalConfig.warmupTimeSecondsFast;
|
||||||
|
} else {
|
||||||
/**
|
this.warmupTimeSeconds = globalConfig.warmupTimeSecondsRegular;
|
||||||
* The final stage where this game is running and updating regulary.
|
}
|
||||||
*/
|
}
|
||||||
stage10GameRunning() {
|
}
|
||||||
if (this.switchStage(stages.s10_gameRunning)) {
|
}
|
||||||
this.core.root.signals.readyToRender.dispatch();
|
|
||||||
|
/**
|
||||||
logSection("GAME STARTED", "#26a69a");
|
* The final stage where this game is running and updating regulary.
|
||||||
|
*/
|
||||||
// Initial resize, might have changed during loading (this is possible)
|
stage10GameRunning() {
|
||||||
this.core.resize(this.app.screenWidth, this.app.screenHeight);
|
if (this.switchStage(stages.s10_gameRunning)) {
|
||||||
}
|
this.core.root.signals.readyToRender.dispatch();
|
||||||
}
|
|
||||||
|
logSection("GAME STARTED", "#26a69a");
|
||||||
/**
|
|
||||||
* This stage destroys the whole game, used to cleanup
|
// Initial resize, might have changed during loading (this is possible)
|
||||||
*/
|
this.core.resize(this.app.screenWidth, this.app.screenHeight);
|
||||||
stageDestroyed() {
|
}
|
||||||
if (this.switchStage(stages.destroyed)) {
|
}
|
||||||
// Cleanup all api calls
|
|
||||||
this.cancelAllAsyncOperations();
|
/**
|
||||||
|
* This stage destroys the whole game, used to cleanup
|
||||||
if (this.syncer) {
|
*/
|
||||||
this.syncer.cancelSync();
|
stageDestroyed() {
|
||||||
this.syncer = null;
|
if (this.switchStage(stages.destroyed)) {
|
||||||
}
|
// Cleanup all api calls
|
||||||
|
this.cancelAllAsyncOperations();
|
||||||
// Cleanup core
|
|
||||||
if (this.core) {
|
if (this.syncer) {
|
||||||
this.core.destruct();
|
this.syncer.cancelSync();
|
||||||
this.core = null;
|
this.syncer = null;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
// Cleanup core
|
||||||
|
if (this.core) {
|
||||||
/**
|
this.core.destruct();
|
||||||
* When leaving the game
|
this.core = null;
|
||||||
*/
|
}
|
||||||
stageLeavingGame() {
|
}
|
||||||
if (this.switchStage(stages.leaving)) {
|
}
|
||||||
// ...
|
|
||||||
}
|
/**
|
||||||
}
|
* When leaving the game
|
||||||
|
*/
|
||||||
// END STAGES
|
stageLeavingGame() {
|
||||||
|
if (this.switchStage(stages.leaving)) {
|
||||||
/**
|
// ...
|
||||||
* Filters the input (keybindings)
|
}
|
||||||
*/
|
}
|
||||||
filterInput() {
|
|
||||||
return this.stage === stages.s10_gameRunning;
|
// END STAGES
|
||||||
}
|
|
||||||
|
/**
|
||||||
/**
|
* Filters the input (keybindings)
|
||||||
* @param {GameCreationPayload} payload
|
*/
|
||||||
*/
|
filterInput() {
|
||||||
onEnter(payload) {
|
return this.stage === stages.s10_gameRunning;
|
||||||
this.app.inputMgr.installFilter(this.boundInputFilter);
|
}
|
||||||
|
|
||||||
this.creationPayload = payload;
|
/**
|
||||||
this.savegame = payload.savegame;
|
* @param {GameCreationPayload} payload
|
||||||
|
*/
|
||||||
this.loadingOverlay = new GameLoadingOverlay(this.app, this.getDivElement());
|
onEnter(payload) {
|
||||||
this.loadingOverlay.showBasic();
|
this.app.inputMgr.installFilter(this.boundInputFilter);
|
||||||
|
|
||||||
// Remove unneded default element
|
this.creationPayload = payload;
|
||||||
document.body.querySelector(".modalDialogParent").remove();
|
this.savegame = payload.savegame;
|
||||||
|
|
||||||
this.asyncChannel.watch(waitNextFrame()).then(() => this.stage3CreateCore());
|
this.loadingOverlay = new GameLoadingOverlay(this.app, this.getDivElement());
|
||||||
}
|
this.loadingOverlay.showBasic();
|
||||||
|
|
||||||
/**
|
// Remove unneded default element
|
||||||
* Render callback
|
document.body.querySelector(".modalDialogParent").remove();
|
||||||
* @param {number} dt
|
|
||||||
*/
|
this.asyncChannel.watch(waitNextFrame()).then(() => this.stage3CreateCore());
|
||||||
onRender(dt) {
|
}
|
||||||
if (APPLICATION_ERROR_OCCURED) {
|
|
||||||
// Application somehow crashed, do not do anything
|
/**
|
||||||
return;
|
* Render callback
|
||||||
}
|
* @param {number} dt
|
||||||
|
*/
|
||||||
if (this.stage === stages.s7_warmup) {
|
onRender(dt) {
|
||||||
this.core.draw();
|
if (APPLICATION_ERROR_OCCURED) {
|
||||||
this.warmupTimeSeconds -= dt / 1000.0;
|
// Application somehow crashed, do not do anything
|
||||||
if (this.warmupTimeSeconds < 0) {
|
return;
|
||||||
logger.log("Warmup completed");
|
}
|
||||||
this.stage10GameRunning();
|
|
||||||
}
|
if (this.stage === stages.s7_warmup) {
|
||||||
}
|
this.core.draw();
|
||||||
|
this.warmupTimeSeconds -= dt / 1000.0;
|
||||||
if (this.stage === stages.s10_gameRunning) {
|
if (this.warmupTimeSeconds < 0) {
|
||||||
this.core.tick(dt);
|
logger.log("Warmup completed");
|
||||||
}
|
this.stage10GameRunning();
|
||||||
|
}
|
||||||
// If the stage is still active (This might not be the case if tick() moved us to game over)
|
}
|
||||||
if (this.stage === stages.s10_gameRunning) {
|
|
||||||
// Only draw if page visible
|
if (this.stage === stages.s10_gameRunning) {
|
||||||
if (this.app.pageVisible) {
|
this.core.tick(dt);
|
||||||
this.core.draw();
|
}
|
||||||
}
|
|
||||||
|
// If the stage is still active (This might not be the case if tick() moved us to game over)
|
||||||
this.loadingOverlay.removeIfAttached();
|
if (this.stage === stages.s10_gameRunning) {
|
||||||
} else {
|
// Only draw if page visible
|
||||||
if (!this.loadingOverlay.isAttached()) {
|
if (this.app.pageVisible) {
|
||||||
this.loadingOverlay.showBasic();
|
this.core.draw();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
this.loadingOverlay.removeIfAttached();
|
||||||
|
} else {
|
||||||
onBackgroundTick(dt) {
|
if (!this.loadingOverlay.isAttached()) {
|
||||||
this.onRender(dt);
|
this.loadingOverlay.showBasic();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/**
|
}
|
||||||
* Saves the game
|
|
||||||
*/
|
onBackgroundTick(dt) {
|
||||||
|
this.onRender(dt);
|
||||||
doSave() {
|
}
|
||||||
if (!this.savegame || !this.savegame.isSaveable()) {
|
|
||||||
return Promise.resolve();
|
/**
|
||||||
}
|
* Saves the game
|
||||||
|
*/
|
||||||
if (APPLICATION_ERROR_OCCURED) {
|
|
||||||
logger.warn("skipping save because application crashed");
|
doSave() {
|
||||||
return Promise.resolve();
|
if (!this.savegame || !this.savegame.isSaveable()) {
|
||||||
}
|
return Promise.resolve();
|
||||||
|
}
|
||||||
if (
|
|
||||||
this.stage !== stages.s10_gameRunning &&
|
if (APPLICATION_ERROR_OCCURED) {
|
||||||
this.stage !== stages.s7_warmup &&
|
logger.warn("skipping save because application crashed");
|
||||||
this.stage !== stages.leaving
|
return Promise.resolve();
|
||||||
) {
|
}
|
||||||
logger.warn("Skipping save because game is not ready");
|
|
||||||
return Promise.resolve();
|
if (
|
||||||
}
|
this.stage !== stages.s10_gameRunning &&
|
||||||
|
this.stage !== stages.s7_warmup &&
|
||||||
// First update the game data
|
this.stage !== stages.leaving
|
||||||
logger.log("Starting to save game ...");
|
) {
|
||||||
this.core.root.signals.gameSaved.dispatch();
|
logger.warn("Skipping save because game is not ready");
|
||||||
this.savegame.updateData(this.core.root);
|
return Promise.resolve();
|
||||||
return this.savegame.writeSavegameAndMetadata().catch(err => {
|
}
|
||||||
logger.warn("Failed to save:", err);
|
|
||||||
});
|
if (this.currentSavePromise) {
|
||||||
}
|
logger.warn("Skipping double save and returning same promise");
|
||||||
}
|
return this.currentSavePromise;
|
||||||
|
}
|
||||||
|
logger.log("Starting to save game ...");
|
||||||
|
this.savegame.updateData(this.core.root);
|
||||||
|
|
||||||
|
this.currentSavePromise = this.savegame
|
||||||
|
.writeSavegameAndMetadata()
|
||||||
|
.catch(err => {
|
||||||
|
// Catch errors
|
||||||
|
logger.warn("Failed to save:", err);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
// Clear promise
|
||||||
|
logger.log("Saved!");
|
||||||
|
this.core.root.signals.gameSaved.dispatch();
|
||||||
|
this.currentSavePromise = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.currentSavePromise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in new issue