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()),
};
const minifyNames = environment === "prod";
const minifyNames = false;
return {
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

@ -41,7 +41,7 @@ def process_image(data, outfilename, src_image):
if isWire:
targetR = 255
targetG = 104
targetB = 232
targetB = 232
for x in range(img.width):
for y in range(img.height):
@ -85,6 +85,8 @@ def generate_blueprint_sprite(infilename, outfilename):
buildings = listdir("buildings")
for buildingId in buildings:
if not ".png" in buildingId:
continue
if "hub" in buildingId:
continue
if "wire-" in buildingId:

View File

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

View File

@ -17,26 +17,32 @@
position: relative;
color: #333438;
transition: all 0.12s ease-in-out;
text-transform: uppercase;
transition-property: opacity, transform;
@include PlainText;
@include S(padding-right, 25px);
opacity: 1;
opacity: 0.8;
&:hover {
opacity: 1 !important;
opacity: 0.9 !important;
}
&.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 {
@include SuperSmallText;
@include S(font-size, 7px);
@include S(width, 150px);
@include SuperDuperSmallText;
@include S(width, 180px);
@include S(padding-right, 25px);
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/mobile_warning";
@import "states/changelog";
@import "states/puzzle_menu";
@import "ingame_hud/buildings_toolbar";
@import "ingame_hud/building_placer";
@ -59,12 +60,14 @@
@import "ingame_hud/mode_menu_next";
@import "ingame_hud/mode_menu";
@import "ingame_hud/mode_settings";
@import "ingame_hud/puzzle_dlc_logo";
// prettier-ignore
$elements:
// Base
ingame_Canvas,
ingame_VignetteOverlay,
ingame_HUD_PuzzleDLCLogo,
// Ingame overlays
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;
$plainTextLineHeight: 17px;
$supersmallTextFontSize: 10px;
$supersmallTextLineHeight: 13px;
$superDuperSmallTextFontSize: 8px;
$superDuperSmallTextLineHeight: 9px;
$superSmallTextFontSize: 10px;
$superSmallTextLineHeight: 13px;
$buttonFontSize: 14px;
$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 {
@include ScaleFont($supersmallTextFontSize, $supersmallTextLineHeight);
@include ScaleFont($superSmallTextFontSize, $superSmallTextLineHeight);
font-weight: $mainFontWeight;
font-family: $mainFont;
letter-spacing: $mainFontSpacing;

View File

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

View File

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

View File

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

View File

@ -1,58 +1,56 @@
/* typehints:start */
import { GameRoot } from "../root";
/* typehints:end */
/* dev:start */
import { TrailerMaker } from "./trailer_maker";
/* dev:end */
import { Signal } from "../../core/signal";
import { globalConfig, IS_MOBILE } from "../../core/config";
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 { HUDBuildingPlacer } from "./parts/building_placer";
import { HUDBlueprintPlacer } from "./parts/blueprint_placer";
import { HUDKeybindingOverlay } from "./parts/keybinding_overlay";
import { HUDUnlockNotification } from "./parts/unlock_notification";
import { HUDGameMenu } from "./parts/game_menu";
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 { HUDCatMemes } from "./parts/cat_memes";
import { HUDColorBlindHelper } from "./parts/color_blind_helper";
import { HUDConstantSignalEdit } from "./parts/constant_signal_edit";
import { HUDChangesDebugger } from "./parts/debug_changes";
import { HUDDebugInfo } from "./parts/debug_info";
import { HUDEntityDebugger } from "./parts/entity_debugger";
import { KEYMAPPINGS } from "../key_action_mapper";
import { HUDWatermark } from "./parts/watermark";
import { HUDModalDialogs } from "./parts/modal_dialogs";
import { HUDPartTutorialHints } from "./parts/tutorial_hints";
import { HUDWaypoints } from "./parts/waypoints";
import { HUDGameMenu } from "./parts/game_menu";
import { HUDInteractiveTutorial } from "./parts/interactive_tutorial";
import { HUDScreenshotExporter } from "./parts/screenshot_exporter";
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 { HUDKeybindingOverlay } from "./parts/keybinding_overlay";
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 { HUDBetaOverlay } from "./parts/beta_overlay";
import { HUDStandaloneAdvantages } from "./parts/standalone_advantages";
import { HUDCatMemes } from "./parts/cat_memes";
import { HUDTutorialVideoOffer } from "./parts/tutorial_video_offer";
import { HUDConstantSignalEdit } from "./parts/constant_signal_edit";
import { HUDModalDialogs } from "./parts/modal_dialogs";
import { HUDModeMenu } from "./parts/mode_menu";
import { HUDModeMenuBack } from "./parts/mode_menu_back";
import { HUDModeMenuNext } from "./parts/mode_menu_next";
import { HUDModeMenu } from "./parts/mode_menu";
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 {
/**
@ -96,6 +94,7 @@ export class GameHUD {
modeMenuNext: HUDModeMenuNext,
modeMenu: HUDModeMenu,
modeSettings: HUDModeSettings,
puzzleDlcLogo: HUDPuzzleDLCLogo,
// Must always exist
pinnedShapes: HUDPinnedShapes,

View File

@ -9,7 +9,7 @@ export class HUDModeMenuBack extends BaseHUDPart {
this.element = makeDiv(parent, "ingame_HUD_ModeMenuBack");
this.button = document.createElement("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.trackClicks(this.button, this.back);

View File

@ -9,7 +9,7 @@ export class HUDModeMenuNext extends BaseHUDPart {
this.element = makeDiv(parent, "ingame_HUD_ModeMenuNext");
this.button = document.createElement("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.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,17 +13,19 @@ export class HUDSettingsMenu extends BaseHUDPart {
this.menuElement = makeDiv(this.background, null, ["menuElement"]);
this.statsElement = makeDiv(
this.background,
null,
["statsElement"],
`
if (this.root.gameMode.hasHub()) {
this.statsElement = makeDiv(
this.background,
null,
["statsElement"],
`
<strong>${T.ingame.settingsMenu.beltsPlaced}</strong><span class="beltsPlaced"></span>
<strong>${T.ingame.settingsMenu.buildingsPlaced}</strong><span class="buildingsPlaced"></span>
<strong>${T.ingame.settingsMenu.playtime}</strong><span class="playtime"></span>
`
);
);
}
this.buttonContainer = makeDiv(this.menuElement, null, ["buttons"]);
@ -94,23 +96,25 @@ export class HUDSettingsMenu extends BaseHUDPart {
const totalMinutesPlayed = Math.ceil(this.root.time.now() / 60);
/** @type {HTMLElement} */
const playtimeElement = this.statsElement.querySelector(".playtime");
/** @type {HTMLElement} */
const buildingsPlacedElement = this.statsElement.querySelector(".buildingsPlaced");
/** @type {HTMLElement} */
const beltsPlacedElement = this.statsElement.querySelector(".beltsPlaced");
if (this.root.gameMode.hasHub()) {
/** @type {HTMLElement} */
const playtimeElement = this.statsElement.querySelector(".playtime");
/** @type {HTMLElement} */
const buildingsPlacedElement = this.statsElement.querySelector(".buildingsPlaced");
/** @type {HTMLElement} */
const beltsPlacedElement = this.statsElement.querySelector(".beltsPlaced");
playtimeElement.innerText = T.global.time.xMinutes.replace("<x>", `${totalMinutesPlayed}`);
playtimeElement.innerText = T.global.time.xMinutes.replace("<x>", `${totalMinutesPlayed}`);
buildingsPlacedElement.innerText = formatBigNumberFull(
this.root.entityMgr.getAllWithComponent(StaticMapEntityComponent).length -
buildingsPlacedElement.innerText = formatBigNumberFull(
this.root.entityMgr.getAllWithComponent(StaticMapEntityComponent).length -
this.root.entityMgr.getAllWithComponent(BeltComponent).length
);
beltsPlacedElement.innerText = formatBigNumberFull(
this.root.entityMgr.getAllWithComponent(BeltComponent).length
);
beltsPlacedElement.innerText = formatBigNumberFull(
this.root.entityMgr.getAllWithComponent(BeltComponent).length
);
);
}
}
close() {

View File

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

View File

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

View File

@ -1,11 +1,12 @@
/* typehints:start */
import { GameRoot } from "../root";
/* typehints:end */
import { globalConfig } from "../../core/config";
import { DrawParameters } from "../../core/draw_parameters";
import { ConstantSignalComponent } from "../components/constant_signal";
import { ItemProducerComponent } from "../components/item_producer";
import { GameSystemWithFilter } from "../game_system_with_filter";
import { MapChunk } from "../map_chunk";
import { GameRoot } from "../root";
export class ConstantProducerSystem extends GameSystemWithFilter {
/** @param {GameRoot} root */
@ -29,6 +30,12 @@ export class ConstantProducerSystem extends GameSystemWithFilter {
}
}
/**
*
* @param {DrawParameters} parameters
* @param {MapChunk} chunk
* @returns
*/
drawChunk(parameters, chunk) {
const contents = chunk.containedEntitiesByLayer.regular;
for (let i = 0; i < contents.length; ++i) {
@ -48,7 +55,7 @@ export class ConstantProducerSystem extends GameSystemWithFilter {
// TODO: Better looking overlay
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 = [
BOOL_FALSE_SINGLETON,
BOOL_TRUE_SINGLETON,
...Object.values(COLOR_ITEM_SINGLETONS),
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()) {
items.push(
this.root.shapeDefinitionMgr.getShapeItemFromDefinition(

View File

@ -8,6 +8,7 @@ import { KeyActionMapper } from "../game/key_action_mapper";
import { Savegame } from "../savegame/savegame";
import { GameCore } from "../game/core";
import { MUSIC } from "../platform/sound";
import { enumGameModeIds } from "../game/game_mode";
const logger = createLogger("state/ingame");
@ -150,7 +151,11 @@ export class InGameState extends GameState {
* Goes back to the menu state
*/
goBackToMenu() {
this.saveThenGoToState("MainMenuState");
if ([enumGameModeIds.puzzleEdit, enumGameModeIds.puzzlePlay].includes(this.gameModeId)) {
this.saveThenGoToState("PuzzleMenuState");
} else {
this.saveThenGoToState("MainMenuState");
}
}
/**
@ -437,6 +442,11 @@ export class InGameState extends GameState {
logger.warn("Skipping double save and returning same promise");
return this.currentSavePromise;
}
if (!this.core.root.gameMode.getIsSaveable()) {
return Promise.resolve();
}
logger.log("Starting to save game ...");
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);
if (G_IS_DEV && globalConfig.debug.fastGameEnter) {
if (globalConfig.debug.testPuzzleMode) {
this.onPuzzleEditButtonClicked();
return;
}
if (G_IS_DEV && globalConfig.debug.testPuzzleMode) {
this.onPuzzleModeButtonClicked();
return;
}
if (G_IS_DEV && globalConfig.debug.fastGameEnter) {
const games = this.app.savegameMgr.getSavegamesMetaData();
if (games.length > 0 && globalConfig.debug.resumeGameOnFastEnter) {
this.resumeGame(games[0]);
@ -369,7 +369,7 @@ export class MainMenuState extends GameState {
}
onPuzzleModeButtonClicked() {
this.renderPuzzleModeMenu();
this.moveToState("PuzzleMenuState");
}
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:
play: Play
edit: Edit
title: Puzzle Mode
createPuzzle: Create Puzzle
categories:
levels: Levels
new: New
topRated: Top Rated
myPuzzles: My Puzzles
dialogs:
buttons:
@ -259,6 +267,11 @@ dialogs:
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?
puzzleLoadFailed:
title: Puzzles failed to load
desc: >-
Unfortunately the puzzles could not be loaded:
ingame:
# This is shown in the top left corner and displays useful keybindings in
# every situation
@ -486,18 +499,18 @@ ingame:
modeMenu:
puzzleEditMode:
back:
title: Main Menu
next:
title: Menu
next:
title: Playtest
desc: You will have to complete the puzzle before being able to publish it
desc: Required for publishing
puzzleEditTestMode:
back:
back:
title: Edit
next:
next:
title: Publish
puzzlePlayMode:
back:
title: Puzzle Menu
title: Menu
next:
title: Next