This commit is contained in:
lifestorm
2024-08-04 22:55:00 +03:00
parent 8064ba84d8
commit 73479cff9e
7338 changed files with 1718883 additions and 14 deletions

View File

@@ -0,0 +1,141 @@
--[[
| 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/
--]]
local DUPE_SEND_SIZE = 60000
if ( CLIENT ) then
--
-- Called by the client to save a dupe they're holding on the server
-- into a file on their computer.
--
local LastDupeArm = 0
concommand.Add( "dupe_arm", function( ply, cmd, arg )
if ( !arg[ 1 ] ) then return end
if ( LastDupeArm > CurTime() and !game.SinglePlayer() ) then ply:ChatPrint( "Please wait a second before trying to load another duplication!" ) return end
LastDupeArm = CurTime() + 1
-- Server doesn't allow us to do this, don't even try to send them data
local res, msg = hook.Run( "CanArmDupe", ply )
if ( res == false ) then ply:ChatPrint( msg or "Refusing to load dupe, server has blocked usage of the Duplicator tool!" ) return end
-- Load the dupe (engine takes care of making sure it's a dupe)
local dupe = engine.OpenDupe( arg[ 1 ] )
if ( !dupe ) then ply:ChatPrint( "Error loading dupe.. (" .. tostring( arg[ 1 ] ) .. ")" ) return end
local uncompressed = util.Decompress( dupe.data, 5242880 )
if ( !uncompressed ) then ply:ChatPrint( "That dupe seems to be corrupted!" ) return end
--
-- And send it to the server
--
local length = dupe.data:len()
local parts = math.ceil( length / DUPE_SEND_SIZE )
local start = 0
for i = 1, parts do
local endbyte = math.min( start + DUPE_SEND_SIZE, length )
local size = endbyte - start
net.Start( "ArmDupe" )
net.WriteUInt( i, 8 )
net.WriteUInt( parts, 8 )
net.WriteUInt( size, 32 )
net.WriteData( dupe.data:sub( start + 1, endbyte + 1 ), size )
net.SendToServer()
start = endbyte
end
end, nil, "Arm a dupe", { FCVAR_DONTRECORD } )
end
if ( SERVER ) then
--
-- Add the name of the net message to the string table (or it won't be able to send!)
--
util.AddNetworkString( "ArmDupe" )
net.Receive( "ArmDupe", function( size, client )
if ( !IsValid( client ) or size < 48 ) then return end
local res, msg = hook.Run( "CanArmDupe", client )
if ( res == false ) then client:ChatPrint( msg or "Server has blocked usage of the Duplicator tool!" ) return end
local part = net.ReadUInt( 8 )
local total = net.ReadUInt( 8 )
local length = net.ReadUInt( 32 )
if ( length > DUPE_SEND_SIZE ) then return end
local datachunk = net.ReadData( length )
client.CurrentDupeBuffer = client.CurrentDupeBuffer or {}
client.CurrentDupeBuffer[ part ] = datachunk
if ( part != total ) then return end
local data = table.concat( client.CurrentDupeBuffer )
client.CurrentDupeBuffer = nil
if ( ( client.LastDupeArm or 0 ) > CurTime() and !game.SinglePlayer() ) then ServerLog( tostring( client ) .. " tried to arm a dupe too quickly!\n" ) return end
client.LastDupeArm = CurTime() + 1
ServerLog( tostring( client ) .. " is arming a dupe, size: " .. data:len() .. "\n" )
local uncompressed = util.Decompress( data, 5242880 )
if ( !uncompressed ) then
client:ChatPrint( "Server failed to decompress the duplication!" )
MsgN( "Couldn't decompress dupe from " .. client:Nick() .. "!" )
return
end
local Dupe = util.JSONToTable( uncompressed )
if ( !istable( Dupe ) ) then return end
if ( !istable( Dupe.Constraints ) ) then return end
if ( !istable( Dupe.Entities ) ) then return end
if ( !isvector( Dupe.Mins ) ) then return end
if ( !isvector( Dupe.Maxs ) ) then return end
client.CurrentDupeArmed = true
client.CurrentDupe = Dupe
client:ConCommand( "gmod_tool duplicator" )
--
-- Tell the client we got a dupe on server, ready to paste
--
local workshopCount = 0
if ( Dupe.RequiredAddons ) then workshopCount = #Dupe.RequiredAddons end
net.Start( "CopiedDupe" )
net.WriteUInt( 0, 1 ) -- Can save
net.WriteVector( Dupe.Mins )
net.WriteVector( Dupe.Maxs )
net.WriteString( "Loaded dupe" )
net.WriteUInt( table.Count( Dupe.Entities ), 24 )
net.WriteUInt( workshopCount, 16 )
if ( Dupe.RequiredAddons ) then
for _, wsid in ipairs( Dupe.RequiredAddons ) do
net.WriteString( wsid )
end
end
net.Send( client )
end )
end

View File

@@ -0,0 +1,280 @@
--[[
| 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/
--]]
hook.Add( "PostRender", "RenderDupeIcon", function()
--
-- g_ClientSaveDupe is set in transport.lua when receiving a dupe from the server
--
if ( !g_ClientSaveDupe ) then return end
--
-- Remove the global straight away
--
local Dupe = g_ClientSaveDupe
g_ClientSaveDupe = nil
local FOV = 17
--
-- This is gonna take some cunning to look awesome!
--
local Size = Dupe.Maxs - Dupe.Mins
local Radius = Size:Length() * 0.5
local CamDist = Radius / math.sin( math.rad( FOV ) / 2 ) -- Works out how far the camera has to be away based on radius + fov!
local Center = LerpVector( 0.5, Dupe.Mins, Dupe.Maxs )
local CamPos = Center + Vector( -1, 0, 0.5 ):GetNormalized() * CamDist
local EyeAng = ( Center - CamPos ):GetNormal():Angle()
--
-- The base view
--
local view = {
type = "3D",
origin = CamPos,
angles = EyeAng,
x = 0,
y = 0,
w = 512,
h = 512,
aspect = 1,
fov = FOV
}
--
-- Create a bunch of entities we're gonna use to render.
--
local entities = {}
local i = 0
for k, ent in pairs( Dupe.Entities ) do
if ( ent.Class == "prop_ragdoll" ) then
entities[ k ] = ClientsideRagdoll( ent.Model or "error.mdl", RENDERGROUP_OTHER )
if ( istable( ent.PhysicsObjects ) ) then
for boneid, v in pairs( ent.PhysicsObjects ) do
local obj = entities[ k ]:GetPhysicsObjectNum( boneid )
if ( IsValid( obj ) ) then
obj:SetPos( v.Pos )
obj:SetAngles( v.Angle )
end
end
entities[ k ]:InvalidateBoneCache()
end
else
entities[ k ] = ClientsideModel( ent.Model or "error.mdl", RENDERGROUP_OTHER )
end
i = i + 1
end
--
-- DRAW THE BLUE BACKGROUND
--
render.SetMaterial( Material( "gui/dupe_bg.png" ) )
render.DrawScreenQuadEx( 0, 0, 512, 512 )
render.UpdateRefractTexture()
--
-- BLACK OUTLINE
-- AWESOME BRUTE FORCE METHOD
--
render.SuppressEngineLighting( true )
-- Rendering icon the way we do is kinda bad and will crash the game with too many entities in the dupe
-- Try to mitigate that to some degree by not rendering the outline when we are above 800 entities
-- 1000 was tested without problems, but we want to give it some space as 1000 was tested in "perfect conditions" with nothing else happening on the map
if ( i < 800 ) then
local BorderSize = CamDist * 0.004
local Up = EyeAng:Up() * BorderSize
local Right = EyeAng:Right() * BorderSize
render.SetColorModulation( 1, 1, 1 )
render.SetBlend( 1 )
render.MaterialOverride( Material( "models/debug/debugwhite" ) )
-- Render each entity in a circle
for k, v in pairs( Dupe.Entities ) do
-- Set the skin and bodygroups
entities[ k ]:SetSkin( v.Skin or 0 )
for bg_k, bg_v in pairs( v.BodyG or {} ) do entities[ k ]:SetBodygroup( bg_k, bg_v ) end
for j = 0, math.pi * 2, 0.2 do
view.origin = CamPos + Up * math.sin( j ) + Right * math.cos( j )
cam.Start( view )
render.Model( {
model = v.Model,
pos = v.Pos,
angle = v.Angle
}, entities[ k ] )
cam.End()
end
end
-- Because we just messed up the depth
render.ClearDepth()
render.SetColorModulation( 0, 0, 0 )
render.SetBlend( 1 )
-- Try to keep the border size consistent with zoom size
BorderSize = CamDist * 0.002
Up = EyeAng:Up() * BorderSize
Right = EyeAng:Right() * BorderSize
-- Render each entity in a circle
for k, v in pairs( Dupe.Entities ) do
for j = 0, math.pi * 2, 0.2 do
view.origin = CamPos + Up * math.sin( j ) + Right * math.cos( j )
cam.Start( view )
render.Model( {
model = v.Model,
pos = v.Pos,
angle = v.Angle
}, entities[ k ] )
cam.End()
end
end
end
--
-- ACUAL RENDER!
--
-- We just fucked the depth up - so clean it
render.ClearDepth()
-- Set up the lighting. This is over-bright on purpose - to make the ents pop
render.SetModelLighting( 0, 0, 0, 0 )
render.SetModelLighting( 1, 2, 2, 2 )
render.SetModelLighting( 2, 3, 2, 0 )
render.SetModelLighting( 3, 0.5, 2.0, 2.5 )
render.SetModelLighting( 4, 3, 3, 3 ) -- top
render.SetModelLighting( 5, 0, 0, 0 )
render.MaterialOverride( nil )
view.origin = CamPos
cam.Start( view )
-- Render each model
for k, v in pairs( Dupe.Entities ) do
render.SetColorModulation( 1, 1, 1 )
render.SetBlend( 1 )
-- EntityMods override this
if ( v._DuplicatedColor ) then
render.SetColorModulation( v._DuplicatedColor.r / 255, v._DuplicatedColor.g / 255, v._DuplicatedColor.b / 255 )
--render.SetBlend( v._DuplicatedColor.a / 255 )
end
if ( v._DuplicatedMaterial ) then render.MaterialOverride( Material( v._DuplicatedMaterial ) ) end
if ( istable( v.EntityMods ) ) then
if ( istable( v.EntityMods.colour ) ) then
render.SetColorModulation( v.EntityMods.colour.Color.r / 255, v.EntityMods.colour.Color.g / 255, v.EntityMods.colour.Color.b / 255 )
--render.SetBlend( v.EntityMods.colour.Color.a / 255 )
end
if ( istable( v.EntityMods.material ) ) then
render.MaterialOverride( Material( v.EntityMods.material.MaterialOverride ) )
end
end
render.Model( {
model = v.Model,
pos = v.Pos,
angle = v.Angle
}, entities[ k ] )
render.MaterialOverride( nil )
end
cam.End()
-- Enable lighting again (or it will affect outside of this loop!)
render.SuppressEngineLighting( false )
render.SetColorModulation( 1, 1, 1 )
render.SetBlend( 1 )
--
-- Finished with the entities - remove them all
--
for k, v in pairs( entities ) do
v:Remove()
end
--
-- This captures a square of the render target, copies it to a jpeg file
-- and returns it to us as a (binary) string.
--
local jpegdata = render.Capture( {
format = "jpeg",
x = 0,
y = 0,
w = 512,
h = 512,
quality = 95
} )
--
-- Try to figure out if any of the models/materials/etc came from some addon
--
duplicator.FigureOutRequiredAddons( Dupe )
--
-- Encode and compress the dupe
--
local DupeJSON = util.TableToJSON( Dupe )
if ( !isstring( DupeJSON ) ) then
MsgN( "There was an error converting the dupe to a json string" )
end
DupeJSON = util.Compress( DupeJSON )
--
-- And save it! (filename is automatic md5 in dupes/)
--
if ( engine.WriteDupe( DupeJSON, jpegdata ) ) then
-- Disable the save button!!
hook.Run( "DupeSaveUnavailable" )
hook.Run( "DupeSaved" )
MsgN( "Saved!" )
spawnmenu.SwitchCreationTab( "#spawnmenu.category.dupes" )
end
end )

View File

@@ -0,0 +1,109 @@
--[[
| 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/
--]]
if ( SERVER ) then
--
-- Add the name of the net message to the string table (or it won't be able to send!)
--
util.AddNetworkString( "ReceiveDupe" )
--
-- Called by the client to save a dupe they're holding on the server
-- into a file on their computer.
--
concommand.Add( "dupe_save", function( ply, cmd, arg )
if ( !IsValid( ply ) ) then return end
-- No dupe to save
if ( !ply.CurrentDupe ) then return end
-- Current dupe was armed from a file. Don't allow immediate resave.
if ( ply.CurrentDupeArmed ) then return end
if ( ply.m_NextDupeSave && ply.m_NextDupeSave > CurTime() && !game.SinglePlayer() ) then
ServerLog( tostring( ply ) .. " tried to save a dupe too quickly!\n" )
return
end
ply.m_NextDupeSave = CurTime() + 1
-- Convert dupe to JSON
local json = util.TableToJSON( ply.CurrentDupe )
-- Compress it
local compressed = util.Compress( json )
local length = compressed:len()
local send_size = 60000
local parts = math.ceil( length / send_size )
ServerLog( tostring( ply ) .. " requested a Dupe. Size: " .. json:len() .. " ( " .. length .. " compressed, " .. parts .. " parts )\n" )
-- And send it(!)
local start = 0
for i = 1, parts do
local endbyte = math.min( start + send_size, length )
local size = endbyte - start
-- print( "S [ " .. i .. " / " .. parts .. " ] Size: " .. size .. " Start: " .. start .. " End: " .. endbyte )
net.Start( "ReceiveDupe" )
net.WriteUInt( i, 8 )
net.WriteUInt( parts, 8 )
net.WriteUInt( size, 32 )
net.WriteData( compressed:sub( start + 1, endbyte + 1 ), size )
net.Send( ply )
start = endbyte
end
end, nil, "Save the current dupe!", { FCVAR_DONTRECORD } )
end
if ( CLIENT ) then
local buffer = ""
net.Receive( "ReceiveDupe", function( len, client )
local part = net.ReadUInt( 8 )
local total = net.ReadUInt( 8 )
local length = net.ReadUInt( 32 )
local data = net.ReadData( length )
buffer = buffer .. data
-- MsgN( "R [ " .. part .. " / " .. total .. " ] Size: " .. data:len() )
if ( part != total ) then return end
MsgN( "Received dupe. Size: " .. buffer:len() )
local uncompressed = util.Decompress( buffer )
buffer = ""
if ( !uncompressed ) then
MsgN( "Received dupe - but couldn't decompress!?" )
return
end
--
-- Set this global so we can pick it up when we're rendering a frame
-- See icon.lua for this process
--
g_ClientSaveDupe = util.JSONToTable( uncompressed )
end )
end