Merge commit '40b7b8b8f77676', fixes #1937
authorRudolf Polzer <divverent@xonotic.org>
Wed, 4 Jun 2014 09:27:34 +0000 (11:27 +0200)
committerRudolf Polzer <divverent@xonotic.org>
Wed, 4 Jun 2014 09:27:34 +0000 (11:27 +0200)
* commit '40b7b8b8f77676':
  Some improvements to invasion (allow team based invasion if map specifies, allow more monsters to spawn at the same time, precache monsters only if they're used)
  Some minor improvements to monsters (auto precache default model, use monster kills as main point limit in invasion)
  Add a monsters mapinfo feature
  Fix invasion monster spawn counts, fix spawning random monsters, clean up monster spawn system to no longer abuse target_spawn_edit_entity, allow monsters to chase targets through warpzones, fix shambler stomping while in the air, add a new "invincible" spawnflag which prevents monster from taking damage, allow dragging monsters around in edit mode and fix monster count off when monster is gibbed

22 files changed:
gamemodes.cfg
monsters.cfg
qcsrc/common/mapinfo.qc
qcsrc/common/mapinfo.qh
qcsrc/common/monsters/monster/mage.qc
qcsrc/common/monsters/monster/shambler.qc
qcsrc/common/monsters/monster/spider.qc
qcsrc/common/monsters/monster/wyvern.qc
qcsrc/common/monsters/monster/zombie.qc
qcsrc/common/monsters/monsters.qc
qcsrc/common/monsters/spawn.qc
qcsrc/common/monsters/spawn.qh
qcsrc/common/monsters/sv_monsters.qc
qcsrc/common/monsters/sv_monsters.qh
qcsrc/server/autocvars.qh
qcsrc/server/cl_weaponsystem.qc
qcsrc/server/command/cmd.qc
qcsrc/server/command/sv_cmd.qc
qcsrc/server/mutators/gamemode_invasion.qc
qcsrc/server/mutators/gamemode_invasion.qh
qcsrc/server/teamplay.qc
qcsrc/server/w_common.qc

index 30352bb..dfc2405 100644 (file)
@@ -80,7 +80,7 @@ seta g_keyhunt_point_leadlimit -1     "Keyhunt point lead limit overriding the mapin
 seta g_race_laps_limit -1      "Race laps limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
 seta g_nexball_goallimit -1 "Nexball goal limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
 seta g_nexball_goalleadlimit -1 "Nexball goal lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
-seta g_invasion_round_limit -1 "Invasion round limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
+seta g_invasion_point_limit -1 "Invasion point limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
 
 
 // =================================
@@ -475,4 +475,7 @@ set g_invasion_round_timelimit 120 "maximum time to kill all monsters"
 set g_invasion_warmup 10 "time between waves to prepare for battle"
 set g_invasion_monster_count 10 "number of monsters on first wave (increments)"
 set g_invasion_zombies_only 0 "only spawn zombies"
-set g_invasion_spawn_delay 2 "spawn more monsters after this delay"
+set g_invasion_spawn_delay 0.25
+set g_invasion_spawnpoint_spawn_delay 0.5
+set g_invasion_teams 0 "number of teams in invasion (note: use mapinfo to set this)"
+set g_invasion_team_spawns 1 "use team spawns in teamplay invasion mode"
index 296d24b..3ff25a4 100644 (file)
@@ -90,6 +90,7 @@ set g_monsters_owners 1
 set g_monsters_teams 1
 set g_monsters_score_kill 0
 set g_monsters_score_spawned 0
+set g_monsters_sounds 1
 set g_monsters_spawnshieldtime 2
 set g_monsters_typefrag 1
 set g_monsters_target_range 2000
index feb0e03..b8fe58b 100644 (file)
@@ -343,6 +343,8 @@ float _MapInfo_Generate(string pFilename) // 0: failure, 1: ok ent, 2: ok bsp
                                        MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_TURRETS;
                                else if(startsWith(v, "vehicle_"))
                                        MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_VEHICLES;
+                               else if(startsWith(v, "monster_"))
+                                       MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_MONSTERS;
                                else if(v == "target_music" || v == "trigger_music")
                                        _MapInfo_Map_worldspawn_music = string_null; // don't use regular BGM
                        }
@@ -631,6 +633,7 @@ void _MapInfo_Map_ApplyGametypeEx(string s, float pWantedType, float pThisType)
                        cvar_set("g_freezetag_teams", v);
                        cvar_set("g_keyhunt_teams", v);
                        cvar_set("g_domination_default_teams", v);
+                       cvar_set("g_invasion_teams", v);
                }
                else if(k == "qualifying_timelimit")
                {
@@ -978,6 +981,7 @@ float MapInfo_Get_ByName_NoFallbacks(string pFilename, float pAllowGenerate, flo
                        if     (t == "weapons") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_WEAPONS;
                        else if(t == "turrets") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_TURRETS;
                        else if(t == "vehicles") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_VEHICLES;
+                       else if(t == "monsters") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_MONSTERS;
                        else if(t == "new_toys") MapInfo_Map_supportedFeatures |= MAPINFO_FEATURE_WEAPONS;
                        else
                                dprint("Map ", pFilename, " supports unknown feature ", t, ", ignored\n");
index 7746dfe..3c0afec 100644 (file)
@@ -75,12 +75,13 @@ REGISTER_GAMETYPE(_("Freeze Tag"),ft,g_freezetag,FREEZETAG,"timelimit=20 pointli
 REGISTER_GAMETYPE(_("Keepaway"),ka,g_keepaway,KEEPAWAY,"timelimit=20 pointlimit=30");
 #define g_keepaway IS_GAMETYPE(KEEPAWAY)
 
-REGISTER_GAMETYPE(_("Invasion"),inv,g_invasion,INVASION,"pointlimit=5");
+REGISTER_GAMETYPE(_("Invasion"),inv,g_invasion,INVASION,"pointlimit=50 teams=0");
 #define g_invasion IS_GAMETYPE(INVASION)
 
 const float MAPINFO_FEATURE_WEAPONS       = 1; // not defined for minstagib-only maps
 const float MAPINFO_FEATURE_VEHICLES      = 2;
 const float MAPINFO_FEATURE_TURRETS       = 4;
+const float MAPINFO_FEATURE_MONSTERS      = 8;
 
 const float MAPINFO_FLAG_HIDDEN           = 1; // not in lsmaps/menu/vcall/etc., can just be changed to manually
 const float MAPINFO_FLAG_FORBIDDEN        = 2; // don't even allow the map by a cvar setting that allows hidden maps
index 2c8ebd5..bda48d5 100644 (file)
@@ -233,7 +233,8 @@ void mage_heal()
                {
                        pointparticles(particleeffectnum("healing_fx"), head.origin, '0 0 0', 1);
                        head.health = bound(0, head.health + (autocvar_g_monster_mage_heal_allies), head.max_health);
-                       WaypointSprite_UpdateHealth(head.sprite, head.health);
+                       if(!(head.spawnflags & MONSTERFLAG_INVINCIBLE))
+                               WaypointSprite_UpdateHealth(head.sprite, head.health);
                }
        }
 
@@ -335,12 +336,7 @@ void spawnfunc_monster_mage()
 {
        self.classname = "monster_mage";
 
-       self.monster_spawnfunc = spawnfunc_monster_mage;
-
-       if(Monster_CheckAppearFlags(self))
-               return;
-
-       if(!monster_initialize(MON_MAGE, FALSE)) { remove(self); return; }
+       if(!monster_initialize(MON_MAGE)) { remove(self); return; }
 }
 
 // compatibility with old spawns
@@ -397,7 +393,7 @@ float m_mage(float req)
                }
                case MR_PRECACHE:
                {
-                       precache_model ("models/monsters/mage.dpm");
+                       precache_model("models/monsters/mage.dpm");
                        precache_sound ("weapons/grenade_impact.wav");
                        precache_sound ("weapons/tagexp1.wav");
                        return TRUE;
@@ -415,7 +411,6 @@ float m_mage(float req)
        {
                case MR_PRECACHE:
                {
-                       precache_model ("models/monsters/mage.dpm");
                        return TRUE;
                }
        }
index 866782d..0e82fe8 100644 (file)
@@ -168,6 +168,7 @@ float shambler_attack(float attack_type)
                case MONSTER_ATTACK_RANGED:
                {
                        if(time >= self.shambler_lastattack) // shambler doesn't attack much
+                       if(self.flags & FL_ONGROUND)
                        if(random() <= 0.5 && vlen(self.enemy.origin - self.origin) <= 500)
                        {
                                self.frame = shambler_anim_smash;
@@ -196,12 +197,7 @@ void spawnfunc_monster_shambler()
 {
        self.classname = "monster_shambler";
 
-       self.monster_spawnfunc = spawnfunc_monster_shambler;
-
-       if(Monster_CheckAppearFlags(self))
-               return;
-
-       if(!monster_initialize(MON_SHAMBLER, FALSE)) { remove(self); return; }
+       if(!monster_initialize(MON_SHAMBLER)) { remove(self); return; }
 }
 
 float m_shambler(float req)
@@ -232,7 +228,7 @@ float m_shambler(float req)
                }
                case MR_PRECACHE:
                {
-                       precache_model ("models/monsters/shambler.mdl");
+                       precache_model("models/monsters/shambler.mdl");
                        return TRUE;
                }
        }
@@ -248,7 +244,6 @@ float m_shambler(float req)
        {
                case MR_PRECACHE:
                {
-                       precache_model ("models/monsters/shambler.mdl");
                        return TRUE;
                }
        }
index 0f46a96..7e35b8b 100644 (file)
@@ -37,7 +37,7 @@ void spider_web_explode()
                pointparticles(particleeffectnum("electro_impact"), self.origin, '0 0 0', 1);
                RadiusDamage(self, self.realowner, 0, 0, 25, world, 25, self.projectiledeathtype, world);
 
-               for(e = findradius(self.origin, 25); e; e = e.chain) if(e != self) if(e.takedamage && e.deadflag == DEAD_NO) if(e.health > 0)
+               for(e = findradius(self.origin, 25); e; e = e.chain) if(e != self) if(e.takedamage && e.deadflag == DEAD_NO) if(e.health > 0) if(e.monsterid != MON_SPIDER)
                        e.spider_slowness = time + (autocvar_g_monster_spider_attack_web_damagetime);
 
                remove(self);
@@ -119,12 +119,7 @@ void spawnfunc_monster_spider()
 {
        self.classname = "monster_spider";
 
-       self.monster_spawnfunc = spawnfunc_monster_spider;
-
-       if(Monster_CheckAppearFlags(self))
-               return;
-
-       if(!monster_initialize(MON_SPIDER, FALSE)) { remove(self); return; }
+       if(!monster_initialize(MON_SPIDER)) { remove(self); return; }
 }
 
 float m_spider(float req)
@@ -154,7 +149,7 @@ float m_spider(float req)
                }
                case MR_PRECACHE:
                {
-                       precache_model ("models/monsters/spider.dpm");
+                       precache_model("models/monsters/spider.dpm");
                        precache_sound ("weapons/electro_fire2.wav");
                        return TRUE;
                }
@@ -171,7 +166,6 @@ float m_spider(float req)
        {
                case MR_PRECACHE:
                {
-                       precache_model ("models/monsters/spider.dpm");
                        return TRUE;
                }
        }
index 2d72b4b..ed4962d 100644 (file)
@@ -96,12 +96,7 @@ void spawnfunc_monster_wyvern()
 {
        self.classname = "monster_wyvern";
 
-       self.monster_spawnfunc = spawnfunc_monster_wyvern;
-
-       if(Monster_CheckAppearFlags(self))
-               return;
-
-       if(!monster_initialize(MON_WYVERN, TRUE)) { remove(self); return; }
+       if(!monster_initialize(MON_WYVERN)) { remove(self); return; }
 }
 
 // compatibility with old spawns
@@ -136,7 +131,7 @@ float m_wyvern(float req)
                }
                case MR_PRECACHE:
                {
-                       precache_model ("models/monsters/wizard.mdl");
+                       precache_model("models/monsters/wizard.mdl");
                        return TRUE;
                }
        }
@@ -152,7 +147,6 @@ float m_wyvern(float req)
        {
                case MR_PRECACHE:
                {
-                       precache_model ("models/monsters/wizard.mdl");
                        return TRUE;
                }
        }
index e5155b8..25afaf7 100644 (file)
@@ -130,14 +130,7 @@ void spawnfunc_monster_zombie()
 {
        self.classname = "monster_zombie";
 
-       self.monster_spawnfunc = spawnfunc_monster_zombie;
-
-       self.spawnflags |= MONSTER_RESPAWN_DEATHPOINT;
-
-       if(Monster_CheckAppearFlags(self))
-               return;
-
-       if(!monster_initialize(MON_ZOMBIE, FALSE)) { remove(self); return; }
+       if(!monster_initialize(MON_ZOMBIE)) { remove(self); return; }
 }
 
 float m_zombie(float req)
@@ -163,6 +156,8 @@ float m_zombie(float req)
                        if(self.spawnflags & MONSTERFLAG_NORESPAWN)
                                self.spawnflags &= ~MONSTERFLAG_NORESPAWN; // zombies always respawn
 
+                       self.spawnflags |= MONSTER_RESPAWN_DEATHPOINT;
+
                        self.monster_loot = spawnfunc_item_health_medium;
                        self.monster_attackfunc = zombie_attack;
                        self.frame = zombie_anim_spawn;
@@ -174,7 +169,7 @@ float m_zombie(float req)
                }
                case MR_PRECACHE:
                {
-                       precache_model ("models/monsters/zombie.dpm");
+                       precache_model("models/monsters/zombie.dpm");
                        return TRUE;
                }
        }
@@ -190,7 +185,6 @@ float m_zombie(float req)
        {
                case MR_PRECACHE:
                {
-                       precache_model ("models/monsters/zombie.dpm");
                        return TRUE;
                }
        }
index 70802db..67e176c 100644 (file)
@@ -18,10 +18,6 @@ void register_monster(float id, float(float) func, float monsterflags, vector mi
        e.mins = min_s;
        e.maxs = max_s;
        e.model = strzone(strcat("models/monsters/", modelname));
-
-       #ifndef MENUQC
-       func(MR_PRECACHE);
-       #endif
 }
 float m_null(float dummy) { return 0; }
 void register_monsters_done()
@@ -46,4 +42,4 @@ entity get_monsterinfo(float id)
        if(m)
                return m;
        return dummy_monster_info;
-}
\ No newline at end of file
+}
index 924a728..be5accf 100644 (file)
@@ -1,7 +1,7 @@
-entity spawnmonster (string monster, float monster_id, entity spawnedby, entity own, vector orig, float respwn, float moveflag)
+entity spawnmonster (string monster, float monster_id, entity spawnedby, entity own, vector orig, float respwn, float invincible, float moveflag)
 {
        // ensure spawnfunc database is initialized
-       initialize_field_db();
+       //initialize_field_db();
 
        entity e = spawn();
        float i;
@@ -11,6 +11,9 @@ entity spawnmonster (string monster, float monster_id, entity spawnedby, entity
        if(!respwn)
                e.spawnflags |= MONSTERFLAG_NORESPAWN;
 
+       if(invincible)
+               e.spawnflags |= MONSTERFLAG_INVINCIBLE;
+
        setorigin(e, orig);
 
        if(monster == "random")
@@ -32,6 +35,7 @@ entity spawnmonster (string monster, float monster_id, entity spawnedby, entity
                        if(mon.netname == monster)
                        {
                                found = TRUE;
+                               monster_id = mon.monsterid; // we have the monster, old monster id is no longer required
                                break;
                        }
                }
@@ -59,9 +63,14 @@ entity spawnmonster (string monster, float monster_id, entity spawnedby, entity
                e.angles = spawnedby.angles;
        }
 
-       monster = strcat("$ spawnfunc_monster_", monster);
+       //monster = strcat("$ spawnfunc_monster_", monster);
+       
+       entity oldself = self;
+       self = e;
+       monster_initialize(monster_id);
+       self = oldself;
 
-       target_spawn_edit_entity(e, monster, world, world, world, world, world);
+       //target_spawn_edit_entity(e, monster, world, world, world, world, world);
 
        return e;
 }
index d3d3fcb..02d3086 100644 (file)
@@ -1 +1 @@
-entity spawnmonster (string monster, float monster_id, entity spawnedby, entity own, vector orig, float respwn, float moveflag);
+entity spawnmonster (string monster, float monster_id, entity spawnedby, entity own, vector orig, float respwn, float invincible, float moveflag);
index 927501e..34e7ceb 100644 (file)
@@ -3,30 +3,13 @@
 // =========================
 
 
-void monster_item_spawn()
-{
-       if(self.monster_loot)
-               self.monster_loot();
-
-       self.gravity = 1;
-       self.reset = SUB_Remove;
-       self.noalign = TRUE;
-       self.velocity = randomvec() * 175 + '0 0 325';
-       self.classname = "droppedweapon"; // hax
-       self.item_spawnshieldtime = time + 0.7;
-
-       SUB_SetFade(self, time + autocvar_g_monsters_drop_time, 1);
-}
-
 void monster_dropitem()
 {
        if(!self.candrop || !self.monster_loot)
                return;
 
        vector org = self.origin + ((self.mins + self.maxs) * 0.5);
-       entity e = spawn();
-
-       setorigin(e, org);
+       entity e = spawn(), oldself = self;
 
        e.monster_loot = self.monster_loot;
 
@@ -34,10 +17,20 @@ void monster_dropitem()
        MUTATOR_CALLHOOK(MonsterDropItem);
        e = other;
 
-       if(e)
+       if(e && e.monster_loot)
        {
-               e.think = monster_item_spawn;
-               e.nextthink = time + 0.3;
+               self = e;
+               e.noalign = TRUE;
+               e.monster_loot();
+               e.gravity = 1;
+               e.movetype = MOVETYPE_TOSS;
+               e.reset = SUB_Remove;
+               setorigin(e, org);
+               e.velocity = randomvec() * 175 + '0 0 325';
+               e.item_spawnshieldtime = time + 0.7;
+               e.classname = "droppedweapon"; // use weapon handling to remove it on touch
+               SUB_SetFade(e, time + autocvar_g_monsters_drop_time, 1);
+               self = oldself;
        }
 }
 
@@ -56,10 +49,10 @@ float monster_isvalidtarget (entity targ, entity ent)
        if(targ == ent)
                return FALSE; // don't attack ourselves
 
-       traceline(ent.origin, targ.origin, MOVE_NORMAL, ent);
+       //traceline(ent.origin, targ.origin, MOVE_NORMAL, ent);
 
-       if(trace_ent != targ)
-               return FALSE;
+       //if(trace_ent != targ)
+               //return FALSE;
 
        if(targ.vehicle_flags & VHF_ISVEHICLE)
        if(!((get_monsterinfo(ent.monsterid)).spawnflags & MON_FLAG_RANGED))
@@ -68,9 +61,6 @@ float monster_isvalidtarget (entity targ, entity ent)
        if(time < game_starttime)
                return FALSE; // monsters do nothing before the match has started
 
-       if(vlen(targ.origin - ent.origin) >= ent.target_range)
-               return FALSE; // enemy is too far away
-
        if(targ.takedamage == DAMAGE_NO)
                return FALSE; // enemy can't be damaged
 
@@ -107,7 +97,7 @@ float monster_isvalidtarget (entity targ, entity ent)
        if (targ.freezetag_frozen)
                return FALSE; // ignore frozen
 
-       if(autocvar_g_monsters_target_infront || ent.spawnflags & MONSTERFLAG_INFRONT)
+       if(autocvar_g_monsters_target_infront || (ent.spawnflags & MONSTERFLAG_INFRONT))
        if(ent.enemy != targ)
        {
                float dot;
@@ -128,6 +118,7 @@ entity FindTarget (entity ent)
 
        entity head, closest_target = world;
        head = findradius(ent.origin, ent.target_range);
+       //head = WarpZone_FindRadius(ent.origin, ent.target_range, TRUE);
 
        while(head) // find the closest acceptable target to pass to
        {
@@ -136,12 +127,16 @@ entity FindTarget (entity ent)
                {
                        // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
                        vector head_center = CENTER_OR_VIEWOFS(head);
+                       //vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
                        vector ent_center = CENTER_OR_VIEWOFS(ent);
 
-                       //if(ctf_CheckPassDirection(head_center, ent_center, ent.v_angle, head.WarpZone_findradius_nearest))
+                       traceline(ent_center, head_center, MOVE_NORMAL, ent);
+
+                       if(trace_ent == head)
                        if(closest_target)
                        {
                                vector closest_target_center = CENTER_OR_VIEWOFS(closest_target);
+                               //vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
                                if(vlen(ent_center - head_center) < vlen(ent_center - closest_target_center))
                                        { closest_target = head; }
                        }
@@ -279,7 +274,10 @@ void UpdateMonsterSounds()
 
 void MonsterSound(.string samplefield, float sound_delay, float delaytoo, float chan)
 {
-       if(delaytoo && time < self.msound_delay)
+       if(!autocvar_g_monsters_sounds) { return; }
+
+       if(delaytoo)
+       if(time < self.msound_delay)
                return; // too early
        GlobalSound(self.samplefield, chan, VOICETYPE_PLAYERSOUND);
 
@@ -357,14 +355,21 @@ float Monster_CanRespawn(entity ent)
        return TRUE;
 }
 
+float monster_initialize(float mon_id);
+void monster_respawn()
+{
+       // is this function really needed?
+       monster_initialize(self.monsterid);
+}
+
 void Monster_Fade ()
 {
        if(Monster_CanRespawn(self))
        {
                self.spawnflags |= MONSTERFLAG_RESPAWNED;
-               self.think = self.monster_spawnfunc;
+               self.think = monster_respawn;
                self.nextthink = time + self.respawntime;
-               self.ltime = 0;
+               self.monster_lifetime = 0;
                self.deadflag = DEAD_RESPAWNING;
                if(self.spawnflags & MONSTER_RESPAWN_DEATHPOINT)
                {
@@ -383,9 +388,6 @@ void Monster_Fade ()
                // number of monsters spawned with mobspawn command
                totalspawned -= 1;
 
-               if(IS_CLIENT(self.realowner))
-                       self.realowner.monstercount -= 1;
-
                SUB_SetFade(self, time + 3, 1);
        }
 }
@@ -472,10 +474,40 @@ vector monster_pickmovetarget(entity targ)
        // enemy is always preferred target
        if(self.enemy)
        {
-               makevectors(self.angles);
+               vector targ_origin = ((self.enemy.absmin + self.enemy.absmax) * 0.5);
+               targ_origin = WarpZone_RefSys_TransformOrigin(self.enemy, self, targ_origin); // origin of target as seen by the monster (us)
+               WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
+               
+               if((self.enemy == world)
+                       || (self.enemy.deadflag != DEAD_NO || self.enemy.health < 1)
+                       || (self.enemy.freezetag_frozen)
+                       || (self.enemy.flags & FL_NOTARGET)
+                       || (self.enemy.alpha < 0.5)
+                       || (self.enemy.takedamage == DAMAGE_NO)
+                       || (vlen(self.origin - targ_origin) > self.target_range)
+                       || ((trace_fraction < 1) && (trace_ent != self.enemy)))
+                       //|| (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit)) // TODO: chase timelimit?
+               {
+                       self.enemy = world;
+                       self.pass_distance = 0;
+               }
+               
+               if(self.enemy)
+               {
+                       /*WarpZone_TrailParticles(world, particleeffectnum("red_pass"), self.origin, targ_origin);
+                       print("Trace origin: ", vtos(targ_origin), "\n");
+                       print("Target origin: ", vtos(self.enemy.origin), "\n");
+                       print("My origin: ", vtos(self.origin), "\n"); */
+                       
+                       self.monster_movestate = MONSTER_MOVE_ENEMY;
+                       self.last_trace = time + 1.2;
+                       return targ_origin;
+               }
+       
+               /*makevectors(self.angles);
                self.monster_movestate = MONSTER_MOVE_ENEMY;
                self.last_trace = time + 1.2;
-               return self.enemy.origin;
+               return self.enemy.origin; */
        }
 
        switch(self.monster_moveflags)
@@ -503,34 +535,70 @@ vector monster_pickmovetarget(entity targ)
                {
                        vector pos;
                        self.monster_movestate = MONSTER_MOVE_WANDER;
-                       self.last_trace = time + 2;
-
-                       self.angles_y = rint(random() * 500);
-                       makevectors(self.angles);
-                       pos = self.origin + v_forward * 600;
-
-                       if(self.flags & FL_FLY || self.flags & FL_SWIM)
-                       if(self.spawnflags & MONSTERFLAG_FLY_VERTICAL)
-                       {
-                               pos_z = random() * 200;
-                               if(random() >= 0.5)
-                                       pos_z *= -1;
-                       }
 
                        if(targ)
                        {
                                self.last_trace = time + 0.5;
                                pos = targ.origin;
                        }
+                       else
+                       {
+                               self.last_trace = time + self.wander_delay;
+
+                               self.angles_y = rint(random() * 500);
+                               makevectors(self.angles);
+                               pos = self.origin + v_forward * self.wander_distance;
+
+                               if(((self.flags & FL_FLY) && (self.spawnflags & MONSTERFLAG_FLY_VERTICAL)) || (self.flags & FL_SWIM))
+                               {
+                                       pos_z = random() * 200;
+                                       if(random() >= 0.5)
+                                               pos_z *= -1;
+                               }
+                       }
 
                        return pos;
                }
        }
 }
 
+void monster_CalculateVelocity(entity mon, vector to, vector from, float turnrate, float movespeed)
+{
+       float current_distance = vlen((('1 0 0' * to_x) + ('0 1 0' * to_y)) - (('1 0 0' * from_x) + ('0 1 0' * from_y))); // for the sake of this check, exclude Z axis
+       float initial_height = 0; //min(50, (targ_distance * tanh(20)));
+       float current_height = (initial_height * min(1, (current_distance / self.pass_distance)));
+       //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
+
+       vector targpos;
+       if(current_height) // make sure we can actually do this arcing path
+       {
+               targpos = (to + ('0 0 1' * current_height));
+               WarpZone_TraceLine(mon.origin, targpos, MOVE_NOMONSTERS, mon);
+               if(trace_fraction < 1)
+               {
+                       //print("normal arc line failed, trying to find new pos...");
+                       WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, mon);
+                       targpos = (trace_endpos + '0 0 -10');
+                       WarpZone_TraceLine(mon.origin, targpos, MOVE_NOMONSTERS, mon);
+                       if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
+                       /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
+               }
+       }
+       else { targpos = to; }
+
+       //mon.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
+
+       vector desired_direction = normalize(targpos - from);
+       if(turnrate) { mon.velocity = (normalize(normalize(mon.velocity) + (desired_direction * 50)) * movespeed); }
+       else { mon.velocity = (desired_direction * movespeed); }
+
+       //mon.steerto = steerlib_attract2(targpos, 0.5, 500, 0.95);
+       //mon.angles = vectoangles(mon.velocity);
+}
+
 void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_run, float manim_walk, float manim_idle)
 {
-       fixedmakevectors(self.angles);
+       //fixedmakevectors(self.angles);
 
        if(self.target2)
                self.goalentity = find(world, targetname, self.target2);
@@ -581,7 +649,7 @@ void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_
        monster_speed_run = runspeed;
        monster_speed_walk = walkspeed;
 
-       if(MUTATOR_CALLHOOK(MonsterMove) || gameover || (round_handler_IsActive() && !round_handler_IsRoundStarted()) || time < game_starttime || (autocvar_g_campaign && !campaign_bots_may_start) || time < self.spawn_time)
+       if(MUTATOR_CALLHOOK(MonsterMove) || gameover || self.draggedby != world || (round_handler_IsActive() && !round_handler_IsRoundStarted()) || time < game_starttime || (autocvar_g_campaign && !campaign_bots_may_start) || time < self.spawn_time)
        {
                runspeed = walkspeed = 0;
                if(time >= self.spawn_time)
@@ -605,22 +673,23 @@ void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_
        if(DIFF_TEAM(self.monster_owner, self))
                self.monster_owner = world;
 
-       if(self.enemy && self.enemy.health < 1)
-               self.enemy = world; // enough!
-
        if(time >= self.last_enemycheck)
        {
-               if(!monster_isvalidtarget(self.enemy, self))
-                       self.enemy = world;
-
                if(!self.enemy)
                {
                        self.enemy = FindTarget(self);
                        if(self.enemy)
+                       {
+                               WarpZone_RefSys_Copy(self.enemy, self);
+                               WarpZone_RefSys_AddInverse(self.enemy, self); // wz1^-1 ... wzn^-1 receiver
+                               self.moveto = WarpZone_RefSys_TransformOrigin(self.enemy, self, (0.5 * (self.enemy.absmin + self.enemy.absmax)));
+                               
+                               self.pass_distance = vlen((('1 0 0' * self.enemy.origin_x) + ('0 1 0' * self.enemy.origin_y)) - (('1 0 0' *  self.origin_x) + ('0 1 0' *  self.origin_y)));
                                MonsterSound(monstersound_sight, 0, FALSE, CH_VOICE);
+                       }
                }
 
-               self.last_enemycheck = time + 0.5;
+               self.last_enemycheck = time + 1; // check for enemies every second
        }
 
        if(self.state == MONSTER_STATE_ATTACK_MELEE && time >= self.attack_finished_single)
@@ -633,49 +702,32 @@ void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_
        if(!self.enemy)
                MonsterSound(monstersound_idle, 7, TRUE, CH_VOICE);
 
-       if(self.state != MONSTER_STATE_ATTACK_LEAP && self.state != MONSTER_STATE_ATTACK_MELEE)
-               self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95);
-
        if(self.state == MONSTER_STATE_ATTACK_LEAP && (self.flags & FL_ONGROUND))
        {
                self.state = 0;
                self.touch = MonsterTouch;
        }
 
-       //self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95);
-
-       float turny = 0;
-       vector real_angle = vectoangles(self.steerto) - self.angles;
-
-       if(self.state != MONSTER_STATE_ATTACK_LEAP && self.state != MONSTER_STATE_ATTACK_MELEE)
-               turny = 20;
-
-       if(self.flags & FL_SWIM)
-               turny = vlen(self.angles - self.moveto);
-
-       if(turny)
-       {
-               turny = bound(turny * -1, shortangle_f(real_angle_y, self.angles_y), turny);
-               self.angles_y += turny;
-       }
-
        if(self.state == MONSTER_STATE_ATTACK_MELEE)
                self.moveto = self.origin;
 
        if(self.enemy && self.enemy.vehicle)
                runspeed = 0;
 
-       if(((self.flags & FL_FLY) || (self.flags & FL_SWIM)) && self.spawnflags & MONSTERFLAG_FLY_VERTICAL)
-               v_forward = normalize(self.moveto - self.origin);
-       else
+       if(!(((self.flags & FL_FLY) && (self.spawnflags & MONSTERFLAG_FLY_VERTICAL)) || (self.flags & FL_SWIM)))
+               //v_forward = normalize(self.moveto - self.origin);
+       //else
                self.moveto_z = self.origin_z;
 
        if(vlen(self.origin - self.moveto) > 64)
        {
-               if(self.flags & FL_FLY || self.flags & FL_SWIM)
+               if((self.flags & FL_ONGROUND) || ((self.flags & FL_FLY) || (self.flags & FL_SWIM)))
+                       monster_CalculateVelocity(self, self.moveto, self.origin, TRUE, ((self.enemy) ? runspeed : walkspeed));
+               
+               /*&if(self.flags & FL_FLY || self.flags & FL_SWIM)
                        movelib_move_simple(v_forward, ((self.enemy) ? runspeed : walkspeed), 0.6);
                else
-                       movelib_move_simple_gravity(v_forward, ((self.enemy) ? runspeed : walkspeed), 0.6);
+                       movelib_move_simple_gravity(v_forward, ((self.enemy) ? runspeed : walkspeed), 0.6); */
 
                if(time > self.pain_finished)
                if(time > self.attack_finished_single)
@@ -698,6 +750,18 @@ void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_
                if (vlen(self.velocity) <= 30)
                        self.frame = manim_idle;
        }
+       
+       self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95);
+       
+       vector real_angle = vectoangles(self.steerto) - self.angles;
+       float turny = 25;
+       if(self.state == MONSTER_STATE_ATTACK_MELEE)
+               turny = 0;
+       if(turny)
+       {
+               turny = bound(turny * -1, shortangle_f(real_angle_y, self.angles_y), turny);
+               self.angles_y += turny;
+       }
 
        monster_checkattack(self, self.enemy);
 }
@@ -723,8 +787,8 @@ void monster_dead_think()
 
        CSQCMODEL_AUTOUPDATE();
 
-       if(self.ltime != 0)
-       if(time >= self.ltime)
+       if(self.monster_lifetime != 0)
+       if(time >= self.monster_lifetime)
        {
                Monster_Fade();
                return;
@@ -740,16 +804,17 @@ void monsters_setstatus()
 void Monster_Appear()
 {
        self.enemy = activator;
-       self.spawnflags &= ~MONSTERFLAG_APPEAR;
-       self.monster_spawnfunc();
+       self.spawnflags &= ~MONSTERFLAG_APPEAR; // otherwise, we get an endless loop
+       monster_initialize(self.monsterid);
 }
 
-float Monster_CheckAppearFlags(entity ent)
+float Monster_CheckAppearFlags(entity ent, float monster_id)
 {
        if(!(ent.spawnflags & MONSTERFLAG_APPEAR))
                return FALSE;
 
        ent.think = func_null;
+       ent.monsterid = monster_id; // set so this monster is properly registered (otherwise, normal initialization is used)
        ent.nextthink = 0;
        ent.use = Monster_Appear;
        ent.flags = FL_MONSTER; // set so this monster can get butchered
@@ -783,11 +848,9 @@ void monsters_corpse_damage (entity inflictor, entity attacker, float damage, fl
                // number of monsters spawned with mobspawn command
                totalspawned -= 1;
 
-               if(IS_CLIENT(self.realowner))
-                       self.realowner.monstercount -= 1;
-
                self.think = SUB_Remove;
                self.nextthink = time + 0.1;
+               self.event_damage = func_null;
        }
 }
 
@@ -795,7 +858,7 @@ void monster_die(entity attacker, float gibbed)
 {
        self.think = monster_dead_think;
        self.nextthink = time;
-       self.ltime = time + 5;
+       self.monster_lifetime = time + 5;
 
        monster_dropitem();
 
@@ -812,15 +875,12 @@ void monster_die(entity attacker, float gibbed)
        {
                // number of monsters spawned with mobspawn command
                totalspawned -= 1;
-
-               if(IS_CLIENT(self.realowner))
-                       self.realowner.monstercount -= 1;
        }
 
        if(self.candrop && self.weapon)
                W_ThrowNewWeapon(self, self.weapon, 0, self.origin, randomvec() * 150 + '0 0 325');
 
-       self.event_damage       = monsters_corpse_damage;
+       self.event_damage       = ((gibbed) ? func_null : monsters_corpse_damage);
        self.solid                      = SOLID_CORPSE;
        self.takedamage         = DAMAGE_AIM;
        self.deadflag           = DEAD_DEAD;
@@ -832,7 +892,7 @@ void monster_die(entity attacker, float gibbed)
        self.state                      = 0;
        self.attack_finished_single = 0;
 
-       if(!(self.flags & FL_FLY))
+       if(!((self.flags & FL_FLY) || (self.flags & FL_SWIM)))
                self.velocity = '0 0 0';
 
        MON_ACTION(self.monsterid, MR_DEATH);
@@ -840,12 +900,18 @@ void monster_die(entity attacker, float gibbed)
 
 void monsters_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
 {
+       if((self.spawnflags & MONSTERFLAG_INVINCIBLE) && deathtype != DEATH_KILL)
+               return;
+
        if(time < self.pain_finished && deathtype != DEATH_KILL)
                return;
 
        if(time < self.spawnshieldtime && deathtype != DEATH_KILL)
                return;
 
+       if(deathtype == DEATH_FALL && self.draggedby != world)
+               return;
+
        vector v;
        float take, save;
 
@@ -902,26 +968,43 @@ void monsters_damage (entity inflictor, entity attacker, float damage, float dea
        }
 }
 
-void monster_setupcolors()
+void monster_setupcolors(entity mon)
 {
-       if(IS_PLAYER(self.monster_owner))
-               self.colormap = self.monster_owner.colormap;
-       else if(teamplay && self.team)
-               self.colormap = 1024 + (self.team - 1) * 17;
+       if(IS_PLAYER(mon.monster_owner))
+               mon.colormap = mon.monster_owner.colormap;
+       else if(teamplay && mon.team)
+               mon.colormap = 1024 + (mon.team - 1) * 17;
        else
        {
-               if(self.monster_skill <= MONSTER_SKILL_EASY)
-                       self.colormap = 1029;
-               else if(self.monster_skill <= MONSTER_SKILL_MEDIUM)
-                       self.colormap = 1027;
-               else if(self.monster_skill <= MONSTER_SKILL_HARD)
-                       self.colormap = 1038;
-               else if(self.monster_skill <= MONSTER_SKILL_INSANE)
-                       self.colormap = 1028;
-               else if(self.monster_skill <= MONSTER_SKILL_NIGHTMARE)
-                       self.colormap = 1032;
+               if(mon.monster_skill <= MONSTER_SKILL_EASY)
+                       mon.colormap = 1029;
+               else if(mon.monster_skill <= MONSTER_SKILL_MEDIUM)
+                       mon.colormap = 1027;
+               else if(mon.monster_skill <= MONSTER_SKILL_HARD)
+                       mon.colormap = 1038;
+               else if(mon.monster_skill <= MONSTER_SKILL_INSANE)
+                       mon.colormap = 1028;
+               else if(mon.monster_skill <= MONSTER_SKILL_NIGHTMARE)
+                       mon.colormap = 1032;
                else
-                       self.colormap = 1024;
+                       mon.colormap = 1024;
+       }
+}
+
+void monster_changeteam(entity ent, float newteam)
+{
+       if(!teamplay) { return; }
+       
+       ent.team = newteam;
+       ent.monster_attack = TRUE; // new team, activate attacking
+       monster_setupcolors(ent);
+       
+       if(ent.sprite)
+       {
+               WaypointSprite_UpdateTeamRadar(ent.sprite, RADARICON_DANGER, ((newteam) ? Team_ColorRGB(newteam) : '1 0 0'));
+
+               ent.sprite.team = newteam;
+               ent.sprite.SendFlags |= 1;
        }
 }
 
@@ -930,8 +1013,8 @@ void monster_think()
        self.think = monster_think;
        self.nextthink = self.ticrate;
 
-       if(self.ltime)
-       if(time >= self.ltime)
+       if(self.monster_lifetime)
+       if(time >= self.monster_lifetime)
        {
                Damage(self, self, self, self.health + self.max_health, DEATH_KILL, self.origin, self.origin);
                return;
@@ -965,6 +1048,9 @@ float monster_spawn()
        if(!self.attack_range)
                self.attack_range = autocvar_g_monsters_attack_range;
 
+       if(!self.wander_delay) { self.wander_delay = 2; }
+       if(!self.wander_distance) { self.wander_distance = 600; }
+
        precache_monstersounds();
        UpdateMonsterSounds();
 
@@ -974,8 +1060,11 @@ float monster_spawn()
        MonsterSound(monstersound_spawn, 0, FALSE, CH_VOICE);
 
        WaypointSprite_Spawn(M_NAME(self.monsterid), 0, 1024, self, '0 0 1' * (self.maxs_z + 15), world, self.team, self, sprite, TRUE, RADARICON_DANGER, ((self.team) ? Team_ColorRGB(self.team) : '1 0 0'));
-       WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
-       WaypointSprite_UpdateHealth(self.sprite, self.health);
+       if(!(self.spawnflags & MONSTERFLAG_INVINCIBLE))
+       {
+               WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
+               WaypointSprite_UpdateHealth(self.sprite, self.health);
+       }
 
        self.think = monster_think;
        self.nextthink = time + self.ticrate;
@@ -986,10 +1075,11 @@ float monster_spawn()
        return TRUE;
 }
 
-float monster_initialize(float mon_id, float nodrop)
+float monster_initialize(float mon_id)
 {
-       if(!autocvar_g_monsters)
-               return FALSE;
+       if(!autocvar_g_monsters) { return FALSE; }
+       if(!(self.spawnflags & MONSTERFLAG_RESPAWNED)) { MON_ACTION(mon_id, MR_PRECACHE); }
+       if(Monster_CheckAppearFlags(self, mon_id)) { return TRUE; } // return true so the monster isn't removed
 
        entity mon = get_monsterinfo(mon_id);
 
@@ -1005,11 +1095,11 @@ float monster_initialize(float mon_id, float nodrop)
                self.team = 0;
 
        if(!(self.spawnflags & MONSTERFLAG_SPAWNED)) // naturally spawned monster
-       if(!(self.spawnflags & MONSTERFLAG_RESPAWNED))
+       if(!(self.spawnflags & MONSTERFLAG_RESPAWNED)) // don't count re-spawning monsters either
                monsters_total += 1;
 
        setmodel(self, mon.model);
-       setsize(self, mon.mins, mon.maxs);
+       //setsize(self, mon.mins, mon.maxs);
        self.flags                              = FL_MONSTER;
        self.takedamage                 = DAMAGE_AIM;
        self.bot_attack                 = TRUE;
@@ -1035,14 +1125,20 @@ float monster_initialize(float mon_id, float nodrop)
        self.candrop                    = TRUE;
        self.view_ofs                   = '0 0 1' * (self.maxs_z * 0.5);
        self.oldtarget2                 = self.target2;
+       self.pass_distance              = 0;
        self.deadflag                   = DEAD_NO;
-       self.scale                              = 1;
-       self.noalign                    = nodrop;
+       self.noalign                    = ((mon.spawnflags & MONSTER_TYPE_FLY) || (mon.spawnflags & MONSTER_TYPE_SWIM));
        self.spawn_time                 = time;
        self.spider_slowness    = 0;
        self.gravity                    = 1;
        self.dphitcontentsmask  = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP;
 
+       if(!self.scale)
+               self.scale = 1;
+
+       if(autocvar_g_monsters_edit)
+               self.grab = 1; // owner may carry their monster
+
        if(autocvar_g_fullbrightplayers)
                self.effects |= EF_FULLBRIGHT;
 
@@ -1059,7 +1155,10 @@ float monster_initialize(float mon_id, float nodrop)
        }
 
        if(mon.spawnflags & MONSTER_SIZE_BROKEN)
-               self.scale = 1.3;
+       if(!(self.spawnflags & MONSTERFLAG_RESPAWNED))
+               self.scale *= 1.3;
+               
+       setsize(self, mon.mins * self.scale, mon.maxs * self.scale);
 
        if(!self.ticrate)
                self.ticrate = autocvar_g_monsters_think_delay;
@@ -1089,7 +1188,7 @@ float monster_initialize(float mon_id, float nodrop)
                return FALSE;
 
        if(!(self.spawnflags & MONSTERFLAG_RESPAWNED))
-               monster_setupcolors();
+               monster_setupcolors(self);
 
        CSQCMODEL_AUTOINIT();
 
index 6533120..239db02 100644 (file)
@@ -2,7 +2,6 @@
 .float monster_attack;
 
 .entity monster_owner; // new monster owner entity, fixes non-solid monsters
-.float monstercount; // per player monster count
 
 .float stat_monsters_killed; // stats
 .float stat_monsters_total;
@@ -11,6 +10,11 @@ float monsters_killed;
 void monsters_setstatus(); // monsters.qc
 .float monster_moveflags; // checks where to move when not attacking
 
+.float wander_delay;
+.float wander_distance;
+
+.float monster_lifetime;
+
 .float spider_slowness; // special spider timer
 
 void monster_remove(entity mon); // removes a monster
@@ -69,11 +73,10 @@ const float MONSTERFLAG_NORESPAWN = 4;
 const float MONSTERFLAG_FLY_VERTICAL = 8; // fly/swim vertically
 const float MONSTERFLAG_INFRONT = 32; // only check for enemies infront of us
 const float MONSTERFLAG_MINIBOSS = 64; // monster spawns as mini-boss (also has a chance of naturally becoming one)
+const float MONSTERFLAG_INVINCIBLE = 128; // monster doesn't take damage (may be used for map objects & temporary monsters)
 const float MONSTERFLAG_SPAWNED = 16384; // flag for spawned monsters
 const float MONSTERFLAG_RESPAWNED = 32768; // flag for re-spawned monsters
 
-.void() monster_spawnfunc;
-
 .float monster_movestate; // used to tell what the monster is currently doing
 const float MONSTER_MOVE_OWNER = 1; // monster will move to owner if in range, or stand still
 const float MONSTER_MOVE_WANDER = 2; // monster will ignore owner & wander around
index 674c95b..ee7d24d 100644 (file)
@@ -1221,6 +1221,7 @@ float autocvar_g_physical_items_damageforcescale;
 float autocvar_g_physical_items_reset;
 float autocvar_g_monsters;
 float autocvar_g_monsters_edit;
+float autocvar_g_monsters_sounds;
 float autocvar_g_monsters_think_delay;
 float autocvar_g_monsters_max;
 float autocvar_g_monsters_max_perplayer;
@@ -1244,7 +1245,10 @@ float autocvar_g_touchexplode_damage;
 float autocvar_g_touchexplode_edgedamage;
 float autocvar_g_touchexplode_force;
 float autocvar_g_invasion_round_timelimit;
-#define autocvar_g_invasion_round_limit cvar("g_invasion_round_limit")
+float autocvar_g_invasion_teams;
+float autocvar_g_invasion_team_spawns;
+float autocvar_g_invasion_spawnpoint_spawn_delay;
+#define autocvar_g_invasion_point_limit cvar("g_invasion_point_limit")
 float autocvar_g_invasion_warmup;
 float autocvar_g_invasion_monster_count;
 float autocvar_g_invasion_zombies_only;
index f3d675f..c36033a 100644 (file)
@@ -98,7 +98,7 @@ void W_HitPlotAnalysis(entity player, vector screenforward, vector screenright,
 
                org = player.origin + player.view_ofs;
                traceline_antilag_force(player, org, org + screenforward * MAX_SHOT_DISTANCE, MOVE_NORMAL, player, lag);
-               if(IS_CLIENT(trace_ent))
+               if(IS_CLIENT(trace_ent) || (trace_ent.flags & FL_MONSTER))
                {
                        antilag_takeback(trace_ent, time - lag);
                        hitplot = W_HitPlotNormalizedUntransform(org, trace_ent, screenforward, screenright, screenup, trace_endpos);
index f123367..7842448 100644 (file)
@@ -267,7 +267,7 @@ void ClientCommand_mobspawn(float request, float argc)
                {
                        entity e;
                        string tospawn;
-                       float moveflag;
+                       float moveflag, monstercount = 0;
                        
                        moveflag = (argv(2) ? stof(argv(2)) : 1); // follow owner if not defined
                        tospawn = strtolower(argv(1));
@@ -278,14 +278,21 @@ void ClientCommand_mobspawn(float request, float argc)
                                return;
                        }
                        
+                       FOR_EACH_MONSTER(e)
+                       {
+                               if(e.realowner == self)
+                                       ++monstercount;
+                       }
+                       
                        if(autocvar_g_monsters_max <= 0 || autocvar_g_monsters_max_perplayer <= 0) { sprint(self, "Monster spawning is disabled.\n"); return; }
                        else if(!IS_PLAYER(self)) { sprint(self, "You can't spawn monsters while spectating.\n"); return; }
                        else if(MUTATOR_CALLHOOK(AllowMobSpawning)) { sprint(self, "Monster spawning is currently disabled by a mutator.\n"); return; }
                        else if(!autocvar_g_monsters) { Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_MONSTERS_DISABLED); return; }
                        else if(self.vehicle) { sprint(self, "You can't spawn monsters while driving a vehicle.\n"); return; }
+                       else if(self.freezetag_frozen) { sprint(self, "You can't spawn monsters while frozen.\n"); return; }
                        else if(autocvar_g_campaign) { sprint(self, "You can't spawn monsters in campaign mode.\n"); return; }
                        else if(self.deadflag != DEAD_NO) { sprint(self, "You can't spawn monsters while dead.\n"); return; }
-                       else if(self.monstercount >= autocvar_g_monsters_max_perplayer) { sprint(self, "You have spawned too many monsters, kill some before trying to spawn any more.\n"); return; }
+                       else if(monstercount >= autocvar_g_monsters_max_perplayer) { sprint(self, "You have spawned too many monsters, kill some before trying to spawn any more.\n"); return; }
                        else if(totalspawned >= autocvar_g_monsters_max) { sprint(self, "The global maximum monster count has been reached, kill some before trying to spawn any more.\n"); return; }
                        else if(tospawn != "")
                        {
@@ -302,16 +309,15 @@ void ClientCommand_mobspawn(float request, float argc)
                                        }
                                }
 
-                               if(found)
+                               if(found || tospawn == "random")
                                {
-                                       self.monstercount += 1;
                                        totalspawned += 1;
                                
                                        makevectors(self.v_angle);
                                        WarpZone_TraceBox (CENTER_OR_VIEWOFS(self), PL_MIN, PL_MAX, CENTER_OR_VIEWOFS(self) + v_forward * 150, TRUE, self);
                                        //WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * 150, MOVE_NORMAL, self);
                                
-                                       e = spawnmonster(tospawn, 0, self, self, trace_endpos, FALSE, moveflag);
+                                       e = spawnmonster(tospawn, 0, self, self, trace_endpos, FALSE, FALSE, moveflag);
                                        
                                        sprint(self, strcat("Spawned ", e.monster_name, "\n"));
                                        
index 694b157..c0da029 100644 (file)
@@ -157,9 +157,6 @@ void GameCommand_mobbutcher(float request)
                                ++removed_count;
                        }
 
-                       FOR_EACH_PLAYER(head)
-                               head.monstercount = 0;
-
                        monsters_total = 0; // reset stats?
                        monsters_killed = 0;
 
index 5acc703..8448a21 100644 (file)
@@ -1,8 +1,12 @@
-void invasion_spawnpoint()
+void spawnfunc_invasion_spawnpoint()
 {
        if(!g_invasion) { remove(self); return; }
 
        self.classname = "invasion_spawnpoint";
+
+       if(autocvar_g_invasion_zombies_only) // precache only if it hasn't been already
+       if(self.monsterid)
+               MON_ACTION(self.monsterid, MR_PRECACHE);
 }
 
 float invasion_PickMonster(float supermonster_count)
@@ -21,7 +25,7 @@ float invasion_PickMonster(float supermonster_count)
                if((mon.spawnflags & MONSTER_TYPE_FLY) || (mon.spawnflags & MONSTER_TYPE_SWIM) || ((mon.spawnflags & MON_FLAG_SUPERMONSTER) && supermonster_count >= 1))
                        continue; // flying/swimming monsters not yet supported
 
-               RandomSelection_Add(world, i, "", 1, 1);
+               RandomSelection_Add(world, i, string_null, 1, 1);
        }
 
        return RandomSelection_chosen_float;
@@ -34,7 +38,10 @@ entity invasion_PickSpawn()
        RandomSelection_Init();
 
        for(e = world;(e = find(e, classname, "invasion_spawnpoint")); )
-               RandomSelection_Add(e, 0, string_null, 1, 1);
+       {
+               RandomSelection_Add(e, 0, string_null, 1, ((time >= e.spawnshieldtime) ? 0.2 : 1)); // give recently used spawnpoints a very low rating
+               e.spawnshieldtime = time + autocvar_g_invasion_spawnpoint_spawn_delay;
+       }
 
        return RandomSelection_chosen_ent;
 }
@@ -47,11 +54,52 @@ void invasion_SpawnChosenMonster(float mon)
 
        if(spawn_point == world)
        {
-               dprint("Warning: couldn't find any invasion_spawnpoint spawnpoints, no monsters will spawn!\n");
-               return;
+               dprint("Warning: couldn't find any invasion_spawnpoint spawnpoints, attempting to spawn monsters in random locations\n");
+               entity e = spawn();
+               setsize(e, (get_monsterinfo(mon)).mins, (get_monsterinfo(mon)).maxs);
+
+               if(MoveToRandomMapLocation(e, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256))
+                       monster = spawnmonster("", mon, world, world, e.origin, FALSE, FALSE, 2);
+               else return;
+
+               e.think = SUB_Remove;
+               e.nextthink = time + 0.1;
+       }
+       else
+               monster = spawnmonster("", ((spawn_point.monsterid) ? spawn_point.monsterid : mon), spawn_point, spawn_point, spawn_point.origin, FALSE, FALSE, 2);
+       
+       if(spawn_point) monster.target2 = spawn_point.target2;
+       monster.spawnshieldtime = time;
+       if(spawn_point && spawn_point.target_range) monster.target_range = spawn_point.target_range;
+       
+       if(teamplay)
+       if(spawn_point && spawn_point.team && inv_monsters_perteam[spawn_point.team] > 0)
+               monster.team = spawn_point.team;
+       else
+       {
+               RandomSelection_Init();
+               if(inv_monsters_perteam[NUM_TEAM_1] > 0) RandomSelection_Add(world, NUM_TEAM_1, string_null, 1, 1);
+               if(inv_monsters_perteam[NUM_TEAM_2] > 0) RandomSelection_Add(world, NUM_TEAM_2, string_null, 1, 1);
+               if(invasion_teams >= 3) if(inv_monsters_perteam[NUM_TEAM_3] > 0) { RandomSelection_Add(world, NUM_TEAM_3, string_null, 1, 1); }
+               if(invasion_teams >= 4) if(inv_monsters_perteam[NUM_TEAM_4] > 0) { RandomSelection_Add(world, NUM_TEAM_4, string_null, 1, 1); }
+               
+               monster.team = RandomSelection_chosen_float;
        }
+       
+       if(teamplay)
+       {
+               monster_setupcolors(monster);
+       
+               if(monster.sprite)
+               {
+                       WaypointSprite_UpdateTeamRadar(monster.sprite, RADARICON_DANGER, ((monster.team) ? Team_ColorRGB(monster.team) : '1 0 0'));
 
-       monster = spawnmonster("", mon, spawn_point, spawn_point, spawn_point.origin, FALSE, 2);
+                       monster.sprite.team = 0;
+                       monster.sprite.SendFlags |= 1;
+               }
+       }
+       
+       monster.monster_attack = FALSE; // it's the player's job to kill all the monsters
 
        if(inv_roundcnt >= inv_maxrounds)
                monster.spawnflags |= MONSTERFLAG_MINIBOSS; // last round spawns minibosses
@@ -72,28 +120,31 @@ float Invasion_CheckWinner()
                FOR_EACH_MONSTER(head)
                        monster_remove(head);
 
-               if(inv_roundcnt >= inv_maxrounds)
-               {
-                       NextLevel();
-                       return 1;
-               }
-
                Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
                Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
                round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
                return 1;
        }
 
-       float total_alive_monsters = 0, supermonster_count = 0;
+       float total_alive_monsters = 0, supermonster_count = 0, red_alive = 0, blue_alive = 0, yellow_alive = 0, pink_alive = 0;;
 
        FOR_EACH_MONSTER(head) if(head.health > 0)
        {
                if((get_monsterinfo(head.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER)
                        ++supermonster_count;
                ++total_alive_monsters;
+
+               if(teamplay)
+               switch(head.team)
+               {
+                       case NUM_TEAM_1: ++red_alive; break;
+                       case NUM_TEAM_2: ++blue_alive; break;
+                       case NUM_TEAM_3: ++yellow_alive; break;
+                       case NUM_TEAM_4: ++pink_alive; break;
+               }
        }
 
-       if((total_alive_monsters + inv_numkilled) < inv_maxspawned && inv_maxcurrent < 10) // 10 at a time should be plenty
+       if((total_alive_monsters + inv_numkilled) < inv_maxspawned && inv_maxcurrent < inv_maxspawned)
        {
                if(time >= inv_lastcheck)
                {
@@ -104,18 +155,35 @@ float Invasion_CheckWinner()
                return 0;
        }
 
-       if(inv_numspawned < 1 || inv_numkilled < inv_maxspawned)
-               return 0; // nothing has spawned yet, or there are still alive monsters
-
-       if(inv_roundcnt >= inv_maxrounds)
+       if(inv_numspawned < 1)
+               return 0; // nothing has spawned yet
+               
+       if(teamplay)
        {
-               NextLevel();
-               return 1;
+               if(((red_alive > 0) + (blue_alive > 0) + (yellow_alive > 0) + (pink_alive > 0)) > 1)
+                       return 0;
        }
+       else if(inv_numkilled < inv_maxspawned)
+               return 0;
 
        entity winner = world;
-       float winning_score = 0;
+       float winning_score = 0, winner_team = 0;
+
 
+       if(teamplay)
+       {
+               if(red_alive > 0) { winner_team = NUM_TEAM_1; }
+               if(blue_alive > 0)
+               if(winner_team) { winner_team = 0; }
+               else { winner_team = NUM_TEAM_2; }
+               if(yellow_alive > 0)
+               if(winner_team) { winner_team = 0; }
+               else { winner_team = NUM_TEAM_3; }
+               if(pink_alive > 0)
+               if(winner_team) { winner_team = 0; }
+               else { winner_team = NUM_TEAM_4; }
+       }       
+       else
        FOR_EACH_PLAYER(head)
        {
                float cs = PlayerScore_Add(head, SP_KILLS, 0);
@@ -129,7 +197,15 @@ float Invasion_CheckWinner()
        FOR_EACH_MONSTER(head)
                monster_remove(head);
 
-       if(winner)
+       if(teamplay)
+       {
+               if(winner_team)
+               {
+                       Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_));
+                       Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_));
+               }
+       }
+       else if(winner)
        {
                Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_PLAYER_WIN, winner.netname);
                Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_PLAYER_WIN, winner.netname);
@@ -155,15 +231,25 @@ void Invasion_RoundStart()
                ++numplayers;
        }
 
-       inv_roundcnt += 1;
+       if(inv_roundcnt < inv_maxrounds)
+               inv_roundcnt += 1; // a limiter to stop crazy counts
 
-       inv_monsterskill = inv_roundcnt + (numplayers * 0.3);
+       inv_monsterskill = inv_roundcnt + max(1, numplayers * 0.3);
 
        inv_maxcurrent = 0;
        inv_numspawned = 0;
        inv_numkilled = 0;
 
-       inv_maxspawned = rint(min(autocvar_g_invasion_monster_count, autocvar_g_invasion_monster_count * (inv_roundcnt * 0.5)));
+       inv_maxspawned = rint(max(autocvar_g_invasion_monster_count, autocvar_g_invasion_monster_count * (inv_roundcnt * 0.5)));
+
+       if(teamplay)
+       {
+               DistributeEvenly_Init(inv_maxspawned, invasion_teams);
+               inv_monsters_perteam[NUM_TEAM_1] = DistributeEvenly_Get(1);
+               inv_monsters_perteam[NUM_TEAM_2] = DistributeEvenly_Get(1);
+               if(invasion_teams >= 3) inv_monsters_perteam[NUM_TEAM_3] = DistributeEvenly_Get(1);
+               if(invasion_teams >= 4) inv_monsters_perteam[NUM_TEAM_4] = DistributeEvenly_Get(1);
+       }
 }
 
 MUTATOR_HOOKFUNCTION(invasion_MonsterDies)
@@ -172,9 +258,17 @@ MUTATOR_HOOKFUNCTION(invasion_MonsterDies)
        {
                inv_numkilled += 1;
                inv_maxcurrent -= 1;
+               if(teamplay) { inv_monsters_perteam[self.team] -= 1; }
 
                if(IS_PLAYER(frag_attacker))
+               if(SAME_TEAM(frag_attacker, self)) // in non-teamplay modes, same team = same player, so this works
+                       PlayerScore_Add(frag_attacker, SP_KILLS, -1);
+               else
+               {
                        PlayerScore_Add(frag_attacker, SP_KILLS, +1);
+                       if(teamplay)
+                               TeamScore_AddToTeam(frag_attacker.team, ST_INV_KILLS, +1);
+               }
        }
 
        return FALSE;
@@ -183,10 +277,7 @@ MUTATOR_HOOKFUNCTION(invasion_MonsterDies)
 MUTATOR_HOOKFUNCTION(invasion_MonsterSpawn)
 {
        if(!(self.spawnflags & MONSTERFLAG_SPAWNED))
-       {
-               monster_remove(self);
-               return FALSE;
-       }
+               return TRUE;
 
        if(!(self.spawnflags & MONSTERFLAG_RESPAWNED))
        {
@@ -204,7 +295,16 @@ MUTATOR_HOOKFUNCTION(invasion_MonsterSpawn)
        return FALSE;
 }
 
-MUTATOR_HOOKFUNCTION(invasion_PlayerThink)
+MUTATOR_HOOKFUNCTION(invasion_OnEntityPreSpawn)
+{
+       if(startsWith(self.classname, "monster_"))
+       if(!(self.spawnflags & MONSTERFLAG_SPAWNED))
+               return TRUE;
+       
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(invasion_StartFrame)
 {
        monsters_total = inv_maxspawned; // TODO: make sure numspawned never exceeds maxspawned
        monsters_killed = inv_numkilled;
@@ -256,6 +356,14 @@ MUTATOR_HOOKFUNCTION(invasion_PlayerCommand)
        return FALSE;
 }
 
+MUTATOR_HOOKFUNCTION(invasion_BotShouldAttack)
+{
+       if(!(checkentity.flags & FL_MONSTER))
+               return TRUE;
+       
+       return FALSE;
+}
+
 MUTATOR_HOOKFUNCTION(invasion_SetStartItems)
 {
        start_health = 200;
@@ -277,18 +385,31 @@ MUTATOR_HOOKFUNCTION(invasion_AllowMobSpawning)
        return TRUE;
 }
 
-void invasion_ScoreRules()
+MUTATOR_HOOKFUNCTION(invasion_GetTeamCount)
 {
-       ScoreRules_basics(0, 0, 0, FALSE);
-       ScoreInfo_SetLabel_PlayerScore(SP_KILLS, "frags", SFL_SORT_PRIO_PRIMARY);
+       ret_float = invasion_teams;
+       return FALSE;
+}
+
+void invasion_ScoreRules(float inv_teams)
+{
+       if(inv_teams) { CheckAllowedTeams(world); }
+       ScoreRules_basics(inv_teams, 0, 0, FALSE);
+       if(inv_teams) ScoreInfo_SetLabel_TeamScore(ST_INV_KILLS, "frags", SFL_SORT_PRIO_PRIMARY);
+       ScoreInfo_SetLabel_PlayerScore(SP_KILLS, "frags", ((inv_teams) ? SFL_SORT_PRIO_SECONDARY : SFL_SORT_PRIO_PRIMARY));
        ScoreRules_basics_end();
 }
 
-void invasion_Initialize()
+void invasion_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
 {
+       if(autocvar_g_invasion_teams)
+               invasion_teams = bound(2, autocvar_g_invasion_teams, 4);
+       else
+               invasion_teams = 0;
+       
        independent_players = 1; // to disable extra useless scores
 
-       invasion_ScoreRules();
+       invasion_ScoreRules(invasion_teams);
 
        independent_players = 0;
 
@@ -298,20 +419,45 @@ void invasion_Initialize()
        allowed_to_spawn = TRUE;
 
        inv_roundcnt = 0;
+       inv_maxrounds = 15; // 15?
+}
+
+void invasion_Initialize()
+{
+       if(autocvar_g_invasion_zombies_only)
+               MON_ACTION(MON_ZOMBIE, MR_PRECACHE);
+       else
+       {
+               float i;
+               entity mon;
+               for(i = MON_FIRST; i <= MON_LAST; ++i)
+               {
+                       mon = get_monsterinfo(i);
+                       if((mon.spawnflags & MONSTER_TYPE_FLY) || (mon.spawnflags & MONSTER_TYPE_SWIM))
+                               continue; // flying/swimming monsters not yet supported
+
+                       MON_ACTION(i, MR_PRECACHE);
+               }
+       }
+       
+       InitializeEntity(world, invasion_DelayedInit, INITPRIO_GAMETYPE);
 }
 
 MUTATOR_DEFINITION(gamemode_invasion)
 {
        MUTATOR_HOOK(MonsterDies, invasion_MonsterDies, CBC_ORDER_ANY);
        MUTATOR_HOOK(MonsterSpawn, invasion_MonsterSpawn, CBC_ORDER_ANY);
-       MUTATOR_HOOK(PlayerPreThink, invasion_PlayerThink, CBC_ORDER_ANY);
+       MUTATOR_HOOK(OnEntityPreSpawn, invasion_OnEntityPreSpawn, CBC_ORDER_ANY);
+       MUTATOR_HOOK(SV_StartFrame, invasion_StartFrame, CBC_ORDER_ANY);
        MUTATOR_HOOK(PlayerRegen, invasion_PlayerRegen, CBC_ORDER_ANY);
        MUTATOR_HOOK(PlayerSpawn, invasion_PlayerSpawn, CBC_ORDER_ANY);
        MUTATOR_HOOK(PlayerDamage_Calculate, invasion_PlayerDamage, CBC_ORDER_ANY);
        MUTATOR_HOOK(SV_ParseClientCommand, invasion_PlayerCommand, CBC_ORDER_ANY);
+       MUTATOR_HOOK(BotShouldAttack, invasion_BotShouldAttack, CBC_ORDER_ANY);
        MUTATOR_HOOK(SetStartItems, invasion_SetStartItems, CBC_ORDER_ANY);
        MUTATOR_HOOK(AccuracyTargetValid, invasion_AccuracyTargetValid, CBC_ORDER_ANY);
        MUTATOR_HOOK(AllowMobSpawning, invasion_AllowMobSpawning, CBC_ORDER_ANY);
+       MUTATOR_HOOK(GetTeamCount, invasion_GetTeamCount, CBC_ORDER_ANY);
 
        MUTATOR_ONADD
        {
index 3438f47..04492d4 100644 (file)
@@ -6,4 +6,9 @@ float inv_numkilled;
 float inv_lastcheck;
 float inv_maxcurrent;
 
+float invasion_teams;
+float inv_monsters_perteam[17];
+
 float inv_monsterskill;
+
+#define ST_INV_KILLS 1
index e332e55..a0c80db 100644 (file)
@@ -197,8 +197,13 @@ void InitGameplayMode()
 
        if(g_invasion)
        {
-               timelimit_override = 0; // no timelimit in invasion, round based
-               fraglimit_override = autocvar_g_invasion_round_limit;
+               fraglimit_override = autocvar_g_invasion_point_limit;
+               if(autocvar_g_invasion_teams >= 2)
+               {
+                       ActivateTeamplay();
+                       if(autocvar_g_invasion_team_spawns)
+                               have_team_spawns = -1; // request team spawns
+               }
                MUTATOR_ADD(gamemode_invasion);
        }
 
@@ -241,12 +246,6 @@ void InitGameplayMode()
                else
                        g_race_qualifying = 0;
        }
-       
-       if(g_invasion)
-       {
-               inv_maxrounds = cvar("fraglimit");
-               cvar_set("fraglimit", "0");
-       }
 
        if(g_race || g_cts)
        {
index f51db6d..a7ae4d3 100644 (file)
@@ -200,9 +200,13 @@ void fireBullet(vector start, vector dir, float spread, float max_solid_penetrat
        if(autocvar_g_antilag == 0 || self.cvar_cl_noantilag)
                lag = 0; // only do hitscan, but no antilag
        if(lag)
+       {
                FOR_EACH_PLAYER(pl)
                        if(pl != self)
                                antilag_takeback(pl, time - lag);
+               FOR_EACH_MONSTER(pl)
+                       antilag_takeback(pl, time - lag);
+       }
 
        WarpZone_trace_forent = self;
 
@@ -303,9 +307,13 @@ void fireBullet(vector start, vector dir, float spread, float max_solid_penetrat
        }
 
        if(lag)
+       {
                FOR_EACH_PLAYER(pl)
                        if(pl != self)
                                antilag_restore(pl);
+               FOR_EACH_MONSTER(pl)
+                       antilag_restore(pl);
+       }
 }
 
 float W_CheckProjectileDamage(entity inflictor, entity projowner, float deathtype, float exception)