1
0
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:
akupiec 2021-01-25 17:38:02 +01:00
parent 5f0a95ba11
commit 24c1f42747
7 changed files with 131 additions and 4 deletions

View File

@ -210,6 +210,7 @@ export class InputDistributor {
this.forwardToReceiver("keydown", { this.forwardToReceiver("keydown", {
keyCode: keyCode, keyCode: keyCode,
shift: event.shiftKey, shift: event.shiftKey,
ctrl: event.ctrlKey,
alt: event.altKey, alt: event.altKey,
initial: isInitial, initial: isInitial,
event, event,

View File

@ -38,6 +38,7 @@ import { ShapeDefinitionManager } from "./shape_definition_manager";
import { AchievementProxy } from "./achievement_proxy"; import { AchievementProxy } from "./achievement_proxy";
import { SoundProxy } from "./sound_proxy"; import { SoundProxy } from "./sound_proxy";
import { GameTime } from "./time/game_time"; import { GameTime } from "./time/game_time";
import { HistoryManager } from "./history_manager";
const logger = createLogger("ingame/core"); const logger = createLogger("ingame/core");
@ -123,6 +124,7 @@ export class GameCore {
root.hubGoals = new HubGoals(root); root.hubGoals = new HubGoals(root);
root.productionAnalytics = new ProductionAnalytics(root); root.productionAnalytics = new ProductionAnalytics(root);
root.buffers = new BufferMaintainer(root); root.buffers = new BufferMaintainer(root);
root.historyMgr = new HistoryManager(root);
// Initialize the hud once everything is loaded // Initialize the hud once everything is loaded
this.root.hud.initialize(); this.root.hud.initialize();

View 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);
}
}

View File

@ -7,6 +7,7 @@ import { Application } from "../application";
import { Signal, STOP_PROPAGATION } from "../core/signal"; import { Signal, STOP_PROPAGATION } from "../core/signal";
import { IS_MOBILE } from "../core/config"; import { IS_MOBILE } from "../core/config";
import { T } from "../translations"; import { T } from "../translations";
function key(str) { function key(str) {
return str.toUpperCase().charCodeAt(0); return str.toUpperCase().charCodeAt(0);
} }
@ -95,6 +96,9 @@ export const KEYMAPPINGS = {
switchDirectionLockSide: { keyCode: key("R") }, switchDirectionLockSide: { keyCode: key("R") },
copyWireValue: { keyCode: key("Z") }, copyWireValue: { keyCode: key("Z") },
undo: { keyCode: key("Z"), ctrl: true, shift: false },
redo: { keyCode: key("Z"), ctrl: true, shift: true },
}, },
massSelect: { massSelect: {
@ -284,14 +288,18 @@ export class Keybinding {
* @param {Application} app * @param {Application} app
* @param {object} param0 * @param {object} param0
* @param {number} param0.keyCode * @param {number} param0.keyCode
* @param {boolean | undefined} param0.ctrl
* @param {boolean | undefined} param0.shift
* @param {boolean=} param0.builtin * @param {boolean=} param0.builtin
* @param {boolean=} param0.repeated * @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); assert(keyCode && Number.isInteger(keyCode), "Invalid key code: " + keyCode);
this.keyMapper = keyMapper; this.keyMapper = keyMapper;
this.app = app; this.app = app;
this.keyCode = keyCode; this.keyCode = keyCode;
this.ctrl = ctrl;
this.shift = shift;
this.builtin = builtin; this.builtin = builtin;
this.repeated = repeated; this.repeated = repeated;
@ -440,17 +448,22 @@ export class KeyActionMapper {
* @param {number} param0.keyCode * @param {number} param0.keyCode
* @param {boolean} param0.shift * @param {boolean} param0.shift
* @param {boolean} param0.alt * @param {boolean} param0.alt
* @param {boolean} param0.ctrl
* @param {boolean=} param0.initial * @param {boolean=} param0.initial
*/ */
handleKeydown({ keyCode, shift, alt, initial }) { handleKeydown({ keyCode, shift, alt, ctrl, initial }) {
let stop = false; let stop = false;
// Find mapping // Find mapping
for (const key in this.keybindings) { for (const key in this.keybindings) {
/** @type {Keybinding} */ /** @type {Keybinding} */
const binding = this.keybindings[key]; const binding = this.keybindings[key];
if (binding.keyCode === keyCode && (initial || binding.repeated)) { let isPressed =
/** @type {Signal} */ 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; const signal = this.keybindings[key].signal;
if (signal.dispatch() === STOP_PROPAGATION) { if (signal.dispatch() === STOP_PROPAGATION) {
return; return;

View File

@ -110,6 +110,7 @@ export class GameLogic {
this.freeEntityAreaBeforeBuild(entity); this.freeEntityAreaBeforeBuild(entity);
this.root.map.placeStaticEntity(entity); this.root.map.placeStaticEntity(entity);
this.root.entityMgr.registerEntity(entity); this.root.entityMgr.registerEntity(entity);
this.root.historyMgr.addAction(entity);
return entity; return entity;
} }
return null; return null;
@ -178,6 +179,7 @@ export class GameLogic {
if (!this.canDeleteBuilding(building)) { if (!this.canDeleteBuilding(building)) {
return false; return false;
} }
this.root.historyMgr.removeAction(building.clone());
this.root.map.removeStaticEntity(building); this.root.map.removeStaticEntity(building);
this.root.entityMgr.destroyEntity(building); this.root.entityMgr.destroyEntity(building);
this.root.entityMgr.processDestroyList(); this.root.entityMgr.processDestroyList();

View File

@ -29,6 +29,7 @@ import { DynamicTickrate } from "./dynamic_tickrate";
import { KeyActionMapper } from "./key_action_mapper"; import { KeyActionMapper } from "./key_action_mapper";
import { Vector } from "../core/vector"; import { Vector } from "../core/vector";
import { GameMode } from "./game_mode"; import { GameMode } from "./game_mode";
import { HistoryManager } from "./history_manager";
/* typehints:end */ /* typehints:end */
const logger = createLogger("game/root"); const logger = createLogger("game/root");
@ -138,6 +139,9 @@ export class GameRoot {
/** @type {GameMode} */ /** @type {GameMode} */
this.gameMode = null; this.gameMode = null;
/** @type {HistoryManager} */
this.historyMgr = null;
this.signals = { this.signals = {
// Entities // Entities
entityManuallyPlaced: /** @type {TypedSignal<[Entity]>} */ (new Signal()), entityManuallyPlaced: /** @type {TypedSignal<[Entity]>} */ (new Signal()),

View File

@ -275,6 +275,8 @@ ingame:
copySelection: Copy copySelection: Copy
clearSelection: Clear selection clearSelection: Clear selection
pipette: Pipette pipette: Pipette
undo: Undo
redo: Redo
switchLayers: Switch layers switchLayers: Switch layers
# Names of the colors, used for the color blind mode # Names of the colors, used for the color blind mode
@ -1131,6 +1133,8 @@ keybindings:
# --- # ---
pipette: Pipette pipette: Pipette
undo: Undo
redo: Redo
rotateWhilePlacing: Rotate rotateWhilePlacing: Rotate
rotateInverseModifier: >- rotateInverseModifier: >-
Modifier: Rotate CCW instead Modifier: Rotate CCW instead