${T.mainMenu.puzzleDlcText}
+ + ${T.puzzleMenu.dlcHint} +diff --git a/Dockerfile b/Dockerfile index 61d54684..b79cac20 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,6 +26,7 @@ COPY translations ./translations COPY src/js ./src/js COPY res_raw ./res_raw COPY .git ./.git +COPY electron ./electron WORKDIR /shapez.io/gulp ENTRYPOINT ["yarn", "gulp"] diff --git a/electron/steam.js b/electron/steam.js index 672fccae..464b7924 100644 --- a/electron/steam.js +++ b/electron/steam.js @@ -75,7 +75,7 @@ function listen() { }); ipcMain.handle("steam:check-app-ownership", (event, appId) => { - return Promise.resolve(greenworks.isSubscribedApp(appId)); + return Promise.resolve(greenworks.isDLCInstalled(appId)); }); } diff --git a/res_raw/sounds/music/puzzle-full.mp3 b/res_raw/sounds/music/puzzle-full.mp3 index 6ea6f544..a2c0d80a 100644 Binary files a/res_raw/sounds/music/puzzle-full.mp3 and b/res_raw/sounds/music/puzzle-full.mp3 differ diff --git a/res_raw/sprites/blueprints/block.png b/res_raw/sprites/blueprints/block.png index ff6107cf..5a27786e 100644 Binary files a/res_raw/sprites/blueprints/block.png and b/res_raw/sprites/blueprints/block.png differ diff --git a/res_raw/sprites/blueprints/constant_producer.png b/res_raw/sprites/blueprints/constant_producer.png index 85b55ded..417a7886 100644 Binary files a/res_raw/sprites/blueprints/constant_producer.png and b/res_raw/sprites/blueprints/constant_producer.png differ diff --git a/src/css/ingame_hud/puzzle_editor_settings.scss b/src/css/ingame_hud/puzzle_editor_settings.scss index 70d16123..9d093c42 100644 --- a/src/css/ingame_hud/puzzle_editor_settings.scss +++ b/src/css/ingame_hud/puzzle_editor_settings.scss @@ -57,6 +57,15 @@ } } } + + > .buildingsButton { + display: grid; + align-items: center; + @include S(margin-top, 4px); + > button { + @include SuperSmallText; + } + } } } } diff --git a/src/css/ingame_hud/puzzle_play_settings.scss b/src/css/ingame_hud/puzzle_play_settings.scss index 13e25c61..b53d0829 100644 --- a/src/css/ingame_hud/puzzle_play_settings.scss +++ b/src/css/ingame_hud/puzzle_play_settings.scss @@ -13,7 +13,7 @@ > .section { display: grid; - @include S(grid-gap, 10px); + @include S(grid-gap, 5px); grid-auto-flow: row; > button { diff --git a/src/css/states/main_menu.scss b/src/css/states/main_menu.scss index 68103929..2b44b56e 100644 --- a/src/css/states/main_menu.scss +++ b/src/css/states/main_menu.scss @@ -230,11 +230,38 @@ align-items: center; justify-content: center; flex-direction: column; - background: #4cc98a; + background: $colorBlueBright; grid-row: 1 / 2; grid-column: 2 / 3; + position: relative; @include S(padding, 20px); @include S(border-radius, $globalBorderRadius); + + > .badge { + color: #fff; + text-transform: uppercase; + font-weight: bold; + position: absolute; + @include S(top, 10px); + @include S(right, 10px); + + transform: translateX(50%) rotate(10deg); + @include Heading; + font-weight: bold; + + @include InlineAnimation(1.3s ease-in-out infinite) { + 50% { + transform: translateX(50%) rotate(12deg) scale(1.1); + } + } + } + + > .hint { + @include SuperDuperSmallText; + @include S(margin-top, 10px); + @include S(width, 200px); + } + > .dlcLogo { @include S(width, 190px); } diff --git a/src/css/states/puzzle_menu.scss b/src/css/states/puzzle_menu.scss index 5f28d902..e2deacf0 100644 --- a/src/css/states/puzzle_menu.scss +++ b/src/css/states/puzzle_menu.scss @@ -159,6 +159,26 @@ } } + > button.delete { + position: absolute; + @include S(top, 5px); + @include S(right, 5px); + background-repeat: no-repeat; + background-position: center center; + background-size: 70%; + background-color: transparent !important; + @include S(width, 20px); + @include S(height, 20px); + padding: 0; + opacity: 0.7; + @include DarkThemeInvert; + + & { + /* @load-async */ + background-image: uiResource("icons/delete.png") !important; + } + } + > .stats { grid-column: 2 / 3; grid-row: 3 / 4; diff --git a/src/js/changelog.js b/src/js/changelog.js index ee37c45f..9b497ff8 100644 --- a/src/js/changelog.js +++ b/src/js/changelog.js @@ -1,11 +1,31 @@ export const CHANGELOG = [ + { + version: "1.4.2", + date: "24.06.2021", + entries: [ + "Puzzle DLC: Goal acceptors now reset after getting no items for a while (This should prevent being able to 'cheat' puzzles) (by Sense101)", + "Puzzle DLC: Added button to clear all buildings / reset the puzzle (by Sense101)", + "Puzzle DLC: Allow copy-paste in puzzle mode (by Sense101)", + "Fixed level achievements being given on the wrong level (by DJ1TJOO)", + "Fixed blueprint not properly clearing on right click", + "Updated translations", + ], + }, + { + version: "1.4.1", + date: "22.06.2021", + entries: [ + "The Puzzle DLC is now available on Steam!", + "The Soundtrack is now also available to wishlist and will be released within the next days, including the new music from the Puzzle DLC!", + ], + }, { version: "1.4.0", date: "04.06.2021", entries: [ "Belts in blueprints should now always paste correctly", "You can now clear belts by selecting them and then pressing 'B'", - "Preparations for the Puzzle DLC, coming June 22nd!", + "Preparations for the Puzzle DLC, coming June 22nd!", ], }, { diff --git a/src/js/core/config.js b/src/js/core/config.js index b4f36af5..005e2719 100644 --- a/src/js/core/config.js +++ b/src/js/core/config.js @@ -18,6 +18,7 @@ export const THIRDPARTY_URLS = { shapeViewer: "https://viewer.shapez.io", standaloneStorePage: "https://store.steampowered.com/app/1318690/shapezio/", + stanaloneCampaignLink: "https://get.shapez.io", puzzleDlcStorePage: "https://store.steampowered.com/app/1625400/shapezio__Puzzle_DLC", levelTutorialVideos: { @@ -72,8 +73,8 @@ export const globalConfig = { readerAnalyzeIntervalSeconds: 10, - goalAcceptorMinimumDurationSeconds: 5, - goalAcceptorsPerProducer: 4.5, + goalAcceptorItemsRequired: 12, + goalAcceptorsPerProducer: 5, puzzleModeSpeed: 3, puzzleMinBoundsSize: 2, puzzleMaxBoundsSize: 20, diff --git a/src/js/game/blueprint.js b/src/js/game/blueprint.js index 3e7cdaa6..795b27c3 100644 --- a/src/js/game/blueprint.js +++ b/src/js/game/blueprint.js @@ -101,8 +101,12 @@ export class Blueprint { const entity = this.entities[i]; const staticComp = entity.components.StaticMapEntity; + // Actually keeping this in as an easter egg to rotate the trash can + // if (staticComp.getMetaBuilding().getIsRotateable()) { staticComp.rotation = (staticComp.rotation + 90) % 360; staticComp.originalRotation = (staticComp.originalRotation + 90) % 360; + // } + staticComp.origin = staticComp.origin.rotateFastMultipleOf90(90); } } @@ -139,6 +143,9 @@ export class Blueprint { * @param {GameRoot} root */ canAfford(root) { + if (root.gameMode.getHasFreeCopyPaste()) { + return true; + } return root.hubGoals.getShapesStoredByKey(root.gameMode.getBlueprintShapeKey()) >= this.getCost(); } diff --git a/src/js/game/components/goal_acceptor.js b/src/js/game/components/goal_acceptor.js index 87c55501..fa5f5908 100644 --- a/src/js/game/components/goal_acceptor.js +++ b/src/js/game/components/goal_acceptor.js @@ -30,20 +30,38 @@ export class GoalAcceptorComponent extends Component { } clear() { - // the last items we delivered - /** @type {{ item: BaseItem; time: number; }[]} */ - this.deliveryHistory = []; + /** + * The last item we delivered + * @type {{ item: BaseItem; time: number; } | null} */ + this.lastDelivery = null; + + // The amount of items we delivered so far + this.currentDeliveredItems = 0; // Used for animations this.displayPercentage = 0; } - getRequiredDeliveryHistorySize() { + /** + * Clears items but doesn't instantly reset the progress bar + */ + clearItems() { + this.lastDelivery = null; + this.currentDeliveredItems = 0; + } + + getRequiredSecondsPerItem() { return ( - (globalConfig.puzzleModeSpeed * - globalConfig.goalAcceptorMinimumDurationSeconds * - globalConfig.beltSpeedItemsPerSecond) / - globalConfig.goalAcceptorsPerProducer + globalConfig.goalAcceptorsPerProducer / + (globalConfig.puzzleModeSpeed * globalConfig.beltSpeedItemsPerSecond) ); } + + /** + * Copy the current state to another component + * @param {GoalAcceptorComponent} otherComponent + */ + copyAdditionalStateTo(otherComponent) { + otherComponent.item = this.item; + } } diff --git a/src/js/game/game_mode.js b/src/js/game/game_mode.js index 5eca211a..bb60d8a6 100644 --- a/src/js/game/game_mode.js +++ b/src/js/game/game_mode.js @@ -166,8 +166,8 @@ export class GameMode extends BasicSerializableObject { } /** @returns {boolean} */ - getSupportsCopyPaste() { - return true; + getHasFreeCopyPaste() { + return false; } /** @returns {boolean} */ diff --git a/src/js/game/hud/hud.js b/src/js/game/hud/hud.js index 10daa561..f35fe018 100644 --- a/src/js/game/hud/hud.js +++ b/src/js/game/hud/hud.js @@ -6,6 +6,7 @@ import { MetaBuilding } from "../meta_building"; import { GameRoot } from "../root"; import { ShapeDefinition } from "../shape_definition"; import { HUDBetaOverlay } from "./parts/beta_overlay"; +import { HUDBlueprintPlacer } from "./parts/blueprint_placer"; import { HUDBuildingsToolbar } from "./parts/buildings_toolbar"; import { HUDBuildingPlacer } from "./parts/building_placer"; import { HUDColorBlindHelper } from "./parts/color_blind_helper"; @@ -44,6 +45,8 @@ export class GameHUD { this.parts = { buildingsToolbar: new HUDBuildingsToolbar(this.root), + + blueprintPlacer: new HUDBlueprintPlacer(this.root), buildingPlacer: new HUDBuildingPlacer(this.root), // Must always exist diff --git a/src/js/game/hud/parts/blueprint_placer.js b/src/js/game/hud/parts/blueprint_placer.js index 54e2e3b7..4b2bafb2 100644 --- a/src/js/game/hud/parts/blueprint_placer.js +++ b/src/js/game/hud/parts/blueprint_placer.js @@ -50,6 +50,10 @@ export class HUDBlueprintPlacer extends BaseHUDPart { this.trackedCanAfford = new TrackedState(this.onCanAffordChanged, this); } + getHasFreeCopyPaste() { + return this.root.gameMode.getHasFreeCopyPaste(); + } + abortPlacement() { if (this.currentBlueprint.get()) { this.currentBlueprint.set(null); @@ -82,7 +86,9 @@ export class HUDBlueprintPlacer extends BaseHUDPart { update() { const currentBlueprint = this.currentBlueprint.get(); - this.domAttach.update(currentBlueprint && currentBlueprint.getCost() > 0); + this.domAttach.update( + !this.getHasFreeCopyPaste() && currentBlueprint && currentBlueprint.getCost() > 0 + ); this.trackedCanAfford.set(currentBlueprint && currentBlueprint.canAfford(this.root)); } @@ -114,7 +120,7 @@ export class HUDBlueprintPlacer extends BaseHUDPart { return; } - if (!blueprint.canAfford(this.root)) { + if (!this.getHasFreeCopyPaste() && !blueprint.canAfford(this.root)) { this.root.soundProxy.playUiError(); return; } @@ -122,8 +128,10 @@ export class HUDBlueprintPlacer extends BaseHUDPart { const worldPos = this.root.camera.screenToWorld(pos); const tile = worldPos.toTileSpace(); if (blueprint.tryPlace(this.root, tile)) { - const cost = blueprint.getCost(); - this.root.hubGoals.takeShapeByKey(this.root.gameMode.getBlueprintShapeKey(), cost); + if (!this.getHasFreeCopyPaste()) { + const cost = blueprint.getCost(); + this.root.hubGoals.takeShapeByKey(this.root.gameMode.getBlueprintShapeKey(), cost); + } this.root.soundProxy.playUi(SOUNDS.placeBuilding); } return STOP_PROPAGATION; @@ -131,7 +139,7 @@ export class HUDBlueprintPlacer extends BaseHUDPart { } /** - * Mose move handler + * Mouse move handler */ onMouseMove() { // Prevent movement while blueprint is selected diff --git a/src/js/game/hud/parts/mass_selector.js b/src/js/game/hud/parts/mass_selector.js index ab933da3..b8283d55 100644 --- a/src/js/game/hud/parts/mass_selector.js +++ b/src/js/game/hud/parts/mass_selector.js @@ -1,5 +1,6 @@ import { globalConfig } from "../../../core/config"; import { DrawParameters } from "../../../core/draw_parameters"; +import { gMetaBuildingRegistry } from "../../../core/global_registries"; import { createLogger } from "../../../core/logging"; import { STOP_PROPAGATION } from "../../../core/signal"; import { formatBigNumberFull } from "../../../core/utils"; @@ -7,6 +8,8 @@ import { Vector } from "../../../core/vector"; import { ACHIEVEMENTS } from "../../../platform/achievement_provider"; import { T } from "../../../translations"; import { Blueprint } from "../../blueprint"; +import { MetaBlockBuilding } from "../../buildings/block"; +import { MetaConstantProducerBuilding } from "../../buildings/constant_producer"; import { enumMouseButton } from "../../camera"; import { Component } from "../../component"; import { Entity } from "../../entity"; @@ -260,7 +263,14 @@ export class HUDMassSelector extends BaseHUDPart { for (let x = realTileStart.x; x <= realTileEnd.x; ++x) { for (let y = realTileStart.y; y <= realTileEnd.y; ++y) { const contents = this.root.map.getLayerContentXY(x, y, this.root.currentLayer); + if (contents && this.root.logic.canDeleteBuilding(contents)) { + const staticComp = contents.components.StaticMapEntity; + + if (!staticComp.getMetaBuilding().getIsRemovable(this.root)) { + continue; + } + this.selectedUids.add(contents.uid); } } @@ -320,6 +330,11 @@ export class HUDMassSelector extends BaseHUDPart { renderedUids.add(uid); const staticComp = contents.components.StaticMapEntity; + + if (!staticComp.getMetaBuilding().getIsRemovable(this.root)) { + continue; + } + const bounds = staticComp.getTileSpaceBounds(); parameters.context.beginRoundedRect( bounds.x * globalConfig.tileSize + boundsBorder, diff --git a/src/js/game/hud/parts/modal_dialogs.js b/src/js/game/hud/parts/modal_dialogs.js index a43260e3..33211cf6 100644 --- a/src/js/game/hud/parts/modal_dialogs.js +++ b/src/js/game/hud/parts/modal_dialogs.js @@ -125,7 +125,7 @@ export class HUDModalDialogs extends BaseHUDPart { dialog.buttonSignals.getStandalone.add(() => { this.app.analytics.trackUiClick("demo_dialog_click"); - window.open(THIRDPARTY_URLS.standaloneStorePage + "?ref=ddc"); + window.open(THIRDPARTY_URLS.stanaloneCampaignLink + "/shapez_demo_dialog"); }); return dialog.buttonSignals; diff --git a/src/js/game/hud/parts/puzzle_editor_review.js b/src/js/game/hud/parts/puzzle_editor_review.js index 68f5360c..727006d6 100644 --- a/src/js/game/hud/parts/puzzle_editor_review.js +++ b/src/js/game/hud/parts/puzzle_editor_review.js @@ -216,8 +216,8 @@ export class HUDPuzzleEditorReview extends BaseHUDPart { if (!goalComp.item) { return T.puzzleMenu.validation.goalAcceptorNoItem; } - const required = goalComp.getRequiredDeliveryHistorySize(); - if (goalComp.deliveryHistory.length < required) { + const required = globalConfig.goalAcceptorItemsRequired; + if (goalComp.currentDeliveredItems < required) { return T.puzzleMenu.validation.goalAcceptorRateNotMet; } } diff --git a/src/js/game/hud/parts/puzzle_editor_settings.js b/src/js/game/hud/parts/puzzle_editor_settings.js index cf283a9b..13564da4 100644 --- a/src/js/game/hud/parts/puzzle_editor_settings.js +++ b/src/js/game/hud/parts/puzzle_editor_settings.js @@ -1,13 +1,14 @@ -/* typehints:start */ -import { PuzzleGameMode } from "../../modes/puzzle"; -/* typehints:end */ - import { globalConfig } from "../../../core/config"; +import { gMetaBuildingRegistry } from "../../../core/global_registries"; import { createLogger } from "../../../core/logging"; import { Rectangle } from "../../../core/rectangle"; import { makeDiv } from "../../../core/utils"; import { T } from "../../../translations"; +import { MetaBlockBuilding } from "../../buildings/block"; +import { MetaConstantProducerBuilding } from "../../buildings/constant_producer"; +import { MetaGoalAcceptorBuilding } from "../../buildings/goal_acceptor"; import { StaticMapEntityComponent } from "../../components/static_map_entity"; +import { PuzzleGameMode } from "../../modes/puzzle"; import { BaseHUDPart } from "../base_hud_part"; const logger = createLogger("puzzle-editor"); @@ -43,8 +44,13 @@ export class HUDPuzzleEditorSettings extends BaseHUDPart {
+ +${T.mainMenu.puzzleDlcText}
+ + ${T.puzzleMenu.dlcHint} +CTRL键 + 拖动:选择区域以复制或删除。SHIFT键: 按住以放置多个同一种设施。ALT键: 反向放置传送带。ALT键:反向放置传送带。CTRL + 拖曳:選擇區域以複製或刪除。 SHIFT: 按住以放置多個。 ALT: 反向放置輸送帶。