Help optimizing a function



  • I redid my weapon tiers system to be serverside and it works but sadly it causes a massive delay at the start of a wave I figured it’s the for loop. I sadly do not know of any other way to get the pointshop weapons

    So far I think there’s a delay cause it’s calling IsWeaponUnlocked twice for every weapon, IsWeaponUnlocked calls GetNumberOfWaves(), GetWave() and CalculateInfliction()

    It’s called by WaveStateChanged at the very end after gamemode.Call(“OnWaveStateChanged”)

    Here is the code

    function GM:UpdateWeaponUnlockes(waveactive)
    	if waveactive then
    		for _, tab in pairs(self.Items) do
    			if tab.SWEP then
    				if not self.WeaponUnlocks[tab.SWEP].Unlocked == self:IsWeaponUnlocked(tab.SWEP) then
    					self.WeaponUnlocks[tab.SWEP].Unlocked = self:IsWeaponUnlocked(tab.SWEP)
    				end
    			end
    		end
    
    		net.Start("zs_weaponlocks")
    			net.WriteTable(self.WeaponUnlocks)
    		net.Broadcast()
    	end
    end
    
    function GM:IsWeaponUnlocked(classname)
        local weaponwave = self.WeaponUnlocks[classname].Wave
        local infliction = self:CalculateInfliction()
        local IsObjective = self.ObjectiveMap
        local wave = self:GetWave()
    
        if weaponwave == nil then
            return true
        end
    
        if IsObjective and wave >= 1 and self:GetWaveActive() then
            return true
        elseif wave == self:GetNumberOfWaves() then
            return true
        elseif wave >= weaponwave then
            return true
        elseif infliction >= 0.8 and weaponwave <= self:GetNumberOfWaves() then
            return true
        elseif infliction >= 0.5 and weaponwave <= 4 then
            return true
        end
    
        return false
    end
    


  • I redid my weapon tiers system to be serverside and it works but sadly it causes a massive delay at the start of a wave I figured it’s the for loop. I sadly do not know of any other way to get the pointshop weapons

    So far I think there’s a delay cause it’s calling IsWeaponUnlocked twice for every weapon, IsWeaponUnlocked calls GetNumberOfWaves(), GetWave() and CalculateInfliction()

    It’s called by WaveStateChanged at the very end after gamemode.Call(“OnWaveStateChanged”)

    Here is the code

    function GM:UpdateWeaponUnlockes(waveactive)
    	if waveactive then
    		for _, tab in pairs(self.Items) do
    			if tab.SWEP then
    				if not self.WeaponUnlocks[tab.SWEP].Unlocked == self:IsWeaponUnlocked(tab.SWEP) then
    					self.WeaponUnlocks[tab.SWEP].Unlocked = self:IsWeaponUnlocked(tab.SWEP)
    				end
    			end
    		end
    
    		net.Start("zs_weaponlocks")
    			net.WriteTable(self.WeaponUnlocks)
    		net.Broadcast()
    	end
    end
    
    function GM:IsWeaponUnlocked(classname)
        local weaponwave = self.WeaponUnlocks[classname].Wave
        local infliction = self:CalculateInfliction()
        local IsObjective = self.ObjectiveMap
        local wave = self:GetWave()
    
        if weaponwave == nil then
            return true
        end
    
        if IsObjective and wave >= 1 and self:GetWaveActive() then
            return true
        elseif wave == self:GetNumberOfWaves() then
            return true
        elseif wave >= weaponwave then
            return true
        elseif infliction >= 0.8 and weaponwave <= self:GetNumberOfWaves() then
            return true
        elseif infliction >= 0.5 and weaponwave <= 4 then
            return true
        end
    
        return false
    end
    


  • You shouldn’t need to keep a data table or network that stuff since the client has access to it all.



  • @JetBoom:

    You shouldn’t need to keep a data table or network that stuff since the client has access to it all.

    I see, how would I go about doing this as the system I had before just had IsWeaponUnlocked on the shared side but sadly it caused issues as CalculateInfliction and ObjectiveMap are on the server so when IsWeaponUnlocked is called by the clientside from ppointshop it would return something different then the server and you’d have wepaons that are locked but unlocked or are unlocked but are locked



  • Hmm well I compeltely redid the system to work like the zombie classes do but I have one problem and that’s this

    function GM:AddPointShopItem(signature, name, desc, category, points, worth, callback, model)
    	local weapon = weapons.GetStored(worth)
    	if weapon then
    		if weapon.Wave then
    			weapon.Wave = math.floor(weapon.Wave * self:GetNumberOfWaves())
    		else
    			weapon.Wave = 0
    		end
    	end
    	return self:AddItem("ps_"..signature, name, desc, category, points, worth, callback, model, false, true)
    end
    

    weapon or weapon.Wave is nil when it shouldn’t be or the math function sets weapon.Wave to 0 because apparently self:GetNumberOfWaves() returns 0. I’m going to have to move this elsewhere but I dunno where exactly



  • If I understand it right, weapon.Wave contains the (percentage)wave in which the weapon should unlock? It makes more sense to do these checks and calculations at purchase time rather than at the start of the game, preventing the player from purchasing it just as if they didn’t have the number of points required. The UI element should change on the same check.



  • @Benjy:

    If I understand it right, weapon.Wave contains the (percentage)wave in which the weapon should unlock? It makes more sense to do these checks and calculations at purchase time rather than at the start of the game, preventing the player from purchasing it just as if they didn’t have the number of points required. The UI element should change on the same check.

    Yes it does and could you explain? from what I can see you mean place this in ItemPanelThink cause like how the points work the panels are red and etc when unable to be purchased and change if you’re in the pointshop when a wave begin.

    eg:

    local function ItemPanelThink(self)
    	local itemtab = FindItem(self.ID)
    	if itemtab then	
    		local newstate = MySelf:GetPoints() >= math.ceil(itemtab.Worth * (GAMEMODE.m_PointsShop.m_LastNearArsenalCrate and GAMEMODE.ArsenalCrateMultiplier or 1)) and not (itemtab.NoClassicMode and GAMEMODE:IsClassicMode())
    		local wavestate = GAMEMODE:IsWeaponUnlocked(itemtab.SWEP)
    		if wavestate ~= self.m_WeaponUnlock then
    			self.m_WeaponUnlock = wavestate
    			if not wavestate then
    				self:AlphaTo(90, 0.75, 0)
    				self.m_NameLabel:SetTextColor(COLOR_RED)
    				self.m_BuyButton:SetImage("icon16/lock.png")
    
    				self.m_BuyButton:SizeToContents()
    			end
    		end
    
    		if not wavestate then
    			return
    		end
    
    		if newstate ~= self.m_LastAbleToBuy then
    			self.m_LastAbleToBuy = newstate
    			if newstate then
    				self:AlphaTo(255, 0.75, 0)
    				self.m_NameLabel:SetTextColor(COLOR_WHITE)
    				self.m_BuyButton:SetImage("icon16/accept.png")
    			else
    				self:AlphaTo(90, 0.75, 0)
    				self.m_NameLabel:SetTextColor(COLOR_RED)
    				self.m_BuyButton:SetImage("icon16/exclamation.png")
    			end
    
    			self.m_BuyButton:SizeToContents()
    		end
    	end
    end
    

    I’ve tried using the code in places like InitPostEntity but still comes out as 0 so I have no idea when GetNumberOfWaves will actually return a value, it’s best I call the code only once cause I do not want to multiply the value by the number of waves a second time



  • @ForrestMarkX:

    I can see you mean place this in ItemPanelThink cause like how the points work the panels are red

    That’s the visual aspect of it yes but the code you posted has way too many operations for a think function since it’s replacing VGUI elements constantly. Something like this should do the trick and doesn’t add much to computational cost.

    
    local function ItemPanelThink(self)
    	local itemtab = FindItem(self.ID)
    	if itemtab then
    		local newstate = MySelf:GetPoints() >= math.ceil(itemtab.Worth * (GAMEMODE.m_PointsShop.m_LastNearArsenalCrate and GAMEMODE.ArsenalCrateMultiplier or 1)) and not (itemtab.NoClassicMode and GAMEMODE:IsClassicMode()) and GAMEMODE:GetWave() >= (GAMEMODE:GetNumberOfWaves() * itemtab.Wave or 0)
    		if newstate ~= self.m_LastAbleToBuy then
    			self.m_LastAbleToBuy = newstate
    			if newstate then
    				self:AlphaTo(255, 0.75, 0)
    				self.m_NameLabel:SetTextColor(COLOR_WHITE)
    				self.m_BuyButton:SetImage("icon16/accept.png")
    			else
    				self:AlphaTo(90, 0.75, 0)
    				self.m_NameLabel:SetTextColor(COLOR_RED)
    				self.m_BuyButton:SetImage("icon16/exclamation.png")
    			end
    
    			self.m_BuyButton:SizeToContents()
    		end
    	end
    end
    
    

    @ForrestMarkX:

    I’ve tried using the code in places like InitPostEntity but still comes out as 0 so I have no idea when GetNumberOfWaves will actually return a value.
    […]
    it’s best I call the code only once cause I do not want to multiply the value by the number of waves a second time

    Only if you overwrite the value, which you shouldn’t be doing for an item parameter. zs_pointsshopbuy in init.lua is where you should be deciding if someone should be allowed the weapon. GetNumberOfWaves gets the current state, if you call this at warm-up then the number of waves can be zero, hence needing to do this on the fly. You only need to add this in zs_pointsshopbuy after the assessment of enough having enough points.

    
    if GAMEMODE:GetWave() < (GAMEMODE:GetNumberOfWaves() * itemtab.Wave or 0)  then
    	sender:CenterNotify(COLOR_RED, "Item is not yet unlocked!")
    	sender:SendLua("surface.PlaySound(\"buttons/button10.wav\")")
    	return
    end
    
    


  • @Benjy:

    That’s the visual aspect of it yes but the code you posted has way too many operations for a think function since it’s replacing VGUI elements constantly. Something like this should do the trick and doesn’t add much to computational cost.

    
    local function ItemPanelThink(self)
    	local itemtab = FindItem(self.ID)
    	if itemtab then
    		local newstate = MySelf:GetPoints() >= math.ceil(itemtab.Worth * (GAMEMODE.m_PointsShop.m_LastNearArsenalCrate and GAMEMODE.ArsenalCrateMultiplier or 1)) and not (itemtab.NoClassicMode and GAMEMODE:IsClassicMode()) and GAMEMODE:GetWave() >= (GAMEMODE:GetNumberOfWaves() * itemtab.Wave or 0)
    		if newstate ~= self.m_LastAbleToBuy then
    			self.m_LastAbleToBuy = newstate
    			if newstate then
    				self:AlphaTo(255, 0.75, 0)
    				self.m_NameLabel:SetTextColor(COLOR_WHITE)
    				self.m_BuyButton:SetImage("icon16/accept.png")
    			else
    				self:AlphaTo(90, 0.75, 0)
    				self.m_NameLabel:SetTextColor(COLOR_RED)
    				self.m_BuyButton:SetImage("icon16/exclamation.png")
    			end
    
    			self.m_BuyButton:SizeToContents()
    		end
    	end
    end
    
    

    Only if you overwrite the value, which you shouldn’t be doing for an item parameter. zs_pointsshopbuy in init.lua is where you should be deciding if someone should be allowed the weapon. GetNumberOfWaves gets the current state, if you call this at warm-up then the number of waves can be zero, hence needing to do this on the fly. You only need to add this in zs_pointsshopbuy after the assessment of enough having enough points.

    
    if GAMEMODE:GetWave() < (GAMEMODE:GetNumberOfWaves() * itemtab.Wave or 0)  then
    	sender:CenterNotify(COLOR_RED, "Item is not yet unlocked!")
    	sender:SendLua("surface.PlaySound(\"buttons/button10.wav\")")
    	return
    end
    
    

    I see to use your code though I’d need to set Wave in Items from AddItem which would need adding a extra parameter to the function hence why I just check Wave in the weapon directly sort of like how it does for the Zombie classes, I probably can not use itemtab.Wave as IsWeaponUnlocked does the exact same thing (seeing your code I could just do the multiplication via IsWeaponUnlocked) except for a few exceptions eg Objective maps which I’m sure may not work for the pointshop panel part as IsObjectiveMap is server-side

    eg:

    function GM:IsWeaponUnlocked(classname)
    	local weapontab = weapons.GetStored(classname)
    	if not weapontab then return true end
    
    	if weapontab.IsClassUnlocked then
    		local ret = weapontab:IsClassUnlocked()
    		if ret ~= nil then return ret end
    	end
    
    	if GAMEMODE.ObjectiveMap and GAMEMODE:GetWave() >= 2 then
    		return true
    	end
    
    	return weapontab.Unlocked or self:GetWave() >= (self:GetNumberOfWaves() * weapontab.Wave or 0)
    end
    


  • @ForrestMarkX:

    I see to use your code though I’d need to set Wave in Items from AddItem which would need adding a extra parameter to the function

    There’s nothing wrong with doing that.



  • @Benjy:

    There’s nothing wrong with doing that.

    True, but wouldn’t it be more modular to just add the Wave variable to the weapon itself? Without needing to modify base functions

    but then agian I would not need to keep calling weapons.GetStored everytime and instead access a table but the real question is how would I go about checking if it’s an objective map then just unlock all the weapons on wave 2?

    Edit: After doing some edits this is the final results

    function GM:IsWeaponUnlocked(classname)
    	local weapontab = self.Items[classname]
    	if not weapontab then return true end
    
    	if self.ObjectiveMap and self:GetWave() >= 2 then
    		return true
    	end
    
    	return weapontab.Unlocked or self:GetWave() >= (self:GetNumberOfWaves() * weapontab.Wave or 0)
    end
    
    function GM:AddItem(signature, name, desc, category, worth, swep, callback, model, worthshop, pointshop, wave, unlocked)
    	local tab = {Signature = signature, Name = name, Description = desc, Category = category, Worth = worth or 0, SWEP = swep, Callback = callback, Model = model, WorthShop = worthshop, PointShop = pointshop, Wave = wave, Unlocked = unlocked}
    	self.Items[#self.Items + 1] = tab
    
    	return tab
    end
    
    function GM:AddStartingItem(signature, name, desc, category, points, worth, callback, model)
    	return self:AddItem(signature, name, desc, category, points, worth, callback, model, true, false, 0, true)
    end
    
    function GM:AddPointShopItem(signature, name, desc, category, points, worth, wave, unlocked, callback, model)
    	return self:AddItem("ps_"..signature, name, desc, category, points, worth, callback, model, false, true, wave or 0, unlocked or true)
    end
    
    GM:AddPointShopItem("uzi", "'Sprayer' Uzi 9mm", nil, ITEMCAT_GUNS, 70, "weapon_zs_uzi", 1 / 3, false)
    GM:AddPointShopItem("shredder", "'Shredder' SMG", nil, ITEMCAT_GUNS, 70, "weapon_zs_smg", 1 / 3, false)
    GM:AddPointShopItem("bulletstorm", "'Bullet Storm' SMG", nil, ITEMCAT_GUNS, 70, "weapon_zs_bulletstorm", 1 / 3, false)
    GM:AddPointShopItem("silencer", "'Silencer' SMG", nil, ITEMCAT_GUNS, 70, "weapon_zs_silencer", 1 / 3, false)
    GM:AddPointShopItem("annihilator", "'Annihilator' SMG", nil, ITEMCAT_GUNS, 75, "weapon_zs_annihilator", 1 / 3, false)
    GM:AddPointShopItem("hunter", "'Hunter' Rifle", nil, ITEMCAT_GUNS, 70, "weapon_zs_hunter", 1 / 3, false)
    
    GM:AddPointShopItem("reaper", "'Reaper' UMP", nil, ITEMCAT_GUNS, 80, "weapon_zs_reaper", 1 / 2, false)
    GM:AddPointShopItem("ender", "'Ender' Automatic Shotgun", nil, ITEMCAT_GUNS, 75, "weapon_zs_ender", 1 / 2, false)
    GM:AddPointShopItem("akbar", "'Akbar' Assault Rifle", nil, ITEMCAT_GUNS, 80, "weapon_zs_akbar", 1 / 2, false)
    GM:AddPointShopItem("oicw", "'OICW' Assault Rifle", nil, ITEMCAT_GUNS, 90, "weapon_zs_oicw", 1 / 2, false)
    GM:AddPointShopItem("riddler", "'Riddler' Assault Rifle", nil, ITEMCAT_GUNS, 90, "weapon_zs_galil", 1 / 2, false)
    
    GM:AddPointShopItem("stalker", "'Stalker' Assault Rifle", nil, ITEMCAT_GUNS, 125, "weapon_zs_m4", 2 / 3, false)
    GM:AddPointShopItem("inferno", "'Inferno' Assault Rifle", nil, ITEMCAT_GUNS, 125, "weapon_zs_inferno", 2 / 3, false)
    GM:AddPointShopItem("annabelle", "'Annabelle' Rifle", nil, ITEMCAT_GUNS, 100, "weapon_zs_annabelle", 2 / 3, false)
    GM:AddPointShopItem("infil", "'Infiltrator' G3SG/1", nil, ITEMCAT_GUNS, 110, "weapon_zs_g3sg1", 2 / 3, false)
    
    GM:AddPointShopItem("crossbow", "'Impaler' Crossbow", nil, ITEMCAT_GUNS, 175, "weapon_zs_crossbow", 5 / 6, false)
    GM:AddPointShopItem("sawedoff", "'Sawed-Off' Shotgun", nil, ITEMCAT_GUNS, 150, "weapon_zs_sawedoff", 5 / 6, false)
    GM:AddPointShopItem("elimantorrifle", "'Eliminator' SIG SG552", nil, ITEMCAT_GUNS, 150, "weapon_zs_sg552", 5 / 6, false)
    GM:AddPointShopItem("sweeper", "'Sweeper' Shotgun", nil, ITEMCAT_GUNS, 200, "weapon_zs_sweepershotgun", 5 / 6, false)
    GM:AddPointShopItem("boomstick", "Boom Stick", nil, ITEMCAT_GUNS, 200, "weapon_zs_boomstick", 5 / 6, false)
    
    GM:AddPointShopItem("slugrifle", "'Tiny' Slug Rifle", nil, ITEMCAT_GUNS, 200, "weapon_zs_slugrifle", 1, false)
    GM:AddPointShopItem("pulserifle", "'Adonis' Pulse Rifle", nil, ITEMCAT_GUNS, 225, "weapon_zs_pulserifle", 1, false)
    GM:AddPointShopItem("killerrifle", "'Killer' SIG SG550", nil, ITEMCAT_GUNS, 230, "weapon_zs_sg550", 1, false)
    GM:AddPointShopItem("combiensniper", "'Destroyer' Sniper Rifle", nil, ITEMCAT_GUNS, 300, "weapon_zs_csniper", 1, false)
    GM:AddPointShopItem("laserboomstick", "Laser BoomStick", nil, ITEMCAT_GUNS, 300, "weapon_zs_laserboomstick", 1, false)
    GM:AddPointShopItem("grimreaper", "'Grim Reaper' Machince Gun", nil, ITEMCAT_GUNS, 350, "weapon_zs_m249", 1, false)
    


  • I also have another issue, I want the players to be notified when a infliction is reached that a certain weapon tier was unlocked but the way it does here would spam the Wave for every weapon I currently do not know how to make it only call once for each level of infliction

            for k, v in ipairs(self.Items) do
    			if v.SWEP then
    				local weapon = weapons.GetStored(v.SWEP)
    				if weapon.Infliction and infliction >= weapon.Infliction and not self:IsWeaponUnlocked(v.SWEP) then
    					v.Unlocked = true
    
    					for _, pl in pairs(player.GetAll()) do
    						pl:CenterNotify(translate.ClientFormat(pl, "x_weapon_wave_unlocked", v.Wave))
    					end
    				end
    			end
            end
    


  • @ForrestMarkX:

    True, but wouldn’t it be more modular to just add the Wave variable to the weapon itself? Without needing to modify base functions

    You’re editing how weapons are bought, you might as well keep it consistent. You can add them to the swep themselves which perhaps makes making custom map logic a bit easier to deal with.

    @ForrestMarkX:

    objective map then just unlock all the weapons on wave 2?

    That doesn’t make a lot of sense. Some objective maps have custom wave structures.

    @ForrestMarkX:

    I also have another issue, I want the players to be notified when a infliction is reached that a certain weapon tier was unlocked but the way it does here would spam the Wave for every weapon I currently do not know how to make it only call once for each level of infliction

            for k, v in ipairs(self.Items) do
    			if v.SWEP then
    				local weapon = weapons.GetStored(v.SWEP)
    				if weapon.Infliction and infliction >= weapon.Infliction and not self:IsWeaponUnlocked(v.SWEP) then
    					v.Unlocked = true
    					
    					for _, pl in pairs(player.GetAll()) do
    						pl:CenterNotify(translate.ClientFormat(pl, "x_weapon_wave_unlocked", v.Wave))
    					end
    				end
    			end
            end
    

    Why not use “and not v.Unlocked” to your if statement?

    GM:CenterNotifyAll(…) exists which is more efficient than that inner for loop but the zombie team probably don’t care which weapons are unlocked so filter for pl:Team()==TEAM_HUMAN



  • @Benjy:

    That doesn’t make a lot of sense. Some objective maps have custom wave structures.

    Well with objective maps after wave 1 obviously some of the better weapons that may be needed to win the map will be locked cause waves never progress in objective maps but every objective map I’ve played has wave 1 then goes to wave 2 where it just stops

    Edit: Wouldn’t a not v.Unlocked statement never work? as it’s set before the notify is called and it would still call it for every weapon that is unlocked



  • If the wavecounter doesn’t show on the hud then the mapper has probably set maxwaves and current wave to -1.



  • @Benjy:

    If the wavecounter doesn’t show on the hud then the mapper has probably set maxwaves and current wave to -1.

    Hmm well that complicates things



  • The infliction thing is super confusing it’s required to be in the for loop cause it needs to know what tier is unlocked but if it’s in the for loop then it’ll say something like

    “Weapon tier 5 is unlocked
    Weapon tier 5 is unlocked
    Weapon tier 5 is unlocked
    Weapon tier 5 is unlocked
    Weapon tier 5 is unlocked”

    but I can’t put in a variable that says something like pl.WeaponWaveNotified cause then it won’t notify for later inflictions like 80%



  • use break to exit out of the for loop then.



  • @Benjy:

    use break to exit out of the for loop then.

    Wouldn’t that then cause the other weapons that should be unlocked to not be unlocked?



  • Oh right, I’d forgotten you’re still assigning this like this so ignore the break. I wouldn’t recommend so many different conventions (why wouldn’t you process weapon locks on infliction like you have done with waves) but if you’re admiant of doing it like this then:

    
            local inflictionwepnotified = false;
            for k, v in ipairs(self.Items) do
    			if v.SWEP then
    				local weapon = weapons.GetStored(v.SWEP)
    				if weapon.Infliction and infliction >= weapon.Infliction and not self:IsWeaponUnlocked(v.SWEP) then
    					v.Unlocked = true
                                            inflictionwepnotify = true;
    				end
    			end
            end
            if inflictionwepnotify then
    	for _, pl in pairs(player.GetAll()) do
    		if pl:Team()==TEAM_HUMAN then pl:CenterNotify(translate.ClientFormat(pl, "x_weapon_wave_unlocked", v.Wave)) end
    	end
    
    

Log in to reply
 

7
Online

11080
Users

15301
Topics

298004
Posts

Looks like your connection to NoXiousNet was lost, please wait while we try to reconnect.