]> git.rm.cloudns.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Add missing files
authorMario <mario.mario@y7mail.com>
Thu, 7 Mar 2013 04:04:50 +0000 (15:04 +1100)
committerMario <mario.mario@y7mail.com>
Thu, 7 Mar 2013 04:04:50 +0000 (15:04 +1100)
qcsrc/server/mutators/gamemode_towerdefense.qc [new file with mode: 0644]
qcsrc/server/mutators/gamemode_towerdefense.qh [new file with mode: 0644]

diff --git a/qcsrc/server/mutators/gamemode_towerdefense.qc b/qcsrc/server/mutators/gamemode_towerdefense.qc
new file mode 100644 (file)
index 0000000..fc30750
--- /dev/null
@@ -0,0 +1,1095 @@
+// Tower Defense
+// Gamemode by Mario
+void spawnfunc_td_controller()
+{
+       if not(g_td) { remove(self); return; }
+       
+       if(autocvar_g_td_force_settings)
+       {
+               // TODO: find a better way to do this?
+               self.dontend = FALSE;
+               self.maxwaves = 0;
+               self.monstercount = 0;
+               self.startwave = 0;
+               self.maxturrets = 0;
+               self.buildtime = 0;
+               self.mspeed_walk = 0;
+               self.mspeed_run = 0;
+               self.spawndelay = 0;
+               self.maxcurrent = 0;
+               self.ignoreturrets = 0;
+       }
+               
+       self.netname = "Tower Defense controller entity";
+       self.classname = "td_controller";
+               
+       gensurvived = FALSE;
+       td_dont_end = ((self.dontend) ? self.dontend : autocvar_g_td_generator_dontend);                
+       max_waves = ((self.maxwaves) ? self.maxwaves : autocvar_g_td_max_waves);        
+       totalmonsters = ((self.monstercount) ? self.monstercount : autocvar_g_td_monster_count);
+       wave_count = ((self.startwave) ? self.startwave : autocvar_g_td_start_wave);
+       max_turrets = ((self.maxturrets) ? self.maxturrets : autocvar_g_td_turret_max);
+       build_time = ((self.buildtime) ? self.buildtime : autocvar_g_td_buildphase_time);
+       m_speed_walk = ((self.mspeed_walk) ? self.mspeed_walk : autocvar_g_td_monsters_speed_walk);
+       m_speed_run = ((self.mspeed_run) ? self.mspeed_run : autocvar_g_td_monsters_speed_run);
+       spawn_delay = ((self.spawndelay) ? self.spawndelay : autocvar_g_td_monsters_spawn_delay);
+       max_current = ((self.maxcurrent) ? self.maxcurrent : autocvar_g_td_current_monsters);
+       ignore_turrets = ((self.ignoreturrets) ? self.ignoreturrets : autocvar_g_td_monsters_ignore_turrets);
+       
+       if(autocvar_g_td_monsters_skill_start)
+               monster_skill = autocvar_g_td_monsters_skill_start;
+               
+       wave_end(TRUE);
+}
+
+void td_generator_die() 
+{
+       if(autocvar_sv_eventlog)
+               GameLogEcho(":gendestroyed");
+               
+       gendestroyed = TRUE;
+       
+       Send_Notification(NOTIF_ALL, world, MSG_MULTI, MULTI_TD_GENDESTROYED);
+       
+       setmodel(self, "models/onslaught/generator_dead.md3");
+       self.solid                      = SOLID_NOT;
+       self.takedamage         = DAMAGE_NO;
+       self.event_damage   = func_null;
+       self.enemy                      = world;
+       td_gencount                     -= 1;
+               
+       pointparticles(particleeffectnum("explosion_medium"), self.origin, '0 0 0', 1);
+       
+       WaypointSprite_Kill(self.sprite);
+}
+
+void td_generator_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) 
+{
+       if(attacker.classname == STR_PLAYER || attacker.turrcaps_flags & TFL_TURRCAPS_ISTURRET || attacker.vehicle_flags & VHF_ISVEHICLE)
+               return;
+       
+       Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_TD_GENDAMAGED);
+       
+       self.health -= damage;
+       
+       WaypointSprite_UpdateHealth(self.sprite, self.health);
+               
+       if(self.health <= 0) 
+               td_generator_die();
+}
+
+void spawnfunc_td_generator() 
+{
+       if not(g_td) { remove(self); return; }
+       
+       gendestroyed = FALSE;
+       
+       if not(self.health)
+               self.health = autocvar_g_td_generator_health;
+
+       // precache generator model
+       precache_model("models/onslaught/generator.md3");
+       precache_model("models/onslaught/generator_dead.md3");   
+       
+       self.model                  = "models/onslaught/generator.md3";
+       setmodel(self, self.model);
+       self.classname      = "td_generator";
+       self.solid                  = SOLID_BBOX;
+       self.takedamage     = DAMAGE_AIM;
+       self.event_damage   = td_generator_damage;
+       self.enemy                  = world;
+       self.nextthink      = -1;
+       self.think                  = func_null;
+       self.max_health     = self.health;
+       self.movetype       = MOVETYPE_NONE;
+       self.monster_attack = TRUE;
+       td_gencount                += 1;
+       self.netname            = "Generator";
+       
+       setsize(self, GENERATOR_MIN, GENERATOR_MAX);
+       
+       droptofloor();
+       
+       WaypointSprite_SpawnFixed(self.netname, self.origin + '0 0 60', self, sprite, RADARICON_OBJECTIVE, '1 0.5 0');  
+       WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
+       WaypointSprite_UpdateHealth(self.sprite, self.health);
+}
+
+entity PickGenerator()
+{
+       entity generator, head;
+       if(td_gencount == 1)
+               generator = find(world, classname, "td_generator");
+       else
+       {
+               RandomSelection_Init();
+               for(head = world;(head = find(head, classname, "td_generator")); )
+               {
+                       RandomSelection_Add(head, 0, string_null, 1, 1);
+               }
+               generator = RandomSelection_chosen_ent; 
+       }
+       return generator;
+}
+
+void spawn_td_fuel(float fuel_size)
+{
+       if not(g_td) {remove(self); return; }
+       
+       self.ammo_fuel = fuel_size * monster_skill;
+       StartItem("models/items/g_fuel.md3", "misc/itempickup.wav", g_pickup_respawntime_ammo, g_pickup_respawntimejitter_ammo, "Turret Fuel", IT_FUEL, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_LOW);
+       
+       self.velocity = randomvec() * 175 + '0 0 325';
+}
+
+void spawnfunc_td_waypoint() 
+{
+       if not(g_td) { remove(self); return; }
+       
+       self.classname = "td_waypoint";
+}
+
+void spawnfunc_monster_swarm()
+{
+       if not(g_td) { remove(self); return; }
+       
+       self.flags = SWARM_NORMAL; // marked as a spawnpoint
+       self.classname = "monster_swarm";
+       
+       if(self.spawntype == SWARM_SWIM) waterspawns_count += 1;
+       if(self.spawntype == SWARM_FLY) flyspawns_count += 1;
+       
+       WaypointSprite_SpawnFixed("Monsters", self.origin + '0 0 60', self, sprite, RADARICON_HERE, '0 0 1');
+       
+       if(self.target == "")
+               dprint("Warning: monster_swarm entity without a set target\n");
+}
+
+void barricade_touch()
+{
+       if not(other.flags & FL_MONSTER)
+               return;
+               
+       if(time < self.dmg_time)
+               return;
+               
+       Damage(other, self, self, autocvar_g_td_barricade_damage, DEATH_HURTTRIGGER, self.origin, '0 0 0');
+       
+       self.dmg_time = time + 1;
+}
+
+void barricade_die()
+{
+       self.takedamage = DAMAGE_NO;
+       self.event_damage = func_null;
+       
+       WaypointSprite_Kill(self.sprite);
+       
+       pointparticles(particleeffectnum("explosion_medium"), self.origin, '0 0 0', 1);
+       sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM);
+       
+       if(self.realowner)
+               self.realowner.turret_cnt -= 1;
+               
+       self.think = SUB_Remove;
+       self.nextthink = time;
+}
+
+void barricade_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+       if not(attacker.flags & FL_MONSTER) return;
+       
+       self.health -= damage;
+       
+       WaypointSprite_UpdateHealth(self.sprite, self.health);
+       
+       if(self.health < 1)
+               barricade_die();
+}
+
+void spawn_barricade()
+{
+       self.health = 2000;
+       self.max_health = self.health;
+       self.dmg_time = time;
+       self.touch = barricade_touch;
+       self.think = func_null;
+       self.nextthink = -1;
+       self.takedamage = DAMAGE_AIM;
+       self.turrcaps_flags = TFL_TURRCAPS_ISTURRET; // for turretremove commands etc.
+       self.solid = SOLID_CORPSE; // hax
+       self.event_damage = barricade_damage;
+       self.netname = "Barricade";
+       
+       WaypointSprite_Spawn(self.netname, 0, 1200, self, '0 0 110', world, 0, self, sprite, FALSE, RADARICON_DOMPOINT, '1 1 0');       
+       WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
+       WaypointSprite_UpdateHealth(self.sprite, self.health);
+       
+       precache_model("models/td/barricade.md3");
+       setmodel(self, "models/td/barricade.md3");
+       
+       droptofloor();
+       
+       self.movetype = MOVETYPE_NONE;
+}
+
+void spawnturret(entity spawnedby, entity own, string turet, vector orig)
+{
+       if(spawnedby.classname != STR_PLAYER)
+       {
+               dprint("Warning: A non-player entity tried to spawn a turret\n");
+               return;
+       }
+               
+       entity oldself;
+       
+       oldself = self;
+       self = spawn();
+       
+       setorigin(self, orig);
+       self.spawnflags = TSL_NO_RESPAWN;
+       self.monster_attack = TRUE;
+       self.realowner = own;
+       self.angles_y = spawnedby.v_angle_y;
+       spawnedby.turret_cnt += 1;
+       self.colormap = spawnedby.colormap;
+       
+       switch(turet)
+       {
+               default:
+               case "plasma": spawnfunc_turret_plasma(); break;
+               case "mlrs": spawnfunc_turret_mlrs(); break;
+               case "phaser": spawnfunc_turret_phaser(); break;
+               case "hellion": spawnfunc_turret_hellion(); break;
+               case "walker": spawnfunc_turret_walker(); break;
+               case "flac": spawnfunc_turret_flac(); break;
+               case "tesla": spawnfunc_turret_tesla(); break;
+               case "fusionreactor": spawnfunc_turret_fusionreactor(); break;
+               case "barricade": spawn_barricade(); break;
+       }
+               
+       self = oldself;
+}
+
+void buffturret (entity tur, float buff)
+{
+       tur.turret_buff           += 1;
+       tur.max_health            *= buff;
+       tur.tur_health             = tur.max_health;
+       tur.health                         = tur.max_health;
+       tur.ammo_max              *= buff;
+       tur.ammo_recharge     *= buff;
+    tur.shot_dmg          *= buff;
+    tur.shot_refire       -= buff * 0.2;
+    tur.shot_radius       *= buff;
+    tur.shot_speed        *= buff;
+    tur.shot_spread       *= buff;
+    tur.shot_force        *= buff;
+}
+
+void AnnounceSpawn(string anounce)
+{
+       Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_TD_ANNOUNCE_SPAWN, anounce);
+}
+
+entity PickSpawn (float strngth, float type)
+{
+       entity e;
+       RandomSelection_Init();
+       for(e = world;(e = find(e, classname, "monster_swarm")); )
+       {
+               if(flyspawns_count > 0 && type == SWARM_FLY && e.spawntype != SWARM_FLY) continue;
+               if(waterspawns_count > 0 && type == SWARM_SWIM && e.spawntype != SWARM_SWIM) continue;
+               
+               RandomSelection_Add(e, 0, string_null, 1, 1);
+       }
+
+       return RandomSelection_chosen_ent;
+}
+
+void TD_SpawnMonster(string mnster, float strngth, float type)
+{
+       entity e, mon;
+       
+       e = PickSpawn(strngth, type);
+       
+       if(e == world) // couldn't find anything for our class, so check for normal spawns
+               e = PickSpawn(SWARM_NORMAL, SWARM_NORMAL);
+               
+       if(e == world)
+       {
+               dprint("Warning: couldn't find any monster_swarm spawnpoints, no monsters will spawn!\n");
+               return;
+       }
+  
+       mon = spawnmonster(mnster, e, e, e.origin, FALSE, 0);
+       if(e.target2)
+       {
+               if(random() > 0.5)
+                       mon.target = e.target2;
+               else
+                       mon.target = e.target;
+       }
+       else
+               mon.target = e.target;
+}
+
+float Monster_GetStrength(string mnster)
+{
+       switch(mnster)
+       {
+               case "knight":
+               case "wizard":
+               case "soldier":
+               case "enforcer":
+               case "zombie":
+               case "tarbaby":
+               case "dog":
+               case "spider":
+               case "fish":
+                       return SWARM_WEAK;
+               case "ogre":
+               case "shambler":
+               case "shalrath":
+               case "hellknight":
+               case "demon":
+                       return SWARM_STRONG;
+               default:
+                       return SWARM_NORMAL;
+       }
+}
+
+float Monster_GetType(string mnster)
+{
+       switch(mnster)
+       {
+               default:
+               case "knight":
+               case "soldier":
+               case "enforcer":
+               case "zombie":
+               case "spider":
+               case "tarbaby":
+               case "dog":
+               case "ogre":
+               case "shambler":
+               case "shalrath":
+               case "hellknight":
+               case "demon":
+                       return SWARM_NORMAL;
+               case "wizard":
+                       return SWARM_FLY;
+               case "fish":
+                       return SWARM_SWIM;
+       }
+}
+
+string RandomMonster()
+{
+       RandomSelection_Init();
+       
+       if(n_demons) RandomSelection_Add(world, 0, "demon", 1, 1);
+       if(n_shalraths) RandomSelection_Add(world, 0, "vore", 1, 1);
+       if(n_soldiers) RandomSelection_Add(world, 0, "soldier", 1, 1);
+       if(n_hknights) RandomSelection_Add(world, 0, "hellknight", 1, 1);
+       if(n_enforcers) RandomSelection_Add(world, 0, "enforcer", 1, 1);
+       if(n_zombies) RandomSelection_Add(world, 0, "zombie", 1, 1);
+       if(n_spiders) RandomSelection_Add(world, 0, "spider", 1, 1);
+       if(n_ogres) RandomSelection_Add(world, 0, "ogre", 1, 1);
+       if(n_dogs) RandomSelection_Add(world, 0, "dog", 1, 1);
+       if(n_knights) RandomSelection_Add(world, 0, "knight", 1, 1);
+       if(n_shamblers) RandomSelection_Add(world, 0, "shambler", 0.2, 0.2);
+       if(n_tarbabies) RandomSelection_Add(world, 0, "spawn", 0.2, 0.2);
+       if(n_wizards && flyspawns_count) RandomSelection_Add(world, 0, "scrag", 1, 1);
+       if(n_fish && waterspawns_count) RandomSelection_Add(world, 0, "fish", 0.2, 0.2);
+       
+       return RandomSelection_chosen_string;
+}
+
+void combat_phase()
+{
+       string whichmon;
+       float mstrength, montype;
+       
+       current_phase = PHASE_COMBAT;
+       
+       if(monster_count <= 0)
+       {
+               wave_end(FALSE);
+               return;
+       }
+       
+       self.think = combat_phase;
+       
+       whichmon = RandomMonster();
+       
+       mstrength = Monster_GetStrength(whichmon);
+       montype = Monster_GetType(whichmon);
+       
+       if(current_monsters <= max_current && whichmon != "")
+       {
+               TD_SpawnMonster(whichmon, mstrength, montype);
+               self.nextthink = time + spawn_delay;
+       }
+       else
+               self.nextthink = time + 6;
+}
+
+void queue_monsters(float maxmonsters)
+{
+       float mc = 11; // note: shambler + tarbaby = 1
+       
+       if(waterspawns_count > 0)
+               mc += 1;
+       if(flyspawns_count > 0)
+               mc += 1;
+               
+       DistributeEvenly_Init(maxmonsters, mc);
+       n_demons        = DistributeEvenly_Get(1);
+       n_ogres         = DistributeEvenly_Get(1);
+       n_dogs          = DistributeEvenly_Get(1);
+       n_knights   = DistributeEvenly_Get(1);
+       n_shalraths = DistributeEvenly_Get(1);
+       n_soldiers  = DistributeEvenly_Get(1);
+       n_hknights  = DistributeEvenly_Get(1);
+       n_enforcers = DistributeEvenly_Get(1);
+       n_zombies   = DistributeEvenly_Get(1);
+       n_spiders   = DistributeEvenly_Get(1);
+       n_tarbabies = DistributeEvenly_Get(0.7);
+       n_shamblers = DistributeEvenly_Get(0.3);
+       if(flyspawns_count > 0)
+               n_wizards = DistributeEvenly_Get(1);
+       if(waterspawns_count > 0)
+               n_fish = DistributeEvenly_Get(1);
+}
+
+void combat_phase_begin()
+{
+       monster_count = totalmonsters;
+       entity gen;
+       
+       Send_Notification(NOTIF_ALL, world, MSG_MULTI, MULTI_TD_PHASE_COMBAT);
+       
+       if(autocvar_sv_eventlog)
+               GameLogEcho(":combatphase");
+               
+       self.think = combat_phase;
+       self.nextthink = time + 1;
+       
+       for(gen = world;(gen = find(gen, classname, "td_generator")); )
+               gen.takedamage = DAMAGE_AIM;
+}
+
+float cphase_updates;
+void combat_phase_announce() // TODO: clean up these fail nextthinks...
+{
+       cphase_updates += 1;
+       
+       if(cphase_updates == 0)
+               Announce("prepareforbattle");
+       else if(cphase_updates == 3)
+               Announce("3");
+       else if(cphase_updates == 4)
+               Announce("2");
+       else if(cphase_updates == 5)
+               Announce("1");
+       else if(cphase_updates == 6)
+       {
+               Announce("begin");
+               combat_phase_begin();
+       }
+       
+       if(cphase_updates >= 6)
+               return;
+
+       self.think = combat_phase_announce;
+       self.nextthink = time + 1;
+}
+
+void build_phase()
+{
+       entity head;
+       float n_players = 0, gen_washealed = FALSE, player_washealed = FALSE;
+       
+       current_phase = PHASE_BUILD;
+       
+       for(head = world;(head = find(head, classname, "td_generator")); )
+       {
+               if(head.health <= 5 && head.max_health > 10)
+                       Announce("lastsecond");
+                       
+               if(head.health < head.max_health)
+               {
+                       gen_washealed = TRUE;
+                       head.health = head.max_health;
+                       WaypointSprite_UpdateHealth(head.sprite, head.health);
+               }
+               head.takedamage = DAMAGE_NO;
+       }
+       
+       FOR_EACH_PLAYER(head)
+       {
+               if(head.health < 100)
+               {
+                       player_washealed = TRUE;
+                       break; // we found 1, so no need to check the others
+               }
+       }
+               
+       totalmonsters += autocvar_g_td_monster_count_increment * wave_count;
+       monster_skill += autocvar_g_td_monsters_skill_increment;
+       
+       if(wave_count < 1) wave_count = 1;
+       
+       Send_Notification(NOTIF_ALL, world, MSG_MULTI, MULTI_TD_PHASE_BUILD, wave_count, totalmonsters, autocvar_g_td_buildphase_time);
+       
+       FOR_EACH_PLAYER(head) 
+    {
+               if(head.health < 100) head.health = 100;
+                       
+               if(gen_washealed) PlayerScore_Add(head, SP_TD_SCORE, -autocvar_g_td_generator_damaged_points);
+                       
+        n_players += 1;
+    }
+    
+    FOR_EACH_MONSTER(head)
+    {
+               if(head.health <= 0)
+                       continue;
+                       
+        dprint(strcat("Warning: Monster still alive during build phase! Monster name: ", head.netname, "\n"));
+               
+               WaypointSprite_Kill(head.sprite);
+        remove(head);
+    }
+       
+       if(n_players >= 2)
+       {
+               totalmonsters += n_players;
+               monster_skill += n_players * 0.05;
+       }
+       
+       if(monster_skill < 1) monster_skill = 1;
+               
+       if(totalmonsters < 1) totalmonsters = ((autocvar_g_td_monster_count > 0) ? autocvar_g_td_monster_count : 10);
+       
+       monsters_total = totalmonsters;
+       monsters_killed = 0;
+               
+       queue_monsters(totalmonsters);
+       
+       cphase_updates = -1;
+       
+       if(autocvar_sv_eventlog)
+        GameLogEcho(strcat(":buildphase:", ftos(wave_count), ":", ftos(totalmonsters)));
+       
+       self.think = combat_phase_announce;
+       self.nextthink = time + build_time - 6;
+}
+
+void wave_end(float starting)
+{
+       if not(starting)
+       {
+               Send_Notification(NOTIF_ALL, world, MSG_MULTI, MULTI_TD_VICTORY, ((wave_count >= max_waves) ? "Level" : "Wave"));
+               
+               if(autocvar_sv_eventlog)
+            GameLogEcho(strcat(":wave:", ftos(wave_count), ":victory"));
+       }
+       
+       if(wave_count >= max_waves)
+       {
+               gensurvived = TRUE;
+               return;
+       }
+       
+       if not(starting)
+               wave_count += 1;
+               
+       self.think = build_phase;
+       self.nextthink = time + 3;
+}
+
+void td_ScoreRules()
+{
+       ScoreInfo_SetLabel_PlayerScore(SP_TD_SCORE,             "score",         SFL_SORT_PRIO_PRIMARY);
+       ScoreInfo_SetLabel_PlayerScore(SP_TD_KILLS,             "kills",         SFL_LOWER_IS_BETTER);
+       ScoreInfo_SetLabel_PlayerScore(SP_TD_TURKILLS,  "frags",         SFL_LOWER_IS_BETTER);
+       ScoreInfo_SetLabel_PlayerScore(SP_TD_DEATHS,    "deaths",    SFL_LOWER_IS_BETTER);
+       ScoreInfo_SetLabel_PlayerScore(SP_TD_SUICIDES,  "suicides",  SFL_LOWER_IS_BETTER | SFL_ALLOW_HIDE);
+       ScoreRules_basics_end();
+}
+
+void td_SpawnController()
+{
+       entity oldself = self;
+       self = spawn();
+       self.classname = "td_controller";
+       spawnfunc_td_controller();
+       self = oldself;
+}
+
+void td_DelayedInit()
+{
+       if(find(world, classname, "td_controller") == world)
+       {
+               print("No ""td_controller"" entity found on this map, creating it anyway.\n");
+               td_SpawnController();
+       }
+       
+       td_ScoreRules();
+}
+
+void td_Init()
+{
+       InitializeEntity(world, td_DelayedInit, INITPRIO_GAMETYPE);
+}
+
+MUTATOR_HOOKFUNCTION(td_TurretValidateTarget)
+{
+       if(turret_flags & TFL_TARGETSELECT_MISSILESONLY)
+    if(turret_target.flags & FL_PROJECTILE)
+       if(turret_target.owner.flags & FL_MONSTER)
+        return TRUE; // flac support
+                       
+       if(turret.turrcaps_flags & TFL_TURRCAPS_SUPPORT && turret_target.turrcaps_flags & TFL_TURRCAPS_ISTURRET)
+               return TRUE;
+       if not(turret_target.flags & FL_MONSTER)
+               turret_target = world;
+               
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_PlayerThink)
+{
+       self.stat_current_wave = wave_count;
+       self.stat_totalwaves = max_waves;
+       
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_PlayerSpawn)
+{
+       self.bot_attack = FALSE;
+       
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_PlayerDies)
+{
+       if(frag_attacker.flags & FL_MONSTER)
+               PlayerScore_Add(frag_target, SP_TD_DEATHS, 1);
+               
+       if(frag_target == frag_attacker)
+               PlayerScore_Add(frag_attacker, SP_TD_SUICIDES, 1);
+
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_GiveFragsForKill)
+{
+       frag_score = 0;
+               
+       return TRUE; // no frags counted in td
+}
+
+MUTATOR_HOOKFUNCTION(td_PlayerDamage)
+{
+       if(frag_attacker.realowner == frag_target)
+               frag_damage = 0;
+               
+       if(frag_target.flags & FL_MONSTER && time < frag_target.spawnshieldtime)
+               frag_damage = 0;
+               
+       if(frag_target.vehicle_flags & VHF_ISVEHICLE && !(frag_attacker.flags & FL_MONSTER))
+               frag_damage = 0;
+               
+       if(frag_attacker.vehicle_flags & VHF_ISVEHICLE && !(frag_target.flags & FL_MONSTER))
+               frag_damage = 0;
+               
+       if(!autocvar_g_td_pvp && frag_attacker != frag_target && frag_target.classname == STR_PLAYER && frag_attacker.classname == STR_PLAYER)
+               frag_damage = 0;
+               
+       if(frag_attacker.turrcaps_flags & TFL_TURRCAPS_ISTURRET && frag_target.classname == STR_PLAYER)
+               frag_damage = 0;
+               
+       if((frag_target.turrcaps_flags & TFL_TURRCAPS_ISTURRET) && !(frag_attacker.flags & FL_MONSTER || frag_attacker.turrcaps_flags & TFL_TURRCAPS_SUPPORT))
+               frag_damage = 0;
+               
+       return TRUE;
+}
+
+MUTATOR_HOOKFUNCTION(td_TurretDies)
+{
+       if(self.realowner)
+               self.realowner.turret_cnt -= 1;
+                       
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_MonsterCheckBossFlag)
+{
+       // No minibosses in tower defense
+       return TRUE;
+}
+
+MUTATOR_HOOKFUNCTION(td_MonsterMove)
+{
+       entity player;
+       float n_players = 0;
+       FOR_EACH_PLAYER(player) { ++n_players; }
+       
+       if(n_players < 1) // no players online, so do nothing
+       {
+               monster_target = world;
+               monster_speed_run = monster_speed_walk = 0;
+               return FALSE;
+       }
+       
+       if not(self.enemy) // don't change targets while attacking
+       if((vlen(self.goalentity.origin - self.origin) <= 100 && self.goalentity.classname == "td_waypoint") || (vlen(self.goalentity.origin - self.origin) <= 200 && self.flags & FL_FLY && self.goalentity.classname == "td_waypoint"))
+       {
+               if(self.goalentity.target2)
+               {
+                       if(random() > 0.5)
+                               self.target = self.goalentity.target2;
+                       else
+                               self.target = self.goalentity.target;
+               }
+               else
+                       self.target = self.goalentity.target;
+                               
+               self.goalentity = find(world, targetname, self.target);
+               
+               if(self.goalentity == world)
+                       self.goalentity = PickGenerator();
+       }
+       
+       monster_speed_run = m_speed_run * monster_skill;
+       monster_speed_walk = m_speed_walk * monster_skill;
+       
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_MonsterSpawn)
+{
+       if(self.realowner == world) // nothing spawned it, so kill it
+       {
+               WaypointSprite_Kill(self.sprite);
+               remove(self);
+               return TRUE;
+       }
+       
+       current_monsters += 1;
+       
+       self.spawnshieldtime = time + autocvar_g_td_monsters_spawnshield_time;
+       
+       self.drop_size = self.health * 0.05;
+       
+       if(self.drop_size < 1) self.drop_size = 1;
+       
+       self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_BODY;
+       
+       self.origin += '0 0 25'; // hopefully this fixes monsters falling through the floor
+       
+       switch(self.classname)
+       {
+               case "monster_knight": n_knights -= 1; break;
+               case "monster_dog": n_dogs -= 1; break;
+               case "monster_ogre": n_ogres -= 1; break;
+               case "monster_shambler": n_shamblers -= 1; AnnounceSpawn("Shambler"); break;
+               case "monster_wizard": n_wizards -= 1; break;
+               case "monster_shalrath": n_shalraths -= 1; break;
+               case "monster_soldier": n_soldiers -= 1; break;
+               case "monster_hellknight": n_hknights -= 1; break;
+               case "monster_enforcer": n_enforcers -= 1; break;
+               case "monster_demon": n_demons -= 1; break;
+               case "monster_zombie": n_zombies -= 1; break;
+               case "monster_spider": n_spiders -= 1; break;
+               case "monster_tarbaby": n_tarbabies -= 1; break;
+       }
+       
+       return TRUE;
+}
+
+MUTATOR_HOOKFUNCTION(td_MonsterDies)
+{
+       entity oldself;
+       vector backuporigin;
+
+       monster_count -= 1;
+       current_monsters -= 1;
+       monsters_killed += 1;
+       
+       if(frag_attacker.classname == STR_PLAYER)
+       {
+               PlayerScore_Add(frag_attacker, SP_TD_SCORE, autocvar_g_td_kill_points);
+               PlayerScore_Add(frag_attacker, SP_TD_KILLS, 1);
+       }
+       else if(frag_attacker.realowner.classname == STR_PLAYER)
+       {
+               PlayerScore_Add(frag_attacker.realowner, SP_TD_SCORE, autocvar_g_td_turretkill_points);
+               PlayerScore_Add(frag_attacker.realowner, SP_TD_TURKILLS, 1);
+       }
+
+       backuporigin = self.origin;
+       oldself = self;
+       self = spawn();
+       
+       self.gravity = 1;
+       setorigin(self, backuporigin + '0 0 5');
+       spawn_td_fuel(oldself.drop_size);
+       self.touch = M_Item_Touch;
+       if(self == world)
+       {
+               self = oldself;
+               return FALSE;
+       }
+       SUB_SetFade(self, time + 5, 1);
+       
+       self = oldself;
+
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_MonsterFindTarget)
+{
+       float n_players = 0;
+       entity player;
+       local entity e;
+       
+       FOR_EACH_PLAYER(player) { ++n_players; }
+       
+       if(n_players < 1) // no players online, so do nothing
+       {
+               self.enemy = world;
+               return TRUE;
+       }
+       
+       for(e = world;(e = findflags(e, monster_attack, TRUE)); ) 
+       {
+               if(ignore_turrets)
+               if(e.turrcaps_flags & TFL_TURRCAPS_ISTURRET)
+                       continue;
+               
+               if(monster_isvalidtarget(e, self, FALSE))
+               if((vlen(trace_endpos - self.origin) < 200 && e.turrcaps_flags & TFL_TURRCAPS_ISTURRET) || (vlen(trace_endpos - self.origin) < 200 && e.classname != "td_generator") || (vlen(trace_endpos - self.origin) < 500 && e.classname == "td_generator"))
+               {
+                       self.enemy = e;
+               }
+       }
+       
+       return TRUE;
+}
+
+MUTATOR_HOOKFUNCTION(td_SetStartItems)
+{
+       start_ammo_fuel = 150; // to be nice...
+       
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_TurretSpawn)
+{
+       if(self.realowner == world)
+               return TRUE; // wasn't spawned by a player
+               
+       self.bot_attack = FALSE;
+       self.turret_buff = 1;
+       
+       return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_DisableVehicles)
+{
+       // you shall not spawn!
+       return TRUE;
+}
+
+MUTATOR_HOOKFUNCTION(td_PlayerCommand)
+{
+       if(MUTATOR_RETURNVALUE) { return FALSE; } // command was already handled?
+       
+       makevectors(self.v_angle);
+       WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * 100, MOVE_NORMAL, self);
+       
+       if(cmd_name == "turretspawn")
+       {
+               if(argv(1) == "list")
+               {
+                       Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_LIST, "mlrs walker plasma towerbuff flac barricade");
+                       return TRUE;
+               }
+               if(self.classname != STR_PLAYER || self.health <= 0)
+               { 
+                       Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_CANTSPAWN);
+                       return TRUE;
+               }
+               if(self.turret_cnt >= max_turrets)
+               {
+                       Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_MAXTURRETS, max_turrets);
+                       return TRUE;
+               }
+               switch(argv(1))
+               {
+                       case "plasma":
+                       {
+                if(self.ammo_fuel < autocvar_g_td_turret_plasma_cost) break;
+                               self.ammo_fuel -= autocvar_g_td_turret_plasma_cost;
+                               spawnturret(self, self, "plasma", trace_endpos);
+                               Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_SPAWN);
+                               return TRUE;
+                       }
+                       case "mlrs":
+                       {
+                if(self.ammo_fuel < autocvar_g_td_turret_mlrs_cost) break;
+                               self.ammo_fuel -= autocvar_g_td_turret_mlrs_cost;
+                               spawnturret(self, self, "mlrs", trace_endpos);
+                               Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_SPAWN);
+                               return TRUE;
+                       }
+                       case "flac":
+                       {
+                if(self.ammo_fuel < autocvar_g_td_turret_flac_cost) break;
+                               self.ammo_fuel -= autocvar_g_td_turret_flac_cost;
+                               spawnturret(self, self, "flac", trace_endpos);
+                               Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_SPAWN);
+                               return TRUE;
+                       }
+                       case "walker":
+                       {
+                if(self.ammo_fuel < autocvar_g_td_turret_walker_cost) break;
+                               self.ammo_fuel -= autocvar_g_td_turret_walker_cost;
+                               spawnturret(self, self, "walker", trace_endpos);
+                               Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_SPAWN);
+                               return TRUE;
+                       }
+                       case "towerbuff":
+                       {
+                if(self.ammo_fuel < autocvar_g_td_tower_buff_cost) break;
+                               self.ammo_fuel -= autocvar_g_td_tower_buff_cost;
+                               spawnturret(self, self, "fusionreactor", trace_endpos);
+                               Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_SPAWN);
+                               return TRUE;
+                       }
+                       case "barricade":
+                       {
+                               if(self.ammo_fuel < autocvar_g_td_barricade_cost) break;
+                               self.ammo_fuel -= autocvar_g_td_barricade_cost;
+                               spawnturret(self, self, "barricade", trace_endpos);
+                               Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_SPAWN);
+                               return TRUE;
+                       }
+                       default:
+                       {
+                               Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_INVALID);
+                               return TRUE;
+                       }
+               }
+               Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_NOFUEL);
+               return TRUE;
+       }
+       if(cmd_name == "repairturret")
+       {
+               if(trace_ent.realowner != self || !(trace_ent.turrcaps_flags & TFL_TURRCAPS_ISTURRET))
+               {
+                       Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_AIM_REPAIR);
+                       return TRUE;
+               }
+               if(self.ammo_fuel < autocvar_g_td_turret_repair_cost)   
+               {
+                       Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_NOFUEL_REPAIR, autocvar_g_td_turret_repair_cost);
+                       return TRUE;
+               }
+               if(trace_ent.health >= trace_ent.max_health)
+               {
+                       Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_MAXHEALTH);
+                       return TRUE;
+               }
+               
+               self.ammo_fuel -= autocvar_g_td_turret_repair_cost;
+               trace_ent.SendFlags |= TNSF_STATUS;
+               trace_ent.health = bound(1, trace_ent.health + 100, trace_ent.max_health);
+               Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_REPAIR);
+               
+               return TRUE;
+       }
+       if(cmd_name == "buffturret")
+       {
+               if(trace_ent.realowner != self || !(trace_ent.turrcaps_flags & TFL_TURRCAPS_ISTURRET))
+               {
+                       Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_AIM_UPGRADE);
+                       return TRUE;
+               }
+               if(self.ammo_fuel < autocvar_g_td_turret_upgrade_cost)  
+               {
+                       Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_NOFUEL_UPGRADE, autocvar_g_td_turret_upgrade_cost);
+                       return TRUE;
+               }
+               if(trace_ent.turret_buff >= 3)
+               {
+                       Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_MAXPOWER);
+                       return TRUE;
+               }
+               
+               self.ammo_fuel -= autocvar_g_td_turret_upgrade_cost;
+               trace_ent.SendFlags |= TNSF_STATUS;
+               buffturret(trace_ent, 1.2);
+               Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_UPGRADE);
+               
+               return TRUE;
+       }
+       if(cmd_name == "turretremove")
+       {
+               if((trace_ent.turrcaps_flags & TFL_TURRCAPS_ISTURRET) && trace_ent.realowner == self)
+               {
+                       self.turret_cnt -= 1;
+                       Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_REMOVE);
+                       WaypointSprite_Kill(trace_ent.sprite);
+                       remove(trace_ent.tur_head);
+                       remove(trace_ent);
+                       return TRUE;
+               }
+               Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TD_AIM_REMOVE);
+               return TRUE;
+       }
+       
+       return FALSE;
+}
+
+MUTATOR_DEFINITION(gamemode_td)
+{
+       MUTATOR_HOOK(MonsterSpawn, td_MonsterSpawn, CBC_ORDER_ANY);
+       MUTATOR_HOOK(MonsterDies, td_MonsterDies, CBC_ORDER_ANY);
+       MUTATOR_HOOK(MonsterMove, td_MonsterMove, CBC_ORDER_ANY);
+       MUTATOR_HOOK(MonsterFindTarget, td_MonsterFindTarget, CBC_ORDER_ANY);
+       MUTATOR_HOOK(MonsterCheckBossFlag, td_MonsterCheckBossFlag, CBC_ORDER_ANY);
+       MUTATOR_HOOK(SetStartItems, td_SetStartItems, CBC_ORDER_ANY);
+       MUTATOR_HOOK(TurretValidateTarget, td_TurretValidateTarget, CBC_ORDER_ANY);
+       MUTATOR_HOOK(TurretSpawn, td_TurretSpawn, CBC_ORDER_ANY);
+       MUTATOR_HOOK(TurretDies, td_TurretDies, CBC_ORDER_ANY);
+       MUTATOR_HOOK(GiveFragsForKill, td_GiveFragsForKill, CBC_ORDER_ANY);
+       MUTATOR_HOOK(PlayerPreThink, td_PlayerThink, CBC_ORDER_ANY);
+       MUTATOR_HOOK(PlayerDies, td_PlayerDies, CBC_ORDER_ANY);
+       MUTATOR_HOOK(PlayerDamage_Calculate, td_PlayerDamage, CBC_ORDER_ANY);
+       MUTATOR_HOOK(PlayerSpawn, td_PlayerSpawn, CBC_ORDER_ANY);
+       MUTATOR_HOOK(VehicleSpawn, td_DisableVehicles, CBC_ORDER_ANY);
+       MUTATOR_HOOK(SV_ParseClientCommand, td_PlayerCommand, CBC_ORDER_ANY);
+       
+       MUTATOR_ONADD
+       {
+               if(time > 1) // game loads at time 1
+                       error("This is a game type and it cannot be added at runtime.");        
+               cvar_settemp("g_monsters", "1");
+               cvar_settemp("g_turrets", "1");
+               td_Init();
+       }
+
+       MUTATOR_ONREMOVE
+       {
+               error("This is a game type and it cannot be removed at runtime.");
+       }
+
+       return FALSE;
+}
diff --git a/qcsrc/server/mutators/gamemode_towerdefense.qh b/qcsrc/server/mutators/gamemode_towerdefense.qh
new file mode 100644 (file)
index 0000000..b199fd5
--- /dev/null
@@ -0,0 +1,62 @@
+// Counters
+float monster_count, totalmonsters;
+float n_knights, n_dogs, n_ogres, n_shamblers, n_wizards, n_shalraths, n_soldiers, n_hknights, n_enforcers, n_demons, n_zombies, n_tarbabies, n_fish, n_spiders;
+float current_monsters;
+float waterspawns_count, flyspawns_count;
+float wave_count, max_waves;
+float max_turrets;
+
+// Monster defs
+.float drop_size;
+float m_speed_run;
+float m_speed_walk;
+
+// Turret defs
+.float turret_buff;
+
+// TD defs
+.float stat_current_wave;
+.float stat_totalwaves;
+.float spawntype;
+float spawn_delay;
+float max_current;
+float ignore_turrets;
+float SWARM_NORMAL     = 0;
+float SWARM_WEAK       = 1;
+float SWARM_STRONG     = 2;
+float SWARM_FLY                = 3;
+float SWARM_SWIM       = 4;
+float build_time;
+float td_dont_end;
+void(float starting) wave_end;
+.float turret_cnt;
+float td_gencount;
+void() spawnfunc_td_controller;
+float current_phase;
+#define PHASE_BUILD    1
+#define PHASE_COMBAT   2
+
+// Scores
+#define SP_TD_KILLS    0
+#define SP_TD_TURKILLS         2
+#define SP_TD_SCORE    4
+#define SP_TD_DEATHS   6
+#define SP_TD_SUICIDES         8
+
+// Controller
+.float maxwaves;
+.float monstercount;
+.float startwave;
+.float dontend;
+.float maxturrets;
+.float buildtime;
+.float mspeed_run;
+.float mspeed_walk;
+.float spawndelay;
+.float maxcurrent;
+.float ignoreturrets;
+
+// Generator
+float gendestroyed;
+#define GENERATOR_MIN '-52 -52 -14'
+#define GENERATOR_MAX '52 52 75'
\ No newline at end of file