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", {
keyCode: keyCode,
shift: event.shiftKey,
ctrl: event.ctrlKey,
alt: event.altKey,
initial: isInitial,
event,

View File

@ -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();

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 { 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;

View File

@ -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();

View File

@ -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()),

View File

@ -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