/* 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; };