1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2026-03-02 03:39:21 +00:00

Merge branch 'master' of git://github.com/tobspr/shapez.io into feature/ore-q-ing

This commit is contained in:
Gerdon Abbink
2020-06-24 19:44:39 +02:00
44 changed files with 1101 additions and 707 deletions

View File

@@ -33,6 +33,7 @@ import { MainMenuState } from "./states/main_menu";
import { MobileWarningState } from "./states/mobile_warning";
import { PreloadState } from "./states/preload";
import { SettingsState } from "./states/settings";
import { ShapezGameAnalytics } from "./platform/browser/game_analytics";
const logger = createLogger("application");
@@ -130,8 +131,7 @@ export class Application {
this.adProvider = new NoAdProvider(this);
this.sound = new SoundImplBrowser(this);
this.analytics = new GoogleAnalyticsImpl(this);
// this.gameAnalytics = new ShapezGameAnalytics(this);
this.gameAnalytics = new NoGameAnalytics(this);
this.gameAnalytics = new ShapezGameAnalytics(this);
}
/**

View File

@@ -1,4 +1,14 @@
export const CHANGELOG = [
{
version: "1.1.18",
date: "24.06.2020",
entries: [
"Preparations for the wires update",
"Add setting (on by default) to store the last used rotation per building instead of globally storing it (by Magos)",
"Added chinese (traditional) translation",
"Updated translations",
],
},
{
version: "1.1.17",
date: "22.06.2020",

View File

@@ -9,7 +9,7 @@ export const IS_DEBUG =
export const IS_DEMO = queryParamOptions.fullVersion
? false
: (G_IS_PROD && !G_IS_STANDALONE) ||
: (!G_IS_DEV && !G_IS_STANDALONE) ||
(typeof window !== "undefined" && window.location.search.indexOf("demo") >= 0);
export const SUPPORT_TOUCH = false;

View File

@@ -769,7 +769,7 @@ export function quantizeFloat(value) {
* @param {number} tickRate Interval of the timer
*/
export function checkTimerExpired(now, lastTick, tickRate) {
if (!G_IS_PROD) {
if (G_IS_DEV) {
if (quantizeFloat(now) !== now) {
console.error("Got non-quantizied time:" + now + " vs " + quantizeFloat(now));
now = quantizeFloat(now);

View File

@@ -3,13 +3,16 @@ import { BaseItem } from "../base_item";
import { Component } from "../component";
import { types } from "../../savegame/serialization";
import { gItemRegistry } from "../../core/global_registries";
import { Entity } from "../entity";
/**
* @typedef {{
* pos: Vector,
* direction: enumDirection,
* item: BaseItem,
* progress: number?
* progress: number?,
* cachedDestSlot?: import("./item_acceptor").ItemAcceptorLocatedSlot,
* cachedTargetEntity?: Entity
* }} ItemEjectorSlot
*/
@@ -19,6 +22,8 @@ export class ItemEjectorComponent extends Component {
}
static getSchema() {
// The cachedDestSlot, cachedTargetEntity, and cachedConnectedSlots fields
// are not serialized.
return {
instantEject: types.bool,
slots: types.array(
@@ -61,6 +66,9 @@ export class ItemEjectorComponent extends Component {
this.instantEject = instantEject;
this.setSlots(slots);
/** @type {ItemEjectorSlot[]} */
this.cachedConnectedSlots = null;
}
/**
@@ -76,6 +84,8 @@ export class ItemEjectorComponent extends Component {
direction: slot.direction,
item: null,
progress: 0,
cachedDestSlot: null,
cachedTargetEntity: null,
});
}
}

View File

@@ -46,7 +46,13 @@ export class HUDBuildingPlacerLogic extends BaseHUDPart {
* The current rotation
* @type {number}
*/
this.currentBaseRotation = 0;
this.currentBaseRotationGeneral = 0;
/**
* The current rotation preference for each building.
* @type{Object.<string,number>}
*/
this.preferredBaseRotations = {};
/**
* Whether we are currently dragging
@@ -115,6 +121,39 @@ export class HUDBuildingPlacerLogic extends BaseHUDPart {
this.root.camera.upPostHandler.add(this.onMouseUp, this);
}
/**
* Returns the current base rotation for the current meta-building.
* @returns {number}
*/
get currentBaseRotation() {
if (!this.root.app.settings.getAllSettings().rotationByBuilding) {
return this.currentBaseRotationGeneral;
}
const metaBuilding = this.currentMetaBuilding.get();
if (metaBuilding && this.preferredBaseRotations.hasOwnProperty(metaBuilding.getId())) {
return this.preferredBaseRotations[metaBuilding.getId()];
} else {
return this.currentBaseRotationGeneral;
}
}
/**
* Sets the base rotation for the current meta-building.
* @param {number} rotation The new rotation/angle.
*/
set currentBaseRotation(rotation) {
if (!this.root.app.settings.getAllSettings().rotationByBuilding) {
this.currentBaseRotationGeneral = rotation;
} else {
const metaBuilding = this.currentMetaBuilding.get();
if (metaBuilding) {
this.preferredBaseRotations[metaBuilding.getId()] = rotation;
} else {
this.currentBaseRotationGeneral = rotation;
}
}
}
/**
* Returns if the direction lock is currently active
* @returns {boolean}

View File

@@ -7,6 +7,7 @@ import { Entity } from "../entity";
import { GameSystemWithFilter } from "../game_system_with_filter";
import { Math_min } from "../../core/builtins";
import { createLogger } from "../../core/logging";
import { Rectangle } from "../../core/rectangle";
const logger = createLogger("systems/ejector");
@@ -14,23 +15,34 @@ export class ItemEjectorSystem extends GameSystemWithFilter {
constructor(root) {
super(root, [ItemEjectorComponent]);
/**
* @type {Array<{
* targetEntity: Entity,
* sourceSlot: import("../components/item_ejector").ItemEjectorSlot,
* destSlot: import("../components/item_acceptor").ItemAcceptorLocatedSlot
* }>}
*/
this.cache = [];
this.cacheNeedsUpdate = true;
this.root.signals.entityAdded.add(this.invalidateCache, this);
this.root.signals.entityDestroyed.add(this.invalidateCache, this);
/**
* @type {Rectangle[]}
*/
this.smallCacheAreas = [];
}
invalidateCache() {
/**
*
* @param {Entity} entity
*/
invalidateCache(entity) {
if (!entity.components.StaticMapEntity) {
return;
}
this.cacheNeedsUpdate = true;
// Optimize for the common case: adding or removing one building at a time. Clicking
// and dragging can cause up to 4 add/remove signals.
const staticComp = entity.components.StaticMapEntity;
const bounds = staticComp.getTileSpaceBounds();
const expandedBounds = bounds.expandedInAllDirections(2);
this.smallCacheAreas.push(expandedBounds);
}
/**
@@ -39,59 +51,102 @@ export class ItemEjectorSystem extends GameSystemWithFilter {
recomputeCache() {
logger.log("Recomputing cache");
const cache = [];
// Try to find acceptors for every ejector
for (let i = 0; i < this.allEntities.length; ++i) {
const entity = this.allEntities[i];
const ejectorComp = entity.components.ItemEjector;
const staticComp = entity.components.StaticMapEntity;
// For every ejector slot, try to find an acceptor
for (let ejectorSlotIndex = 0; ejectorSlotIndex < ejectorComp.slots.length; ++ejectorSlotIndex) {
const ejectorSlot = ejectorComp.slots[ejectorSlotIndex];
// Figure out where and into which direction we eject items
const ejectSlotWsTile = staticComp.localTileToWorld(ejectorSlot.pos);
const ejectSlotWsDirection = staticComp.localDirectionToWorld(ejectorSlot.direction);
const ejectSlotWsDirectionVector = enumDirectionToVector[ejectSlotWsDirection];
const ejectSlotTargetWsTile = ejectSlotWsTile.add(ejectSlotWsDirectionVector);
// Try to find the given acceptor component to take the item
const targetEntity = this.root.map.getTileContent(ejectSlotTargetWsTile);
if (!targetEntity) {
// No consumer for item
continue;
}
const targetAcceptorComp = targetEntity.components.ItemAcceptor;
const targetStaticComp = targetEntity.components.StaticMapEntity;
if (!targetAcceptorComp) {
// Entity doesn't accept items
continue;
}
const matchingSlot = targetAcceptorComp.findMatchingSlot(
targetStaticComp.worldToLocalTile(ejectSlotTargetWsTile),
targetStaticComp.worldDirectionToLocal(ejectSlotWsDirection)
);
if (!matchingSlot) {
// No matching slot found
continue;
}
// Ok we found a connection
cache.push({
targetEntity,
sourceSlot: ejectorSlot,
destSlot: matchingSlot,
});
let entryCount = 0;
if (this.smallCacheAreas.length <= 4) {
// Only recompute caches of entities inside the rectangles.
for (let i = 0; i < this.smallCacheAreas.length; i++) {
entryCount += this.recomputeAreaCaches(this.smallCacheAreas[i]);
}
} else {
// Try to find acceptors for every ejector
for (let i = 0; i < this.allEntities.length; ++i) {
const entity = this.allEntities[i];
entryCount += this.recomputeSingleEntityCache(entity);
}
}
logger.log("Found", entryCount, "entries to update");
this.cache = cache;
logger.log("Found", cache.length, "entries to update");
this.smallCacheAreas = [];
}
/**
*
* @param {Rectangle} area
*/
recomputeAreaCaches(area) {
let entryCount = 0;
for (let x = area.x; x < area.right(); ++x) {
for (let y = area.y; y < area.bottom(); ++y) {
const entity = this.root.map.getTileContentXY(x, y);
if (entity && entity.components.ItemEjector) {
entryCount += this.recomputeSingleEntityCache(entity);
}
}
}
return entryCount;
}
/**
*
* @param {Entity} entity
*/
recomputeSingleEntityCache(entity) {
const ejectorComp = entity.components.ItemEjector;
const staticComp = entity.components.StaticMapEntity;
// Clear the old cache.
ejectorComp.cachedConnectedSlots = null;
// For every ejector slot, try to find an acceptor
let entryCount = 0;
for (let ejectorSlotIndex = 0; ejectorSlotIndex < ejectorComp.slots.length; ++ejectorSlotIndex) {
const ejectorSlot = ejectorComp.slots[ejectorSlotIndex];
// Clear the old cache.
ejectorSlot.cachedDestSlot = null;
ejectorSlot.cachedTargetEntity = null;
// Figure out where and into which direction we eject items
const ejectSlotWsTile = staticComp.localTileToWorld(ejectorSlot.pos);
const ejectSlotWsDirection = staticComp.localDirectionToWorld(ejectorSlot.direction);
const ejectSlotWsDirectionVector = enumDirectionToVector[ejectSlotWsDirection];
const ejectSlotTargetWsTile = ejectSlotWsTile.add(ejectSlotWsDirectionVector);
// Try to find the given acceptor component to take the item
const targetEntity = this.root.map.getTileContent(ejectSlotTargetWsTile);
if (!targetEntity) {
// No consumer for item
continue;
}
const targetAcceptorComp = targetEntity.components.ItemAcceptor;
const targetStaticComp = targetEntity.components.StaticMapEntity;
if (!targetAcceptorComp) {
// Entity doesn't accept items
continue;
}
const matchingSlot = targetAcceptorComp.findMatchingSlot(
targetStaticComp.worldToLocalTile(ejectSlotTargetWsTile),
targetStaticComp.worldDirectionToLocal(ejectSlotWsDirection)
);
if (!matchingSlot) {
// No matching slot found
continue;
}
// Ok we found a connection
if (ejectorComp.cachedConnectedSlots) {
ejectorComp.cachedConnectedSlots.push(ejectorSlot);
} else {
ejectorComp.cachedConnectedSlots = [ejectorSlot];
}
ejectorSlot.cachedTargetEntity = targetEntity;
ejectorSlot.cachedDestSlot = matchingSlot;
entryCount += 1;
}
return entryCount;
}
update() {
@@ -109,35 +164,45 @@ export class ItemEjectorSystem extends GameSystemWithFilter {
}
// Go over all cache entries
for (let i = 0; i < this.cache.length; ++i) {
const { sourceSlot, destSlot, targetEntity } = this.cache[i];
const item = sourceSlot.item;
if (!item) {
// No item available to be ejected
for (let i = 0; i < this.allEntities.length; ++i) {
const sourceEntity = this.allEntities[i];
const sourceEjectorComp = sourceEntity.components.ItemEjector;
if (!sourceEjectorComp.cachedConnectedSlots) {
continue;
}
for (let j = 0; j < sourceEjectorComp.cachedConnectedSlots.length; j++) {
const sourceSlot = sourceEjectorComp.cachedConnectedSlots[j];
const destSlot = sourceSlot.cachedDestSlot;
const targetEntity = sourceSlot.cachedTargetEntity;
// Advance items on the slot
sourceSlot.progress = Math_min(1, sourceSlot.progress + progressGrowth);
const item = sourceSlot.item;
// Check if we are still in the process of ejecting, can't proceed then
if (sourceSlot.progress < 1.0) {
continue;
}
if (!item) {
// No item available to be ejected
continue;
}
// Check if the target acceptor can actually accept this item
const targetAcceptorComp = targetEntity.components.ItemAcceptor;
if (!targetAcceptorComp.canAcceptItem(destSlot.index, item)) {
continue;
}
// Advance items on the slot
sourceSlot.progress = Math_min(1, sourceSlot.progress + progressGrowth);
// Try to hand over the item
if (this.tryPassOverItem(item, targetEntity, destSlot.index)) {
// Handover successful, clear slot
targetAcceptorComp.onItemAccepted(destSlot.index, destSlot.acceptedDirection, item);
sourceSlot.item = null;
continue;
// Check if we are still in the process of ejecting, can't proceed then
if (sourceSlot.progress < 1.0) {
continue;
}
// Check if the target acceptor can actually accept this item
const targetAcceptorComp = targetEntity.components.ItemAcceptor;
if (!targetAcceptorComp.canAcceptItem(destSlot.index, item)) {
continue;
}
// Try to hand over the item
if (this.tryPassOverItem(item, targetEntity, destSlot.index)) {
// Handover successful, clear slot
targetAcceptorComp.onItemAccepted(destSlot.index, destSlot.acceptedDirection, item);
sourceSlot.item = null;
continue;
}
}
}
}

1
src/js/globals.d.ts vendored
View File

@@ -1,7 +1,6 @@
// Globals defined by webpack
declare const G_IS_DEV: boolean;
declare const G_IS_PROD: boolean;
declare function assert(condition: boolean | object | string, ...errorMessage: string[]): void;
declare function assertAlways(condition: boolean | object | string, ...errorMessage: string[]): void;

View File

@@ -51,10 +51,10 @@ export const LANGUAGES = {
region: "",
},
"es-419": {
name: "Español (Latinoamérica)",
name: "Español",
data: require("./built-temp/base-es.json"),
code: "es",
region: "419",
region: "",
},
"pl": {
name: "Polski",
@@ -80,12 +80,23 @@ export const LANGUAGES = {
code: "no",
region: "",
},
"zh-CN": {
name: "简体中文",
// simplified
name: "中文简体",
data: require("./built-temp/base-zh-CN.json"),
code: "zh",
region: "CN",
},
"zh-TW": {
// traditional
name: "中文繁體",
data: require("./built-temp/base-zh-TW.json"),
code: "zh",
region: "TW",
},
"sv": {
name: "Svenska",
data: require("./built-temp/base-sv.json"),

View File

@@ -1,11 +1,12 @@
import { GameAnalyticsInterface } from "../game_analytics";
import { createLogger } from "../../core/logging";
import { ShapeDefinition } from "../../game/shape_definition";
import { Savegame } from "../../savegame/savegame";
import { FILE_NOT_FOUND } from "../storage";
import { globalConfig } from "../../core/config";
import { InGameState } from "../../states/ingame";
import { createLogger } from "../../core/logging";
import { GameRoot } from "../../game/root";
import { InGameState } from "../../states/ingame";
import { GameAnalyticsInterface } from "../game_analytics";
import { FILE_NOT_FOUND } from "../storage";
import { blueprintShape, UPGRADES } from "../../game/upgrades";
import { tutorialGoals } from "../../game/tutorial_goals";
import { BeltComponent } from "../../game/components/belt";
import { StaticMapEntityComponent } from "../../game/components/static_map_entity";
const logger = createLogger("game_analytics");
@@ -14,16 +15,32 @@ const analyticsUrl = G_IS_DEV ? "http://localhost:8001" : "https://analytics.sha
// Be sure to increment the ID whenever it changes to make sure all
// users are tracked
const analyticsLocalFile = "analytics_token.3.bin";
const analyticsLocalFile = "shapez_token_123.bin";
export class ShapezGameAnalytics extends GameAnalyticsInterface {
get environment() {
if (G_IS_DEV) {
return "dev";
}
if (G_IS_STANDALONE) {
return "steam";
}
if (G_IS_RELEASE) {
return "prod";
}
return "beta";
}
/**
* @returns {Promise<void>}
*/
initialize() {
this.syncKey = null;
setInterval(() => this.sendTimePoints(), 120 * 1000);
setInterval(() => this.sendTimePoints(), 60 * 1000);
// Retrieve sync key from player
return this.app.storage.readFileAsync(analyticsLocalFile).then(
@@ -38,7 +55,7 @@ export class ShapezGameAnalytics extends GameAnalyticsInterface {
// Perform call to get a new key from the API
this.sendToApi("/v1/register", {
environment: G_APP_ENVIRONMENT,
environment: this.environment,
})
.then(res => {
// Try to read and parse the key from the api
@@ -135,10 +152,12 @@ export class ShapezGameAnalytics extends GameAnalyticsInterface {
playerKey: this.syncKey,
gameKey: savegameId,
ingameTime: root.time.now(),
environment: this.environment,
category,
value,
version: G_BUILD_VERSION,
gameDump: this.generateGameDump(root, category === "sync"),
level: root.hubGoals.level,
gameDump: this.generateGameDump(root),
});
}
@@ -151,52 +170,58 @@ export class ShapezGameAnalytics extends GameAnalyticsInterface {
}
/**
* Generates a game dump
* @param {GameRoot} root
* @param {boolean=} metaOnly
* Returns true if the shape is interesting
* @param {string} key
*/
generateGameDump(root, metaOnly = false) {
let staticEntities = [];
isInterestingShape(key) {
if (key === blueprintShape) {
return true;
}
const entities = root.entityMgr.getAllWithComponent(StaticMapEntityComponent);
// Limit the entities
if (!metaOnly && entities.length < 500) {
for (let i = 0; i < entities.length; ++i) {
const entity = entities[i];
const staticComp = entity.components.StaticMapEntity;
const payload = {};
payload.origin = staticComp.origin;
payload.tileSize = staticComp.tileSize;
payload.rotation = staticComp.rotation;
if (entity.components.Belt) {
payload.type = "belt";
} else if (entity.components.UndergroundBelt) {
payload.type = "tunnel";
} else if (entity.components.ItemProcessor) {
payload.type = entity.components.ItemProcessor.type;
} else if (entity.components.Miner) {
payload.type = "extractor";
} else {
logger.warn("Unkown entity type", entity);
}
staticEntities.push(payload);
// Check if its a story goal
for (let i = 0; i < tutorialGoals.length; ++i) {
if (key === tutorialGoals[i].shape) {
return true;
}
}
return {
storedShapes: root.hubGoals.storedShapes,
gainedRewards: root.hubGoals.gainedRewards,
upgradeLevels: root.hubGoals.upgradeLevels,
staticEntities,
};
// Check if its required to unlock an upgrade
for (const upgradeKey in UPGRADES) {
const handle = UPGRADES[upgradeKey];
const tiers = handle.tiers;
for (let i = 0; i < tiers.length; ++i) {
const tier = tiers[i];
const required = tier.required;
for (let k = 0; k < required.length; ++k) {
if (required[k].shape === key) {
return true;
}
}
}
}
return false;
}
/**
* @param {ShapeDefinition} definition
* Generates a game dump
* @param {GameRoot} root
*/
handleShapeDelivered(definition) {}
generateGameDump(root) {
const shapeIds = Object.keys(root.hubGoals.storedShapes).filter(this.isInterestingShape.bind(this));
let shapes = {};
for (let i = 0; i < shapeIds.length; ++i) {
shapes[shapeIds[i]] = root.hubGoals.storedShapes[shapeIds[i]];
}
return {
shapes,
upgrades: root.hubGoals.upgradeLevels,
belts: root.entityMgr.getAllWithComponent(BeltComponent).length,
buildings:
root.entityMgr.getAllWithComponent(StaticMapEntityComponent).length -
root.entityMgr.getAllWithComponent(BeltComponent).length,
};
}
/**
*/
@@ -204,6 +229,12 @@ export class ShapezGameAnalytics extends GameAnalyticsInterface {
this.sendGameEvent("game_start", "");
}
/**
*/
handleGameResumed() {
this.sendTimePoints();
}
/**
* Handles the given level completed
* @param {number} level

View File

@@ -25,9 +25,9 @@ export class GameAnalyticsInterface {
handleGameStarted() {}
/**
* @param {ShapeDefinition} definition
* Handles a resumed game
*/
handleShapeDelivered(definition) {}
handleGameResumed() {}
/**
* Handles the given level completed

View File

@@ -251,6 +251,7 @@ export const allApplicationSettings = [
new BoolSetting("vignette", categoryGame, (app, value) => {}),
new BoolSetting("compactBuildingInfo", categoryGame, (app, value) => {}),
new BoolSetting("disableCutDeleteWarnings", categoryGame, (app, value) => {}),
new BoolSetting("rotationByBuilding", categoryGame, (app, value) => {}),
];
export function getApplicationSettingById(id) {
@@ -277,6 +278,7 @@ class SettingsStorage {
this.vignette = true;
this.compactBuildingInfo = false;
this.disableCutDeleteWarnings = false;
this.rotationByBuilding = true;
this.enableColorBlindHelper = false;
@@ -479,7 +481,7 @@ export class ApplicationSettings extends ReadWriteProxy {
}
getCurrentVersion() {
return 17;
return 18;
}
/** @param {{settings: SettingsStorage, version: number}} data */
@@ -552,6 +554,11 @@ export class ApplicationSettings extends ReadWriteProxy {
data.version = 17;
}
if (data.version < 18) {
data.settings.rotationByBuilding = true;
data.version = 18;
}
return ExplainedResult.good();
}
}

View File

@@ -217,7 +217,6 @@ export class InGameState extends GameState {
this.core.initializeRoot(this, this.savegame);
if (this.savegame.hasGameDump()) {
this.app.gameAnalytics.handleGameStarted();
this.stage4bResumeGame();
} else {
this.app.gameAnalytics.handleGameStarted();
@@ -245,6 +244,7 @@ export class InGameState extends GameState {
this.onInitializationFailure("Savegame is corrupt and can not be restored.");
return;
}
this.app.gameAnalytics.handleGameResumed();
this.stage5FirstUpdate();
}
}