diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 40053d64..b7a362ee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,6 +42,7 @@ jobs: run: | cd gulp yarn gulp translations.fullBuild + yarn gulp localConfig.findOrCreate cd .. yarn tslint diff --git a/src/js/game/achievement_proxy.js b/src/js/game/achievement_proxy.js index 2ae9ee52..ed05b700 100644 --- a/src/js/game/achievement_proxy.js +++ b/src/js/game/achievement_proxy.js @@ -46,7 +46,7 @@ export class AchievementProxy { } initialize() { - this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.darkMode); + this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.darkMode, null); if (this.has(ACHIEVEMENTS.mam)) { this.root.signals.entityAdded.add(this.onMamFailure, this); @@ -136,7 +136,7 @@ export class AchievementProxy { this.root.signals.entityDestroyed.add(this.onMamFailure, this); } - this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.mam); + this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.mam, null); // reset on every level this.root.savegame.currentData.stats.failedMam = false; diff --git a/src/js/game/hud/parts/building_placer_logic.js b/src/js/game/hud/parts/building_placer_logic.js index a875aad8..bd805449 100644 --- a/src/js/game/hud/parts/building_placer_logic.js +++ b/src/js/game/hud/parts/building_placer_logic.js @@ -110,6 +110,12 @@ export class HUDBuildingPlacerLogic extends BaseHUDPart { // KEYBINDINGS const keyActionMapper = this.root.keyMapper; keyActionMapper.getBinding(KEYMAPPINGS.placement.rotateWhilePlacing).add(this.tryRotate, this); + + keyActionMapper.getBinding(KEYMAPPINGS.placement.rotateToUp).add(this.trySetRotate, this); + keyActionMapper.getBinding(KEYMAPPINGS.placement.rotateToDown).add(this.trySetRotate, this); + keyActionMapper.getBinding(KEYMAPPINGS.placement.rotateToRight).add(this.trySetRotate, this); + keyActionMapper.getBinding(KEYMAPPINGS.placement.rotateToLeft).add(this.trySetRotate, this); + keyActionMapper.getBinding(KEYMAPPINGS.placement.cycleBuildingVariants).add(this.cycleVariants, this); keyActionMapper .getBinding(KEYMAPPINGS.placement.switchDirectionLockSide) @@ -290,6 +296,28 @@ export class HUDBuildingPlacerLogic extends BaseHUDPart { staticComp.rotation = this.currentBaseRotation; } } + + /** + * Rotates the current building to the specified direction. + */ + trySetRotate() { + const selectedBuilding = this.currentMetaBuilding.get(); + if (selectedBuilding) { + if (this.root.keyMapper.getBinding(KEYMAPPINGS.placement.rotateToUp).pressed) { + this.currentBaseRotation = 0; + } else if (this.root.keyMapper.getBinding(KEYMAPPINGS.placement.rotateToDown).pressed) { + this.currentBaseRotation = 180; + } else if (this.root.keyMapper.getBinding(KEYMAPPINGS.placement.rotateToRight).pressed) { + this.currentBaseRotation = 90; + } else if (this.root.keyMapper.getBinding(KEYMAPPINGS.placement.rotateToLeft).pressed) { + this.currentBaseRotation = 270; + } + + const staticComp = this.fakeEntity.components.StaticMapEntity; + staticComp.rotation = this.currentBaseRotation; + } + } + /** * Tries to delete the building under the mouse */ diff --git a/src/js/game/key_action_mapper.js b/src/js/game/key_action_mapper.js index 6f8e914a..2b93f685 100644 --- a/src/js/game/key_action_mapper.js +++ b/src/js/game/key_action_mapper.js @@ -12,6 +12,11 @@ function key(str) { return str.toUpperCase().charCodeAt(0); } +const KEYCODE_UP_ARROW = 38; +const KEYCODE_DOWN_ARROW = 40; +const KEYCODE_LEFT_ARROW = 37; +const KEYCODE_RIGHT_ARROW = 39; + export const KEYMAPPINGS = { general: { confirm: { keyCode: 13 }, // enter @@ -82,6 +87,10 @@ export const KEYMAPPINGS = { pipette: { keyCode: key("Q") }, rotateWhilePlacing: { keyCode: key("R") }, rotateInverseModifier: { keyCode: 16 }, // SHIFT + rotateToUp: { keyCode: KEYCODE_UP_ARROW }, + rotateToDown: { keyCode: KEYCODE_DOWN_ARROW }, + rotateToRight: { keyCode: KEYCODE_RIGHT_ARROW }, + rotateToLeft: { keyCode: KEYCODE_LEFT_ARROW }, cycleBuildingVariants: { keyCode: key("T") }, cycleBuildings: { keyCode: 9 }, // TAB switchDirectionLockSide: { keyCode: key("R") }, @@ -163,13 +172,13 @@ export function getStringForKeyCode(code) { return "END"; case 36: return "HOME"; - case 37: + case KEYCODE_LEFT_ARROW: return "⬅"; - case 38: + case KEYCODE_UP_ARROW: return "⬆"; - case 39: + case KEYCODE_RIGHT_ARROW: return "➡"; - case 40: + case KEYCODE_DOWN_ARROW: return "⬇"; case 44: return "PRNT"; diff --git a/src/js/game/root.js b/src/js/game/root.js index 8a274680..54752e57 100644 --- a/src/js/game/root.js +++ b/src/js/game/root.js @@ -181,7 +181,7 @@ export class GameRoot { freeEntityAreaBeforeBuild: /** @type {TypedSignal<[Entity]>} */ (new Signal()), // Called with an achievement key and necessary args to validate it can be unlocked. - achievementCheck: /** @type {TypedSignal<(string|any)[]>} */ (new Signal()), + achievementCheck: /** @type {TypedSignal<[string, any]>} */ (new Signal()), bulkAchievementCheck: /** @type {TypedSignal<(string|any)[]>} */ (new Signal()), }; diff --git a/src/js/game/shape_definition_manager.js b/src/js/game/shape_definition_manager.js index 92fc9538..89203f1e 100644 --- a/src/js/game/shape_definition_manager.js +++ b/src/js/game/shape_definition_manager.js @@ -97,7 +97,7 @@ export class ShapeDefinitionManager extends BasicSerializableObject { const rightSide = definition.cloneFilteredByQuadrants([2, 3]); const leftSide = definition.cloneFilteredByQuadrants([0, 1]); - this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.cutShape); + this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.cutShape, null); return /** @type {[ShapeDefinition, ShapeDefinition]} */ (this.operationCache[key] = [ this.registerOrReturnHandle(rightSide), @@ -140,7 +140,7 @@ export class ShapeDefinitionManager extends BasicSerializableObject { const rotated = definition.cloneRotateCW(); - this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.rotateShape); + this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.rotateShape, null); return /** @type {ShapeDefinition} */ (this.operationCache[key] = this.registerOrReturnHandle( rotated @@ -195,7 +195,7 @@ export class ShapeDefinitionManager extends BasicSerializableObject { return /** @type {ShapeDefinition} */ (this.operationCache[key]); } - this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.stackShape); + this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.stackShape, null); const stacked = lowerDefinition.cloneAndStackWith(upperDefinition); return /** @type {ShapeDefinition} */ (this.operationCache[key] = this.registerOrReturnHandle( @@ -215,7 +215,7 @@ export class ShapeDefinitionManager extends BasicSerializableObject { return /** @type {ShapeDefinition} */ (this.operationCache[key]); } - this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.paintShape); + this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.paintShape, null); const colorized = definition.cloneAndPaintWith(color); return /** @type {ShapeDefinition} */ (this.operationCache[key] = this.registerOrReturnHandle( diff --git a/src/js/platform/achievement_provider.js b/src/js/platform/achievement_provider.js index 4b4ccef0..cfa1f28a 100644 --- a/src/js/platform/achievement_provider.js +++ b/src/js/platform/achievement_provider.js @@ -2,11 +2,11 @@ import { Application } from "../application"; import { Entity } from "../game/entity"; import { GameRoot } from "../game/root"; -import { ShapeDefinition } from "../game/shape_definition"; import { VANILLA_THEMES } from "../game/theme"; /* typehints:end */ import { enumAnalyticsDataSource } from "../game/production_analytics"; +import { ShapeDefinition } from "../game/shape_definition"; import { ShapeItem } from "../game/items/shape_item"; import { globalConfig } from "../core/config"; import { codes } from "../modloader/old_buildings_codes"; @@ -172,14 +172,8 @@ export class AchievementCollection { isValid: this.isBelt500TilesValid, signal: "entityAdded", }); - this.add(ACHIEVEMENTS.blueprint100k, { - isValid: this.isBlueprint100kValid, - signal: "shapeDelivered", - }); - this.add(ACHIEVEMENTS.blueprint1m, { - isValid: this.isBlueprint1mValid, - signal: "shapeDelivered", - }); + this.add(ACHIEVEMENTS.blueprint100k, this.createBlueprintOptions(100000)); + this.add(ACHIEVEMENTS.blueprint1m, this.createBlueprintOptions(1000000)); this.add(ACHIEVEMENTS.completeLvl26, this.createLevelOptions(26)); this.add(ACHIEVEMENTS.cutShape); this.add(ACHIEVEMENTS.darkMode, { @@ -244,10 +238,12 @@ export class AchievementCollection { }); this.add(ACHIEVEMENTS.stackShape); this.add(ACHIEVEMENTS.store100Unique, { + init: this.initStore100Unique, isValid: this.isStore100UniqueValid, signal: "shapeDelivered", }); this.add(ACHIEVEMENTS.storeShape, { + init: this.initStoreShape, isValid: this.isStoreShapeValid, }); this.add(ACHIEVEMENTS.throughputBp25, this.createRateOptions(SHAPE_BP, 25)); @@ -272,14 +268,14 @@ export class AchievementCollection { this.root.signals.bulkAchievementCheck.add(this.bulkUnlock, this); for (let [key, achievement] of this.map.entries()) { - if (achievement.init) { - achievement.init(); - } - if (achievement.signal) { achievement.receiver = this.unlock.bind(this, key); this.root.signals[achievement.signal].add(achievement.receiver); } + + if (achievement.init) { + achievement.init(); + } } if (!this.hasDefaultReceivers()) { @@ -327,7 +323,7 @@ export class AchievementCollection { /** * @param {string} key - Maps to an Achievement - * @param {?*} data - Data received from signal dispatches for validation + * @param {any} data - Data received from signal dispatches for validation */ unlock(key, data) { if (!this.map.has(key)) { @@ -420,8 +416,18 @@ export class AchievementCollection { return item.getItemType() === ITEM_SHAPE && item.definition.getHash() === shape; } + createBlueprintOptions(count) { + return { + init: ({ key }) => this.unlock(key, ShapeDefinition.fromShortKey(SHAPE_BP)), + isValid: definition => + definition.cachedHash === SHAPE_BP && this.root.hubGoals.storedShapes[SHAPE_BP] >= count, + signal: "shapeDelivered", + }; + } + createLevelOptions(level) { return { + init: ({ key }) => this.unlock(key, this.root.hubGoals.level), isValid: currentLevel => currentLevel >= level, signal: "storyGoalCompleted", }; @@ -464,6 +470,7 @@ export class AchievementCollection { createUpgradeOptions(tier) { return { + init: ({ key }) => this.unlock(key, null), isValid: () => this.hasAllUpgradesAtLeastAtTier(tier), signal: "upgradePurchased", }; @@ -474,16 +481,6 @@ export class AchievementCollection { return entity.components.Belt && entity.components.Belt.assignedPath.totalLength >= 500; } - /** @param {ShapeDefinition} definition @returns {boolean} */ - isBlueprint100kValid(definition) { - return definition.cachedHash === SHAPE_BP && this.root.hubGoals.storedShapes[SHAPE_BP] >= 100000; - } - - /** @param {ShapeDefinition} definition @returns {boolean} */ - isBlueprint1mValid(definition) { - return definition.cachedHash === SHAPE_BP && this.root.hubGoals.storedShapes[SHAPE_BP] >= 1000000; - } - /** @returns {boolean} */ isDarkModeValid() { return this.root.app.settings.currentData.settings.theme === DARK_MODE; @@ -524,7 +521,7 @@ export class AchievementCollection { return this.root.hubGoals.level < 18 && this.isShape(item, SHAPE_LOGO); } - /** @params {number} level @returns {boolean} */ + /** @returns {boolean} */ isMamValid() { return this.root.hubGoals.level > 27 && !this.root.savegame.currentData.stats.failedMam; } @@ -596,11 +593,21 @@ export class AchievementCollection { return item.getItemType() === ITEM_SHAPE && item.definition.layers.length === 4; } + /** @param {Achievement} achievement */ + initStore100Unique({ key }) { + this.unlock(key, null); + } + /** @returns {boolean} */ isStore100UniqueValid() { return Object.keys(this.root.hubGoals.storedShapes).length >= 100; } + /** @param {Achievement} achievement */ + initStoreShape({ key }) { + this.unlock(key, null); + } + /** @returns {boolean} */ isStoreShapeValid() { const entities = this.root.systemMgr.systems.storage.allEntities; @@ -618,15 +625,17 @@ export class AchievementCollection { return false; } - initTrash1000() { + /** @param {Achievement} achievement */ + initTrash1000({ key }) { if (Number(this.root.savegame.currentData.stats.trashedCount)) { + this.unlock(key, 0); return; } this.root.savegame.currentData.stats.trashedCount = 0; } - /** @params {number} count @returns {boolean} */ + /** @param {number} count @returns {boolean} */ isTrash1000Valid(count) { this.root.savegame.currentData.stats.trashedCount += count; diff --git a/translations/base-en.yaml b/translations/base-en.yaml index 2dd47ebc..c4b2cfd5 100644 --- a/translations/base-en.yaml +++ b/translations/base-en.yaml @@ -1138,6 +1138,10 @@ keybindings: rotateWhilePlacing: Rotate rotateInverseModifier: >- Modifier: Rotate CCW instead + rotateToUp: "Rotate: Point Up" + rotateToDown: "Rotate: Point Down" + rotateToRight: "Rotate: Point Right" + rotateToLeft: "Rotate: Point Left" cycleBuildingVariants: Cycle Variants confirmMassDelete: Delete area pasteLastBlueprint: Paste last blueprint