mirror of
https://github.com/tobspr/shapez.io.git
synced 2025-06-13 13:04:03 +00:00
Waypoints - Step 1
This commit is contained in:
parent
5bda1ee8a5
commit
7390fa350a
95
src/css/ingame_hud/waypoints.scss
Normal file
95
src/css/ingame_hud/waypoints.scss
Normal file
@ -0,0 +1,95 @@
|
||||
#ingame_HUD_Waypoints {
|
||||
.content {
|
||||
@include S(width, 500px);
|
||||
}
|
||||
|
||||
.wizardWrap {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
@include S(column-gap, 5px);
|
||||
}
|
||||
|
||||
.content {
|
||||
@include S(margin-top, 10px);
|
||||
@include S(height, 350px);
|
||||
overflow-y: scroll;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
justify-content: flex-start;
|
||||
|
||||
@include S(padding-right, 4px);
|
||||
|
||||
> .noWaypoints {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
@include PlainText;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
> div {
|
||||
background: #f4f4f4;
|
||||
@include S(border-radius, $globalBorderRadius);
|
||||
@include S(margin-bottom, 4px);
|
||||
display: grid;
|
||||
|
||||
@include DarkThemeOverride {
|
||||
background: #222428;
|
||||
color: #efefef;
|
||||
}
|
||||
|
||||
grid-template-columns: 1fr auto;
|
||||
@include S(padding, 5px);
|
||||
@include S(padding-left, 10px);
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
@include Text;
|
||||
|
||||
@include S(padding-top, 10px);
|
||||
}
|
||||
|
||||
.position {
|
||||
@include PlainText;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
button {
|
||||
@include S(margin, 4px);
|
||||
}
|
||||
|
||||
.removeButton {
|
||||
background: #df3f3d;
|
||||
}
|
||||
|
||||
.teleportButton {
|
||||
background: #804db1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dialogInner {
|
||||
&[data-displaymode="detailed"] .content.hasEntries {
|
||||
> div {
|
||||
@include S(padding, 10px);
|
||||
@include S(height, 40px);
|
||||
grid-template-columns: auto 1fr auto;
|
||||
@include S(grid-column-gap, 15px);
|
||||
|
||||
.counter {
|
||||
grid-column: 3 / 4;
|
||||
grid-row: 1 / 2;
|
||||
@include Heading;
|
||||
align-self: center;
|
||||
text-align: right;
|
||||
color: #55595a;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -39,6 +39,7 @@
|
||||
@import "ingame_hud/vignette_overlay";
|
||||
@import "ingame_hud/statistics";
|
||||
@import "ingame_hud/pinned_shapes";
|
||||
@import "ingame_hud/waypoints";
|
||||
@import "ingame_hud/notifications";
|
||||
@import "ingame_hud/settings_menu";
|
||||
@import "ingame_hud/debug_info";
|
||||
@ -71,6 +72,7 @@ ingame_HUD_BetaOverlay,
|
||||
ingame_HUD_UnlockNotification,
|
||||
ingame_HUD_Shop,
|
||||
ingame_HUD_Statistics,
|
||||
ingame_HUD_Waypoints,
|
||||
ingame_HUD_SettingsMenu,
|
||||
ingame_HUD_ModalDialogs;
|
||||
|
||||
|
@ -56,6 +56,9 @@ export class Camera extends BasicSerializableObject {
|
||||
/** @type {Vector} */
|
||||
this.center = new Vector(0, 0);
|
||||
|
||||
/** @type {{ name: string, pos: Vector }[]} */
|
||||
this.waypoints = [];
|
||||
|
||||
// Input handling
|
||||
this.currentlyMoving = false;
|
||||
this.lastMovingPosition = null;
|
||||
@ -117,6 +120,12 @@ export class Camera extends BasicSerializableObject {
|
||||
return {
|
||||
zoomLevel: types.float,
|
||||
center: types.vector,
|
||||
waypoints: types.array(
|
||||
types.structured({
|
||||
name: types.string,
|
||||
pos: types.vector,
|
||||
})
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ import { HUDVignetteOverlay } from "./parts/vignette_overlay";
|
||||
import { HUDStatistics } from "./parts/statistics";
|
||||
import { MetaBuilding } from "../meta_building";
|
||||
import { HUDPinnedShapes } from "./parts/pinned_shapes";
|
||||
import { HUDWaypoints } from "./parts/waypoints";
|
||||
import { ShapeDefinition } from "../shape_definition";
|
||||
import { HUDNotifications, enumNotificationType } from "./parts/notifications";
|
||||
import { HUDSettingsMenu } from "./parts/settings_menu";
|
||||
@ -26,6 +27,7 @@ import { HUDEntityDebugger } from "./parts/entity_debugger";
|
||||
import { KEYMAPPINGS } from "../key_action_mapper";
|
||||
import { HUDWatermark } from "./parts/watermark";
|
||||
import { HUDModalDialogs } from "./parts/modal_dialogs";
|
||||
import { Vector } from "../../core/vector";
|
||||
|
||||
export class GameHUD {
|
||||
/**
|
||||
@ -53,6 +55,7 @@ export class GameHUD {
|
||||
|
||||
shop: new HUDShop(this.root),
|
||||
statistics: new HUDStatistics(this.root),
|
||||
waypoints: new HUDWaypoints(this.root),
|
||||
|
||||
vignetteOverlay: new HUDVignetteOverlay(this.root),
|
||||
|
||||
|
@ -29,6 +29,12 @@ export class HUDGameMenu extends BaseHUDPart {
|
||||
handler: () => this.root.hud.parts.statistics.show(),
|
||||
keybinding: KEYMAPPINGS.ingame.menuOpenStats,
|
||||
},
|
||||
{
|
||||
id: "waypoints",
|
||||
label: "Waypoints",
|
||||
handler: () => this.root.hud.parts.waypoints.show(),
|
||||
keybinding: KEYMAPPINGS.ingame.menuOpenWaypoints,
|
||||
},
|
||||
];
|
||||
|
||||
/** @type {Array<{
|
||||
|
215
src/js/game/hud/parts/waypoints.js
Normal file
215
src/js/game/hud/parts/waypoints.js
Normal file
@ -0,0 +1,215 @@
|
||||
import { BaseHUDPart } from "../base_hud_part";
|
||||
import { DrawParameters } from "../../../core/draw_parameters";
|
||||
import { makeDiv, removeAllChildren, makeButton } from "../../../core/utils";
|
||||
import { T } from "../../../translations";
|
||||
import { DynamicDomAttach } from "../dynamic_dom_attach";
|
||||
import { InputReceiver } from "../../../core/input_receiver";
|
||||
import { KeyActionMapper, KEYMAPPINGS } from "../../key_action_mapper";
|
||||
import { createLogger } from "../../../core/logging";
|
||||
|
||||
const logger = createLogger("waypoints");
|
||||
|
||||
export class HUDWaypoints extends BaseHUDPart {
|
||||
createElements(parent) {
|
||||
this.background = makeDiv(parent, "ingame_HUD_Waypoints", ["ingameDialog"]);
|
||||
|
||||
// DIALOG Inner / Wrapper
|
||||
this.dialogInner = makeDiv(this.background, null, ["dialogInner"]);
|
||||
this.title = makeDiv(this.dialogInner, null, ["title"], T.ingame.waypoints.title);
|
||||
this.closeButton = makeDiv(this.title, null, ["closeButton"]);
|
||||
this.trackClicks(this.closeButton, this.close);
|
||||
|
||||
this.wizardWrap = makeDiv(this.dialogInner, null, ["wizardWrap"]);
|
||||
|
||||
// FIXME: Make use of built-in methods
|
||||
this.nameInput = document.createElement("input");
|
||||
this.nameInput.classList.add("findOrCreate");
|
||||
this.nameInput.placeholder = T.ingame.waypoints.findOrCreate;
|
||||
|
||||
this.nameInput.addEventListener("focus", () => {
|
||||
this.root.app.inputMgr.makeSureAttachedAndOnTop(this.textInputReciever);
|
||||
});
|
||||
|
||||
this.nameInput.addEventListener("blur", () => {
|
||||
this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever);
|
||||
});
|
||||
|
||||
this.nameInput.addEventListener("keyup", ev => {
|
||||
if (ev.keyCode == 13) {
|
||||
ev.preventDefault();
|
||||
if (!this.newWaypoint()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.nameInput.blur();
|
||||
return;
|
||||
}
|
||||
|
||||
this.rerenderFull();
|
||||
});
|
||||
|
||||
this.wizardWrap.appendChild(this.nameInput);
|
||||
|
||||
this.newButton = makeButton(this.wizardWrap, ["newButton"], T.ingame.waypoints.buttonNew);
|
||||
this.trackClicks(this.newButton, this.newWaypoint);
|
||||
|
||||
this.contentDiv = makeDiv(this.dialogInner, null, ["content"]);
|
||||
}
|
||||
|
||||
initialize() {
|
||||
this.domAttach = new DynamicDomAttach(this.root, this.background, {
|
||||
attachClass: "visible",
|
||||
});
|
||||
|
||||
this.textInputReciever = new InputReceiver("waypoints_text");
|
||||
|
||||
this.inputReciever = new InputReceiver("waypoints");
|
||||
this.keyActionMapper = new KeyActionMapper(this.root, this.inputReciever);
|
||||
|
||||
this.keyActionMapper.getBinding(KEYMAPPINGS.general.back).add(this.close, this);
|
||||
this.keyActionMapper.getBinding(KEYMAPPINGS.ingame.menuOpenWaypoints).add(this.close, this);
|
||||
|
||||
this.close();
|
||||
this.rerenderFull();
|
||||
}
|
||||
|
||||
getNextWaypointName() {
|
||||
const inputName = this.nameInput.value.trim().substr(0, 32);
|
||||
if (inputName !== "") return inputName;
|
||||
|
||||
let counter = 0;
|
||||
let autoName = "The BEST name for a WAYPOINT!";
|
||||
|
||||
do {
|
||||
counter++;
|
||||
autoName = T.ingame.waypoints.defaultName.replace("<num>", counter.toString());
|
||||
} while (this.waypoints.find(w => w.name == autoName));
|
||||
|
||||
return autoName;
|
||||
}
|
||||
|
||||
newWaypoint() {
|
||||
const vector = this.root.camera.center.round();
|
||||
if (this.waypoints.find(w => w.pos.distance(vector) < 2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.waypoints.push({
|
||||
name: this.getNextWaypointName(),
|
||||
pos: vector,
|
||||
});
|
||||
|
||||
this.nameInput.value = "";
|
||||
this.rerenderFull();
|
||||
return true;
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
document.body.classList.remove("ingameDialogOpen");
|
||||
}
|
||||
|
||||
show() {
|
||||
this.visible = true;
|
||||
document.body.classList.add("ingameDialogOpen");
|
||||
this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever);
|
||||
this.rerenderFull();
|
||||
this.update();
|
||||
}
|
||||
|
||||
close() {
|
||||
this.nameInput.value = "";
|
||||
|
||||
this.visible = false;
|
||||
document.body.classList.remove("ingameDialogOpen");
|
||||
this.root.app.inputMgr.makeSureDetached(this.inputReciever);
|
||||
this.root.app.inputMgr.makeSureDetached(this.textInputReciever);
|
||||
this.update();
|
||||
}
|
||||
|
||||
update() {
|
||||
this.domAttach.update(this.visible);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} index
|
||||
*/
|
||||
removeWaypoint(index) {
|
||||
if (this.waypoints[index] === undefined) {
|
||||
logger.warn("Attempt to remove nonexisting waypoint", index);
|
||||
return;
|
||||
}
|
||||
|
||||
this.waypoints.splice(index, 1);
|
||||
this.rerenderFull();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} index
|
||||
*/
|
||||
waypointTeleport(index) {
|
||||
if (this.waypoints[index] === undefined) {
|
||||
logger.warn("Attempt to teleport to nonexisting waypoint", index);
|
||||
return;
|
||||
}
|
||||
|
||||
this.close();
|
||||
this.root.camera.setDesiredCenter(this.waypoints[index].pos);
|
||||
}
|
||||
|
||||
rerenderFull() {
|
||||
this.waypoints = this.root.camera.waypoints;
|
||||
removeAllChildren(this.contentDiv);
|
||||
|
||||
if (this.waypoints.length == 0) {
|
||||
return (this.contentDiv.innerHTML = `
|
||||
<strong class="noWaypoints">
|
||||
${T.ingame.waypoints.noWaypoints.replace("<buttonNew>", T.ingame.waypoints.buttonNew)}
|
||||
</strong>
|
||||
`);
|
||||
}
|
||||
|
||||
this.waypoints.forEach(waypoint => {
|
||||
const term = this.nameInput.value.replaceAll(" ", "");
|
||||
if (term !== "") {
|
||||
const simpleName = waypoint.name.toLowerCase().replaceAll(" ", "");
|
||||
if (!simpleName.includes(term.toLowerCase())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const tilePos = waypoint.pos.toTileSpace();
|
||||
|
||||
const wpContainer = makeDiv(this.contentDiv, null, ["waypoint"]);
|
||||
const positionStr = T.ingame.waypoints.position
|
||||
.replace("<xpos>", tilePos.x)
|
||||
.replace("<ypos>", tilePos.y);
|
||||
|
||||
const index = this.waypoints.indexOf(waypoint);
|
||||
|
||||
// Waypoint name
|
||||
makeDiv(wpContainer, null, ["title"], waypoint.name);
|
||||
|
||||
// Remove button
|
||||
const buttonRemove = makeButton(wpContainer, ["removeButton"], T.ingame.waypoints.buttonRemove);
|
||||
this.trackClicks(buttonRemove, this.removeWaypoint.bind(this, index));
|
||||
|
||||
// Waypoint coords
|
||||
makeDiv(wpContainer, null, ["position"], positionStr);
|
||||
|
||||
// Teleport button
|
||||
const buttonTeleport = makeButton(
|
||||
wpContainer,
|
||||
["teleportButton"],
|
||||
T.ingame.waypoints.buttonTeleport
|
||||
);
|
||||
this.trackClicks(buttonTeleport, this.waypointTeleport.bind(this, index));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DrawParameters} parameters
|
||||
*/
|
||||
drawOverlays(parameters) {
|
||||
// TODO: Draw tile overlays on existing waypoints
|
||||
}
|
||||
}
|
@ -29,6 +29,7 @@ export const KEYMAPPINGS = {
|
||||
|
||||
menuOpenShop: { keyCode: key("F") },
|
||||
menuOpenStats: { keyCode: key("G") },
|
||||
menuOpenWaypoints: { keyCode: key("H") },
|
||||
|
||||
toggleHud: { keyCode: 113 }, // F2
|
||||
toggleFPSInfo: { keyCode: 115 }, // F1
|
||||
|
@ -239,6 +239,21 @@ ingame:
|
||||
# Displays the shapes per minute, e.g. '523 / m'
|
||||
shapesPerMinute: <shapes> / m
|
||||
|
||||
# The "Waypoints" window
|
||||
waypoints:
|
||||
title: Waypoints
|
||||
position: "X: <xpos>; Y: <ypos>"
|
||||
|
||||
findOrCreate: Find or create...
|
||||
buttonNew: Add Waypoint
|
||||
buttonTeleport: Teleport
|
||||
buttonRemove: Remove
|
||||
|
||||
# When a new waypoint is created, this name is used.
|
||||
defaultName: Waypoint <num>
|
||||
noWaypoints: >-
|
||||
There are no waypoints. Click "<buttonNew>" to create a new waypoint.
|
||||
|
||||
# Settings menu, when you press "ESC"
|
||||
settingsMenu:
|
||||
playtime: Playtime
|
||||
|
Loading…
Reference in New Issue
Block a user