mirror of
https://github.com/tobspr/shapez.io.git
synced 2026-03-02 03:39:21 +00:00
Further performance improvements, show indicator while game is saving
This commit is contained in:
@@ -1,83 +1,92 @@
|
||||
/* typehints:start */
|
||||
import { MetaBuilding } from "./meta_building";
|
||||
import { AtlasSprite } from "../core/sprites";
|
||||
import { Vector } from "../core/vector";
|
||||
/* typehints:end */
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* metaClass: typeof MetaBuilding,
|
||||
* metaInstance?: MetaBuilding,
|
||||
* variant?: string,
|
||||
* rotationVariant?: number,
|
||||
* tileSize?: Vector,
|
||||
* sprite?: AtlasSprite,
|
||||
* blueprintSprite?: AtlasSprite,
|
||||
* silhouetteColor?: string
|
||||
* }} BuildingVariantIdentifier
|
||||
*/
|
||||
|
||||
/**
|
||||
* Stores a lookup table for all building variants (for better performance)
|
||||
* @type {Object<number, BuildingVariantIdentifier>}
|
||||
*/
|
||||
export const gBuildingVariants = {
|
||||
// Set later
|
||||
};
|
||||
|
||||
/**
|
||||
* Registers a new variant
|
||||
* @param {number} id
|
||||
* @param {typeof MetaBuilding} meta
|
||||
* @param {string} variant
|
||||
* @param {number} rotationVariant
|
||||
*/
|
||||
export function registerBuildingVariant(
|
||||
id,
|
||||
meta,
|
||||
variant = "default" /* FIXME: Circular dependency, actually its defaultBuildingVariant */,
|
||||
rotationVariant = 0
|
||||
) {
|
||||
assert(!gBuildingVariants[id], "Duplicate id: " + id);
|
||||
gBuildingVariants[id] = {
|
||||
metaClass: meta,
|
||||
variant,
|
||||
rotationVariant,
|
||||
// @ts-ignore
|
||||
tileSize: new meta().getDimensions(variant),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} code
|
||||
* @returns {BuildingVariantIdentifier}
|
||||
*/
|
||||
export function getBuildingDataFromCode(code) {
|
||||
assert(gBuildingVariants[code], "Invalid building code: " + code);
|
||||
return gBuildingVariants[code];
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the code for a given variant
|
||||
* @param {MetaBuilding} metaBuilding
|
||||
* @param {string} variant
|
||||
* @param {number} rotationVariant
|
||||
*/
|
||||
export function getCodeFromBuildingData(metaBuilding, variant, rotationVariant) {
|
||||
for (const key in gBuildingVariants) {
|
||||
const data = gBuildingVariants[key];
|
||||
if (
|
||||
data.metaInstance.getId() === metaBuilding.getId() &&
|
||||
data.variant === variant &&
|
||||
data.rotationVariant === rotationVariant
|
||||
) {
|
||||
return +key;
|
||||
}
|
||||
}
|
||||
assertAlways(
|
||||
false,
|
||||
"Building not found by data: " + metaBuilding.getId() + " / " + variant + " / " + rotationVariant
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
/* typehints:start */
|
||||
import { MetaBuilding } from "./meta_building";
|
||||
import { AtlasSprite } from "../core/sprites";
|
||||
import { Vector } from "../core/vector";
|
||||
/* typehints:end */
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* metaClass: typeof MetaBuilding,
|
||||
* metaInstance?: MetaBuilding,
|
||||
* variant?: string,
|
||||
* rotationVariant?: number,
|
||||
* tileSize?: Vector,
|
||||
* sprite?: AtlasSprite,
|
||||
* blueprintSprite?: AtlasSprite,
|
||||
* silhouetteColor?: string
|
||||
* }} BuildingVariantIdentifier
|
||||
*/
|
||||
|
||||
/**
|
||||
* Stores a lookup table for all building variants (for better performance)
|
||||
* @type {Object<number, BuildingVariantIdentifier>}
|
||||
*/
|
||||
export const gBuildingVariants = {
|
||||
// Set later
|
||||
};
|
||||
|
||||
/**
|
||||
* Mapping from 'metaBuildingId/variant/rotationVariant' to building code
|
||||
* @type {Map<string, number>}
|
||||
*/
|
||||
const variantsCache = new Map();
|
||||
|
||||
/**
|
||||
* Registers a new variant
|
||||
* @param {number} code
|
||||
* @param {typeof MetaBuilding} meta
|
||||
* @param {string} variant
|
||||
* @param {number} rotationVariant
|
||||
*/
|
||||
export function registerBuildingVariant(
|
||||
code,
|
||||
meta,
|
||||
variant = "default" /* FIXME: Circular dependency, actually its defaultBuildingVariant */,
|
||||
rotationVariant = 0
|
||||
) {
|
||||
assert(!gBuildingVariants[code], "Duplicate id: " + code);
|
||||
gBuildingVariants[code] = {
|
||||
metaClass: meta,
|
||||
variant,
|
||||
rotationVariant,
|
||||
// @ts-ignore
|
||||
tileSize: new meta().getDimensions(variant),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} code
|
||||
* @returns {BuildingVariantIdentifier}
|
||||
*/
|
||||
export function getBuildingDataFromCode(code) {
|
||||
assert(gBuildingVariants[code], "Invalid building code: " + code);
|
||||
return gBuildingVariants[code];
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the cache for the codes
|
||||
*/
|
||||
export function buildBuildingCodeCache() {
|
||||
for (const code in gBuildingVariants) {
|
||||
const data = gBuildingVariants[code];
|
||||
const hash = data.metaInstance.getId() + "/" + data.variant + "/" + data.rotationVariant;
|
||||
variantsCache.set(hash, +code);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the code for a given variant
|
||||
* @param {MetaBuilding} metaBuilding
|
||||
* @param {string} variant
|
||||
* @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;
|
||||
}
|
||||
|
||||
@@ -67,11 +67,8 @@ export class EntityManager extends BasicSerializableObject {
|
||||
}
|
||||
assert(!entity.destroyed, `Attempting to register destroyed entity ${entity}`);
|
||||
|
||||
if (G_IS_DEV && uid !== null) {
|
||||
if (G_IS_DEV && !globalConfig.debug.disableSlowAsserts && uid !== null) {
|
||||
assert(!this.findByUid(uid, false), "Entity uid already taken: " + uid);
|
||||
}
|
||||
|
||||
if (uid !== null) {
|
||||
assert(uid >= 0 && uid < Number.MAX_SAFE_INTEGER, "Invalid uid passed: " + uid);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { enumNotificationType } from "./notifications";
|
||||
import { T } from "../../../translations";
|
||||
import { KEYMAPPINGS } from "../../key_action_mapper";
|
||||
import { DynamicDomAttach } from "../dynamic_dom_attach";
|
||||
import { TrackedState } from "../../../core/tracked_state";
|
||||
|
||||
export class HUDGameMenu extends BaseHUDPart {
|
||||
createElements(parent) {
|
||||
@@ -97,12 +98,17 @@ export class HUDGameMenu extends BaseHUDPart {
|
||||
|
||||
initialize() {
|
||||
this.root.signals.gameSaved.add(this.onGameSaved, this);
|
||||
|
||||
this.trackedIsSaving = new TrackedState(this.onIsSavingChanged, this);
|
||||
}
|
||||
|
||||
update() {
|
||||
let playSound = false;
|
||||
let notifications = new Set();
|
||||
|
||||
// Check whether we are saving
|
||||
this.trackedIsSaving.set(!!this.root.gameState.currentSavePromise);
|
||||
|
||||
// Update visibility of buttons
|
||||
for (let i = 0; i < this.visibilityToUpdate.length; ++i) {
|
||||
const { condition, domAttach } = this.visibilityToUpdate[i];
|
||||
@@ -154,6 +160,10 @@ export class HUDGameMenu extends BaseHUDPart {
|
||||
});
|
||||
}
|
||||
|
||||
onIsSavingChanged(isSaving) {
|
||||
this.saveButton.classList.toggle("saving", isSaving);
|
||||
}
|
||||
|
||||
onGameSaved() {
|
||||
this.saveButton.classList.toggle("animEven");
|
||||
this.saveButton.classList.toggle("animOdd");
|
||||
|
||||
@@ -1,56 +1,55 @@
|
||||
import { BaseHUDPart } from "../base_hud_part";
|
||||
import { makeDiv } from "../../../core/utils";
|
||||
import { T } from "../../../translations";
|
||||
import { IS_DEMO } from "../../../core/config";
|
||||
|
||||
/** @enum {string} */
|
||||
export const enumNotificationType = {
|
||||
saved: "saved",
|
||||
upgrade: "upgrade",
|
||||
success: "success",
|
||||
};
|
||||
|
||||
const notificationDuration = 3;
|
||||
|
||||
export class HUDNotifications extends BaseHUDPart {
|
||||
createElements(parent) {
|
||||
this.element = makeDiv(parent, "ingame_HUD_Notifications", [], ``);
|
||||
}
|
||||
|
||||
initialize() {
|
||||
this.root.hud.signals.notification.add(this.onNotification, this);
|
||||
|
||||
/** @type {Array<{ element: HTMLElement, expireAt: number}>} */
|
||||
this.notificationElements = [];
|
||||
|
||||
// Automatic notifications
|
||||
this.root.signals.gameSaved.add(() =>
|
||||
this.onNotification(T.ingame.notifications.gameSaved, enumNotificationType.saved)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} message
|
||||
* @param {enumNotificationType} type
|
||||
*/
|
||||
onNotification(message, type) {
|
||||
const element = makeDiv(this.element, null, ["notification", "type-" + type], message);
|
||||
element.setAttribute("data-icon", "icons/notification_" + type + ".png");
|
||||
|
||||
this.notificationElements.push({
|
||||
element,
|
||||
expireAt: this.root.time.realtimeNow() + notificationDuration,
|
||||
});
|
||||
}
|
||||
|
||||
update() {
|
||||
const now = this.root.time.realtimeNow();
|
||||
for (let i = 0; i < this.notificationElements.length; ++i) {
|
||||
const handle = this.notificationElements[i];
|
||||
if (handle.expireAt <= now) {
|
||||
handle.element.remove();
|
||||
this.notificationElements.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
import { makeDiv } from "../../../core/utils";
|
||||
import { T } from "../../../translations";
|
||||
import { BaseHUDPart } from "../base_hud_part";
|
||||
|
||||
/** @enum {string} */
|
||||
export const enumNotificationType = {
|
||||
saved: "saved",
|
||||
upgrade: "upgrade",
|
||||
success: "success",
|
||||
};
|
||||
|
||||
const notificationDuration = 3;
|
||||
|
||||
export class HUDNotifications extends BaseHUDPart {
|
||||
createElements(parent) {
|
||||
this.element = makeDiv(parent, "ingame_HUD_Notifications", [], ``);
|
||||
}
|
||||
|
||||
initialize() {
|
||||
this.root.hud.signals.notification.add(this.onNotification, this);
|
||||
|
||||
/** @type {Array<{ element: HTMLElement, expireAt: number}>} */
|
||||
this.notificationElements = [];
|
||||
|
||||
// Automatic notifications
|
||||
this.root.signals.gameSaved.add(() =>
|
||||
this.onNotification(T.ingame.notifications.gameSaved, enumNotificationType.saved)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} message
|
||||
* @param {enumNotificationType} type
|
||||
*/
|
||||
onNotification(message, type) {
|
||||
const element = makeDiv(this.element, null, ["notification", "type-" + type], message);
|
||||
element.setAttribute("data-icon", "icons/notification_" + type + ".png");
|
||||
|
||||
this.notificationElements.push({
|
||||
element,
|
||||
expireAt: this.root.time.realtimeNow() + notificationDuration,
|
||||
});
|
||||
}
|
||||
|
||||
update() {
|
||||
const now = this.root.time.realtimeNow();
|
||||
for (let i = 0; i < this.notificationElements.length; ++i) {
|
||||
const handle = this.notificationElements[i];
|
||||
if (handle.expireAt <= now) {
|
||||
handle.element.remove();
|
||||
this.notificationElements.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,236 +1,236 @@
|
||||
import { globalConfig } from "../core/config";
|
||||
import { Vector } from "../core/vector";
|
||||
import { BasicSerializableObject, types } from "../savegame/serialization";
|
||||
import { BaseItem } from "./base_item";
|
||||
import { Entity } from "./entity";
|
||||
import { MapChunkView } from "./map_chunk_view";
|
||||
import { GameRoot } from "./root";
|
||||
|
||||
export class BaseMap extends BasicSerializableObject {
|
||||
static getId() {
|
||||
return "Map";
|
||||
}
|
||||
|
||||
static getSchema() {
|
||||
return {
|
||||
seed: types.uint,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {GameRoot} root
|
||||
*/
|
||||
constructor(root) {
|
||||
super();
|
||||
this.root = root;
|
||||
|
||||
this.seed = 0;
|
||||
|
||||
/**
|
||||
* Mapping of 'X|Y' to chunk
|
||||
* @type {Map<string, MapChunkView>} */
|
||||
this.chunksById = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given chunk by index
|
||||
* @param {number} chunkX
|
||||
* @param {number} chunkY
|
||||
*/
|
||||
getChunk(chunkX, chunkY, createIfNotExistent = false) {
|
||||
const chunkIdentifier = chunkX + "|" + chunkY;
|
||||
let storedChunk;
|
||||
|
||||
if ((storedChunk = this.chunksById.get(chunkIdentifier))) {
|
||||
return storedChunk;
|
||||
}
|
||||
|
||||
if (createIfNotExistent) {
|
||||
const instance = new MapChunkView(this.root, chunkX, chunkY);
|
||||
this.chunksById.set(chunkIdentifier, instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or creates a new chunk if not existent for the given tile
|
||||
* @param {number} tileX
|
||||
* @param {number} tileY
|
||||
* @returns {MapChunkView}
|
||||
*/
|
||||
getOrCreateChunkAtTile(tileX, tileY) {
|
||||
const chunkX = Math.floor(tileX / globalConfig.mapChunkSize);
|
||||
const chunkY = Math.floor(tileY / globalConfig.mapChunkSize);
|
||||
return this.getChunk(chunkX, chunkY, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a chunk if not existent for the given tile
|
||||
* @param {number} tileX
|
||||
* @param {number} tileY
|
||||
* @returns {MapChunkView?}
|
||||
*/
|
||||
getChunkAtTileOrNull(tileX, tileY) {
|
||||
const chunkX = Math.floor(tileX / globalConfig.mapChunkSize);
|
||||
const chunkY = Math.floor(tileY / globalConfig.mapChunkSize);
|
||||
return this.getChunk(chunkX, chunkY, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given tile is within the map bounds
|
||||
* @param {Vector} tile
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isValidTile(tile) {
|
||||
if (G_IS_DEV) {
|
||||
assert(tile instanceof Vector, "tile is not a vector");
|
||||
}
|
||||
return Number.isInteger(tile.x) && Number.isInteger(tile.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the tile content of a given tile
|
||||
* @param {Vector} tile
|
||||
* @param {Layer} layer
|
||||
* @returns {Entity} Entity or null
|
||||
*/
|
||||
getTileContent(tile, layer) {
|
||||
if (G_IS_DEV) {
|
||||
this.internalCheckTile(tile);
|
||||
}
|
||||
const chunk = this.getChunkAtTileOrNull(tile.x, tile.y);
|
||||
return chunk && chunk.getLayerContentFromWorldCoords(tile.x, tile.y, layer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the lower layers content of the given tile
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @returns {BaseItem=}
|
||||
*/
|
||||
getLowerLayerContentXY(x, y) {
|
||||
return this.getOrCreateChunkAtTile(x, y).getLowerLayerFromWorldCoords(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the tile content of a given tile
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {Layer} layer
|
||||
* @returns {Entity} Entity or null
|
||||
*/
|
||||
getLayerContentXY(x, y, layer) {
|
||||
const chunk = this.getChunkAtTileOrNull(x, y);
|
||||
return chunk && chunk.getLayerContentFromWorldCoords(x, y, layer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the tile contents of a given tile
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @returns {Array<Entity>} Entity or null
|
||||
*/
|
||||
getLayersContentsMultipleXY(x, y) {
|
||||
const chunk = this.getChunkAtTileOrNull(x, y);
|
||||
if (!chunk) {
|
||||
return [];
|
||||
}
|
||||
return chunk.getLayersContentsMultipleFromWorldCoords(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the tile is used
|
||||
* @param {Vector} tile
|
||||
* @param {Layer} layer
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isTileUsed(tile, layer) {
|
||||
if (G_IS_DEV) {
|
||||
this.internalCheckTile(tile);
|
||||
}
|
||||
const chunk = this.getChunkAtTileOrNull(tile.x, tile.y);
|
||||
return chunk && chunk.getLayerContentFromWorldCoords(tile.x, tile.y, layer) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the tile is used
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {Layer} layer
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isTileUsedXY(x, y, layer) {
|
||||
const chunk = this.getChunkAtTileOrNull(x, y);
|
||||
return chunk && chunk.getLayerContentFromWorldCoords(x, y, layer) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tiles content
|
||||
* @param {Vector} tile
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
setTileContent(tile, entity) {
|
||||
if (G_IS_DEV) {
|
||||
this.internalCheckTile(tile);
|
||||
}
|
||||
|
||||
this.getOrCreateChunkAtTile(tile.x, tile.y).setLayerContentFromWorldCords(
|
||||
tile.x,
|
||||
tile.y,
|
||||
entity,
|
||||
entity.layer
|
||||
);
|
||||
|
||||
const staticComponent = entity.components.StaticMapEntity;
|
||||
assert(staticComponent, "Can only place static map entities in tiles");
|
||||
}
|
||||
|
||||
/**
|
||||
* Places an entity with the StaticMapEntity component
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
placeStaticEntity(entity) {
|
||||
assert(entity.components.StaticMapEntity, "Entity is not static");
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
const rect = staticComp.getTileSpaceBounds();
|
||||
for (let dx = 0; dx < rect.w; ++dx) {
|
||||
for (let dy = 0; dy < rect.h; ++dy) {
|
||||
const x = rect.x + dx;
|
||||
const y = rect.y + dy;
|
||||
this.getOrCreateChunkAtTile(x, y).setLayerContentFromWorldCords(x, y, entity, entity.layer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an entity with the StaticMapEntity component
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
removeStaticEntity(entity) {
|
||||
assert(entity.components.StaticMapEntity, "Entity is not static");
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
const rect = staticComp.getTileSpaceBounds();
|
||||
for (let dx = 0; dx < rect.w; ++dx) {
|
||||
for (let dy = 0; dy < rect.h; ++dy) {
|
||||
const x = rect.x + dx;
|
||||
const y = rect.y + dy;
|
||||
this.getOrCreateChunkAtTile(x, y).setLayerContentFromWorldCords(x, y, null, entity.layer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Internal
|
||||
|
||||
/**
|
||||
* Checks a given tile for validty
|
||||
* @param {Vector} tile
|
||||
*/
|
||||
internalCheckTile(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.y % 1 === 0, "Tile Y is not a valid integer: " + tile.y);
|
||||
}
|
||||
}
|
||||
import { globalConfig } from "../core/config";
|
||||
import { Vector } from "../core/vector";
|
||||
import { BasicSerializableObject, types } from "../savegame/serialization";
|
||||
import { BaseItem } from "./base_item";
|
||||
import { Entity } from "./entity";
|
||||
import { MapChunkView } from "./map_chunk_view";
|
||||
import { GameRoot } from "./root";
|
||||
|
||||
export class BaseMap extends BasicSerializableObject {
|
||||
static getId() {
|
||||
return "Map";
|
||||
}
|
||||
|
||||
static getSchema() {
|
||||
return {
|
||||
seed: types.uint,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {GameRoot} root
|
||||
*/
|
||||
constructor(root) {
|
||||
super();
|
||||
this.root = root;
|
||||
|
||||
this.seed = 0;
|
||||
|
||||
/**
|
||||
* Mapping of 'X|Y' to chunk
|
||||
* @type {Map<string, MapChunkView>} */
|
||||
this.chunksById = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given chunk by index
|
||||
* @param {number} chunkX
|
||||
* @param {number} chunkY
|
||||
*/
|
||||
getChunk(chunkX, chunkY, createIfNotExistent = false) {
|
||||
const chunkIdentifier = chunkX + "|" + chunkY;
|
||||
let storedChunk;
|
||||
|
||||
if ((storedChunk = this.chunksById.get(chunkIdentifier))) {
|
||||
return storedChunk;
|
||||
}
|
||||
|
||||
if (createIfNotExistent) {
|
||||
const instance = new MapChunkView(this.root, chunkX, chunkY);
|
||||
this.chunksById.set(chunkIdentifier, instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or creates a new chunk if not existent for the given tile
|
||||
* @param {number} tileX
|
||||
* @param {number} tileY
|
||||
* @returns {MapChunkView}
|
||||
*/
|
||||
getOrCreateChunkAtTile(tileX, tileY) {
|
||||
const chunkX = Math.floor(tileX / globalConfig.mapChunkSize);
|
||||
const chunkY = Math.floor(tileY / globalConfig.mapChunkSize);
|
||||
return this.getChunk(chunkX, chunkY, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a chunk if not existent for the given tile
|
||||
* @param {number} tileX
|
||||
* @param {number} tileY
|
||||
* @returns {MapChunkView?}
|
||||
*/
|
||||
getChunkAtTileOrNull(tileX, tileY) {
|
||||
const chunkX = Math.floor(tileX / globalConfig.mapChunkSize);
|
||||
const chunkY = Math.floor(tileY / globalConfig.mapChunkSize);
|
||||
return this.getChunk(chunkX, chunkY, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given tile is within the map bounds
|
||||
* @param {Vector} tile
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isValidTile(tile) {
|
||||
if (G_IS_DEV) {
|
||||
assert(tile instanceof Vector, "tile is not a vector");
|
||||
}
|
||||
return Number.isInteger(tile.x) && Number.isInteger(tile.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the tile content of a given tile
|
||||
* @param {Vector} tile
|
||||
* @param {Layer} layer
|
||||
* @returns {Entity} Entity or null
|
||||
*/
|
||||
getTileContent(tile, layer) {
|
||||
if (G_IS_DEV) {
|
||||
this.internalCheckTile(tile);
|
||||
}
|
||||
const chunk = this.getChunkAtTileOrNull(tile.x, tile.y);
|
||||
return chunk && chunk.getLayerContentFromWorldCoords(tile.x, tile.y, layer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the lower layers content of the given tile
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @returns {BaseItem=}
|
||||
*/
|
||||
getLowerLayerContentXY(x, y) {
|
||||
return this.getOrCreateChunkAtTile(x, y).getLowerLayerFromWorldCoords(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the tile content of a given tile
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {Layer} layer
|
||||
* @returns {Entity} Entity or null
|
||||
*/
|
||||
getLayerContentXY(x, y, layer) {
|
||||
const chunk = this.getChunkAtTileOrNull(x, y);
|
||||
return chunk && chunk.getLayerContentFromWorldCoords(x, y, layer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the tile contents of a given tile
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @returns {Array<Entity>} Entity or null
|
||||
*/
|
||||
getLayersContentsMultipleXY(x, y) {
|
||||
const chunk = this.getChunkAtTileOrNull(x, y);
|
||||
if (!chunk) {
|
||||
return [];
|
||||
}
|
||||
return chunk.getLayersContentsMultipleFromWorldCoords(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the tile is used
|
||||
* @param {Vector} tile
|
||||
* @param {Layer} layer
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isTileUsed(tile, layer) {
|
||||
if (G_IS_DEV) {
|
||||
this.internalCheckTile(tile);
|
||||
}
|
||||
const chunk = this.getChunkAtTileOrNull(tile.x, tile.y);
|
||||
return chunk && chunk.getLayerContentFromWorldCoords(tile.x, tile.y, layer) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the tile is used
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {Layer} layer
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isTileUsedXY(x, y, layer) {
|
||||
const chunk = this.getChunkAtTileOrNull(x, y);
|
||||
return chunk && chunk.getLayerContentFromWorldCoords(x, y, layer) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tiles content
|
||||
* @param {Vector} tile
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
setTileContent(tile, entity) {
|
||||
if (G_IS_DEV) {
|
||||
this.internalCheckTile(tile);
|
||||
}
|
||||
|
||||
this.getOrCreateChunkAtTile(tile.x, tile.y).setLayerContentFromWorldCords(
|
||||
tile.x,
|
||||
tile.y,
|
||||
entity,
|
||||
entity.layer
|
||||
);
|
||||
|
||||
const staticComponent = entity.components.StaticMapEntity;
|
||||
assert(staticComponent, "Can only place static map entities in tiles");
|
||||
}
|
||||
|
||||
/**
|
||||
* Places an entity with the StaticMapEntity component
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
placeStaticEntity(entity) {
|
||||
assert(entity.components.StaticMapEntity, "Entity is not static");
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
const rect = staticComp.getTileSpaceBounds();
|
||||
for (let dx = 0; dx < rect.w; ++dx) {
|
||||
for (let dy = 0; dy < rect.h; ++dy) {
|
||||
const x = rect.x + dx;
|
||||
const y = rect.y + dy;
|
||||
this.getOrCreateChunkAtTile(x, y).setLayerContentFromWorldCords(x, y, entity, entity.layer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an entity with the StaticMapEntity component
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
removeStaticEntity(entity) {
|
||||
assert(entity.components.StaticMapEntity, "Entity is not static");
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
const rect = staticComp.getTileSpaceBounds();
|
||||
for (let dx = 0; dx < rect.w; ++dx) {
|
||||
for (let dy = 0; dy < rect.h; ++dy) {
|
||||
const x = rect.x + dx;
|
||||
const y = rect.y + dy;
|
||||
this.getOrCreateChunkAtTile(x, y).setLayerContentFromWorldCords(x, y, null, entity.layer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Internal
|
||||
|
||||
/**
|
||||
* Checks a given tile for validty
|
||||
* @param {Vector} tile
|
||||
*/
|
||||
internalCheckTile(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.y % 1 === 0, "Tile Y is not a valid integer: " + tile.y);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,32 +66,34 @@ export class MapView extends BaseMap {
|
||||
* @param {DrawParameters} drawParameters
|
||||
*/
|
||||
drawStaticEntityDebugOverlays(drawParameters) {
|
||||
const cullRange = drawParameters.visibleRect.toTileCullRectangle();
|
||||
const top = cullRange.top();
|
||||
const right = cullRange.right();
|
||||
const bottom = cullRange.bottom();
|
||||
const left = cullRange.left();
|
||||
if (G_IS_DEV && (globalConfig.debug.showAcceptorEjectors || globalConfig.debug.showEntityBounds)) {
|
||||
const cullRange = drawParameters.visibleRect.toTileCullRectangle();
|
||||
const top = cullRange.top();
|
||||
const right = cullRange.right();
|
||||
const bottom = cullRange.bottom();
|
||||
const left = cullRange.left();
|
||||
|
||||
const border = 1;
|
||||
const border = 1;
|
||||
|
||||
const minY = top - border;
|
||||
const maxY = bottom + border;
|
||||
const minX = left - border;
|
||||
const maxX = right + border - 1;
|
||||
const minY = top - border;
|
||||
const maxY = bottom + border;
|
||||
const minX = left - border;
|
||||
const maxX = right + border - 1;
|
||||
|
||||
// Render y from top down for proper blending
|
||||
for (let y = minY; y <= maxY; ++y) {
|
||||
for (let x = minX; x <= maxX; ++x) {
|
||||
// const content = this.tiles[x][y];
|
||||
const chunk = this.getChunkAtTileOrNull(x, y);
|
||||
if (!chunk) {
|
||||
continue;
|
||||
}
|
||||
const content = chunk.getTileContentFromWorldCoords(x, y);
|
||||
if (content) {
|
||||
let isBorder = x <= left - 1 || x >= right + 1 || y <= top - 1 || y >= bottom + 1;
|
||||
if (!isBorder) {
|
||||
content.drawDebugOverlays(drawParameters);
|
||||
// Render y from top down for proper blending
|
||||
for (let y = minY; y <= maxY; ++y) {
|
||||
for (let x = minX; x <= maxX; ++x) {
|
||||
// const content = this.tiles[x][y];
|
||||
const chunk = this.getChunkAtTileOrNull(x, y);
|
||||
if (!chunk) {
|
||||
continue;
|
||||
}
|
||||
const content = chunk.getTileContentFromWorldCoords(x, y);
|
||||
if (content) {
|
||||
let isBorder = x <= left - 1 || x >= right + 1 || y <= top - 1 || y >= bottom + 1;
|
||||
if (!isBorder) {
|
||||
content.drawDebugOverlays(drawParameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { MetaStackerBuilding } from "./buildings/stacker";
|
||||
import { enumTrashVariants, MetaTrashBuilding } from "./buildings/trash";
|
||||
import { enumUndergroundBeltVariants, MetaUndergroundBeltBuilding } from "./buildings/underground_belt";
|
||||
import { MetaWireBuilding } from "./buildings/wire";
|
||||
import { gBuildingVariants, registerBuildingVariant } from "./building_codes";
|
||||
import { buildBuildingCodeCache, gBuildingVariants, registerBuildingVariant } from "./building_codes";
|
||||
import { defaultBuildingVariant } from "./meta_building";
|
||||
import { MetaConstantSignalBuilding } from "./buildings/constant_signal";
|
||||
import { MetaLogicGateBuilding, enumLogicGateVariants } from "./buildings/logic_gate";
|
||||
@@ -174,4 +174,7 @@ export function initBuildingCodesAfterResourcesLoaded() {
|
||||
);
|
||||
variant.silhouetteColor = variant.metaInstance.getSilhouetteColor();
|
||||
}
|
||||
|
||||
// Update caches
|
||||
buildBuildingCodeCache();
|
||||
}
|
||||
|
||||
@@ -1,101 +1,101 @@
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { StorageComponent } from "../components/storage";
|
||||
import { DrawParameters } from "../../core/draw_parameters";
|
||||
import { formatBigNumber, lerp } from "../../core/utils";
|
||||
import { Loader } from "../../core/loader";
|
||||
import { BOOL_TRUE_SINGLETON, BOOL_FALSE_SINGLETON } from "../items/boolean_item";
|
||||
import { MapChunkView } from "../map_chunk_view";
|
||||
|
||||
export class StorageSystem extends GameSystemWithFilter {
|
||||
constructor(root) {
|
||||
super(root, [StorageComponent]);
|
||||
|
||||
this.storageOverlaySprite = Loader.getSprite("sprites/misc/storage_overlay.png");
|
||||
|
||||
/**
|
||||
* Stores which uids were already drawn to avoid drawing entities twice
|
||||
* @type {Set<number>}
|
||||
*/
|
||||
this.drawnUids = new Set();
|
||||
|
||||
this.root.signals.gameFrameStarted.add(this.clearDrawnUids, this);
|
||||
}
|
||||
|
||||
clearDrawnUids() {
|
||||
this.drawnUids.clear();
|
||||
}
|
||||
|
||||
update() {
|
||||
for (let i = 0; i < this.allEntities.length; ++i) {
|
||||
const entity = this.allEntities[i];
|
||||
const storageComp = entity.components.Storage;
|
||||
const pinsComp = entity.components.WiredPins;
|
||||
|
||||
// Eject from storage
|
||||
if (storageComp.storedItem && storageComp.storedCount > 0) {
|
||||
const ejectorComp = entity.components.ItemEjector;
|
||||
|
||||
const nextSlot = ejectorComp.getFirstFreeSlot();
|
||||
if (nextSlot !== null) {
|
||||
if (ejectorComp.tryEject(nextSlot, storageComp.storedItem)) {
|
||||
storageComp.storedCount--;
|
||||
|
||||
if (storageComp.storedCount === 0) {
|
||||
storageComp.storedItem = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let targetAlpha = storageComp.storedCount > 0 ? 1 : 0;
|
||||
storageComp.overlayOpacity = lerp(storageComp.overlayOpacity, targetAlpha, 0.05);
|
||||
|
||||
pinsComp.slots[0].value = storageComp.storedItem;
|
||||
pinsComp.slots[1].value = storageComp.getIsFull() ? BOOL_TRUE_SINGLETON : BOOL_FALSE_SINGLETON;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DrawParameters} parameters
|
||||
* @param {MapChunkView} chunk
|
||||
*/
|
||||
drawChunk(parameters, chunk) {
|
||||
const contents = chunk.containedEntitiesByLayer.regular;
|
||||
for (let i = 0; i < contents.length; ++i) {
|
||||
const entity = contents[i];
|
||||
const storageComp = entity.components.Storage;
|
||||
if (!storageComp) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const storedItem = storageComp.storedItem;
|
||||
if (!storedItem) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.drawnUids.has(entity.uid)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.drawnUids.add(entity.uid);
|
||||
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
|
||||
const context = parameters.context;
|
||||
context.globalAlpha = storageComp.overlayOpacity;
|
||||
const center = staticComp.getTileSpaceBounds().getCenter().toWorldSpace();
|
||||
storedItem.drawItemCenteredClipped(center.x, center.y, parameters, 30);
|
||||
|
||||
this.storageOverlaySprite.drawCached(parameters, center.x - 15, center.y + 15, 30, 15);
|
||||
|
||||
if (parameters.visibleRect.containsCircle(center.x, center.y + 25, 20)) {
|
||||
context.font = "bold 10px GameFont";
|
||||
context.textAlign = "center";
|
||||
context.fillStyle = "#64666e";
|
||||
context.fillText(formatBigNumber(storageComp.storedCount), center.x, center.y + 25.5);
|
||||
context.textAlign = "left";
|
||||
}
|
||||
context.globalAlpha = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { StorageComponent } from "../components/storage";
|
||||
import { DrawParameters } from "../../core/draw_parameters";
|
||||
import { formatBigNumber, lerp } from "../../core/utils";
|
||||
import { Loader } from "../../core/loader";
|
||||
import { BOOL_TRUE_SINGLETON, BOOL_FALSE_SINGLETON } from "../items/boolean_item";
|
||||
import { MapChunkView } from "../map_chunk_view";
|
||||
|
||||
export class StorageSystem extends GameSystemWithFilter {
|
||||
constructor(root) {
|
||||
super(root, [StorageComponent]);
|
||||
|
||||
this.storageOverlaySprite = Loader.getSprite("sprites/misc/storage_overlay.png");
|
||||
|
||||
/**
|
||||
* Stores which uids were already drawn to avoid drawing entities twice
|
||||
* @type {Set<number>}
|
||||
*/
|
||||
this.drawnUids = new Set();
|
||||
|
||||
this.root.signals.gameFrameStarted.add(this.clearDrawnUids, this);
|
||||
}
|
||||
|
||||
clearDrawnUids() {
|
||||
this.drawnUids.clear();
|
||||
}
|
||||
|
||||
update() {
|
||||
for (let i = 0; i < this.allEntities.length; ++i) {
|
||||
const entity = this.allEntities[i];
|
||||
const storageComp = entity.components.Storage;
|
||||
const pinsComp = entity.components.WiredPins;
|
||||
|
||||
// Eject from storage
|
||||
if (storageComp.storedItem && storageComp.storedCount > 0) {
|
||||
const ejectorComp = entity.components.ItemEjector;
|
||||
|
||||
const nextSlot = ejectorComp.getFirstFreeSlot();
|
||||
if (nextSlot !== null) {
|
||||
if (ejectorComp.tryEject(nextSlot, storageComp.storedItem)) {
|
||||
storageComp.storedCount--;
|
||||
|
||||
if (storageComp.storedCount === 0) {
|
||||
storageComp.storedItem = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let targetAlpha = storageComp.storedCount > 0 ? 1 : 0;
|
||||
storageComp.overlayOpacity = lerp(storageComp.overlayOpacity, targetAlpha, 0.05);
|
||||
|
||||
pinsComp.slots[0].value = storageComp.storedItem;
|
||||
pinsComp.slots[1].value = storageComp.getIsFull() ? BOOL_TRUE_SINGLETON : BOOL_FALSE_SINGLETON;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DrawParameters} parameters
|
||||
* @param {MapChunkView} chunk
|
||||
*/
|
||||
drawChunk(parameters, chunk) {
|
||||
const contents = chunk.containedEntitiesByLayer.regular;
|
||||
for (let i = 0; i < contents.length; ++i) {
|
||||
const entity = contents[i];
|
||||
const storageComp = entity.components.Storage;
|
||||
if (!storageComp) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const storedItem = storageComp.storedItem;
|
||||
if (!storedItem) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.drawnUids.has(entity.uid)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.drawnUids.add(entity.uid);
|
||||
|
||||
const staticComp = entity.components.StaticMapEntity;
|
||||
|
||||
const context = parameters.context;
|
||||
context.globalAlpha = storageComp.overlayOpacity;
|
||||
const center = staticComp.getTileSpaceBounds().getCenter().toWorldSpace();
|
||||
storedItem.drawItemCenteredClipped(center.x, center.y, parameters, 30);
|
||||
|
||||
this.storageOverlaySprite.drawCached(parameters, center.x - 15, center.y + 15, 30, 15);
|
||||
|
||||
if (parameters.visibleRect.containsCircle(center.x, center.y + 25, 20)) {
|
||||
context.font = "bold 10px GameFont";
|
||||
context.textAlign = "center";
|
||||
context.fillStyle = "#64666e";
|
||||
context.fillText(formatBigNumber(storageComp.storedCount), center.x, center.y + 25.5);
|
||||
context.textAlign = "left";
|
||||
}
|
||||
context.globalAlpha = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user