Puzzle mode, part 1
@ -40,7 +40,7 @@ module.exports = ({
|
||||
G_ALL_UI_IMAGES: JSON.stringify(getAllResourceImages()),
|
||||
};
|
||||
|
||||
const minifyNames = environment === "prod";
|
||||
const minifyNames = false;
|
||||
|
||||
return {
|
||||
mode: "production",
|
||||
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 3.5 KiB |
BIN
res/ui/icons/puzzle_complete_indicator.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
res/ui/icons/puzzle_upvotes.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
res/ui/icons/state_next_button.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
res/ui/puzzle_dlc_logo.png
Normal file
After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 9.6 KiB |
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 17 KiB |
@ -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:
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
12
src/css/ingame_hud/puzzle_dlc_logo.scss
Normal 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;
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
181
src/css/states/puzzle_menu.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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";
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
13
src/js/game/hud/parts/puzzle_dlc_logo.js
Normal 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() {}
|
||||
}
|
@ -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() {
|
||||
|
@ -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;
|
||||
|
@ -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({
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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() {
|
||||
|
218
src/js/states/puzzle_menu.js
Normal 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,
|
||||
});
|
||||
}
|
||||
}
|
@ -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
|
||||
|
||||
|