• ClientsideModel manipulation
    29 replies, posted
Hey all, I'm modifying a Pointshop addon, and have come across a bit of a problem. I'm adding particle effects as an option to buy, I accomplish this by implementing the typical item purchase through the existing Pointshop addon, but then making the model extremely small and attach the particle effect to it. The creator of the addon uses ClientsideModels for the models after purchase and equipping by players. These models effectively disappear when the player disconnects (seemingly without any code from the addon?) however the particle effect does not. I have tried everything I can think of, but there's no hook for player disconnection on the client side, and no matter how I try to pass the ClientsideModel to the server to manipulate (ent:StopParticles), the entity comes through as nil. I assume this is because it is clientside and just cannot be changed by the server. My question for you: How can I either make the particles disappear when the model it's attached to does, or pass the model object to the server so that I can call StopParticles on it when the player disconnects? Thanks for help ahead of time, and please, if you're just going to tell me I should make my own addon instead of trying to change someone else's, don't reply. EDIT: Also, the reason that I don't directly attach the particle to the player is because it seems like I can only attach particles to attachments on players; not bones. The server I'm making this for has a whole bunch of custom models that have common bones (i.e. head, legs, arms) but not common attachments. Thus, I can only attach a model to the bone, and a particle to the model. So if anyone has a method to attach particle effects directly to bones, that would also solve my problem.
Bumpin, really hope to get this solved guys, at the very least let me know it's not possible.
Client side models only exist on the client. The server doesn't even know if they exist or not. In case you didn't, you can manipulate entities on the client as well. I am having a hard time understand exactly what you want, but try doing what you tried on the server on the client.
Thanks for the reply, my problem is that after the client disconnects, other clients can still see the particle effect hovering in midair. Since the client is gone and it's too late to stop particles on that end; how can I do it?
I guess the main problem here is that I don't understand how the ClientsideModel is passed to ALL clients. When ClientsideModel() is called, all the clients see the model that's created as well as the ParticleEffect that's attached when I call ParticleEffectAttach, but when the client disconnects, these models/ParticleEffects aren't deleted for all the other clients
[url]http://wiki.garrysmod.com/page/gameevent/Listen[/url] Go crazy.
I know how to listen for disconnect, but the problem is that I don't know how to delete the ParticleEffects from the client when a player disconnects. General series of events: Player connects Player attaches a model to body ParticleEffect attaches to model Player disconnects Model is deleted ParticleEffect hovers in air permanently I need to call ent:StopParticles() on the model so that it will stop playing the effect, but the ClientsideModel is only accessible from the client, and that client has already left. So again, my question is how can I remove these particles from the server, or remove them from every client?
[QUOTE=RealDope;45350547]I know how to listen for disconnect, but the problem is that I don't know how to delete the ParticleEffects from the client when a player disconnects. General series of events: Player connects Player attaches a model to body ParticleEffect attaches to model Player disconnects Model is deleted ParticleEffect hovers in air permanently I need to call ent:StopParticles() on the model so that it will stop playing the effect, but the ClientsideModel is only accessible from the client, and that client has already left. So again, my question is how can I remove these particles from the server, or remove them from every client?[/QUOTE] Listen for player disconnect on client Grab player entity using the data passed by gameevent delete particles
[B]--Snip--[/B] Sorry, saw you're using clientside models. I don't know about that, sorry :/
You actually can't remove a particle effect because you don't spawn it as an object When you stop the particle with StopParticles you're stoping the emition, but some particles like tf2 doesn't use emiter, so that's why these keeps forever on the map, actually i haven't foun a solution for deal against these particles
[QUOTE=gonzalolog;45350786]You actually can't remove a particle effect because you don't spawn it as an object When you stop the particle with StopParticles you're stoping the emition, but some particles like tf2 doesn't use emiter, so that's why these keeps forever on the map, actually i haven't foun a solution for deal against these particles[/QUOTE] No way its impossible.. There must be a way :/ And @Humble, you can't listen for player disconnect on the client.
Maybe move the object too far away and just ignore it :v jk...Maybe you can search trought all the entities on the client and delete it in some way
[QUOTE=gonzalolog;45350786]You actually can't remove a particle effect because you don't spawn it as an object When you stop the particle with StopParticles you're stoping the emition, but some particles like tf2 doesn't use emiter, so that's why these keeps forever on the map, actually i haven't foun a solution for deal against these particles[/QUOTE] There are certain times when I call StopParticles on the tf2 particle systems that I'm using, and they do go away. The problem is just that I can't figure out where to call it when a player disconnects.
Bump :/
[QUOTE=RealDope;45350945]There are certain times when I call StopParticles on the tf2 particle systems that I'm using, and they do go away. The problem is just that I can't figure out where to call it when a player disconnects.[/QUOTE] I can't remember if players are caught in EntityRemoved but they should be, this hook is called right before the entity is removed and should suit you.
[QUOTE=RealDope;45350864]No way its impossible.. There must be a way :/ And @Humble, you can't listen for player disconnect on the client.[/QUOTE] gameevent player_disconnect is shared, isn't it?
[QUOTE=HumbleTH;45356186]gameevent player_disconnect is shared, isn't it?[/QUOTE] Is GM:ShutDown() called client-side when disconnecting? It signifies the shutting down of the Lua engine, so it might be.
Don't think it's shared, atleast the hook PlayerDisconnected isn't, and it makes sense; by the time the event is fired, the player has already disconnected, and the client is gone, so we can't listen for that event on their end. Also guys, you're missing the problem. I can listen for PlayerDisconnected on the serverside just fine, but my problem is that I have no way to access the ClientsideModel object. I think it would help if someone could explain the logic behind the creation of ClientsideModels. Like I said, I'm not writing this script, just modifying it. When the ClientsideModel() is called, the model appears for all clients. How does it get shared between them if the server never even knows about it? Thanks for all the replies guys, really hoping to get a fix.
[QUOTE=jackwilsdon;45356226]Is GM:ShutDown() called client-side when disconnecting? It signifies the shutting down of the Lua engine, so it might be.[/QUOTE] Yeah, it is, I send a message to client on shut-down. [code]function GM:ShutDown( ) if ( SERVER ) then MsgC( COLOR_RED, "[" .. GAMEMODE.Name .. "]-GameMode ShutDown\n" ); for k, v in pairs( player.GetAll( ) ) do v:Save( ); end else MsgC(COLOR_AMBER, "[" .. GAMEMODE.Name .. "] Thank you, we hope you had fun playing on our server, feel free to add us to your favorites list.\n"); MsgC(COLOR_AMBER, "[" .. GAMEMODE.Name .. "] If you experienced any bugs or issues with other players and wish to make a report,\n"); MsgC(COLOR_AMBER, "[" .. GAMEMODE.Name .. "] please email " .. GAMEMODE.Author .. " at " .. GAMEMODE.Email .. " or visit us on the web at " .. GAMEMODE.Website .. "\n"); end end[/code]
But that is a server side event for when the whole server shuts down, no? I have a ClientsideModel attached to a player, and a ParticleEffect (maybe ParticleSystem since I have to cache it first, not totally sure of the difference), and when the player disconnects, the ClientsideModel no longer shows to all other clients, but the particles do. I need to remove these particles..
Bumpin
I still don't understand how the ClientsideModel is visible to all players.
This is the function that creates the ClientsideModel: [code] function Player:PS_AddClientsideModel(item_id) if not PS.Items[item_id] then return false end local ITEM = PS.Items[item_id] local mdl = ClientsideModel(ITEM.Model, RENDERGROUP_OPAQUE) mdl:SetNoDraw(true) if ITEM.Particle ~= nil then ParticleEffectAttach(ITEM.Particle, PATTACH_ABSORIGIN_FOLLOW, mdl, 0) end if not PS.ClientsideModels[self] then PS.ClientsideModels[self] = {} end PS.ClientsideModels[self][item_id] = mdl end [/code] I cntrl+f searched the entirety of pointshop, and it's only ever called in a context that creates ClientsideModels for each item that a single player owns. So if I owned and was wearing 3 pointshop items, this function would be called 3 times for me. It never sends each player's models to every client so that all clients can see them, it seems that ClientsideModels do that automagically. These models follow the player by being moved on PostPlayerDraw like so: [code] hook.Add('PostPlayerDraw', 'PS_PostPlayerDraw', function(ply) if not ply:Alive() then return end if ply == LocalPlayer() and GetViewEntity():GetClass() == 'player' and (GetConVar('thirdperson') and GetConVar('thirdperson'):GetInt() == 0) then return end if not PS.ClientsideModels[ply] then return end for item_id, model in pairs(PS.ClientsideModels[ply]) do if not PS.Items[item_id] then PS.ClientsideModel[ply][item_id] = nil continue end local ITEM = PS.Items[item_id] if not ITEM.Attachment and not ITEM.Bone then PS.ClientsideModel[ply][item_id] = nil continue end local pos = Vector() local ang = Angle() if ITEM.Attachment then local attach_id = ply:LookupAttachment(ITEM.Attachment) if not attach_id then return end local attach = ply:GetAttachment(attach_id) if not attach then return end pos = attach.Pos ang = attach.Ang else local bone_id = ply:LookupBone(ITEM.Bone) if not bone_id then return end pos, ang = ply:GetBonePosition(bone_id) end model, pos, ang = ITEM:ModifyClientsideModel(ply, model, pos, ang) model:SetPos(pos) model:SetAngles(ang) model:SetRenderOrigin(pos) model:SetRenderAngles(ang) model:SetupBones() model:DrawModel() model:SetRenderOrigin() model:SetRenderAngles() end end) [/code] So the model is never officially attached to the player in any way, it just follows them around. However, when they disconnect, the model disappears. The only thing the addon does on disconnect is in sv_player_extension: [code] function Player:PS_PlayerDisconnected() self:PS_Save() PS.ClientsideModels[self] = nil if timer.Exists('PS_PointsOverTime_' .. self:UniqueID()) then timer.Destroy('PS_PointsOverTime_' .. self:UniqueID()) end end [/code] And at first glance, this may appear like it is setting the ClientsideModel to nil, however PS.ClientsideModel is a shared variable, declared in sh_pointshop.lua, so the client version and server are completely isolated from eachother, and in fact if you look at where the server adds items to the PS.ClientsideModels table, again in sv_player_extension: [code] PS.ClientsideModels[self][item_id] = item_id [/code] All it does is save the item id (an integer) for the simple purpose of knowing whether or not the item has a clientsidemodel for that player.
Bump... :(
[QUOTE=RealDope;45362458]But that is a server side event for when the whole server shuts down, no? I have a ClientsideModel attached to a player, and a ParticleEffect (maybe ParticleSystem since I have to cache it first, not totally sure of the difference), and when the player disconnects, the ClientsideModel no longer shows to all other clients, but the particles do. I need to remove these particles..[/QUOTE] GM:ShutDown is a SHARED function, meaning it can execute on client and server. It is set to execute on CLIENT when the client disconnects, reconnects, exists, etc.. It is set to execute on the SERVER when the server changes level, changes map, shutsdown ( but not from crashes unfortunately )... [QUOTE=jackwilsdon;45370025]I still don't understand how the ClientsideModel is visible to all players.[/QUOTE] The pointshop executes the command on all players so all players create their own ClientsideModel for the player that uses it. There are better ways, and if not properly managed can lead to memory leaks, disappearing entities on the map, and other issues. I haven't heard many complaints about the pointshop, but have for FAS2 [QUOTE=RealDope;45370814]This is the function that creates the ClientsideModel: [code] function Player:PS_AddClientsideModel(item_id) end [/code] I cntrl+f searched the entirety of pointshop, and it's only ever called in a context that creates ClientsideModels for each item that a single player owns. So if I owned and was wearing 3 pointshop items, this function would be called 3 times for me. It never sends each player's models to every client so that all clients can see them, it seems that ClientsideModels do that automagically. These models follow the player by being moved on PostPlayerDraw like so: [code] hook.Add('PostPlayerDraw', 'PS_PostPlayerDraw', function(ply) if not ply:Alive() then return end if ply == LocalPlayer() and GetViewEntity():GetClass() == 'player' and (GetConVar('thirdperson') and GetConVar('thirdperson'):GetInt() == 0) then return end if not PS.ClientsideModels[ply] then return end for item_id, model in pairs(PS.ClientsideModels[ply]) do if not PS.Items[item_id] then PS.ClientsideModel[ply][item_id] = nil continue end local ITEM = PS.Items[item_id] if not ITEM.Attachment and not ITEM.Bone then PS.ClientsideModel[ply][item_id] = nil continue end local pos = Vector() local ang = Angle() if ITEM.Attachment then local attach_id = ply:LookupAttachment(ITEM.Attachment) if not attach_id then return end local attach = ply:GetAttachment(attach_id) if not attach then return end pos = attach.Pos ang = attach.Ang else local bone_id = ply:LookupBone(ITEM.Bone) if not bone_id then return end pos, ang = ply:GetBonePosition(bone_id) end model, pos, ang = ITEM:ModifyClientsideModel(ply, model, pos, ang) model:SetPos(pos) model:SetAngles(ang) model:SetRenderOrigin(pos) model:SetRenderAngles(ang) model:SetupBones() model:DrawModel() model:SetRenderOrigin() model:SetRenderAngles() end end) [/code] So the model is never officially attached to the player in any way, it just follows them around. However, when they disconnect, the model disappears. The only thing the addon does on disconnect is in sv_player_extension: [code] function Player:PS_PlayerDisconnected() self:PS_Save() PS.ClientsideModels[self] = nil if timer.Exists('PS_PointsOverTime_' .. self:UniqueID()) then timer.Destroy('PS_PointsOverTime_' .. self:UniqueID()) end end [/code] And at first glance, this may appear like it is setting the ClientsideModel to nil, however PS.ClientsideModel is a shared variable, declared in sh_pointshop.lua, so the client version and server are completely isolated from eachother, and in fact if you look at where the server adds items to the PS.ClientsideModels table, again in sv_player_extension: [code] PS.ClientsideModels[self][item_id] = item_id [/code] All it does is save the item id (an integer) for the simple purpose of knowing whether or not the item has a clientsidemodel for that player.[/QUOTE] PostPlayerDraw is a Clientside function. It is called on ALL clients after they render a player. PrePlayerDraw is called beforehand. So, for every player on the server, for every player they see, Pre/PostPlayerDraw hook is called on them. So what happens is, when a client sees a player that is wearing something ( These are probably networked: for item_id, model in pairs(PS.ClientsideModels[ply]) do if not PS.Items[item_id] then PS.ClientsideModel[ply][item_id] = nil continue end ) -- the client loads the models, positions them, attaches them, etc...
sv_player_extension.lua [code] function Player:PS_AddClientsideModel(item_id) if not PS.Items[item_id] then return false end if not self:PS_HasItem(item_id) then return false end net.Start('PS_AddClientsideModel') net.WriteEntity(self) net.WriteString(item_id) net.Broadcast() if not PS.ClientsideModels[self] then PS.ClientsideModels[self] = {} end PS.ClientsideModels[self][item_id] = item_id end [/code] cl_pointshop.lua [code] net.Receive('PS_AddClientsideModel', function(length) local ply = net.ReadEntity() local item_id = net.ReadString() if not IsValid(ply) then if not invalidplayeritems[ply] then invalidplayeritems[ply] = {} end table.insert(invalidplayeritems[ply], item_id) return end ply:PS_AddClientsideModel(item_id) end) [/code] cl_player_extension.lua [code] function Player:PS_AddClientsideModel(item_id) if not PS.Items[item_id] then return false end local ITEM = PS.Items[item_id] local mdl = ClientsideModel(ITEM.Model, RENDERGROUP_OPAQUE) mdl:SetNoDraw(true) if not PS.ClientsideModels[self] then PS.ClientsideModels[self] = {} end PS.ClientsideModels[self][item_id] = mdl end [/code] sv_player_extension.lua [code] function Player:PS_PlayerDisconnected() self:PS_Save() PS.ClientsideModels[self] = nil if timer.Exists('PS_PointsOverTime_' .. self:UniqueID()) then timer.Destroy('PS_PointsOverTime_' .. self:UniqueID()) end end [/code] cl_pointshop.lua [code] hook.Add('PostPlayerDraw', 'PS_PostPlayerDraw', function(ply) if not ply:Alive() then return end if ply == LocalPlayer() and GetViewEntity():GetClass() == 'player' and (GetConVar('thirdperson') and GetConVar('thirdperson'):GetInt() == 0) then return end if not PS.ClientsideModels[ply] then return end for item_id, model in pairs(PS.ClientsideModels[ply]) do if not PS.Items[item_id] then PS.ClientsideModel[ply][item_id] = nil continue end local ITEM = PS.Items[item_id] if not ITEM.Attachment and not ITEM.Bone then PS.ClientsideModel[ply][item_id] = nil continue end local pos = Vector() local ang = Angle() if ITEM.Attachment then local attach_id = ply:LookupAttachment(ITEM.Attachment) if not attach_id then return end local attach = ply:GetAttachment(attach_id) if not attach then return end pos = attach.Pos ang = attach.Ang else local bone_id = ply:LookupBone(ITEM.Bone) if not bone_id then return end pos, ang = ply:GetBonePosition(bone_id) end model, pos, ang = ITEM:ModifyClientsideModel(ply, model, pos, ang) model:SetPos(pos) model:SetAngles(ang) model:SetRenderOrigin(pos) model:SetRenderAngles(ang) model:SetupBones() model:DrawModel() model:SetRenderOrigin() model:SetRenderAngles() end end) [/code] These are most of the relevant bits to how PointShop handles this. Also if you're ctrl+Fing through all files individually, both notepad++ and sublime text have options to search through all files in a directory.
-snip- I'm an idiot
Ok new problem: The creator of the addon stores tables in PS.ClientsideModels, indexing by player object, generally referenced from the client with "self". However, the ShutDown hook is called after the player object for that client is gone, so when I try to do: [code] hook.Add('ShutDown', 'shuttingdown', function() for item_id, mdl in pairs(PS.ClientsideModels[self]) do -- self is already nil so I can't reference these models mdl:StopParticles() end end) [/code] Self is already nil and I can't access the models saved for this player.
It's only referenced as self because that's a local variable included by the player meta functions. If the code is clientside, which it should be, then do PS.ClientsideModles[LocalPlayer()], that might work, I've not tested it and I'm unsure of it the player object is already gone. I assume so because when the Lua environment shutdown it would be in the menu state.
Out of curiosity, would PostPlayerDraw be called one last time when the player disconnects? Can I use that final call of it to disable the particles for all their models? [editline]14th July 2014[/editline] Also: I don't see any where in here that removes the ClientsideModels when players disconnect, how do the other clients know that they should no longer see the ClientsideModel that was being drawn attached to the disconnected player. How come it doesn't just hover where the player was before since PostPlayerDraw is no longer being called and modifying the location of the model? [editline]14th July 2014[/editline] UPDATE: The LocalPlayer() didn't help, it's still nil.
Sorry, you need to Log In to post a reply to this thread.