4
.gitattributes
vendored
@ -1,4 +0,0 @@
|
||||
*.wav filter=lfs diff=lfs merge=lfs -text
|
||||
*.webm filter=lfs diff=lfs merge=lfs -text
|
||||
*.mp3 filter=lfs diff=lfs merge=lfs -text
|
||||
*.psd filter=lfs diff=lfs merge=lfs -text
|
||||
@ -22,8 +22,6 @@ Your goal is to produce shapes by cutting, rotating, merging and painting parts
|
||||
|
||||
## Building
|
||||
|
||||
- Make sure git `git lfs` extension is on your path
|
||||
- Run `git lfs pull` to download sound assets
|
||||
- Make sure `ffmpeg` is on your path
|
||||
- Install Node.js and Yarn
|
||||
- Run `yarn` in the root folder
|
||||
|
||||
1
gulp/.gitattributes
vendored
@ -1 +0,0 @@
|
||||
*.wav filter=lfs diff=lfs merge=lfs -text
|
||||
26
gulp/html.js
@ -54,19 +54,19 @@ function gulptasksHTML($, gulp, buildFolder) {
|
||||
document.head.appendChild(css);
|
||||
|
||||
// Append async css
|
||||
const asyncCss = document.createElement("link");
|
||||
asyncCss.rel = "stylesheet";
|
||||
asyncCss.type = "text/css";
|
||||
asyncCss.media = "none";
|
||||
asyncCss.setAttribute("onload", "this.media='all'");
|
||||
asyncCss.href = cachebust("async-resources.css");
|
||||
if (integrity) {
|
||||
asyncCss.setAttribute(
|
||||
"integrity",
|
||||
computeIntegrityHash(path.join(buildFolder, "async-resources.css"))
|
||||
);
|
||||
}
|
||||
document.head.appendChild(asyncCss);
|
||||
// const asyncCss = document.createElement("link");
|
||||
// asyncCss.rel = "stylesheet";
|
||||
// asyncCss.type = "text/css";
|
||||
// asyncCss.media = "none";
|
||||
// asyncCss.setAttribute("onload", "this.media='all'");
|
||||
// asyncCss.href = cachebust("async-resources.css");
|
||||
// if (integrity) {
|
||||
// asyncCss.setAttribute(
|
||||
// "integrity",
|
||||
// computeIntegrityHash(path.join(buildFolder, "async-resources.css"))
|
||||
// );
|
||||
// }
|
||||
// document.head.appendChild(asyncCss);
|
||||
|
||||
if (app) {
|
||||
// Append cordova link
|
||||
|
||||
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 274 KiB After Width: | Height: | Size: 283 KiB |
|
Before Width: | Height: | Size: 713 KiB After Width: | Height: | Size: 706 KiB |
BIN
res_raw/sounds/music/theme-short.mp3
Normal file
@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:df7487fb5e8cb34cecee2519b9c3162a5107d2d7b1301c4a550904cfb108a015
|
||||
size 223361394
|
||||
BIN
res_raw/sounds/sfx/copy.wav
Normal file
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.6 KiB |
@ -1,8 +1,24 @@
|
||||
#ingame_HUD_BetaOverlay {
|
||||
position: fixed;
|
||||
@include S(top, 10px);
|
||||
@include S(right, 15px);
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
color: $colorRedBright;
|
||||
@include Heading;
|
||||
text-transform: uppercase;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
|
||||
h2 {
|
||||
@include PlainText;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #555;
|
||||
@include SuperSmallText;
|
||||
}
|
||||
}
|
||||
|
||||
@ -178,6 +178,27 @@
|
||||
display: list-item;
|
||||
}
|
||||
}
|
||||
|
||||
.ingameItemChooser {
|
||||
@include S(margin, 20px, 0);
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-auto-columns: 1fr;
|
||||
@include S(grid-column-gap, 3px);
|
||||
|
||||
canvas {
|
||||
pointer-events: all;
|
||||
@include S(width, 25px);
|
||||
@include S(height, 25px);
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
@include IncreasedClickArea(3px);
|
||||
|
||||
&:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .buttons {
|
||||
|
||||
@ -8,6 +8,7 @@ import { Signal } from "./signal";
|
||||
import { SOUNDS, MUSIC } from "../platform/sound";
|
||||
import { AtlasDefinition, atlasFiles } from "./atlas_definitions";
|
||||
import { initBuildingCodesAfterResourcesLoaded } from "../game/meta_building_registry";
|
||||
import { cachebust } from "./cachebust";
|
||||
|
||||
const logger = createLogger("background_loader");
|
||||
|
||||
@ -110,6 +111,7 @@ export class BackgroundResourcesLoader {
|
||||
essentialBareGameSounds,
|
||||
essentialBareGameAtlases
|
||||
)
|
||||
.then(() => this.internalPreloadCss("async-resources.scss"))
|
||||
.catch(err => {
|
||||
logger.warn("⏰ Failed to load essentials for bare game:", err);
|
||||
})
|
||||
@ -136,6 +138,21 @@ export class BackgroundResourcesLoader {
|
||||
});
|
||||
}
|
||||
|
||||
internalPreloadCss(name) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const link = document.createElement("link");
|
||||
|
||||
link.onload = resolve;
|
||||
link.onerror = reject;
|
||||
|
||||
link.setAttribute("rel", "stylesheet");
|
||||
link.setAttribute("media", "all");
|
||||
link.setAttribute("type", "text/css");
|
||||
link.setAttribute("href", cachebust("async-resources.css"));
|
||||
document.head.appendChild(link);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array<string>} sprites
|
||||
* @param {Array<string>} sounds
|
||||
|
||||
@ -20,6 +20,7 @@ export const THIRDPARTY_URLS = {
|
||||
discord: "https://discord.gg/HN7EVzV",
|
||||
github: "https://github.com/tobspr/shapez.io",
|
||||
reddit: "https://www.reddit.com/r/shapezio",
|
||||
shapeViewer: "https://viewer.shapez.io",
|
||||
|
||||
standaloneStorePage: "https://store.steampowered.com/app/1318690/shapezio/",
|
||||
};
|
||||
|
||||
@ -60,6 +60,8 @@ export class Dialog {
|
||||
this.buttonSignals[buttonId] = new Signal();
|
||||
}
|
||||
|
||||
this.valueChosen = new Signal();
|
||||
|
||||
this.timeouts = [];
|
||||
this.clickDetectors = [];
|
||||
|
||||
@ -431,10 +433,12 @@ 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.valueChosen.dispatch, this.valueChosen);
|
||||
}
|
||||
|
||||
waitNextFrame().then(() => {
|
||||
this.formElements[0].focus();
|
||||
this.formElements[this.formElements.length - 1].focus();
|
||||
});
|
||||
|
||||
return div;
|
||||
|
||||
@ -1,9 +1,13 @@
|
||||
import { BaseItem } from "../game/base_item";
|
||||
import { ClickDetector } from "./click_detector";
|
||||
import { Signal } from "./signal";
|
||||
|
||||
export class FormElement {
|
||||
constructor(id, label) {
|
||||
this.id = id;
|
||||
this.label = label;
|
||||
|
||||
this.valueChosen = new Signal();
|
||||
}
|
||||
|
||||
getHtml() {
|
||||
@ -148,3 +152,70 @@ export class FormElementCheckbox extends FormElement {
|
||||
|
||||
focus(parent) {}
|
||||
}
|
||||
|
||||
export class FormElementItemChooser extends FormElement {
|
||||
/**
|
||||
*
|
||||
* @param {object} param0
|
||||
* @param {string} param0.id
|
||||
* @param {string=} param0.label
|
||||
* @param {Array<BaseItem>} param0.items
|
||||
*/
|
||||
constructor({ id, label, items = [] }) {
|
||||
super(id, label);
|
||||
this.items = items;
|
||||
this.element = null;
|
||||
|
||||
/**
|
||||
* @type {BaseItem}
|
||||
*/
|
||||
this.chosenItem = null;
|
||||
}
|
||||
|
||||
getHtml() {
|
||||
let classes = [];
|
||||
|
||||
return `
|
||||
<div class="formElement">
|
||||
${this.label ? `<label>${this.label}</label>` : ""}
|
||||
<div class="ingameItemChooser input" data-formId="${this.id}"></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} parent
|
||||
* @param {Array<ClickDetector>} clickTrackers
|
||||
*/
|
||||
bindEvents(parent, clickTrackers) {
|
||||
this.element = this.getFormElement(parent);
|
||||
|
||||
for (let i = 0; i < this.items.length; ++i) {
|
||||
const item = this.items[i];
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = 128;
|
||||
canvas.height = 128;
|
||||
const context = canvas.getContext("2d");
|
||||
item.drawFullSizeOnCanvas(context, 128);
|
||||
this.element.appendChild(canvas);
|
||||
|
||||
const detector = new ClickDetector(canvas, {});
|
||||
clickTrackers.push(detector);
|
||||
detector.click.add(() => {
|
||||
this.chosenItem = item;
|
||||
this.valueChosen.dispatch(item);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
isValid() {
|
||||
return true;
|
||||
}
|
||||
|
||||
getValue() {
|
||||
return null;
|
||||
}
|
||||
|
||||
focus() {}
|
||||
}
|
||||
|
||||
@ -298,9 +298,7 @@ export function formatBigNumber(num, separator = T.global.decimalSeparator, prec
|
||||
}
|
||||
const suffix = T.global.suffix[bigNumberSuffixTranslationKeys[suffixIndex]];
|
||||
const leadingDigitsRounded = Number(leadingDigits.toPrecision(precision));
|
||||
const leadingDigitsNoTrailingDecimal = leadingDigitsRounded
|
||||
.toString()
|
||||
.replace(".", separator);
|
||||
const leadingDigitsNoTrailingDecimal = leadingDigitsRounded.toString().replace(".", separator);
|
||||
return sign + leadingDigitsNoTrailingDecimal + suffix;
|
||||
}
|
||||
|
||||
@ -669,3 +667,14 @@ export function safeModulo(n, m) {
|
||||
export function smoothPulse(time) {
|
||||
return Math.sin(time * 4) * 0.5 + 0.5;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills in a <link> tag
|
||||
* @param {string} translation
|
||||
* @param {string} link
|
||||
*/
|
||||
export function fillInLinkIntoTranslation(translation, link) {
|
||||
return translation
|
||||
.replace("<link>", "<a href='" + link + "' target='_blank'>")
|
||||
.replace("</link>", "</a>");
|
||||
}
|
||||
|
||||
@ -28,6 +28,15 @@ export class BaseItem extends BasicSerializableObject {
|
||||
return "shape";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string id of the item
|
||||
* @returns {string}
|
||||
*/
|
||||
getAsCopyableKey() {
|
||||
abstract;
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the item equals the other itme
|
||||
* @param {BaseItem} other
|
||||
@ -51,6 +60,15 @@ export class BaseItem extends BasicSerializableObject {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the item to a canvas
|
||||
* @param {CanvasRenderingContext2D} context
|
||||
* @param {number} size
|
||||
*/
|
||||
drawFullSizeOnCanvas(context, size) {
|
||||
abstract;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the item at the given position
|
||||
* @param {number} x
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
import { globalConfig } from "../core/config";
|
||||
import { DrawParameters } from "../core/draw_parameters";
|
||||
import { Loader } from "../core/loader";
|
||||
import { createLogger } from "../core/logging";
|
||||
import { findNiceIntegerValue } from "../core/utils";
|
||||
import { Vector } from "../core/vector";
|
||||
import { Entity } from "./entity";
|
||||
import { GameRoot } from "./root";
|
||||
import { findNiceIntegerValue } from "../core/utils";
|
||||
import { blueprintShape } from "./upgrades";
|
||||
import { globalConfig } from "../core/config";
|
||||
|
||||
const logger = createLogger("blueprint");
|
||||
|
||||
|
||||
@ -53,6 +53,6 @@ export class MetaWireTunnelBuilding extends MetaBuilding {
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
setupEntityComponents(entity) {
|
||||
entity.addComponent(new WireTunnelComponent({}));
|
||||
entity.addComponent(new WireTunnelComponent());
|
||||
}
|
||||
}
|
||||
|
||||
@ -502,10 +502,10 @@ export class Camera extends BasicSerializableObject {
|
||||
}
|
||||
const prevZoom = this.zoomLevel;
|
||||
|
||||
const delta = Math.sign(event.deltaY) * -0.15 * this.root.app.settings.getScrollWheelSensitivity();
|
||||
const scale = 1 + 0.15 * this.root.app.settings.getScrollWheelSensitivity();
|
||||
assert(Number.isFinite(delta), "Got invalid delta in mouse wheel event: " + event.deltaY);
|
||||
assert(Number.isFinite(this.zoomLevel), "Got invalid zoom level *before* wheel: " + this.zoomLevel);
|
||||
this.zoomLevel *= 1 + delta;
|
||||
this.zoomLevel *= (event.deltaY < 0) ? scale : 1/scale;
|
||||
assert(Number.isFinite(this.zoomLevel), "Got invalid zoom level *after* wheel: " + this.zoomLevel);
|
||||
|
||||
this.clampZoomLevel();
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import { globalConfig } from "../core/config";
|
||||
import { RandomNumberGenerator } from "../core/rng";
|
||||
import { clamp, findNiceIntegerValue, randomChoice, randomInt } from "../core/utils";
|
||||
import { BasicSerializableObject, types } from "../savegame/serialization";
|
||||
import { enumColors } from "./colors";
|
||||
import { enumItemProcessorTypes } from "./components/item_processor";
|
||||
import { enumAnalyticsDataSource } from "./production_analytics";
|
||||
import { GameRoot } from "./root";
|
||||
import { enumSubShape, ShapeDefinition } from "./shape_definition";
|
||||
import { enumHubGoalRewards, tutorialGoals } from "./tutorial_goals";
|
||||
@ -18,12 +20,6 @@ export class HubGoals extends BasicSerializableObject {
|
||||
level: types.uint,
|
||||
storedShapes: types.keyValueMap(types.uint),
|
||||
upgradeLevels: types.keyValueMap(types.uint),
|
||||
|
||||
currentGoal: types.structured({
|
||||
definition: types.knownType(ShapeDefinition),
|
||||
required: types.uint,
|
||||
reward: types.nullable(types.enum(enumHubGoalRewards)),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
@ -53,15 +49,7 @@ export class HubGoals extends BasicSerializableObject {
|
||||
}
|
||||
|
||||
// Compute current goal
|
||||
const goal = tutorialGoals[this.level - 1];
|
||||
if (goal) {
|
||||
this.currentGoal = {
|
||||
/** @type {ShapeDefinition} */
|
||||
definition: this.root.shapeDefinitionMgr.getShapeFromShortKey(goal.shape),
|
||||
required: goal.required,
|
||||
reward: goal.reward,
|
||||
};
|
||||
}
|
||||
this.computeNextGoal();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -106,7 +94,7 @@ export class HubGoals extends BasicSerializableObject {
|
||||
this.upgradeImprovements[key] = 1;
|
||||
}
|
||||
|
||||
this.createNextGoal();
|
||||
this.computeNextGoal();
|
||||
|
||||
// Allow quickly switching goals in dev mode
|
||||
if (G_IS_DEV) {
|
||||
@ -155,6 +143,13 @@ export class HubGoals extends BasicSerializableObject {
|
||||
* Returns how much of the current goal was already delivered
|
||||
*/
|
||||
getCurrentGoalDelivered() {
|
||||
if (this.currentGoal.throughputOnly) {
|
||||
return this.root.productionAnalytics.getCurrentShapeRate(
|
||||
enumAnalyticsDataSource.delivered,
|
||||
this.currentGoal.definition
|
||||
);
|
||||
}
|
||||
|
||||
return this.getShapesStored(this.currentGoal.definition);
|
||||
}
|
||||
|
||||
@ -189,9 +184,8 @@ export class HubGoals extends BasicSerializableObject {
|
||||
this.root.signals.shapeDelivered.dispatch(definition);
|
||||
|
||||
// Check if we have enough for the next level
|
||||
const targetHash = this.currentGoal.definition.getHash();
|
||||
if (
|
||||
this.storedShapes[targetHash] >= this.currentGoal.required ||
|
||||
this.getCurrentGoalDelivered() >= this.currentGoal.required ||
|
||||
(G_IS_DEV && globalConfig.debug.rewardsInstant)
|
||||
) {
|
||||
this.onGoalCompleted();
|
||||
@ -201,24 +195,28 @@ export class HubGoals extends BasicSerializableObject {
|
||||
/**
|
||||
* Creates the next goal
|
||||
*/
|
||||
createNextGoal() {
|
||||
computeNextGoal() {
|
||||
const storyIndex = this.level - 1;
|
||||
if (storyIndex < tutorialGoals.length) {
|
||||
const { shape, required, reward } = tutorialGoals[storyIndex];
|
||||
const { shape, required, reward, throughputOnly } = tutorialGoals[storyIndex];
|
||||
this.currentGoal = {
|
||||
/** @type {ShapeDefinition} */
|
||||
definition: this.root.shapeDefinitionMgr.getShapeFromShortKey(shape),
|
||||
required,
|
||||
reward,
|
||||
throughputOnly,
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
const required = 4 + (this.level - 27) * 0.25;
|
||||
|
||||
this.currentGoal = {
|
||||
/** @type {ShapeDefinition} */
|
||||
definition: this.createRandomShape(),
|
||||
required: findNiceIntegerValue(1000 + Math.pow(this.level * 2000, 0.8)),
|
||||
definition: this.computeFreeplayShape(this.level),
|
||||
required,
|
||||
reward: enumHubGoalRewards.no_reward_freeplay,
|
||||
throughputOnly: true,
|
||||
};
|
||||
}
|
||||
|
||||
@ -231,7 +229,7 @@ export class HubGoals extends BasicSerializableObject {
|
||||
|
||||
this.root.app.gameAnalytics.handleLevelCompleted(this.level);
|
||||
++this.level;
|
||||
this.createNextGoal();
|
||||
this.computeNextGoal();
|
||||
|
||||
this.root.signals.storyGoalCompleted.dispatch(this.level - 1, reward);
|
||||
}
|
||||
@ -325,15 +323,85 @@ export class HubGoals extends BasicSerializableObject {
|
||||
}
|
||||
|
||||
/**
|
||||
* Picks random colors which are close to each other
|
||||
* @param {RandomNumberGenerator} rng
|
||||
*/
|
||||
generateRandomColorSet(rng, allowUncolored = false) {
|
||||
const colorWheel = [
|
||||
enumColors.red,
|
||||
enumColors.yellow,
|
||||
enumColors.green,
|
||||
enumColors.cyan,
|
||||
enumColors.blue,
|
||||
enumColors.purple,
|
||||
enumColors.red,
|
||||
enumColors.yellow,
|
||||
];
|
||||
|
||||
const universalColors = [enumColors.white];
|
||||
if (allowUncolored) {
|
||||
universalColors.push(enumColors.uncolored);
|
||||
}
|
||||
const index = rng.nextIntRangeInclusive(0, colorWheel.length - 3);
|
||||
const pickedColors = colorWheel.slice(index, index + 3);
|
||||
pickedColors.push(rng.choice(universalColors));
|
||||
return pickedColors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a (seeded) random shape
|
||||
* @param {number} level
|
||||
* @returns {ShapeDefinition}
|
||||
*/
|
||||
createRandomShape() {
|
||||
computeFreeplayShape(level) {
|
||||
const layerCount = clamp(this.level / 25, 2, 4);
|
||||
|
||||
/** @type {Array<import("./shape_definition").ShapeLayer>} */
|
||||
let layers = [];
|
||||
|
||||
const randomColor = () => randomChoice(Object.values(enumColors));
|
||||
const randomShape = () => randomChoice(Object.values(enumSubShape));
|
||||
const rng = new RandomNumberGenerator(this.root.map.seed + "/" + level);
|
||||
|
||||
const colors = this.generateRandomColorSet(rng, level > 35);
|
||||
|
||||
let pickedSymmetry = null; // pairs of quadrants that must be the same
|
||||
let availableShapes = [enumSubShape.rect, enumSubShape.circle, enumSubShape.star];
|
||||
if (rng.next() < 0.5) {
|
||||
pickedSymmetry = [
|
||||
// radial symmetry
|
||||
[0, 2],
|
||||
[1, 3],
|
||||
];
|
||||
availableShapes.push(enumSubShape.windmill); // windmill looks good only in radial symmetry
|
||||
} else {
|
||||
const symmetries = [
|
||||
[
|
||||
// horizontal axis
|
||||
[0, 3],
|
||||
[1, 2],
|
||||
],
|
||||
[
|
||||
// vertical axis
|
||||
[0, 1],
|
||||
[2, 3],
|
||||
],
|
||||
[
|
||||
// diagonal axis
|
||||
[0, 2],
|
||||
[1],
|
||||
[3],
|
||||
],
|
||||
[
|
||||
// other diagonal axis
|
||||
[1, 3],
|
||||
[0],
|
||||
[2],
|
||||
],
|
||||
];
|
||||
pickedSymmetry = rng.choice(symmetries);
|
||||
}
|
||||
|
||||
const randomColor = () => rng.choice(colors);
|
||||
const randomShape = () => rng.choice(Object.values(enumSubShape));
|
||||
|
||||
let anyIsMissingTwo = false;
|
||||
|
||||
@ -341,23 +409,24 @@ export class HubGoals extends BasicSerializableObject {
|
||||
/** @type {import("./shape_definition").ShapeLayer} */
|
||||
const layer = [null, null, null, null];
|
||||
|
||||
for (let quad = 0; quad < 4; ++quad) {
|
||||
for (let j = 0; j < pickedSymmetry.length; ++j) {
|
||||
const group = pickedSymmetry[j];
|
||||
const shape = randomShape();
|
||||
const color = randomColor();
|
||||
for (let k = 0; k < group.length; ++k) {
|
||||
const quad = group[k];
|
||||
layer[quad] = {
|
||||
subShape: randomShape(),
|
||||
color: randomColor(),
|
||||
subShape: shape,
|
||||
color,
|
||||
};
|
||||
}
|
||||
|
||||
// Sometimes shapes are missing
|
||||
if (Math.random() > 0.85) {
|
||||
layer[randomInt(0, 3)] = null;
|
||||
}
|
||||
|
||||
// Sometimes they actually are missing *two* ones!
|
||||
// Make sure at max only one layer is missing it though, otherwise we could
|
||||
// create an uncreateable shape
|
||||
if (Math.random() > 0.95 && !anyIsMissingTwo) {
|
||||
layer[randomInt(0, 3)] = null;
|
||||
if (level > 75 && rng.next() > 0.95 && !anyIsMissingTwo) {
|
||||
layer[rng.nextIntRange(0, 4)] = null;
|
||||
anyIsMissingTwo = true;
|
||||
}
|
||||
|
||||
|
||||
@ -44,6 +44,8 @@ import { HUDWireInfo } from "./parts/wire_info";
|
||||
import { HUDLeverToggle } from "./parts/lever_toggle";
|
||||
import { HUDLayerPreview } from "./parts/layer_preview";
|
||||
import { HUDMinerHighlight } from "./parts/miner_highlight";
|
||||
import { HUDBetaOverlay } from "./parts/beta_overlay";
|
||||
import { HUDPerformanceWarning } from "./parts/performance_warning";
|
||||
|
||||
export class GameHUD {
|
||||
/**
|
||||
@ -75,7 +77,6 @@ export class GameHUD {
|
||||
pinnedShapes: new HUDPinnedShapes(this.root),
|
||||
notifications: new HUDNotifications(this.root),
|
||||
settingsMenu: new HUDSettingsMenu(this.root),
|
||||
// betaOverlay: new HUDBetaOverlay(this.root),
|
||||
debugInfo: new HUDDebugInfo(this.root),
|
||||
dialogs: new HUDModalDialogs(this.root),
|
||||
screenshotExporter: new HUDScreenshotExporter(this.root),
|
||||
@ -85,6 +86,7 @@ export class GameHUD {
|
||||
layerPreview: new HUDLayerPreview(this.root),
|
||||
|
||||
minerHighlight: new HUDMinerHighlight(this.root),
|
||||
performanceWarning: new HUDPerformanceWarning(this.root),
|
||||
|
||||
// Typing hints
|
||||
/* typehints:start */
|
||||
@ -137,6 +139,10 @@ export class GameHUD {
|
||||
this.parts.sandboxController = new HUDSandboxController(this.root);
|
||||
}
|
||||
|
||||
if (!G_IS_RELEASE) {
|
||||
this.parts.betaOverlay = new HUDBetaOverlay(this.root);
|
||||
}
|
||||
|
||||
const frag = document.createDocumentFragment();
|
||||
for (const key in this.parts) {
|
||||
this.parts[key].createElements(frag);
|
||||
|
||||
@ -3,7 +3,12 @@ import { makeDiv } from "../../../core/utils";
|
||||
|
||||
export class HUDBetaOverlay extends BaseHUDPart {
|
||||
createElements(parent) {
|
||||
this.element = makeDiv(parent, "ingame_HUD_BetaOverlay", [], "CLOSED BETA");
|
||||
this.element = makeDiv(
|
||||
parent,
|
||||
"ingame_HUD_BetaOverlay",
|
||||
[],
|
||||
"<h2>CLOSED BETA VERSION</h2><span>This version is unstable, might crash and is not final!</span>"
|
||||
);
|
||||
}
|
||||
|
||||
initialize() {}
|
||||
|
||||
@ -334,7 +334,11 @@ export class HUDBuildingPlacerLogic extends BaseHUDPart {
|
||||
const tileBelow = this.root.map.getLowerLayerContentXY(tile.x, tile.y);
|
||||
|
||||
// Check if there's a shape or color item below, if so select the miner
|
||||
if (tileBelow && this.root.app.settings.getAllSettings().pickMinerOnPatch) {
|
||||
if (
|
||||
tileBelow &&
|
||||
this.root.app.settings.getAllSettings().pickMinerOnPatch &&
|
||||
this.root.currentLayer === "regular"
|
||||
) {
|
||||
this.currentMetaBuilding.set(gMetaBuildingRegistry.findByClass(MetaMinerBuilding));
|
||||
|
||||
// Select chained miner if available, since that's always desired once unlocked
|
||||
|
||||
16
src/js/game/hud/parts/performance_warning.js
Normal file
@ -0,0 +1,16 @@
|
||||
import { T } from "../../../translations";
|
||||
import { BaseHUDPart } from "../base_hud_part";
|
||||
|
||||
export class HUDPerformanceWarning extends BaseHUDPart {
|
||||
initialize() {
|
||||
this.warningShown = false;
|
||||
this.root.signals.entityManuallyPlaced.add(this.checkAfterPlace, this);
|
||||
}
|
||||
|
||||
checkAfterPlace() {
|
||||
if (!this.warningShown && this.root.entityMgr.entities.length > 10000) {
|
||||
this.root.hud.parts.dialogs.showInfo(T.dialogs.entityWarning.title, T.dialogs.entityWarning.desc);
|
||||
this.warningShown = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,6 +4,8 @@ import { ShapeDefinition } from "../../shape_definition";
|
||||
import { BaseHUDPart } from "../base_hud_part";
|
||||
import { blueprintShape, UPGRADES } from "../../upgrades";
|
||||
import { enumHubGoalRewards } from "../../tutorial_goals";
|
||||
import { enumAnalyticsDataSource } from "../../production_analytics";
|
||||
import { T } from "../../../translations";
|
||||
|
||||
/**
|
||||
* Manages the pinned shapes on the left side of the screen
|
||||
@ -22,11 +24,13 @@ export class HUDPinnedShapes extends BaseHUDPart {
|
||||
* convenient. Also allows for cleaning up handles.
|
||||
* @type {Array<{
|
||||
* key: string,
|
||||
* definition: ShapeDefinition,
|
||||
* amountLabel: HTMLElement,
|
||||
* lastRenderedValue: string,
|
||||
* element: HTMLElement,
|
||||
* detector?: ClickDetector,
|
||||
* infoDetector?: ClickDetector
|
||||
* infoDetector?: ClickDetector,
|
||||
* throughputOnly?: boolean
|
||||
* }>}
|
||||
*/
|
||||
this.handles = [];
|
||||
@ -163,29 +167,40 @@ export class HUDPinnedShapes extends BaseHUDPart {
|
||||
this.handles = [];
|
||||
|
||||
// Pin story goal
|
||||
this.internalPinShape(currentKey, false, "goal");
|
||||
this.internalPinShape({
|
||||
key: currentKey,
|
||||
canUnpin: false,
|
||||
className: "goal",
|
||||
throughputOnly: currentGoal.throughputOnly,
|
||||
});
|
||||
|
||||
// Pin blueprint shape as well
|
||||
if (this.root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_blueprints)) {
|
||||
this.internalPinShape(blueprintShape, false, "blueprint");
|
||||
this.internalPinShape({
|
||||
key: blueprintShape,
|
||||
canUnpin: false,
|
||||
className: "blueprint",
|
||||
});
|
||||
}
|
||||
|
||||
// Pin manually pinned shapes
|
||||
for (let i = 0; i < this.pinnedShapes.length; ++i) {
|
||||
const key = this.pinnedShapes[i];
|
||||
if (key !== currentKey) {
|
||||
this.internalPinShape(key);
|
||||
this.internalPinShape({ key });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pins a new shape
|
||||
* @param {string} key
|
||||
* @param {boolean} canUnpin
|
||||
* @param {string=} className
|
||||
* @param {object} param0
|
||||
* @param {string} param0.key
|
||||
* @param {boolean=} param0.canUnpin
|
||||
* @param {string=} param0.className
|
||||
* @param {boolean=} param0.throughputOnly
|
||||
*/
|
||||
internalPinShape(key, canUnpin = true, className = null) {
|
||||
internalPinShape({ key, canUnpin = true, className = null, throughputOnly = false }) {
|
||||
const definition = this.root.shapeDefinitionMgr.getShapeFromShortKey(key);
|
||||
|
||||
const element = makeDiv(this.element, null, ["shape"]);
|
||||
@ -229,11 +244,13 @@ export class HUDPinnedShapes extends BaseHUDPart {
|
||||
|
||||
this.handles.push({
|
||||
key,
|
||||
definition,
|
||||
element,
|
||||
amountLabel,
|
||||
lastRenderedValue: "",
|
||||
detector,
|
||||
infoDetector,
|
||||
throughputOnly,
|
||||
});
|
||||
}
|
||||
|
||||
@ -244,8 +261,20 @@ export class HUDPinnedShapes extends BaseHUDPart {
|
||||
for (let i = 0; i < this.handles.length; ++i) {
|
||||
const handle = this.handles[i];
|
||||
|
||||
const currentValue = this.root.hubGoals.getShapesStoredByKey(handle.key);
|
||||
const currentValueFormatted = formatBigNumber(currentValue);
|
||||
let currentValue = this.root.hubGoals.getShapesStoredByKey(handle.key);
|
||||
let currentValueFormatted = formatBigNumber(currentValue);
|
||||
|
||||
if (handle.throughputOnly) {
|
||||
currentValue = this.root.productionAnalytics.getCurrentShapeRate(
|
||||
enumAnalyticsDataSource.delivered,
|
||||
handle.definition
|
||||
);
|
||||
currentValueFormatted = T.ingame.statistics.shapesDisplayUnits.second.replace(
|
||||
"<shapes>",
|
||||
String(currentValue)
|
||||
);
|
||||
}
|
||||
|
||||
if (currentValueFormatted !== handle.lastRenderedValue) {
|
||||
handle.lastRenderedValue = currentValueFormatted;
|
||||
handle.amountLabel.innerText = currentValueFormatted;
|
||||
|
||||
@ -113,7 +113,7 @@ export class HUDSandboxController extends BaseHUDPart {
|
||||
modifyLevel(amount) {
|
||||
const hubGoals = this.root.hubGoals;
|
||||
hubGoals.level = Math.max(1, hubGoals.level + amount);
|
||||
hubGoals.createNextGoal();
|
||||
hubGoals.computeNextGoal();
|
||||
|
||||
// Clear all shapes of this level
|
||||
hubGoals.storedShapes[hubGoals.currentGoal.definition.getHash()] = 0;
|
||||
|
||||
@ -90,17 +90,15 @@ export class HUDShop extends BaseHUDPart {
|
||||
// Max level
|
||||
handle.elemDescription.innerText = T.ingame.shop.maximumLevel.replace(
|
||||
"<currentMult>",
|
||||
currentTierMultiplier.toString()
|
||||
formatBigNumber(currentTierMultiplier)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set description
|
||||
handle.elemDescription.innerText = T.shopUpgrades[upgradeId].description
|
||||
.replace("<currentMult>", currentTierMultiplier.toString())
|
||||
.replace("<newMult>", (currentTierMultiplier + tierHandle.improvement).toString())
|
||||
// Backwards compatibility
|
||||
.replace("<gain>", (tierHandle.improvement * 100.0).toString());
|
||||
.replace("<currentMult>", formatBigNumber(currentTierMultiplier))
|
||||
.replace("<newMult>", formatBigNumber(currentTierMultiplier + tierHandle.improvement));
|
||||
|
||||
tierHandle.required.forEach(({ shape, amount }) => {
|
||||
const container = makeDiv(handle.elemRequirements, null, ["requirement"]);
|
||||
|
||||
@ -4,11 +4,12 @@ import { makeDiv } from "../../../core/utils";
|
||||
import { SOUNDS } from "../../../platform/sound";
|
||||
import { T } from "../../../translations";
|
||||
import { defaultBuildingVariant } from "../../meta_building";
|
||||
import { enumHubGoalRewards } from "../../tutorial_goals";
|
||||
import { enumHubGoalRewards, tutorialGoals } from "../../tutorial_goals";
|
||||
import { BaseHUDPart } from "../base_hud_part";
|
||||
import { DynamicDomAttach } from "../dynamic_dom_attach";
|
||||
import { enumHubGoalRewardsToContentUnlocked } from "../../tutorial_goals_mappings";
|
||||
import { InputReceiver } from "../../../core/input_receiver";
|
||||
import { enumNotificationType } from "./notifications";
|
||||
|
||||
export class HUDUnlockNotification extends BaseHUDPart {
|
||||
initialize() {
|
||||
@ -50,6 +51,14 @@ export class HUDUnlockNotification extends BaseHUDPart {
|
||||
* @param {enumHubGoalRewards} reward
|
||||
*/
|
||||
showForLevel(level, reward) {
|
||||
if (level > tutorialGoals.length) {
|
||||
this.root.hud.signals.notification.dispatch(
|
||||
T.ingame.notifications.freeplayLevelComplete.replace("<level>", String(level)),
|
||||
enumNotificationType.success
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever);
|
||||
this.elemTitle.innerText = T.ingame.levelCompleteNotification.levelTitle.replace(
|
||||
"<level>",
|
||||
|
||||
@ -1,12 +1,18 @@
|
||||
import { makeOffscreenBuffer } from "../../../core/buffer_utils";
|
||||
import { globalConfig, IS_DEMO } from "../../../core/config";
|
||||
import { globalConfig, IS_DEMO, THIRDPARTY_URLS } from "../../../core/config";
|
||||
import { DrawParameters } from "../../../core/draw_parameters";
|
||||
import { Loader } from "../../../core/loader";
|
||||
import { DialogWithForm } from "../../../core/modal_dialog_elements";
|
||||
import { FormElementInput } from "../../../core/modal_dialog_forms";
|
||||
import { Rectangle } from "../../../core/rectangle";
|
||||
import { STOP_PROPAGATION } from "../../../core/signal";
|
||||
import { arrayDeleteValue, lerp, makeDiv, removeAllChildren } from "../../../core/utils";
|
||||
import {
|
||||
arrayDeleteValue,
|
||||
fillInLinkIntoTranslation,
|
||||
lerp,
|
||||
makeDiv,
|
||||
removeAllChildren,
|
||||
} from "../../../core/utils";
|
||||
import { Vector } from "../../../core/vector";
|
||||
import { T } from "../../../translations";
|
||||
import { BaseItem } from "../../base_item";
|
||||
@ -272,7 +278,7 @@ export class HUDWaypoints extends BaseHUDPart {
|
||||
const dialog = new DialogWithForm({
|
||||
app: this.root.app,
|
||||
title: waypoint ? T.dialogs.createMarker.titleEdit : T.dialogs.createMarker.title,
|
||||
desc: T.dialogs.createMarker.desc,
|
||||
desc: fillInLinkIntoTranslation(T.dialogs.createMarker.desc, THIRDPARTY_URLS.shapeViewer),
|
||||
formElements: [markerNameInput],
|
||||
buttons: waypoint ? ["delete:bad", "cancel", "ok:good"] : ["cancel", "ok:good"],
|
||||
});
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
import { makeOffscreenBuffer } from "../../../core/buffer_utils";
|
||||
import { globalConfig } from "../../../core/config";
|
||||
import { DrawParameters } from "../../../core/draw_parameters";
|
||||
import { KEYMAPPINGS } from "../../key_action_mapper";
|
||||
import { THEME } from "../../theme";
|
||||
import { BaseHUDPart } from "../base_hud_part";
|
||||
import { Loader } from "../../../core/loader";
|
||||
import { lerp } from "../../../core/utils";
|
||||
import { SOUNDS } from "../../../platform/sound";
|
||||
import { KEYMAPPINGS } from "../../key_action_mapper";
|
||||
import { enumHubGoalRewards } from "../../tutorial_goals";
|
||||
import { BaseHUDPart } from "../base_hud_part";
|
||||
|
||||
const copy = require("clipboard-copy");
|
||||
const wiresBackgroundDpi = 4;
|
||||
|
||||
export class HUDWiresOverlay extends BaseHUDPart {
|
||||
@ -16,6 +17,7 @@ export class HUDWiresOverlay extends BaseHUDPart {
|
||||
initialize() {
|
||||
// Probably not the best location, but the one which makes most sense
|
||||
this.root.keyMapper.getBinding(KEYMAPPINGS.ingame.switchLayers).add(this.switchLayers, this);
|
||||
this.root.keyMapper.getBinding(KEYMAPPINGS.placement.copyWireValue).add(this.copyWireValue, this);
|
||||
|
||||
this.generateTilePattern();
|
||||
|
||||
@ -54,8 +56,54 @@ export class HUDWiresOverlay extends BaseHUDPart {
|
||||
|
||||
update() {
|
||||
const desiredAlpha = this.root.currentLayer === "wires" ? 1.0 : 0.0;
|
||||
|
||||
// On low performance, skip the fade
|
||||
if (this.root.entityMgr.entities.length > 5000 || this.root.dynamicTickrate.averageFps < 50) {
|
||||
this.currentAlpha = desiredAlpha;
|
||||
} else {
|
||||
this.currentAlpha = lerp(this.currentAlpha, desiredAlpha, 0.12);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the wires value below the cursor
|
||||
*/
|
||||
copyWireValue() {
|
||||
if (this.root.currentLayer !== "wires") {
|
||||
return;
|
||||
}
|
||||
|
||||
const mousePos = this.root.app.mousePosition;
|
||||
if (!mousePos) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tile = this.root.camera.screenToWorld(mousePos).toTileSpace();
|
||||
const contents = this.root.map.getLayerContentXY(tile.x, tile.y, "wires");
|
||||
if (!contents) {
|
||||
return;
|
||||
}
|
||||
|
||||
let value = null;
|
||||
if (contents.components.Wire) {
|
||||
const network = contents.components.Wire.linkedNetwork;
|
||||
if (network && network.hasValue()) {
|
||||
value = network.currentValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (contents.components.ConstantSignal) {
|
||||
value = contents.components.ConstantSignal.signal;
|
||||
}
|
||||
|
||||
if (value) {
|
||||
copy(value.getAsCopyableKey());
|
||||
this.root.soundProxy.playUi(SOUNDS.copy);
|
||||
} else {
|
||||
copy("");
|
||||
this.root.soundProxy.playUiError();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
@ -8,6 +8,9 @@ import { MetaVirtualProcessorBuilding } from "../../buildings/virtual_processor"
|
||||
import { MetaTransistorBuilding } from "../../buildings/transistor";
|
||||
import { MetaAnalyzerBuilding } from "../../buildings/analyzer";
|
||||
import { MetaComparatorBuilding } from "../../buildings/comparator";
|
||||
import { MetaReaderBuilding } from "../../buildings/reader";
|
||||
import { MetaFilterBuilding } from "../../buildings/filter";
|
||||
import { MetaDisplayBuilding } from "../../buildings/display";
|
||||
|
||||
export class HUDWiresToolbar extends HUDBaseToolbar {
|
||||
constructor(root) {
|
||||
@ -16,13 +19,18 @@ export class HUDWiresToolbar extends HUDBaseToolbar {
|
||||
MetaWireBuilding,
|
||||
MetaWireTunnelBuilding,
|
||||
MetaConstantSignalBuilding,
|
||||
MetaLeverBuilding,
|
||||
MetaLogicGateBuilding,
|
||||
MetaVirtualProcessorBuilding,
|
||||
MetaAnalyzerBuilding,
|
||||
MetaComparatorBuilding,
|
||||
MetaTransistorBuilding,
|
||||
],
|
||||
secondaryBuildings: [
|
||||
MetaReaderBuilding,
|
||||
MetaLeverBuilding,
|
||||
MetaFilterBuilding,
|
||||
MetaDisplayBuilding,
|
||||
],
|
||||
visibilityCondition: () =>
|
||||
!this.root.camera.getIsMapOverlayActive() && this.root.currentLayer === "wires",
|
||||
htmlElementId: "ingame_HUD_wires_toolbar",
|
||||
|
||||
@ -26,6 +26,13 @@ export class BooleanItem extends BaseItem {
|
||||
return "boolean";
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
getAsCopyableKey() {
|
||||
return this.value ? "1" : "0";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} value
|
||||
*/
|
||||
@ -56,6 +63,21 @@ export class BooleanItem extends BaseItem {
|
||||
}
|
||||
sprite.drawCachedCentered(parameters, x, y, diameter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the item to a canvas
|
||||
* @param {CanvasRenderingContext2D} context
|
||||
* @param {number} size
|
||||
*/
|
||||
drawFullSizeOnCanvas(context, size) {
|
||||
let sprite;
|
||||
if (this.value) {
|
||||
sprite = Loader.getSprite("sprites/wires/boolean_true.png");
|
||||
} else {
|
||||
sprite = Loader.getSprite("sprites/wires/boolean_false.png");
|
||||
}
|
||||
sprite.drawCentered(context, size / 2, size / 2, size);
|
||||
}
|
||||
}
|
||||
|
||||
export const BOOL_FALSE_SINGLETON = new BooleanItem(0);
|
||||
|
||||
@ -28,6 +28,13 @@ export class ColorItem extends BaseItem {
|
||||
return "color";
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
getAsCopyableKey() {
|
||||
return this.color;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {BaseItem} other
|
||||
*/
|
||||
@ -47,6 +54,18 @@ export class ColorItem extends BaseItem {
|
||||
return THEME.map.resources[this.color];
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the item to a canvas
|
||||
* @param {CanvasRenderingContext2D} context
|
||||
* @param {number} size
|
||||
*/
|
||||
drawFullSizeOnCanvas(context, size) {
|
||||
if (!this.cachedSprite) {
|
||||
this.cachedSprite = Loader.getSprite("sprites/colors/" + this.color + ".png");
|
||||
}
|
||||
this.cachedSprite.drawCentered(context, size / 2, size / 2, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
|
||||
@ -27,6 +27,13 @@ export class ShapeItem extends BaseItem {
|
||||
return "shape";
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
getAsCopyableKey() {
|
||||
return this.definition.getHash();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {BaseItem} other
|
||||
*/
|
||||
@ -50,6 +57,15 @@ export class ShapeItem extends BaseItem {
|
||||
return THEME.map.resources.shape;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the item to a canvas
|
||||
* @param {CanvasRenderingContext2D} context
|
||||
* @param {number} size
|
||||
*/
|
||||
drawFullSizeOnCanvas(context, size) {
|
||||
this.definition.drawFullSizeOnCanvas(context, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
|
||||
@ -67,12 +67,11 @@ export const KEYMAPPINGS = {
|
||||
wire: { keyCode: key("1") },
|
||||
wire_tunnel: { keyCode: key("2") },
|
||||
constant_signal: { keyCode: key("3") },
|
||||
lever_wires: { keyCode: key("4") },
|
||||
logic_gate: { keyCode: key("5") },
|
||||
virtual_processor: { keyCode: key("6") },
|
||||
analyzer: { keyCode: key("7") },
|
||||
comparator: { keyCode: key("8") },
|
||||
transistor: { keyCode: key("9") },
|
||||
logic_gate: { keyCode: key("4") },
|
||||
virtual_processor: { keyCode: key("5") },
|
||||
analyzer: { keyCode: key("6") },
|
||||
comparator: { keyCode: key("7") },
|
||||
transistor: { keyCode: key("8") },
|
||||
},
|
||||
|
||||
placement: {
|
||||
@ -82,6 +81,8 @@ export const KEYMAPPINGS = {
|
||||
cycleBuildingVariants: { keyCode: key("T") },
|
||||
cycleBuildings: { keyCode: 9 }, // TAB
|
||||
switchDirectionLockSide: { keyCode: key("R") },
|
||||
|
||||
copyWireValue: { keyCode: key("Z") },
|
||||
},
|
||||
|
||||
massSelect: {
|
||||
|
||||
@ -297,6 +297,15 @@ export class ShapeDefinition extends BasicSerializableObject {
|
||||
parameters.context.drawImage(canvas, x - diameter / 2, y - diameter / 2, diameter, diameter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the item to a canvas
|
||||
* @param {CanvasRenderingContext2D} context
|
||||
* @param {number} size
|
||||
*/
|
||||
drawFullSizeOnCanvas(context, size) {
|
||||
this.internalGenerateShapeBuffer(null, context, size, size, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates this shape as a canvas
|
||||
* @param {number} size
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import trim from "trim";
|
||||
import { THIRDPARTY_URLS } from "../../core/config";
|
||||
import { DialogWithForm } from "../../core/modal_dialog_elements";
|
||||
import { FormElementInput } from "../../core/modal_dialog_forms";
|
||||
import { FormElementInput, FormElementItemChooser } from "../../core/modal_dialog_forms";
|
||||
import { fillInLinkIntoTranslation } from "../../core/utils";
|
||||
import { T } from "../../translations";
|
||||
import { BaseItem } from "../base_item";
|
||||
import { enumColors } from "../colors";
|
||||
import { ConstantSignalComponent } from "../components/constant_signal";
|
||||
@ -9,6 +12,7 @@ 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 { ShapeDefinition } from "../shape_definition";
|
||||
import { blueprintShape } from "../upgrades";
|
||||
|
||||
export class ConstantSignalSystem extends GameSystemWithFilter {
|
||||
constructor(root) {
|
||||
@ -41,23 +45,35 @@ export class ConstantSignalSystem extends GameSystemWithFilter {
|
||||
|
||||
const signalValueInput = new FormElementInput({
|
||||
id: "signalValue",
|
||||
label: null,
|
||||
label: fillInLinkIntoTranslation(T.dialogs.editSignal.descShortKey, THIRDPARTY_URLS.shapeViewer),
|
||||
placeholder: "",
|
||||
defaultValue: "",
|
||||
validator: val => this.parseSignalCode(val),
|
||||
});
|
||||
|
||||
const itemInput = new FormElementItemChooser({
|
||||
id: "signalItem",
|
||||
label: null,
|
||||
items: [
|
||||
BOOL_FALSE_SINGLETON,
|
||||
BOOL_TRUE_SINGLETON,
|
||||
...Object.values(COLOR_ITEM_SINGLETONS),
|
||||
this.root.shapeDefinitionMgr.getShapeItemFromShortKey(blueprintShape),
|
||||
],
|
||||
});
|
||||
|
||||
const dialog = new DialogWithForm({
|
||||
app: this.root.app,
|
||||
title: "Set Signal",
|
||||
desc: "Enter a shape code, color or '0' or '1'",
|
||||
formElements: [signalValueInput],
|
||||
title: T.dialogs.editSignal.title,
|
||||
desc: T.dialogs.editSignal.descItems,
|
||||
formElements: [itemInput, signalValueInput],
|
||||
buttons: ["cancel:bad:escape", "ok:good:enter"],
|
||||
closeButton: false,
|
||||
});
|
||||
this.root.hud.parts.dialogs.internalShowDialog(dialog);
|
||||
|
||||
// When confirmed, set the signal
|
||||
dialog.buttonSignals.ok.add(() => {
|
||||
const closeHandler = () => {
|
||||
if (!this.root || !this.root.entityMgr) {
|
||||
// Game got stopped
|
||||
return;
|
||||
@ -75,8 +91,16 @@ export class ConstantSignalSystem extends GameSystemWithFilter {
|
||||
return;
|
||||
}
|
||||
|
||||
if (itemInput.chosenItem) {
|
||||
console.log(itemInput.chosenItem);
|
||||
constantComp.signal = itemInput.chosenItem;
|
||||
} else {
|
||||
constantComp.signal = this.parseSignalCode(signalValueInput.getValue());
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
dialog.buttonSignals.ok.add(closeHandler);
|
||||
dialog.valueChosen.add(closeHandler);
|
||||
|
||||
// When cancelled, destroy the entity again
|
||||
dialog.buttonSignals.cancel.add(() => {
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
import { globalConfig } from "../../core/config";
|
||||
import { smoothenDpi } from "../../core/dpi_manager";
|
||||
import { DrawParameters } from "../../core/draw_parameters";
|
||||
import { drawSpriteClipped } from "../../core/draw_utils";
|
||||
import { Loader } from "../../core/loader";
|
||||
import { Rectangle } from "../../core/rectangle";
|
||||
import { ORIGINAL_SPRITE_SCALE } from "../../core/sprites";
|
||||
import { formatBigNumber } from "../../core/utils";
|
||||
import { T } from "../../translations";
|
||||
import { HubComponent } from "../components/hub";
|
||||
import { Entity } from "../entity";
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { globalConfig } from "../../core/config";
|
||||
import { smoothenDpi } from "../../core/dpi_manager";
|
||||
import { drawSpriteClipped } from "../../core/draw_utils";
|
||||
import { Rectangle } from "../../core/rectangle";
|
||||
import { ORIGINAL_SPRITE_SCALE } from "../../core/sprites";
|
||||
|
||||
const HUB_SIZE_TILES = 4;
|
||||
const HUB_SIZE_PIXELS = HUB_SIZE_TILES * globalConfig.tileSize;
|
||||
@ -73,14 +73,24 @@ export class HubSystem extends GameSystemWithFilter {
|
||||
const textOffsetX = 70;
|
||||
const textOffsetY = 61;
|
||||
|
||||
if (goals.throughputOnly) {
|
||||
// Throughput
|
||||
const deliveredText = T.ingame.statistics.shapesDisplayUnits.second.replace(
|
||||
"<shapes>",
|
||||
formatBigNumber(goals.required)
|
||||
);
|
||||
|
||||
context.font = "bold 12px GameFont";
|
||||
context.fillStyle = "#64666e";
|
||||
context.textAlign = "left";
|
||||
context.fillText(deliveredText, textOffsetX, textOffsetY);
|
||||
} else {
|
||||
// Deliver count
|
||||
const delivered = this.root.hubGoals.getCurrentGoalDelivered();
|
||||
const deliveredText = "" + formatBigNumber(delivered);
|
||||
|
||||
if (delivered > 9999) {
|
||||
if (delivered > 999) {
|
||||
context.font = "bold 16px GameFont";
|
||||
} else if (delivered > 999) {
|
||||
context.font = "bold 20px GameFont";
|
||||
} else {
|
||||
context.font = "bold 25px GameFont";
|
||||
}
|
||||
@ -92,6 +102,7 @@ export class HubSystem extends GameSystemWithFilter {
|
||||
context.font = "13px GameFont";
|
||||
context.fillStyle = "#a4a6b0";
|
||||
context.fillText("/ " + formatBigNumber(goals.required), textOffsetX, textOffsetY + 13);
|
||||
}
|
||||
|
||||
// Reward
|
||||
const rewardText = T.storyRewards[goals.reward].title.toUpperCase();
|
||||
|
||||
@ -3,7 +3,7 @@ import { enumColors } from "../colors";
|
||||
import { enumLogicGateType, LogicGateComponent } from "../components/logic_gate";
|
||||
import { enumPinSlotType } from "../components/wired_pins";
|
||||
import { GameSystemWithFilter } from "../game_system_with_filter";
|
||||
import { BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON, isTruthyItem } from "../items/boolean_item";
|
||||
import { BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON, BooleanItem, isTruthyItem } from "../items/boolean_item";
|
||||
import { ColorItem, COLOR_ITEM_SINGLETONS } from "../items/color_item";
|
||||
import { ShapeItem } from "../items/shape_item";
|
||||
import { ShapeDefinition } from "../shape_definition";
|
||||
|
||||
@ -143,10 +143,10 @@ export const tutorialGoals = [
|
||||
// 14
|
||||
// Belt reader
|
||||
{
|
||||
// @todo
|
||||
shape: "CuCuCuCu",
|
||||
required: 0,
|
||||
shape: "--Cg----:--Cr----", // unused
|
||||
required: 16, // Per second!
|
||||
reward: enumHubGoalRewards.reward_belt_reader,
|
||||
throughputOnly: true,
|
||||
},
|
||||
|
||||
// 15
|
||||
@ -176,8 +176,7 @@ export const tutorialGoals = [
|
||||
// 18
|
||||
// Rotater (180deg)
|
||||
{
|
||||
// @TODO
|
||||
shape: "CuCuCuCu",
|
||||
shape: "Sg----Sg:CgCgCgCg:--CyCy--", // unused
|
||||
required: 20000,
|
||||
reward: enumHubGoalRewards.reward_rotater_180,
|
||||
},
|
||||
@ -185,8 +184,7 @@ export const tutorialGoals = [
|
||||
// 19
|
||||
// Compact splitter
|
||||
{
|
||||
// @TODO
|
||||
shape: "CuCuCuCu",
|
||||
shape: "CpRpCp--:SwSwSwSw",
|
||||
required: 25000,
|
||||
reward: enumHubGoalRewards.reward_splitter,
|
||||
},
|
||||
@ -195,15 +193,14 @@ export const tutorialGoals = [
|
||||
// WIRES
|
||||
{
|
||||
shape: finalGameShape,
|
||||
required: 50000,
|
||||
required: 25000,
|
||||
reward: enumHubGoalRewards.reward_wires_filters_and_levers,
|
||||
},
|
||||
|
||||
// 21
|
||||
// Display
|
||||
{
|
||||
// @TODO
|
||||
shape: "CuCuCuCu",
|
||||
shape: "CrCrCrCr:CwCwCwCw:CrCrCrCr:CwCwCwCw",
|
||||
required: 25000,
|
||||
reward: enumHubGoalRewards.reward_display,
|
||||
},
|
||||
@ -211,43 +208,37 @@ export const tutorialGoals = [
|
||||
// 22
|
||||
// Constant signal
|
||||
{
|
||||
// @TODO
|
||||
shape: "CuCuCuCu",
|
||||
required: 30000,
|
||||
shape: "Cg----Cr:Cw----Cw:Sy------:Cy----Cy",
|
||||
required: 25000,
|
||||
reward: enumHubGoalRewards.reward_constant_signal,
|
||||
},
|
||||
|
||||
// 23
|
||||
// Quad Painter
|
||||
{
|
||||
// @TODO
|
||||
shape: "CuCuCuCu",
|
||||
// shape: "WrRgWrRg:CwCrCwCr:SgSgSgSg", // processors t4 (two variants)
|
||||
required: 35000,
|
||||
shape: "CcSyCcSy:SyCcSyCc:CcSyCcSy",
|
||||
required: 5000,
|
||||
reward: enumHubGoalRewards.reward_painter_quad,
|
||||
},
|
||||
|
||||
// 24 Logic gates
|
||||
{
|
||||
// @TODO
|
||||
shape: "CuCuCuCu",
|
||||
required: 40000,
|
||||
shape: "CcRcCcRc:RwCwRwCw:Sr--Sw--:CyCyCyCy",
|
||||
required: 10000,
|
||||
reward: enumHubGoalRewards.reward_logic_gates,
|
||||
},
|
||||
|
||||
// 25 Virtual Processing
|
||||
{
|
||||
// @TODO
|
||||
shape: "CuCuCuCu",
|
||||
required: 45000,
|
||||
shape: "Rg--Rg--:CwRwCwRw:--Rg--Rg",
|
||||
required: 10000,
|
||||
reward: enumHubGoalRewards.reward_virtual_processing,
|
||||
},
|
||||
|
||||
// 26 Freeplay
|
||||
{
|
||||
// @TODO
|
||||
shape: "CuCuCuCu",
|
||||
required: 100000,
|
||||
shape: "CbCuCbCu:Sr------:--CrSrCr:CwCwCwCw",
|
||||
required: 10000,
|
||||
reward: enumHubGoalRewards.reward_freeplay,
|
||||
},
|
||||
];
|
||||
|
||||
@ -2,10 +2,28 @@ import { findNiceIntegerValue } from "../core/utils";
|
||||
import { ShapeDefinition } from "./shape_definition";
|
||||
|
||||
export const finalGameShape = "RuCw--Cw:----Ru--";
|
||||
export const rocketShape = "CbCuCbCu:Sr------:--CrSrCr:CwCwCwCw";
|
||||
export const blueprintShape = "CbCbCbRb:CwCwCwCw";
|
||||
|
||||
const fixedImprovements = [0.5, 0.5, 1, 1, 2, 2];
|
||||
|
||||
const numEndgameUpgrades = G_IS_DEV || G_IS_STANDALONE ? 20 - fixedImprovements.length - 1 : 0;
|
||||
|
||||
function generateEndgameUpgrades() {
|
||||
return new Array(numEndgameUpgrades).fill(null).map((_, i) => ({
|
||||
required: [
|
||||
{ shape: blueprintShape, amount: 30000 + i * 10000 },
|
||||
{ shape: finalGameShape, amount: 20000 + i * 5000 },
|
||||
{ shape: rocketShape, amount: 20000 + i * 5000 },
|
||||
],
|
||||
excludePrevious: true,
|
||||
}));
|
||||
}
|
||||
|
||||
for (let i = 0; i < numEndgameUpgrades; ++i) {
|
||||
fixedImprovements.push(0.1);
|
||||
}
|
||||
|
||||
/** @typedef {{
|
||||
* shape: string,
|
||||
* amount: number
|
||||
@ -41,6 +59,7 @@ export const UPGRADES = {
|
||||
required: [{ shape: finalGameShape, amount: 50000 }],
|
||||
excludePrevious: true,
|
||||
},
|
||||
...generateEndgameUpgrades(),
|
||||
],
|
||||
|
||||
miner: [
|
||||
@ -63,6 +82,7 @@ export const UPGRADES = {
|
||||
required: [{ shape: finalGameShape, amount: 50000 }],
|
||||
excludePrevious: true,
|
||||
},
|
||||
...generateEndgameUpgrades(),
|
||||
],
|
||||
|
||||
processors: [
|
||||
@ -85,6 +105,7 @@ export const UPGRADES = {
|
||||
required: [{ shape: finalGameShape, amount: 50000 }],
|
||||
excludePrevious: true,
|
||||
},
|
||||
...generateEndgameUpgrades(),
|
||||
],
|
||||
|
||||
painting: [
|
||||
@ -107,6 +128,7 @@ export const UPGRADES = {
|
||||
required: [{ shape: finalGameShape, amount: 50000 }],
|
||||
excludePrevious: true,
|
||||
},
|
||||
...generateEndgameUpgrades(),
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
3
src/js/globals.d.ts
vendored
@ -19,9 +19,6 @@ declare const G_BUILD_VERSION: string;
|
||||
declare const G_ALL_UI_IMAGES: Array<string>;
|
||||
declare const G_IS_RELEASE: boolean;
|
||||
|
||||
// Node require
|
||||
declare function require(...args): any;
|
||||
|
||||
// Polyfills
|
||||
declare interface String {
|
||||
replaceAll(search: string, replacement: string): string;
|
||||
|
||||
@ -25,6 +25,7 @@ export const SOUNDS = {
|
||||
destroyBuilding: "destroy_building",
|
||||
placeBuilding: "place_building",
|
||||
placeBelt: "place_belt",
|
||||
copy: "copy",
|
||||
};
|
||||
|
||||
export const MUSIC = {
|
||||
|
||||
@ -19,6 +19,7 @@ import { getCodeFromBuildingData } from "../../game/building_codes.js";
|
||||
import { StaticMapEntityComponent } from "../../game/components/static_map_entity.js";
|
||||
import { Entity } from "../../game/entity.js";
|
||||
import { defaultBuildingVariant, MetaBuilding } from "../../game/meta_building.js";
|
||||
import { finalGameShape } from "../../game/upgrades.js";
|
||||
import { SavegameInterface_V1005 } from "./1005.js";
|
||||
|
||||
const schema = require("./1006.json");
|
||||
@ -151,11 +152,26 @@ export class SavegameInterface_V1006 extends SavegameInterface_V1005 {
|
||||
stored[shapeKey] = rebalance(stored[shapeKey]);
|
||||
}
|
||||
|
||||
stored[finalGameShape] = 0;
|
||||
|
||||
// Reduce goals
|
||||
if (dump.hubGoals.currentGoal) {
|
||||
dump.hubGoals.currentGoal.required = rebalance(dump.hubGoals.currentGoal.required);
|
||||
}
|
||||
|
||||
let level = Math.min(19, dump.hubGoals.level);
|
||||
|
||||
const levelMapping = {
|
||||
14: 15,
|
||||
15: 16,
|
||||
16: 17,
|
||||
17: 18,
|
||||
18: 19,
|
||||
19: 20,
|
||||
};
|
||||
|
||||
dump.hubGoals.level = levelMapping[level] || level;
|
||||
|
||||
// Update entities
|
||||
const entities = dump.entities;
|
||||
for (let i = 0; i < entities.length; ++i) {
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
const fs = require("fs");
|
||||
const matchAll = require("match-all");
|
||||
const path = require("path");
|
||||
const YAWN = require("yawn-yaml/cjs");
|
||||
const YAML = require("yaml");
|
||||
|
||||
const files = fs
|
||||
@ -55,8 +54,6 @@ function match(originalObj, translatedObj, path = "/") {
|
||||
} else {
|
||||
console.warn(" | Unknown type: ", typeof valueOriginal);
|
||||
}
|
||||
|
||||
// const matching = translatedObj[key];
|
||||
}
|
||||
|
||||
for (const key in translatedObj) {
|
||||
@ -71,12 +68,13 @@ for (let i = 0; i < files.length; ++i) {
|
||||
const filePath = path.join(__dirname, "translations", files[i]);
|
||||
console.log("Processing", files[i]);
|
||||
const translatedContents = fs.readFileSync(filePath).toString("utf-8");
|
||||
const translated = YAML.parse(translatedContents);
|
||||
const handle = new YAWN(translatedContents);
|
||||
|
||||
const json = handle.json;
|
||||
const json = YAML.parse(translatedContents);
|
||||
match(original, json, "/");
|
||||
handle.json = json;
|
||||
|
||||
fs.writeFileSync(filePath, handle.yaml, "utf-8");
|
||||
const stringified = YAML.stringify(json, {
|
||||
indent: 4,
|
||||
simpleKeys: true,
|
||||
});
|
||||
fs.writeFileSync(filePath, stringified, "utf-8");
|
||||
}
|
||||
|
||||
@ -268,7 +268,13 @@ dialogs:
|
||||
createMarker:
|
||||
title: New Marker
|
||||
titleEdit: Edit Marker
|
||||
desc: Give it a meaningful name, you can also include a <strong>short key</strong> of a shape (Which you can generate <a href="https://viewer.shapez.io" target="_blank">here</a>)
|
||||
desc: Give it a meaningful name, you can also include a <strong>short key</strong> of a shape (Which you can generate <link>here</link>)
|
||||
|
||||
editSignal:
|
||||
title: Set Signal
|
||||
descItems: >-
|
||||
Choose a pre-defined item:
|
||||
descShortKey: ... or enter the <strong>short key</strong> of a shape (Which you can generate <link>here</link>)
|
||||
|
||||
markerDemoLimit:
|
||||
desc: You can only create two custom markers in the demo. Get the standalone for unlimited markers!
|
||||
@ -281,6 +287,10 @@ dialogs:
|
||||
title: Rename Savegame
|
||||
desc: You can rename your savegame here.
|
||||
|
||||
entityWarning:
|
||||
title: Performance Warning
|
||||
desc: You have placed a lot of buildings, this is just a friendly reminder that the game can not handle an endless count of buildings - So try to keep your factories compact!
|
||||
|
||||
ingame:
|
||||
# This is shown in the top left corner and displays useful keybindings in
|
||||
# every situation
|
||||
@ -350,6 +360,7 @@ ingame:
|
||||
notifications:
|
||||
newUpgrade: A new upgrade is available!
|
||||
gameSaved: Your game has been saved.
|
||||
freeplayLevelComplete: Level <level> has been completed!
|
||||
|
||||
# The "Upgrades" window
|
||||
shop:
|
||||
@ -360,7 +371,8 @@ ingame:
|
||||
tier: Tier <x>
|
||||
|
||||
# The roman number for each tier
|
||||
tierLabels: [I, II, III, IV, V, VI, VII, VIII, IX, X]
|
||||
tierLabels:
|
||||
[I, II, III, IV, V, VI, VII, VIII, IX, X, XI, XII, XIII, XIV, XV, XVI, XVII, XVIII, XIX, XX]
|
||||
|
||||
maximumLevel: MAXIMUM LEVEL (Speed x<currentMult>)
|
||||
|
||||
@ -788,13 +800,13 @@ storyRewards:
|
||||
no_reward_freeplay:
|
||||
title: Next level
|
||||
desc: >-
|
||||
Congratulations! By the way, more content is planned for the standalone!
|
||||
Congratulations!
|
||||
|
||||
reward_freeplay:
|
||||
title: Freeplay
|
||||
desc: >-
|
||||
You did it! You unlocked the <strong>free-play mode</strong>! This means that shapes are now <strong>randomly</strong> generated!<br><br>
|
||||
Since the hub will only require low quantities from now on, I highly recommend to build a machine which automatically delivers the requested shape!<br><br>
|
||||
Since the hub will require a <strong>throughput</strong> from now on, I highly recommend to build a machine which automatically delivers the requested shape!<br><br>
|
||||
The HUB outputs the requested shape on the wires layer, so all you have to do is to analyze it and automatically configure your factory based on that.
|
||||
|
||||
settings:
|
||||
@ -1039,8 +1051,7 @@ keybindings:
|
||||
wire: *wire
|
||||
constant_signal: *constant_signal
|
||||
logic_gate: Logic Gate
|
||||
lever: Switch (regular)
|
||||
lever_wires: Switch (wires)
|
||||
lever: *lever
|
||||
filter: *filter
|
||||
wire_tunnel: *wire_tunnel
|
||||
display: *display
|
||||
@ -1062,7 +1073,8 @@ keybindings:
|
||||
lockBeltDirection: Enable belt planner
|
||||
switchDirectionLockSide: >-
|
||||
Planner: Switch side
|
||||
|
||||
copyWireValue: >-
|
||||
Wires: Copy value below cursor
|
||||
massSelectStart: Hold and drag to start
|
||||
massSelectSelectMultiple: Select multiple areas
|
||||
massSelectCopy: Copy area
|
||||
|
||||