From ab5ea7895799f1b9104e31ce7940ea3454efdcba Mon Sep 17 00:00:00 2001 From: Kibi Kelburton Date: Sun, 10 May 2026 04:27:29 +0200 Subject: [PATCH] testing to fix memleak and disabling voting requires maprotation --- maps/mp/gametypes/_callbacksetup.gsc | 20 +++--- maps/mp/gametypes/_gamelogic.gsc | 13 +++- mod/main.gsc | 92 +++++++++++++++++++--------- mod/streaks.gsc | 11 +++- mod/vote.gsc | 8 +-- mod/weapons.gsc | 2 + 6 files changed, 98 insertions(+), 48 deletions(-) diff --git a/maps/mp/gametypes/_callbacksetup.gsc b/maps/mp/gametypes/_callbacksetup.gsc index ff4074b..ebb384d 100755 --- a/maps/mp/gametypes/_callbacksetup.gsc +++ b/maps/mp/gametypes/_callbacksetup.gsc @@ -94,14 +94,18 @@ CodeCallback_PlayerDamage(eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath return; } } - damagehud = eAttacker createDamageHud(); - damageHud setValue(iDamage); - damageHud moveOverTime(1); - damageHud.x -= 300; - damageHud.y += (-1 * randomInt(150)); - damageHud fadeOverTime(1); - damageHud.alpha = 0; - eAttacker thread destroyDamageHud(damageHud); + // Guard: eAttacker is undefined for world/self damage (falls, barrels, etc.) + if(isPlayer(eAttacker) && eAttacker != self) + { + damagehud = eAttacker createDamageHud(); + damageHud setValue(iDamage); + damageHud moveOverTime(1); + damageHud.x -= 300; + damageHud.y += (-1 * randomInt(150)); + damageHud fadeOverTime(1); + damageHud.alpha = 0; + eAttacker thread destroyDamageHud(damageHud); + } self thread updateDamageHud(); } if(level.state != "ingame") diff --git a/maps/mp/gametypes/_gamelogic.gsc b/maps/mp/gametypes/_gamelogic.gsc index 0052f01..fa55e62 100755 --- a/maps/mp/gametypes/_gamelogic.gsc +++ b/maps/mp/gametypes/_gamelogic.gsc @@ -2106,14 +2106,21 @@ endGame( winner, endReasonText, nukeDetonated ) { player closepopupMenu(); player closeInGameMenu(); - //player notify ( "reset_outcome" ); // opens da scoreboard - //player thread maps\mp\gametypes\_playerlogic::spawnIntermission(); } - if(level.players.size > 0) + if(level.players.size > 0 && getDvarInt("vote_enabled", 1)) { level notify("spawnVote"); level waittill("endVote"); } + else + { + // No vote — show the normal scoreboard and leaderboard + foreach ( player in level.players ) + { + player notify ( "reset_outcome" ); + player thread maps\mp\gametypes\_playerlogic::spawnIntermission(); + } + } // End of Round diff --git a/mod/main.gsc b/mod/main.gsc index a8d413f..e24fbf1 100755 --- a/mod/main.gsc +++ b/mod/main.gsc @@ -166,6 +166,9 @@ loadSettings() setDvar("bots_main_chat", 0); SetDvarIfUninitialized("scr_nuke_enabled", 1); + // Map voting: set to 1 to show custom vote screen at end of game, + // 0 to use the normal leaderboard + server map rotation instead. + SetDvarIfUninitialized("vote_enabled", 0); // Cache mode flags as level vars — avoids repeated getDvar() in hot per-player loops. level.isTeamGame = (getDvar("g_gametype") == "gungame_team"); level.isKillConfirmed = (getDvar("gunmode") == "Kill Confirmed"); @@ -182,6 +185,10 @@ deleteSentries() } launchGame() { + // Wait for gametype to initialize _hud_util globals (level.fontHeight, level.uiParent). + // launchGame runs from Init() which fires before the gametype callback. + while(!isDefined(level.fontHeight) || !isDefined(level.uiParent)) + waitFrame(); hud = createServerFontString("hudbig", 1); hud setPoint("CENTER", "CENTER", 0,-80); hud.color = (1,1,0); @@ -253,6 +260,7 @@ firstSpawn() self.firstSpawn = true; self.knifeKills = 0; self.gungameKills = 0; + self.isJugger = false; self.streaks = []; self setClientDvar("cg_drawSplatter", 0); self setClientDvar("cg_drawDamageFlash", 0); @@ -262,7 +270,8 @@ firstSpawn() self setClientDvar("bg_shock_movement", 0); self setClientDvar("bg_shock_lookControl", 0); self setClientDvar("scr_game_allowkillcam", 0); - self.hud_damagefeedback.color = (1,0,0); + if(isDefined(self.hud_damagefeedback)) + self.hud_damagefeedback.color = (1,0,0); self.line = self createRectangle("CENTER", "LEFT", 0,-90,300,5,(1,1,0),"line_horizontal",1); // Subtle crosshair dot — light blue, low alpha self.crosshair = self createRectangle("CENTER", "CENTER", 0, 0, 3, 3, (0.5, 0.85, 1.0), "white", 1); @@ -409,27 +418,47 @@ updateWeapon() self notify("updateWeapon"); self endon("updateWeapon"); + // Don't proceed until the weapon list is fully built + if(!isDefined(level.weaponsLoaded) || !level.weaponsLoaded) + return; if(self.current > (level.gungameList.size - 1)) { self thread tryNuke(); return; } //self iPrintlnBold(level.gungameList[self.current]); + weaponName = level.gungameList[self.current]; + // Guard: skip if the weapon entry is missing, empty, or "none". + if(!isDefined(weaponName) || weaponName == "none" || weaponName == "") + return; variant = randomInt(9); if(getDvar("gunmode") == "Fungame") variant = 0; - if (isSubstr(level.gungameList[self.current], "_akimbo")) - self giveWeapon(level.gungameList[self.current], variant, true); + if (isSubstr(weaponName, "_akimbo")) + self giveWeapon(weaponName, variant, true); else - self giveWeapon(level.gungameList[self.current], variant, false); + self giveWeapon(weaponName, variant, false); + // Verify the weapon was actually loaded — giveWeapon silently fails if + // the weapon asset doesn't exist in the engine. + if(!self hasWeapon(weaponName)) + { + self.current++; + return; + } self giveWeapon("onemanarmy_mp"); - self takeWeapon(self getCurrentWeapon()); - self giveMaxAmmo(level.gungameList[self.current]); - self setWeaponAmmoClip(level.gungameList[self.current], 9999); // force full clip — giveMaxAmmo only fills stock/reserve + self takeWeapon("onemanarmy_mp"); + // Skip ammo operations for weapons with no ammo pool (riotshield, defaultweapon, etc.) + // giveMaxAmmo on these causes "Weapon name none" engine errors. + if(weaponName != "riotshield_mp" && weaponName != "defaultweapon_mp") + { + self giveMaxAmmo(weaponName); + self setWeaponAmmoClip(weaponName, 9999); + } waitFrame(); - self switchtoweaponimmediate(level.gungameList[self.current]); + self switchtoweaponimmediate(weaponName); waitFrame(); - self setWeaponAmmoClip(level.gungameList[self.current], 9999); // re-apply after switch to prevent forced reload + if(weaponName != "riotshield_mp" && weaponName != "defaultweapon_mp") + self setWeaponAmmoClip(weaponName, 9999); if(level.state == "prematch" || level.state == "ingame") self show(); // Always restore the correct speed, clamped to the mode's configured base floor. @@ -485,7 +514,8 @@ refillOnFire() { self waittill("weapon_fired"); weapon = self getCurrentWeapon(); - self giveMaxAmmo(weapon); + if(weapon != "none" && weapon != "") + self giveMaxAmmo(weapon); } } onKilling() { @@ -615,7 +645,7 @@ clearWarning(hud) } createText(font,fontScale,align,relative,x,y,alpha,input,color,gcolor,galpha) { - text = self createFontString(font,fontScale,self); + text = self createFontString(font,fontScale); text setPoint(align,relative,x,y); text.sort = 999; text.alpha = alpha; @@ -675,28 +705,28 @@ scorepopup(amount) // Each unique amount string burned a permanent configstring slot. self.scoretext_amount.label = &"+"; self.scoretext_amount setValue(self.amount); - if(self.multiplier > 1) + // Clamp multitext index to the valid range [2..5] to prevent undefined access + mtIdx = self.multiplier; + if(mtIdx > 5) mtIdx = 5; + if(self.multiplier > 1 && isDefined(self.multitext[mtIdx])) { - self.multitext[self.multiplier].alpha = 1; - self.multitext[self.multiplier] fadeOverTime(0.1); - self.multitext[self.multiplier].color = color; - self.multitext[self.multiplier].glowColor = glowColor; + self.multitext[mtIdx].alpha = 1; + self.multitext[mtIdx] fadeOverTime(0.1); + self.multitext[mtIdx].color = color; + self.multitext[mtIdx].glowColor = glowColor; } if(self.multiplier == 2) { self thread lowerMultitext(self.multiplier); - self.multitext[self.multiplier] setText("Double Kill!"); + self.multitext[mtIdx] setText("Double Kill!"); } else if(self.multiplier == 3) - self.multitext[self.multiplier] setText("Triple Kill!"); + self.multitext[mtIdx] setText("Triple Kill!"); else if(self.multiplier == 4) - self.multitext[self.multiplier] setText("Multi Kill!"); + self.multitext[mtIdx] setText("Multi Kill!"); else if(self.multiplier > 4) { difference = self.multiplier - 4; - // FIX: Use a fixed string instead of randomized color codes. - // The old rainbow text generated a unique configstring for every kill spree - // (random colors = unique string every time = configstring leak). // FIX: Use .label + setValue() to avoid configstring leak per spree level. self.multitext[5].label = &"^1K^2i^3l^4l^5i^6n^7g ^1S^2p^3r^4e^5e ^3x"; self.multitext[5] setValue(difference); @@ -718,8 +748,11 @@ lowerMultitext(multiplier) self waittill_any("start_lowering", "disconnect"); for(i=2;i<6;i++) { - self.multitext[i] fadeOverTime(3); - self.multitext[i].alpha = 0; + if(isDefined(self.multitext[i])) + { + self.multitext[i] fadeOverTime(3); + self.multitext[i].alpha = 0; + } wait .75; } wait 4; @@ -910,13 +943,18 @@ takeInvalidWeapon() continue; if(self isMantling()) continue; + // Don't poll until weapons are fully loaded + if(!isDefined(level.weaponsLoaded) || !level.weaponsLoaded) + continue; weapon = self getCurrentWeapon(); + // Skip if player is in a weapon transition ("none" is normal during switches) + if(weapon == "none" || weapon == "") + continue; if(weapon != level.gungameList[self.current]) { self takeAllWeapons(); self thread updateWeapon(); // Short cooldown — just enough for switchtoweaponimmediate to settle. - // The old 2.5s wait was causing the visible mid-game-join delay. wait 0.5; } } @@ -1084,11 +1122,9 @@ tryCreateMarkerIcons() createRectangle(align, relative, x, y, width, height, color, shader, sort) { hud = newClientHudElem(self); - hud.sTexType = "bar"; + hud.elemType = "icon"; hud.width = width; hud.height = height; - hud.align = align; - hud.relative = relative; hud.xOffset = 0; hud.yOffset = 0; hud.children = []; diff --git a/mod/streaks.gsc b/mod/streaks.gsc index 2275717..35a4c92 100755 --- a/mod/streaks.gsc +++ b/mod/streaks.gsc @@ -22,6 +22,8 @@ loadStreaks() // Pre-cache Jetpack FX at level init (loadfx must NOT be called at runtime). level._effect["jetpack_smoke"] = loadfx("smoke/smoke_trail_white_heli"); level._effect["jetpack_flare"] = loadfx("misc/flares_cobra"); + // Small bullet-impact explosion for the Explosive Bullets streak. + level._effect["bullet_explode"] = loadfx("explosions/grenadeExp_default"); level.streaks3 = []; level.streaks6 = []; @@ -115,7 +117,7 @@ setStreaks() } drawStreaks() { - if(isDefined(self.streakIcons[0])) + if(isDefined(self.streakIcons) && isDefined(self.streakIcons[0])) { for(i=0;i<3;i++) { @@ -321,7 +323,7 @@ Explosive() forward = self getTagOrigin("j_head"); end = self thread vector_scal(anglestoforward(self getPlayerAngles()),1000000); Location = BulletTrace( forward, end, 0, self )[ "position" ]; - playFx(level._effect["claymore_explode"], Location); + playFx(level._effect["bullet_explode"], Location); RadiusDamage(Location, 50, 30, 20, self ); //self RadiusDamage(Location,250,self.explodmg,self.explomindmg,self,"MOD_Explosive","barrel_mp"); wait self.explotime; @@ -355,7 +357,7 @@ Jetpack() { if(self usebuttonpressed() && self.jetpack>0) { - self playsound("cobra_helicopter_dying_loop"); + self playloopsound("cobra_helicopter_dying_loop"); self setstance("crouch"); playfx(level._effect["jetpack_smoke"],self gettagorigin("j_spine4")); playfx(level._effect["jetpack_flare"],self gettagorigin("j_spine4")); @@ -364,6 +366,8 @@ Jetpack() if(self getvelocity()[2]<300) self setvelocity(self getvelocity()+(0,0,60)); } + else + self stoploopsound("cobra_helicopter_dying_loop"); if(self.jetpack < 80 &&!self usebuttonpressed()) self.jetpack++; JETPACKBACK updateBar(self.jetpack/80); @@ -694,6 +698,7 @@ NoReload() if(self AttackButtonPressed()) { current = self getCurrentWeapon(); + if(current == "none" || current == "") { waitFrame(); continue; } clip = self GetWeaponAmmoClip(current); stock = self GetWeaponAmmoStock(current); if(stock > 0) diff --git a/mod/vote.gsc b/mod/vote.gsc index 49ebc3f..4fa41ab 100755 --- a/mod/vote.gsc +++ b/mod/vote.gsc @@ -296,11 +296,9 @@ getDisplayName(map) createRectangle(shader,align, relative, x, y, width, height) { hud = newHudElem(); - hud.sTexType = "bar"; + hud.elemType = "icon"; hud.width = width; hud.height = height; - hud.align = align; - hud.relative = relative; hud.alpha = 1; hud.xOffset = 0; hud.yOffset = 0; @@ -316,11 +314,9 @@ createRectangle(shader,align, relative, x, y, width, height) createPlayerRectangle(shader,align, relative, x, y, width, height) { hud = newClientHudElem(self); - hud.sTexType = "bar"; + hud.elemType = "icon"; hud.width = width; hud.height = height; - hud.align = align; - hud.relative = relative; hud.alpha = 1; hud.xOffset = 0; hud.yOffset = 0; diff --git a/mod/weapons.gsc b/mod/weapons.gsc index 0887ad8..3977163 100755 --- a/mod/weapons.gsc +++ b/mod/weapons.gsc @@ -125,6 +125,7 @@ loadWeapons() } //level.gungameList[1] = setWeapon("rpg_mp"); //level.gungameList[1] = "rpg_mp"; + level.weaponsLoaded = true; } removeIDfromArray(id, weaponList) { @@ -594,6 +595,7 @@ loadFungameList() level.gungameList[level.gungameList.size] = level.fungameWeapons[i]; } } + level.weaponsLoaded = true; // signal that the list is ready for use } addFungameWeapon(weapon)