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

Puzzle mode, part 3

This commit is contained in:
tobspr 2021-04-30 14:34:11 +02:00
parent f64714ec25
commit b732db58b9
30 changed files with 478 additions and 191 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -67,6 +67,14 @@
* {
color: #fff;
}
display: flex;
flex-direction: column;
.text {
text-transform: uppercase;
@include S(margin-bottom, 10px);
}
}
> .dialogInner {
@ -168,6 +176,11 @@
&.errored {
background-color: rgb(250, 206, 206);
&::placeholder {
color: #fff;
opacity: 0.8;
}
}
}

View File

@ -1,4 +1,4 @@
#ingame_HUD_ModeMenuNext {
#ingame_HUD_PuzzleReview {
position: absolute;
@include S(top, 15px);
@include S(right, 10px);

View File

@ -80,7 +80,7 @@ ingame_HUD_PinnedShapes,
ingame_HUD_GameMenu,
ingame_HUD_KeybindingOverlay,
ingame_HUD_ModeMenuBack,
ingame_HUD_ModeMenuNext,
ingame_HUD_PuzzleReview,
ingame_HUD_PuzzleEditorControls,
ingame_HUD_PuzzleEditorTitle,
ingame_HUD_ModeMenu,
@ -128,7 +128,7 @@ body.uiHidden {
#ingame_HUD_GameMenu,
#ingame_HUD_PinnedShapes,
#ingame_HUD_ModeMenuBack,
#ingame_HUD_ModeMenuNext,
#ingame_HUD_PuzzleReview,
#ingame_HUD_Notifications,
#ingame_HUD_TutorialHints,
#ingame_HUD_Waypoints,

View File

@ -71,6 +71,10 @@ export const globalConfig = {
readerAnalyzeIntervalSeconds: 10,
goalAcceptorMinimumDurationSeconds: G_IS_DEV ? 1 : 5,
goalAcceptorsPerProducer: G_IS_DEV ? 4 : 4,
puzzleModeSpeed: 3,
buildingSpeeds: {
cutter: 1 / 4,
cutterQuad: 1 / 4,

View File

@ -267,7 +267,7 @@ export class Dialog {
* Dialog which simply shows a loading spinner
*/
export class DialogLoading extends Dialog {
constructor(app) {
constructor(app, text = "") {
super({
app,
title: "",
@ -279,6 +279,8 @@ export class DialogLoading extends Dialog {
// Loading dialog can not get closed with back button
this.inputReciever.backButton.removeAll();
this.inputReciever.context = "dialog-loading";
this.text = text;
}
createElement() {
@ -287,6 +289,13 @@ export class DialogLoading extends Dialog {
elem.classList.add("loadingDialog");
this.element = elem;
if (this.text) {
const text = document.createElement("div");
text.classList.add("text");
text.innerText = this.text;
elem.appendChild(text);
}
const loader = document.createElement("div");
loader.classList.add("prefab_LoadingTextWithAnim");
loader.classList.add("loadingIndicator");
@ -309,7 +318,7 @@ export class DialogOptionChooser extends Dialog {
<div class='option ${value === options.active ? "active" : ""} ${
iconPrefix ? "hasIcon" : ""
}' data-optionvalue='${value}'>
${iconHtml}
${iconHtml}
<span class='title'>${text}</span>
${descHtml}
</div>
@ -444,7 +453,7 @@ export class DialogWithForm extends Dialog {
for (let i = 0; i < this.formElements.length; ++i) {
const elem = this.formElements[i];
elem.bindEvents(div, this.clickDetectors);
elem.valueChosen.add(this.closeRequested.dispatch, this.closeRequested);
// elem.valueChosen.add(this.closeRequested.dispatch, this.closeRequested);
elem.valueChosen.add(this.valueChosen.dispatch, this.valueChosen);
}

View File

@ -117,6 +117,11 @@ export class FormElementInput extends FormElement {
return this.element.value;
}
setValue(value) {
this.element.value = value;
this.updateErrorState();
}
focus() {
this.element.focus();
}

View File

@ -98,6 +98,18 @@ export class MetaBalancerBuilding extends MetaBuilding {
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;
}

View File

@ -29,7 +29,7 @@ export class MetaGoalAcceptorBuilding extends MetaBuilding {
slots: [
{
pos: new Vector(0, 0),
directions: [enumDirection.top],
directions: [enumDirection.bottom],
},
],
})
@ -41,12 +41,6 @@ export class MetaGoalAcceptorBuilding extends MetaBuilding {
})
);
entity.addComponent(
new BeltReaderComponent({
type: enumBeltReaderType.wireless,
})
);
entity.addComponent(new GoalAcceptorComponent({}));
}
}

View File

@ -393,11 +393,11 @@ export class Camera extends BasicSerializableObject {
}
getMaximumZoom() {
return this.root.gameMode.getMaximumZoom() * this.root.app.platformWrapper.getScreenScale();
return this.root.gameMode.getMaximumZoom();
}
getMinimumZoom() {
return this.root.gameMode.getMinimumZoom() * this.root.app.platformWrapper.getScreenScale();
return this.root.gameMode.getMinimumZoom();
}
/**

View File

@ -1,11 +1,19 @@
import { globalConfig } from "../../core/config";
import { BaseItem } from "../base_item";
import { Component } from "../component";
import { typeItemSingleton } from "../item_resolver";
export class GoalAcceptorComponent extends Component {
static getId() {
return "GoalAcceptor";
}
static getSchema() {
return {
item: typeItemSingleton,
};
}
/**
* @param {object} param0
* @param {BaseItem=} param0.item
@ -13,8 +21,25 @@ export class GoalAcceptorComponent extends Component {
*/
constructor({ item = null, rate = null }) {
super();
// ths item to produce
/** @type {BaseItem | undefined} */
this.item = item;
this.achieved = false;
// the last items we delivered
/** @type {{ item: BaseItem; time: number; }[]} */
this.deliveryHistory = [];
// Used for animations
this.displayPercentage = 0;
}
getRequiredDeliveryHistorySize() {
return (
(globalConfig.puzzleModeSpeed *
globalConfig.goalAcceptorMinimumDurationSeconds *
globalConfig.beltSpeedItemsPerSecond) /
globalConfig.goalAcceptorsPerProducer
);
}
}

View File

@ -102,12 +102,12 @@ export class GameCore {
// This isn't nice, but we need it right here
root.keyMapper = new KeyActionMapper(root, this.root.gameState.inputReciever);
// Needs to come first
root.dynamicTickrate = new DynamicTickrate(root);
// Init game mode
root.gameMode = GameMode.create(root, gameModeId);
// Needs to come first
root.dynamicTickrate = new DynamicTickrate(root);
// Init classes
root.camera = new Camera(root);
root.map = new MapView(root);

View File

@ -23,10 +23,16 @@ export class DynamicTickrate {
this.averageFps = 60;
this.setTickRate(this.root.app.settings.getDesiredFps());
const fixedRate = this.root.gameMode.getFixedTickrate();
if (fixedRate) {
logger.log("Setting fixed tickrate of", fixedRate);
this.setTickRate(fixedRate);
} else {
this.setTickRate(this.root.app.settings.getDesiredFps());
if (G_IS_DEV && globalConfig.debug.renderForTrailer) {
this.setTickRate(300);
if (G_IS_DEV && globalConfig.debug.renderForTrailer) {
this.setTickRate(300);
}
}
}
@ -99,9 +105,7 @@ export class DynamicTickrate {
this.averageTickDuration = average;
const desiredFps = this.root.app.settings.getDesiredFps();
// Disabled for now: Dynamicall adjusting tick rate
// Disabled for now: Dynamically adjusting tick rate
// if (this.averageFps > desiredFps * 0.9) {
// // if (average < maxTickDuration) {
// this.increaseTickRate();

View File

@ -172,6 +172,21 @@ export class GameMode extends BasicSerializableObject {
return true;
}
/** @returns {boolean} */
getIsDeterministic() {
return false;
}
/** @returns {boolean} */
getIsEditor() {
return false;
}
/** @returns {number | undefined} */
getFixedTickrate() {
return;
}
/** @returns {string} */
getBlueprintShapeKey() {
return "CbCbCbRb:CwCwCwCw";

View File

@ -184,10 +184,6 @@ export class HubGoals extends BasicSerializableObject {
* @param {string} upgradeId
*/
getUpgradeLevel(upgradeId) {
if (this.root.gameMode.throughputDoesNotMatter()) {
return 10;
}
return this.upgradeLevels[upgradeId] || 0;
}
@ -481,7 +477,7 @@ export class HubGoals extends BasicSerializableObject {
*/
getBeltBaseSpeed() {
if (this.root.gameMode.throughputDoesNotMatter()) {
return globalConfig.beltSpeedItemsPerSecond * 5;
return globalConfig.beltSpeedItemsPerSecond * globalConfig.puzzleModeSpeed;
}
return globalConfig.beltSpeedItemsPerSecond * this.upgradeImprovements.belt;
}
@ -492,7 +488,7 @@ export class HubGoals extends BasicSerializableObject {
*/
getUndergroundBeltBaseSpeed() {
if (this.root.gameMode.throughputDoesNotMatter()) {
return globalConfig.beltSpeedItemsPerSecond * 5;
return globalConfig.beltSpeedItemsPerSecond * globalConfig.puzzleModeSpeed;
}
return globalConfig.beltSpeedItemsPerSecond * this.upgradeImprovements.belt;
}
@ -503,7 +499,7 @@ export class HubGoals extends BasicSerializableObject {
*/
getMinerBaseSpeed() {
if (this.root.gameMode.throughputDoesNotMatter()) {
return globalConfig.minerSpeedItemsPerSecond * 5;
return globalConfig.minerSpeedItemsPerSecond * globalConfig.puzzleModeSpeed;
}
return globalConfig.minerSpeedItemsPerSecond * this.upgradeImprovements.miner;
}
@ -515,7 +511,7 @@ export class HubGoals extends BasicSerializableObject {
*/
getProcessorBaseSpeed(processorType) {
if (this.root.gameMode.throughputDoesNotMatter()) {
return 10;
return globalConfig.beltSpeedItemsPerSecond * globalConfig.puzzleModeSpeed * 10;
}
switch (processorType) {

View File

@ -25,7 +25,7 @@ import { HUDMinerHighlight } from "./parts/miner_highlight";
import { HUDModalDialogs } from "./parts/modal_dialogs";
import { HUDModeMenu } from "./parts/mode_menu";
import { HUDModeMenuBack } from "./parts/mode_menu_back";
import { HUDModeMenuNext } from "./parts/mode_menu_next";
import { HUDPuzzleReview } from "./parts/mode_puzzle_review";
import { HUDModeSettings } from "./parts/mode_settings";
import { enumNotificationType, HUDNotifications } from "./parts/notifications";
import { HUDPinnedShapes } from "./parts/pinned_shapes";
@ -88,7 +88,7 @@ export class GameHUD {
leverToggle: HUDLeverToggle,
constantSignalEdit: HUDConstantSignalEdit,
modeMenuBack: HUDModeMenuBack,
modeMenuNext: HUDModeMenuNext,
PuzzleReview: HUDPuzzleReview,
modeMenu: HUDModeMenu,
modeSettings: HUDModeSettings,
puzzleDlcLogo: HUDPuzzleDLCLogo,

View File

@ -29,11 +29,14 @@ export class HUDModalDialogs extends BaseHUDPart {
}
shouldPauseRendering() {
return this.dialogStack.length > 0;
// return this.dialogStack.length > 0;
// @todo: Check if change this affects anything
return false;
}
shouldPauseGame() {
return this.shouldPauseRendering();
// @todo: Check if this change affects anything
return false;
}
createElements(parent) {
@ -139,8 +142,8 @@ export class HUDModalDialogs extends BaseHUDPart {
}
// Returns method to be called when laoding finishd
showLoadingDialog() {
const dialog = new DialogLoading(this.app);
showLoadingDialog(text = "") {
const dialog = new DialogLoading(this.app, text);
this.internalShowDialog(dialog);
return this.closeDialog.bind(this, dialog);
}

View File

@ -1,23 +0,0 @@
import { BaseHUDPart } from "../base_hud_part";
import { makeDiv } from "../../../core/utils";
import { T } from "../../../translations";
export class HUDModeMenuNext extends BaseHUDPart {
createElements(parent) {
const key = this.root.gameMode.getId();
this.element = makeDiv(parent, "ingame_HUD_ModeMenuNext");
this.button = document.createElement("button");
this.button.classList.add("button");
this.button.textContent = T.ingame.modeMenu[key].next.title;
this.element.appendChild(this.button);
this.content = makeDiv(this.element, null, ["content"], T.ingame.modeMenu[key].next.desc);
this.trackClicks(this.button, this.next);
}
initialize() {}
next() {}
}

View File

@ -0,0 +1,167 @@
import { globalConfig, THIRDPARTY_URLS } from "../../../core/config";
import { createLogger } from "../../../core/logging";
import { DialogWithForm } from "../../../core/modal_dialog_elements";
import { FormElementInput, FormElementItemChooser } from "../../../core/modal_dialog_forms";
import { fillInLinkIntoTranslation, makeDiv } from "../../../core/utils";
import { PuzzleSerializer } from "../../../savegame/puzzle_serializer";
import { T } from "../../../translations";
import { ConstantSignalComponent } from "../../components/constant_signal";
import { GoalAcceptorComponent } from "../../components/goal_acceptor";
import { ShapeItem } from "../../items/shape_item";
import { ShapeDefinition } from "../../shape_definition";
import { BaseHUDPart } from "../base_hud_part";
const trim = require("trim");
const logger = createLogger("puzzle-review");
export class HUDPuzzleReview extends BaseHUDPart {
constructor(root) {
super(root);
this.validationEndsIn = null;
this.callOnceValidationEnded = null;
}
createElements(parent) {
const key = this.root.gameMode.getId();
this.element = makeDiv(parent, "ingame_HUD_PuzzleReview");
this.button = document.createElement("button");
this.button.classList.add("button");
this.button.textContent = T.puzzleMenu.reviewPuzzle;
this.element.appendChild(this.button);
this.trackClicks(this.button, this.startReview);
}
initialize() {}
startReview() {
const validationError = this.validatePuzzle();
if (validationError) {
this.root.hud.parts.dialogs.showWarning(T.puzzleMenu.validation.title, validationError);
return;
}
const closeLoading = this.root.hud.parts.dialogs.showLoadingDialog(T.puzzleMenu.validtingPuzzle);
this.validationEndsIn = this.root.time.now() + globalConfig.goalAcceptorMinimumDurationSeconds;
this.callOnceValidationEnded = () => {
closeLoading();
const validationError = this.validatePuzzle();
if (validationError) {
this.root.hud.parts.dialogs.showWarning(T.puzzleMenu.validation.title, validationError);
return;
}
this.startSubmit();
};
}
startSubmit() {
const regex = /^[a-zA-Z0-9_\- ]{1,20}$/;
const nameInput = new FormElementInput({
id: "nameInput",
label: T.dialogs.submitPuzzle.descName,
placeholder: T.dialogs.submitPuzzle.placeholderName,
defaultValue: "",
validator: val => val.match(regex) && trim(val).length > 0,
});
let items = new Set();
const acceptors = this.root.entityMgr.getAllWithComponent(GoalAcceptorComponent);
for (const acceptor of acceptors) {
const item = acceptor.components.GoalAcceptor.item;
if (item.getItemType() === "shape") {
items.add(item);
}
}
while (items.size < 8) {
// add some randoms
const item = this.root.hubGoals.computeFreeplayShape(Math.round(10 + Math.random() * 10000));
items.add(new ShapeItem(item));
}
const itemInput = new FormElementItemChooser({
id: "signalItem",
label: fillInLinkIntoTranslation(T.dialogs.submitPuzzle.descIcon, THIRDPARTY_URLS.shapeViewer),
items: Array.from(items),
});
const shapeKeyInput = new FormElementInput({
id: "shapeKeyInput",
label: null,
placeholder: "CuCuCuCu",
defaultValue: "",
validator: val => ShapeDefinition.isValidShortKey(trim(val)),
});
const dialog = new DialogWithForm({
app: this.root.app,
title: T.dialogs.submitPuzzle.title,
desc: "",
formElements: [nameInput, itemInput, shapeKeyInput],
buttons: ["cancel:bad:escape", "ok:good:enter"],
});
itemInput.valueChosen.add(value => {
shapeKeyInput.setValue(value.definition.getHash());
});
this.root.hud.parts.dialogs.internalShowDialog(dialog);
dialog.buttonSignals.ok.add(() => {
const title = trim(nameInput.getValue());
const shortKey = trim(shapeKeyInput.getValue());
this.doSubmitPuzzle(title, shortKey);
});
}
doSubmitPuzzle(title, shortKey) {
const serialized = new PuzzleSerializer().generateDumpFromGameRoot(this.root);
logger.log("Submitting puzzle, title=", title, "shortKey=", shortKey);
logger.log("Serialized data:", serialized);
const closeLoading = this.root.hud.parts.dialogs.showLoadingDialog(T.puzzleMenu.submittingPuzzle);
// @todo
}
update() {
if (
this.validationEndsIn &&
this.validationEndsIn < this.root.time.now() &&
this.callOnceValidationEnded
) {
const callMethod = this.callOnceValidationEnded;
this.callOnceValidationEnded = null;
callMethod();
}
}
validatePuzzle() {
// Check there is at least one constant producer and goal acceptor
const producers = this.root.entityMgr.getAllWithComponent(ConstantSignalComponent);
const acceptors = this.root.entityMgr.getAllWithComponent(GoalAcceptorComponent);
if (producers.length === 0) {
return T.puzzleMenu.validation.noProducers;
}
if (acceptors.length === 0) {
return T.puzzleMenu.validation.noGoalAcceptors;
}
// Check if all acceptors satisfy the constraints
for (const acceptor of acceptors) {
const goalComp = acceptor.components.GoalAcceptor;
if (!goalComp.item) {
return T.puzzleMenu.validation.goalAcceptorNoItem;
}
const required = goalComp.getRequiredDeliveryHistorySize();
if (goalComp.deliveryHistory.length < required) {
return T.puzzleMenu.validation.goalAcceptorRateNotMet;
}
}
}
}

View File

@ -8,9 +8,8 @@ export class HUDPuzzleEditorControls extends BaseHUDPart {
this.element.innerHTML = `
<span>1. Build constant producers to generate resources.</span>
<span>2. Build goal acceptors the capture shapes.</span>
<span>3. Produce your desired shape(s) within the puzzle area and deliver it to the goal acceptors, which will capture it.</span>
<span>4. Once you are done, press 'Playtest' to validate your puzzle.</span>
<span>2. Build goal acceptors and deliver shapes to set the puzzle goals.</span>
<span>3. Once you are done, press 'Playtest' to validate your puzzle.</span>
`;
this.titleElement = makeDiv(parent, "ingame_HUD_PuzzleEditorTitle");

View File

@ -77,6 +77,7 @@ export class MapChunkView extends MapChunk {
systems.display.drawChunk(parameters, this);
systems.storage.drawChunk(parameters, this);
systems.constantProducer.drawChunk(parameters, this);
systems.goalAcceptor.drawChunk(parameters, this);
systems.itemProcessorOverlays.drawChunk(parameters, this);
}

View File

@ -82,6 +82,10 @@ export class PuzzleGameMode extends GameMode {
return 1;
}
getMaximumZoom() {
return 4;
}
getIsSaveable() {
return false;
}
@ -98,6 +102,14 @@ export class PuzzleGameMode extends GameMode {
return false;
}
getIsDeterministic() {
return true;
}
getFixedTickrate() {
return 300;
}
/** @returns {boolean} */
getIsFreeplayAvailable() {
return true;

View File

@ -66,4 +66,8 @@ export class PuzzleEditGameMode extends PuzzleGameMode {
this.zone = this.createCenteredRectangle(this.zoneWidth, this.zoneHeight);
}
getIsEditor() {
return true;
}
}

View File

@ -2,13 +2,11 @@
import { GameRoot } from "../root";
/* typehints:end */
import { queryParamOptions } from "../../core/query_parameters";
import { findNiceIntegerValue } from "../../core/utils";
import { MetaConstantProducerBuilding } from "../buildings/constant_producer";
import { MetaGoalAcceptorBuilding } from "../buildings/goal_acceptor";
import { MetaItemProducerBuilding } from "../buildings/item_producer";
import { HUDModeMenuBack } from "../hud/parts/mode_menu_back";
import { HUDModeMenuNext } from "../hud/parts/mode_menu_next";
import { HUDPuzzleReview } from "../hud/parts/mode_puzzle_review";
import { HUDModeMenu } from "../hud/parts/mode_menu";
import { HUDModeSettings } from "../hud/parts/mode_settings";
import { enumGameModeIds, enumGameModeTypes, GameMode } from "../game_mode";
@ -520,7 +518,7 @@ export class RegularGameMode extends GameMode {
this.hiddenHurtParts = {
[HUDModeMenuBack.name]: false,
[HUDModeMenuNext.name]: false,
[HUDPuzzleReview.name]: false,
[HUDModeMenu.name]: false,
[HUDModeSettings.name]: false,
[HUDPuzzleDLCLogo.name]: false,

View File

@ -1,7 +1,6 @@
/* typehints:start */
/* typehints:end */
import { globalConfig } from "../../core/config";
import { DrawParameters } from "../../core/draw_parameters";
import { Vector } from "../../core/vector";
import { ConstantSignalComponent } from "../components/constant_signal";
import { ItemProducerComponent } from "../components/item_producer";
import { GameSystemWithFilter } from "../game_system_with_filter";
@ -43,19 +42,25 @@ export class ConstantProducerSystem extends GameSystemWithFilter {
const signalComp = contents[i].components.ConstantSignal;
if (!producerComp || !producerComp.isWireless() || !signalComp || !signalComp.isWireless()) {
return;
continue;
}
const staticComp = contents[i].components.StaticMapEntity;
const item = signalComp.signal;
if (!item) {
return;
continue;
}
// TODO: Better looking overlay
const center = staticComp.getTileSpaceBounds().getCenter().toWorldSpace();
item.drawItemCenteredClipped(center.x, center.y + 1, parameters, globalConfig.tileSize * 0.65);
const localOffset = new Vector(0, 1).rotateFastMultipleOf90(staticComp.rotation);
item.drawItemCenteredClipped(
center.x + localOffset.x,
center.y + localOffset.y,
parameters,
globalConfig.tileSize * 0.65
);
}
}
}

View File

@ -57,7 +57,7 @@ export class ConstantSignalSystem extends GameSystemWithFilter {
label: fillInLinkIntoTranslation(T.dialogs.editSignal.descShortKey, THIRDPARTY_URLS.shapeViewer),
placeholder: "",
defaultValue: "",
validator: val => this.parseSignalCode(val),
validator: val => this.parseSignalCode(entity.components.ConstantSignal.type, val),
});
const items = [
@ -93,7 +93,7 @@ export class ConstantSignalSystem extends GameSystemWithFilter {
const dialog = new DialogWithForm({
app: this.root.app,
title: T.dialogs.editSignal.title,
title: T.dialogs.editConstantProducer.title,
desc: T.dialogs.editSignal.descItems,
formElements: [itemInput, signalValueInput],
buttons: ["cancel:bad:escape", "ok:good:enter"],
@ -123,12 +123,20 @@ export class ConstantSignalSystem extends GameSystemWithFilter {
if (itemInput.chosenItem) {
constantComp.signal = itemInput.chosenItem;
} else {
constantComp.signal = this.parseSignalCode(signalValueInput.getValue());
constantComp.signal = this.parseSignalCode(
entity.components.ConstantSignal.type,
signalValueInput.getValue()
);
}
};
dialog.buttonSignals.ok.add(closeHandler);
dialog.valueChosen.add(closeHandler);
dialog.buttonSignals.ok.add(() => {
closeHandler();
});
dialog.valueChosen.add(() => {
dialog.closeRequested.dispatch();
closeHandler();
});
// When cancelled, destroy the entity again
if (deleteOnCancel) {
@ -157,10 +165,11 @@ export class ConstantSignalSystem extends GameSystemWithFilter {
/**
* Tries to parse a signal code
* @param {string} type
* @param {string} code
* @returns {BaseItem}
*/
parseSignalCode(code) {
parseSignalCode(type, code) {
if (!this.root || !this.root.shapeDefinitionMgr) {
// Stale reference
return null;
@ -172,12 +181,15 @@ export class ConstantSignalSystem extends GameSystemWithFilter {
if (enumColors[codeLower]) {
return COLOR_ITEM_SINGLETONS[codeLower];
}
if (code === "1" || codeLower === "true") {
return BOOL_TRUE_SINGLETON;
}
if (code === "0" || codeLower === "false") {
return BOOL_FALSE_SINGLETON;
if (type === enumConstantSignalType.wired) {
if (code === "1" || codeLower === "true") {
return BOOL_TRUE_SINGLETON;
}
if (code === "0" || codeLower === "false") {
return BOOL_FALSE_SINGLETON;
}
}
if (ShapeDefinition.isValidShortKey(code)) {

View File

@ -1,111 +1,114 @@
/* typehints:start */
import { GameRoot } from "../root";
/* typehints:end */
import { THIRDPARTY_URLS, globalConfig } from "../../core/config";
import { DialogWithForm } from "../../core/modal_dialog_elements";
import { FormElementInput, FormElementItemChooser } from "../../core/modal_dialog_forms";
import { fillInLinkIntoTranslation } from "../../core/utils";
import { T } from "../../translations";
import { globalConfig } from "../../core/config";
import { DrawParameters } from "../../core/draw_parameters";
import { clamp, lerp } from "../../core/utils";
import { Vector } from "../../core/vector";
import { GoalAcceptorComponent } from "../components/goal_acceptor";
import { GameSystemWithFilter } from "../game_system_with_filter";
// import { BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON } from "../items/boolean_item";
// import { COLOR_ITEM_SINGLETONS } from "../items/color_item";
import { MapChunk } from "../map_chunk";
import { GameRoot } from "../root";
export class GoalAcceptorSystem extends GameSystemWithFilter {
/** @param {GameRoot} root */
constructor(root) {
super(root, [GoalAcceptorComponent]);
this.root.signals.entityManuallyPlaced.add(this.editGoal, this);
}
update() {
const now = this.root.time.now();
for (let i = 0; i < this.allEntities.length; ++i) {
const entity = this.allEntities[i];
const goalComp = entity.components.GoalAcceptor;
const readerComp = entity.components.BeltReader;
// Check against goals (set on placement)
// filter the ones which are no longer active, or which are not the same
goalComp.deliveryHistory = goalComp.deliveryHistory.filter(
d =>
now - d.time < globalConfig.goalAcceptorMinimumDurationSeconds && d.item === goalComp.item
);
}
// Check if goal criteria has been met for all goals
}
/**
*
* @param {DrawParameters} parameters
* @param {MapChunk} chunk
* @returns
*/
drawChunk(parameters, chunk) {
/*
*const contents = chunk.containedEntitiesByLayer.regular;
*for (let i = 0; i < contents.length; ++i) {}
*/
}
const contents = chunk.containedEntitiesByLayer.regular;
for (let i = 0; i < contents.length; ++i) {
const goalComp = contents[i].components.GoalAcceptor;
editGoal(entity) {
if (!entity.components.GoalAcceptor) {
return;
}
const uid = entity.uid;
const goalComp = entity.components.GoalAcceptor;
const itemInput = new FormElementInput({
id: "goalItemInput",
label: fillInLinkIntoTranslation(T.dialogs.editGoalAcceptor.desc, THIRDPARTY_URLS.shapeViewer),
placeholder: "CuCuCuCu",
defaultValue: "CuCuCuCu",
validator: val => this.parseItem(val),
});
const dialog = new DialogWithForm({
app: this.root.app,
title: T.dialogs.editGoalAcceptor.title,
desc: "",
formElements: [itemInput],
buttons: ["cancel:bad:escape", "ok:good:enter"],
closeButton: false,
});
this.root.hud.parts.dialogs.internalShowDialog(dialog);
const closeHandler = () => {
if (this.isEntityStale(uid)) {
return;
if (!goalComp) {
continue;
}
goalComp.item = this.parseItem(itemInput.getValue());
};
const staticComp = contents[i].components.StaticMapEntity;
const item = goalComp.item;
dialog.buttonSignals.ok.add(closeHandler);
dialog.buttonSignals.cancel.add(() => {
if (this.isEntityStale(uid)) {
return;
const requiredItemsForSuccess = goalComp.getRequiredDeliveryHistorySize();
const percentage = clamp(goalComp.deliveryHistory.length / requiredItemsForSuccess, 0, 1);
const center = staticComp.getTileSpaceBounds().getCenter().toWorldSpace();
if (item) {
const localOffset = new Vector(0, -1.8).rotateFastMultipleOf90(staticComp.rotation);
item.drawItemCenteredClipped(
center.x + localOffset.x,
center.y + localOffset.y,
parameters,
globalConfig.tileSize * 0.65
);
}
this.root.logic.tryDeleteBuilding(entity);
});
}
const isValid = item && goalComp.deliveryHistory.length >= requiredItemsForSuccess;
parseRate(value) {
return Number(value);
}
parameters.context.translate(center.x, center.y);
parameters.context.rotate((staticComp.rotation / 180) * Math.PI);
parseItem(value) {
return this.root.systemMgr.systems.constantSignal.parseSignalCode(value);
}
parameters.context.lineWidth = 1;
parameters.context.fillStyle = "#8de255";
parameters.context.strokeStyle = "#64666e";
parameters.context.lineCap = "round";
isEntityStale(uid) {
if (!this.root || !this.root.entityMgr) {
return true;
// progress arc
goalComp.displayPercentage = lerp(goalComp.displayPercentage, percentage, 0.3);
const startAngle = Math.PI * 0.595;
const maxAngle = Math.PI * 1.82;
parameters.context.beginPath();
parameters.context.arc(
0.25,
-1.5,
11.6,
startAngle,
startAngle + goalComp.displayPercentage * maxAngle,
false
);
parameters.context.arc(
0.25,
-1.5,
15.5,
startAngle + goalComp.displayPercentage * maxAngle,
startAngle,
true
);
parameters.context.closePath();
parameters.context.fill();
parameters.context.stroke();
parameters.context.lineCap = "butt";
// LED indicator
parameters.context.lineWidth = 1;
parameters.context.strokeStyle = "#64666e";
parameters.context.fillStyle = isValid ? "#8de255" : "#e2555f";
parameters.context.beginCircle(10, 11.8, 3);
parameters.context.fill();
parameters.context.stroke();
parameters.context.rotate((-staticComp.rotation / 180) * Math.PI);
parameters.context.translate(-center.x, -center.y);
}
const entity = this.root.entityMgr.findByUid(uid, false);
if (!entity) {
return true;
}
const goalComp = entity.components.GoalAcceptor;
if (!goalComp) {
return true;
}
return false;
}
}

View File

@ -568,8 +568,18 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
* @param {ProcessorImplementationPayload} payload
*/
process_GOAL(payload) {
const readerComp = payload.entity.components.BeltReader;
readerComp.lastItemTimes.push(this.root.time.now());
readerComp.lastItem = payload.items[payload.items.length - 1].item;
const goalComp = payload.entity.components.GoalAcceptor;
if (this.root.gameMode.getIsEditor()) {
// while playing in editor, assign the item
goalComp.item = payload.items[0].item;
}
const now = this.root.time.now();
// push our new entry
goalComp.deliveryHistory.push({
item: payload.items[0].item,
time: now,
});
}
}

View File

@ -0,0 +1,18 @@
import { GameRoot } from "../game/root";
export class PuzzleSerializer {
/**
* Serializes the game root into a dump
* @param {GameRoot} root
* @param {boolean=} sanityChecks Whether to check for validity
* @returns {object}
*/
generateDumpFromGameRoot(root, sanityChecks = true) {
console.log("serializing", root);
return {
type: "puzzle",
contents: "foo",
};
}
}

View File

@ -124,6 +124,9 @@ puzzleMenu:
edit: Edit
title: Puzzle Mode
createPuzzle: Create Puzzle
reviewPuzzle: Review & Publish
validtingPuzzle: Validating Puzzle
submittingPuzzle: Submitting Puzzle
categories:
levels: Levels
@ -131,6 +134,14 @@ puzzleMenu:
topRated: Top Rated
myPuzzles: My Puzzles
validation:
title: Invalid Puzzle
noProducers: Please place a Constant Producer!
noGoalAcceptors: Please place a Goal Acceptor!
goalAcceptorNoItem: >-
One or more Goal Acceptors have not yet assigned an item. Deliver a shape to them to set a goal.
goalAcceptorRateNotMet: >-
One or more Goal Acceptors are not getting enough items. Make sure that the indicators are green for all acceptors.
dialogs:
buttons:
ok: OK
@ -248,9 +259,8 @@ dialogs:
Choose a pre-defined item:
descShortKey: ... or enter the <strong>short key</strong> of a shape (Which you can generate <link>here</link>)
editGoalAcceptor:
title: Set Goal
desc: Enter the <strong>short key</strong> of a shape (Which you can generate <link>here</link>). The goal will count as completed once 1 item /s is delivered.
editConstantProducer:
title: Set Item
markerDemoLimit:
desc: You can only create two custom markers in the demo. Get the standalone for unlimited markers!
@ -276,6 +286,15 @@ dialogs:
desc: >-
Unfortunately the puzzles could not be loaded:
submitPuzzle:
title: Submit Puzzle
descName: >-
Give your puzzle a name:
descIcon: >-
Please enter a unique short key, which will be shown as the icon of your puzzle (You can generate them <link>here</link>, or choose one of the randomly suggested shapes below):
placeholderName: Puzzle Title
ingame:
# This is shown in the top left corner and displays useful keybindings in
# every situation
@ -500,24 +519,6 @@ ingame:
title: Support me
desc: I develop the game in my spare time!
modeMenu:
puzzleEditMode:
back:
title: Menu
next:
title: Playtest
desc: Required for publishing
puzzleEditTestMode:
back:
title: Edit
next:
title: Publish
puzzlePlayMode:
back:
title: Menu
next:
title: Next
# All shop upgrades
shopUpgrades:
belt: