mirror of
https://github.com/tobspr/shapez.io.git
synced 2025-06-13 13:04:03 +00:00
Integrate with addition of new setting in master.
This commit is contained in:
commit
6f7d0a4444
12
README.md
12
README.md
@ -1,15 +1,15 @@
|
||||
# shapez.io
|
||||
|
||||
<img src="https://i.imgur.com/Y5Z2iqQ.png" alt="shapez.io Logo">
|
||||
<img src="https://i.imgur.com/W25Fkl0.png" alt="shapez.io Screenshot">
|
||||
|
||||
This is the source code for shapez.io, an open source base building game inspired by Factorio.
|
||||
|
||||
Your goal is to produce shapes by cutting, rotating, merging and painting parts of shapes.
|
||||
|
||||
## Playing
|
||||
|
||||
You can already play it [here](https://shapez.io).
|
||||
- [Trello Board & Roadmap](https://trello.com/b/ISQncpJP/shapezio)
|
||||
- [Free web version](https://shapez.io)
|
||||
- [itch.io Page](https://tobspr.itch.io/shapezio)
|
||||
- [Steam Page](https://steam.shapez.io)
|
||||
- [Official Discord](https://discord.com/invite/HN7EVzV) <- _Highly recommended to join!_
|
||||
|
||||
## Building
|
||||
|
||||
@ -46,3 +46,5 @@ This project is based on ES5. Some ES2015 features are used but most of them are
|
||||
For most assets I use Adobe Photoshop, you can find them in `assets/`.
|
||||
|
||||
You will need a <a href="https://www.codeandweb.com/texturepacker" target="_blank">Texture Packer</a> license in order to regenerate the atlas. If you don't have one but want to contribute assets, let me know and I might compile it for you. I'm currently switching to an open source solution but I can't give an estimate when thats done.
|
||||
|
||||
<img src="https://i.imgur.com/W25Fkl0.png" alt="shapez.io Screenshot">
|
||||
|
||||
9
gulp/.itch.toml
Normal file
9
gulp/.itch.toml
Normal file
@ -0,0 +1,9 @@
|
||||
[[actions]]
|
||||
name = "play"
|
||||
path = "shapezio.exe"
|
||||
platform = "windows"
|
||||
|
||||
[[actions]]
|
||||
name = "play"
|
||||
path = "play.sh"
|
||||
platform = "linux"
|
||||
@ -121,6 +121,8 @@ function gulptasksStandalone($, gulp, buildFolder) {
|
||||
* @param {boolean=} isRelease
|
||||
*/
|
||||
function packageStandalone(platform, arch, cb, isRelease = false) {
|
||||
const tomlFile = fs.readFileSync(path.join(__dirname, ".itch.toml"));
|
||||
|
||||
packager({
|
||||
dir: tempDestBuildDir,
|
||||
appCopyright: "Tobias Springer",
|
||||
@ -150,17 +152,25 @@ function gulptasksStandalone($, gulp, buildFolder) {
|
||||
fs.readFileSync(path.join(__dirname, "..", "LICENSE"))
|
||||
);
|
||||
|
||||
const playablePath = appPath + "_playable";
|
||||
fse.copySync(appPath, playablePath);
|
||||
fs.writeFileSync(path.join(playablePath, "steam_appid.txt"), "1134480");
|
||||
fs.writeFileSync(
|
||||
path.join(playablePath, "play.bat"),
|
||||
"start shapezio --dev --disable-direct-composition --in-process-gpu\r\n"
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(playablePath, "play_local.bat"),
|
||||
"start shapezio --local --dev --disable-direct-composition --in-process-gpu\r\n"
|
||||
);
|
||||
fs.writeFileSync(path.join(appPath, ".itch.toml"), tomlFile);
|
||||
|
||||
if (platform === "linux" || platform === "darwin") {
|
||||
fs.writeFileSync(path.join(appPath, "play.sh"), "#!/usr/bin/env bash\n./shapezio\n");
|
||||
fs.chmodSync(path.join(appPath, "play.sh"), 0o775);
|
||||
} else if (platform === "win32") {
|
||||
// Optional: Create a playable copy. Shouldn't be required
|
||||
// const playablePath = appPath + "_playable";
|
||||
// fse.copySync(appPath, playablePath);
|
||||
// fs.writeFileSync(path.join(playablePath, "steam_appid.txt"), "1134480");
|
||||
// fs.writeFileSync(
|
||||
// path.join(playablePath, "play.bat"),
|
||||
// "start shapezio --dev --disable-direct-composition --in-process-gpu\r\n"
|
||||
// );
|
||||
// fs.writeFileSync(
|
||||
// path.join(playablePath, "play_local.bat"),
|
||||
// "start shapezio --local --dev --disable-direct-composition --in-process-gpu\r\n"
|
||||
// );
|
||||
}
|
||||
});
|
||||
|
||||
cb();
|
||||
@ -182,10 +192,10 @@ function gulptasksStandalone($, gulp, buildFolder) {
|
||||
"standalone.package.prod",
|
||||
$.sequence("standalone.prepare", [
|
||||
"standalone.package.prod.win64",
|
||||
// "standalone.package.prod.linux64",
|
||||
"standalone.package.prod.linux64",
|
||||
"standalone.package.prod.darwin64",
|
||||
// "standalone.package.prod.win32",
|
||||
// "standalone.package.prod.linux32",
|
||||
// "standalone.package.prod.darwin64"
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
@ -11,7 +11,8 @@
|
||||
"tslint": "cd src/js && tsc",
|
||||
"lint": "npx eslint src/js",
|
||||
"prettier-all": "prettier --write src/**/*.* && prettier --write gulp/**/*.*",
|
||||
"publishOnItch": "butler push tmp_standalone_files/shapez.io-standalone-win32-x64 tobspr/shapezio:windows --userversion-file version",
|
||||
"publishOnItchWindows": "butler push tmp_standalone_files/shapez.io-standalone-win32-x64 tobspr/shapezio:windows --userversion-file version",
|
||||
"publishOnItchLinux": "butler push tmp_standalone_files/shapez.io-standalone-linux-x64 tobspr/shapezio:linux-experimental --userversion-file version",
|
||||
"publishOnSteam": "cd gulp/steampipe && ./upload.bat",
|
||||
"publishStandalone": "yarn publishOnItch && yarn publishOnSteam",
|
||||
"publishWeb": "cd gulp && yarn main.deploy.prod",
|
||||
|
||||
@ -11,7 +11,11 @@
|
||||
border-bottom-width: 0;
|
||||
transition: transform 0.12s ease-in-out;
|
||||
|
||||
background: rgba(mix(#ddd, $colorBlueBright, 80%), 0.69);
|
||||
background: rgba(mix(#ddd, $colorBlueBright, 90%), 0.75);
|
||||
|
||||
@include DarkThemeOverride {
|
||||
background: #222428;
|
||||
}
|
||||
|
||||
&:not(.visible) {
|
||||
transform: translateX(-50%) translateY(#{D(100px)});
|
||||
|
||||
@ -6,8 +6,12 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
color: #fff;
|
||||
text-shadow: #{D(1px)} #{D(1px)} 0 rgba(0, 10, 20, 0.1);
|
||||
color: #333438;
|
||||
// text-shadow: #{D(1px)} #{D(1px)} 0 rgba(0, 10, 20, 0.1);
|
||||
|
||||
@include DarkThemeOverride {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
> .binding {
|
||||
display: inline-grid;
|
||||
@ -42,10 +46,13 @@
|
||||
}
|
||||
|
||||
label {
|
||||
color: $accentColorDark;
|
||||
color: #333438;
|
||||
@include SuperSmallText;
|
||||
text-transform: uppercase;
|
||||
color: #fff;
|
||||
// color: #fff;
|
||||
@include DarkThemeOverride {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@include S(margin-left, 5px);
|
||||
}
|
||||
|
||||
@ -16,8 +16,8 @@
|
||||
grid-template-columns: auto 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
@include S(margin-bottom, 4px);
|
||||
color: #fff;
|
||||
text-shadow: #{D(1px)} #{D(1px)} 0 rgba(0, 10, 20, 0.2);
|
||||
color: #333438;
|
||||
// text-shadow: #{D(1px)} #{D(1px)} 0 rgba(0, 10, 20, 0.2);
|
||||
|
||||
&.unpinable {
|
||||
> canvas {
|
||||
@ -59,7 +59,7 @@
|
||||
|
||||
> .goalLabel {
|
||||
@include S(font-size, 7px);
|
||||
opacity: 0.5;
|
||||
opacity: 0.9;
|
||||
align-self: start;
|
||||
justify-self: start;
|
||||
font-weight: normal;
|
||||
@ -81,7 +81,6 @@
|
||||
display: inline-block;
|
||||
@include S(width, 8px);
|
||||
@include S(height, 8px);
|
||||
opacity: 0.8;
|
||||
@include S(top, 4px);
|
||||
@include S(left, -7px);
|
||||
background: uiResource("icons/current_goal_marker.png") center center / contain no-repeat;
|
||||
|
||||
@ -38,13 +38,13 @@
|
||||
@include SuperSmallText;
|
||||
pointer-events: all;
|
||||
cursor: pointer;
|
||||
color: #000;
|
||||
color: #333438;
|
||||
@include S(padding-left, 11px);
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
align-items: center;
|
||||
background: uiResource("icons/waypoint.png") left 50% / #{D(8px)} no-repeat;
|
||||
opacity: 0.5;
|
||||
opacity: 0.7;
|
||||
@include S(margin-bottom, 1px);
|
||||
font-weight: bold;
|
||||
&:hover {
|
||||
|
||||
@ -36,6 +36,9 @@
|
||||
@include S(padding, 1px, 2px);
|
||||
@include S(margin-right, 3px);
|
||||
}
|
||||
a {
|
||||
color: $colorBlueBright;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -40,6 +40,9 @@
|
||||
<meta http-equiv="Expires" content="0" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico" />
|
||||
<link rel="canonical" href="https://shapez.io" />
|
||||
|
||||
<!-- a/b testing -->
|
||||
<script src="https://www.googleoptimize.com/optimize.js?id=OPT-M5NHCV7"></script>
|
||||
</head>
|
||||
|
||||
<body oncontextmenu="return false" style="background: #393747;"></body>
|
||||
|
||||
@ -1,11 +1,25 @@
|
||||
export const CHANGELOG = [
|
||||
{
|
||||
version: "1.1.2",
|
||||
date: "unreleased",
|
||||
version: "1.1.3",
|
||||
date: "01.06.2020",
|
||||
entries: [
|
||||
"Added setting to configure zoom / mouse wheel / touchpad sensitivity",
|
||||
"Fix belts being too slow when copied via blueprint (by Dimava)",
|
||||
"Allow binding mouse buttons to actions (by Dimava)",
|
||||
"Increase readability of certain HUD elements",
|
||||
],
|
||||
},
|
||||
{
|
||||
version: "1.1.2",
|
||||
date: "30.05.2020",
|
||||
entries: [
|
||||
"The official trailer is now ready! Check it out <a href='https://www.youtube.com/watch?v=KyorY1uIqiQ' target='_blank'>here</a>!",
|
||||
"The <a href='https://steam.shapez.io' target='_blank'>steam page</a> is now live!",
|
||||
"Experimental linux builds are now available! Please give me feedback on them in the discord",
|
||||
"Allow hovering pinned shapes to enlarge them",
|
||||
"Allow deselecting blueprints with right click and 'Q'",
|
||||
"Move default key for deleting from 'X' to 'DEL'",
|
||||
"Show confirmation when deleting > 100 buildings",
|
||||
"Show confirmation when deleting more than 100 buildings",
|
||||
"Reintroduce 'SPACE' keybinding to center on map",
|
||||
"Improved keybinding hints",
|
||||
"Fixed some keybindings showing as 'undefined'",
|
||||
|
||||
@ -311,7 +311,7 @@ export class ClickDetector {
|
||||
const position = /** @type {typeof ClickDetector} */ (this.constructor).extractPointerPosition(event);
|
||||
|
||||
if (event instanceof MouseEvent) {
|
||||
const isRightClick = event.which == 3;
|
||||
const isRightClick = event.button === 2;
|
||||
if (isRightClick) {
|
||||
// Ignore right clicks
|
||||
this.rightClick.dispatch(position, event);
|
||||
@ -384,7 +384,7 @@ export class ClickDetector {
|
||||
}
|
||||
|
||||
if (event instanceof MouseEvent) {
|
||||
const isRightClick = event.which == 3;
|
||||
const isRightClick = event.button === 2;
|
||||
if (isRightClick) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -83,7 +83,7 @@ export const globalConfig = {
|
||||
|
||||
debug: {
|
||||
/* dev:start */
|
||||
fastGameEnter: true,
|
||||
// fastGameEnter: true,
|
||||
// noArtificialDelays: true,
|
||||
// disableSavegameWrite: true,
|
||||
// showEntityBounds: true,
|
||||
|
||||
@ -141,8 +141,13 @@ export class InputDistributor {
|
||||
bindToEvents() {
|
||||
window.addEventListener("popstate", this.handleBackButton.bind(this), false);
|
||||
document.addEventListener("backbutton", this.handleBackButton.bind(this), false);
|
||||
window.addEventListener("keydown", this.handleKeydown.bind(this));
|
||||
window.addEventListener("keyup", this.handleKeyup.bind(this));
|
||||
|
||||
window.addEventListener("keydown", this.handleKeyMouseDown.bind(this));
|
||||
window.addEventListener("keyup", this.handleKeyMouseUp.bind(this));
|
||||
|
||||
window.addEventListener("mousedown", this.handleKeyMouseDown.bind(this));
|
||||
window.addEventListener("mouseup", this.handleKeyMouseUp.bind(this));
|
||||
|
||||
window.addEventListener("blur", this.handleBlur.bind(this));
|
||||
}
|
||||
|
||||
@ -182,25 +187,28 @@ export class InputDistributor {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {KeyboardEvent} event
|
||||
* @param {KeyboardEvent | MouseEvent} event
|
||||
*/
|
||||
handleKeydown(event) {
|
||||
handleKeyMouseDown(event) {
|
||||
const keyCode = event instanceof MouseEvent ? event.button + 1 : event.keyCode;
|
||||
if (
|
||||
event.keyCode === 9 || // TAB
|
||||
event.keyCode === 16 || // SHIFT
|
||||
event.keyCode === 17 || // CTRL
|
||||
event.keyCode === 18 || // ALT
|
||||
(event.keyCode >= 112 && event.keyCode < 122) // F1 - F10
|
||||
keyCode === 4 || // MB4
|
||||
keyCode === 5 || // MB5
|
||||
keyCode === 9 || // TAB
|
||||
keyCode === 16 || // SHIFT
|
||||
keyCode === 17 || // CTRL
|
||||
keyCode === 18 || // ALT
|
||||
(keyCode >= 112 && keyCode < 122) // F1 - F10
|
||||
) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
const isInitial = !this.keysDown.has(event.keyCode);
|
||||
this.keysDown.add(event.keyCode);
|
||||
const isInitial = !this.keysDown.has(keyCode);
|
||||
this.keysDown.add(keyCode);
|
||||
|
||||
if (
|
||||
this.forwardToReceiver("keydown", {
|
||||
keyCode: event.keyCode,
|
||||
keyCode: keyCode,
|
||||
shift: event.shiftKey,
|
||||
alt: event.altKey,
|
||||
initial: isInitial,
|
||||
@ -210,8 +218,7 @@ export class InputDistributor {
|
||||
return;
|
||||
}
|
||||
|
||||
const code = event.keyCode;
|
||||
if (code === 27) {
|
||||
if (keyCode === 27) {
|
||||
// Escape key
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
@ -220,13 +227,14 @@ export class InputDistributor {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {KeyboardEvent} event
|
||||
* @param {KeyboardEvent | MouseEvent} event
|
||||
*/
|
||||
handleKeyup(event) {
|
||||
this.keysDown.delete(event.keyCode);
|
||||
handleKeyMouseUp(event) {
|
||||
const keyCode = event instanceof MouseEvent ? event.button + 1 : event.keyCode;
|
||||
this.keysDown.delete(keyCode);
|
||||
|
||||
this.forwardToReceiver("keyup", {
|
||||
keyCode: event.keyCode,
|
||||
keyCode: keyCode,
|
||||
shift: event.shiftKey,
|
||||
alt: event.altKey,
|
||||
});
|
||||
|
||||
@ -24,9 +24,7 @@ export const BOTTOM = new Vector(0, 1);
|
||||
export const LEFT = new Vector(-1, 0);
|
||||
export const ALL_DIRECTIONS = [TOP, RIGHT, BOTTOM, LEFT];
|
||||
|
||||
export const thousand = 1000;
|
||||
export const million = 1000 * 1000;
|
||||
export const billion = 1000 * 1000 * 1000;
|
||||
const bigNumberSuffixTranslationKeys = ["thousands", "millions", "billions", "trillions"];
|
||||
|
||||
/**
|
||||
* Returns the build id
|
||||
@ -435,21 +433,20 @@ export function formatBigNumber(num, divider = ".") {
|
||||
|
||||
if (num < 1000) {
|
||||
return sign + "" + num;
|
||||
} else {
|
||||
let leadingDigits = num;
|
||||
let suffix = "";
|
||||
for (let suffixIndex = 0; suffixIndex < bigNumberSuffixTranslationKeys.length; ++suffixIndex) {
|
||||
leadingDigits = leadingDigits / 1000;
|
||||
suffix = T.global.suffix[bigNumberSuffixTranslationKeys[suffixIndex]];
|
||||
if (leadingDigits < 1000) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
const leadingDigitsRounded = round1Digit(leadingDigits);
|
||||
const leadingDigitsNoTrailingDecimal = leadingDigitsRounded.toString().replace(".0", "");
|
||||
return sign + leadingDigitsNoTrailingDecimal + suffix;
|
||||
}
|
||||
if (num > 10000) {
|
||||
return Math_floor(num / 1000.0) + "k";
|
||||
}
|
||||
|
||||
let rest = num;
|
||||
let out = "";
|
||||
|
||||
while (rest >= 1000) {
|
||||
out = (rest % 1000).toString().padStart(3, "0") + (out !== "" ? divider : "") + out;
|
||||
rest = Math_floor(rest / 1000);
|
||||
}
|
||||
|
||||
out = rest + divider + out;
|
||||
return sign + out;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -731,14 +728,14 @@ export function checkTimerExpired(now, lastTick, tickRate) {
|
||||
Client A computes the timer and checks T > lastTick + interval. He computes
|
||||
|
||||
30 >= 29.90 + 0.1 <=> 30 >= 30.0000 <=> True <=> Tick performed
|
||||
|
||||
|
||||
However, this is what it looks on client B:
|
||||
|
||||
|
||||
33 >= 32.90 + 0.1 <=> 33 >= 32.999999999999998 <=> False <=> No tick performed!
|
||||
|
||||
|
||||
This means that Client B will only tick at the *next* frame, which means it from now is out
|
||||
of sync by one tick, which means the game will resync further or later and be not able to recover,
|
||||
since it will run into the same issue over and over.
|
||||
since it will run into the same issue over and over.
|
||||
*/
|
||||
|
||||
// The next tick, in our example it would be 30.0000 / 32.99999999998. In order to fix it, we quantize
|
||||
|
||||
@ -440,11 +440,11 @@ export class Camera extends BasicSerializableObject {
|
||||
}
|
||||
|
||||
this.touchPostMoveVelocity = new Vector(0, 0);
|
||||
if (event.which === 1) {
|
||||
if (event.button === 0) {
|
||||
this.combinedSingleTouchStartHandler(event.clientX, event.clientY);
|
||||
} else if (event.which === 2) {
|
||||
} else if (event.button === 1) {
|
||||
this.downPreHandler.dispatch(new Vector(event.clientX, event.clientY), enumMouseButton.middle);
|
||||
} else if (event.which === 3) {
|
||||
} else if (event.button === 2) {
|
||||
this.downPreHandler.dispatch(new Vector(event.clientX, event.clientY), enumMouseButton.right);
|
||||
}
|
||||
return false;
|
||||
@ -464,7 +464,7 @@ export class Camera extends BasicSerializableObject {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.which === 1) {
|
||||
if (event.button === 0) {
|
||||
this.combinedSingleTouchMoveHandler(event.clientX, event.clientY);
|
||||
}
|
||||
|
||||
@ -503,7 +503,7 @@ export class Camera extends BasicSerializableObject {
|
||||
// event.stopPropagation();
|
||||
}
|
||||
|
||||
const delta = Math.sign(event.deltaY) * -0.15;
|
||||
const delta = Math.sign(event.deltaY) * -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;
|
||||
|
||||
@ -44,7 +44,7 @@ export class ItemEjectorComponent extends Component {
|
||||
|
||||
return new ItemEjectorComponent({
|
||||
slots: slotsCopy,
|
||||
instantEject: false,
|
||||
instantEject: this.instantEject,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -48,8 +48,8 @@ export class GameHUD {
|
||||
this.parts = {
|
||||
processingOverlay: new HUDProcessingOverlay(this.root),
|
||||
buildingsToolbar: new HUDBuildingsToolbar(this.root),
|
||||
buildingPlacer: new HUDBuildingPlacer(this.root),
|
||||
blueprintPlacer: new HUDBlueprintPlacer(this.root),
|
||||
buildingPlacer: new HUDBuildingPlacer(this.root),
|
||||
unlockNotification: new HUDUnlockNotification(this.root),
|
||||
gameMenu: new HUDGameMenu(this.root),
|
||||
massSelector: new HUDMassSelector(this.root),
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { DrawParameters } from "../../../core/draw_parameters";
|
||||
//www.youtube.com/watch?v=KyorY1uIqiQimport { DrawParameters } from "../../../core/draw_parameters";
|
||||
import { STOP_PROPAGATION } from "../../../core/signal";
|
||||
import { TrackedState } from "../../../core/tracked_state";
|
||||
import { Vector } from "../../../core/vector";
|
||||
@ -36,6 +36,9 @@ export class HUDBlueprintPlacer extends BaseHUDPart {
|
||||
.getBinding(KEYMAPPINGS.placement.abortBuildingPlacement)
|
||||
.add(this.abortPlacement, this);
|
||||
keyActionMapper.getBinding(KEYMAPPINGS.placement.rotateWhilePlacing).add(this.rotateBlueprint, this);
|
||||
keyActionMapper
|
||||
.getBinding(KEYMAPPINGS.placement.abortBuildingPlacement)
|
||||
.add(this.abortPlacement, this);
|
||||
|
||||
this.root.camera.downPreHandler.add(this.onMouseDown, this);
|
||||
this.root.camera.movePreHandler.add(this.onMouseMove, this);
|
||||
|
||||
@ -89,6 +89,16 @@ for (const categoryId in KEYMAPPINGS) {
|
||||
*/
|
||||
export function getStringForKeyCode(code) {
|
||||
switch (code) {
|
||||
case 1:
|
||||
return "LMB";
|
||||
case 2:
|
||||
return "MMB";
|
||||
case 3:
|
||||
return "RMB";
|
||||
case 4:
|
||||
return "MB4";
|
||||
case 5:
|
||||
return "MB5";
|
||||
case 8:
|
||||
return "⌫";
|
||||
case 9:
|
||||
|
||||
@ -66,11 +66,6 @@ export class GoogleAnalyticsImpl extends AnalyticsInterface {
|
||||
}
|
||||
|
||||
trackUiClick(elementName) {
|
||||
// Only track a fraction of clicks to not annoy google analytics
|
||||
if (Math_random() < 0.9) {
|
||||
return;
|
||||
}
|
||||
|
||||
const stateKey = this.app.stateMgr.getCurrentState().key;
|
||||
const fullSelector = stateKey + ">" + elementName;
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@ import { createLogger } from "../core/logging";
|
||||
import { ExplainedResult } from "../core/explained_result";
|
||||
import { THEMES, THEME, applyGameTheme } from "../game/theme";
|
||||
import { IS_DEMO } from "../core/config";
|
||||
import { T } from "../translations";
|
||||
|
||||
const logger = createLogger("application_settings");
|
||||
|
||||
@ -18,27 +19,45 @@ export const uiScales = [
|
||||
{
|
||||
id: "super_small",
|
||||
size: 0.6,
|
||||
label: "Super small",
|
||||
},
|
||||
{
|
||||
id: "small",
|
||||
size: 0.8,
|
||||
label: "Small",
|
||||
},
|
||||
{
|
||||
id: "regular",
|
||||
size: 1,
|
||||
label: "Regular",
|
||||
},
|
||||
{
|
||||
id: "large",
|
||||
size: 1.2,
|
||||
label: "Large",
|
||||
},
|
||||
{
|
||||
id: "huge",
|
||||
size: 1.4,
|
||||
label: "Huge",
|
||||
},
|
||||
];
|
||||
|
||||
export const scrollWheelSensitivities = [
|
||||
{
|
||||
id: "super_slow",
|
||||
scale: 0.25,
|
||||
},
|
||||
{
|
||||
id: "slow",
|
||||
scale: 0.5,
|
||||
},
|
||||
{
|
||||
id: "regular",
|
||||
scale: 1,
|
||||
},
|
||||
{
|
||||
id: "fast",
|
||||
scale: 2,
|
||||
},
|
||||
{
|
||||
id: "super_fast",
|
||||
scale: 4,
|
||||
},
|
||||
];
|
||||
|
||||
@ -47,7 +66,7 @@ export const allApplicationSettings = [
|
||||
new EnumSetting("uiScale", {
|
||||
options: uiScales.sort((a, b) => a.size - b.size),
|
||||
valueGetter: scale => scale.id,
|
||||
textGetter: scale => scale.label,
|
||||
textGetter: scale => T.settings.labels.uiScale.scales[scale.id],
|
||||
category: categoryApp,
|
||||
restartRequired: false,
|
||||
changeCb:
|
||||
@ -56,6 +75,7 @@ export const allApplicationSettings = [
|
||||
*/
|
||||
(app, id) => app.updateAfterUiScaleChanged(),
|
||||
}),
|
||||
|
||||
new BoolSetting(
|
||||
"fullscreen",
|
||||
categoryApp,
|
||||
@ -86,6 +106,18 @@ export const allApplicationSettings = [
|
||||
*/
|
||||
(app, value) => app.sound.setMusicMuted(value)
|
||||
),
|
||||
new EnumSetting("scrollWheelSensitivity", {
|
||||
options: scrollWheelSensitivities.sort((a, b) => a.scale - b.scale),
|
||||
valueGetter: scale => scale.id,
|
||||
textGetter: scale => T.settings.labels.scrollWheelSensitivity.sensitivity[scale.id],
|
||||
category: categoryApp,
|
||||
restartRequired: false,
|
||||
changeCb:
|
||||
/**
|
||||
* @param {Application} app
|
||||
*/
|
||||
(app, id) => app.updateAfterUiScaleChanged(),
|
||||
}),
|
||||
|
||||
// GAME
|
||||
new EnumSetting("theme", {
|
||||
@ -133,6 +165,7 @@ class SettingsStorage {
|
||||
this.musicMuted = false;
|
||||
this.theme = "light";
|
||||
this.refreshRate = "60";
|
||||
this.scrollWheelSensitivity = "regular";
|
||||
|
||||
this.alwaysMultiplace = false;
|
||||
this.abortPlacementOnDeletion = true;
|
||||
@ -209,6 +242,17 @@ export class ApplicationSettings extends ReadWriteProxy {
|
||||
return 1;
|
||||
}
|
||||
|
||||
getScrollWheelSensitivity() {
|
||||
const id = this.getAllSettings().scrollWheelSensitivity;
|
||||
for (let i = 0; i < scrollWheelSensitivities.length; ++i) {
|
||||
if (scrollWheelSensitivities[i].id === id) {
|
||||
return scrollWheelSensitivities[i].scale;
|
||||
}
|
||||
}
|
||||
logger.error("Unknown scroll wheel sensitivity id:", id);
|
||||
return 1;
|
||||
}
|
||||
|
||||
getIsFullScreen() {
|
||||
return this.getAllSettings().fullscreen;
|
||||
}
|
||||
@ -295,7 +339,7 @@ export class ApplicationSettings extends ReadWriteProxy {
|
||||
}
|
||||
|
||||
getCurrentVersion() {
|
||||
return 8;
|
||||
return 9;
|
||||
}
|
||||
|
||||
/** @param {{settings: SettingsStorage, version: number}} data */
|
||||
@ -318,10 +362,15 @@ export class ApplicationSettings extends ReadWriteProxy {
|
||||
}
|
||||
|
||||
if (data.version < 8) {
|
||||
data.settings.abortPlacementOnDeletion = true;
|
||||
data.settings.scrollWheelSensitivity = "regular";
|
||||
data.version = 8;
|
||||
}
|
||||
|
||||
if (data.version < 9) {
|
||||
data.settings.abortPlacementOnDeletion = true;
|
||||
data.version = 9;
|
||||
}
|
||||
|
||||
return ExplainedResult.good();
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,8 +10,9 @@ import { BaseSavegameInterface } from "./savegame_interface";
|
||||
import { createLogger } from "../core/logging";
|
||||
import { globalConfig } from "../core/config";
|
||||
import { SavegameInterface_V1000 } from "./schemas/1000";
|
||||
import { getSavegameInterface } from "./savegame_interface_registry";
|
||||
import { getSavegameInterface, savegameInterfaces } from "./savegame_interface_registry";
|
||||
import { SavegameInterface_V1001 } from "./schemas/1001";
|
||||
import { SavegameInterface_V1002 } from "./schemas/1002";
|
||||
|
||||
const logger = createLogger("savegame");
|
||||
|
||||
@ -30,6 +31,11 @@ export class Savegame extends ReadWriteProxy {
|
||||
|
||||
/** @type {import("./savegame_typedefs").SavegameData} */
|
||||
this.currentData = this.getDefaultData();
|
||||
|
||||
assert(
|
||||
savegameInterfaces[Savegame.getCurrentVersion()],
|
||||
"Savegame interface not defined: " + Savegame.getCurrentVersion()
|
||||
);
|
||||
}
|
||||
|
||||
//////// RW Proxy Impl //////////
|
||||
@ -38,14 +44,14 @@ export class Savegame extends ReadWriteProxy {
|
||||
* @returns {number}
|
||||
*/
|
||||
static getCurrentVersion() {
|
||||
return 1001;
|
||||
return 1002;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {typeof BaseSavegameInterface}
|
||||
*/
|
||||
static getReaderClass() {
|
||||
return SavegameInterface_V1001;
|
||||
return savegameInterfaces[Savegame.getCurrentVersion()];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -82,6 +88,11 @@ export class Savegame extends ReadWriteProxy {
|
||||
data.version = 1001;
|
||||
}
|
||||
|
||||
if (data.version === 1001) {
|
||||
SavegameInterface_V1002.migrate1001to1002(data);
|
||||
data.version = 1002;
|
||||
}
|
||||
|
||||
return ExplainedResult.good();
|
||||
}
|
||||
|
||||
|
||||
@ -2,11 +2,13 @@ import { BaseSavegameInterface } from "./savegame_interface";
|
||||
import { SavegameInterface_V1000 } from "./schemas/1000";
|
||||
import { createLogger } from "../core/logging";
|
||||
import { SavegameInterface_V1001 } from "./schemas/1001";
|
||||
import { SavegameInterface_V1002 } from "./schemas/1002";
|
||||
|
||||
/** @type {Object.<number, typeof BaseSavegameInterface>} */
|
||||
const interfaces = {
|
||||
export const savegameInterfaces = {
|
||||
1000: SavegameInterface_V1000,
|
||||
1001: SavegameInterface_V1001,
|
||||
1002: SavegameInterface_V1002,
|
||||
};
|
||||
|
||||
const logger = createLogger("savegame_interface_registry");
|
||||
@ -27,7 +29,7 @@ export function getSavegameInterface(savegame) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const interfaceClass = interfaces[version];
|
||||
const interfaceClass = savegameInterfaces[version];
|
||||
if (!interfaceClass) {
|
||||
logger.warn("Version", version, "has no implemented interface!");
|
||||
return null;
|
||||
|
||||
37
src/js/savegame/schemas/1002.js
Normal file
37
src/js/savegame/schemas/1002.js
Normal file
@ -0,0 +1,37 @@
|
||||
import { createLogger } from "../../core/logging.js";
|
||||
import { T } from "../../translations.js";
|
||||
import { SavegameInterface_V1001 } from "./1001.js";
|
||||
|
||||
const schema = require("./1002.json");
|
||||
const logger = createLogger("savegame_interface/1002");
|
||||
|
||||
export class SavegameInterface_V1002 extends SavegameInterface_V1001 {
|
||||
getVersion() {
|
||||
return 1002;
|
||||
}
|
||||
|
||||
getSchemaUncached() {
|
||||
return schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("../savegame_typedefs.js").SavegameData} data
|
||||
*/
|
||||
static migrate1001to1002(data) {
|
||||
logger.log("Migrating 1001 to 1002");
|
||||
const dump = data.dump;
|
||||
if (!dump) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const entities = dump.entities;
|
||||
for (let i = 0; i < entities.length; ++i) {
|
||||
const entity = entities[i];
|
||||
const beltComp = entity.components.Belt;
|
||||
const ejectorComp = entity.components.ItemEjector;
|
||||
if (beltComp && ejectorComp) {
|
||||
ejectorComp.instantEject = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
5
src/js/savegame/schemas/1002.json
Normal file
5
src/js/savegame/schemas/1002.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"type": "object",
|
||||
"required": [],
|
||||
"additionalProperties": true
|
||||
}
|
||||
@ -105,6 +105,10 @@ export class KeybindingsState extends TextualGameState {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
if (event.target && event.target.tagName === "BUTTON" && keyCode === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
// Enter
|
||||
keyCode === 13 ||
|
||||
@ -122,8 +126,8 @@ export class KeybindingsState extends TextualGameState {
|
||||
});
|
||||
|
||||
dialog.inputReciever.backButton.add(() => {});
|
||||
|
||||
this.dialogs.internalShowDialog(dialog);
|
||||
|
||||
this.app.sound.playUiSound(SOUNDS.dialogOk);
|
||||
}
|
||||
|
||||
|
||||
@ -26,6 +26,13 @@ global:
|
||||
# How big numbers are rendered, e.g. "10,000"
|
||||
thousandsDivider: ","
|
||||
|
||||
# The suffix for large numbers, e.g. 1.3k, 400.2M, etc.
|
||||
suffix:
|
||||
thousands: k
|
||||
millions: M
|
||||
billions: B
|
||||
trillions: T
|
||||
|
||||
# Shown for infinitely big numbers
|
||||
infinite: inf
|
||||
|
||||
@ -127,7 +134,7 @@ dialogs:
|
||||
|
||||
editKeybinding:
|
||||
title: Change Keybinding
|
||||
desc: Press the key you want to assign, or escape to cancel.
|
||||
desc: Press the key or mouse button you want to assign, or escape to cancel.
|
||||
|
||||
resetKeybindingsConfirmation:
|
||||
title: Reset keybindings
|
||||
@ -511,6 +518,23 @@ settings:
|
||||
title: Interface scale
|
||||
description: >-
|
||||
Changes the size of the user interface. The interface will still scale based on your device resolution, but this setting controls the amount of scale.
|
||||
scales:
|
||||
super_small: Super small
|
||||
small: Small
|
||||
regular: Regular
|
||||
large: Large
|
||||
huge: Huge
|
||||
|
||||
scrollWheelSensitivity:
|
||||
title: Zoom sensitivity
|
||||
description: >-
|
||||
Changes how sensitive the zoom is (Either mouse wheel or trackpad).
|
||||
sensitivity:
|
||||
super_slow: Super slow
|
||||
slow: Slow
|
||||
regular: Regular
|
||||
fast: Fast
|
||||
super_fast: Super fast
|
||||
|
||||
fullscreen:
|
||||
title: Fullscreen
|
||||
|
||||
Loading…
Reference in New Issue
Block a user