mirror of
https://github.com/PixelGuys/Cubyz.git
synced 2025-08-03 03:06:55 -04:00
Add /undo
command (#1228)
* Add undo and redo commands * Remove unintended change * Remove redo code * Clean up redo code * Use CircularBufferQueue * Fix formatting issues * Update src/server/server.zig Co-authored-by: IntegratedQuantum <43880493+IntegratedQuantum@users.noreply.github.com> * Apply review requests --------- Co-authored-by: IntegratedQuantum <43880493+IntegratedQuantum@users.noreply.github.com>
This commit is contained in:
parent
fba10a94c2
commit
12d05a06ff
@ -6,6 +6,7 @@ pub const kill = @import("kill.zig");
|
||||
pub const time = @import("time.zig");
|
||||
pub const tp = @import("tp.zig");
|
||||
|
||||
pub const undo = @import("worldedit/undo.zig");
|
||||
pub const pos1 = @import("worldedit/pos1.zig");
|
||||
pub const pos2 = @import("worldedit/pos2.zig");
|
||||
pub const deselect = @import("worldedit/deselect.zig");
|
||||
|
@ -22,6 +22,21 @@ pub fn execute(args: []const u8, source: *User) void {
|
||||
if(source.worldEditData.clipboard) |clipboard| {
|
||||
const pos: Vec3i = @intFromFloat(source.player.pos);
|
||||
source.sendMessage("Pasting: {}", .{pos});
|
||||
|
||||
const undo = Blueprint.capture(main.globalAllocator, pos, .{
|
||||
pos[0] + @as(i32, @intCast(clipboard.blocks.width)) - 1,
|
||||
pos[1] + @as(i32, @intCast(clipboard.blocks.depth)) - 1,
|
||||
pos[2] + @as(i32, @intCast(clipboard.blocks.height)) - 1,
|
||||
});
|
||||
switch(undo) {
|
||||
.success => |blueprint| {
|
||||
source.worldEditData.undoHistory.push(.init(blueprint, pos, "paste"));
|
||||
},
|
||||
.failure => {
|
||||
source.sendMessage("#ff0000Error: Could not capture undo history.", .{});
|
||||
},
|
||||
}
|
||||
|
||||
clipboard.paste(pos);
|
||||
} else {
|
||||
source.sendMessage("#ff0000Error: No clipboard content to paste.", .{});
|
||||
|
23
src/server/command/worldedit/undo.zig
Normal file
23
src/server/command/worldedit/undo.zig
Normal file
@ -0,0 +1,23 @@
|
||||
const std = @import("std");
|
||||
|
||||
const main = @import("root");
|
||||
const User = main.server.User;
|
||||
|
||||
const Block = main.blocks.Block;
|
||||
const Blueprint = main.blueprint.Blueprint;
|
||||
|
||||
pub const description = "Undo last change done to world with world editing commands.";
|
||||
pub const usage = "/undo";
|
||||
|
||||
pub fn execute(args: []const u8, source: *User) void {
|
||||
if(args.len != 0) {
|
||||
source.sendMessage("#ff0000Too many arguments for command /undo. Expected no arguments.", .{});
|
||||
return;
|
||||
}
|
||||
if(source.worldEditData.undoHistory.pop()) |action| {
|
||||
action.blueprint.paste(action.position);
|
||||
source.sendMessage("#00ff00Un-done last {s}.", .{action.message});
|
||||
} else {
|
||||
source.sendMessage("#ccccccNothing to undo.", .{});
|
||||
}
|
||||
}
|
@ -12,6 +12,9 @@ const Vec3d = vec.Vec3d;
|
||||
const Vec3i = vec.Vec3i;
|
||||
const BinaryReader = main.utils.BinaryReader;
|
||||
const BinaryWriter = main.utils.BinaryWriter;
|
||||
const Blueprint = main.blueprint.Blueprint;
|
||||
const NeverFailingAllocator = main.heap.NeverFailingAllocator;
|
||||
const CircularBufferQueue = main.utils.CircularBufferQueue;
|
||||
|
||||
pub const ServerWorld = @import("world.zig").ServerWorld;
|
||||
pub const terrain = @import("terrain/terrain.zig");
|
||||
@ -21,14 +24,58 @@ pub const storage = @import("storage.zig");
|
||||
const command = @import("command/_command.zig");
|
||||
|
||||
pub const WorldEditData = struct {
|
||||
const maxWorldEditHistoryCapacity: u32 = 1024;
|
||||
|
||||
selectionPosition1: ?Vec3i = null,
|
||||
selectionPosition2: ?Vec3i = null,
|
||||
clipboard: ?main.blueprint.Blueprint = null,
|
||||
clipboard: ?Blueprint = null,
|
||||
undoHistory: History,
|
||||
|
||||
const History = struct {
|
||||
changes: CircularBufferQueue(Value),
|
||||
|
||||
const Value = struct {
|
||||
blueprint: Blueprint,
|
||||
position: Vec3i,
|
||||
message: []const u8,
|
||||
|
||||
pub fn init(blueprint: Blueprint, position: Vec3i, message: []const u8) Value {
|
||||
return .{.blueprint = blueprint, .position = position, .message = main.globalAllocator.dupe(u8, message)};
|
||||
}
|
||||
pub fn deinit(self: Value) void {
|
||||
main.globalAllocator.free(self.message);
|
||||
self.blueprint.deinit(main.globalAllocator);
|
||||
}
|
||||
};
|
||||
pub fn init() History {
|
||||
return .{.changes = .init(main.globalAllocator, maxWorldEditHistoryCapacity)};
|
||||
}
|
||||
pub fn deinit(self: *History) void {
|
||||
self.clear();
|
||||
self.changes.deinit();
|
||||
}
|
||||
pub fn clear(self: *History) void {
|
||||
while(self.changes.dequeue()) |item| item.deinit();
|
||||
}
|
||||
pub fn push(self: *History, value: Value) void {
|
||||
if(self.changes.reachedCapacity()) {
|
||||
if(self.changes.dequeue()) |oldValue| oldValue.deinit();
|
||||
}
|
||||
|
||||
self.changes.enqueue(value);
|
||||
}
|
||||
pub fn pop(self: *History) ?Value {
|
||||
return self.changes.dequeue_front();
|
||||
}
|
||||
};
|
||||
pub fn init() WorldEditData {
|
||||
return .{.undoHistory = History.init()};
|
||||
}
|
||||
pub fn deinit(self: *WorldEditData) void {
|
||||
if(self.clipboard != null) {
|
||||
self.clipboard.?.deinit(main.globalAllocator);
|
||||
}
|
||||
self.undoHistory.deinit();
|
||||
}
|
||||
};
|
||||
|
||||
@ -53,7 +100,7 @@ pub const User = struct { // MARK: User
|
||||
lastRenderDistance: u16 = 0,
|
||||
lastPos: Vec3i = @splat(0),
|
||||
gamemode: std.atomic.Value(main.game.Gamemode) = .init(.creative),
|
||||
worldEditData: WorldEditData = .{},
|
||||
worldEditData: WorldEditData = undefined,
|
||||
|
||||
inventoryClientToServerIdMap: std.AutoHashMap(u32, u32) = undefined,
|
||||
|
||||
@ -71,6 +118,7 @@ pub const User = struct { // MARK: User
|
||||
self.interpolation.init(@ptrCast(&self.player.pos), @ptrCast(&self.player.vel));
|
||||
self.conn = try Connection.init(manager, ipPort, self);
|
||||
self.increaseRefCount();
|
||||
self.worldEditData = .init();
|
||||
network.Protocols.handShake.serverSide(self.conn);
|
||||
return self;
|
||||
}
|
||||
|
@ -391,6 +391,10 @@ pub fn CircularBufferQueue(comptime T: type) type { // MARK: CircularBufferQueue
|
||||
pub fn empty(self: *Self) bool {
|
||||
return self.startIndex == self.endIndex;
|
||||
}
|
||||
|
||||
pub fn reachedCapacity(self: *Self) bool {
|
||||
return self.startIndex == (self.endIndex + 1) & self.mask;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user