οΈ 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
-
Create the map folder
-
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.
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
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 - -700; //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 - -800; //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;
// 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");
wait 3;
self IPrintLn("First box by ^1twitch.tv/^2AlexInZombies");
}
}