quake-rerelease-qc/quakec_mg1/horde.qc
2022-04-06 14:43:08 -05:00

2074 lines
No EOL
49 KiB
C++

/* Copyright (C) 1996-2022 id Software LLC
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
See file, 'COPYING', for details.
*/
.float storednextthink;
.void() storedthink;
void() Countdown;
void() Wavecheck;
void() SpawnAmmo;
.float wave; // what wave of the horde mode we're on
.float fodder; // how many fodder squads to spawn
.float elites; // how many elite squads to spawn
.float bosses; // how many bosses to spawn
.float spawncount; // number of monster spawns
.float army; // Spawn Army? True/fallse
float(entity spawnpoint) CheckBlockedSpawn;
float key_spawned; // TRUE/FALSE, determine if key has already been spawned, prevent dupes.
float SPAWN_RESET_TIME = 5; // amount of time that a spawn point is considered occupied and not valid for use
/*
================
HordeGetPlayersAlive
added Aug31 2021
Returns a float for the number of living players
================
*/
float() HordeGetPlayersAlive =
{
local float playercount;
local entity e;
// count entities
e = find(world, classname, "player");
while(e)
{
if (e.health > 0)
{
if (e.flags & FL_CLIENT)
playercount++;
else
dprint("Player is no longer a client!\n");
}
e = find(e, classname, "player");
}
dprint("there are ");
dprint(ftos(playercount));
dprint(" players alive.\n");
return playercount;
};
/*
================
HordeGetMonstersAlive
added Oct28 2021
Manually counts all living monsters
================
*/
float() HordeGetMonstersAlive =
{
local float monstercount;
local entity e;
// count monster entities
e = find(world, category, "monster");
while(e)
{
if ((e.health > 0) && (e.classname != "monster_zombie"))
monstercount++;
e = find(e, category, "monster");
}
dprint("verification count. There are ");
dprint(ftos(monstercount));
dprint(" monsters alive.\n");
return monstercount;
};
/*
================
HordeFindTarget
added Aug31 2021
Returns a random living player
================
*/
entity() HordeFindTarget =
{
local float playercount = HordeGetPlayersAlive();
if (playercount == 0)
return world;
local entity e;
local float rcount; // random value [0,count]
rcount = random() * playercount;
playercount = 0; // reset for new count
e = find(world, classname, "player");
while(e)
{
if (e.health > 0)
playercount++;
if ((rcount <= playercount) && !(e.flags & FL_NOTARGET))
return e;
else
e = find(e, classname, "player");
}
return world;
};
/*QUAKED info_monster_start START_OFF
if targeted, it will toggle between on or off, like lights
*/
float SKIP_BLOCK_CHECK = 2;
void() monster_start_use =
{
if (self.spawnflags & START_OFF)
self.spawnflags = self.spawnflags - START_OFF;
else
self.spawnflags = self.spawnflags + START_OFF;
};
void() info_monster_start =
{
self.wait = FALSE; // used for checking if this is a valid spawn
self.use = monster_start_use;
setorigin (self, self.origin);
setsize(self, '-80 -80 0', '80 80 128');
};
void() info_monster_start_ranged = // used for ogres and enforcers
{
self.wait = FALSE; // used for checking if this is a valid spawn
self.use = monster_start_use;
setorigin (self, self.origin);
setsize(self, '-44 -44 0', '44 44 128');
};
void() info_monster_start_flying = // used exclusively for scrags (wizards)
{
self.wait = FALSE; // used for checking if this is a valid spawn
self.use = monster_start_use;
setorigin (self, self.origin);
setsize(self, '-80 -80 0', '80 80 128');
};
void() info_monster_start_boss = // used for shalraths and shamblers
{
self.wait = FALSE; // used for checking if this is a valid spawn
self.use = monster_start_use;
setorigin (self, self.origin);
setsize(self, '-44 -44 0', '44 44 128');
};
// ENTITY
void() info_horde_ammo =
{
//self.wait = FALSE;
self.think = SpawnAmmo;
self.nextthink = time + HORDE_START_DELAY + random() * 3;
};
// ENTITY
void() info_horde_item =
{
self.wait = FALSE;
};
float KEY_FIRST = 1;
float KEY_SECOND = 2;
float KEY_THIRD = 4;
float KEY_FOURTH_PLUS = 8;
// ENTITY
void() info_horde_key =
{
self.wait = FALSE;
};
//============================================================================
void() PrecacheMonsters =
{
// knight
precache_model ("progs/knight.mdl");
precache_model ("progs/h_knight.mdl");
precache_sound ("knight/kdeath.wav");
precache_sound ("knight/khurt.wav");
precache_sound ("knight/ksight.wav");
precache_sound ("knight/sword1.wav");
precache_sound ("knight/sword2.wav");
precache_sound ("knight/idle.wav");
// hellknight
precache_model2 ("progs/hknight.mdl");
precache_model2 ("progs/k_spike.mdl");
precache_model2 ("progs/h_hellkn.mdl");
precache_sound2 ("hknight/attack1.wav");
precache_sound2 ("hknight/death1.wav");
precache_sound2 ("hknight/pain1.wav");
precache_sound2 ("hknight/sight1.wav");
precache_sound ("hknight/hit.wav"); // used by C code, so don't sound2
precache_sound2 ("hknight/slash1.wav");
precache_sound2 ("hknight/idle.wav");
precache_sound2 ("hknight/grunt.wav");
precache_sound ("knight/sword1.wav");
precache_sound ("knight/sword2.wav");
// dog
precache_model ("progs/h_dog.mdl");
precache_model ("progs/dog.mdl");
precache_sound ("dog/dattack1.wav");
precache_sound ("dog/ddeath.wav");
precache_sound ("dog/dpain1.wav");
precache_sound ("dog/dsight.wav");
precache_sound ("dog/idle.wav");
// demon
precache_model ("progs/demon.mdl");
precache_model ("progs/h_demon.mdl");
precache_sound ("demon/ddeath.wav");
precache_sound ("demon/dhit2.wav");
precache_sound ("demon/djump.wav");
precache_sound ("demon/dpain1.wav");
precache_sound ("demon/idle1.wav");
precache_sound ("demon/sight2.wav");
// ogre
precache_model ("progs/ogre.mdl");
precache_model ("progs/h_ogre.mdl");
precache_model ("progs/grenade.mdl");
precache_sound ("ogre/ogdrag.wav");
precache_sound ("ogre/ogdth.wav");
precache_sound ("ogre/ogidle.wav");
precache_sound ("ogre/ogidle2.wav");
precache_sound ("ogre/ogpain1.wav");
precache_sound ("ogre/ogsawatk.wav");
precache_sound ("ogre/ogwake.wav");
// grunt
precache_model ("progs/soldier.mdl");
precache_model ("progs/h_guard.mdl");
precache_model ("progs/gib1.mdl");
precache_model ("progs/gib2.mdl");
precache_model ("progs/gib3.mdl");
precache_sound ("soldier/death1.wav");
precache_sound ("soldier/idle.wav");
precache_sound ("soldier/pain1.wav");
precache_sound ("soldier/pain2.wav");
precache_sound ("soldier/sattck1.wav");
precache_sound ("soldier/sight1.wav");
precache_sound ("player/udeath.wav"); // gib death
// enforcer
precache_model2 ("progs/enforcer.mdl");
precache_model2 ("progs/h_mega.mdl");
precache_model2 ("progs/laser.mdl");
precache_sound2 ("enforcer/death1.wav");
precache_sound2 ("enforcer/enfire.wav");
precache_sound2 ("enforcer/enfstop.wav");
precache_sound2 ("enforcer/idle1.wav");
precache_sound2 ("enforcer/pain1.wav");
precache_sound2 ("enforcer/pain2.wav");
precache_sound2 ("enforcer/sight1.wav");
precache_sound2 ("enforcer/sight2.wav");
precache_sound2 ("enforcer/sight3.wav");
precache_sound2 ("enforcer/sight4.wav");
// shambler
precache_model ("progs/shambler.mdl");
precache_model ("progs/s_light.mdl");
precache_model ("progs/h_shams.mdl");
precache_model ("progs/bolt.mdl");
precache_sound ("shambler/sattck1.wav");
precache_sound ("shambler/sboom.wav");
precache_sound ("shambler/sdeath.wav");
precache_sound ("shambler/shurt2.wav");
precache_sound ("shambler/sidle.wav");
precache_sound ("shambler/ssight.wav");
precache_sound ("shambler/melee1.wav");
precache_sound ("shambler/melee2.wav");
precache_sound ("shambler/smack.wav");
// shalrath
precache_model2 ("progs/shalrath.mdl");
precache_model2 ("progs/h_shal.mdl");
precache_model2 ("progs/v_spike.mdl");
precache_sound2 ("shalrath/attack.wav");
precache_sound2 ("shalrath/attack2.wav");
precache_sound2 ("shalrath/death.wav");
precache_sound2 ("shalrath/idle.wav");
precache_sound2 ("shalrath/pain.wav");
precache_sound2 ("shalrath/sight.wav");
// wizard
precache_model ("progs/wizard.mdl");
precache_model ("progs/h_wizard.mdl");
precache_model ("progs/w_spike.mdl");
precache_sound ("wizard/hit.wav"); // used by c code
precache_sound ("wizard/wattack.wav");
precache_sound ("wizard/wdeath.wav");
precache_sound ("wizard/widle1.wav");
precache_sound ("wizard/widle2.wav");
precache_sound ("wizard/wpain.wav");
precache_sound ("wizard/wsight.wav");
// zombies
precache_model ("progs/zombie.mdl");
precache_model ("progs/h_zombie.mdl");
precache_model ("progs/zom_gib.mdl");
precache_sound ("zombie/z_idle.wav");
precache_sound ("zombie/z_idle1.wav");
precache_sound ("zombie/z_shot1.wav");
precache_sound ("zombie/z_gib.wav");
precache_sound ("zombie/z_pain.wav");
precache_sound ("zombie/z_pain1.wav");
precache_sound ("zombie/z_fall.wav");
precache_sound ("zombie/z_miss.wav");
precache_sound ("zombie/z_hit.wav");
precache_sound ("zombie/idle_w2.wav");
// ammo
precache_model ("maps/b_shell1.bsp");
precache_model ("maps/b_shell0.bsp");
precache_model ("maps/b_nail1.bsp");
precache_model ("maps/b_nail0.bsp");
precache_model ("maps/b_rock1.bsp");
precache_model ("maps/b_rock0.bsp");
precache_model ("maps/b_batt1.bsp");
precache_model ("maps/b_batt0.bsp");
// items
precache_model("maps/b_bh10.bsp"); // rotten health
precache_sound("items/r_item1.wav");
precache_model("maps/b_bh25.bsp"); // regular health
precache_sound("items/health1.wav");
precache_model ("progs/armor.mdl"); // regular armor
// quad damage
precache_model ("progs/quaddama.mdl");
precache_sound ("items/damage.wav");
precache_sound ("items/damage2.wav");
precache_sound ("items/damage3.wav");
// pentagram (invulnerability)
precache_model ("progs/invulner.mdl");
precache_sound ("items/protect.wav");
precache_sound ("items/protect2.wav");
precache_sound ("items/protect3.wav");
// new horde sounds
precache_sound("horde/nyom.wav");
// precache keys
if (world.worldtype == WORLDTYPE_MEDIEVAL || world.worldtype == WORLDTYPE_HUB)
{
precache_sound ("misc/medkey.wav");
precache_model("progs/w_g_key.mdl"); // gold key
precache_model("progs/w_s_key.mdl"); // silver key
}
if (world.worldtype == WORLDTYPE_METAL)
{
precache_sound ("misc/runekey.wav");
precache_model ("progs/m_g_key.mdl");
precache_model ("progs/m_s_key.mdl");
}
if (world.worldtype == WORLDTYPE_BASE)
{
precache_sound2 ("misc/basekey.wav");
precache_model2 ("progs/b_g_key.mdl");
precache_model2 ("progs/b_s_key.mdl");
}
};
// =================================================================
void() GibMonster =
{
T_Damage(self, world, world, 4000);
};
// =================================================================
/*
================
SpawnMonster
================
*/
entity(string class, vector org, vector temp_angles) SpawnMonster =
{
local entity monster;
monster = spawn();
monster.angles = temp_angles;
monster.solid = SOLID_SLIDEBOX;
monster.movetype = MOVETYPE_STEP;
//monster.target = "horde_manager";
monster.flags = monster.flags | FL_MONSTER;
//monster.alpha = 1; // only set monster alpha after they're a corpse
local entity oself; // old self
// perform enemy-type specific loading
if (class == "knight")
{
setmodel (monster, "progs/knight.mdl");
setsize (monster, '-16 -16 -24', '16 16 40');
org = org + '0 0 24'; // offset based on size
monster.classname = "monster_knight";
monster.health = 75;
monster.th_stand = knight_stand1;
monster.th_walk = knight_walk1;
monster.th_run = knight_run1;
monster.th_melee = knight_atk1;
monster.th_pain = knight_pain;
monster.th_die = knight_die;
}
else if (class == "hellknight")
{
setmodel (monster, "progs/hknight.mdl");
setsize (monster, '-16 -16 -24', '16 16 40');
org = org + '0 0 24';
monster.classname = "monster_hell_knight";
monster.health = 250;
monster.th_stand = hknight_stand1;
monster.th_walk = hknight_walk1;
monster.th_run = hknight_run1;
monster.th_melee = hknight_melee;
monster.th_missile = hknight_magicc1;
monster.th_pain = hknight_pain;
monster.th_die = hknight_die;
}
else if (class == "dog")
{
setmodel (monster, "progs/dog.mdl");
setsize (monster, '-32 -32 -24', '32 32 40');
org = org + '0 0 24';
monster.classname = "monster_dog";
monster.health = 25;
monster.th_stand = dog_stand1;
monster.th_walk = dog_walk1;
monster.th_run = dog_run1;
monster.th_pain = dog_pain;
monster.th_die = dog_die;
monster.th_melee = dog_atta1;
monster.th_missile = dog_leap1;
}
else if (class == "demon")
{
setmodel (monster, "progs/demon.mdl");
setsize (monster, VEC_HULL2_MIN, VEC_HULL2_MAX);
monster.classname = "monster_demon1";
monster.health = 300;
org = org + '0 0 48';
monster.th_stand = demon1_stand1;
monster.th_walk = demon1_walk1;
monster.th_run = demon1_run1;
monster.th_die = demon_die;
monster.th_melee = Demon_MeleeAttack;
monster.th_missile = demon1_jump1;
monster.th_pain = demon1_pain;
}
else if (class == "ogre")
{
setmodel (monster, "progs/ogre.mdl");
setsize (monster, VEC_HULL2_MIN, VEC_HULL2_MAX);
monster.classname = "monster_ogre";
monster.health = 200;
org = org + '0 0 32';
monster.th_stand = ogre_stand1;
monster.th_walk = ogre_walk1;
monster.th_run = ogre_run1;
monster.th_die = ogre_die;
monster.th_melee = ogre_melee;
monster.th_missile = ogre_nail1;
monster.th_pain = ogre_pain;
}
else if (class == "grunt")
{
setmodel (monster, "progs/soldier.mdl");
setsize (monster, '-16 -16 -24', '16 16 40');
monster.classname = "monster_army";
monster.health = 30;
org = org + '0 0 24';
monster.th_stand = army_stand1;
monster.th_walk = army_walk1;
monster.th_run = army_run1;
monster.th_missile = army_atk1;
monster.th_pain = army_pain;
monster.th_die = army_die;
}
else if (class == "enforcer")
{
setmodel (monster, "progs/enforcer.mdl");
setsize (monster, '-16 -16 -24', '16 16 40');
monster.classname = "monster_enforcer";
monster.health = 80;
org = org + '0 0 24';
monster.th_stand = enf_stand1;
monster.th_walk = enf_walk1;
monster.th_run = enf_run1;
monster.th_pain = enf_pain;
monster.th_die = enf_die;
monster.th_missile = enf_atk1;
}
else if (class == "shambler")
{
setmodel (monster, "progs/shambler.mdl");
setsize (monster, VEC_HULL2_MIN, VEC_HULL2_MAX);
monster.classname = "monster_shambler";
monster.health = 600;
org = org + '0 0 32';
monster.th_stand = sham_stand1;
monster.th_walk = sham_walk1;
monster.th_run = sham_run1;
monster.th_die = sham_die;
monster.th_melee = sham_melee;
monster.th_missile = sham_magic1;
monster.th_pain = sham_pain;
}
else if (class == "shalrath")
{
setmodel (monster, "progs/shalrath.mdl");
setsize (monster, VEC_HULL2_MIN, VEC_HULL2_MAX);
monster.classname = "monster_shalrath";
org = org + '0 0 32';
monster.health = 400;
monster.th_stand = shal_stand;
monster.th_walk = shal_walk1;
monster.th_run = shal_run1;
monster.th_die = shalrath_die;
monster.th_pain = shalrath_pain;
monster.th_missile = shal_attack1;
}
else if (class == "wizard")
{
setmodel (monster, "progs/wizard.mdl");
monster.flags = monster.flags | FL_FLY; // special behavior
monster.classname = "monster_wizard";
setsize (monster, VEC_HULL_MIN, VEC_HULL_MAX);
org = org + '0 0 32';
monster.health = 80;
monster.th_stand = wiz_stand1;
monster.th_walk = wiz_walk1;
monster.th_run = wiz_run1;
monster.th_missile = Wiz_Missile;
monster.th_pain = Wiz_Pain;
monster.th_die = wiz_die;
}
else if (class == "zombie")
{
setmodel (monster, "progs/zombie.mdl");
setsize (monster, '-16 -16 -24', '16 16 40');
monster.classname = "monster_zombie";
org = org + '0 0 32';
monster.health = 60;
monster.th_stand = zombie_stand1;
monster.th_walk = zombie_walk1;
monster.th_run = zombie_run1;
monster.th_pain = zombie_pain;
monster.th_die = zombie_die;
monster.th_missile = zombie_missile;
monster.target = string_null; // don't target anything!
}
else
{
dprint("ERROR: No monster provided");
}
// finish the shared settings
setorigin (monster, org);
monster.origin_z = monster.origin_z + 1; // raise off floor a bit
// temporarily swap self for next funcs
oself = self;
self = monster;
if (class != "wizard")
droptofloor();
if (!walkmove(0,0))
{
dprint ("walkmonster in wall at: ");
dprint (vtos(self.origin));
dprint ("\n");
}
// resume normal self behavior
self = oself;
monster.category = CATEGORY_MONSTER;
monster.takedamage = DAMAGE_AIM;
monster.ideal_yaw = monster.angles * '0 1 0';
if (!monster.yaw_speed)
monster.yaw_speed = 20;
monster.view_ofs = '0 0 25';
monster.use = monster_use;
monster.flags = monster.flags | FL_MONSTER;
monster.enemy = HordeFindTarget(); // find a random living player
//monster.enemy = find (world, classname, "player");
monster.think = FoundTarget;
//monster.nextthink = monster.nextthink + 0.1;
monster.nextthink = time + 0.1;
spawn_tdeath_fast (monster.origin, monster);
//spawn_tfog(monster.origin);
monster.owner = self;
if (monster.classname != "monster_zombie") // don't count zombies toward total goal
total_monsters = total_monsters + 1;
return monster;
};
void() horde_ammo_touch =
{
local entity temp_player, t;
local float best_weapon;
// early exit touch if not a player or not alive
if (other.classname != "player")
return;
if (other.health <= 0)
return;
// if the player was using their best weapon, change up to the new one if better
t = self; // hang on to the ammo entity for a moment
self = other;
best_weapon = W_BestWeapon();
self = t; // set self back to ammo entity
// if player is at max ammo for ammo's type, return
if (((self.weapon == 1) && (other.ammo_shells >= 100)) || // shotgun
((self.weapon == 2) && (other.ammo_nails >= 200)) || // nailgun
((self.weapon == 3) && (other.ammo_rockets >= 100)) || // rockets
((self.weapon == 4) && (other.ammo_cells >= 100))) // cells
return;
sprint(other, "$qc_got_item", self.netname);
// ammo touch sound
sound (other, CHAN_ITEM, "weapons/lock4.wav", 1, ATTN_NORM);
stuffcmd (other, "bf\n");
// change to a better weapon if appropriate
if ( other.weapon == best_weapon )
{
t = self;
self = other;
self.weapon = W_BestWeapon();
W_SetCurrentAmmo ();
self = t;
}
// loop through all players and give ammo
temp_player = find(world, classname, "player");
while(temp_player)
{
if (temp_player.health > 0) // only give ammo to living players
{
if (self.weapon == 1) // shotgun
temp_player.ammo_shells += self.aflag;
else if (self.weapon == 2) // spikes
temp_player.ammo_nails += self.aflag;
else if (self.weapon == 3) // rockets
temp_player.ammo_rockets += self.aflag;
else if (self.weapon == 4) // cells
temp_player.ammo_cells += self.aflag;
// temp swap and set bounds
t = other;
other = temp_player;
bound_other_ammo();
other = t;
// temp swap again and set current ammo
t = self;
self = temp_player;
W_SetCurrentAmmo();
self = t;
}
temp_player = find(temp_player, classname, "player");
}
// remove
self.model = string_null;
self.solid = SOLID_NOT;
// AY dec06 2021
//self.owner.wait = FALSE; The Old Way
self.owner.think = SpawnAmmo;
self.owner.nextthink = time + HORDE_AMMO_RESPAWN_DELAY;
// end AY
remove(self);
};
void() horde_health_touch =
{
if (other.classname != "player")
return;
if (!T_Heal(other, self.healamount, 0))
return;
sprint(other, "$qc_item_health", ftos(self.healamount));
// health touch sound
sound(other, CHAN_ITEM, self.noise, 1, ATTN_NORM);
stuffcmd (other, "bf\n");
self.model = string_null;
self.solid = SOLID_NOT;
self.owner.wait = FALSE;
remove(self);
};
void() horde_armor_touch =
{
local float type, value, bit;
if (other.health <= 0)
return;
if (other.classname != "player")
return;
if (self.classname == "item_armor1")
{
type = 0.3;
value = 100;
bit = IT_ARMOR1;
}
if (self.classname == "item_armor2")
{
type = 0.6;
value = 150;
bit = IT_ARMOR2;
}
if (self.classname == "item_armorInv")
{
type = 0.8;
value = 200;
bit = IT_ARMOR3;
}
if (other.armortype*other.armorvalue >= type*value)
return;
other.armortype = type;
other.armorvalue = value;
other.items = other.items - (other.items & (IT_ARMOR1 | IT_ARMOR2 | IT_ARMOR3)) + bit;
self.solid = SOLID_NOT;
self.model = string_null;
sprint(other, "$qc_item_armor");
// armor touch sound
sound(other, CHAN_ITEM, "items/armor1.wav", 1, ATTN_NORM);
stuffcmd (other, "bf\n");
self.owner.wait = FALSE;
remove(self);
};
void() horde_print_keys =
{
dprint(ftos(keys_silver));
dprint(" silver keys : ");
dprint(ftos(keys_gold));
dprint(" gold keys\n");
};
void(entity temp_player) horde_set_keys =
{
// player has just respawned, reset their keys based on global key values
if (keys_silver > 0)
temp_player.items+= IT_KEY1;
if (keys_gold > 0)
temp_player.items+= IT_KEY2;
};
void(float key_item) horde_key_give_all =
{
local entity temp_player;
temp_player = find(world, classname, "player");
while (temp_player)
{
temp_player.items = temp_player.items + key_item;
temp_player = find(temp_player, classname, "player");
}
horde_print_keys();
};
void(float key_item) horde_key_remove_all =
{
local entity temp_player;
temp_player = find(world, classname, "player");
while (temp_player)
{
temp_player.items = temp_player.items - key_item;
temp_player = find(temp_player, classname, "player");
}
horde_print_keys();
};
void(float key_item)horde_key_give =
{
if (key_item & IT_KEY1)
{
keys_silver++;
if (keys_silver == 1)
horde_key_give_all(IT_KEY1);
}
else if (key_item & IT_KEY2)
{
keys_gold++;
if (keys_gold == 1)
horde_key_give_all(IT_KEY2);
}
horde_print_keys();
}
void(float key_item)horde_key_spend =
{
if (key_item & IT_KEY1)
{
keys_silver--;
if (keys_silver == 0)
horde_key_remove_all(IT_KEY1);
}
else if (key_item & IT_KEY2)
{
keys_gold--;
if (keys_gold == 0)
horde_key_remove_all(IT_KEY2);
}
horde_print_keys();
}
void() horde_key_touch =
{
if (other.classname != "player")
return;
if (other.health <= 0)
return;
if (other.flags & FL_ISBOT)
return;
sprint(other, "$qc_got_item", self.netname);
sound (other, CHAN_ITEM, self.noise, 1, ATTN_NORM);
stuffcmd (other, "bf\n");
horde_key_give(self.items);
// find the horde manager and trigger the next wave
if (key_spawned)
{
dprint("key triggers next wave\n");
horde_ent.wait = TRUE;
horde_ent.think = Countdown;
horde_ent.nextthink = time;
}
else
dprint("key shouldn't trigger next wave!\n");
/*
local entity t = find(world, classname, "horde_manager");
if (t != world)
{
t.wait = TRUE;
t.think = Countdown;
t.nextthink = time;
}
*/
remove(self);
}
void() SpawnAmmo =
{
local float r, is_big;
local entity item;
local vector pos;
pos = self.origin;
item = spawn();
// roll a d4, a 1 means big ammo.
is_big = (random() * 4) <= 1;
if (is_big)
pos = pos - '16 16 0';
else
pos = pos - '12 12 0';
// NEW: roll a d20, 1-8 is shells, 9-14 is spikes, 14-17 is rockets, 18-20 is cells
// OLD: roll a d12, 1-5 is shells, 6-10 is spikes, 11 is rockets, 12 is cells
/*
ITEM New % Old %
shells 40% 41.66%
spikes 30% 33.33%
rockets 20% 8.33 %
cells 10% 8.33 %
*/
r = random() * 20;
if (r <= 7) // shells
{
item.classname = "item_shells";
if (is_big)
{
setmodel (item, "maps/b_shell1.bsp");
item.aflag = 40;
}
else
{
setmodel (item, "maps/b_shell0.bsp");
item.aflag = 20;
}
item.weapon = 1;
item.netname = "$qc_shells";
}
else if (r <= 14) // spikes
{
item.classname = "item_spikes";
if (is_big)
{
setmodel (item, "maps/b_nail1.bsp");
item.aflag = 50;
}
else
{
setmodel (item, "maps/b_nail0.bsp");
item.aflag = 25;
}
item.weapon = 2;
item.netname = "$qc_nails";
}
else if (r <= 17) // rockets
{
item.classname = "item_rockets";
if (is_big)
{
setmodel (item, "maps/b_rock1.bsp");
item.aflag = 10;
}
else
{
setmodel (item, "maps/b_rock0.bsp");
item.aflag = 5;
}
item.weapon = 3;
item.netname = "$qc_rockets";
}
else // cells
{
item.classname = "item_cells";
if (is_big)
{
setmodel (item, "maps/b_batt1.bsp");
item.aflag = 12;
}
else
{
setmodel (item, "maps/b_batt0.bsp");
item.aflag = 6;
}
item.weapon = 4;
item.netname = "$qc_cells";
}
setorigin (item, pos + '0 0 1');
setsize (item, '0 0 0', '32 32 56');
item.owner = self;
self.wait = TRUE;
item.movetype = MOVETYPE_TOSS;
item.solid = SOLID_TRIGGER;
item.flags = FL_ITEM;
item.touch = horde_ammo_touch;
spawn_tfog_silent(pos + '0 0 8'); // added aug30
//item.think = PlaceItem;
//item.nextthink = time + 0.1;
}
void() SpawnItem =
{
local float r;
local entity item;
local vector pos;
item = spawn();
pos = self.origin; // set to origin, then offset as needed
r = random() * 6;
if (r < 3) // rotten health
{
item.classname = "item_health";
item.touch = horde_health_touch;
setmodel (item, "maps/b_bh10.bsp");
item.noise = "items/r_item1.wav";
item.healamount = 15;
item.healtype = 0;
setsize (item, '0 0 0', '32 32 56');
//pos = pos - '16 16 0';
}
if (r < 5) // regular health
{
item.classname = "item_health";
item.touch = horde_health_touch;
setmodel (item, "maps/b_bh25.bsp");
item.noise = "items/health1.wav";
item.healamount = 25;
item.healtype = 1;
setsize (item, '0 0 0', '32 32 56');
pos = pos - '16 16 0';
}
else // armor
{
item.classname = "item_armor1";
item.touch = horde_armor_touch;
item.armortype = 0.3;
item.armorvalue = 100;
setmodel (item, "progs/armor.mdl");
item.skin = 0;
setsize (item, '-16 -16 0', '16 16 56');
}
setorigin (item, pos + '0 0 1');
item.owner = self;
self.wait = TRUE;
item.movetype = MOVETYPE_TOSS;
item.solid = SOLID_TRIGGER;
item.flags = FL_ITEM;
//item.think = PlaceItem;
//item.nextthink = time + 0.1;
spawn_tfog_silent(pos + '0 0 8'); // added aug30
};
/* Spawn Key
A key should spawn after every boss wave.
*/
void(float is_gold) SpawnKey =
{
local entity item;
item = spawn();
if (is_gold)
{
item.items = IT_KEY2;
// set model and sound based on worldtype
if (world.worldtype == WORLDTYPE_METAL)
{
item.netname = "$qc_gold_runekey";
item.noise = "misc/runekey.wav";
setmodel (item, "progs/m_g_key.mdl");
centerprint_all("$qc_horde_gold_runekey_appears");
}
else if (world.worldtype == WORLDTYPE_BASE)
{
item.netname = "$qc_gold_keycard";
item.noise = "misc/basekey.wav";
setmodel (item, "progs/b_g_key.mdl");
centerprint_all("$qc_horde_gold_keycard_appears");
}
else // assume medieval
{
item.netname = "$qc_gold_key";
item.noise = "misc/medkey.wav";
setmodel (item, "progs/w_g_key.mdl");
centerprint_all("$qc_horde_gold_key_appears");
}
}
else
{
item.items = IT_KEY1;
// set model and sound based on worldtype
if (world.worldtype == WORLDTYPE_METAL)
{
item.netname = "$qc_silver_runekey";
item.noise = "misc/runekey.wav";
setmodel (item, "progs/m_s_key.mdl");
centerprint_all("$qc_horde_silver_runekey_appears");
}
else if (world.worldtype == WORLDTYPE_BASE)
{
item.netname = "$qc_silver_keycard";
item.noise = "misc/basekey.wav";
setmodel (item, "progs/b_s_key.mdl");
centerprint_all("$qc_horde_silver_keycard_appears");
}
else // assume medieval
{
item.netname = "$qc_silver_key";
item.noise = "misc/medkey.wav";
setmodel (item, "progs/w_s_key.mdl");
centerprint_all("$qc_horde_silver_key_appears");
}
}
//item.noise = "misc/medkey.wav";
setsize (item, '-16 -16 -25', '16 16 32');
item.touch = horde_key_touch;
//item.think = PlaceItem;
//item.nextthink = time + 0.2;
item.target = "horde_manager"; // when used, retrigger the horde manager
item.flags = FL_ITEM; // make extra wide
//item.flags = item.flags & FL_NOBOTS; // AY 11 Nov 2021 so bots can't get keys
item.solid = SOLID_TRIGGER;
item.movetype = MOVETYPE_TOSS;
setorigin (item, self.origin + '0 0 32');
item.velocity = '0 0 255';
// AY Feb22, 2022, rune keys shouldn't have light effects on them
if (world.worldtype != WORLDTYPE_METAL)
item.effects = EF_BRIGHTLIGHT;
//dprint(" spawned key\n");
spawn_tfog(item.origin);
key_spawned = 1;
};
void() SpawnGoldKey =
{
SpawnKey(TRUE);
};
void() SpawnSilverKey =
{
SpawnKey(FALSE);
};
// Function get a key given the current wave
// Wave 3 should return a key with spawnflag "KEY_FIRST"
// Wave 6 should return a key with spawnflag "KEY_SECOND"
// Wave 9 should return a key with spawnflag "KEY_THIRD"
// Wave 9+ should return a key with spawnflag "KEY_FOURTH_PLUS"
// If a key with the correct spawnflag isn't found, return any key
void() GetKey =
{
local entity t, l;
t = find(world, classname, "info_horde_key");
l = t; // save ref for later
// check if any keys exist
if (t == world)
{
dprint("ERROR: No info_horde_key!\n");
// continue as if player got the key
self.think = Countdown;
self.nextthink = time + 4;
return;
}
while(t != world)
{
if (self.wave <= 3) // looking for KEY_FIRST
{
if (t.spawnflags & KEY_FIRST)
{
t.think = SpawnSilverKey;
t.nextthink = time;
return;
}
}
else if (self.wave <= 6) // looking for KEY_SECOND
{
if (t.spawnflags & KEY_SECOND)
{
t.think = SpawnSilverKey;
t.nextthink = time;
return;
}
}
else if (self.wave <= 9) // looking for KEY_THIRD
{
if (t.spawnflags & KEY_THIRD)
{
t.think = SpawnGoldKey;
t.nextthink = time;
return;
}
}
else // looking for KEY_FOURTH_PLUS
{
// check next key
if (t.spawnflags & KEY_FOURTH_PLUS)
{
t.think = SpawnSilverKey;
t.nextthink = time;
return;
}
}
t = find(t, classname, "info_horde_key");
}
// didn't find key with matching spawnflag, return any key
dprint("didn't find key with matching spawnflag. Return last: ");
dprint(l.classname);
if (self.wave == 9)
l.think = SpawnGoldKey;
else
l.think = SpawnSilverKey;
l.nextthink = time;
};
//============================================================================
/*
SpawnSquad2
Yoder FEB02 2022
*/
void(string name, vector org, vector temp_angles) SpawnSquad2 =
{
if (name == "3 grunts")
{
if (skill > 0)
{
SpawnMonster("grunt", org + '0 -40 0', temp_angles);
SpawnMonster("grunt", org + '40 40 0', temp_angles);
SpawnMonster("grunt", org + '-40 40 0', temp_angles);
}
else
{
SpawnMonster("grunt", org + '-40 0 0', temp_angles);
SpawnMonster("grunt", org + '40 0 0', temp_angles);
}
}
else if (name == "2 grunts, 1 dog")
{
SpawnMonster("dog", org + '44 0 0', temp_angles);
SpawnMonster("grunt", org + '-40 -40 0', temp_angles);
SpawnMonster("grunt", org + '-40 40 0', temp_angles);
}
else if (name == "2 dogs")
{
if (skill > 0)
{
SpawnMonster("dog", org + '-0 -44 0', temp_angles);
SpawnMonster("dog", org + '0 44 0', temp_angles);
}
else
SpawnMonster("dog", org + '-0 -44 0', temp_angles);
}
else if (name == "1 enforcer")
{
SpawnMonster("enforcer", org + '0 0 0', temp_angles);
}
else if (name == "2 enforcers")
{
if (skill > 0)
{
SpawnMonster("enforcer", org + '40 0 0', temp_angles);
SpawnMonster("enforcer", org + '-40 0 0', temp_angles);
}
else
SpawnMonster("enforcer", org + '0 0 0', temp_angles);
}
else if (name == "1 ogre")
{
SpawnMonster("ogre", org + '0 0 0', temp_angles);
}
else if (name == "2 knights")
{
if (skill > 0)
{
SpawnMonster("knight", org + '40 0 0', temp_angles);
SpawnMonster("knight", org + '-40 0 0', temp_angles);
}
else
{
SpawnMonster("knight", org + '40 0 0', temp_angles);
}
}
else if (name == "2 zombies")
{
SpawnMonster("zombie", org + '40 0 0', temp_angles);
SpawnMonster("zombie", org + '-40 0 0', temp_angles);
}
else if (name == "1 wizard")
SpawnMonster("wizard", org + '0 0 0', temp_angles);
else if (name == "2 hellknights")
{
if (skill > 0)
{
SpawnMonster("hellknight", org + '0 40 0', temp_angles);
SpawnMonster("hellknight", org + '0 -40 0', temp_angles);
}
else
SpawnMonster("hellknight", org + '0 40 0', temp_angles);
}
else if (name == "2 knights, 1 hellknight")
{
SpawnMonster("hellknight", org + '40 0 0', temp_angles);
SpawnMonster("knight", org + '-40 40 0', temp_angles);
SpawnMonster("knight", org + '-40 -40 0', temp_angles);
}
else if (name == "3 wizards")
{
if (skill > 0)
{
SpawnMonster("wizard", org + '40 40 40', temp_angles);
SpawnMonster("wizard", org + '-40 40 40', temp_angles);
SpawnMonster("wizard", org + '-40 -40 40', temp_angles);
}
else
{
SpawnMonster("wizard", org + '40 40 40', temp_angles);
SpawnMonster("wizard", org + '-40 40 40', temp_angles);
}
}
else if (name == "shambler")
SpawnMonster("shambler", org, temp_angles);
else if (name == "double demon")
{
if (skill >= 3 && random() > 0.8)
{
// double shambler
SpawnMonster("shambler", org + '40 40 0', temp_angles);
SpawnMonster("shambler", org + '-40 -40 0', temp_angles);
}
else if (skill >= 1)
{
SpawnMonster("demon", org + '40 40 0', temp_angles);
SpawnMonster("demon", org + '-40 -40 0', temp_angles);
}
else
SpawnMonster("demon", org, temp_angles);
}
else if (name == "shalrath")
SpawnMonster("shalrath", org, temp_angles);
else
dprint("ERROR: unrecognized squad name!\n");
spawn_tfog(org);
};
/*
HordeFindSpawnpoint
Yoder February 2nd 2022
*/
float HORDE_SQUAD_TYPE_NORMAL = 0;
float HORDE_SQUAD_TYPE_RANGED = 1;
float HORDE_SQUAD_TYPE_FLYING = 2;
float HORDE_SQUAD_TYPE_BOSS = 3;
float HORDE_SQUAD_CAT_ERROR = -1;
float HORDE_SQUAD_CAT_FODDER = 0;
float HORDE_SQUAD_CAT_ELITE = 1;
float HORDE_SQUAD_CAT_BOSS = 2;
entity(float squad_type)HordeFindSpawnpoint =
{
local float spawnpoint, temp_spawncount, loopcount, randomspawn;
local float temp_spawncount_valid; // how many of the spawn points are valid
local entity t;
spawnpoint = 0;
loopcount = 0;
string squad_class;
if (squad_type == HORDE_SQUAD_TYPE_BOSS)
squad_class = "info_monster_start_boss";
else if (squad_type == HORDE_SQUAD_TYPE_FLYING)
squad_class = "info_monster_start_flying";
else if (squad_type == HORDE_SQUAD_TYPE_RANGED)
squad_class = "info_monster_start_ranged";
else
squad_class = "info_monster_start";
// STEP 1: count all spawns of spawn type
temp_spawncount = 0;
temp_spawncount_valid = 0;
// count the spawnpoints for squad_class
t = find(world, classname, squad_class);
while (t)
{
temp_spawncount++;
if ((time > t.wait) && (!(t.spawnflags & START_OFF)) &&
(!CheckBlockedSpawn(t)))
temp_spawncount_valid++;
t = find(t, classname, squad_class);
}
// verify against 0 valid spawns
if (temp_spawncount_valid <= 0)
{
if (squad_type == HORDE_SQUAD_TYPE_NORMAL)
{
dprint("HordeFindSpawnPoint: FOUND 0 Valid spawns\n");
return world; // already on fallback option, oops
}
else
{
squad_type = HORDE_SQUAD_TYPE_NORMAL;
squad_class = "info_monster_start";
t = world;
loopcount = 1;
}
}
// count again, but for Normal spawn points?
if (loopcount)
{
temp_spawncount = 0;
temp_spawncount_valid = 0;
t = find(world, classname, squad_class);
while (t)
{
temp_spawncount++;
if ((time > t.wait) && (!(t.spawnflags & START_OFF)) &&
(!CheckBlockedSpawn(t)))
temp_spawncount_valid++;
t = find(t, classname, squad_class);
}
}
// verify against 0 valid spawns
if (temp_spawncount_valid <= 0)
{
dprint("HordeFindSpawnPoint: FOUND 0 Valid spawns after recheck\n");
return world; // already on fallback option, oops
}
// pick random spawncount
loopcount = 0;
randomspawn = temp_spawncount * random();
dprint("randomspawn: ");dprint(ftos(randomspawn));
dprint(" | whole count: ");dprint(ftos(temp_spawncount));
dprint(" | valid count: ");dprint(ftos(temp_spawncount_valid));dprint("\n");
t = find (world, classname, squad_class);
while(t)
{
spawnpoint++;
if (spawnpoint >= randomspawn)
{
if (!(t.spawnflags & START_OFF) && (time > t.wait) && (!(CheckBlockedSpawn(t))))
{
dprint("picked spawnpoint: ");dprint(ftos(spawnpoint));dprint("\n");
return t;
}
}
t = find(t, classname, squad_class);
if (!t)
{
if (loopcount)
dprint("something has gone horribly wrong!\n");
else
{
loopcount = 1;
t = find(t, classname, squad_class);
spawnpoint = 0;
randomspawn = 0; // allow any valid spawn
}
}
}
return world;
};
/*
SpawnWave2
Instead of finding a spawn point and then spawning a squad,
decide a squad, and then find a spawn point.
Yoder February 2nd 2022
*/
void() SpawnWave2 =
{
local entity t, oself; // t is the temp spawn, oself is for temp storage of self
local float squad_type;
local string squad_name; // specific squad name
local float r; // random roll
local float squad_cat; //
//STEP 1: determine what kind of monster is getting spawned
if (self.army)
{
if (self.fodder > 0)
{
r = random() * 4;
if (r < 1)
squad_name = "3 grunts";
else if (r < 2)
squad_name = "2 grunts, 1 dog";
else if (r < 3.5)
squad_name = "2 dogs";
else
{
squad_name = "1 enforcer";
squad_type = HORDE_SQUAD_TYPE_RANGED;
}
squad_cat = HORDE_SQUAD_CAT_FODDER;
}
else if (self.elites > 0)
{
squad_type = HORDE_SQUAD_TYPE_RANGED;
r = random() * 2;
if (r < 1.5)
squad_name = "2 enforcers";
else
squad_name = "1 ogre";
squad_cat = HORDE_SQUAD_CAT_ELITE;
}
else if (self.bosses > 0)
{
dprint("ERROR: should not spawn boss squad in army wave\n");
self.bosses = 0;
squad_cat = HORDE_SQUAD_CAT_ERROR;
}
}
else // NON-ARMY
{
if (self.fodder > 0)
{
r = random() * 4;
if (r < 2)
squad_name = "2 knights";
else if (r < 3)
squad_name = "2 zombies";
else
{
squad_name = "1 wizard";
squad_type = HORDE_SQUAD_TYPE_FLYING;
}
squad_cat = HORDE_SQUAD_CAT_FODDER;
}
else if (self.elites > 0)
{
r = random() * 4;
if (r < 1)
squad_name = "2 hellknights";
else if (r < 2)
squad_name = "2 knights, 1 hellknight";
else if (r < 3)
{
squad_name = "1 ogre";
squad_type = HORDE_SQUAD_TYPE_RANGED;
}
else
{
squad_name = "3 wizards";
squad_type = HORDE_SQUAD_TYPE_FLYING;
}
squad_cat = HORDE_SQUAD_CAT_ELITE;
}
else if (self.bosses > 0)
{
squad_type = HORDE_SQUAD_TYPE_BOSS;
r = random() * 3;
if (r < 1)
squad_name = "shambler";
else if (r < 2.5)
{
squad_name = "double demon";
squad_type = HORDE_SQUAD_TYPE_NORMAL;
}
else
squad_name = "shalrath";
squad_cat = HORDE_SQUAD_CAT_BOSS;
}
}
//STEP 2: find a spawn point for the squad
t = HordeFindSpawnpoint(squad_type);
dprint("spawnpoint found was: ");dprint(t.classname);dprint("\n");
if (t && (squad_cat != HORDE_SQUAD_CAT_ERROR))
{
t.wait = time + SPAWN_RESET_TIME; // block spawnpoint from reuse for a duration
// use targets
oself = self;
self = t;
SUB_UseTargets();
self = oself;
// set angles, just in case
if (!t.angles)
t.angles = '0 0 0';
SpawnSquad2(squad_name, t.origin, t.angles);
WriteByte(MSG_ALL, SVC_UPDATESTAT);
WriteByte(MSG_ALL, 12); // 12 = STAT_TOTALMONSTERS
WriteLong(MSG_ALL, total_monsters);
if (squad_cat == HORDE_SQUAD_CAT_FODDER)
self.fodder--;
else if (squad_cat == HORDE_SQUAD_CAT_ELITE)
self.elites--;
else if (squad_cat == HORDE_SQUAD_CAT_BOSS)
self.bosses--;
if ((self.fodder + self.elites + self.bosses) <= 0) // max spawns hit
{
dprint("wave spawn completed!\n");
self.wait = FALSE;
self.think = Wavecheck;
self.nextthink = time + 30;
}
else
{
// normal spawnwave wait time
self.think = SpawnWave2;
self.nextthink = time + 1 + random() + 1;
}
}
else
{
dprint("no valid spawns, wait a moment\n");
self.think = SpawnWave2;
self.nextthink = time + 1;
}
};
/* SpawnWavePrep
called once at the countdown starts
determines the number of squads by type
also spawns ammo and items
*/
float() SpawnWavePrep =
{
local entity p;
local float playercount, playerscalar;
// Reset Key Spawn
key_spawned = FALSE;
// Respawn dead players!
if (HordeGetPlayersAlive() < 1)
{
dprint("WARNING: Last player died at round end, don't start new round!\n");
return 0;
}
// Items
p = find (world, classname, "info_horde_item");
while (p)
{
if (!p.wait)
{
p.think = SpawnItem;
p.nextthink = time + random() * 2;
}
p = find (p, classname, "info_horde_item");
}
self.wave++;
dprint("wave ");
dprint(ftos(self.wave));
dprint("\n");
// See spreadsheet for notes on squad scaling
// AY FEB07 scaling start wave based on difficulty
local float temp_wave;
if (skill >= 3)
temp_wave = self.wave + 6;
else if (skill >= 2)
temp_wave = self.wave + 3;
else
temp_wave = self.wave;
// Determine if Army wave
if ((temp_wave + 2) % 3 == 0)
self.army = TRUE;
else
self.army = FALSE;
// player scalar
playercount = HordeGetPlayersAlive();
if (playercount >= 4)
playerscalar = 2;
else if (playercount >= 3)
playerscalar = 1.5;
else if (playercount >= 2)
playerscalar = 1.25;
else
playerscalar = 1;
// Boss Count
if (temp_wave % 3 == 0)
{
self.bosses = floor((temp_wave+1)/4);
}
else if ((skill > 1) && (!self.army) && (temp_wave > 9))
{
//AY feb07, some extra spice
self.bosses = floor((temp_wave+1)/8);
}
// Elite Count
self.elites = ceil((temp_wave - 1)/3) - floor(self.bosses/2);
self.elites = floor(self.elites * playerscalar);
// Fodder Count
self.fodder = (temp_wave + 2) - (self.bosses * 2 + self.elites);
self.fodder = floor(self.fodder * playerscalar);
// Trigger map entities
local entity stemp;
p = find (world, targetname, self.target);
while(p)
{
stemp = self;
self = p;
if (self.use != SUB_Null)
{
if (self.use)
self.use ();
}
self = stemp;
p = find (p, targetname, self.target);
}
self.wait = TRUE;
return 1;
}
void() Countdown4 =
{
if (self.wave % 3 == 0)
centerprint_all("$qc_horde_boss_wave");
else
centerprint_all("$qc_horde_fight");
SpawnWave2();
sound(self, CHAN_VOICE, "misc/talk.wav", 1, ATTN_NONE);
};
void() Countdown3 =
{
centerprint_all("1");
self.think = Countdown4;
self.nextthink = time + 1;
sound(self, CHAN_VOICE, "misc/talk.wav", 1, ATTN_NONE);
};
void() Countdown2 =
{
centerprint_all("2");
self.think = Countdown3;
self.nextthink = time + 1;
sound(self, CHAN_VOICE, "misc/talk.wav", 1, ATTN_NONE);
};
void() Countdown =
{
if (!SpawnWavePrep())
return;
centerprint_all("3");
self.think = Countdown2;
self.nextthink = time + 1;
sound(self, CHAN_VOICE, "misc/talk.wav", 1, ATTN_NONE);
};
void() RespawnAllPlayers =
{
local entity p, oself;
p = find (world, classname, "player");
while (p)
{
if (p.deadflag > 0)
{
oself = self;
self = p;
horde_respawn_teammate();
self = oself;
//p.think = horde_respawn_teammate;
//p.nextthink = time; // next frame
}
p = find (p, classname, "player");
}
};
/*
When each enemy spawned by the horde manager dies, they trigger self.owner.use, which is wavecheck.
Wavecheck sees if all spawned monsters are dead, so it can determine if it needs to start a new wave.
*/
void() Wavecheck =
{
if (self.wait) // still in the process of spawning monsters, don't wavecheck
return;
self.think = Wavecheck;
self.nextthink = time + 10;
dprint("\n===============\n");
dprint("checking kills: ");
dprint(ftos(killed_monsters));
dprint(" of ");
dprint(ftos(total_monsters));
dprint("\n");
// debug checking last monster position
if (killed_monsters + 3 >= total_monsters)
{
local entity t;
t = find(world, category, CATEGORY_MONSTER);
while(t)
{
if (t.health > 0)
{
dprint("Position of monster: ");
dprint(t.classname);
dprint(" : ");
dprint(vtos(t.origin));
dprint("\n");
}
else
t.category = string_null;
t = find(t, category, CATEGORY_MONSTER);
}
}
// Early exit for kill count
if ((self.wave % 3 == 0) || (self.wave < 3)) // Be exact?
{
dprint("check monsters killed as boss wave\n");
if (HordeGetMonstersAlive() > 0) // testing the new way
return;
//if (killed_monsters < total_monsters)
// return;
}
else
{
dprint("check monsters killed as any other wave\n");
if (HordeGetMonstersAlive() > 5)
return;
//if (killed_monsters < (total_monsters -5))
// return;
}
// Made it this far, means wave completed successfully
dprint("wavecheck looks good! Respawning players\n");
RespawnAllPlayers();
self.wait = 1;
self.think = Countdown;
if (self.wave % 3 == 0) // it was a boss wave
{
if(!key_spawned) // key was already spawned, don't spawn another yet!
GetKey();
self.nextthink = time + 20; // in case the players are slow to find the key
}
else
{
self.nextthink = time;
}
dprint("wavecheck now completed.\n");
};
// setup horde rules
void() SetHorde =
{
dprint("TEST: It is September 23, 2021!\n"); // yoder sanity test
self.think = Countdown;
self.nextthink = time + 1;
}
// define entity
void() horde_manager =
{
// AY, Dec06 2021, auto set horde cvar
if (!cvar("horde") && !deathmatch)
cvar_set("horde", "1");
// end AY
PrecacheMonsters();
horde_ent = self; // set global reference to the horde manager
if (!self.target)
self.target = "horde_event"; // default name
self.targetname = "horde_manager";
self.think = SetHorde;
self.nextthink = time + HORDE_START_DELAY;
self.use = Wavecheck;
self.wait = 1; // waiting before next wave?
self.delay = 9; // max number of squads to spawn in a wave
self.wave = 0;
//if (!horde)
// horde = 1; // in case CVAR isn't already set
};
void() PowerupFade =
{
if (self.alpha > 0)
{
self.alpha = self.alpha - 0.25 * frametime;
self.nextthink = time; // think next frame
}
else
remove(self);
}
void() horde_powerup_think =
{
if (self.velocity_z < 0)
{
dprint("WARNING: Powerup fell out of world. Remove\n");
remove(self);
}
else
{
self.alpha = 1;
self.think = PowerupFade;
self.nextthink = time;
}
};
void() horde_spawn_powerup =
{
if (!powerup_chance)
powerup_chance = DEFAULT_POWERUP_CHANCE;
if (random() < powerup_chance) // "if(1)" to guarantee powerup drop
{
dprint("powerup chance was: ");
dprint(ftos(powerup_chance));
dprint("\n");
powerup_chance = DEFAULT_POWERUP_CHANCE;
local entity powerup = spawn();
setorigin (powerup, self.origin + '0 0 0');
//setsize (powerup, '-16 -16 -24', '16 16 32');
powerup.flags = FL_ITEM;
powerup.solid = SOLID_TRIGGER;
powerup.movetype = MOVETYPE_BOUNCE;
powerup.velocity = '0 0 300';
//powerup.velocity_x = crandom() * 64;
//powerup.velocity_y = crandom() * 64;
powerup.touch = powerup_touch;
powerup.think = horde_powerup_think;
powerup.nextthink = time + 10;
if (random() < 0.25) // spawn pentagram (invulnerability)
{
powerup.noise = "items/protect.wav";
setmodel(powerup, "progs/invulner.mdl");
powerup.netname = "$qc_pentagram_of_protection";
powerup.items = IT_INVULNERABILITY;
powerup.classname = "item_artifact_invulnerability";
}
else // spawn quad damage
{
powerup.noise = "items/damage.wav";
setmodel(powerup, "progs/quaddama.mdl");
powerup.netname = "$qc_quad_damage";
powerup.items = IT_QUAD;
powerup.classname = "item_artifact_super_damage";
}
//powerup.effects = EF_DIMLIGHT;
setsize (powerup, '-12 -12 -12', '12 12 12');
}
else
powerup_chance = powerup_chance + POWERUP_CHANCE_GAIN;
};
// remote wavecheck
// this is to trigger a wavecheck from outside of horde mode
void() remote_wavecheck =
{
if(!horde_ent || (intermission_running))
{
dprint("no wavecheck.");
if (!horde_ent)
dprint(" no horde ent found.");
if (intermission_running)
dprint(" intermission running.");
dprint("\n\n");
return;
}
dprint("remote wavecheck from: ");
dprint(self.classname);
dprint("\n");
local entity stemp = self; // the entity that is triggering the remote wavecheck
self = horde_ent;
self.use();
self = stemp; // set self back to whatever it was
};
/* CheckBlockedSpawn
AY Feb24, 2022
Checks the horde spawn against all living players
Returns true/false if spawn is blocked.
*/
float(entity spawnpoint) CheckBlockedSpawn =
{
local float blocked = FALSE;
local entity p;
local vector p_mins, p_maxs, s_mins, s_maxs;
if (spawnpoint.spawnflags & SKIP_BLOCK_CHECK)
return FALSE;
p = find(world, classname, "player");
while(p && !blocked)
{
if ((p.health > 0) && (p.deadflag <= 0))
{
p_mins = p.origin + p.mins;
p_maxs = p.origin + p.maxs;
s_mins = spawnpoint.origin + spawnpoint.mins;
s_maxs = spawnpoint.origin + spawnpoint.maxs;
if (((p_maxs_x > s_mins_x) && (p_mins_x < s_maxs_x)) &&
((p_maxs_y > s_mins_y) && (p_mins_y < s_maxs_y)) &&
((p_maxs_y > s_mins_y) && (p_mins_y < s_maxs_y)))
{
dprint("player blocking spawn\n");
blocked = TRUE;
}
}
p = find(p, classname, "player");
}
return blocked;
};