1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2025-06-13 13:04:03 +00:00

Puzzle mode, part 1

This commit is contained in:
tobspr 2021-04-29 18:27:46 +02:00
parent 3318d869b3
commit 752503d892
35 changed files with 618 additions and 110 deletions

View File

@ -40,7 +40,7 @@ module.exports = ({
G_ALL_UI_IMAGES: JSON.stringify(getAllResourceImages()), G_ALL_UI_IMAGES: JSON.stringify(getAllResourceImages()),
}; };
const minifyNames = environment === "prod"; const minifyNames = false;
return { return {
mode: "production", mode: "production",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
res/ui/puzzle_dlc_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -85,6 +85,8 @@ def generate_blueprint_sprite(infilename, outfilename):
buildings = listdir("buildings") buildings = listdir("buildings")
for buildingId in buildings: for buildingId in buildings:
if not ".png" in buildingId:
continue
if "hub" in buildingId: if "hub" in buildingId:
continue continue
if "wire-" in buildingId: if "wire-" in buildingId:

View File

@ -18,18 +18,23 @@
color: #333438; color: #333438;
transition: all 0.12s ease-in-out; transition: all 0.12s ease-in-out;
transition-property: opacity, transform; transition-property: opacity, transform;
text-transform: uppercase;
@include PlainText;
opacity: 0.8; opacity: 1;
&:hover { &:hover {
opacity: 1 !important; opacity: 0.9 !important;
} }
&.pressed { &.pressed {
transform: scale(0.9) !important; transform: scale(0.95) !important;
} }
@include DarkThemeOverride { @include S(padding-left, 25px);
color: #fff;
& {
/* @load-async */
background: uiResource("icons/state_back_button.png") left center / D(15px) no-repeat;
} }
} }
} }

View File

@ -17,26 +17,32 @@
position: relative; position: relative;
color: #333438; color: #333438;
transition: all 0.12s ease-in-out; transition: all 0.12s ease-in-out;
text-transform: uppercase;
transition-property: opacity, transform; transition-property: opacity, transform;
@include PlainText;
@include S(padding-right, 25px);
opacity: 1;
opacity: 0.8;
&:hover { &:hover {
opacity: 1 !important; opacity: 0.9 !important;
} }
&.pressed { &.pressed {
transform: scale(0.9) !important; transform: scale(0.95) !important;
} }
@include DarkThemeOverride { & {
color: #fff; /* @load-async */
background: uiResource("icons/state_next_button.png") right center / D(15px) no-repeat;
} }
} }
> .content { > .content {
@include SuperSmallText; @include SuperDuperSmallText;
@include S(font-size, 7px); @include S(width, 180px);
@include S(width, 150px); @include S(padding-right, 25px);
text-align: right; text-align: right;
text-transform: uppercase;
color: $accentColorDark;
} }
} }

View File

@ -0,0 +1,12 @@
#ingame_HUD_PuzzleDLCLogo {
position: absolute;
@include S(width, 150px);
@include S(height, 40px);
@include S(bottom, 10px);
@include S(right, 15px);
& {
/* @load-async */
background: uiResource("puzzle_dlc_logo.png") center center / contain no-repeat;
}
}

View File

@ -29,6 +29,7 @@
@import "states/about"; @import "states/about";
@import "states/mobile_warning"; @import "states/mobile_warning";
@import "states/changelog"; @import "states/changelog";
@import "states/puzzle_menu";
@import "ingame_hud/buildings_toolbar"; @import "ingame_hud/buildings_toolbar";
@import "ingame_hud/building_placer"; @import "ingame_hud/building_placer";
@ -59,12 +60,14 @@
@import "ingame_hud/mode_menu_next"; @import "ingame_hud/mode_menu_next";
@import "ingame_hud/mode_menu"; @import "ingame_hud/mode_menu";
@import "ingame_hud/mode_settings"; @import "ingame_hud/mode_settings";
@import "ingame_hud/puzzle_dlc_logo";
// prettier-ignore // prettier-ignore
$elements: $elements:
// Base // Base
ingame_Canvas, ingame_Canvas,
ingame_VignetteOverlay, ingame_VignetteOverlay,
ingame_HUD_PuzzleDLCLogo,
// Ingame overlays // Ingame overlays
ingame_HUD_Waypoints, ingame_HUD_Waypoints,

View File

@ -0,0 +1,181 @@
#state_PuzzleMenuState {
> .headerBar {
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
}
> .container {
> .mainContent {
overflow: hidden;
> .categoryChooser {
display: grid;
grid-auto-columns: 1fr;
grid-auto-flow: column;
@include S(grid-gap, 2px);
@include S(padding-right, 10px);
> .category {
background: $accentColorBright;
border-radius: 0;
color: $accentColorDark;
transition: all 0.12s ease-in-out;
transition-property: opacity, background-color, color;
&:first-child {
@include S(border-top-left-radius, $globalBorderRadius);
@include S(border-bottom-left-radius, $globalBorderRadius);
}
&:last-child {
border-top-right-radius: $globalBorderRadius;
border-bottom-right-radius: $globalBorderRadius;
}
&.active {
background: $colorBlueBright;
opacity: 1 !important;
color: #fff;
cursor: default;
}
}
}
> .puzzles {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
@include S(grid-auto-rows, 120px);
@include S(grid-gap, 3px);
@include S(grid-auto-columns, 1fr);
@include S(margin-top, 10px);
@include S(padding-right, 4px);
@include S(height, 360px);
overflow-y: scroll;
pointer-events: all;
> .puzzle {
width: 100%;
@include S(height, 120px);
background: #f3f3f8;
@include S(border-radius, $globalBorderRadius);
display: grid;
grid-template-columns: 1fr auto;
grid-template-rows: D(15px) 1fr auto;
@include S(padding, 5px);
@include S(grid-column-gap, 5px);
box-sizing: border-box;
pointer-events: all;
cursor: pointer;
position: relative;
@include InlineAnimation(0.12s ease-in-out) {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
&:hover {
background: #f0f0f8;
}
> .title {
grid-column: 1 / 2;
grid-row: 1/ 2;
@include PlainText;
}
> .icon {
grid-column: 1 / 3;
grid-row: 2 / 3;
align-self: center;
justify-self: center;
@include S(width, 70px);
@include S(height, 70px);
canvas {
width: 100%;
height: 100%;
}
}
> .author {
grid-column: 1 / 2;
grid-row: 3 / 4;
@include SuperSmallText;
color: $accentColorDark;
align-self: end;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
> .playcount {
grid-column: 1 / 2;
display: none;
grid-row: 3 / 4;
@include SuperSmallText;
}
> .upvotes {
@include SuperSmallText;
grid-column: 2 / 3;
grid-row: 3 / 4;
color: $accentColorDark;
align-self: end;
justify-self: end;
font-weight: bold;
@include S(padding-right, 12px);
& {
/* @load-async */
background: uiResource("icons/puzzle_upvotes.png") calc(100% - #{D(2px)}) #{D(
3.3px
)} / #{D(8px)} #{D(8px)} no-repeat;
}
}
&.completed {
.icon,
.upvotes,
.playcount,
.author,
.title {
opacity: 0.5;
}
background: #fafafa;
&::after {
content: "";
position: absolute;
@include S(top, 10px);
@include S(right, 10px);
@include S(width, 30px);
@include S(height, 30px);
opacity: 0.1;
& {
/* @load-async */
background: uiResource("icons/puzzle_complete_indicator.png") center center /
contain no-repeat;
}
}
}
}
> .loader {
grid-column: 1 / -1;
grid-row: 1 / 3;
display: flex;
align-items: center;
color: $accentColorBright;
justify-content: center;
}
}
}
}
}

View File

@ -18,8 +18,10 @@ $textLineHeight: 21px;
$plainTextFontSize: 13px; $plainTextFontSize: 13px;
$plainTextLineHeight: 17px; $plainTextLineHeight: 17px;
$supersmallTextFontSize: 10px; $superDuperSmallTextFontSize: 8px;
$supersmallTextLineHeight: 13px; $superDuperSmallTextLineHeight: 9px;
$superSmallTextFontSize: 10px;
$superSmallTextLineHeight: 13px;
$buttonFontSize: 14px; $buttonFontSize: 14px;
$buttonLineHeight: 18px; $buttonLineHeight: 18px;
@ -76,8 +78,16 @@ $mainFontScale: 1;
// } // }
} }
@mixin SuperDuperSmallText {
@include ScaleFont($superDuperSmallTextFontSize, $superDuperSmallTextLineHeight);
font-weight: $mainFontWeight;
font-family: $mainFont;
letter-spacing: $mainFontSpacing;
@include DebugText(green);
}
@mixin SuperSmallText { @mixin SuperSmallText {
@include ScaleFont($supersmallTextFontSize, $supersmallTextLineHeight); @include ScaleFont($superSmallTextFontSize, $superSmallTextLineHeight);
font-weight: $mainFontWeight; font-weight: $mainFontWeight;
font-family: $mainFont; font-family: $mainFont;
letter-spacing: $mainFontSpacing; letter-spacing: $mainFontSpacing;

View File

@ -31,6 +31,7 @@ import { PreloadState } from "./states/preload";
import { SettingsState } from "./states/settings"; import { SettingsState } from "./states/settings";
import { ShapezGameAnalytics } from "./platform/browser/game_analytics"; import { ShapezGameAnalytics } from "./platform/browser/game_analytics";
import { RestrictionManager } from "./core/restriction_manager"; import { RestrictionManager } from "./core/restriction_manager";
import { PuzzleMenuState } from "./states/puzzle_menu";
/** /**
* @typedef {import("./platform/achievement_provider").AchievementProviderInterface} AchievementProviderInterface * @typedef {import("./platform/achievement_provider").AchievementProviderInterface} AchievementProviderInterface
@ -159,6 +160,7 @@ export class Application {
KeybindingsState, KeybindingsState,
AboutState, AboutState,
ChangelogState, ChangelogState,
PuzzleMenuState,
]; ];
for (let i = 0; i < states.length; ++i) { for (let i = 0; i < states.length; ++i) {

View File

@ -474,6 +474,7 @@ export class Camera extends BasicSerializableObject {
// Clamp everything afterwards // Clamp everything afterwards
this.clampZoomLevel(); this.clampZoomLevel();
this.clampToBounds();
return false; return false;
} }
@ -759,18 +760,16 @@ export class Camera extends BasicSerializableObject {
} }
/** /**
* Clamps x, y position within set boundaries * Clamps the center within set boundaries
* @param {Vector} vector
*/ */
clampToBounds(vector) { clampToBounds() {
if (!this.root.gameMode.hasBounds()) { if (!this.root.gameMode.hasBounds()) {
return; return;
} }
const bounds = this.root.gameMode.getBounds().allScaled(globalConfig.tileSize); const bounds = this.root.gameMode.getBounds().allScaled(globalConfig.tileSize);
this.center.x = clamp(this.center.x, bounds.x, bounds.x + bounds.w);
vector.x = clamp(vector.x, bounds.x, bounds.x + bounds.w); this.center.y = clamp(this.center.y, bounds.y, bounds.y + bounds.h);
vector.y = clamp(vector.y, bounds.y, bounds.y + bounds.h);
} }
/** /**
@ -876,7 +875,7 @@ export class Camera extends BasicSerializableObject {
// Panning // Panning
this.currentPan = mixVector(this.currentPan, this.desiredPan, 0.06); this.currentPan = mixVector(this.currentPan, this.desiredPan, 0.06);
this.center = this.center.add(this.currentPan.multiplyScalar((0.5 * dt) / this.zoomLevel)); this.center = this.center.add(this.currentPan.multiplyScalar((0.5 * dt) / this.zoomLevel));
this.clampToBounds(this.center); this.clampToBounds();
} }
} }
@ -942,7 +941,7 @@ export class Camera extends BasicSerializableObject {
) )
); );
this.clampToBounds(this.center); this.clampToBounds();
} }
/** /**
@ -1029,7 +1028,7 @@ export class Camera extends BasicSerializableObject {
this.center.x += moveAmount * forceX * movementSpeed; this.center.x += moveAmount * forceX * movementSpeed;
this.center.y += moveAmount * forceY * movementSpeed; this.center.y += moveAmount * forceY * movementSpeed;
this.clampToBounds(this.center); this.clampToBounds();
} }
} }
} }

View File

@ -177,6 +177,16 @@ export class GameMode extends BasicSerializableObject {
return false; return false;
} }
/** @returns {boolean} */
getIsSaveable() {
return true;
}
/** @returns {boolean} */
getSupportsCopyPaste() {
return true;
}
/** @returns {string} */ /** @returns {string} */
getBlueprintShapeKey() { getBlueprintShapeKey() {
return "CbCbCbRb:CwCwCwCw"; return "CbCbCbRb:CwCwCwCw";

View File

@ -1,58 +1,56 @@
/* typehints:start */ /* typehints:start */
import { GameRoot } from "../root"; import { globalConfig, IS_MOBILE } from "../../core/config";
/* typehints:end */
/* dev:start */
import { TrailerMaker } from "./trailer_maker";
/* dev:end */
import { Signal } from "../../core/signal";
import { DrawParameters } from "../../core/draw_parameters"; import { DrawParameters } from "../../core/draw_parameters";
/* dev:end */
import { Signal } from "../../core/signal";
import { KEYMAPPINGS } from "../key_action_mapper";
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 { HUDBuildingsToolbar } from "./parts/buildings_toolbar";
import { HUDBuildingPlacer } from "./parts/building_placer"; import { HUDBuildingPlacer } from "./parts/building_placer";
import { HUDBlueprintPlacer } from "./parts/blueprint_placer"; import { HUDCatMemes } from "./parts/cat_memes";
import { HUDKeybindingOverlay } from "./parts/keybinding_overlay"; import { HUDColorBlindHelper } from "./parts/color_blind_helper";
import { HUDUnlockNotification } from "./parts/unlock_notification"; import { HUDConstantSignalEdit } from "./parts/constant_signal_edit";
import { HUDGameMenu } from "./parts/game_menu"; import { HUDChangesDebugger } from "./parts/debug_changes";
import { HUDShop } from "./parts/shop";
import { IS_MOBILE, globalConfig } from "../../core/config";
import { HUDMassSelector } from "./parts/mass_selector";
import { HUDVignetteOverlay } from "./parts/vignette_overlay";
import { HUDStatistics } from "./parts/statistics";
import { MetaBuilding } from "../meta_building";
import { HUDPinnedShapes } from "./parts/pinned_shapes";
import { ShapeDefinition } from "../shape_definition";
import { HUDNotifications, enumNotificationType } from "./parts/notifications";
import { HUDSettingsMenu } from "./parts/settings_menu";
import { HUDDebugInfo } from "./parts/debug_info"; import { HUDDebugInfo } from "./parts/debug_info";
import { HUDEntityDebugger } from "./parts/entity_debugger"; import { HUDEntityDebugger } from "./parts/entity_debugger";
import { KEYMAPPINGS } from "../key_action_mapper"; import { HUDGameMenu } from "./parts/game_menu";
import { HUDWatermark } from "./parts/watermark";
import { HUDModalDialogs } from "./parts/modal_dialogs";
import { HUDPartTutorialHints } from "./parts/tutorial_hints";
import { HUDWaypoints } from "./parts/waypoints";
import { HUDInteractiveTutorial } from "./parts/interactive_tutorial"; import { HUDInteractiveTutorial } from "./parts/interactive_tutorial";
import { HUDScreenshotExporter } from "./parts/screenshot_exporter"; import { HUDKeybindingOverlay } from "./parts/keybinding_overlay";
import { HUDColorBlindHelper } from "./parts/color_blind_helper";
import { HUDShapeViewer } from "./parts/shape_viewer";
import { HUDWiresOverlay } from "./parts/wires_overlay";
import { HUDChangesDebugger } from "./parts/debug_changes";
import { queryParamOptions } from "../../core/query_parameters";
import { HUDSandboxController } from "./parts/sandbox_controller";
import { HUDWiresToolbar } from "./parts/wires_toolbar";
import { HUDWireInfo } from "./parts/wire_info";
import { HUDLeverToggle } from "./parts/lever_toggle";
import { HUDLayerPreview } from "./parts/layer_preview"; import { HUDLayerPreview } from "./parts/layer_preview";
import { HUDLeverToggle } from "./parts/lever_toggle";
import { HUDMassSelector } from "./parts/mass_selector";
import { HUDMinerHighlight } from "./parts/miner_highlight"; import { HUDMinerHighlight } from "./parts/miner_highlight";
import { HUDBetaOverlay } from "./parts/beta_overlay"; import { HUDModalDialogs } from "./parts/modal_dialogs";
import { HUDStandaloneAdvantages } from "./parts/standalone_advantages"; import { HUDModeMenu } from "./parts/mode_menu";
import { HUDCatMemes } from "./parts/cat_memes";
import { HUDTutorialVideoOffer } from "./parts/tutorial_video_offer";
import { HUDConstantSignalEdit } from "./parts/constant_signal_edit";
import { HUDModeMenuBack } from "./parts/mode_menu_back"; import { HUDModeMenuBack } from "./parts/mode_menu_back";
import { HUDModeMenuNext } from "./parts/mode_menu_next"; import { HUDModeMenuNext } from "./parts/mode_menu_next";
import { HUDModeMenu } from "./parts/mode_menu";
import { HUDModeSettings } from "./parts/mode_settings"; import { HUDModeSettings } from "./parts/mode_settings";
import { enumNotificationType, HUDNotifications } from "./parts/notifications";
import { HUDPinnedShapes } from "./parts/pinned_shapes";
import { HUDPuzzleDLCLogo } from "./parts/puzzle_dlc_logo";
import { HUDSandboxController } from "./parts/sandbox_controller";
import { HUDScreenshotExporter } from "./parts/screenshot_exporter";
import { HUDSettingsMenu } from "./parts/settings_menu";
import { HUDShapeViewer } from "./parts/shape_viewer";
import { HUDShop } from "./parts/shop";
import { HUDStandaloneAdvantages } from "./parts/standalone_advantages";
import { HUDStatistics } from "./parts/statistics";
import { HUDPartTutorialHints } from "./parts/tutorial_hints";
import { HUDTutorialVideoOffer } from "./parts/tutorial_video_offer";
import { HUDUnlockNotification } from "./parts/unlock_notification";
import { HUDVignetteOverlay } from "./parts/vignette_overlay";
import { HUDWatermark } from "./parts/watermark";
import { HUDWaypoints } from "./parts/waypoints";
import { HUDWiresOverlay } from "./parts/wires_overlay";
import { HUDWiresToolbar } from "./parts/wires_toolbar";
import { HUDWireInfo } from "./parts/wire_info";
/* typehints:end */
/* dev:start */
import { TrailerMaker } from "./trailer_maker";
export class GameHUD { export class GameHUD {
/** /**
@ -96,6 +94,7 @@ export class GameHUD {
modeMenuNext: HUDModeMenuNext, modeMenuNext: HUDModeMenuNext,
modeMenu: HUDModeMenu, modeMenu: HUDModeMenu,
modeSettings: HUDModeSettings, modeSettings: HUDModeSettings,
puzzleDlcLogo: HUDPuzzleDLCLogo,
// Must always exist // Must always exist
pinnedShapes: HUDPinnedShapes, pinnedShapes: HUDPinnedShapes,

View File

@ -9,7 +9,7 @@ export class HUDModeMenuBack extends BaseHUDPart {
this.element = makeDiv(parent, "ingame_HUD_ModeMenuBack"); this.element = makeDiv(parent, "ingame_HUD_ModeMenuBack");
this.button = document.createElement("button"); this.button = document.createElement("button");
this.button.classList.add("button"); this.button.classList.add("button");
this.button.textContent = "⬅ " + T.ingame.modeMenu[key].back.title; this.button.textContent = T.ingame.modeMenu[key].back.title;
this.element.appendChild(this.button); this.element.appendChild(this.button);
this.trackClicks(this.button, this.back); this.trackClicks(this.button, this.back);

View File

@ -9,7 +9,7 @@ export class HUDModeMenuNext extends BaseHUDPart {
this.element = makeDiv(parent, "ingame_HUD_ModeMenuNext"); this.element = makeDiv(parent, "ingame_HUD_ModeMenuNext");
this.button = document.createElement("button"); this.button = document.createElement("button");
this.button.classList.add("button"); this.button.classList.add("button");
this.button.textContent = T.ingame.modeMenu[key].next.title + " ➡ "; this.button.textContent = T.ingame.modeMenu[key].next.title;
this.element.appendChild(this.button); this.element.appendChild(this.button);
this.content = makeDiv(this.element, null, ["content"], T.ingame.modeMenu[key].next.desc); this.content = makeDiv(this.element, null, ["content"], T.ingame.modeMenu[key].next.desc);

View File

@ -0,0 +1,13 @@
import { makeDiv } from "../../../core/utils";
import { BaseHUDPart } from "../base_hud_part";
export class HUDPuzzleDLCLogo extends BaseHUDPart {
createElements(parent) {
this.element = makeDiv(parent, "ingame_HUD_PuzzleDLCLogo");
parent.appendChild(this.element);
}
initialize() {}
next() {}
}

View File

@ -13,6 +13,7 @@ export class HUDSettingsMenu extends BaseHUDPart {
this.menuElement = makeDiv(this.background, null, ["menuElement"]); this.menuElement = makeDiv(this.background, null, ["menuElement"]);
if (this.root.gameMode.hasHub()) {
this.statsElement = makeDiv( this.statsElement = makeDiv(
this.background, this.background,
null, null,
@ -24,6 +25,7 @@ export class HUDSettingsMenu extends BaseHUDPart {
` `
); );
}
this.buttonContainer = makeDiv(this.menuElement, null, ["buttons"]); this.buttonContainer = makeDiv(this.menuElement, null, ["buttons"]);
@ -94,6 +96,7 @@ export class HUDSettingsMenu extends BaseHUDPart {
const totalMinutesPlayed = Math.ceil(this.root.time.now() / 60); const totalMinutesPlayed = Math.ceil(this.root.time.now() / 60);
if (this.root.gameMode.hasHub()) {
/** @type {HTMLElement} */ /** @type {HTMLElement} */
const playtimeElement = this.statsElement.querySelector(".playtime"); const playtimeElement = this.statsElement.querySelector(".playtime");
/** @type {HTMLElement} */ /** @type {HTMLElement} */
@ -112,6 +115,7 @@ export class HUDSettingsMenu extends BaseHUDPart {
this.root.entityMgr.getAllWithComponent(BeltComponent).length this.root.entityMgr.getAllWithComponent(BeltComponent).length
); );
} }
}
close() { close() {
this.visible = false; this.visible = false;

View File

@ -11,6 +11,7 @@ import { HUDKeybindingOverlay } from "../hud/parts/keybinding_overlay";
import { HUDPartTutorialHints } from "../hud/parts/tutorial_hints"; import { HUDPartTutorialHints } from "../hud/parts/tutorial_hints";
import { HUDPinnedShapes } from "../hud/parts/pinned_shapes"; import { HUDPinnedShapes } from "../hud/parts/pinned_shapes";
import { HUDWaypoints } from "../hud/parts/waypoints"; import { HUDWaypoints } from "../hud/parts/waypoints";
import { HUDMassSelector } from "../hud/parts/mass_selector";
export class PuzzleGameMode extends GameMode { export class PuzzleGameMode extends GameMode {
static getType() { static getType() {
@ -33,6 +34,7 @@ export class PuzzleGameMode extends GameMode {
this.setHudParts({ this.setHudParts({
[HUDGameMenu.name]: false, [HUDGameMenu.name]: false,
[HUDMassSelector.name]: false,
[HUDInteractiveTutorial.name]: false, [HUDInteractiveTutorial.name]: false,
[HUDKeybindingOverlay.name]: false, [HUDKeybindingOverlay.name]: false,
[HUDPartTutorialHints.name]: false, [HUDPartTutorialHints.name]: false,
@ -122,6 +124,14 @@ export class PuzzleGameMode extends GameMode {
return 1; return 1;
} }
getIsSaveable() {
return false;
}
getSupportsCopyPaste() {
return false;
}
/** @returns {boolean} */ /** @returns {boolean} */
getIsFreeplayAvailable() { getIsFreeplayAvailable() {
return true; return true;

View File

@ -14,6 +14,7 @@ import { HUDModeSettings } from "../hud/parts/mode_settings";
import { enumGameModeIds, enumGameModeTypes, GameMode } from "../game_mode"; import { enumGameModeIds, enumGameModeTypes, GameMode } from "../game_mode";
import { ShapeDefinition } from "../shape_definition"; import { ShapeDefinition } from "../shape_definition";
import { enumHubGoalRewards } from "../tutorial_goals"; import { enumHubGoalRewards } from "../tutorial_goals";
import { HUDPuzzleDLCLogo } from "../hud/parts/puzzle_dlc_logo";
/** @typedef {{ /** @typedef {{
* shape: string, * shape: string,
@ -522,6 +523,7 @@ export class RegularGameMode extends GameMode {
[HUDModeMenuNext.name]: false, [HUDModeMenuNext.name]: false,
[HUDModeMenu.name]: false, [HUDModeMenu.name]: false,
[HUDModeSettings.name]: false, [HUDModeSettings.name]: false,
[HUDPuzzleDLCLogo.name]: false,
}); });
this.setBuildings({ this.setBuildings({

View File

@ -1,11 +1,12 @@
/* typehints:start */ /* typehints:start */
import { GameRoot } from "../root";
/* typehints:end */ /* typehints:end */
import { globalConfig } from "../../core/config"; import { globalConfig } from "../../core/config";
import { DrawParameters } from "../../core/draw_parameters";
import { ConstantSignalComponent } from "../components/constant_signal"; import { ConstantSignalComponent } from "../components/constant_signal";
import { ItemProducerComponent } from "../components/item_producer"; import { ItemProducerComponent } from "../components/item_producer";
import { GameSystemWithFilter } from "../game_system_with_filter"; import { GameSystemWithFilter } from "../game_system_with_filter";
import { MapChunk } from "../map_chunk";
import { GameRoot } from "../root";
export class ConstantProducerSystem extends GameSystemWithFilter { export class ConstantProducerSystem extends GameSystemWithFilter {
/** @param {GameRoot} root */ /** @param {GameRoot} root */
@ -29,6 +30,12 @@ export class ConstantProducerSystem extends GameSystemWithFilter {
} }
} }
/**
*
* @param {DrawParameters} parameters
* @param {MapChunk} chunk
* @returns
*/
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) {
@ -48,7 +55,7 @@ export class ConstantProducerSystem extends GameSystemWithFilter {
// TODO: Better looking overlay // TODO: Better looking overlay
const center = staticComp.getTileSpaceBounds().getCenter().toWorldSpace(); const center = staticComp.getTileSpaceBounds().getCenter().toWorldSpace();
item.drawItemCenteredClipped(center.x, center.y, parameters, globalConfig.tileSize); item.drawItemCenteredClipped(center.x, center.y + 1, parameters, globalConfig.tileSize * 0.65);
} }
} }
} }

View File

@ -61,12 +61,14 @@ export class ConstantSignalSystem extends GameSystemWithFilter {
}); });
const items = [ const items = [
BOOL_FALSE_SINGLETON,
BOOL_TRUE_SINGLETON,
...Object.values(COLOR_ITEM_SINGLETONS), ...Object.values(COLOR_ITEM_SINGLETONS),
this.root.shapeDefinitionMgr.getShapeItemFromShortKey(this.root.gameMode.getBlueprintShapeKey()), this.root.shapeDefinitionMgr.getShapeItemFromShortKey(this.root.gameMode.getBlueprintShapeKey()),
]; ];
if (entity.components.ConstantSignal.type === enumConstantSignalType.wired) {
items.unshift(BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON);
}
if (this.root.gameMode.hasHub()) { if (this.root.gameMode.hasHub()) {
items.push( items.push(
this.root.shapeDefinitionMgr.getShapeItemFromDefinition( this.root.shapeDefinitionMgr.getShapeItemFromDefinition(

View File

@ -8,6 +8,7 @@ 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";
import { enumGameModeIds } from "../game/game_mode";
const logger = createLogger("state/ingame"); const logger = createLogger("state/ingame");
@ -150,8 +151,12 @@ export class InGameState extends GameState {
* Goes back to the menu state * Goes back to the menu state
*/ */
goBackToMenu() { goBackToMenu() {
if ([enumGameModeIds.puzzleEdit, enumGameModeIds.puzzlePlay].includes(this.gameModeId)) {
this.saveThenGoToState("PuzzleMenuState");
} else {
this.saveThenGoToState("MainMenuState"); this.saveThenGoToState("MainMenuState");
} }
}
/** /**
* Goes back to the settings state * Goes back to the settings state
@ -437,6 +442,11 @@ export class InGameState extends GameState {
logger.warn("Skipping double save and returning same promise"); logger.warn("Skipping double save and returning same promise");
return this.currentSavePromise; return this.currentSavePromise;
} }
if (!this.core.root.gameMode.getIsSaveable()) {
return Promise.resolve();
}
logger.log("Starting to save game ..."); logger.log("Starting to save game ...");
this.savegame.updateData(this.core.root); this.savegame.updateData(this.core.root);

View File

@ -207,12 +207,12 @@ export class MainMenuState extends GameState {
const qs = this.htmlElement.querySelector.bind(this.htmlElement); const qs = this.htmlElement.querySelector.bind(this.htmlElement);
if (G_IS_DEV && globalConfig.debug.fastGameEnter) { if (G_IS_DEV && globalConfig.debug.testPuzzleMode) {
if (globalConfig.debug.testPuzzleMode) { this.onPuzzleModeButtonClicked();
this.onPuzzleEditButtonClicked();
return; return;
} }
if (G_IS_DEV && globalConfig.debug.fastGameEnter) {
const games = this.app.savegameMgr.getSavegamesMetaData(); const games = this.app.savegameMgr.getSavegamesMetaData();
if (games.length > 0 && globalConfig.debug.resumeGameOnFastEnter) { if (games.length > 0 && globalConfig.debug.resumeGameOnFastEnter) {
this.resumeGame(games[0]); this.resumeGame(games[0]);
@ -369,7 +369,7 @@ export class MainMenuState extends GameState {
} }
onPuzzleModeButtonClicked() { onPuzzleModeButtonClicked() {
this.renderPuzzleModeMenu(); this.moveToState("PuzzleMenuState");
} }
onBackButtonClicked() { onBackButtonClicked() {

View File

@ -0,0 +1,218 @@
import { TextualGameState } from "../core/textual_game_state";
import { formatBigNumberFull } from "../core/utils";
import { enumGameModeIds } from "../game/game_mode";
import { ShapeDefinition } from "../game/shape_definition";
import { T } from "../translations";
const categories = ["levels", "new", "topRated", "myPuzzles"];
/**
* @typedef {{
* shortKey: string;
* upvotes: number;
* playcount: number;
* title: string;
* author: string;
* completed: boolean;
* }} PuzzleMetadata
*/
const SAMPLE_PUZZLE = {
shortKey: "CuCuCuCu",
upvotes: 10000,
playcount: 1000,
title: "Level 1",
author: "verylongsteamnamewhichbreaks",
completed: false,
};
const BUILTIN_PUZZLES = [
{ ...SAMPLE_PUZZLE, completed: true },
{ ...SAMPLE_PUZZLE, completed: true },
SAMPLE_PUZZLE,
SAMPLE_PUZZLE,
SAMPLE_PUZZLE,
SAMPLE_PUZZLE,
SAMPLE_PUZZLE,
SAMPLE_PUZZLE,
SAMPLE_PUZZLE,
SAMPLE_PUZZLE,
SAMPLE_PUZZLE,
SAMPLE_PUZZLE,
SAMPLE_PUZZLE,
];
export class PuzzleMenuState extends TextualGameState {
constructor() {
super("PuzzleMenuState");
this.loading = false;
this.activeCategory = "";
}
getStateHeaderTitle() {
return T.puzzleMenu.title;
}
/**
* Overrides the GameState implementation to provide our own html
*/
internalGetFullHtml() {
let headerHtml = `
<div class="headerBar">
<h1><button class="backButton"></button> ${this.getStateHeaderTitle()}</h1>
<div class="actions">
<button class="styledButton createPuzzle">+ ${T.puzzleMenu.createPuzzle}</button>
</div>
</div>`;
return `
${headerHtml}
<div class="container">
${this.getInnerHTML()}
</div>
`;
}
getMainContentHTML() {
let html = `
<div class="categoryChooser">
${categories
.map(
category => `
<button data-category="${category}" class="styledButton category">${T.puzzleMenu.categories[category]}</button>
`
)
.join("")}
</div>
<div class="puzzles" id="mainContainer">
<div class="puzzle"></div>
<div class="puzzle"></div>
<div class="puzzle"></div>
<div class="puzzle"></div>
</div>
`;
return html;
}
selectCategory(category) {
if (category === this.activeCategory) {
return;
}
if (this.loading) {
return;
}
this.loading = true;
this.activeCategory = category;
const activeCategory = this.htmlElement.querySelector(".active[data-category]");
if (activeCategory) {
activeCategory.classList.remove("active");
}
this.htmlElement.querySelector(`[data-category="${category}"]`).classList.add("active");
const container = this.htmlElement.querySelector("#mainContainer");
while (container.firstChild) {
container.removeChild(container.firstChild);
}
const loadingElement = document.createElement("div");
loadingElement.classList.add("loader");
loadingElement.innerText = T.global.loading + "...";
container.appendChild(loadingElement);
this.asyncChannel
.watch(this.getPuzzlesForCategory(category))
.then(
puzzles => this.renderPuzzles(puzzles),
error => {
this.dialogs.showWarning(
T.dialogs.puzzleLoadFailed.title,
T.dialogs.puzzleLoadFailed.desc + " " + error
);
}
)
.then(() => (this.loading = false));
}
/**
*
* @param {PuzzleMetadata[]} puzzles
*/
renderPuzzles(puzzles) {
const container = this.htmlElement.querySelector("#mainContainer");
while (container.firstChild) {
container.removeChild(container.firstChild);
}
for (const puzzle of puzzles) {
const elem = document.createElement("div");
elem.classList.add("puzzle");
elem.classList.toggle("completed", puzzle.completed);
if (puzzle.title) {
const title = document.createElement("div");
title.classList.add("title");
title.innerText = puzzle.title;
elem.appendChild(title);
}
if (puzzle.author) {
const author = document.createElement("div");
author.classList.add("author");
author.innerText = "by " + puzzle.author;
elem.appendChild(author);
}
if (puzzle.upvotes) {
const upvotes = document.createElement("div");
upvotes.classList.add("upvotes");
upvotes.innerText = formatBigNumberFull(puzzle.upvotes);
elem.appendChild(upvotes);
}
if (puzzle.playcount) {
const playcount = document.createElement("div");
playcount.classList.add("playcount");
playcount.innerText = String(puzzle.playcount) + " plays";
elem.appendChild(playcount);
}
const definition = ShapeDefinition.fromShortKey(puzzle.shortKey);
const canvas = definition.generateAsCanvas(100 * this.app.getEffectiveUiScale());
const icon = document.createElement("div");
icon.classList.add("icon");
icon.appendChild(canvas);
elem.appendChild(icon);
container.appendChild(elem);
}
}
getPuzzlesForCategory(category) {
return new Promise(resolve => setTimeout(() => resolve(BUILTIN_PUZZLES), 100));
}
onEnter() {
this.selectCategory("levels");
for (const category of categories) {
const button = this.htmlElement.querySelector(`[data-category="${category}"]`);
this.trackClicks(button, () => this.selectCategory(category));
}
this.trackClicks(this.htmlElement.querySelector("button.createPuzzle"), this.createNewPuzzle);
}
createNewPuzzle() {
const savegame = this.app.savegameMgr.createNewSavegame();
this.moveToState("InGameState", {
gameModeId: enumGameModeIds.puzzleEdit,
savegame,
});
}
}

View File

@ -122,6 +122,14 @@ mainMenu:
puzzleMenu: puzzleMenu:
play: Play play: Play
edit: Edit edit: Edit
title: Puzzle Mode
createPuzzle: Create Puzzle
categories:
levels: Levels
new: New
topRated: Top Rated
myPuzzles: My Puzzles
dialogs: dialogs:
buttons: buttons:
@ -259,6 +267,11 @@ dialogs:
title: Tutorial Available title: Tutorial Available
desc: There is a tutorial video available for this level, but it is only available in English. Would you like to watch it? desc: There is a tutorial video available for this level, but it is only available in English. Would you like to watch it?
puzzleLoadFailed:
title: Puzzles failed to load
desc: >-
Unfortunately the puzzles could not be loaded:
ingame: ingame:
# This is shown in the top left corner and displays useful keybindings in # This is shown in the top left corner and displays useful keybindings in
# every situation # every situation
@ -486,10 +499,10 @@ ingame:
modeMenu: modeMenu:
puzzleEditMode: puzzleEditMode:
back: back:
title: Main Menu title: Menu
next: next:
title: Playtest title: Playtest
desc: You will have to complete the puzzle before being able to publish it desc: Required for publishing
puzzleEditTestMode: puzzleEditTestMode:
back: back:
title: Edit title: Edit
@ -497,7 +510,7 @@ ingame:
title: Publish title: Publish
puzzlePlayMode: puzzlePlayMode:
back: back:
title: Puzzle Menu title: Menu
next: next:
title: Next title: Next