· 5 years ago · May 19, 2020, 08:10 AM
1--[[
2 © 2020 The Empire Limited, re-use or modify
3
4 without permission of its author (chris@theempire.com).
5]]
6
7
8
9
10util.AddNetworkString("ReloadScoreboard")
11util.AddNetworkString("ProcessLoader.ClientProcessComplete")
12util.AddNetworkString("ProcessLoader.ProcessData")
13util.AddNetworkString("ProcessLoader.PlayerLoaded") -- Called when the process loader finishes sending data
14util.AddNetworkString("ProcessLoader.ClientReady") -- When the client is fully loaded, starts the process loader
15CreateConVar("e_spawning", 0, FCVAR_ARCHIVE)
16
17Empire.ProcessLoader = Empire.ProcessLoader or {}
18Empire.ProcessLoader.processes = Empire.ProcessLoader.processes or {}
19Empire.ProcessLoader.client_process_names = Empire.ProcessLoader.client_process_names or {} // Table of process names for clients
20Empire.ProcessLoader.PROCESS_TIMEOUT = 22 -- The max execution time a process can take after this we kill it and move on
21
22
23 -- * The function for register player load processes.
24 -- * When your module loads call this function on the hook "ERP.ProcessLoader.Initialized"
25 -- * process_name: The friendly name of your process
26 -- * process_func: The function to call when the process is executed the ply param will be passed
27 -- * @return: Int your assigned process id. Or false if registration failed
28
29function Empire.ProcessLoader:Register(process_name, process_func )
30
31 // Check that both parameters exists
32 if not process_name or not process_func then
33 local name = process_name or "Unknown module"
34 ErrorNoHalt("[ProcessLoader] ERROR: Not registering module: " .. name .. ". Invalid parameters")
35 return false;
36 end
37
38 if not isfunction(process_func) then
39 ErrorNoHalt("[ProcessLoader] ERROR: Not registering module: " .. process_name .. ". Invalid function passed")
40 return false;
41 end
42
43 // Create the process table and insert it to the processes
44 local process = {
45 name = process_name,
46 func = process_func
47 }
48 local Process_ID = table.insert(self.processes, process)
49 self.client_process_names[Process_ID] = process_name // Add the process name to the table clients will receive
50
51 Empire.DebugPrint("[ProcessLoader] Process" , process_name, "(", Process_ID, ") Registered." )
52
53 return Process_ID
54end
55
56// Function responsible for executing processes
57function Empire.ProcessLoader:ExecuteProcess( ply, Process )
58
59 if not ply or not IsEntity(ply) or ply:IsBot() then
60 Empire.DebugPrint("[ProcessLoader] Process" , process_name, "was unable to execute because the player is invalid.")
61 return false
62 end
63 -- Attempt to call the process function
64 local success, err = pcall( Process.func, ply )
65
66 -- Check the response
67 if success then
68
69 -- This timer will kill a process if it doesn't complete within the set timeout length
70 timer.Create("Empire.ProcessLoader.Client:"..ply:SteamID64()..".timeout", self.PROCESS_TIMEOUT, 1, function()
71 if not IsValid(ply) then return end
72 print("[ProcessLoader] WARNING: Process: " .. Process.name .. " timed out for player: " .. ply:Nick() )
73 self:NextProcess(ply)
74 end )
75
76 return true
77
78 else
79 print(err)
80 ErrorNoHalt("[ProcessLoader] ERROR: Failed to execute process " .. Process.name .. " on player " .. ply:Nick() .. "\n")
81 return false
82 end
83end
84
85// Starts off the process loader for the player passed
86function Empire.ProcessLoader:Start(ply)
87
88 if not ply or not IsValid(ply) or ply:IsBot() then
89 ErrorNoHalt("[ProcessLoader] Error: Start function called on invalid player entity")
90 return false
91 end
92
93 // Check to see if the player has already started loading.
94 if ply:GetNW2Int("ProcessLoader.CurrentProcess", -1) != -1 then
95 ErrorNoHalt("[ProcessLoader] Error: Attempted to start on loading/loaded player: ", ply:Nick())
96 return false;
97 end
98
99 // Initialize the player and start the loader
100 ply:SetNW2Int("ProcessLoader.CurrentProcess", 0)
101
102 net.Start("ProcessLoader.ProcessData")
103 net.WriteTable(self.client_process_names)
104 net.Send(ply)
105
106 ply:Freeze(true)
107 ply.FullySpawned = false
108 ply.Starting = CurTime() // Used to track how long the entire load takes
109
110 -- This will break if there are no processes to load, but that should never happen so I'm allowing it to throw an error and crash so
111 -- If that problem ever does occurr it will be noticed and hopefully fixed.
112 self:NextProcess(ply)
113end
114
115function Empire.ProcessLoader:NextProcess(ply)
116 local process_num = ply:GetNW2Int("ProcessLoader.CurrentProcess")
117
118 -- kill the timeout timer.
119 if timer.Exists("Empire.ProcessLoader.Client:"..ply:SteamID64()..".timeout") then
120 timer.Destroy("Empire.ProcessLoader.Client:"..ply:SteamID64()..".timeout")
121 end
122
123 // Check if they're done loading
124 if process_num == #self.processes then
125 Empire.ProcessLoader:LoadingComplete(ply)
126 return
127 end
128
129 ply:SetNW2Int("ProcessLoader.CurrentProcess", process_num + 1)
130 ply.LastStartTime = CurTime()
131 local next_process = self.processes[process_num+1]
132 // Attempt to run the next one
133 if not Empire.ProcessLoader:ExecuteProcess(ply, next_process) then
134 // failed, move on to the next process
135 Empire.ProcessLoader:NextProcess(ply)
136 end
137end
138
139
140// Called when the process loader is finished loading the players data
141function Empire.ProcessLoader:LoadingComplete(ply)
142 if not IsValid(ply) then
143 ErrorNoHalt("[ProcessLoader] Error: Attempted to call LoadingComplete on a invalid player.")
144 return false
145 end
146 Empire.DebugPrint("[ProcessLoader] Finished loading player",ply:Nick(), " in ", CurTime() - ply.Starting, "seconds")
147
148 net.Start("ProcessLoader.PlayerLoaded") // Notify the client we are done
149 net.Send(ply)
150
151 ply:Freeze(false)
152 ply.FullySpawned = true
153 ply:SetNW2Int("ProcessLoader.CurrentProcess", #Empire.ProcessLoader.processes + 1)
154 ply:SetNW2Bool("ProcessLoader.FullyLoaded", true)
155 ply:Employ(Empire.Jobs.DefaultJob or 1)
156 ply:Spawn()
157 erp_settings:LoadConVars(ply)
158
159
160 -- Send the player all currently conneted player names
161 ply:SendInitialPlayerNames()
162
163 // Notify the modules that the player is now loaded in case they want to do anything
164 hook.Call( "ERP.ProcessLoader.PlayerLoaded", GAMEMODE, ply)
165end
166
167// Usually this is called by the client but if you need to call it on the server you can, But don't use this and the client library
168function Empire.ProcessLoader:ProcessCompleted(ply)
169 local process_num = ply:GetNW2Int("ProcessLoader.CurrentProcess", -1)
170
171 if process_num == -1 then
172 error("[ProcessLoader] Received process complete notification for player with unset process. (Bad client code?)")
173 return
174 end
175
176 if process_num > #self.processes then
177 return
178 end
179
180 local ProcessTime = CurTime() - ply.LastStartTime
181 // Check if the client is done
182
183 Empire.ProcessLoader:NextProcess(ply)
184end
185
186// This is where we recieve updates that the client completed loading
187net.Receive("ProcessLoader.ClientProcessComplete", function(l, ply)
188 Empire.ProcessLoader:ProcessCompleted(ply)
189end)
190
191
192
193// Here we create our custom hook that lets modules know we are ready to recieve there processes
194// This might not be needed but I didn't want any race conditions where a module would try to reference this one before it loaded
195hook.Add("PostGamemodeLoaded", "ERP.ProcessLoaderStartRegister", function()
196 Empire.DebugPrint("[ProcessLoader] Ready to register modules. Calling Hook...");
197 hook.Call( "ERP.ProcessLoader.Initialized" )
198end)
199
200
201-- This initializes the basics on the player and calls ply:InitalizeData() this doesn't start the data loading
202hook.Add("PlayerInitialSpawn", "ERP.ProcessLoader.MainPlayerSpawn", function(ply)
203 -- Update everyones scoreboard data
204 net.Start("ReloadScoreboard")
205 net.SendOmit(ply)
206 -- Set the players default data
207 ply:SetModel("models/player/zelpa/male_0"..math.random(1,9).."_extended.mdl")
208 ply:SetPos(Vector(math.random(-7677, -9123), math.random(-6866, -8719), 136.03))
209 ply:SetTeam(Empire.Jobs.DefaultJob)
210 Empire.DebugPrint("[ProcessLoader] player loaded")
211
212 timer.Simple(0, function()
213 ply:Spectate(OBS_MODE_FREEZECAM)
214 ply:SetObserverMode(OBS_MODE_FREEZECAM)
215 end)
216end)
217
218-- Unspectate when the process loader finishes, probably not needed since the player is respawned anyway
219hook.Add("ERP.ProcessLoader.PlayerLoaded", "ERP.ProcessLoader.UnSpectate", function(ply)
220 ply:UnSpectate()
221end)
222
223-- The client will send this net message once it is fully loaded and ready to recieve data
224net.Receive("ProcessLoader.ClientReady", function(len, ply)
225 print(ply:Nick())
226 ply:InitializeData() -- This starts the process loader, this will call the load function in sv_sql which will then call process loader start
227end)
228
229hook.Add("PlayerDisconnect", "RemovePlayers", function(ply)
230 timer.Simple(1, function()
231 net.Start("ReloadScoreboard")
232 net.Broadcast()
233 end)
234end)