Git Repos / fte_dogmode / qc / func / monster_spawner.qc
Last update to this file was on 2025-08-13 at 05:20.
Show monster_spawner.qc
//==============================================================================
// func_monster_spawner -- mobot.qc
//==============================================================================
// Using "bot" creation code for func_monster_spawner
// most of the code is from a tutorial on creating a bot monster found here:
// https://www.quaddicted.com/webarchive/minion.planetquake.gamespy.com/tutorial/tutor9.htm
// I added the MobotSpawnPoint, retriggering and random gen with help from
// kreathor, 4LT, Paril, Spoike and others on the Quake Mapping Discord.
// ===================================================================
// Big thanks to ryanscissorhands for helping fix telefragging issues!
// ===================================================================
//======================================================================
// constants
//======================================================================
#ifdef SSQC
//----------------------------------------------------------------------
// func_monster_spawner spawnflags -- CEV
//----------------------------------------------------------------------
typedef enumflags
{
        SPAWNFLAG_FUNC_MOBOT_SPAWNER_RESET = 1,
        SPAWNFLAG_FUNC_MOBOT_PASSIVE = 2, // MOBOT_DONT_SPAWN_ANGRY
        SPAWNFLAG_FUNC_MOBOT_DONT_ADD_KILL_COUNT = 4,
        SPAWNFLAG_FUNC_MOBOT_SILENT_SPAWN = 32
        // SPAWNFLAG_NOT_ON_EASY = 256, // see base_entities.qc -- CEV
        // SPAWNFLAG_NOT_ON_NORMAL = 512,
        // SPAWNFLAG_NOT_ON_HARD_OR_NIGHTMARE = 1024,
        // SPAWNFLAG_NOT_IN_DEATHMATCH = 2048,
        // SPAWNFLAG_NOT_IN_COOP = 4096,
        // SPAWNFLAG_NOT_IN_SP = 8192,
        // SPAWNFLAG_NOT_ON_SKILL2 = 32768, // see base_entities.qc -- CEV
        // SPAWNFLAG_NOT_ON_SKILL3 = 65536, // see base_entities.qc -- CEV
        // SPAWNFLAG_CENTERPRINTALL = 131072 // see base_entities.qc -- CEV
} func_monster_spawner_spawnflags;
#endif
//======================================================================
// forward declarations
//======================================================================
// func_monster_spawner
#ifdef SSQC
entity() func_monster_spawner_find_spawnpoint;
void(entity e) func_monster_spawner_tfrag;
void() func_monster_spawner_create;
void() func_monster_spawner_think;
void(entity e) func_monster_spawner_init; // Initialization
void() func_monster_spawner;
#endif
//------------------------------------------------------------------------------
/*QUAKED func_monster_spawner (.75 0 .75) (-8 -8 -8) (8 8 8) REUSABLE X X X X X X X NOT_ON_EASY NOT_ON_NORMAL NOT_ON_HARD_OR_NIGHTMARE NOT_IN_DEATHMATCH NOT_IN_COOP NOT_IN_SINGLEPLAYER X NOT_ON_HARD_ONLY NOT_ON_NIGHTMARE_ONLY
Spawns monsters to targeted info_monster_spawnpoint
bot
Choose Style
• 1 (Dog (Default)
• 2 (Grunt)
• 3 (Enforcer)
• 4 (Ogre)
• 5 (Fiend)
• 6 (Wizard)
• 7 (Shambler)
• 8 (Knight)
• 9 (HellKnight)
• 11 (Zombie)
• 12 (Shalrath)
Style 2 set to 1 overrides Style and chooses a random monster (excluding Zombies and Vores)
Default time between spawns is 5 seconds; 12 for Zombies and Vores to reduce telefrags
Can set Berserk to 1 to skip most pain animations.
Can only use default health, models and sounds.
*/
//----------------------------------------------------------------------
// class func_monster_spawner: base_func
// {
#ifdef SSQC
        //--------------------------------------------------------------
        // find_spawnpoint -- Returns the entity to spawn at; was named
        // MobotSpawnPoint
        //--------------------------------------------------------------
        entity() func_monster_spawner_find_spawnpoint =
        {
                local entity spot;
                // spot = find (world, classname, "info_monster_spawnpoint");
                spot = find (world, targetname, self.target);
                if (!spot)
                        objerror ("func_monster_spawner_find_spawnpoint: "
                                "couldn't find target!\n");
                return spot;
        };
        //--------------------------------------------------------------
        void(entity e) func_monster_spawner_tfrag =
        {
                if (!(self.spawnflags & SPAWNFLAG_FUNC_MOBOT_SILENT_SPAWN))
                        // SILENT
                        spawn_tfog (e.origin);
                spawn_tdeath (e.origin, e);
        };
        //--------------------------------------------------------------
        void() func_monster_spawner_create =
        {
                // let's not use deathmatch points for this -- dumptruck_ds
                // spawn_spot = SelectSpawnPoint ();
                local entity spawn_spot =
                        func_monster_spawner_find_spawnpoint ();
                // use a flag instead of the spawn_angry field -- CEV
                local float sflags = 0;
                if (!(self.spawnflags & SPAWNFLAG_FUNC_MOBOT_PASSIVE))
                        sflags |= SPAWNFLAG_MONSTER_ANGRY;
                local entity e = __NULL__;
                switch (self.style)
                {
                        case 1:
                                // spawn a Doggo
                                e = spawn_monster_dog (
                                        spawn_spot.origin + '0 0 1',
                                        spawn_spot.angles,
                                        sflags,
                                        120);
                                e.view_ofs = '0 0 22';
                                break;
                        case 2:
                                // spawn a Grunt
                                e = spawn_monster_army (
                                        spawn_spot.origin + '0 0 1',
                                        spawn_spot.angles,
                                        sflags,
                                        120);
                                e.view_ofs = '0 0 22';
                                break;
                        case 3:
                                // spawn an Enforcer
                                e = spawn_monster_enforcer (
                                        spawn_spot.origin + '0 0 1',
                                        spawn_spot.angles,
                                        sflags,
                                        120);
                                e.view_ofs = '0 0 22';
                                break;
                        case 4:
                                // spawn an Ogre
                                e = spawn_monster_ogre (
                                        spawn_spot.origin + '0 0 1',
                                        spawn_spot.angles,
                                        sflags,
                                        120);
                                e.view_ofs = '0 0 22';
                                break;
                        case 5:
                                // spawn a Fiend
                                e = spawn_monster_demon1 (
                                        spawn_spot.origin + '0 0 1',
                                        spawn_spot.angles,
                                        sflags,
                                        120);
                                e.view_ofs = '0 0 22';
                                break;
                        case 6:
                                // spawn a Wizard / Scrag
                                e = spawn_monster_wizard (
                                        spawn_spot.origin + '0 0 1',
                                        spawn_spot.angles,
                                        sflags,
                                        120);
                                // e.mdl_proj = "progs/w_spike.mdl";
                                e.view_ofs = '0 0 22';
                                break;
                        case 7:
                                // spawn a Shambler
                                e = spawn_monster_shambler (
                                        spawn_spot.origin + '0 0 1',
                                        spawn_spot.angles,
                                        sflags,
                                        120);
                                e.view_ofs = '0 0 2';
                                break;
                        case 8:
                                // spawn a Knight
                                e = spawn_monster_knight (
                                        spawn_spot.origin + '0 0 1',
                                        spawn_spot.angles,
                                        sflags,
                                        120);
                                e.view_ofs = '0 0 22';
                                break;
                        case 9:
                                // spawn a HellKnight
                                e = spawn_monster_hell_knight (
                                        spawn_spot.origin + '0 0 1',
                                        spawn_spot.angles,
                                        sflags,
                                        120);
                                e.view_ofs = '0 0 22';
                                break;
                        case 11:
                                // spawn a Zombie
                                e = spawn_monster_zombie (
                                        spawn_spot.origin + '0 0 1',
                                        spawn_spot.angles,
                                        sflags,
                                        120);
                                e.view_ofs = '0 0 2';
                                break;
                        case 12:
                                // spawn a Shalrath
                                e = spawn_monster_shalrath (
                                        spawn_spot.origin + '0 0 1',
                                        spawn_spot.angles,
                                        sflags,
                                        120);
                                e.view_ofs = '0 0 22';
                                break;
                        default:
                                dprint (sprintf("func_monster_spawner_create: "
                                        "unknown style %g!\n", self.style));
                }
                if (e)
                {
                        e.fixangle = TRUE;
                        func_monster_spawner_tfrag (e);
                        if (!(self.spawnflags &
                                SPAWNFLAG_FUNC_MOBOT_DONT_ADD_KILL_COUNT))
                        {
                                // replacement function from iw -- dumptruck_ds
                                monster_update_total (1);
                        }
                }
        };
        //--------------------------------------------------------------
        void() func_monster_spawner_think =
        {
                dprint ("func_monster_spawner_think: starting...\n");
                // telefrag check thanks to ryanscissorhands for this code!
                // -- dumptruck_ds
                // findradius (vector origin, float radius in Quake units)
                local entity nearby = findradius (self.origin, 128);
                while (nearby)
                {
                        if (nearby.takedamage && nearby.solid != SOLID_CORPSE)
                        {
                                dprint ("func_monster_spawner_think: "
                                        "monster waiting to spawn\n");
                                // qss crash fix from paavohuhtala
                                // -- dumptruck_ds
                                self.think = func_monster_spawner_think;
                                // delay the spawn by some amount;
                                self.nextthink = time + 1;
                                return;
                        }
                        nearby = nearby.chain;
                }
                self.count = self.count - 1;
                if (self.count < 0)
                        return;
                if (self.count != 0)
                {
                        if !(self.wait)
                                self.nextthink = time + 5;
                        else
                                self.nextthink = time + self.wait;
                        if (self.style2 == 1)
                        {
                                // random monster!! -- dumptruck_ds
                                self.style = floor (random() * 12) + 1;
                                // re-roll 10s -- CEV
                                while (self.style == 10)
                                        self.style = floor (random() * 12) + 1;
                                // thanks whirledtsar for your help on this
                                // -- dumptruck_ds
                                func_monster_spawner_create ();
                        }
                        else
                        {
                                func_monster_spawner_create ();
                        }
                }
                else
                {
                        if (self.spawnflags &
                                SPAWNFLAG_FUNC_MOBOT_SPAWNER_RESET)
                        {
                                self.count = self.cnt;
                                self.think = sub_null;
                        }
                }
                self.think = func_monster_spawner_think;
        };
        //==============================================================
        // Initialization
        //==============================================================
        //--------------------------------------------------------------
        void(entity e) func_monster_spawner_init =
        {
                if !(e.target)
                {
                        objerror ("func_monster_spawner needs a target");
                        remove (e);
                        return;
                }
                e.classname = "func_monster_spawner";
                e.classtype = CT_FUNC_MONSTER_SPAWNER;
                base_mapentity_init (e);
                if !(e.style)
                        e.style = 1;
                if (!e.count)
                        e.count = 5;
                // fixes count display
                e.count = e.count + 1;
                // hold original count
                e.cnt = e.count;
                e.use = func_monster_spawner_think;
                // don't transmit this entity to the client -- CEV
                // e.SendEntity = base_entity_netsend_null;
        };
        //--------------------------------------------------------------
        void() func_monster_spawner =
        {
                BASE_FUNC_PREINIT (__NULL__)
                func_monster_spawner_init (self);
        };
#endif
// };
Return to the top of this page or return to the overview of this repo.
Log monster_spawner.qc
| Date | Commit Message | Author | + | - | 
|---|---|---|---|---|
| 2025-08-13 | Another big commit. Item changes, field rework, etc. | cev | +1 | -4 | 
| 2025-03-30 | Big commit. Entity networking, etc. | cev | +59 | -41 | 
| 2024-09-17 | Ice, improved stairs, other movement changes | cev | +3 | -2 | 
| 2024-06-15 | Major update, committing as-is, will have bugs | cev | +148 | -135 | 
| 2024-04-08 | Registered monsters, projectile bugfixes | cev | +47 | -54 | 
| 2024-02-18 | Client/player, projectiles, entrypoints refactor | cev | +141 | -436 | 
| 2024-01-31 | Class based monster refactor & start projectiles | cev | +58 | -3 | 
| 2024-01-09 | Continue OO / Class-based refactor | cev | +514 | -503 | 
| 2023-11-27 | Code reorg, minor movement changes, misc | cev | +565 | 
Return to the top of this page or return to the overview of this repo.