mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-17 05:43:46 +03:00
Upload
This commit is contained in:
519
lua/includes/modules/undo.lua
Normal file
519
lua/includes/modules/undo.lua
Normal file
@@ -0,0 +1,519 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
module( "undo", package.seeall )
|
||||
|
||||
-- undo.Create("Wheel")
|
||||
-- undo.AddEntity( axis )
|
||||
-- undo.AddEntity( constraint )
|
||||
-- undo.SetPlayer( self.Owner )
|
||||
-- undo.Finish()
|
||||
|
||||
if ( CLIENT ) then
|
||||
|
||||
local ClientUndos = {}
|
||||
local bIsDirty = true
|
||||
|
||||
--[[---------------------------------------------------------
|
||||
GetTable
|
||||
Returns the undo table for whatever reason
|
||||
-----------------------------------------------------------]]
|
||||
function GetTable()
|
||||
return ClientUndos
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------
|
||||
UpdateUI
|
||||
Actually updates the UI. Removes old controls and
|
||||
re-creates them using the new data.
|
||||
-----------------------------------------------------------]]
|
||||
local function UpdateUI()
|
||||
|
||||
local Panel = controlpanel.Get( "Undo" )
|
||||
if ( !IsValid( Panel ) ) then return end
|
||||
|
||||
Panel:Clear()
|
||||
Panel:AddControl( "Header", { Description = "#spawnmenu.utilities.undo.help" } )
|
||||
|
||||
local ComboBox = Panel:ListBox()
|
||||
ComboBox:SetTall( 500 )
|
||||
|
||||
local Limit = 100
|
||||
local Count = 0
|
||||
for k, v in ipairs( ClientUndos ) do
|
||||
|
||||
local Item = ComboBox:AddItem( tostring( v.Name ) )
|
||||
Item.DoClick = function() RunConsoleCommand( "gmod_undonum", tostring( v.Key ) ) end
|
||||
|
||||
Count = Count + 1
|
||||
if ( Count > Limit ) then break end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------
|
||||
AddUndo
|
||||
Called from server. Adds a new undo to our UI
|
||||
-----------------------------------------------------------]]
|
||||
net.Receive( "Undo_AddUndo", function()
|
||||
|
||||
local k = net.ReadInt( 16 )
|
||||
local v = net.ReadString()
|
||||
|
||||
table.insert( ClientUndos, 1, { Key = k, Name = v } )
|
||||
|
||||
MakeUIDirty()
|
||||
|
||||
end )
|
||||
|
||||
-- Called from server, fires GM:OnUndo
|
||||
net.Receive( "Undo_FireUndo", function()
|
||||
|
||||
local name = net.ReadString()
|
||||
local hasCustomText = net.ReadBool()
|
||||
local customtext
|
||||
if ( hasCustomText ) then
|
||||
customtext = net.ReadString()
|
||||
end
|
||||
|
||||
hook.Run( "OnUndo", name, customtext )
|
||||
|
||||
end )
|
||||
|
||||
|
||||
--[[---------------------------------------------------------
|
||||
Undone
|
||||
Called from server. Notifies us that one of our undos
|
||||
has been undone or made redundant. We act by updating
|
||||
out data (We wait until the UI is viewed until updating)
|
||||
-----------------------------------------------------------]]
|
||||
local function Undone()
|
||||
|
||||
local key = net.ReadInt( 16 )
|
||||
|
||||
local NewUndo = {}
|
||||
local i = 1
|
||||
for k, v in ipairs( ClientUndos ) do
|
||||
|
||||
if ( v.Key != key ) then
|
||||
NewUndo [ i ] = v
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
|
||||
ClientUndos = NewUndo
|
||||
NewUndo = nil
|
||||
|
||||
MakeUIDirty()
|
||||
|
||||
end
|
||||
net.Receive( "Undo_Undone", Undone )
|
||||
|
||||
--[[---------------------------------------------------------
|
||||
MakeUIDirty
|
||||
Makes the UI dirty - it will re-create the controls
|
||||
the next time it is viewed.
|
||||
-----------------------------------------------------------]]
|
||||
function MakeUIDirty()
|
||||
|
||||
bIsDirty = true
|
||||
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------
|
||||
CPanelPaint
|
||||
Called when the inner panel of the undo CPanel is painted
|
||||
We hook onto this to determine when the panel is viewed.
|
||||
When it's viewed we update the UI if it's marked as dirty
|
||||
-----------------------------------------------------------]]
|
||||
local function CPanelUpdate( panel )
|
||||
|
||||
-- This is kind of a shitty way of doing it - but we only want
|
||||
-- to update the UI when it's visible.
|
||||
if ( bIsDirty ) then
|
||||
|
||||
-- Doing this in a timer so it calls it in a sensible place
|
||||
-- Calling in the paint function could cause all kinds of problems
|
||||
-- It's a hack but hey welcome to GMod!
|
||||
timer.Simple( 0, UpdateUI )
|
||||
bIsDirty = false
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------
|
||||
SetupUI
|
||||
Adds a hook (CPanelPaint) to the control panel paint
|
||||
function so we can determine when it is being drawn.
|
||||
-----------------------------------------------------------]]
|
||||
function SetupUI()
|
||||
|
||||
local UndoPanel = controlpanel.Get( "Undo" )
|
||||
if ( !IsValid( UndoPanel ) ) then return end
|
||||
|
||||
-- Mark as dirty please
|
||||
MakeUIDirty()
|
||||
|
||||
-- Panels only think when they're visible
|
||||
UndoPanel.Think = CPanelUpdate
|
||||
|
||||
end
|
||||
|
||||
hook.Add( "PostReloadToolsMenu", "BuildUndoUI", SetupUI )
|
||||
|
||||
end
|
||||
|
||||
|
||||
if ( !SERVER ) then return end
|
||||
|
||||
local PlayerUndo = {}
|
||||
-- PlayerUndo
|
||||
-- - Player UniqueID
|
||||
-- - Undo Table
|
||||
-- - Name
|
||||
-- - Entities (table of ents)
|
||||
-- - Owner (player)
|
||||
|
||||
local Current_Undo = nil
|
||||
|
||||
util.AddNetworkString( "Undo_Undone" )
|
||||
util.AddNetworkString( "Undo_AddUndo" )
|
||||
util.AddNetworkString( "Undo_FireUndo" )
|
||||
|
||||
--[[---------------------------------------------------------
|
||||
GetTable
|
||||
Returns the undo table for whatever reason
|
||||
-----------------------------------------------------------]]
|
||||
function GetTable()
|
||||
return PlayerUndo
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------
|
||||
GetTable
|
||||
Save/Restore the undo tables
|
||||
-----------------------------------------------------------]]
|
||||
local function Save( save )
|
||||
|
||||
saverestore.WriteTable( PlayerUndo, save )
|
||||
|
||||
end
|
||||
|
||||
local function Restore( restore )
|
||||
|
||||
PlayerUndo = saverestore.ReadTable( restore )
|
||||
|
||||
end
|
||||
|
||||
saverestore.AddSaveHook( "UndoTable", Save )
|
||||
saverestore.AddRestoreHook( "UndoTable", Restore )
|
||||
|
||||
--[[---------------------------------------------------------
|
||||
Start a new undo
|
||||
-----------------------------------------------------------]]
|
||||
function Create( text )
|
||||
|
||||
Current_Undo = {}
|
||||
Current_Undo.Name = text
|
||||
Current_Undo.Entities = {}
|
||||
Current_Undo.Owner = nil
|
||||
Current_Undo.Functions = {}
|
||||
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------
|
||||
Adds an entity to this undo (The entity is removed on undo)
|
||||
-----------------------------------------------------------]]
|
||||
function SetCustomUndoText( CustomUndoText )
|
||||
|
||||
if ( !Current_Undo ) then return end
|
||||
|
||||
Current_Undo.CustomUndoText = CustomUndoText
|
||||
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------
|
||||
Adds an entity to this undo (The entity is removed on undo)
|
||||
-----------------------------------------------------------]]
|
||||
function AddEntity( ent )
|
||||
|
||||
if ( !Current_Undo ) then return end
|
||||
if ( !IsValid( ent ) ) then return end
|
||||
|
||||
table.insert( Current_Undo.Entities, ent )
|
||||
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------
|
||||
Add a function to call to this undo
|
||||
-----------------------------------------------------------]]
|
||||
function AddFunction( func, ... )
|
||||
|
||||
if ( !Current_Undo ) then return end
|
||||
if ( !func ) then return end
|
||||
|
||||
table.insert( Current_Undo.Functions, { func, {...} } )
|
||||
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------
|
||||
Replace Entity
|
||||
-----------------------------------------------------------]]
|
||||
function ReplaceEntity( from, to )
|
||||
|
||||
local ActionTaken = false
|
||||
|
||||
for _, PlayerTable in pairs( PlayerUndo ) do
|
||||
for _, UndoTable in pairs( PlayerTable ) do
|
||||
if ( UndoTable.Entities ) then
|
||||
|
||||
for key, ent in pairs( UndoTable.Entities ) do
|
||||
if ( ent == from ) then
|
||||
UndoTable.Entities[ key ] = to
|
||||
ActionTaken = true
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return ActionTaken
|
||||
|
||||
end
|
||||
|
||||
|
||||
--[[---------------------------------------------------------
|
||||
Sets who's undo this is
|
||||
-----------------------------------------------------------]]
|
||||
function SetPlayer( ply )
|
||||
|
||||
if ( !Current_Undo ) then return end
|
||||
if ( !IsValid( ply ) ) then return end
|
||||
|
||||
Current_Undo.Owner = ply
|
||||
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------
|
||||
SendUndoneMessage
|
||||
Sends a message to notify the client that one of their
|
||||
undos has been removed. They can then update their GUI.
|
||||
-----------------------------------------------------------]]
|
||||
local function SendUndoneMessage( ent, id, ply )
|
||||
|
||||
if ( !IsValid( ply ) ) then return end
|
||||
|
||||
-- For further optimization we could queue up the ids and send them
|
||||
-- in one batch every 0.5 seconds or something along those lines.
|
||||
|
||||
net.Start( "Undo_Undone" )
|
||||
net.WriteInt( id, 16 )
|
||||
net.Send( ply )
|
||||
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------
|
||||
Checks whether an undo is allowed to be created
|
||||
-----------------------------------------------------------]]
|
||||
local function Can_CreateUndo( undo )
|
||||
|
||||
local call = hook.Run( "CanCreateUndo", undo.Owner, undo )
|
||||
|
||||
return call == true or call == nil
|
||||
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------
|
||||
Finish
|
||||
-----------------------------------------------------------]]
|
||||
function Finish( NiceText )
|
||||
|
||||
if ( !Current_Undo ) then return end
|
||||
|
||||
-- Do not add undos that have no owner or anything to undo
|
||||
if ( !IsValid( Current_Undo.Owner ) or ( table.IsEmpty( Current_Undo.Entities ) && table.IsEmpty( Current_Undo.Functions ) ) or !Can_CreateUndo( Current_Undo ) ) then
|
||||
Current_Undo = nil
|
||||
return false
|
||||
end
|
||||
|
||||
local index = Current_Undo.Owner:UniqueID()
|
||||
PlayerUndo[ index ] = PlayerUndo[ index ] or {}
|
||||
|
||||
Current_Undo.NiceText = NiceText or Current_Undo.Name
|
||||
|
||||
local id = table.insert( PlayerUndo[ index ], Current_Undo )
|
||||
|
||||
net.Start( "Undo_AddUndo" )
|
||||
net.WriteInt( id, 16 )
|
||||
net.WriteString( Current_Undo.NiceText )
|
||||
net.Send( Current_Undo.Owner )
|
||||
|
||||
-- Have one of the entities in the undo tell us when it gets undone.
|
||||
if ( IsValid( Current_Undo.Entities[ 1 ] ) ) then
|
||||
|
||||
local ent = Current_Undo.Entities[ 1 ]
|
||||
ent:CallOnRemove( "undo" .. id, SendUndoneMessage, id, Current_Undo.Owner )
|
||||
|
||||
end
|
||||
|
||||
Current_Undo = nil
|
||||
|
||||
return true
|
||||
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------
|
||||
Undos an undo
|
||||
-----------------------------------------------------------]]
|
||||
function Do_Undo( undo )
|
||||
|
||||
if ( !undo ) then return false end
|
||||
|
||||
if ( hook.Run( "PreUndo", undo ) == false ) then return end
|
||||
|
||||
local count = 0
|
||||
|
||||
-- Call each function
|
||||
if ( undo.Functions ) then
|
||||
for index, func in pairs( undo.Functions ) do
|
||||
|
||||
local success = func[ 1 ]( undo, unpack( func[ 2 ] ) )
|
||||
if ( success != false ) then
|
||||
count = count + 1
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
-- Remove each entity in this undo
|
||||
if ( undo.Entities ) then
|
||||
for index, entity in pairs( undo.Entities ) do
|
||||
|
||||
if ( IsValid( entity ) ) then
|
||||
entity:Remove()
|
||||
count = count + 1
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
if ( count > 0 ) then
|
||||
net.Start( "Undo_FireUndo" )
|
||||
net.WriteString( undo.Name )
|
||||
net.WriteBool( undo.CustomUndoText != nil )
|
||||
if ( undo.CustomUndoText != nil ) then
|
||||
net.WriteString( undo.CustomUndoText )
|
||||
end
|
||||
net.Send( undo.Owner )
|
||||
end
|
||||
|
||||
hook.Run( "PostUndo", undo, count )
|
||||
|
||||
return count
|
||||
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------
|
||||
Checks whether a player is allowed to undo
|
||||
-----------------------------------------------------------]]
|
||||
local function Can_Undo( ply, undo )
|
||||
|
||||
local call = hook.Run( "CanUndo", ply, undo )
|
||||
|
||||
return call == true or call == nil
|
||||
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------
|
||||
Console command
|
||||
-----------------------------------------------------------]]
|
||||
local function CC_UndoLast( pl, command, args )
|
||||
|
||||
local index = pl:UniqueID()
|
||||
PlayerUndo[ index ] = PlayerUndo[ index ] or {}
|
||||
|
||||
local last = nil
|
||||
local lastk = nil
|
||||
|
||||
for k, v in pairs( PlayerUndo[ index ] ) do
|
||||
|
||||
lastk = k
|
||||
last = v
|
||||
|
||||
end
|
||||
|
||||
-- No undos
|
||||
if ( !last ) then return end
|
||||
|
||||
-- This is quite messy, but if the player rejoined the server
|
||||
-- 'Owner' might no longer be a valid entity. So replace the Owner
|
||||
-- with the player that is doing the undoing
|
||||
last.Owner = pl
|
||||
|
||||
if ( !Can_Undo( pl, last ) ) then return end
|
||||
|
||||
local count = Do_Undo( last )
|
||||
|
||||
net.Start( "Undo_Undone" )
|
||||
net.WriteInt( lastk, 16 )
|
||||
net.Send( pl )
|
||||
|
||||
PlayerUndo[ index ][ lastk ] = nil
|
||||
|
||||
-- If our last undo object is already deleted then compact
|
||||
-- the undos until we hit one that does something
|
||||
if ( count == 0 ) then
|
||||
CC_UndoLast( pl )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------
|
||||
Console command
|
||||
-----------------------------------------------------------]]
|
||||
local function CC_UndoNum( ply, command, args )
|
||||
|
||||
if ( !args[ 1 ] ) then return end
|
||||
|
||||
local index = ply:UniqueID()
|
||||
PlayerUndo[ index ] = PlayerUndo[ index ] or {}
|
||||
|
||||
local UndoNum = tonumber( args[ 1 ] )
|
||||
if ( !UndoNum ) then return end
|
||||
|
||||
local TheUndo = PlayerUndo[ index ][ UndoNum ]
|
||||
if ( !TheUndo ) then return end
|
||||
|
||||
-- Do the same as above
|
||||
TheUndo.Owner = ply
|
||||
|
||||
if ( !Can_Undo( ply, TheUndo ) ) then return end
|
||||
|
||||
-- Undo!
|
||||
Do_Undo( TheUndo )
|
||||
|
||||
-- Notify the client UI that the undo happened
|
||||
-- This is normally called by the deleted entity via SendUndoneMessage
|
||||
-- But in cases where the undo only has functions that will not do
|
||||
net.Start( "Undo_Undone" )
|
||||
net.WriteInt( UndoNum, 16 )
|
||||
net.Send( ply )
|
||||
|
||||
-- Don't delete the entry completely so nothing new takes its place and ruin CC_UndoLast's logic (expecting newest entry be at highest index)
|
||||
PlayerUndo[ index ][ UndoNum ] = {}
|
||||
|
||||
end
|
||||
|
||||
concommand.Add( "undo", CC_UndoLast, nil, "", { FCVAR_DONTRECORD } )
|
||||
concommand.Add( "gmod_undo", CC_UndoLast, nil, "", { FCVAR_DONTRECORD } )
|
||||
concommand.Add( "gmod_undonum", CC_UndoNum, nil, "", { FCVAR_DONTRECORD } )
|
||||
Reference in New Issue
Block a user