mirror of
https://github.com/tobspr/shapez.io.git
synced 2025-06-13 13:04:03 +00:00
undo / and simple redo - entity block
This commit is contained in:
parent
5f0a95ba11
commit
24c1f42747
@ -210,6 +210,7 @@ export class InputDistributor {
|
||||
this.forwardToReceiver("keydown", {
|
||||
keyCode: keyCode,
|
||||
shift: event.shiftKey,
|
||||
ctrl: event.ctrlKey,
|
||||
alt: event.altKey,
|
||||
initial: isInitial,
|
||||
event,
|
||||
|
@ -38,6 +38,7 @@ import { ShapeDefinitionManager } from "./shape_definition_manager";
|
||||
import { AchievementProxy } from "./achievement_proxy";
|
||||
import { SoundProxy } from "./sound_proxy";
|
||||
import { GameTime } from "./time/game_time";
|
||||
import { HistoryManager } from "./history_manager";
|
||||
|
||||
const logger = createLogger("ingame/core");
|
||||
|
||||
@ -123,6 +124,7 @@ export class GameCore {
|
||||
root.hubGoals = new HubGoals(root);
|
||||
root.productionAnalytics = new ProductionAnalytics(root);
|
||||
root.buffers = new BufferMaintainer(root);
|
||||
root.historyMgr = new HistoryManager(root);
|
||||
|
||||
// Initialize the hud once everything is loaded
|
||||
this.root.hud.initialize();
|
||||
|
101
src/js/game/history_manager.js
Normal file
101
src/js/game/history_manager.js
Normal file
@ -0,0 +1,101 @@
|
||||
import { Entity } from "./entity";
|
||||
import { SOUNDS } from "../platform/sound";
|
||||
import { KEYMAPPINGS } from "./key_action_mapper";
|
||||
|
||||
class LiFoQueue {
|
||||
constructor(size = 20) {
|
||||
this.size = size;
|
||||
this.items = [];
|
||||
}
|
||||
|
||||
enqueue(element) {
|
||||
if (this.size < this.items.length + 1) {
|
||||
this.items.shift();
|
||||
}
|
||||
this.items.push(element);
|
||||
}
|
||||
|
||||
dequeue() {
|
||||
return this.items.pop();
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.items = [];
|
||||
}
|
||||
}
|
||||
|
||||
const ActionType = {
|
||||
add: "ADD",
|
||||
remove: "REMOVE",
|
||||
};
|
||||
|
||||
export class HistoryManager {
|
||||
constructor(root) {
|
||||
this.root = root;
|
||||
this._entities = new LiFoQueue();
|
||||
this._forRedo = new LiFoQueue();
|
||||
|
||||
this.initializeBindings();
|
||||
}
|
||||
|
||||
initializeBindings() {
|
||||
this.root.keyMapper.getBinding(KEYMAPPINGS.placement.undo).add(this._undo, this);
|
||||
this.root.keyMapper.getBinding(KEYMAPPINGS.placement.redo).add(this._redo, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
addAction(entity) {
|
||||
this._forRedo.clear();
|
||||
this._entities.enqueue({ type: ActionType.add, entity });
|
||||
}
|
||||
|
||||
removeAction(entity) {
|
||||
this._forRedo.clear();
|
||||
this._entities.enqueue({ type: ActionType.remove, entity });
|
||||
}
|
||||
|
||||
_undo() {
|
||||
const { type, entity } = this._entities.dequeue() || {};
|
||||
if (!entity) {
|
||||
return;
|
||||
}
|
||||
if (type === ActionType.add && this.root.logic.canDeleteBuilding(entity)) {
|
||||
this._forRedo.enqueue({ type: ActionType.remove, entity: entity.clone() });
|
||||
this._removeEntity(entity);
|
||||
}
|
||||
if (type === ActionType.remove && this.root.logic.checkCanPlaceEntity(entity)) {
|
||||
this._forRedo.enqueue({ type: ActionType.add, entity: entity });
|
||||
this._placeEntity(entity);
|
||||
}
|
||||
}
|
||||
|
||||
_redo() {
|
||||
const { type, entity } = this._forRedo.dequeue() || {};
|
||||
if (!entity) {
|
||||
return;
|
||||
}
|
||||
if (type === ActionType.remove && this.root.logic.checkCanPlaceEntity(entity)) {
|
||||
this._placeEntity(entity);
|
||||
this._entities.enqueue({ type: ActionType.add, entity });
|
||||
}
|
||||
if (type === ActionType.add && this.root.logic.canDeleteBuilding(entity)) {
|
||||
this._entities.enqueue({ type: ActionType.remove, entity: entity.clone() });
|
||||
this._removeEntity(entity);
|
||||
}
|
||||
}
|
||||
|
||||
_removeEntity(entity) {
|
||||
this.root.map.removeStaticEntity(entity);
|
||||
this.root.entityMgr.destroyEntity(entity);
|
||||
this.root.entityMgr.processDestroyList();
|
||||
this.root.soundProxy.playUi(SOUNDS.destroyBuilding);
|
||||
}
|
||||
|
||||
_placeEntity(entity) {
|
||||
this.root.logic.freeEntityAreaBeforeBuild(entity);
|
||||
this.root.map.placeStaticEntity(entity);
|
||||
this.root.entityMgr.registerEntity(entity);
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ import { Application } from "../application";
|
||||
import { Signal, STOP_PROPAGATION } from "../core/signal";
|
||||
import { IS_MOBILE } from "../core/config";
|
||||
import { T } from "../translations";
|
||||
|
||||
function key(str) {
|
||||
return str.toUpperCase().charCodeAt(0);
|
||||
}
|
||||
@ -95,6 +96,9 @@ export const KEYMAPPINGS = {
|
||||
switchDirectionLockSide: { keyCode: key("R") },
|
||||
|
||||
copyWireValue: { keyCode: key("Z") },
|
||||
|
||||
undo: { keyCode: key("Z"), ctrl: true, shift: false },
|
||||
redo: { keyCode: key("Z"), ctrl: true, shift: true },
|
||||
},
|
||||
|
||||
massSelect: {
|
||||
@ -284,14 +288,18 @@ export class Keybinding {
|
||||
* @param {Application} app
|
||||
* @param {object} param0
|
||||
* @param {number} param0.keyCode
|
||||
* @param {boolean | undefined} param0.ctrl
|
||||
* @param {boolean | undefined} param0.shift
|
||||
* @param {boolean=} param0.builtin
|
||||
* @param {boolean=} param0.repeated
|
||||
*/
|
||||
constructor(keyMapper, app, { keyCode, builtin = false, repeated = false }) {
|
||||
constructor(keyMapper, app, { keyCode, ctrl, shift, builtin = false, repeated = false }) {
|
||||
assert(keyCode && Number.isInteger(keyCode), "Invalid key code: " + keyCode);
|
||||
this.keyMapper = keyMapper;
|
||||
this.app = app;
|
||||
this.keyCode = keyCode;
|
||||
this.ctrl = ctrl;
|
||||
this.shift = shift;
|
||||
this.builtin = builtin;
|
||||
this.repeated = repeated;
|
||||
|
||||
@ -440,17 +448,22 @@ export class KeyActionMapper {
|
||||
* @param {number} param0.keyCode
|
||||
* @param {boolean} param0.shift
|
||||
* @param {boolean} param0.alt
|
||||
* @param {boolean} param0.ctrl
|
||||
* @param {boolean=} param0.initial
|
||||
*/
|
||||
handleKeydown({ keyCode, shift, alt, initial }) {
|
||||
handleKeydown({ keyCode, shift, alt, ctrl, initial }) {
|
||||
let stop = false;
|
||||
|
||||
// Find mapping
|
||||
for (const key in this.keybindings) {
|
||||
/** @type {Keybinding} */
|
||||
const binding = this.keybindings[key];
|
||||
if (binding.keyCode === keyCode && (initial || binding.repeated)) {
|
||||
/** @type {Signal} */
|
||||
let isPressed =
|
||||
binding.keyCode === keyCode &&
|
||||
(initial || binding.repeated) &&
|
||||
(binding.ctrl === undefined || binding.ctrl === ctrl) &&
|
||||
(binding.shift === undefined || binding.shift === shift);
|
||||
if (isPressed) {
|
||||
const signal = this.keybindings[key].signal;
|
||||
if (signal.dispatch() === STOP_PROPAGATION) {
|
||||
return;
|
||||
|
@ -110,6 +110,7 @@ export class GameLogic {
|
||||
this.freeEntityAreaBeforeBuild(entity);
|
||||
this.root.map.placeStaticEntity(entity);
|
||||
this.root.entityMgr.registerEntity(entity);
|
||||
this.root.historyMgr.addAction(entity);
|
||||
return entity;
|
||||
}
|
||||
return null;
|
||||
@ -178,6 +179,7 @@ export class GameLogic {
|
||||
if (!this.canDeleteBuilding(building)) {
|
||||
return false;
|
||||
}
|
||||
this.root.historyMgr.removeAction(building.clone());
|
||||
this.root.map.removeStaticEntity(building);
|
||||
this.root.entityMgr.destroyEntity(building);
|
||||
this.root.entityMgr.processDestroyList();
|
||||
|
@ -29,6 +29,7 @@ import { DynamicTickrate } from "./dynamic_tickrate";
|
||||
import { KeyActionMapper } from "./key_action_mapper";
|
||||
import { Vector } from "../core/vector";
|
||||
import { GameMode } from "./game_mode";
|
||||
import { HistoryManager } from "./history_manager";
|
||||
/* typehints:end */
|
||||
|
||||
const logger = createLogger("game/root");
|
||||
@ -138,6 +139,9 @@ export class GameRoot {
|
||||
/** @type {GameMode} */
|
||||
this.gameMode = null;
|
||||
|
||||
/** @type {HistoryManager} */
|
||||
this.historyMgr = null;
|
||||
|
||||
this.signals = {
|
||||
// Entities
|
||||
entityManuallyPlaced: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
|
||||
|
@ -275,6 +275,8 @@ ingame:
|
||||
copySelection: Copy
|
||||
clearSelection: Clear selection
|
||||
pipette: Pipette
|
||||
undo: Undo
|
||||
redo: Redo
|
||||
switchLayers: Switch layers
|
||||
|
||||
# Names of the colors, used for the color blind mode
|
||||
@ -1131,6 +1133,8 @@ keybindings:
|
||||
# ---
|
||||
|
||||
pipette: Pipette
|
||||
undo: Undo
|
||||
redo: Redo
|
||||
rotateWhilePlacing: Rotate
|
||||
rotateInverseModifier: >-
|
||||
Modifier: Rotate CCW instead
|
||||
|
Loading…
Reference in New Issue
Block a user