οΈ Moon β First Box Patch (Plutonium only)
This patch makes the first box hits on Moon always give you the right weapons for early setup.
It also includes a few small tweaks that you would need as a zombies player.
π§© Features
-
Guaranteed Wave Gun, Gersh Device, and (in co-op) Ray Gun from the box up to round 20
-
QEDs added as a third box hit
-
Game timer
-
Round Timer
-
Movement fix β backspeed and strafe speed now match console behavior
-
Juggernog always spawns at No Manβs Land
-
Spawn Cancel fix (my custom implementation)
-
Excavator 11 will breach at round 15
Installation Instructions:
Windows 11: View β Show β File name extensions
Windows 10: View β File name extensions
-
This ensures the file is correctly saved as patch.gsc and not patch.gsc.txt
-
Create the script file
-
Open Notepad (or any text editor).
-
Copy and paste the script code provided below.
-
Save the file as: patch.gsc
-
Press Windows + R
-
Navigate to: %localappdata%/Plutonium/storage/t5/raw/scripts/sp
-
Inside the sp folder, create a new folder named: zombie_moon
-
Place your patch.gsc file inside this folder.
Restore the original common file
Replace your common_zombie_patch.ff with the original game file before launching the map.
You will get an error screen if youβre using a modified common file unless it was specifically made for moon.
You can do so by either replacing it from your game directory (Steam or Bgamer for example) or by putting it inside Plutonium's Zone folder, which will overwrite your original file without replacing it in the original game files, the file in the zone folder will load instead of the one inside your game
If you donβt have the original file, download and unzip it here:
Download patch
π§ͺ Update Log
v1.1 β 10/20/2025
-
Added QEDs as third box hit
-
Added Ray Gun (co-op only) as fourth hit
-
Added game timer
-
Juggernog now always spawns at NML
v1.2 - 10/22/2025
v1.3 - 10/22/2025
V1.4 - 10/27/2025
V1.5 - 12/26/2025
- Implemented a fix to prevent other excavators from powering up on round 15 other than tunnel 11
Notes
Works only on Plutonium (uses replaceFunc)
Tested and confirmed working 100%
Report any gameplay issues if you find them
#include maps\_utility;
#include common_scripts\utility;
#include maps\_zombiemode_utility;
main()
{
if ( GetDvar( #"zombiemode" ) == "1" )
{
level thread onPlayerConnect();
replacefunc(maps\_zombiemode_weapons::treasure_chest_ChooseWeightedRandomWeapon, ::custom_treasure_chest_ChooseWeightedRandomWeapon);
replacefunc(maps\zombie_moon_wasteland::perk_machine_arrival_update, ::custom_perk_machine_arrival_update);
replacefunc(maps\_zombiemode::round_think, ::custom_round_think);
replacefunc(maps\_zombiemode_ai_faller::do_zombie_fall, ::custom_do_zombie_fall);
replacefunc(maps\_zombiemode_weapons::init_starting_chest_location, ::custom_init_starting_chest_location);
level.moon_visits = 0;
}
}
any_player_has_weapon(weap)
{
players = get_players();
has_weap = false;
for(i = 0; players.size > i; i++)
{
if(players[i] maps\_zombiemode_weapons::has_weapon_or_upgrade(weap))
has_weap = true;
}
return has_weap;
}
game_timer()
{
hud = create_simple_hud( self );
hud.foreground = true;
hud.sort = 1;
hud.hidewheninmenu = true;
hud.alignX = "left";
hud.alignY = "top";
hud.horzAlign = "user_left";
hud.vertAlign = "user_top";
hud.alpha = 1;
//=======================CUSTOM SECTION==============
hud.x = hud.x - -1100; //This is for the position on X axis, decrease this number to move it further left, and increase it to go right.
hud.y = hud.y + 35; //This is for the position on Y axis, decrease this number to move it down, and increase it to go up.
hud.fontscale = 1.2; //This is for the font size, increase it by whatever you want and see what you like
hud.color = (255, 255, 255); //This is for the color using RGB, use a website for that
//=========================================
flag_wait("all_players_spawned");
hud setTimerUp(1);
}
round_timer()
{
timerHud = create_simple_hud( self );
timerHud.foreground = true;
timerHud.sort = 1;
timerHud.hidewheninmenu = true;
timerHud.alignX = "left";
timerHud.alignY = "top";
timerHud.horzAlign = "user_left";
timerHud.vertAlign = "user_top";
//=======================CUSTOM SECTION==============
timerHud.x = timerHud.x - -1100; //This is for the position on X axis, decrease this number to move it further left, and increase it to go right.
timerHud.y = timerHud.y + 45; //This is for the position on Y axis, decrease this number to move it down, and increase it to go up.
timerHud.fontscale = 1.2; //This is for the font size, increase it by whatever you want and see what you like
timerHud.color = (255, 0, 0); //This is for the color using RGB, use a website for that
//=========================================
timerHud.alpha = 1;
flag_wait("all_players_spawned");
for (;;){
start_time = GetTime() / 1000;
timerHud setTimerUp(0);
level waittill("end_of_round");
end_time = GetTime() / 1000;
time = end_time - start_time;
set_time_frozen(timerHud, time);
}
}
set_time_frozen(hud, time)
{
level endon("start_of_round");
time = time - 0.1;
while(1)
{
hud settimer(time);
wait(0.5);
}
}
patch_moon()
{
//WaveGun
if(!self maps\_zombiemode_weapons::has_weapon_or_upgrade("microwavegundw_zm") && !any_player_has_weapon("microwavegundw_zm"))
return "microwavegundw_zm";
//Gersh
if(!self maps\_zombiemode_weapons::has_weapon_or_upgrade("zombie_black_hole_bomb"))
return "zombie_black_hole_bomb";
if(!self maps\_zombiemode_weapons::has_weapon_or_upgrade("zombie_quantum_bomb"))
return "zombie_quantum_bomb";
if (get_players().size > 1){
//RayGun for coop
if(!self maps\_zombiemode_weapons::has_weapon_or_upgrade("ray_gun_zm"))
return "ray_gun_zm";
}
}
custom_treasure_chest_ChooseWeightedRandomWeapon( player )
{
//First Box by Crybaby / AlexInVr
if(level.round_number < 20)
{
//Moon
if (level.script == "zombie_moon")
return player patch_moon();
}
keys = GetArrayKeys( level.zombie_weapons );
toggle_weapons_in_use = 0;
// Filter out any weapons the player already has
filtered = [];
for( i = 0; i < keys.size; i++ )
{
if( !maps\_zombiemode_weapons::get_is_in_box( keys[i] ) )
{
continue;
}
if( isdefined( player ) && is_player_valid(player) && player maps\_zombiemode_weapons::has_weapon_or_upgrade( keys[i] ) )
{
if ( maps\_zombiemode_weapons::is_weapon_toggle( keys[i] ) )
{
toggle_weapons_in_use++;
}
continue;
}
if( !IsDefined( keys[i] ) )
{
continue;
}
num_entries = [[ level.weapon_weighting_funcs[keys[i]] ]]();
for( j = 0; j < num_entries; j++ )
{
filtered[filtered.size] = keys[i];
}
}
// Filter out the limited weapons
if( IsDefined( level.limited_weapons ) )
{
keys2 = GetArrayKeys( level.limited_weapons );
players = get_players();
pap_triggers = GetEntArray("zombie_vending_upgrade", "targetname");
for( q = 0; q < keys2.size; q++ )
{
count = 0;
for( i = 0; i < players.size; i++ )
{
if( players[i] maps\_zombiemode_weapons::has_weapon_or_upgrade( keys2[q] ) )
{
count++;
}
}
// Check the pack a punch machines to see if they are holding what we're looking for
for ( k=0; k<pap_triggers.size; k++ )
{
if ( IsDefined(pap_triggers[k].current_weapon) && pap_triggers[k].current_weapon == keys2[q] )
{
count++;
}
}
// Check the other boxes so we don't offer something currently being offered during a fire sale
for ( chestIndex = 0; chestIndex < level.chests.size; chestIndex++ )
{
if ( IsDefined( level.chests[chestIndex].chest_origin.weapon_string ) && level.chests[chestIndex].chest_origin.weapon_string == keys2[q] )
{
count++;
}
}
if ( isdefined( level.random_weapon_powerups ) )
{
for ( powerupIndex = 0; powerupIndex < level.random_weapon_powerups.size; powerupIndex++ )
{
if ( IsDefined( level.random_weapon_powerups[powerupIndex] ) && level.random_weapon_powerups[powerupIndex].base_weapon == keys2[q] )
{
count++;
}
}
}
if ( maps\_zombiemode_weapons::is_weapon_toggle( keys2[q] ) )
{
toggle_weapons_in_use += count;
}
if( count >= level.limited_weapons[keys2[q]] )
{
filtered = array_remove( filtered, keys2[q] );
}
}
}
// finally, filter based on toggle mechanic
if ( IsDefined( level.zombie_weapon_toggles ) )
{
keys2 = GetArrayKeys( level.zombie_weapon_toggles );
for( q = 0; q < keys2.size; q++ )
{
if ( level.zombie_weapon_toggles[keys2[q]].active )
{
if ( toggle_weapons_in_use < level.zombie_weapon_toggle_max_active_count )
{
continue;
}
}
filtered = array_remove( filtered, keys2[q] );
}
}
// try to "force" a little more "real randomness" by randomizing the array before randomly picking a slot in it
filtered = array_randomize( filtered );
return filtered[RandomInt( filtered.size )];
}
custom_perk_machine_arrival_update()
{
top_height = 1200; // 700
fall_time = 4;
num_model_swaps = 20;
perk_index = randomintrange( 0, 2 );
// Flash an effect to the perk machines destination
ent = level.speed_cola_ents[0];
level thread maps\zombie_moon_wasteland::perk_arrive_fx( ent.origin );
//while( 1 )
{
// Move the perk machines high in the sky
maps\zombie_moon_wasteland::move_perk( top_height, 0.01, 0.001 );
wait( 0.3 );
maps\zombie_moon_wasteland::perk_machines_hide( 0, 0, true );
wait( 1 );
// Start the machines falling
maps\zombie_moon_wasteland::move_perk( top_height*-1, fall_time, 1.5 );
// Swap visible Perk as we fall
wait_step = fall_time / num_model_swaps;
for( i=0; i<num_model_swaps; i++ )
{
maps\zombie_moon_wasteland::perk_machine_show_selected( perk_index, true );
wait( wait_step );
perk_index++;
if( perk_index > 1 )
{
perk_index = 0;
}
}
// Make sure we don't get a perk machine duplicate next time we visit
while( perk_index == level.last_perk_index )
{
perk_index = randomintrange( 0, 2 );
}
level.last_perk_index = perk_index;
custom_perk_index = 1; //Default to Jug (0: Cola, 1: Jug)
if (isDefined(level.ever_been_on_the_moon) && level.ever_been_on_the_moon)
{
// If we've been on the moon, we already took jug (You should have.... not my problem otherwise)
// Switch the perk to SpeedCola (Index 0)
level.moon_visits++;
if (level.moon_visits >= 2)
{
custom_perk_index = perk_index;
}
else
{
custom_perk_index = 0;
}
}
maps\zombie_moon_wasteland::perk_machine_show_selected( custom_perk_index, false );
}
}
custom_round_think()
{
for( ;; )
{
//////////////////////////////////////////
//designed by prod DT#36173
maxreward = 50 * level.round_number;
if ( maxreward > 500 )
maxreward = 500;
level.zombie_vars["rebuild_barrier_cap_per_round"] = maxreward;
//////////////////////////////////////////
level.pro_tips_start_time = GetTime();
level.zombie_last_run_time = GetTime(); // Resets the last time a zombie ran
level thread maps\_zombiemode_audio::change_zombie_music( "round_start" );
maps\_zombiemode::chalk_one_up();
maps\_zombiemode_powerups::powerup_round_start();
players = get_players();
array_thread( players, maps\_zombiemode_blockers::rebuild_barrier_reward_reset );
level thread maps\_zombiemode::award_grenades_for_survivors();
bbPrint( "zombie_rounds: round %d player_count %d", level.round_number, players.size );
level.round_start_time = GetTime();
level thread [[level.round_spawn_func]]();
level notify( "start_of_round" );
[[level.round_wait_func]]();
level.first_round = false;
level notify( "end_of_round" );
level thread maps\_zombiemode_audio::change_zombie_music( "round_end" );
UploadStats();
if ( 1 != players.size )
{
level thread maps\_zombiemode::spectators_respawn();
}
level maps\_zombiemode::chalk_round_over();
// here's the difficulty increase over time area
timer = level.zombie_vars["zombie_spawn_delay"];
if ( timer > 0.08 )
{
level.zombie_vars["zombie_spawn_delay"] = timer * 0.95;
}
else if ( timer < 0.08 )
{
level.zombie_vars["zombie_spawn_delay"] = 0.08;
}
// Increase the zombie move speed
level.zombie_move_speed = level.round_number * level.zombie_vars["zombie_move_speed_multiplier"];
level.round_number++;
level notify( "between_round_over" );
}
}
custom_do_zombie_fall()
{
self endon("death");
self thread maps\_zombiemode_ai_faller::setup_deathfunc();
// don't drop powerups until we are on the ground
self.no_powerups = true;
self.in_the_ceiling = true;
self.anchor = spawn("script_origin", self.origin);
self.anchor.angles = self.angles;
self linkto(self.anchor);
if ( !IsDefined( self.zone_name ) )
{
self.zone_name = self get_current_zone();
}
spots = maps\_zombiemode_ai_faller::get_available_fall_locations();
if( spots.size < 1 )
{
self unlink();
self.anchor delete();
//IPrintLnBold("deleting zombie faller - no available fall locations");
//can't delete if we're in the middle of spawning, so wait a frame
self Hide();//hide so we're not visible for one frame while waiting to delete
self delayThread( 0.1, maps\_zombiemode_ai_faller::zombie_faller_delete );
return;
}
else if ( GetDvarInt(#"zombie_fall_test") )
{
// use the spot closest to the first player always
player = GetPlayers()[0];
spot = undefined;
bestDist = 0.0;
for ( i = 0; i < spots.size; i++ )
{
checkDist = DistanceSquared(spots[i].origin, player.origin);
if ( !IsDefined(spot) || checkDist < bestDist )
{
spot = spots[i];
bestDist = checkDist;
}
}
}
else
{
spot = random(spots);
}
self.zombie_faller_location = spot;
//NOTE: multiple zombie fallers could be waiting in the same spot now, need to have spawners detect this
// and not use the spot again until the previous zombie has died or dropped down
self.zombie_faller_location.is_enabled = false;
self thread maps\_zombiemode_ai_faller::zombie_faller_death_wait();
self.zombie_faller_location maps\_zombiemode_ai_faller::parse_script_parameters();
// Start a monitor that will ensure the spot gets re-enabled if this zombie dies early or gets stuck.
// This is defensive: it will re-enable on death or after a timeout.
self thread zombie_faller_spot_monitor(self, spot);
if( !isDefined( spot.angles ) )
{
spot.angles = (0, 0, 0);
}
//Patch begin
anim_org = spot.origin;
anim_ang = spot.angles;
self Hide();
self.anchor moveto(anim_org, .05);
self.anchor waittill("movedone");
// face goal
target_org = maps\_zombiemode_spawner::get_desired_origin();
if (IsDefined(target_org))
{
anim_ang = VectorToAngles(target_org - self.origin);
current_pos = self.anchor.origin;
// Instantly rotate the anchor instead of waiting for RotateTo
self.anchor ForceTeleport(current_pos, (0, anim_ang[1], 0), false, false);
}
self unlink();
self.anchor delete();
self thread maps\_zombiemode_ai_faller::hide_pop(); // hack to hide the pop when the zombie gets to the start position before the anim starts
level thread maps\_zombiemode_ai_faller::zombie_fall_death(self, spot);
spot thread maps\_zombiemode_ai_faller::zombie_fall_fx(self);
self thread maps\_zombiemode_ai_faller::zombie_faller_death_wait();
//need to thread off the rest because we're apparently still in the middle of our init!
self thread maps\_zombiemode_ai_faller::zombie_faller_do_fall();
}
zombie_faller_spot_monitor(zombie, spot)
{
// sanity
if ( !IsDefined(spot) )
return;
// If the zombie dies cleanly, we re-enable the spot immediately.
zombie waittill("death");
// At this point the zombie is dead (or the entity has been freed).
if ( IsDefined(spot) )
{
spot.is_enabled = true;
}
// Safety: also ensure we don't leave the spot disabled in pathological cases.
wait(0.25);
if ( IsDefined(spot) )
spot.is_enabled = true;
}
custom_init_starting_chest_location()
{
level.chest_index = 0;
start_chest_found = false;
for( i = 0; i < level.chests.size; i++ )
{
if(level.script == "zombie_theater")
{
if(IsSubStr(level.chests[i].script_noteworthy, "crematorium_chest" ))
{
level.chest_index = i;
level.chests[level.chest_index] maps\_zombiemode_weapons::hide_rubble();
level.chests[level.chest_index].hidden = false;
}
else
{
level.chests[i] maps\_zombiemode_weapons::hide_chest();
}
}
else if(level.script == "zombie_pentagon")
{
if(level.chests[i].script_noteworthy == "start_chest")
//if(IsSubStr(level.chests[i].script_noteworthy, "start_chest" ))
{
level.chest_index = i;
level.chests[level.chest_index] maps\_zombiemode_weapons::hide_rubble();
level.chests[level.chest_index].hidden = false;
}
else
{
level.chests[i] maps\_zombiemode_weapons::hide_chest();
}
}
else if(level.script == "zombie_coast")
{
if(IsSubStr(level.chests[i].script_noteworthy, "start_chest" )) //residence_chest = flopper
{
level.chest_index = i;
level.chests[level.chest_index] maps\_zombiemode_weapons::hide_rubble();
level.chests[level.chest_index].hidden = false;
}
else
{
level.chests[i] maps\_zombiemode_weapons::hide_chest();
}
}
else if(level.script == "zombie_temple")
{
if(IsSubStr(level.chests[i].script_noteworthy, "bridge_chest" ))
{
level.chest_index = i;
level.chests[level.chest_index] maps\_zombiemode_weapons::hide_rubble();
level.chests[level.chest_index].hidden = false;
}
else
{
level.chests[i] maps\_zombiemode_weapons::hide_chest();
}
}
else if(level.script == "zombie_moon")
{
if(IsSubStr(level.chests[i].script_noteworthy, "forest_chest" ))
{
level.chest_index = i;
level.chests[level.chest_index] maps\_zombiemode_weapons::hide_rubble();
level.chests[level.chest_index].hidden = false;
}
else
{
level.chests[i] maps\_zombiemode_weapons::hide_chest();
}
}
else if( isdefined( level.random_pandora_box_start ) && level.random_pandora_box_start == true )
{
if ( start_chest_found || (IsDefined( level.chests[i].start_exclude ) && level.chests[i].start_exclude == 1) )
{
level.chests[i] maps\_zombiemode_weapons::hide_chest();
}
else
{
level.chest_index = i;
level.chests[level.chest_index] maps\_zombiemode_weapons::hide_rubble();
level.chests[level.chest_index].hidden = false;
start_chest_found = true;
}
}
else
{
// Semi-random implementation (not completely random). The list is randomized
// prior to getting here.
// Pick from any box marked as the "start_chest"
if ( start_chest_found || !IsDefined(level.chests[i].script_noteworthy ) || ( !IsSubStr( level.chests[i].script_noteworthy, "start_chest" ) ) )
{
level.chests[i] maps\_zombiemode_weapons::hide_chest();
}
else
{
level.chest_index = i;
level.chests[level.chest_index] maps\_zombiemode_weapons::hide_rubble();
level.chests[level.chest_index].hidden = false;
start_chest_found = true;
}
}
}
// Show the beacon
if( !isDefined( level.pandora_show_func ) )
{
level.pandora_show_func = maps\_zombiemode_weapons::default_pandora_show_func;
}
level.chests[level.chest_index] thread [[ level.pandora_show_func ]]();
}
activate_tunnel_11()
{
level endon("game_ended");
for (;;)
{
wait 1;
// Wait until round 15+
if (level.round_number < 15)
continue;
if (level flag_exists("start_teleporter_digger") && flag("start_teleporter_digger")){
flag_clear("start_teleporter_digger");
}
if (level flag_exists("start_biodome_digger") && flag("start_biodome_digger")){
flag_clear("start_biodome_digger");
}
// Only run once
if (level flag_exists("start_hangar_digger") && !flag("start_hangar_digger"))
{
maps\zombie_moon_digger::digger_activate("hangar");
}
break;
}
}
onPlayerConnect()
{
for(;;)
{
level waittill ("connecting", player);
player thread onPlayerSpawned();
}
}
onPlayerSpawned()
{
for(;;)
{
self waittill("spawned_player");
self thread game_timer();
self thread round_timer();
level thread activate_tunnel_11();
self SetClientDvars(
"player_backSpeedScale", "1",
"player_strafeSpeedScale", "1",
"cg_fov", "100",
"sv_cheats", "1");
wait 3;
self IPrintLn("First box by ^1twitch.tv/^2AlexInZombies");
}
}