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

New difficulty

This commit is contained in:
tobspr 2021-05-03 23:02:33 +02:00
parent 62fc46f29f
commit 4f846edb9b
22 changed files with 142 additions and 217 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 680 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -61,8 +61,6 @@
}
> .contents {
@include S(width, 400px);
@include S(height, 170px);
@include InlineAnimation(0.5s ease-in-out) {
0% {
transform: translateX(-100vw);
@ -75,6 +73,7 @@
transform: translateX(-2vw);
}
}
display: flex;
flex-direction: column;
align-items: center;
@ -84,6 +83,7 @@
display: flex;
flex-direction: column;
@include S(margin-bottom, 10px);
@include SuperSmallText;
> .buttons {
display: flex;
@ -92,8 +92,8 @@
@include S(margin, 10px, 0);
> button {
@include S(width, 40px);
@include S(height, 40px);
@include S(width, 60px);
@include S(height, 60px);
@include S(margin, 0, 10px);
box-sizing: border-box;
@include S(border-radius, $globalBorderRadius);
@ -101,12 +101,7 @@
&.liked-yes {
/* @load-async */
background: uiResource("icons/puzzle_action_liked_yes.png") center center / 60%
no-repeat;
}
&.liked-no {
/* @load-async */
background: uiResource("icons/puzzle_action_liked_no.png") center center / 60%
background: uiResource("icons/puzzle_action_liked_yes.png") center center / 70%
no-repeat;
}
@ -124,88 +119,30 @@
}
}
> .stepDifficulty {
display: flex;
flex-direction: column;
align-items: center;
@include S(margin-bottom, 10px);
> .actions {
position: absolute;
@include S(bottom, 40px);
> .desc {
display: grid;
@include S(grid-gap, 15px);
grid-auto-flow: column;
button {
@include SuperSmallText;
opacity: 0.4;
@include S(margin-bottom, 4px);
}
> .shapes {
@include S(margin-top, 10px);
display: flex;
align-items: center;
> .rating {
@include S(border-radius, $globalBorderRadius);
pointer-events: all;
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 {
background-color: #151118 !important;
box-shadow: 0 0 0 D(2px) #151118;
}
&:not(.active) {
opacity: 0.4;
}
}
.report {
background-color: $accentColorDark;
}
}
}
> .actions {
position: absolute;
@include S(bottom, 40px);
display: grid;
@include S(grid-gap, 15px);
grid-auto-flow: column;
button {
@include SuperSmallText;
}
.report {
background-color: $accentColorDark;
}
}
button.close {
border: 0;
position: relative;
@include S(margin-top, 30px);
@include S(margin-top, 15px);
background: $colorGreenBright;
@include S(padding, 10px, 40px);
&:not(.visible) {
opacity: 0;
pointer-events: none;
}
@include Heading;
@include S(padding, 14px, 40px);
}
}
}

View File

@ -1,6 +1,6 @@
$buildings: belt, cutter, miner, mixer, painter, rotater, balancer, stacker, trash, underground_belt, wire,
constant_signal, logic_gate, lever, filter, wire_tunnel, display, virtual_processor, reader, storage,
transistor, analyzer, comparator, item_producer, constant_producer, goal_acceptor;
transistor, analyzer, comparator, item_producer, constant_producer, goal_acceptor, block;
@each $building in $buildings {
[data-icon="building_icons/#{$building}.png"] {
@ -14,7 +14,7 @@ $buildingsAndVariants: belt, balancer, underground_belt, underground_belt-tier2,
reader, rotater-rotate180, display, constant_signal, wire, wire_tunnel, logic_gate-or, logic_gate-not,
logic_gate-xor, analyzer, virtual_processor-rotater, virtual_processor-unstacker, item_producer,
constant_producer, virtual_processor-stacker, virtual_processor-painter, wire-second, painter,
painter-mirrored, comparator, goal_acceptor;
painter-mirrored, comparator, goal_acceptor, block;
@each $building in $buildingsAndVariants {
[data-icon="building_tutorials/#{$building}.png"] {
/* @load-async */

View File

@ -158,7 +158,6 @@
> .downloads {
@include SuperSmallText;
color: #000;
justify-self: start;
font-weight: bold;
@include S(margin-right, 5px);
@include S(padding-left, 12px);
@ -170,8 +169,9 @@
& {
/* @load-async */
background: uiResource("icons/puzzle_plays.png") #{D(2px)} center / #{D(8px)}
#{D(8px)} no-repeat;
background: uiResource("icons/puzzle_plays.png") #{D(2px)} #{D(2.5px)} / #{D(
8px
)} #{D(8px)} no-repeat;
}
}
@ -180,26 +180,35 @@
align-items: center;
justify-content: center;
color: #000;
justify-self: start;
font-weight: bold;
@include S(padding-left, 12px);
@include S(padding-left, 14px);
opacity: 0.7;
@include DarkThemeInvert;
& {
/* @load-async */
background: uiResource("icons/puzzle_upvotes.png") #{D(2px)} center / #{D(
8px
)} #{D(8px)} no-repeat;
background: uiResource("icons/puzzle_upvotes.png") #{D(2px)} #{D(2.4px)} / #{D(
9px
)} #{D(9px)} no-repeat;
}
}
> .difficulty {
@include S(margin-top, 1px);
@include S(margin-right, 7px);
display: inline-flex;
@include SuperSmallText;
align-items: center;
justify-content: center;
color: #000;
font-weight: bold;
@include S(margin-right, 3px);
@include S(padding-left, 14px);
opacity: 0.7;
@include DarkThemeInvert;
& {
/* @load-async */
background: uiResource("icons/puzzle_completion_rate.png") #{D(1px)} #{D(2px)} /
#{D(10px)} #{D(10px)} no-repeat;
}
}
}

View File

@ -0,0 +1,30 @@
/* typehints:start */
import { Entity } from "../entity";
/* typehints:end */
import { MetaBuilding } from "../meta_building";
export class MetaBlockBuilding extends MetaBuilding {
constructor() {
super("block");
}
getSilhouetteColor() {
return "#333";
}
/**
*
* @param {import("../../savegame/savegame_serializer").GameRoot} root
* @returns
*/
getIsRemovable(root) {
return root.gameMode.getIsEditor();
}
/**
* Creates the entity at the given location
* @param {Entity} entity
*/
setupEntityComponents(entity) {}
}

View File

@ -17,6 +17,7 @@ import { MetaStorageBuilding } from "../../buildings/storage";
import { MetaItemProducerBuilding } from "../../buildings/item_producer";
import { MetaConstantProducerBuilding } from "../../buildings/constant_producer";
import { MetaGoalAcceptorBuilding } from "../../buildings/goal_acceptor";
import { MetaBlockBuilding } from "../../buildings/block";
export class HUDBuildingsToolbar extends HUDBaseToolbar {
constructor(root) {
@ -28,6 +29,7 @@ export class HUDBuildingsToolbar extends HUDBaseToolbar {
MetaBalancerBuilding,
MetaUndergroundBeltBuilding,
MetaMinerBuilding,
MetaBlockBuilding,
MetaCutterBuilding,
MetaRotaterBuilding,
MetaStackerBuilding,

View File

@ -14,14 +14,6 @@ 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 {
initialize() {
this.visible = false;
@ -32,8 +24,7 @@ export class HUDPuzzleCompleteNotification extends BaseHUDPart {
this.root.signals.puzzleComplete.add(this.show, this);
this.selectionLiked = false;
this.selectionDifficulty = null;
this.userDidLikePuzzle = false;
this.timeOfCompletion = 0;
}
@ -48,18 +39,6 @@ export class HUDPuzzleCompleteNotification extends BaseHUDPart {
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"]);
makeDiv(stepLike, null, ["title"], T.ingame.puzzleCompletion.titleLike);
@ -69,45 +48,10 @@ export class HUDPuzzleCompleteNotification extends BaseHUDPart {
this.buttonLikeYes.classList.add("liked-yes");
likeButtons.appendChild(this.buttonLikeYes);
this.trackClicks(this.buttonLikeYes, () => {
this.selectionLiked = !this.selectionLiked;
this.userDidLikePuzzle = !this.userDidLikePuzzle;
this.updateState();
});
const stepDifficulty = makeDiv(this.elemContents, null, ["step", "stepDifficulty"]);
makeDiv(stepDifficulty, null, ["title"], T.ingame.puzzleCompletion.titleRating);
makeDiv(stepDifficulty, null, ["desc"], T.ingame.puzzleCompletion.titleRatingDesc);
const shapeContainer = makeDiv(stepDifficulty, null, ["shapes"]);
this.difficultyElements = [];
let index = 0;
for (const shape of PUZZLE_RATINGS) {
const localIndex = index;
const elem = document.createElement("div");
elem.classList.add("rating");
shapeContainer.appendChild(elem);
const canvas = document.createElement("canvas");
canvas.width = 128;
canvas.height = 128;
const context = canvas.getContext("2d");
shape.drawFullSizeOnCanvas(context, 128);
elem.appendChild(canvas);
this.trackClicks(elem, () => {
this.selectionDifficulty = localIndex;
this.updateState();
});
this.difficultyElements.push(elem);
const desc = document.createElement("div");
desc.classList.add("description");
desc.innerText = T.ingame.puzzleCompletion.difficulties[localIndex];
elem.appendChild(desc);
++index;
}
this.btnClose = document.createElement("button");
this.btnClose.classList.add("close", "styledButton");
this.btnClose.innerText = T.ingame.puzzleCompletion.buttonSubmit;
@ -116,26 +60,8 @@ export class HUDPuzzleCompleteNotification extends BaseHUDPart {
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().then(() => this.close());
}
updateState() {
this.buttonLikeYes.classList.toggle("active", this.selectionLiked === true);
this.difficultyElements.forEach((canvas, index) =>
canvas.classList.toggle("active", index === this.selectionDifficulty)
);
this.btnClose.classList.toggle(
"visible",
typeof this.selectionDifficulty === "number" && typeof this.selectionLiked === "boolean"
);
this.buttonLikeYes.classList.toggle("active", this.userDidLikePuzzle === true);
}
show() {
@ -155,7 +81,7 @@ export class HUDPuzzleCompleteNotification extends BaseHUDPart {
close() {
/** @type {PuzzlePlayGameMode} */ (this.root.gameMode)
.trackCompleted(this.selectionLiked, this.selectionDifficulty, Math.round(this.timeOfCompletion))
.trackCompleted(this.userDidLikePuzzle, Math.round(this.timeOfCompletion))
.then(() => {
// this.root.gameState.moveToState("PuzzleMenuState");
this.visible = false;

View File

@ -32,31 +32,16 @@ export class HUDPuzzlePlayMetadata extends BaseHUDPart {
<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(2) +
" (" +
T.ingame.puzzleCompletion.difficulties[Math.round(puzzle.meta.difficulty)] +
")"
: 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>
<span>${puzzle.meta.averageTime ? formatSeconds(puzzle.meta.averageTime) : "-"}</span>
</div>
<div class="info rating">
<label>${T.ingame.puzzleMetadata.completionRate}</label>
<span>${
puzzle.meta.downloads > 10
puzzle.meta.downloads > 0
? ((puzzle.meta.completions / puzzle.meta.downloads) * 100.0).toFixed(1) + "%"
: T.puzzleMenu.difficultyNotDetermined
: "-"
}</span>
</div>

View File

@ -52,6 +52,7 @@ export const KEYMAPPINGS = {
// Puzzle buildings
constant_producer: { keyCode: key("H") },
goal_acceptor: { keyCode: key("N") },
block: { keyCode: key("4") },
// Primary Toolbar
belt: { keyCode: key("1") },

View File

@ -4,6 +4,7 @@ import { T } from "../translations";
import { MetaAnalyzerBuilding } from "./buildings/analyzer";
import { enumBalancerVariants, MetaBalancerBuilding } from "./buildings/balancer";
import { MetaBeltBuilding } from "./buildings/belt";
import { MetaBlockBuilding } from "./buildings/block";
import { MetaComparatorBuilding } from "./buildings/comparator";
import { MetaConstantProducerBuilding } from "./buildings/constant_producer";
import { MetaConstantSignalBuilding } from "./buildings/constant_signal";
@ -63,6 +64,7 @@ export function initMetaBuildingRegistry() {
gMetaBuildingRegistry.register(MetaComparatorBuilding);
gMetaBuildingRegistry.register(MetaItemProducerBuilding);
gMetaBuildingRegistry.register(MetaConstantProducerBuilding);
gMetaBuildingRegistry.register(MetaBlockBuilding);
// Belt
registerBuildingVariant(1, MetaBeltBuilding, defaultBuildingVariant, 0);
@ -175,6 +177,9 @@ export function initMetaBuildingRegistry() {
// Goal acceptor
registerBuildingVariant(63, MetaGoalAcceptorBuilding);
// Block
registerBuildingVariant(64, MetaBlockBuilding);
// Propagate instances
for (const key in gBuildingVariants) {
gBuildingVariants[key].metaInstance = gMetaBuildingRegistry.findByClass(

View File

@ -21,13 +21,13 @@ import { MetaComparatorBuilding } from "../buildings/comparator";
import { MetaTransistorBuilding } from "../buildings/transistor";
import { MetaConstantProducerBuilding } from "../buildings/constant_producer";
import { MetaGoalAcceptorBuilding } from "../buildings/goal_acceptor";
import { HUDConstantSignalEdit } from "../hud/parts/constant_signal_edit";
import { PuzzleSerializer } from "../../savegame/puzzle_serializer";
import { T } from "../../translations";
import { HUDPuzzlePlayMetadata } from "../hud/parts/puzzle_play_metadata";
import { createLogger } from "../../core/logging";
import { HUDPuzzleCompleteNotification } from "../hud/parts/puzzle_complete_notification";
import { HUDPuzzlePlaySettings } from "../hud/parts/puzzle_play_settings";
import { MetaBlockBuilding } from "../buildings/block";
const logger = createLogger("puzzle-play");
const copy = require("clipboard-copy");
@ -48,6 +48,7 @@ export class PuzzlePlayGameMode extends PuzzleGameMode {
this.hiddenBuildings = [
MetaConstantProducerBuilding,
MetaGoalAcceptorBuilding,
MetaBlockBuilding,
MetaStorageBuilding,
MetaReaderBuilding,
@ -106,16 +107,14 @@ export class PuzzlePlayGameMode extends PuzzleGameMode {
/**
*
* @param {boolean} liked
* @param {number} difficulty
* @param {number} time
*/
trackCompleted(liked, difficulty, time) {
trackCompleted(liked, time) {
const closeLoading = this.root.hud.parts.dialogs.showLoadingDialog();
return this.root.app.clientApi
.apiCompletePuzzle(this.puzzle.meta.id, {
time,
difficulty,
liked,
})
.catch(err => {

View File

@ -36,6 +36,7 @@ import { HUDPartTutorialHints } from "../hud/parts/tutorial_hints";
import { HUDInteractiveTutorial } from "../hud/parts/interactive_tutorial";
import { HUDSandboxController } from "../hud/parts/sandbox_controller";
import { queryParamOptions } from "../../core/query_parameters";
import { MetaBlockBuilding } from "../buildings/block";
/** @typedef {{
* shape: string,
@ -581,7 +582,7 @@ export class RegularGameMode extends GameMode {
this.additionalHudParts.sandboxController = HUDSandboxController;
}
this.hiddenBuildings = [MetaConstantProducerBuilding, MetaGoalAcceptorBuilding];
this.hiddenBuildings = [MetaConstantProducerBuilding, MetaGoalAcceptorBuilding, MetaBlockBuilding];
}
/**

View File

@ -168,7 +168,6 @@ export class ClientAPI {
* @param {number} puzzleId
* @param {object} payload
* @param {number} payload.time
* @param {number} payload.difficulty
* @param {boolean} payload.liked
* @returns {Promise<{ success: true }>}
*/

View File

@ -3,6 +3,7 @@ import { createLogger } from "../../core/logging";
import { queryParamOptions } from "../../core/query_parameters";
import { BeltComponent } from "../../game/components/belt";
import { StaticMapEntityComponent } from "../../game/components/static_map_entity";
import { RegularGameMode } from "../../game/modes/regular";
import { GameRoot } from "../../game/root";
import { InGameState } from "../../states/ingame";
import { GameAnalyticsInterface } from "../game_analytics";
@ -163,6 +164,10 @@ export class ShapezGameAnalytics extends GameAnalyticsInterface {
return;
}
if (!(root.gameMode instanceof RegularGameMode)) {
return;
}
logger.log("Sending event", category, value);
this.sendToApi("/v1/game-event", {

View File

@ -16,6 +16,7 @@ import trim from "trim";
import { enumColors } from "../game/colors";
import { COLOR_ITEM_SINGLETONS } from "../game/items/color_item";
import { ShapeDefinition } from "../game/shape_definition";
import { MetaBlockBuilding } from "../game/buildings/block";
const logger = createLogger("puzzle-serializer");
@ -67,6 +68,17 @@ export class PuzzleSerializer {
});
continue;
}
if (staticComp.getMetaBuilding().id === gMetaBuildingRegistry.findByClass(MetaBlockBuilding).id) {
buildings.push({
type: "block",
pos: {
x: staticComp.origin.x,
y: staticComp.origin.y,
r: staticComp.rotation,
},
});
}
}
const mode = /** @type {PuzzleGameMode} */ (root.gameMode);
@ -160,6 +172,21 @@ export class PuzzleSerializer {
entity.components.GoalAcceptor.item = item;
break;
}
case "block": {
const entity = root.logic.tryPlaceBuilding({
origin: new Vector(building.pos.x, building.pos.y),
building: gMetaBuildingRegistry.findByClass(MetaBlockBuilding),
originalRotation: building.pos.r,
rotation: building.pos.r,
rotationVariant: 0,
variant: defaultBuildingVariant,
});
if (!entity) {
logger.warn("Failed to place block:", building);
return "failed-to-place-block";
}
break;
}
default: {
// @ts-ignore
return "invalid-building-type: " + building.type;

View File

@ -73,11 +73,18 @@
* }} PuzzleGameBuildingGoal
*/
/**
* @typedef {{
* type: "block";
* pos: { x: number; y: number; r: number }
* }} PuzzleGameBuildingBlock
*/
/**
* @typedef {{
* version: number;
* bounds: { w: number; h: number; },
* buildings: (PuzzleGameBuildingGoal | PuzzleGameBuildingConstantProducer)[]
* buildings: (PuzzleGameBuildingGoal | PuzzleGameBuildingConstantProducer | PuzzleGameBuildingBlock)[]
* }} PuzzleGameData
*/

View File

@ -51,6 +51,8 @@ const BUILTIN_PUZZLES = G_IS_DEV
const logger = createLogger("puzzle-menu");
let lastCategory = categories[0];
export class PuzzleMenuState extends TextualGameState {
constructor() {
super("PuzzleMenuState");
@ -104,6 +106,7 @@ export class PuzzleMenuState extends TextualGameState {
}
selectCategory(category) {
lastCategory = category;
if (category === this.activeCategory) {
return;
}
@ -180,19 +183,10 @@ export class PuzzleMenuState extends TextualGameState {
stats.classList.add("stats");
elem.appendChild(stats);
if (puzzle.difficulty !== null) {
if (puzzle.downloads > 0) {
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);
difficulty.innerText = Math.round((puzzle.completions / puzzle.downloads) * 100.0) + "%";
stats.appendChild(difficulty);
}
@ -284,7 +278,7 @@ export class PuzzleMenuState extends TextualGameState {
}
onEnter(payload) {
this.selectCategory(categories[0]);
this.selectCategory(lastCategory);
if (payload && payload.error) {
this.dialogs.showWarning(payload.error.title, payload.error.desc);

View File

@ -130,7 +130,6 @@ puzzleMenu:
validatingPuzzle: Validating Puzzle
submittingPuzzle: Submitting Puzzle
noPuzzles: There are currently no puzzles in this section.
difficultyNotDetermined: Not yet determined
categories:
levels: Levels
@ -627,13 +626,6 @@ ingame:
buttonSubmit: Continue
difficulties:
- No challenge
- Easy
- Medium
- Hard
- Impossible
puzzleMetadata:
author: Author
shortKey: Short Key
@ -875,6 +867,11 @@ buildings:
name: &goal_acceptor Goal Acceptor
description: Deliver shapes to the goal acceptor to set them as a goal.
block:
default:
name: &block Block
description: Allows you to block a tile.
storyRewards:
# Those are the rewards gained from completing the store
reward_cutter_and_trash:
@ -1304,6 +1301,7 @@ keybindings:
item_producer: Item Producer (Sandbox)
constant_producer: *constant_producer
goal_acceptor: *goal_acceptor
block: *block
# ---
pipette: Pipette