Puzzle mode, almost done
Before Width: | Height: | Size: 271 KiB After Width: | Height: | Size: 129 KiB |
Before Width: | Height: | Size: 271 KiB After Width: | Height: | Size: 100 KiB |
BIN
res/ui/icons/puzzle_complete_indicator_inverse.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
res/ui/puzzle_dlc_logo_inverse.png
Normal file
After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 25 KiB |
@ -22,6 +22,8 @@
|
|||||||
@include S(width, 30px);
|
@include S(width, 30px);
|
||||||
@include S(height, 30px);
|
@include S(height, 30px);
|
||||||
|
|
||||||
|
@include DarkThemeInvert;
|
||||||
|
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 0.9 !important;
|
opacity: 0.9 !important;
|
||||||
|
@ -60,7 +60,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.contents {
|
> .contents {
|
||||||
@include S(width, 400px);
|
@include S(width, 400px);
|
||||||
@include S(height, 170px);
|
@include S(height, 170px);
|
||||||
@include InlineAnimation(0.5s ease-in-out) {
|
@include InlineAnimation(0.5s ease-in-out) {
|
||||||
@ -94,7 +94,6 @@
|
|||||||
> button {
|
> button {
|
||||||
@include S(width, 40px);
|
@include S(width, 40px);
|
||||||
@include S(height, 40px);
|
@include S(height, 40px);
|
||||||
background: green;
|
|
||||||
@include S(margin, 0, 10px);
|
@include S(margin, 0, 10px);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@include S(border-radius, $globalBorderRadius);
|
@include S(border-radius, $globalBorderRadius);
|
||||||
@ -136,16 +135,31 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
> canvas {
|
> .rating {
|
||||||
@include S(margin, 0, 5px);
|
|
||||||
@include S(width, 30px);
|
|
||||||
@include S(height, 30px);
|
|
||||||
@include S(border-radius, $globalBorderRadius);
|
@include S(border-radius, $globalBorderRadius);
|
||||||
transition: opacity 0.12s ease-in-out, background-color 0.12s ease-in-out,
|
|
||||||
box-shadow 0.12s ease-in-out;
|
|
||||||
|
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
@include S(margin, 0, 5px);
|
||||||
|
@include S(width, 65px);
|
||||||
|
@include S(height, 50px);
|
||||||
|
|
||||||
|
> canvas {
|
||||||
|
@include S(width, 30px);
|
||||||
|
@include S(height, 30px);
|
||||||
|
transition: opacity 0.12s ease-in-out, background-color 0.12s ease-in-out,
|
||||||
|
box-shadow 0.12s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .description {
|
||||||
|
@include SuperSmallText;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
background-color: #151118 !important;
|
background-color: #151118 !important;
|
||||||
box-shadow: 0 0 0 D(2px) #151118;
|
box-shadow: 0 0 0 D(2px) #151118;
|
||||||
@ -154,27 +168,24 @@
|
|||||||
&:not(.active) {
|
&:not(.active) {
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:nth-child(1) {
|
> .actions {
|
||||||
transform: scale(0.8) !important;
|
position: absolute;
|
||||||
}
|
@include S(bottom, 40px);
|
||||||
&:nth-child(2) {
|
|
||||||
transform: scale(0.9) !important;
|
display: grid;
|
||||||
}
|
@include S(grid-gap, 15px);
|
||||||
&:nth-child(3) {
|
grid-auto-flow: column;
|
||||||
transform: scale(1) !important;
|
|
||||||
}
|
button {
|
||||||
&:nth-child(4) {
|
@include SuperSmallText;
|
||||||
transform: scale(1.1) !important;
|
|
||||||
}
|
|
||||||
&:nth-child(5) {
|
|
||||||
transform: scale(1.2) !important;
|
|
||||||
}
|
|
||||||
&:nth-child(6) {
|
|
||||||
transform: scale(1.3) !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
.report {
|
||||||
|
background-color: $accentColorDark;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,6 +193,8 @@
|
|||||||
border: 0;
|
border: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
@include S(margin-top, 30px);
|
@include S(margin-top, 30px);
|
||||||
|
background: $colorGreenBright;
|
||||||
|
@include S(padding, 10px, 40px);
|
||||||
|
|
||||||
&:not(.visible) {
|
&:not(.visible) {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
@ -9,4 +9,11 @@
|
|||||||
/* @load-async */
|
/* @load-async */
|
||||||
background: uiResource("puzzle_dlc_logo.png") center center / contain no-repeat;
|
background: uiResource("puzzle_dlc_logo.png") center center / contain no-repeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@include DarkThemeOverride {
|
||||||
|
& {
|
||||||
|
/* @load-async */
|
||||||
|
background: uiResource("puzzle_dlc_logo_inverse.png") center center / contain no-repeat;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@include DarkThemeInvert;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ingame_HUD_PuzzleEditorTitle {
|
#ingame_HUD_PuzzleEditorTitle {
|
||||||
@ -27,4 +29,6 @@
|
|||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
@include Heading;
|
@include Heading;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
|
@include DarkThemeInvert;
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,8 @@
|
|||||||
@include S(padding-right, 25px);
|
@include S(padding-right, 25px);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
|
||||||
|
@include DarkThemeInvert;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 0.9 !important;
|
opacity: 0.9 !important;
|
||||||
}
|
}
|
||||||
|
@ -44,9 +44,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .buttons > button.trim {
|
> .buttons {
|
||||||
|
> .buttonBar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
@include S(margin-top, 10px);
|
@include S(margin-top, 10px);
|
||||||
|
> button {
|
||||||
|
@include S(margin-right, 4px);
|
||||||
@include SuperSmallText;
|
@include SuperSmallText;
|
||||||
|
&:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,14 +6,94 @@
|
|||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@include SuperDuperSmallText;
|
|
||||||
@include S(width, 200px);
|
@include S(width, 200px);
|
||||||
|
|
||||||
> span {
|
> .info {
|
||||||
@include S(margin-bottom, 10px);
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
@include SuperSmallText;
|
||||||
|
@include S(margin-bottom, 5px);
|
||||||
|
|
||||||
strong {
|
> label {
|
||||||
|
text-transform: uppercase;
|
||||||
|
@include SuperSmallText;
|
||||||
|
}
|
||||||
|
> span {
|
||||||
|
display: flex;
|
||||||
|
@include SuperSmallText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .plays {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-self: end;
|
||||||
|
align-self: end;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
@include DarkThemeInvert;
|
||||||
|
opacity: 0.4;
|
||||||
|
|
||||||
|
> .downloads {
|
||||||
|
@include SuperSmallText;
|
||||||
|
color: #000;
|
||||||
|
align-self: start;
|
||||||
|
justify-self: start;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
@include S(margin-right, 10px);
|
||||||
|
@include S(padding-left, 14px);
|
||||||
|
opacity: 0.7;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
& {
|
||||||
|
/* @load-async */
|
||||||
|
background: uiResource("icons/puzzle_plays.png") #{D(2px)} center / #{D(8px)} #{D(8px)} no-repeat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .likes {
|
||||||
|
@include SuperSmallText;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #000;
|
||||||
|
align-self: start;
|
||||||
|
justify-self: start;
|
||||||
|
font-weight: bold;
|
||||||
|
@include S(padding-left, 14px);
|
||||||
|
opacity: 0.7;
|
||||||
|
|
||||||
|
& {
|
||||||
|
/* @load-async */
|
||||||
|
background: uiResource("icons/puzzle_upvotes.png") #{D(2px)} center / #{D(8px)} #{D(8px)} no-repeat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .key {
|
||||||
|
button {
|
||||||
|
@include S(margin-top, 2px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
@include SuperSmallText;
|
||||||
|
align-self: start;
|
||||||
|
@include S(min-width, 50px);
|
||||||
|
|
||||||
|
&.report {
|
||||||
|
background-color: $accentColorDark;
|
||||||
|
@include SuperDuperSmallText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
> button {
|
||||||
|
@include S(margin-bottom, 4px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
23
src/css/ingame_hud/puzzle_play_settings.scss
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#ingame_HUD_PuzzlePlaySettings {
|
||||||
|
position: absolute;
|
||||||
|
background: $ingameHudBg;
|
||||||
|
@include S(padding, 10px);
|
||||||
|
@include S(bottom, 60px);
|
||||||
|
@include S(left, 10px);
|
||||||
|
|
||||||
|
@include SuperSmallText;
|
||||||
|
color: #eee;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
@include S(border-radius, $globalBorderRadius);
|
||||||
|
|
||||||
|
> .section {
|
||||||
|
display: grid;
|
||||||
|
@include S(grid-gap, 10px);
|
||||||
|
grid-auto-flow: row;
|
||||||
|
|
||||||
|
> button {
|
||||||
|
@include SuperSmallText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -61,6 +61,7 @@
|
|||||||
@import "ingame_hud/puzzle_dlc_logo";
|
@import "ingame_hud/puzzle_dlc_logo";
|
||||||
@import "ingame_hud/puzzle_editor_controls";
|
@import "ingame_hud/puzzle_editor_controls";
|
||||||
@import "ingame_hud/puzzle_editor_settings";
|
@import "ingame_hud/puzzle_editor_settings";
|
||||||
|
@import "ingame_hud/puzzle_play_settings";
|
||||||
@import "ingame_hud/puzzle_play_metadata";
|
@import "ingame_hud/puzzle_play_metadata";
|
||||||
@import "ingame_hud/puzzle_complete_notification";
|
@import "ingame_hud/puzzle_complete_notification";
|
||||||
|
|
||||||
@ -85,6 +86,7 @@ ingame_HUD_PuzzleEditorReview,
|
|||||||
ingame_HUD_PuzzleEditorControls,
|
ingame_HUD_PuzzleEditorControls,
|
||||||
ingame_HUD_PuzzleEditorTitle,
|
ingame_HUD_PuzzleEditorTitle,
|
||||||
ingame_HUD_PuzzleEditorSettings,
|
ingame_HUD_PuzzleEditorSettings,
|
||||||
|
ingame_HUD_PuzzlePlaySettings,
|
||||||
ingame_HUD_PuzzlePlayMetadata,
|
ingame_HUD_PuzzlePlayMetadata,
|
||||||
ingame_HUD_PuzzlePlayTitle,
|
ingame_HUD_PuzzlePlayTitle,
|
||||||
ingame_HUD_Notifications,
|
ingame_HUD_Notifications,
|
||||||
|
@ -183,7 +183,7 @@
|
|||||||
.updateLabel {
|
.updateLabel {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
transform: translateX(50%) rotate(-5deg);
|
transform: translateX(50%) rotate(-5deg);
|
||||||
color: #3291e9;
|
color: #ff590b;
|
||||||
@include Heading;
|
@include Heading;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@include S(right, 40px);
|
@include S(right, 40px);
|
||||||
|
@ -7,6 +7,10 @@
|
|||||||
> h1 {
|
> h1 {
|
||||||
justify-self: start;
|
justify-self: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.createPuzzle {
|
||||||
|
background-color: $colorGreenBright;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .container {
|
> .container {
|
||||||
@ -42,12 +46,22 @@
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@include DarkThemeOverride {
|
||||||
|
background: $accentColorDark;
|
||||||
|
color: #bbbbc4;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: $colorBlueBright;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .puzzles {
|
> .puzzles {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(D(150px), 1fr));
|
grid-template-columns: repeat(auto-fit, D(150px));
|
||||||
@include S(grid-auto-rows, 120px);
|
@include S(grid-auto-rows, 120px);
|
||||||
@include S(grid-gap, 3px);
|
@include S(grid-gap, 3px);
|
||||||
@include S(margin-top, 10px);
|
@include S(margin-top, 10px);
|
||||||
@ -55,6 +69,7 @@
|
|||||||
@include S(height, 360px);
|
@include S(height, 360px);
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
> .puzzle {
|
> .puzzle {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -72,6 +87,10 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
@include DarkThemeOverride {
|
||||||
|
background: rgba(0, 0, 10, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
@include InlineAnimation(0.12s ease-in-out) {
|
@include InlineAnimation(0.12s ease-in-out) {
|
||||||
0% {
|
0% {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
@ -86,9 +105,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
> .title {
|
> .title {
|
||||||
grid-column: 1 / 2;
|
grid-column: 1 / 3;
|
||||||
grid-row: 1/ 2;
|
grid-row: 1 / 2;
|
||||||
@include PlainText;
|
@include PlainText;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .icon {
|
> .icon {
|
||||||
@ -122,12 +144,14 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-self: end;
|
justify-self: end;
|
||||||
|
justify-content: center;
|
||||||
align-self: end;
|
align-self: end;
|
||||||
|
|
||||||
|
@include DarkThemeInvert;
|
||||||
|
|
||||||
> .downloads {
|
> .downloads {
|
||||||
@include SuperSmallText;
|
@include SuperSmallText;
|
||||||
color: #000;
|
color: #000;
|
||||||
align-self: start;
|
|
||||||
justify-self: start;
|
justify-self: start;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@include S(margin-right, 10px);
|
@include S(margin-right, 10px);
|
||||||
@ -149,7 +173,6 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
color: #000;
|
color: #000;
|
||||||
align-self: start;
|
|
||||||
justify-self: start;
|
justify-self: start;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@include S(padding-left, 14px);
|
@include S(padding-left, 14px);
|
||||||
@ -162,6 +185,14 @@
|
|||||||
)} #{D(8px)} no-repeat;
|
)} #{D(8px)} no-repeat;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> .difficulty {
|
||||||
|
@include S(margin-top, 1px);
|
||||||
|
@include S(margin-right, 7px);
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.completed {
|
&.completed {
|
||||||
@ -189,17 +220,27 @@
|
|||||||
contain no-repeat;
|
contain no-repeat;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@include DarkThemeOverride {
|
||||||
|
&::after {
|
||||||
|
/* @load-async */
|
||||||
|
background: uiResource("icons/puzzle_complete_indicator_inverse.png") center
|
||||||
|
center / contain no-repeat;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .loader,
|
> .loader,
|
||||||
> .empty {
|
> .empty {
|
||||||
grid-column: 1 / -1;
|
|
||||||
grid-row: 1 / 3;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: $accentColorDark;
|
color: $accentColorDark;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ export const CHANGELOG = [
|
|||||||
{
|
{
|
||||||
version: "1.4.0",
|
version: "1.4.0",
|
||||||
date: "UNRELEASED",
|
date: "UNRELEASED",
|
||||||
entries: ["Added puzzle mode"],
|
entries: ["Added puzzle mode", "Belts in blueprints should now always paste correctly"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
version: "1.3.1",
|
version: "1.3.1",
|
||||||
|
@ -76,6 +76,7 @@ export const globalConfig = {
|
|||||||
puzzleModeSpeed: 3,
|
puzzleModeSpeed: 3,
|
||||||
puzzleMinBoundsSize: 2,
|
puzzleMinBoundsSize: 2,
|
||||||
puzzleMaxBoundsSize: 20,
|
puzzleMaxBoundsSize: 20,
|
||||||
|
puzzleValidationDurationSeconds: 30,
|
||||||
|
|
||||||
buildingSpeeds: {
|
buildingSpeeds: {
|
||||||
cutter: 1 / 4,
|
cutter: 1 / 4,
|
||||||
@ -99,7 +100,7 @@ export const globalConfig = {
|
|||||||
gameSpeed: 1,
|
gameSpeed: 1,
|
||||||
|
|
||||||
warmupTimeSecondsFast: 0.5,
|
warmupTimeSecondsFast: 0.5,
|
||||||
warmupTimeSecondsRegular: 3,
|
warmupTimeSecondsRegular: 1.5,
|
||||||
|
|
||||||
smoothing: {
|
smoothing: {
|
||||||
smoothMainCanvas: smoothCanvas && true,
|
smoothMainCanvas: smoothCanvas && true,
|
||||||
|
@ -108,6 +108,14 @@ export class BeltPath extends BasicSerializableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears all items
|
||||||
|
*/
|
||||||
|
clearAllItems() {
|
||||||
|
this.items = [];
|
||||||
|
this.spacingToFirstItem = this.totalLength;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether this path can accept a new item
|
* Returns whether this path can accept a new item
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
|
@ -149,6 +149,7 @@ export class Blueprint {
|
|||||||
*/
|
*/
|
||||||
tryPlace(root, tile) {
|
tryPlace(root, tile) {
|
||||||
return root.logic.performBulkOperation(() => {
|
return root.logic.performBulkOperation(() => {
|
||||||
|
return root.logic.performImmutableOperation(() => {
|
||||||
let count = 0;
|
let count = 0;
|
||||||
for (let i = 0; i < this.entities.length; ++i) {
|
for (let i = 0; i < this.entities.length; ++i) {
|
||||||
const entity = this.entities[i];
|
const entity = this.entities[i];
|
||||||
@ -173,5 +174,6 @@ export class Blueprint {
|
|||||||
|
|
||||||
return count !== 0;
|
return count !== 0;
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,18 +102,6 @@ export class MetaBalancerBuilding extends MetaBuilding {
|
|||||||
available.push(enumBalancerVariants.splitter, enumBalancerVariants.splitterInverse);
|
available.push(enumBalancerVariants.splitter, enumBalancerVariants.splitterInverse);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root.gameMode.getIsDeterministic()) {
|
|
||||||
// mergers are not deterministic
|
|
||||||
available = available.filter(
|
|
||||||
v =>
|
|
||||||
![
|
|
||||||
enumBalancerVariants.merger,
|
|
||||||
enumBalancerVariants.mergerInverse,
|
|
||||||
defaultBuildingVariant,
|
|
||||||
].includes(v)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return available;
|
return available;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,7 +74,10 @@ export class MetaPainterBuilding extends MetaBuilding {
|
|||||||
if (root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_painter_double)) {
|
if (root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_painter_double)) {
|
||||||
variants.push(enumPainterVariants.double);
|
variants.push(enumPainterVariants.double);
|
||||||
}
|
}
|
||||||
if (root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_wires_painter_and_levers)) {
|
if (
|
||||||
|
root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_wires_painter_and_levers) &&
|
||||||
|
root.gameMode.getSupportsWires()
|
||||||
|
) {
|
||||||
variants.push(enumPainterVariants.quad);
|
variants.push(enumPainterVariants.quad);
|
||||||
}
|
}
|
||||||
return variants;
|
return variants;
|
||||||
|
@ -23,6 +23,11 @@ export class Component extends BasicSerializableObject {
|
|||||||
*/
|
*/
|
||||||
copyAdditionalStateTo(otherComponent) {}
|
copyAdditionalStateTo(otherComponent) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears all items and state
|
||||||
|
*/
|
||||||
|
clear() {}
|
||||||
|
|
||||||
/* dev:start */
|
/* dev:start */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -30,6 +30,10 @@ export class BeltReaderComponent extends Component {
|
|||||||
|
|
||||||
this.type = type;
|
this.type = type;
|
||||||
|
|
||||||
|
this.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
/**
|
/**
|
||||||
* Which items went through the reader, we only store the time
|
* Which items went through the reader, we only store the time
|
||||||
* @type {Array<number>}
|
* @type {Array<number>}
|
||||||
|
@ -40,6 +40,10 @@ export class FilterComponent extends Component {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
this.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
/**
|
/**
|
||||||
* Items in queue to leave through
|
* Items in queue to leave through
|
||||||
* @type {Array<PendingFilterItem>}
|
* @type {Array<PendingFilterItem>}
|
||||||
|
@ -26,6 +26,10 @@ export class GoalAcceptorComponent extends Component {
|
|||||||
/** @type {BaseItem | undefined} */
|
/** @type {BaseItem | undefined} */
|
||||||
this.item = item;
|
this.item = item;
|
||||||
|
|
||||||
|
this.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
// the last items we delivered
|
// the last items we delivered
|
||||||
/** @type {{ item: BaseItem; time: number; }[]} */
|
/** @type {{ item: BaseItem; time: number; }[]} */
|
||||||
this.deliveryHistory = [];
|
this.deliveryHistory = [];
|
||||||
|
@ -36,6 +36,11 @@ export class ItemAcceptorComponent extends Component {
|
|||||||
constructor({ slots = [] }) {
|
constructor({ slots = [] }) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
this.setSlots(slots);
|
||||||
|
this.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
/**
|
/**
|
||||||
* Fixes belt animations
|
* Fixes belt animations
|
||||||
* @type {Array<{
|
* @type {Array<{
|
||||||
@ -46,8 +51,6 @@ export class ItemAcceptorComponent extends Component {
|
|||||||
* }>}
|
* }>}
|
||||||
*/
|
*/
|
||||||
this.itemConsumptionAnimations = [];
|
this.itemConsumptionAnimations = [];
|
||||||
|
|
||||||
this.setSlots(slots);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -48,6 +48,13 @@ export class ItemEjectorComponent extends Component {
|
|||||||
this.renderFloatingItems = renderFloatingItems;
|
this.renderFloatingItems = renderFloatingItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
for (const slot of this.slots) {
|
||||||
|
slot.item = null;
|
||||||
|
slot.progress = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Array<{pos: Vector, direction: enumDirection }>} slots The slots to eject on
|
* @param {Array<{pos: Vector, direction: enumDirection }>} slots The slots to eject on
|
||||||
*/
|
*/
|
||||||
|
@ -64,10 +64,8 @@ export class ItemProcessorComponent extends Component {
|
|||||||
}) {
|
}) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
// Which slot to emit next, this is only a preference and if it can't emit
|
// How many inputs we need for one charge
|
||||||
// it will take the other one. Some machines ignore this (e.g. the balancer) to make
|
this.inputsPerCharge = inputsPerCharge;
|
||||||
// sure the outputs always match
|
|
||||||
this.nextOutputSlot = 0;
|
|
||||||
|
|
||||||
// Type of the processor
|
// Type of the processor
|
||||||
this.type = processorType;
|
this.type = processorType;
|
||||||
@ -75,8 +73,14 @@ export class ItemProcessorComponent extends Component {
|
|||||||
// Type of processing requirement
|
// Type of processing requirement
|
||||||
this.processingRequirement = processingRequirement;
|
this.processingRequirement = processingRequirement;
|
||||||
|
|
||||||
// How many inputs we need for one charge
|
this.clear();
|
||||||
this.inputsPerCharge = inputsPerCharge;
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
// Which slot to emit next, this is only a preference and if it can't emit
|
||||||
|
// it will take the other one. Some machines ignore this (e.g. the balancer) to make
|
||||||
|
// sure the outputs always match
|
||||||
|
this.nextOutputSlot = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Our current inputs
|
* Our current inputs
|
||||||
|
@ -24,13 +24,6 @@ export class MinerComponent extends Component {
|
|||||||
this.lastMiningTime = 0;
|
this.lastMiningTime = 0;
|
||||||
this.chainable = chainable;
|
this.chainable = chainable;
|
||||||
|
|
||||||
/**
|
|
||||||
* Stores items from other miners which were chained to this
|
|
||||||
* miner.
|
|
||||||
* @type {Array<BaseItem>}
|
|
||||||
*/
|
|
||||||
this.itemChainBuffer = [];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {BaseItem}
|
* @type {BaseItem}
|
||||||
*/
|
*/
|
||||||
@ -42,6 +35,17 @@ export class MinerComponent extends Component {
|
|||||||
* @type {Entity|null|false}
|
* @type {Entity|null|false}
|
||||||
*/
|
*/
|
||||||
this.cachedChainedMiner = null;
|
this.cachedChainedMiner = null;
|
||||||
|
|
||||||
|
this.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
/**
|
||||||
|
* Stores items from other miners which were chained to this
|
||||||
|
* miner.
|
||||||
|
* @type {Array<BaseItem>}
|
||||||
|
*/
|
||||||
|
this.itemChainBuffer = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -71,6 +71,14 @@ export class StaticMapEntityComponent extends Component {
|
|||||||
return getBuildingDataFromCode(this.code).variant;
|
return getBuildingDataFromCode(this.code).variant;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the buildings rotation variant
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
getRotationVariant() {
|
||||||
|
return getBuildingDataFromCode(this.code).rotationVariant;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copy the current state to another component
|
* Copy the current state to another component
|
||||||
* @param {Component} otherComponent
|
* @param {Component} otherComponent
|
||||||
|
@ -41,6 +41,17 @@ export class UndergroundBeltComponent extends Component {
|
|||||||
this.mode = mode;
|
this.mode = mode;
|
||||||
this.tier = tier;
|
this.tier = tier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The linked entity, used to speed up performance. This contains either
|
||||||
|
* the entrance or exit depending on the tunnel type
|
||||||
|
* @type {LinkedUndergroundBelt}
|
||||||
|
*/
|
||||||
|
this.cachedLinkedEntity = null;
|
||||||
|
|
||||||
|
this.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
/** @type {Array<{ item: BaseItem, progress: number }>} */
|
/** @type {Array<{ item: BaseItem, progress: number }>} */
|
||||||
this.consumptionAnimations = [];
|
this.consumptionAnimations = [];
|
||||||
|
|
||||||
@ -51,13 +62,6 @@ export class UndergroundBeltComponent extends Component {
|
|||||||
* @type {Array<[BaseItem, number]>} Format is [Item, ingame time to eject the item]
|
* @type {Array<[BaseItem, number]>} Format is [Item, ingame time to eject the item]
|
||||||
*/
|
*/
|
||||||
this.pendingItems = [];
|
this.pendingItems = [];
|
||||||
|
|
||||||
/**
|
|
||||||
* The linked entity, used to speed up performance. This contains either
|
|
||||||
* the entrance or exit depending on the tunnel type
|
|
||||||
* @type {LinkedUndergroundBelt}
|
|
||||||
*/
|
|
||||||
this.cachedLinkedEntity = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -170,11 +170,6 @@ export class GameMode extends BasicSerializableObject {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {boolean} */
|
|
||||||
getIsDeterministic() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @returns {boolean} */
|
/** @returns {boolean} */
|
||||||
getIsEditor() {
|
getIsEditor() {
|
||||||
return false;
|
return false;
|
||||||
|
@ -1,13 +1,26 @@
|
|||||||
|
/* typehints:start */
|
||||||
|
import { PuzzlePlayGameMode } from "../../modes/puzzle_play";
|
||||||
|
/* typehints:end */
|
||||||
|
|
||||||
import { InputReceiver } from "../../../core/input_receiver";
|
import { InputReceiver } from "../../../core/input_receiver";
|
||||||
import { makeDiv } from "../../../core/utils";
|
import { makeDiv } from "../../../core/utils";
|
||||||
import { SOUNDS } from "../../../platform/sound";
|
import { SOUNDS } from "../../../platform/sound";
|
||||||
import { T } from "../../../translations";
|
import { T } from "../../../translations";
|
||||||
import { enumColors } from "../../colors";
|
import { enumColors } from "../../colors";
|
||||||
import { ColorItem } from "../../items/color_item";
|
import { ColorItem } from "../../items/color_item";
|
||||||
import { PuzzlePlayGameMode } from "../../modes/puzzle_play";
|
|
||||||
import { finalGameShape, rocketShape } from "../../modes/regular";
|
import { finalGameShape, rocketShape } from "../../modes/regular";
|
||||||
import { BaseHUDPart } from "../base_hud_part";
|
import { BaseHUDPart } from "../base_hud_part";
|
||||||
import { DynamicDomAttach } from "../dynamic_dom_attach";
|
import { DynamicDomAttach } from "../dynamic_dom_attach";
|
||||||
|
import { ShapeItem } from "../../items/shape_item";
|
||||||
|
import { ShapeDefinition } from "../../shape_definition";
|
||||||
|
|
||||||
|
export const PUZZLE_RATINGS = [
|
||||||
|
new ColorItem(enumColors.red),
|
||||||
|
new ShapeItem(ShapeDefinition.fromShortKey("CuCuCuCu")),
|
||||||
|
new ShapeItem(ShapeDefinition.fromShortKey("WwWwWwWw")),
|
||||||
|
new ShapeItem(ShapeDefinition.fromShortKey(finalGameShape)),
|
||||||
|
new ShapeItem(ShapeDefinition.fromShortKey(rocketShape)),
|
||||||
|
];
|
||||||
|
|
||||||
export class HUDPuzzleCompleteNotification extends BaseHUDPart {
|
export class HUDPuzzleCompleteNotification extends BaseHUDPart {
|
||||||
initialize() {
|
initialize() {
|
||||||
@ -33,15 +46,28 @@ export class HUDPuzzleCompleteNotification extends BaseHUDPart {
|
|||||||
|
|
||||||
this.elemTitle = makeDiv(dialog, null, ["title"], T.ingame.puzzleCompletion.title);
|
this.elemTitle = makeDiv(dialog, null, ["title"], T.ingame.puzzleCompletion.title);
|
||||||
this.elemContents = makeDiv(dialog, null, ["contents"]);
|
this.elemContents = makeDiv(dialog, null, ["contents"]);
|
||||||
|
this.elemActions = makeDiv(dialog, null, ["actions"]);
|
||||||
|
|
||||||
|
const reportBtn = document.createElement("button");
|
||||||
|
reportBtn.classList.add("styledButton", "report");
|
||||||
|
reportBtn.innerHTML = T.ingame.puzzleEditorSettings.report;
|
||||||
|
this.elemActions.appendChild(reportBtn);
|
||||||
|
this.trackClicks(reportBtn, this.report);
|
||||||
|
|
||||||
|
const shareBtn = document.createElement("button");
|
||||||
|
shareBtn.classList.add("styledButton", "share");
|
||||||
|
shareBtn.innerHTML = T.ingame.puzzleEditorSettings.share;
|
||||||
|
this.elemActions.appendChild(shareBtn);
|
||||||
|
this.trackClicks(shareBtn, this.share);
|
||||||
|
|
||||||
const stepLike = makeDiv(this.elemContents, null, ["step", "stepLike"]);
|
const stepLike = makeDiv(this.elemContents, null, ["step", "stepLike"]);
|
||||||
makeDiv(stepLike, null, ["title"], T.ingame.puzzleCompletion.titleLike);
|
makeDiv(stepLike, null, ["title"], T.ingame.puzzleCompletion.titleLike);
|
||||||
|
|
||||||
const buttons = makeDiv(stepLike, null, ["buttons"]);
|
const likeButtons = makeDiv(stepLike, null, ["buttons"]);
|
||||||
|
|
||||||
this.buttonLikeYes = document.createElement("button");
|
this.buttonLikeYes = document.createElement("button");
|
||||||
this.buttonLikeYes.classList.add("liked-yes");
|
this.buttonLikeYes.classList.add("liked-yes");
|
||||||
buttons.appendChild(this.buttonLikeYes);
|
likeButtons.appendChild(this.buttonLikeYes);
|
||||||
this.trackClicks(this.buttonLikeYes, () => {
|
this.trackClicks(this.buttonLikeYes, () => {
|
||||||
this.selectionLiked = true;
|
this.selectionLiked = true;
|
||||||
this.updateState();
|
this.updateState();
|
||||||
@ -49,7 +75,7 @@ export class HUDPuzzleCompleteNotification extends BaseHUDPart {
|
|||||||
|
|
||||||
this.buttonLikeNo = document.createElement("button");
|
this.buttonLikeNo = document.createElement("button");
|
||||||
this.buttonLikeNo.classList.add("liked-no");
|
this.buttonLikeNo.classList.add("liked-no");
|
||||||
buttons.appendChild(this.buttonLikeNo);
|
likeButtons.appendChild(this.buttonLikeNo);
|
||||||
this.trackClicks(this.buttonLikeNo, () => {
|
this.trackClicks(this.buttonLikeNo, () => {
|
||||||
this.selectionLiked = false;
|
this.selectionLiked = false;
|
||||||
this.updateState();
|
this.updateState();
|
||||||
@ -59,30 +85,33 @@ export class HUDPuzzleCompleteNotification extends BaseHUDPart {
|
|||||||
makeDiv(stepDifficulty, null, ["title"], T.ingame.puzzleCompletion.titleRating);
|
makeDiv(stepDifficulty, null, ["title"], T.ingame.puzzleCompletion.titleRating);
|
||||||
|
|
||||||
const shapeContainer = makeDiv(stepDifficulty, null, ["shapes"]);
|
const shapeContainer = makeDiv(stepDifficulty, null, ["shapes"]);
|
||||||
const items = [
|
|
||||||
new ColorItem(enumColors.red),
|
|
||||||
this.root.shapeDefinitionMgr.getShapeItemFromShortKey("CuCuCuCu"),
|
|
||||||
this.root.shapeDefinitionMgr.getShapeItemFromShortKey("WwWwWwWw"),
|
|
||||||
this.root.shapeDefinitionMgr.getShapeItemFromShortKey("WrRgWrRg:CwCrCwCr:SgSgSgSg"),
|
|
||||||
this.root.shapeDefinitionMgr.getShapeItemFromShortKey(finalGameShape),
|
|
||||||
this.root.shapeDefinitionMgr.getShapeItemFromShortKey(rocketShape),
|
|
||||||
];
|
|
||||||
|
|
||||||
this.difficultyCanvases = [];
|
this.difficultyElements = [];
|
||||||
let index = 0;
|
let index = 0;
|
||||||
for (const shape of items) {
|
for (const shape of PUZZLE_RATINGS) {
|
||||||
const localIndex = index;
|
const localIndex = index;
|
||||||
|
|
||||||
|
const elem = document.createElement("div");
|
||||||
|
elem.classList.add("rating");
|
||||||
|
shapeContainer.appendChild(elem);
|
||||||
|
|
||||||
const canvas = document.createElement("canvas");
|
const canvas = document.createElement("canvas");
|
||||||
canvas.width = 128;
|
canvas.width = 128;
|
||||||
canvas.height = 128;
|
canvas.height = 128;
|
||||||
const context = canvas.getContext("2d");
|
const context = canvas.getContext("2d");
|
||||||
shape.drawFullSizeOnCanvas(context, 128);
|
shape.drawFullSizeOnCanvas(context, 128);
|
||||||
shapeContainer.appendChild(canvas);
|
elem.appendChild(canvas);
|
||||||
this.trackClicks(canvas, () => {
|
|
||||||
|
this.trackClicks(elem, () => {
|
||||||
this.selectionDifficulty = localIndex;
|
this.selectionDifficulty = localIndex;
|
||||||
this.updateState();
|
this.updateState();
|
||||||
});
|
});
|
||||||
this.difficultyCanvases.push(canvas);
|
this.difficultyElements.push(elem);
|
||||||
|
|
||||||
|
const desc = document.createElement("div");
|
||||||
|
desc.classList.add("description");
|
||||||
|
desc.innerText = T.ingame.puzzleCompletion.difficulties[localIndex];
|
||||||
|
elem.appendChild(desc);
|
||||||
++index;
|
++index;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,10 +123,20 @@ export class HUDPuzzleCompleteNotification extends BaseHUDPart {
|
|||||||
this.trackClicks(this.btnClose, this.close);
|
this.trackClicks(this.btnClose, this.close);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
share() {
|
||||||
|
const mode = /** @type {PuzzlePlayGameMode} */ (this.root.gameMode);
|
||||||
|
mode.sharePuzzle();
|
||||||
|
}
|
||||||
|
|
||||||
|
report() {
|
||||||
|
const mode = /** @type {PuzzlePlayGameMode} */ (this.root.gameMode);
|
||||||
|
mode.reportPuzzle();
|
||||||
|
}
|
||||||
|
|
||||||
updateState() {
|
updateState() {
|
||||||
this.buttonLikeYes.classList.toggle("active", this.selectionLiked === true);
|
this.buttonLikeYes.classList.toggle("active", this.selectionLiked === true);
|
||||||
this.buttonLikeNo.classList.toggle("active", this.selectionLiked === false);
|
this.buttonLikeNo.classList.toggle("active", this.selectionLiked === false);
|
||||||
this.difficultyCanvases.forEach((canvas, index) =>
|
this.difficultyElements.forEach((canvas, index) =>
|
||||||
canvas.classList.toggle("active", index === this.selectionDifficulty)
|
canvas.classList.toggle("active", index === this.selectionDifficulty)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -19,9 +19,6 @@ const logger = createLogger("puzzle-review");
|
|||||||
export class HUDPuzzleEditorReview extends BaseHUDPart {
|
export class HUDPuzzleEditorReview extends BaseHUDPart {
|
||||||
constructor(root) {
|
constructor(root) {
|
||||||
super(root);
|
super(root);
|
||||||
|
|
||||||
this.validationEndsIn = null;
|
|
||||||
this.callOnceValidationEnded = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createElements(parent) {
|
createElements(parent) {
|
||||||
@ -45,9 +42,37 @@ export class HUDPuzzleEditorReview extends BaseHUDPart {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const closeLoading = this.root.hud.parts.dialogs.showLoadingDialog(T.puzzleMenu.validtingPuzzle);
|
const closeLoading = this.root.hud.parts.dialogs.showLoadingDialog(T.puzzleMenu.validatingPuzzle);
|
||||||
this.validationEndsIn = this.root.time.now() + globalConfig.goalAcceptorMinimumDurationSeconds;
|
|
||||||
this.callOnceValidationEnded = () => {
|
// Wait a bit, so the user sees the puzzle actually got validated
|
||||||
|
setTimeout(() => {
|
||||||
|
// Manually simulate ticks
|
||||||
|
this.root.logic.clearAllBeltsAndItems();
|
||||||
|
|
||||||
|
const ticks =
|
||||||
|
this.root.gameMode.getFixedTickrate() * globalConfig.puzzleValidationDurationSeconds;
|
||||||
|
const deltaMs = this.root.dynamicTickrate.deltaMs;
|
||||||
|
logger.log("Simulating", ticks, "ticks, start=", this.root.time.now().toFixed(1));
|
||||||
|
const now = performance.now();
|
||||||
|
for (let i = 0; i < ticks; ++i) {
|
||||||
|
if (i % Math.round((ticks - 1) / 10) === 0) {
|
||||||
|
console.log("Ticking", Math.round((i / ticks) * 100) + "%");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform logic ticks
|
||||||
|
this.root.time.performTicks(deltaMs, this.root.gameState.core.boundInternalTick);
|
||||||
|
}
|
||||||
|
const duration = performance.now() - now;
|
||||||
|
logger.log(
|
||||||
|
"Simulated",
|
||||||
|
ticks,
|
||||||
|
"ticks, end=",
|
||||||
|
this.root.time.now().toFixed(1),
|
||||||
|
"duration=",
|
||||||
|
duration.toFixed(2),
|
||||||
|
"ms"
|
||||||
|
);
|
||||||
|
|
||||||
closeLoading();
|
closeLoading();
|
||||||
const validationError = this.validatePuzzle();
|
const validationError = this.validatePuzzle();
|
||||||
if (validationError) {
|
if (validationError) {
|
||||||
@ -55,7 +80,7 @@ export class HUDPuzzleEditorReview extends BaseHUDPart {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.startSubmit();
|
this.startSubmit();
|
||||||
};
|
}, 750);
|
||||||
}
|
}
|
||||||
|
|
||||||
startSubmit(title = "", shortKey = "") {
|
startSubmit(title = "", shortKey = "") {
|
||||||
@ -102,7 +127,7 @@ export class HUDPuzzleEditorReview extends BaseHUDPart {
|
|||||||
title: T.dialogs.submitPuzzle.title,
|
title: T.dialogs.submitPuzzle.title,
|
||||||
desc: "",
|
desc: "",
|
||||||
formElements: [nameInput, itemInput, shapeKeyInput],
|
formElements: [nameInput, itemInput, shapeKeyInput],
|
||||||
buttons: ["cancel:bad:escape", "ok:good:enter"],
|
buttons: ["ok:good:enter"],
|
||||||
});
|
});
|
||||||
|
|
||||||
itemInput.valueChosen.add(value => {
|
itemInput.valueChosen.add(value => {
|
||||||
@ -154,18 +179,6 @@ export class HUDPuzzleEditorReview extends BaseHUDPart {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
|
||||||
if (
|
|
||||||
this.validationEndsIn &&
|
|
||||||
this.validationEndsIn < this.root.time.now() &&
|
|
||||||
this.callOnceValidationEnded
|
|
||||||
) {
|
|
||||||
const callMethod = this.callOnceValidationEnded;
|
|
||||||
this.callOnceValidationEnded = null;
|
|
||||||
callMethod();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
validatePuzzle() {
|
validatePuzzle() {
|
||||||
// Check there is at least one constant producer and goal acceptor
|
// Check there is at least one constant producer and goal acceptor
|
||||||
const producers = this.root.entityMgr.getAllWithComponent(ConstantSignalComponent);
|
const producers = this.root.entityMgr.getAllWithComponent(ConstantSignalComponent);
|
||||||
|
@ -41,7 +41,10 @@ export class HUDPuzzleEditorSettings extends BaseHUDPart {
|
|||||||
<button class="styledButton plus">+</button>
|
<button class="styledButton plus">+</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="buttonBar">
|
||||||
<button class="styledButton trim">${T.ingame.puzzleEditorSettings.trimZone}</button>
|
<button class="styledButton trim">${T.ingame.puzzleEditorSettings.trimZone}</button>
|
||||||
|
<button class="styledButton clear">${T.ingame.puzzleEditorSettings.clearItems}</button>
|
||||||
|
</div>
|
||||||
</div>`
|
</div>`
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -50,20 +53,82 @@ export class HUDPuzzleEditorSettings extends BaseHUDPart {
|
|||||||
bind(".zoneHeight .minus", () => this.modifyZone(0, -1));
|
bind(".zoneHeight .minus", () => this.modifyZone(0, -1));
|
||||||
bind(".zoneHeight .plus", () => this.modifyZone(0, 1));
|
bind(".zoneHeight .plus", () => this.modifyZone(0, 1));
|
||||||
bind("button.trim", this.trim);
|
bind("button.trim", this.trim);
|
||||||
|
bind("button.clear", this.clear);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this.root.logic.clearAllBeltsAndItems();
|
||||||
|
}
|
||||||
|
|
||||||
trim() {
|
trim() {
|
||||||
const mode = /** @type {PuzzleGameMode} */ (this.root.gameMode);
|
// Now, find the center
|
||||||
|
const buildings = this.root.entityMgr.entities.slice();
|
||||||
|
|
||||||
let w = mode.zoneWidth;
|
if (buildings.length === 0) {
|
||||||
let h = mode.zoneHeight;
|
// nothing to do
|
||||||
if (this.anyBuildingOutsideZone(w, h)) {
|
|
||||||
logger.error("Trim: Zone is already too small");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.log("Zone trim: Starts at", w, h);
|
let minRect = null;
|
||||||
|
|
||||||
|
for (const building of buildings) {
|
||||||
|
const staticComp = building.components.StaticMapEntity;
|
||||||
|
const bounds = staticComp.getTileSpaceBounds();
|
||||||
|
|
||||||
|
if (!minRect) {
|
||||||
|
minRect = bounds;
|
||||||
|
} else {
|
||||||
|
minRect = minRect.getUnion(bounds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mode = /** @type {PuzzleGameMode} */ (this.root.gameMode);
|
||||||
|
const moveByInverse = minRect.getCenter().round();
|
||||||
|
|
||||||
|
// move buildings
|
||||||
|
if (moveByInverse.length() > 0) {
|
||||||
|
// increase area size
|
||||||
|
mode.zoneWidth = globalConfig.puzzleMaxBoundsSize;
|
||||||
|
mode.zoneHeight = globalConfig.puzzleMaxBoundsSize;
|
||||||
|
|
||||||
|
// First, remove any items etc
|
||||||
|
this.root.logic.clearAllBeltsAndItems();
|
||||||
|
|
||||||
|
this.root.logic.performImmutableOperation(() => {
|
||||||
|
// 1. remove all buildings
|
||||||
|
for (const building of buildings) {
|
||||||
|
if (!this.root.logic.tryDeleteBuilding(building)) {
|
||||||
|
assertAlways(false, "Failed to remove building in trim");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. place them again, but centered
|
||||||
|
for (const building of buildings) {
|
||||||
|
const staticComp = building.components.StaticMapEntity;
|
||||||
|
const result = this.root.logic.tryPlaceBuilding({
|
||||||
|
origin: staticComp.origin.sub(moveByInverse),
|
||||||
|
building: staticComp.getMetaBuilding(),
|
||||||
|
originalRotation: staticComp.originalRotation,
|
||||||
|
rotation: staticComp.rotation,
|
||||||
|
rotationVariant: staticComp.getRotationVariant(),
|
||||||
|
variant: staticComp.getVariant(),
|
||||||
|
});
|
||||||
|
if (!result) {
|
||||||
|
this.root.bulkOperationRunning = false;
|
||||||
|
assertAlways(false, "Failed to re-place building in trim");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (building.components.ConstantSignal) {
|
||||||
|
result.components.ConstantSignal.signal = building.components.ConstantSignal.signal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Actually trim
|
||||||
|
let w = mode.zoneWidth;
|
||||||
|
let h = mode.zoneHeight;
|
||||||
|
|
||||||
while (!this.anyBuildingOutsideZone(w - 1, h)) {
|
while (!this.anyBuildingOutsideZone(w - 1, h)) {
|
||||||
--w;
|
--w;
|
||||||
@ -73,12 +138,6 @@ export class HUDPuzzleEditorSettings extends BaseHUDPart {
|
|||||||
--h;
|
--h;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.log("Zone trim: After height pass at", w, h);
|
|
||||||
if (this.anyBuildingOutsideZone(w, h)) {
|
|
||||||
logger.error("Trim: Zone is too small *after* trim");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mode.zoneWidth = w;
|
mode.zoneWidth = w;
|
||||||
mode.zoneHeight = h;
|
mode.zoneHeight = h;
|
||||||
this.updateZoneValues();
|
this.updateZoneValues();
|
||||||
|
@ -1,22 +1,76 @@
|
|||||||
import { makeDiv } from "../../../core/utils";
|
/* typehints:start */
|
||||||
|
import { PuzzlePlayGameMode } from "../../modes/puzzle_play";
|
||||||
|
/* typehints:end */
|
||||||
|
|
||||||
|
import { formatBigNumberFull, formatSeconds, makeDiv } from "../../../core/utils";
|
||||||
|
import { T } from "../../../translations";
|
||||||
import { BaseHUDPart } from "../base_hud_part";
|
import { BaseHUDPart } from "../base_hud_part";
|
||||||
|
|
||||||
|
const copy = require("clipboard-copy");
|
||||||
|
|
||||||
export class HUDPuzzlePlayMetadata extends BaseHUDPart {
|
export class HUDPuzzlePlayMetadata extends BaseHUDPart {
|
||||||
createElements(parent) {
|
createElements(parent) {
|
||||||
this.titleElement = makeDiv(parent, "ingame_HUD_PuzzlePlayTitle");
|
this.titleElement = makeDiv(parent, "ingame_HUD_PuzzlePlayTitle");
|
||||||
this.titleElement.innerText = "PUZZLE";
|
this.titleElement.innerText = "PUZZLE";
|
||||||
|
|
||||||
|
const mode = /** @type {PuzzlePlayGameMode} */ (this.root.gameMode);
|
||||||
|
const puzzle = mode.puzzle;
|
||||||
|
|
||||||
this.puzzleNameElement = makeDiv(this.titleElement, null, ["name"]);
|
this.puzzleNameElement = makeDiv(this.titleElement, null, ["name"]);
|
||||||
this.puzzleNameElement.innerText = "tobspr's first puzzle";
|
this.puzzleNameElement.innerText = puzzle.meta.title;
|
||||||
|
|
||||||
this.element = makeDiv(parent, "ingame_HUD_PuzzlePlayMetadata");
|
this.element = makeDiv(parent, "ingame_HUD_PuzzlePlayMetadata");
|
||||||
this.element.innerHTML = `
|
this.element.innerHTML = `
|
||||||
|
|
||||||
<div class="author">Author: tobspr</div>
|
<div class="info plays">
|
||||||
<div class="plays">Plays: 12.000</div>
|
<span class="downloads">${formatBigNumberFull(puzzle.meta.downloads)}</span>
|
||||||
<div class="likes">Likes: 512</div>
|
<span class="likes">${formatBigNumberFull(puzzle.meta.likes)}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="info author"><label>${T.ingame.puzzleMetadata.author}</label><span></span></div>
|
||||||
|
<div class="info key">
|
||||||
|
<label>${T.ingame.puzzleMetadata.shortKey}</label><span>${puzzle.meta.shortKey}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info rating">
|
||||||
|
<label>${T.ingame.puzzleMetadata.rating}</label>
|
||||||
|
<span>${
|
||||||
|
puzzle.meta.difficulty
|
||||||
|
? puzzle.meta.difficulty.toFixed(1)
|
||||||
|
: T.puzzleMenu.difficultyNotDetermined
|
||||||
|
}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info rating">
|
||||||
|
<label>${T.ingame.puzzleMetadata.averageDuration}</label>
|
||||||
|
<span>${
|
||||||
|
puzzle.meta.averageTime
|
||||||
|
? formatSeconds(puzzle.meta.averageTime)
|
||||||
|
: T.puzzleMenu.difficultyNotDetermined
|
||||||
|
}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="buttons">
|
||||||
|
<button class="styledButton share">${T.ingame.puzzleEditorSettings.share}</button>
|
||||||
|
<button class="styledButton report">${T.ingame.puzzleEditorSettings.report}</button>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
this.trackClicks(this.element.querySelector("button.share"), this.share);
|
||||||
|
this.trackClicks(this.element.querySelector("button.report"), this.report);
|
||||||
|
|
||||||
|
/** @type {HTMLElement} */ (this.element.querySelector(".author span")).innerText =
|
||||||
|
puzzle.meta.author;
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize() {}
|
initialize() {}
|
||||||
|
|
||||||
|
share() {
|
||||||
|
const mode = /** @type {PuzzlePlayGameMode} */ (this.root.gameMode);
|
||||||
|
mode.sharePuzzle();
|
||||||
|
}
|
||||||
|
|
||||||
|
report() {
|
||||||
|
const mode = /** @type {PuzzlePlayGameMode} */ (this.root.gameMode);
|
||||||
|
mode.reportPuzzle();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
36
src/js/game/hud/parts/puzzle_play_settings.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { createLogger } from "../../../core/logging";
|
||||||
|
import { makeDiv } from "../../../core/utils";
|
||||||
|
import { T } from "../../../translations";
|
||||||
|
import { BaseHUDPart } from "../base_hud_part";
|
||||||
|
|
||||||
|
const logger = createLogger("puzzle-play");
|
||||||
|
|
||||||
|
export class HUDPuzzlePlaySettings extends BaseHUDPart {
|
||||||
|
createElements(parent) {
|
||||||
|
this.element = makeDiv(parent, "ingame_HUD_PuzzlePlaySettings");
|
||||||
|
|
||||||
|
if (this.root.gameMode.getBuildableZones()) {
|
||||||
|
const bind = (selector, handler) =>
|
||||||
|
this.trackClicks(this.element.querySelector(selector), handler);
|
||||||
|
makeDiv(
|
||||||
|
this.element,
|
||||||
|
null,
|
||||||
|
["section"],
|
||||||
|
`
|
||||||
|
<button class="styledButton clear">${T.ingame.puzzleEditorSettings.clearItems}</button>
|
||||||
|
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
bind("button.clear", this.clear);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this.root.logic.clearAllBeltsAndItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
initialize() {
|
||||||
|
this.visible = true;
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ import { STOP_PROPAGATION } from "../core/signal";
|
|||||||
import { round2Digits } from "../core/utils";
|
import { round2Digits } from "../core/utils";
|
||||||
import { enumDirection, enumDirectionToVector, enumInvertedDirections, Vector } from "../core/vector";
|
import { enumDirection, enumDirectionToVector, enumInvertedDirections, Vector } from "../core/vector";
|
||||||
import { getBuildingDataFromCode } from "./building_codes";
|
import { getBuildingDataFromCode } from "./building_codes";
|
||||||
|
import { Component } from "./component";
|
||||||
import { enumWireVariant } from "./components/wire";
|
import { enumWireVariant } from "./components/wire";
|
||||||
import { Entity } from "./entity";
|
import { Entity } from "./entity";
|
||||||
import { CHUNK_OVERLAY_RES } from "./map_chunk_view";
|
import { CHUNK_OVERLAY_RES } from "./map_chunk_view";
|
||||||
@ -161,6 +162,27 @@ export class GameLogic {
|
|||||||
return returnValue;
|
return returnValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a immutable operation, causing no recalculations
|
||||||
|
* @param {function} operation
|
||||||
|
*/
|
||||||
|
performImmutableOperation(operation) {
|
||||||
|
logger.warn("Running immutable operation ...");
|
||||||
|
assert(!this.root.immutableOperationRunning, "Can not run two immutalbe operations twice");
|
||||||
|
this.root.immutableOperationRunning = true;
|
||||||
|
const now = performance.now();
|
||||||
|
const returnValue = operation();
|
||||||
|
const duration = performance.now() - now;
|
||||||
|
logger.log("Done in", round2Digits(duration), "ms");
|
||||||
|
assert(
|
||||||
|
this.root.immutableOperationRunning,
|
||||||
|
"Immutable operation = false while immutable operation was running"
|
||||||
|
);
|
||||||
|
this.root.immutableOperationRunning = false;
|
||||||
|
this.root.signals.immutableOperationFinished.dispatch();
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the given building can get removed
|
* Returns whether the given building can get removed
|
||||||
* @param {Entity} building
|
* @param {Entity} building
|
||||||
@ -342,8 +364,6 @@ export class GameLogic {
|
|||||||
return !!overlayMatrix[localPosition.x + localPosition.y * 3];
|
return !!overlayMatrix[localPosition.x + localPosition.y * 3];
|
||||||
}
|
}
|
||||||
|
|
||||||
g(tile, edge) {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the acceptors and ejectors which affect the current tile
|
* Returns the acceptors and ejectors which affect the current tile
|
||||||
* @param {Vector} tile
|
* @param {Vector} tile
|
||||||
@ -425,4 +445,22 @@ export class GameLogic {
|
|||||||
}
|
}
|
||||||
return { ejectors, acceptors };
|
return { ejectors, acceptors };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears all belts and items
|
||||||
|
*/
|
||||||
|
clearAllBeltsAndItems() {
|
||||||
|
// Belts
|
||||||
|
const beltPaths = this.root.systemMgr.systems.belt.beltPaths;
|
||||||
|
for (const path of beltPaths) {
|
||||||
|
path.clearAllItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acceptors
|
||||||
|
for (const entity of this.root.entityMgr.entities) {
|
||||||
|
for (const component of Object.values(entity.components)) {
|
||||||
|
/** @type {Component} */ (component).clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,10 +84,6 @@ export class PuzzleGameMode extends GameMode {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
getIsDeterministic() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
getFixedTickrate() {
|
getFixedTickrate() {
|
||||||
return 300;
|
return 300;
|
||||||
}
|
}
|
||||||
|
@ -27,8 +27,10 @@ import { T } from "../../translations";
|
|||||||
import { HUDPuzzlePlayMetadata } from "../hud/parts/puzzle_play_metadata";
|
import { HUDPuzzlePlayMetadata } from "../hud/parts/puzzle_play_metadata";
|
||||||
import { createLogger } from "../../core/logging";
|
import { createLogger } from "../../core/logging";
|
||||||
import { HUDPuzzleCompleteNotification } from "../hud/parts/puzzle_complete_notification";
|
import { HUDPuzzleCompleteNotification } from "../hud/parts/puzzle_complete_notification";
|
||||||
|
import { HUDPuzzlePlaySettings } from "../hud/parts/puzzle_play_settings";
|
||||||
|
|
||||||
const logger = createLogger("puzzle-play");
|
const logger = createLogger("puzzle-play");
|
||||||
|
const copy = require("clipboard-copy");
|
||||||
|
|
||||||
export class PuzzlePlayGameMode extends PuzzleGameMode {
|
export class PuzzlePlayGameMode extends PuzzleGameMode {
|
||||||
static getId() {
|
static getId() {
|
||||||
@ -66,6 +68,7 @@ export class PuzzlePlayGameMode extends PuzzleGameMode {
|
|||||||
];
|
];
|
||||||
|
|
||||||
this.additionalHudParts.puzzlePlayMetadata = HUDPuzzlePlayMetadata;
|
this.additionalHudParts.puzzlePlayMetadata = HUDPuzzlePlayMetadata;
|
||||||
|
this.additionalHudParts.puzzlePlaySettings = HUDPuzzlePlaySettings;
|
||||||
this.additionalHudParts.puzzleCompleteNotification = HUDPuzzleCompleteNotification;
|
this.additionalHudParts.puzzleCompleteNotification = HUDPuzzleCompleteNotification;
|
||||||
|
|
||||||
root.signals.postLoadHook.add(this.loadPuzzle, this);
|
root.signals.postLoadHook.add(this.loadPuzzle, this);
|
||||||
@ -122,4 +125,47 @@ export class PuzzlePlayGameMode extends PuzzleGameMode {
|
|||||||
closeLoading();
|
closeLoading();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sharePuzzle() {
|
||||||
|
copy(this.puzzle.meta.shortKey);
|
||||||
|
|
||||||
|
this.root.hud.parts.dialogs.showInfo(
|
||||||
|
T.dialogs.puzzleShare.title,
|
||||||
|
T.dialogs.puzzleShare.desc.replace("<key>", this.puzzle.meta.shortKey)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
reportPuzzle() {
|
||||||
|
const { optionSelected } = this.root.hud.parts.dialogs.showOptionChooser(
|
||||||
|
T.dialogs.puzzleReport.title,
|
||||||
|
{
|
||||||
|
options: [
|
||||||
|
{ value: "profane", text: T.dialogs.puzzleReport.options.profane },
|
||||||
|
{ value: "unsolvable", text: T.dialogs.puzzleReport.options.unsolvable },
|
||||||
|
{ value: "trolling", text: T.dialogs.puzzleReport.options.trolling },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
optionSelected.add(option => {
|
||||||
|
const closeLoading = this.root.hud.parts.dialogs.showLoadingDialog();
|
||||||
|
|
||||||
|
this.root.app.clientApi.apiReportPuzzle(this.puzzle.meta.id, option).then(
|
||||||
|
() => {
|
||||||
|
closeLoading();
|
||||||
|
this.root.hud.parts.dialogs.showInfo(
|
||||||
|
T.dialogs.puzzleReportComplete.title,
|
||||||
|
T.dialogs.puzzleReportComplete.desc
|
||||||
|
);
|
||||||
|
},
|
||||||
|
err => {
|
||||||
|
closeLoading();
|
||||||
|
this.root.hud.parts.dialogs.showInfo(
|
||||||
|
T.dialogs.puzzleReportError.title,
|
||||||
|
T.dialogs.puzzleReportError.desc + " " + err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,6 +79,11 @@ export class GameRoot {
|
|||||||
*/
|
*/
|
||||||
this.bulkOperationRunning = false;
|
this.bulkOperationRunning = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether a immutable operation is running
|
||||||
|
*/
|
||||||
|
this.immutableOperationRunning = false;
|
||||||
|
|
||||||
//////// Other properties ///////
|
//////// Other properties ///////
|
||||||
|
|
||||||
/** @type {Camera} */
|
/** @type {Camera} */
|
||||||
@ -169,6 +174,7 @@ export class GameRoot {
|
|||||||
itemProduced: /** @type {TypedSignal<[BaseItem]>} */ (new Signal()),
|
itemProduced: /** @type {TypedSignal<[BaseItem]>} */ (new Signal()),
|
||||||
|
|
||||||
bulkOperationFinished: /** @type {TypedSignal<[]>} */ (new Signal()),
|
bulkOperationFinished: /** @type {TypedSignal<[]>} */ (new Signal()),
|
||||||
|
immutableOperationFinished: /** @type {TypedSignal<[]>} */ (new Signal()),
|
||||||
|
|
||||||
editModeChanged: /** @type {TypedSignal<[Layer]>} */ (new Signal()),
|
editModeChanged: /** @type {TypedSignal<[Layer]>} */ (new Signal()),
|
||||||
|
|
||||||
|
@ -123,6 +123,10 @@ export class BeltSystem extends GameSystemWithFilter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.root.immutableOperationRunning) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const metaBelt = gMetaBuildingRegistry.findByClass(MetaBeltBuilding);
|
const metaBelt = gMetaBuildingRegistry.findByClass(MetaBeltBuilding);
|
||||||
// Compute affected area
|
// Compute affected area
|
||||||
const originalRect = staticComp.getTileSpaceBounds();
|
const originalRect = staticComp.getTileSpaceBounds();
|
||||||
|
@ -50,10 +50,8 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"zone": {
|
"zone": {
|
||||||
"background": "#fff",
|
"borderSolid": "rgba(23, 192, 255, 1)",
|
||||||
"border": "rgba(23, 192, 255, 0.1)",
|
"outerColor": "rgba(20 , 20, 25, 0.5)"
|
||||||
"borderSolid": "rgba(23, 192, 255, 0.7)",
|
|
||||||
"outerColor": "rgba(240, 240, 255, 0.5)"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
import { Application } from "../application";
|
import { Application } from "../application";
|
||||||
/* typehints:end */
|
/* typehints:end */
|
||||||
import { createLogger } from "../core/logging";
|
import { createLogger } from "../core/logging";
|
||||||
|
import { compressX64 } from "../core/lzstring";
|
||||||
|
import { T } from "../translations";
|
||||||
|
|
||||||
const logger = createLogger("puzzle-api");
|
const logger = createLogger("puzzle-api");
|
||||||
|
|
||||||
@ -70,7 +72,8 @@ export class ClientAPI {
|
|||||||
])
|
])
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
throw data.error;
|
logger.warn("Got error from api:", data);
|
||||||
|
throw T.backendErrors[data.error] || data.error;
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
})
|
})
|
||||||
@ -126,6 +129,31 @@ export class ClientAPI {
|
|||||||
return this._request("/v1/puzzles/download/" + puzzleId, {});
|
return this._request("/v1/puzzles/download/" + puzzleId, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} shortKey
|
||||||
|
* @returns {Promise<import("../savegame/savegame_typedefs").PuzzleFullData>}
|
||||||
|
*/
|
||||||
|
apiDownloadPuzzleByKey(shortKey) {
|
||||||
|
if (!this.isLoggedIn()) {
|
||||||
|
return Promise.reject("not-logged-in");
|
||||||
|
}
|
||||||
|
return this._request("/v1/puzzles/download/" + shortKey, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} puzzleId
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
apiReportPuzzle(puzzleId, reason) {
|
||||||
|
if (!this.isLoggedIn()) {
|
||||||
|
return Promise.reject("not-logged-in");
|
||||||
|
}
|
||||||
|
return this._request("/v1/puzzles/report/" + puzzleId, {
|
||||||
|
method: "POST",
|
||||||
|
body: { reason },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {number} puzzleId
|
* @param {number} puzzleId
|
||||||
* @param {object} payload
|
* @param {object} payload
|
||||||
@ -157,7 +185,10 @@ export class ClientAPI {
|
|||||||
}
|
}
|
||||||
return this._request("/v1/puzzles/submit", {
|
return this._request("/v1/puzzles/submit", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: payload,
|
body: {
|
||||||
|
...payload,
|
||||||
|
data: compressX64(JSON.stringify(payload.data)),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,8 @@
|
|||||||
* shortKey: string;
|
* shortKey: string;
|
||||||
* likes: number;
|
* likes: number;
|
||||||
* downloads: number;
|
* downloads: number;
|
||||||
|
* difficulty: number | null;
|
||||||
|
* averageTime: number | null;
|
||||||
* title: string;
|
* title: string;
|
||||||
* author: string;
|
* author: string;
|
||||||
* completed: boolean;
|
* completed: boolean;
|
||||||
|
@ -67,7 +67,7 @@ export class MainMenuState extends GameState {
|
|||||||
<img src="${cachebust(
|
<img src="${cachebust(
|
||||||
G_CHINA_VERSION ? "res/logo_cn.png" : "res/logo.png"
|
G_CHINA_VERSION ? "res/logo_cn.png" : "res/logo.png"
|
||||||
)}" alt="shapez.io Logo">
|
)}" alt="shapez.io Logo">
|
||||||
<span class="updateLabel">v${G_BUILD_VERSION} - Achievements!</span>
|
<span class="updateLabel">v${G_BUILD_VERSION} - Puzzle DLC!</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mainWrapper ${showDemoBadges ? "demo" : "noDemo"}">
|
<div class="mainWrapper ${showDemoBadges ? "demo" : "noDemo"}">
|
||||||
@ -208,7 +208,7 @@ 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.testPuzzleMode) {
|
if (G_IS_DEV && globalConfig.debug.testPuzzleMode) {
|
||||||
this.onPuzzleModeButtonClicked();
|
this.onPuzzleModeButtonClicked(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -320,10 +320,22 @@ export class MainMenuState extends GameState {
|
|||||||
const puzzleModeButton = makeButton(bottomButtonContainer, ["styledButton"], T.mainMenu.puzzleMode);
|
const puzzleModeButton = makeButton(bottomButtonContainer, ["styledButton"], T.mainMenu.puzzleMode);
|
||||||
|
|
||||||
bottomButtonContainer.appendChild(puzzleModeButton);
|
bottomButtonContainer.appendChild(puzzleModeButton);
|
||||||
this.trackClicks(puzzleModeButton, this.onPuzzleModeButtonClicked);
|
this.trackClicks(puzzleModeButton, () => this.onPuzzleModeButtonClicked());
|
||||||
|
}
|
||||||
|
|
||||||
|
onPuzzleModeButtonClicked(force = false) {
|
||||||
|
const hasUnlockedBlueprints = this.app.savegameMgr.getSavegamesMetaData().some(s => s.level >= 12);
|
||||||
|
console.log(hasUnlockedBlueprints);
|
||||||
|
if (!force && !hasUnlockedBlueprints) {
|
||||||
|
const { ok } = this.dialogs.showWarning(
|
||||||
|
T.dialogs.puzzlePlayRegularRecommendation.title,
|
||||||
|
T.dialogs.puzzlePlayRegularRecommendation.desc,
|
||||||
|
["cancel:good", "ok:bad:timeout"]
|
||||||
|
);
|
||||||
|
ok.add(() => this.onPuzzleModeButtonClicked(true));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
onPuzzleModeButtonClicked() {
|
|
||||||
this.moveToState("LoginState", {
|
this.moveToState("LoginState", {
|
||||||
nextStateId: "PuzzleMenuState",
|
nextStateId: "PuzzleMenuState",
|
||||||
});
|
});
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
import { globalConfig } from "../core/config";
|
import { globalConfig } from "../core/config";
|
||||||
import { createLogger } from "../core/logging";
|
import { createLogger } from "../core/logging";
|
||||||
|
import { DialogWithForm } from "../core/modal_dialog_elements";
|
||||||
|
import { FormElementInput } from "../core/modal_dialog_forms";
|
||||||
import { TextualGameState } from "../core/textual_game_state";
|
import { TextualGameState } from "../core/textual_game_state";
|
||||||
import { formatBigNumberFull } from "../core/utils";
|
import { clamp, formatBigNumberFull } from "../core/utils";
|
||||||
import { enumGameModeIds } from "../game/game_mode";
|
import { enumGameModeIds } from "../game/game_mode";
|
||||||
|
import { PUZZLE_RATINGS } from "../game/hud/parts/puzzle_complete_notification";
|
||||||
import { ShapeDefinition } from "../game/shape_definition";
|
import { ShapeDefinition } from "../game/shape_definition";
|
||||||
|
import { Savegame } from "../savegame/savegame";
|
||||||
import { T } from "../translations";
|
import { T } from "../translations";
|
||||||
|
|
||||||
const categories = ["levels", "new", "top-rated", "mine"];
|
const categories = ["top-rated", "short", "hard", "new", "mine"];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {import("../savegame/savegame_typedefs").PuzzleMetadata}
|
* @type {import("../savegame/savegame_typedefs").PuzzleMetadata}
|
||||||
@ -16,6 +20,8 @@ const SAMPLE_PUZZLE = {
|
|||||||
shortKey: "CuCuCuCu",
|
shortKey: "CuCuCuCu",
|
||||||
downloads: 0,
|
downloads: 0,
|
||||||
likes: 0,
|
likes: 0,
|
||||||
|
averageTime: 1,
|
||||||
|
difficulty: null,
|
||||||
title: "Level 1",
|
title: "Level 1",
|
||||||
author: "verylongsteamnamewhichbreaks",
|
author: "verylongsteamnamewhichbreaks",
|
||||||
completed: false,
|
completed: false,
|
||||||
@ -63,6 +69,7 @@ export class PuzzleMenuState extends TextualGameState {
|
|||||||
<h1><button class="backButton"></button> ${this.getStateHeaderTitle()}</h1>
|
<h1><button class="backButton"></button> ${this.getStateHeaderTitle()}</h1>
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
|
<button class="styledButton loadPuzzle">${T.puzzleMenu.loadPuzzle}</button>
|
||||||
<button class="styledButton createPuzzle">+ ${T.puzzleMenu.createPuzzle}</button>
|
<button class="styledButton createPuzzle">+ ${T.puzzleMenu.createPuzzle}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
@ -89,12 +96,7 @@ export class PuzzleMenuState extends TextualGameState {
|
|||||||
.join("")}
|
.join("")}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="puzzles" id="mainContainer">
|
<div class="puzzles" id="mainContainer"></div>
|
||||||
<div class="puzzle"></div>
|
|
||||||
<div class="puzzle"></div>
|
|
||||||
<div class="puzzle"></div>
|
|
||||||
<div class="puzzle"></div>
|
|
||||||
</div>
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
return html;
|
return html;
|
||||||
@ -104,9 +106,11 @@ export class PuzzleMenuState extends TextualGameState {
|
|||||||
if (category === this.activeCategory) {
|
if (category === this.activeCategory) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.loading) {
|
if (this.loading) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.activeCategory = category;
|
this.activeCategory = category;
|
||||||
|
|
||||||
@ -175,6 +179,22 @@ export class PuzzleMenuState extends TextualGameState {
|
|||||||
stats.classList.add("stats");
|
stats.classList.add("stats");
|
||||||
elem.appendChild(stats);
|
elem.appendChild(stats);
|
||||||
|
|
||||||
|
if (puzzle.difficulty !== null) {
|
||||||
|
const difficulty = document.createElement("div");
|
||||||
|
difficulty.classList.add("difficulty");
|
||||||
|
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
canvas.width = 32;
|
||||||
|
canvas.height = 32;
|
||||||
|
const context = canvas.getContext("2d");
|
||||||
|
PUZZLE_RATINGS[
|
||||||
|
clamp(Math.round(puzzle.difficulty), 0, PUZZLE_RATINGS.length - 1)
|
||||||
|
].drawFullSizeOnCanvas(context, 32);
|
||||||
|
difficulty.appendChild(canvas);
|
||||||
|
|
||||||
|
stats.appendChild(difficulty);
|
||||||
|
}
|
||||||
|
|
||||||
const downloads = document.createElement("div");
|
const downloads = document.createElement("div");
|
||||||
downloads.classList.add("downloads");
|
downloads.classList.add("downloads");
|
||||||
downloads.innerText = String(puzzle.downloads);
|
downloads.innerText = String(puzzle.downloads);
|
||||||
@ -233,16 +253,8 @@ export class PuzzleMenuState extends TextualGameState {
|
|||||||
this.app.clientApi.apiDownloadPuzzle(puzzle.id).then(
|
this.app.clientApi.apiDownloadPuzzle(puzzle.id).then(
|
||||||
puzzleData => {
|
puzzleData => {
|
||||||
closeLoading();
|
closeLoading();
|
||||||
|
|
||||||
logger.log("Got puzzle:", puzzleData);
|
logger.log("Got puzzle:", puzzleData);
|
||||||
const savegame = this.app.savegameMgr.createNewSavegame();
|
this.startLoadedPuzzle(puzzleData);
|
||||||
this.moveToState("InGameState", {
|
|
||||||
gameModeId: enumGameModeIds.puzzlePlay,
|
|
||||||
gameModeParameters: {
|
|
||||||
puzzle: puzzleData,
|
|
||||||
},
|
|
||||||
savegame,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
err => {
|
err => {
|
||||||
closeLoading();
|
closeLoading();
|
||||||
@ -255,8 +267,23 @@ export class PuzzleMenuState extends TextualGameState {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {import("../savegame/savegame_typedefs").PuzzleFullData} puzzle
|
||||||
|
*/
|
||||||
|
startLoadedPuzzle(puzzle) {
|
||||||
|
const savegame = this.createEmptySavegame();
|
||||||
|
this.moveToState("InGameState", {
|
||||||
|
gameModeId: enumGameModeIds.puzzlePlay,
|
||||||
|
gameModeParameters: {
|
||||||
|
puzzle,
|
||||||
|
},
|
||||||
|
savegame,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
onEnter(payload) {
|
onEnter(payload) {
|
||||||
this.selectCategory("levels");
|
this.selectCategory(categories[0]);
|
||||||
|
|
||||||
if (payload && payload.error) {
|
if (payload && payload.error) {
|
||||||
this.dialogs.showWarning(payload.error.title, payload.error.desc);
|
this.dialogs.showWarning(payload.error.title, payload.error.desc);
|
||||||
@ -268,6 +295,7 @@ export class PuzzleMenuState extends TextualGameState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.trackClicks(this.htmlElement.querySelector("button.createPuzzle"), () => this.createNewPuzzle());
|
this.trackClicks(this.htmlElement.querySelector("button.createPuzzle"), () => this.createNewPuzzle());
|
||||||
|
this.trackClicks(this.htmlElement.querySelector("button.loadPuzzle"), () => this.loadPuzzle());
|
||||||
|
|
||||||
if (G_IS_DEV && globalConfig.debug.testPuzzleMode) {
|
if (G_IS_DEV && globalConfig.debug.testPuzzleMode) {
|
||||||
// this.createNewPuzzle();
|
// this.createNewPuzzle();
|
||||||
@ -275,6 +303,56 @@ export class PuzzleMenuState extends TextualGameState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createEmptySavegame() {
|
||||||
|
return new Savegame(this.app, {
|
||||||
|
internalId: "puzzle",
|
||||||
|
metaDataRef: {
|
||||||
|
internalId: "puzzle",
|
||||||
|
lastUpdate: 0,
|
||||||
|
version: 0,
|
||||||
|
level: 0,
|
||||||
|
name: "puzzle",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadPuzzle() {
|
||||||
|
const shortKeyInput = new FormElementInput({
|
||||||
|
id: "shortKey",
|
||||||
|
label: null,
|
||||||
|
placeholder: "",
|
||||||
|
defaultValue: "",
|
||||||
|
validator: val => ShapeDefinition.isValidShortKey(val),
|
||||||
|
});
|
||||||
|
|
||||||
|
const dialog = new DialogWithForm({
|
||||||
|
app: this.app,
|
||||||
|
title: T.dialogs.puzzleLoadShortKey.title,
|
||||||
|
desc: T.dialogs.puzzleLoadShortKey.desc,
|
||||||
|
formElements: [shortKeyInput],
|
||||||
|
buttons: ["ok:good:enter"],
|
||||||
|
});
|
||||||
|
this.dialogs.internalShowDialog(dialog);
|
||||||
|
|
||||||
|
dialog.buttonSignals.ok.add(() => {
|
||||||
|
const closeLoading = this.dialogs.showLoadingDialog();
|
||||||
|
|
||||||
|
this.app.clientApi.apiDownloadPuzzleByKey(shortKeyInput.getValue()).then(
|
||||||
|
puzzle => {
|
||||||
|
closeLoading();
|
||||||
|
this.startLoadedPuzzle(puzzle);
|
||||||
|
},
|
||||||
|
err => {
|
||||||
|
closeLoading();
|
||||||
|
this.dialogs.showWarning(
|
||||||
|
T.dialogs.puzzleDownloadError.title,
|
||||||
|
T.dialogs.puzzleDownloadError.desc + " " + err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
createNewPuzzle(force = false) {
|
createNewPuzzle(force = false) {
|
||||||
if (!force && !this.app.clientApi.isLoggedIn()) {
|
if (!force && !this.app.clientApi.isLoggedIn()) {
|
||||||
const signals = this.dialogs.showWarning(
|
const signals = this.dialogs.showWarning(
|
||||||
@ -286,7 +364,7 @@ export class PuzzleMenuState extends TextualGameState {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const savegame = this.app.savegameMgr.createNewSavegame();
|
const savegame = this.createEmptySavegame();
|
||||||
this.moveToState("InGameState", {
|
this.moveToState("InGameState", {
|
||||||
gameModeId: enumGameModeIds.puzzleEdit,
|
gameModeId: enumGameModeIds.puzzleEdit,
|
||||||
savegame,
|
savegame,
|
||||||
|
@ -125,16 +125,20 @@ puzzleMenu:
|
|||||||
edit: Edit
|
edit: Edit
|
||||||
title: Puzzle Mode
|
title: Puzzle Mode
|
||||||
createPuzzle: Create Puzzle
|
createPuzzle: Create Puzzle
|
||||||
|
loadPuzzle: Load
|
||||||
reviewPuzzle: Review & Publish
|
reviewPuzzle: Review & Publish
|
||||||
validtingPuzzle: Validating Puzzle
|
validatingPuzzle: Validating Puzzle
|
||||||
submittingPuzzle: Submitting Puzzle
|
submittingPuzzle: Submitting Puzzle
|
||||||
noPuzzles: There are currently no puzzles in this section.
|
noPuzzles: There are currently no puzzles in this section.
|
||||||
|
difficultyNotDetermined: Not yet determined
|
||||||
|
|
||||||
categories:
|
categories:
|
||||||
levels: Levels
|
levels: Levels
|
||||||
new: New
|
new: New
|
||||||
top-rated: Top Rated
|
top-rated: Top Rated
|
||||||
mine: My Puzzles
|
mine: My Puzzles
|
||||||
|
short: Short
|
||||||
|
hard: Hard
|
||||||
|
|
||||||
validation:
|
validation:
|
||||||
title: Invalid Puzzle
|
title: Invalid Puzzle
|
||||||
@ -337,6 +341,38 @@ dialogs:
|
|||||||
desc: >-
|
desc: >-
|
||||||
Since you are offline, you will not be able to save and/or publish your puzzle. Would you still like to continue?
|
Since you are offline, you will not be able to save and/or publish your puzzle. Would you still like to continue?
|
||||||
|
|
||||||
|
puzzlePlayRegularRecommendation:
|
||||||
|
title: Recommendation
|
||||||
|
desc: >-
|
||||||
|
I <strong>strongly</strong> recommend playing the normal game to level 12 before attempting the puzzle DLC, otherwise you will have comprehension problems. Do you still want to continue?
|
||||||
|
|
||||||
|
puzzleShare:
|
||||||
|
title: Short Key Copied
|
||||||
|
desc: >-
|
||||||
|
The short key of the puzzle (<key>) has been copied to your clipboard! It can be entered in the puzzle menu to access the puzzle.
|
||||||
|
|
||||||
|
puzzleReport:
|
||||||
|
title: Report Puzzle
|
||||||
|
options:
|
||||||
|
profane: Profane
|
||||||
|
unsolvable: Not solvable
|
||||||
|
trolling: Trolling
|
||||||
|
|
||||||
|
puzzleReportComplete:
|
||||||
|
title: Thank you for your feedback!
|
||||||
|
desc: >-
|
||||||
|
The puzzle has been flagged.
|
||||||
|
|
||||||
|
puzzleReportError:
|
||||||
|
title: Failed to report
|
||||||
|
desc: >-
|
||||||
|
Your report could not get processed:
|
||||||
|
|
||||||
|
puzzleLoadShortKey:
|
||||||
|
title: Enter short key
|
||||||
|
desc: >-
|
||||||
|
Enter the short key of the puzzle to load it.
|
||||||
|
|
||||||
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
|
||||||
@ -567,6 +603,9 @@ ingame:
|
|||||||
zoneWidth: Width
|
zoneWidth: Width
|
||||||
zoneHeight: Height
|
zoneHeight: Height
|
||||||
trimZone: Trim
|
trimZone: Trim
|
||||||
|
clearItems: Clear Items
|
||||||
|
share: Share
|
||||||
|
report: Report
|
||||||
|
|
||||||
puzzleEditorControls:
|
puzzleEditorControls:
|
||||||
title: Puzzle Creator
|
title: Puzzle Creator
|
||||||
@ -584,7 +623,20 @@ ingame:
|
|||||||
Please rate the puzzle:
|
Please rate the puzzle:
|
||||||
titleRating: How difficult did you find the puzzle?
|
titleRating: How difficult did you find the puzzle?
|
||||||
|
|
||||||
buttonSubmit: Submit
|
buttonSubmit: Continue
|
||||||
|
|
||||||
|
difficulties:
|
||||||
|
- No challenge
|
||||||
|
- Easy
|
||||||
|
- Medium
|
||||||
|
- Hard
|
||||||
|
- Impossible
|
||||||
|
|
||||||
|
puzzleMetadata:
|
||||||
|
author: Author
|
||||||
|
shortKey: Short Key
|
||||||
|
rating: Difficulty
|
||||||
|
averageDuration: Avg. Duration
|
||||||
|
|
||||||
# All shop upgrades
|
# All shop upgrades
|
||||||
shopUpgrades:
|
shopUpgrades:
|
||||||
@ -1303,6 +1355,26 @@ demo:
|
|||||||
|
|
||||||
settingNotAvailable: Not available in the demo.
|
settingNotAvailable: Not available in the demo.
|
||||||
|
|
||||||
|
backendErrors:
|
||||||
|
ratelimit: You are performing your actions too frequent. Please wait a bit.
|
||||||
|
invalid-api-key: Failed to communicate with the backend, please try to update/restart the game (Invalid Api Key).
|
||||||
|
unauthorized: Failed to communicate with the backend, please try to update/restart the game (Unauthorized).
|
||||||
|
bad-token: Failed to communicate with the backend, please try to update/restart the game (Bad Token).
|
||||||
|
bad-id: Invalid puzzle identifier.
|
||||||
|
not-found: The given puzzle could not be found.
|
||||||
|
bad-category: The given category could not be found.
|
||||||
|
bad-short-key: The given short key is invalid.
|
||||||
|
profane-title: Your puzzle title contains profane words.
|
||||||
|
bad-title-too-many-spaces: Your puzzle title is too short.
|
||||||
|
bad-shape-key-in-emitter: A constant producer has an invalid item.
|
||||||
|
bad-shape-key-in-goal: A goal acceptor has an invalid item.
|
||||||
|
no-emitters: Your puzzle does not contain any constant producers.
|
||||||
|
no-goals: Your puzzle does not contain any goal acceptors.
|
||||||
|
short-key-already-taken: This short key is already taken, please use another one.
|
||||||
|
can-not-report-your-own-puzzle: You can not report your own puzzle.
|
||||||
|
bad-payload: The request contains invalid data.
|
||||||
|
bad-building-placement: Your puzzle contains invalid placed buildings.
|
||||||
|
|
||||||
tips:
|
tips:
|
||||||
- The hub will accept any input, not just the current shape!
|
- The hub will accept any input, not just the current shape!
|
||||||
- Make sure your factories are modular - it will pay out!
|
- Make sure your factories are modular - it will pay out!
|
||||||
|