I created this mod to play cooperatively with a friend. It includes several cool features that make the experience more intense, in my opinion.
Zenkai: Every time you die or very near to die, you become stronger, faster, jump higher, and gain more life. Speed and jump have limits, but health and damage don't. You can farm as many Zenkais has you wan't but make the game very boring. Use them wisely.
True Quick Revive Coop: As in the solo game, if you have a quick revive, you revive almost automatically without another player having to do it. (It doesn't always work, I don't know why.)
More Max Ammo: If your ammunition level drops, the probability of obtaining a random max ammo drop increases exponentially. If you have no ammunition in any of your weapons and you kill a zombie, you will obtain max ammo with a 100% probability.
No Limit Perk: Explains itself.
It's very fun to play in town. It's a little buggy, maybe I'll keep improving it. If anyone wants to do it, go ahead.
Install: Just name a file however you want with the .gsc extension, copy this code and drop it in: \AppData\Local\Plutonium\storage\t6\scripts\zm
#include maps\mp\_utility;
#include maps\mp\zombies\_zm_utility;
#include maps\mp\zombies\_zm_powerups;
#include maps\mp\zombies\_zm_score;
#include common_scripts\utility;
#include maps\_laststand;
init()
{
level thread onPlayerConnect();
level thread chatMonitor();
level thread zombieMaxAmmoMonitor();
level.original_damagecallback = level.callbackactordamage;
level.callbackactordamage = ::actor_damage_override_wrapper;
//register_player_damage_callback( ::damage_callback );
level.perk_purchase_limit = 9;
}
onPlayerConnect()
{
while(1)
{
level waittill("connected", player);
player thread onPlayerSpawned();
}
}
onPlayerSpawned()
{
self endon("disconnect");
while(1)
{
self waittill("spawned_player");
self notify("stop_mod");
if(!isDefined(self.modLoaded))
{
self.modLoaded = true;
self iPrintLnBold("^2MOD CARGADO");
self iPrintLn("^3Chat: ^2!maxammo ^7| ^2!nuke ^7| ^2!zenkai ^7| ^2!health");
}
// Inicializar estados únicos (PERSISTENTES a través de muertes/revives)
self.custom_speed = (isDefined(self.pers["custom_speed"])) ? self.pers["custom_speed"] : 1.0;
self.base_health = 100;
self.zenkai_bonus = (isDefined(self.pers["zenkai_bonus"])) ? self.pers["zenkai_bonus"] : 0;
self.juggernog_bonus = 0;
self.jump_boost = (isDefined(self.pers["jump_boost"])) ? self.pers["jump_boost"] : 0;
self.already_jumped = false;
self.damage_multiplier = (isDefined(self.pers["damage_multiplier"])) ? self.pers["damage_multiplier"] : 1;
self.has_quickRevive = false;
// Stamina infinita para mejor jugabilidad
self SetPerk("specialty_unlimitedsprint");
// Set inicial de vida y velocidad (IMPORTANTE post-spawn)
self.maxhealth = self.base_health + self.juggernog_bonus + self.zenkai_bonus;
self.health = self.maxhealth;
// Damos dinero infinito
//self.score = 999999;
//self.pers["score"] = 999999;
self.score = 1500;
self.pers["score"] = 1500;
// Hilos por jugador
self thread mainPlayerThread();
self thread reviveMonitor();
self thread juggernogMonitor();
self thread quickReviveMonitor();
self thread watchQuickReviveCoop();
//self thread showHUDInfo(); //debug
}
}
mainPlayerThread()
{
self endon("disconnect");
self endon("stop_mod");
self thread zenkaiThread();
self thread jumpingThread();
}
// =====================================================
// Manejo de daño
// =====================================================
actor_damage_override_wrapper( inflictor, attacker, damage, flags, meansofdeath, weapon, vpoint, vdir, shitloc, psoffsettime, boneindex )
{
damage_override = self actor_damage_override_override( inflictor, attacker, damage, flags, meansofdeath, weapon, vpoint, vdir, shitloc, psoffsettime, boneindex );
self finishactordamage( inflictor, attacker, damage_override, flags, meansofdeath, weapon, vpoint, vdir, shitloc, psoffsettime, boneindex );
}
actor_damage_override_override( inflictor, attacker, damage, flags, meansofdeath, weapon, vpoint, vdir, shitloc, psoffsettime, boneindex )
{
// Si la víctima ES un jugador, usa el comportamiento normal (no queremos matar al jugador con instakill)
if ( isPlayer(self) )
{
return [[level.original_damagecallback]]( inflictor, attacker, damage, flags, meansofdeath, weapon, vpoint, vdir, shitloc, psoffsettime, boneindex );
}
// Si el atacante NO es un jugador, usa el daño normal (ej: trampas, fuego, otros zombies)
if ( !isDefined(attacker) || !isPlayer(attacker) )
{
return [[level.original_damagecallback]]( inflictor, attacker, damage, flags, meansofdeath, weapon, vpoint, vdir, shitloc, psoffsettime, boneindex );
}
// A partir de aquí: víctima = zombie/enemigo, atacante = jugador humano
// Detectar si la víctima es un zombie (opcional, pero más seguro)
// En Zombies estándar, los zombies tienen "is_zombie" definido o archetype == "zombie"
if ( !isDefined(self.is_zombie) && self.archetype != "zombie" )
{
// Si no es un zombie (ej: perro, avogadro, objeto), usar daño normal
return [[level.original_damagecallback]]( inflictor, attacker, damage, flags, meansofdeath, weapon, vpoint, vdir, shitloc, psoffsettime, boneindex );
}
// Inicializar finaldamage
finaldamage = damage;
// Multiplicamos por el multiplicador de daño del jugadFor
if (isDefined(attacker.damage_multiplier))
{
finaldamage = int(damage * attacker.damage_multiplier);
}
// Notificacion
attacker iPrintLnBold("^3Daño infligido: ^7" + int(finaldamage));
// No se porque pero funciona
return finaldamage;
}
// =====================================================
// Monitor de revivir
// =====================================================
reviveMonitor()
{
while(1)
{
// Pausa hasta que un jugador reviva
self waittill("player_revived");
// Asignamos vida maxima
self.maxhealth = self.base_health + self.juggernog_bonus + self.zenkai_bonus;
}
}
// =====================================================
// Juggernog monitor, monitorea en tiempo real si el jugador tiene Juggernog
// =====================================================
juggernogMonitor()
{
self endon("disconnect"); // Termina si el jugador se desconecta
self endon("stop_mod"); // Termina si el mod se detiene
// Bucle infinito que verifica continuamente si el jugador tiene el juggernaut
while(1)
{
// Si tengo activado le juggernog activo el bonus
if (self HasPerk("specialty_armorvest"))
{
// Bonus
self.juggernog_bonus = 150;
// Asignamos vida maxima
self.maxhealth = self.base_health + self.juggernog_bonus + self.zenkai_bonus;
}
else
{
// Si no tenemos el juggernog, desactivamos el bonus
self.juggernog_bonus = 0;
}
// Espera brevemente antes de la próxima comprobación (100 ms)
wait 0.1;
}
}
// =====================================================
// Auto-Renanimacion (Quick Revive Solo en Coop)
// =====================================================
watchQuickReviveCoop()
{
self endon("disconnect"); // Termina si el jugador se desconecta
self endon("stop_mod"); // Termina si el mod se detiene
// SOLO FUNCIONA EN COOP (más de 1 jugador)
if (level.players.size <= 1) return;
while(1)
{
// Se activa cuando YO me tumbo
self waittill("player_downed");
// Verificar si tengo Quick Revive activo
if (self.has_quickRevive)
{
// Pequeño delay para evitar conflictos
wait 1;
// Reanimarme a mi mismo
maps\mp\zombies\_zm_laststand::auto_revive(self);
// Ya no tenemos quick revive
self.has_quickRevive = false;
}
}
}
// Función que monitorea en tiempo real si el jugador tiene Quick Revive
quickReviveMonitor()
{
self endon("disconnect"); // Termina si el jugador se desconecta
self endon("stop_mod"); // Termina si el mod se detiene
while(1)
{
// Si tengo Quick Revive activo la variable
if (self HasPerk("specialty_quickrevive"))
{
self.has_quickRevive = true;
}
else
{
// Si no, no
self.has_quickRevive = false;
}
// Espera brevemente antes de la próxima comprobación (100 ms)
wait 0.1;
}
}
// =====================================================
// Hilo de Zenkai
// =====================================================
zenkaiThread()
{
self endon("disconnect"); // Termina si el jugador se desconecta
self endon("stop_mod"); // Termina si el mod se detiene
// Estado inicial: Zenkai no activado.
self.zenkai_activated = false;
while(1)
{
// Zenkai se activa no esta activado y la salud es menor o igual a 20
if(!self.zenkai_activated && self.health <= 20)
{
// Marca Zenkai como activado
self.zenkai_activated = true;
// Aumenta el bono de salud por Zenkai
self.zenkai_bonus += 20;
self.pers["zenkai_bonus"] = self.zenkai_bonus;
// Incrementa la velocidad de movimiento
self.custom_speed += 0.1;
if(self.custom_speed > 1.7) self.custom_speed = 1.7;
self.pers["custom_speed"] = self.custom_speed;
// Aplica la nueva velocidad
self setMoveSpeedScale(self.custom_speed);
// Aumenta el impulso de salto
self.jump_boost += 4;
if(self.jump_boost > 24) self.jump_boost = 24;
self.pers["jump_boost"] = self.jump_boost;
// Aumenta el multiplicador de daño
self.damage_multiplier += 0.5;
self.pers["damage_multiplier"] = self.damage_multiplier;
// Asignamos vida maxima
self.maxhealth = self.base_health + self.juggernog_bonus + self.zenkai_bonus;
// Notificar
self iPrintLnBold("^4Zenkai Boost! ^2Salud maxima actualizada: ^7" + self.maxhealth);
}
// Si casi está al máximo de salud
if (self.health >= self.maxhealth - 5)
{
// Permite reactivar Zenkai en el futuro
self.zenkai_activated = false;
}
// Espera breve antes de volver a comprobar.
wait 0.1;
}
}
// =====================================================
// Thread de saltar
// =====================================================
jumpingThread()
{
self endon("disconnect"); // Termina si el jugador se desconecta
self endon("stop_mod"); // Termina si el mod se detiene
while(1)
{
// Espera el salto
if (!self isOnGround() && !self.already_jumped) {
for(i = 0; i < self.jump_boost; i++)
{
newPos = self getOrigin() + (0, 0, 1); // Sube X unidades en Z (altura)
self setOrigin(newPos);
}
self.already_jumped = true;
}
if (self isOnGround())
{
self.already_jumped = false;
}
wait 0.01;
}
}
// =====================================================
// HUD
// =====================================================
showHUDInfo()
{
self endon("disconnect"); // Termina si el jugador se desconecta
self endon("stop_mod"); // Termina si el mod se detiene
// Limpieza
self destroyStatHUD(self.hud_health_label, self.hud_health_value);
self destroyStatHUD(self.hud_jump_label, self.hud_jump_value);
self destroyStatHUD(self.hud_speed_label, self.hud_speed_value);
self destroyStatHUD(self.hud_damage_label, self.hud_damage_value);
// Labels: right-aligned, x=-12 (derecha), terminan EN ": " (ESPACIO después de :)
self.hud_health_label = createStatLabelHUD("right", "top", -12, 20, 1.1, "Salud: ");
self.hud_jump_label = createStatLabelHUD("right", "top", -12, 36, 1.0, "Salto: ");
self.hud_speed_label = createStatLabelHUD("right", "top", -12, 52, 1.0, "Velocidad: ");
self.hud_damage_label = createStatLabelHUD("right", "top", -12, 68, 1.0, "Daño: ");
// Values: left-aligned, x MISMO QUE LABEL (-12) → ¡empieza JUSTO donde termina el label (después del espacio)!
self.hud_health_value = createStatLeftTextHUD("right", "top", -12, 20, 1.1);
self.hud_jump_value = createStatLeftTextHUD("right", "top", -12, 36, 1.0);
self.hud_speed_value = createStatLeftTextHUD("right", "top", -12, 52, 1.0);
self.hud_damage_value = createStatLeftTextHUD("right", "top", -12, 68, 1.0);
self thread cleanupHUDOnDisconnect();
while(1)
{
// Salud
if(isDefined(self.hud_health_value))
{
self.hud_health_value setText("" + self.health);
color = (self.health <= 20) ? (1, 0.2, 0.2) : (0, 1, 0.4);
self.hud_health_value.color = color;
self.hud_health_label.color = color;
}
// Salto
jumpVal = isDefined(self.jump_boost) ? self.jump_boost : 0;
if(isDefined(self.hud_jump_value))
{
self.hud_jump_value setText("" + jumpVal);
color = (jumpVal > 0) ? (0.3, 0.7, 1) : (0.7, 0.7, 0.7);
self.hud_jump_value.color = color;
self.hud_jump_label.color = color;
}
// Velocidad (formateado: 1.0 → "1.0")
speedVal = isDefined(self.custom_speed) ? self.custom_speed : 1.0;
if(isDefined(self.hud_speed_value))
{
self.hud_speed_value setText("" + speedVal);
color = (speedVal > 1.0) ? (1, 0.8, 0.2) : (0.7, 0.7, 0.7);
self.hud_speed_value.color = color;
self.hud_speed_label.color = color;
}
// Daño
dmgVal = isDefined(self.damage_multiplier) ? self.damage_multiplier : 1;
if(isDefined(self.hud_damage_value))
{
self.hud_damage_value setText("" + dmgVal);
color = (dmgVal > 1) ? (1, 0.4, 0.8) : (0.7, 0.7, 0.7);
self.hud_damage_value.color = color;
self.hud_damage_label.color = color;
}
wait 0.05; // Más fluido
}
}
// NUEVO: Helper para VALUES como TEXT (left-aligned)
createStatLeftTextHUD(hAlign, vAlign, x, y, scale)
{
hud = NewHudElem();
hud.horzAlign = hAlign; // "right" (posición desde derecha)
hud.vertAlign = vAlign;
hud.alignX = "left"; // ← ¡IZQUIERDA! (texto crece a la derecha)
hud.alignY = vAlign;
hud.x = x;
hud.y = y;
hud.font = "default";
hud.fontscale = scale;
hud.alpha = 1;
hud.sort = 10;
return hud;
}
// Helper para label fijo (setText una vez)
createStatLabelHUD(hAlign, vAlign, x, y, scale, text)
{
hud = NewHudElem();
hud.horzAlign = hAlign;
hud.vertAlign = vAlign;
hud.alignX = hAlign;
hud.alignY = vAlign;
hud.x = x;
hud.y = y;
hud.font = "default";
hud.fontscale = scale;
hud.alpha = 1;
hud.sort = 10;
hud setText(text); // Fijo, un solo configstring
return hud;
}
// Destruir pares de HUD (label + value)
destroyStatHUD(label, value)
{
if(isDefined(label)) label destroy();
if(isDefined(value)) value destroy();
}
// Limpieza en disconnect (agrega en onPlayerSpawned(): self thread cleanupHUDOnDisconnect();)
cleanupHUDOnDisconnect()
{
self waittill("disconnect");
self destroyStatHUD(self.hud_health_label, self.hud_health_value);
self destroyStatHUD(self.hud_jump_label, self.hud_jump_value);
self destroyStatHUD(self.hud_speed_label, self.hud_speed_value);
self destroyStatHUD(self.hud_damage_label, self.hud_damage_value);
}
// =====================================================
// Monitor de chat
// =====================================================
chatMonitor()
{
level endon("game_ended");
while(1)
{
level waittill("say", msg, player);
cmd = tolower(msg);
if (getsubstr(cmd, 0, 1) == "!")
{
cmd = getsubstr(cmd, 1); // remove "!"
if(cmd == "maxammo")
{
player thread giveMaxAmmo(); // SOLO PARA ESE JUGADOR
player iPrintLnBold("^2¡Max Ammo!");
}
else if(cmd == "nuke")
{
// Nuke es global (como en el juego original)
level thread spawnNuke();
iPrintLnBold("^2Nuke activada por ^1" + player.name);
}
else if(cmd == "zenkai")
{
player.health = 20;
}
else if(cmd == "health")
{
// Notificar
player iPrintLnBold("^2Salud maxima: ^7" + player.maxhealth);
}
}
wait 0.1;
}
}
// =====================================================
// Max Ammo directo (sin power up)
// =====================================================
giveMaxAmmo()
{
self endon("disconnect");
weapons = self GetWeaponsList();
foreach(weapon in weapons)
{
self SetWeaponAmmoClip(weapon, WeaponClipSize(weapon));
self SetWeaponAmmoStock(weapon, WeaponMaxAmmo(weapon));
}
}
// =====================================================
// Spawnear nuke
// =====================================================
spawnNuke()
{
players = get_players();
if(players.size)
{
origin = players[randomInt(players.size)].origin;
level specific_powerup_drop("nuke", origin);
}
}
// =====================================================
// Monitero de drops de Max Ammo
// =====================================================
calculate_total_ammo(player)
{
weapons = player GetWeaponsList();
total = 0;
foreach(weapon in weapons)
{
total += player GetWeaponAmmoClip(weapon) + player GetWeaponAmmoStock(weapon);
}
return total;
}
calculate_total_max(player)
{
weapons = player GetWeaponsList();
total = 0;
foreach(weapon in weapons)
{
total += WeaponClipSize(weapon) + WeaponMaxAmmo(weapon);
}
return total;
}
zombieMaxAmmoMonitor()
{
level endon("game_ended");
prev_count = 0;
level.forced_max_ammo = false; // bandera global: ¿alguien se quedó sin balas?
while(1)
{
players = get_players();
curr_count = GetAiArray("axis").size;
// Detectar si algún jugador se quedó sin munición
foreach(player in players)
{
if(!isDefined(player.prev_ammo))
player.prev_ammo = calculate_total_ammo(player);
current_ammo = calculate_total_ammo(player);
if(current_ammo == 0 && player.prev_ammo > 0)
{
level.forced_max_ammo = true;
}
player.prev_ammo = current_ammo;
}
// Al morir un zombie
if(curr_count < prev_count)
{
min_perc = 1.0;
foreach(player in players)
{
total_left = calculate_total_ammo(player);
total_max = calculate_total_max(player);
if(total_max > 0)
{
perc = total_left / total_max;
if(perc < min_perc) min_perc = perc;
}
}
drop = false;
if(level.forced_max_ammo)
{
drop = true;
level.forced_max_ammo = false;
}
else
{
max_desp = 1.0 - min_perc;
chance = int(100 * (max_desp * max_desp));
if(randomInt(100) < chance)
drop = true;
}
if(drop && players.size)
{
player = players[randomInt(players.size)];
drop_origin = player.origin + (randomIntRange(-120,120), randomIntRange(-120,120), 50);
trace = bulletTrace(drop_origin + (0,0,100), drop_origin + (0,0,-400), 0, player);
level thread specific_powerup_drop("full_ammo", trace["position"]);
iPrintLnBold("^3Max ammo dropeado: ^7" + int(chance) + " probabilidad");
}
}
prev_count = curr_count;
wait 0.1;
}
}
The code is a mess. Sorry for mix English and Spanish but English is not my first language.
Credits to https://forum.plutonium.pw/topic/32538/release-zm-black-ops-2-custom-perks this post. It give me the respective functions for damage manage.
It can be played in solo too, it's fun. I hope someone enjoy this mod like i did making it. 