From 2a975ac5a71da64febdf4be500f7995312cd093b Mon Sep 17 00:00:00 2001 From: Mario Date: Sat, 18 May 2013 00:40:17 +1000 Subject: [PATCH] Begin converting tower defense to a teamplay mode --- effectinfo.txt | 40 + qcsrc/common/mapinfo.qc | 2 +- qcsrc/server/autocvars.qh | 25 +- qcsrc/server/g_world.qc | 34 - qcsrc/server/generator.qc | 2 +- qcsrc/server/monsters/lib/monsters.qc | 2 +- qcsrc/server/monsters/lib/spawn.qc | 7 +- .../server/mutators/gamemode_towerdefense.qc | 1151 ++++++----------- .../server/mutators/gamemode_towerdefense.qh | 60 +- qcsrc/server/mutators/mutators.qh | 2 +- qcsrc/server/teamplay.qc | 5 +- 11 files changed, 483 insertions(+), 847 deletions(-) diff --git a/effectinfo.txt b/effectinfo.txt index c2f702fa5..05e881c5d 100644 --- a/effectinfo.txt +++ b/effectinfo.txt @@ -7977,3 +7977,43 @@ size 100 100 alpha 190 190 180 sizeincrease -80 color 0xFFFFFF 0xFFFFFF + +// waypoint_link_red - red waypoint linking effect +effect waypoint_link_red +countabsolute 1 +type beam +tex 200 200 +size 1 1 +alpha 256 256 64 +color 0xFF0F0F 0xFF0F0F +sizeincrease 1 + +// waypoint_link_blue - blue waypoint linking effect +effect waypoint_link_blue +countabsolute 1 +type beam +tex 200 200 +size 1 1 +alpha 256 256 64 +color 0x0F0FFF 0x0F0FFF +sizeincrease 1 + +// waypoint_link_yellow - yellow waypoint linking effect +effect waypoint_link_yellow +countabsolute 1 +type beam +tex 200 200 +size 1 1 +alpha 256 256 64 +color 0xFFFF0F 0xFFFF0F +sizeincrease 1 + +// waypoint_link_pink - pink waypoint linking effect +effect waypoint_link_pink +countabsolute 1 +type beam +tex 200 200 +size 1 1 +alpha 256 256 64 +color 0xFF0FFF 0xFF0FFF +sizeincrease 1 diff --git a/qcsrc/common/mapinfo.qc b/qcsrc/common/mapinfo.qc index 97ee8cc9b..bc557a73d 100644 --- a/qcsrc/common/mapinfo.qc +++ b/qcsrc/common/mapinfo.qc @@ -313,7 +313,7 @@ float _MapInfo_Generate(string pFilename) // 0: failure, 1: ok ent, 2: ok bsp MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_CTF; else if(v == "team_CTF_blueflag") MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_CTF; - else if(v == "td_generator" || v == "monster_swarm") + else if(v == "td_generator" || v == "td_spawnpoint") MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_TD; else if(v == "target_assault_roundend") MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_ASSAULT; diff --git a/qcsrc/server/autocvars.qh b/qcsrc/server/autocvars.qh index 8784af9f9..4457c603a 100644 --- a/qcsrc/server/autocvars.qh +++ b/qcsrc/server/autocvars.qh @@ -1230,30 +1230,6 @@ float autocvar_physics_ode; float autocvar_g_physical_items; float autocvar_g_physical_items_damageforcescale; float autocvar_g_physical_items_reset; -float autocvar_g_td_start_wave; -float autocvar_g_td_generator_health; -float autocvar_g_td_current_monsters; -float autocvar_g_td_generator_damaged_points; -float autocvar_g_td_monster_count; -float autocvar_g_td_monster_count_increment; -float autocvar_g_td_buildphase_time; -float autocvar_g_td_pvp; -float autocvar_g_td_max_waves; -float autocvar_g_td_kill_points; -float autocvar_g_td_turretkill_points; -float autocvar_g_td_generator_dontend; -float autocvar_g_td_force_settings; -float autocvar_g_td_turret_max; -float autocvar_g_td_monsters_skill_start; -float autocvar_g_td_monsters_skill_increment; -float autocvar_g_td_monsters_speed_walk; -float autocvar_g_td_monsters_speed_run; -float autocvar_g_td_monsters_spawn_delay; -float autocvar_g_td_monsters_spawnshield_time; -float autocvar_g_td_monsters_ignore_turrets; -float autocvar_g_td_turret_upgrade_cost; -float autocvar_g_td_turret_repair_cost; -float autocvar_g_td_barricade_damage; float autocvar_g_monsters; float autocvar_g_monsters_think_delay; float autocvar_g_monsters_max; @@ -1283,3 +1259,4 @@ float autocvar_g_touchexplode_radius; float autocvar_g_touchexplode_damage; float autocvar_g_touchexplode_edgedamage; float autocvar_g_touchexplode_force; +float autocvar_g_td_debug; diff --git a/qcsrc/server/g_world.qc b/qcsrc/server/g_world.qc index db5b1d44f..cf2d12612 100644 --- a/qcsrc/server/g_world.qc +++ b/qcsrc/server/g_world.qc @@ -2072,36 +2072,6 @@ float WinningCondition_RanOutOfSpawns() return WINNING_NO; } -// TD winning condition: -// game terminates if there are no generators (or 1 dies if td_dont_end is FALSE) -float gensurvived; -float WinningCondition_TowerDefense() -{ - WinningConditionHelper(); // set worldstatus - - if(inWarmupStage) - return WINNING_NO; - - // first check if the game has ended - if(gendestroyed == TRUE) - if(td_gencount < 1 || !td_dont_end) - { - ClearWinners(); - dprint("Everyone lost, ending game.\n"); - return WINNING_YES; - } - - if(gensurvived) - { - ClearWinners(); - checkrules_equality = TRUE; - return WINNING_YES; - } - - // Two or more teams remain - return WINNING_NO; -} - /* ============ CheckRules_World @@ -2257,10 +2227,6 @@ void CheckRules_World() { checkrules_status = WinningCondition_Onslaught(); // TODO remove this? } - else if(g_td) - { - checkrules_status = WinningCondition_TowerDefense(); - } else { checkrules_status = WinningCondition_Scores(fraglimit, leadlimit); diff --git a/qcsrc/server/generator.qc b/qcsrc/server/generator.qc index 5bc3d119e..1588aef6b 100644 --- a/qcsrc/server/generator.qc +++ b/qcsrc/server/generator.qc @@ -237,7 +237,7 @@ void generator_damage(float hp) } void generator_construct() -{ +{ self.netname = "Generator"; setorigin(self, self.origin); diff --git a/qcsrc/server/monsters/lib/monsters.qc b/qcsrc/server/monsters/lib/monsters.qc index a42ac1de4..06f9d4187 100644 --- a/qcsrc/server/monsters/lib/monsters.qc +++ b/qcsrc/server/monsters/lib/monsters.qc @@ -446,7 +446,7 @@ vector monster_pickmovetarget(entity targ) { self.monster_movestate = MONSTER_MOVE_OWNER; self.last_trace = time + 0.3; - if(self.monster_owner && self.monster_owner.classname != "monster_swarm") + if(self.monster_owner && self.monster_owner.classname != "td_spawnpoint") return self.monster_owner.origin; } case MONSTER_MOVE_SPAWNLOC: diff --git a/qcsrc/server/monsters/lib/spawn.qc b/qcsrc/server/monsters/lib/spawn.qc index 2912f0ad7..a756fad11 100644 --- a/qcsrc/server/monsters/lib/spawn.qc +++ b/qcsrc/server/monsters/lib/spawn.qc @@ -33,8 +33,11 @@ entity spawnmonster (string monster, entity spawnedby, entity own, vector orig, if(moveflag) e.monster_moveflags = moveflag; - if (spawnedby.classname == "monster_swarm") - e.monster_owner = own; + if (spawnedby.classname == "td_spawnpoint") + { + e.monster_owner = own; + e.team = spawnedby.team; + } else if(IS_PLAYER(spawnedby)) { if(teamplay && autocvar_g_monsters_teams) diff --git a/qcsrc/server/mutators/gamemode_towerdefense.qc b/qcsrc/server/mutators/gamemode_towerdefense.qc index 970373a84..fd178d29e 100644 --- a/qcsrc/server/mutators/gamemode_towerdefense.qc +++ b/qcsrc/server/mutators/gamemode_towerdefense.qc @@ -1,60 +1,76 @@ -// Tower Defense -// Gamemode by Mario +float redalive, bluealive, total_alive; -float td_moncount[MONSTER_LAST]; - -void spawnfunc_td_controller() +var float max_monsters = 20; +var float max_alive = 10; + +float total_killed; + +var float max_turrets = 3; + +.float newfuel; // hack to not give players fuel every time they spawn + +float last_check; + +.float turret_cnt; + +.float level; +.float last_trace; + +void td_debug(string input) { - if not(g_td) { remove(self); return; } - - if(autocvar_g_td_force_settings) + switch(autocvar_g_td_debug) + { + case 1: dprint(input); break; + case 2: print(input); break; + } +} + +void td_waypoint_link(float tm, vector from, vector to) +{ + switch(tm) + { + case NUM_TEAM_1: + WarpZone_TrailParticles(world, particleeffectnum("waypoint_link_red"), from, to); + break; + case NUM_TEAM_2: + WarpZone_TrailParticles(world, particleeffectnum("waypoint_link_blue"), from, to); + break; + } +} + +void td_waypoint_think() +{ + if(gameover) { - // 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; + remove(self); + return; } + + if(time >= self.last_trace) + { + entity e, e2, e3; - self.netname = "Tower Defense controller entity"; - self.classname = "td_controller"; + e = find(world, targetname, self.target); + e2 = find(world, target, self.targetname); + e3 = find(world, targetname, self.target2); - gensurvived = FALSE; - - td_dont_end = ((self.dontend == 0) ? autocvar_g_td_generator_dontend : self.dontend); - max_waves = ((self.maxwaves == 0) ? autocvar_g_td_max_waves : self.maxwaves); - totalmonsters = ((self.monstercount == 0) ? autocvar_g_td_monster_count : self.monstercount); - wave_count = ((self.startwave == 0) ? autocvar_g_td_start_wave : self.startwave); - max_turrets = ((self.maxturrets == 0) ? autocvar_g_td_turret_max : self.maxturrets); - build_time = ((self.buildtime == 0) ? autocvar_g_td_buildphase_time : self.buildtime); - m_speed_walk = ((self.mspeed_walk == 0) ? autocvar_g_td_monsters_speed_walk : self.mspeed_walk); - m_speed_run = ((self.mspeed_run == 0) ? autocvar_g_td_monsters_speed_run : self.mspeed_run); - spawn_delay = ((self.spawndelay == 0) ? autocvar_g_td_monsters_spawn_delay : self.spawndelay); - max_current = ((self.maxcurrent == 0) ? autocvar_g_td_current_monsters : self.maxcurrent); - ignore_turrets = ((self.ignoreturrets == 0) ? autocvar_g_td_monsters_ignore_turrets : self.ignoreturrets); - - if(autocvar_g_td_monsters_skill_start) - monster_skill = autocvar_g_td_monsters_skill_start; + if(e.classname == "td_waypoint" || e.flags & FL_GENERATOR) + td_waypoint_link(self.team, self.origin, e.origin); + if(e2.classname == "td_spawnpoint") + td_waypoint_link(self.team, self.origin, e2.origin); + if(e3.classname == "td_waypoint" || e3.flags & FL_GENERATOR) + td_waypoint_link(self.team, self.origin, e3.origin); - wave_end(TRUE); + self.last_trace = time + 0.5; + } + + self.nextthink = time + 0.1; } void td_generator_die() { if(autocvar_sv_eventlog) GameLogEcho(":gendestroyed"); - - gendestroyed = TRUE; - - pointparticles(particleeffectnum("explosion_medium"), self.origin, '0 0 0', 1); - sound(self, CH_TRIGGER, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM); Send_Notification(NOTIF_ALL, world, MSG_MULTI, MULTI_TD_GENDESTROYED); @@ -62,7 +78,6 @@ void td_generator_die() self.takedamage = DAMAGE_NO; self.event_damage = func_null; self.enemy = world; - td_gencount -= 1; WaypointSprite_Kill(self.sprite); } @@ -72,10 +87,12 @@ void td_generator_damage(entity inflictor, entity attacker, float damage, float if(IS_PLAYER(attacker) || attacker.turrcaps_flags & TFL_TURRCAPS_ISTURRET || attacker.vehicle_flags & VHF_ISVEHICLE || self.takedamage == DAMAGE_NO) return; + entity head; + if (time > self.pain_finished) { self.pain_finished = time + 10; - play2all("onslaught/generator_underattack.wav"); + play2team(self.team, "onslaught/generator_underattack.wav"); } if (random() < 0.5) @@ -83,14 +100,24 @@ void td_generator_damage(entity inflictor, entity attacker, float damage, float else spamsound(self, CH_TRIGGER, "onslaught/ons_hit2.wav", VOL_BASE, ATTN_NORM); - Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_TD_GENDAMAGED); + + FOR_EACH_REALPLAYER(head) + if(!IsDifferentTeam(head, self)) + Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_TD_GENDAMAGED); self.health -= damage; WaypointSprite_UpdateHealth(self.sprite, self.health); - if(self.health <= 0) + if(self.health <= 0) + { + FOR_EACH_REALPLAYER(head) + if(!IsDifferentTeam(head, attacker)) + PlayerScore_Add(head, SP_TD_DESTROYS, 1); + + TeamScore_AddToTeam(attacker.team, ST_TD_DESTROYS, 1); td_generator_die(); + } self.SendFlags |= GSF_STATUS; } @@ -99,164 +126,119 @@ void td_generator_setup() { self.think = func_null; self.nextthink = -1; - self.solid = SOLID_BBOX; - self.takedamage = DAMAGE_AIM; - self.event_damage = td_generator_damage; - self.enemy = world; - self.movetype = MOVETYPE_NONE; - self.monster_attack = TRUE; + self.solid = SOLID_BBOX; + self.takedamage = DAMAGE_AIM; + self.event_damage = td_generator_damage; + self.movetype = MOVETYPE_NONE; + self.monster_attack = TRUE; + self.SendFlags = GSF_SETUP; self.netname = "Generator"; - self.SendFlags = GSF_SETUP; + self.SendFlags |= GSF_STATUS; - WaypointSprite_SpawnFixed(self.netname, self.origin + '0 0 90', self, sprite, RADARICON_OBJECTIVE, '1 0.5 0'); + WaypointSprite_SpawnFixed(self.netname, self.origin + '0 0 90', self, sprite, RADARICON_OBJECTIVE, Team_ColorRGB(self.team)); WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health); WaypointSprite_UpdateHealth(self.sprite, self.health); } -void spawnfunc_td_generator() +void AnnounceSpawn(string anounce) { - if not(g_td) { remove(self); return; } - - precache_sound("onslaught/generator_underattack.wav"); - precache_sound("onslaught/ons_hit1.wav"); - precache_sound("onslaught/ons_hit2.wav"); - precache_sound("weapons/rocket_impact.wav"); - - gendestroyed = FALSE; - - if not(self.health) - self.health = autocvar_g_td_generator_health; - - self.max_health = self.health; - - self.classname = "td_generator"; - self.flags = FL_GENERATOR; - td_gencount += 1; - - setsize(self, GENERATOR_MIN, GENERATOR_MAX); - - setorigin(self, self.origin + '0 0 20'); - droptofloor(); + entity e; + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_TD_ANNOUNCE_SPAWN, anounce); - generator_link(td_generator_setup); -} - -entity PickGenerator() -{ - entity generator, head; - if(td_gencount == 1) - generator = findflags(world, flags, FL_GENERATOR); - else - { - RandomSelection_Init(); - for(head = world;(head = findflags(head, flags, FL_GENERATOR)); ) - { - RandomSelection_Add(head, 0, string_null, 1, 1); - } - generator = RandomSelection_chosen_ent; - } - return generator; + FOR_EACH_REALCLIENT(e) soundto(MSG_ONE, e, CHAN_AUTO, "kh/alarm.wav", VOL_BASE, ATTN_NONE); } -void spawn_td_fuel(float fuel_size) +entity PickSpawn (float tm) { - 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'; -} + entity e; + RandomSelection_Init(); + for(e = world;(e = find(e, classname, "td_spawnpoint")); ) + if(e.team == tm) + RandomSelection_Add(e, 0, string_null, 1, 1); -void spawnfunc_td_waypoint() -{ - if not(g_td) { remove(self); return; } - - self.classname = "td_waypoint"; + return RandomSelection_chosen_ent; } -void spawnfunc_monster_swarm() +void TD_SpawnMonster(float tm, string mnster) { - 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'); + entity e, mon; - if(self.target == "") - dprint("Warning: monster_swarm entity without a set target\n"); + e = PickSpawn(tm); + + if(e == world) + { + td_debug("Warning: couldn't find any td_spawnpoint spawnpoints, no monsters will spawn!\n"); + return; + } + + mon = spawnmonster(mnster, e, e, e.origin, FALSE, 2); + if(e.target2) + { + if(random() <= 0.5 && e.target) + mon.target2 = e.target; + else + mon.target2 = e.target2; + } + else + mon.target2 = e.target; } -void barricade_touch() +string monster_type2string(float mnster) { - 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; + switch(mnster) + { + case MONSTER_ZOMBIE: return "zombie"; + case MONSTER_BRUTE: return "brute"; + case MONSTER_ANIMUS: return "animus"; + case MONSTER_SHAMBLER: return "shambler"; + case MONSTER_BRUISER: return "bruiser"; + case MONSTER_WYVERN: return "wyvern"; + case MONSTER_CERBERUS: return "cerberus"; + case MONSTER_SLIME: return "slime"; + case MONSTER_KNIGHT: return "knight"; + case MONSTER_STINGRAY: return "stingray"; + case MONSTER_MAGE: return "mage"; + case MONSTER_SPIDER: return "spider"; + default: return ""; + } } -void barricade_die() +float RandomMonster() { - self.takedamage = DAMAGE_NO; - self.event_damage = func_null; - - WaypointSprite_Kill(self.sprite); + RandomSelection_Init(); - pointparticles(particleeffectnum("explosion_medium"), self.origin, '0 0 0', 1); - sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM); + float i; - if(self.realowner) - self.realowner.turret_cnt -= 1; + for(i = MONSTER_FIRST + 1; i < MONSTER_LAST; ++i) + { + if(i == MONSTER_STINGRAY || i == MONSTER_WYVERN) + continue; // flying/swimming monsters not yet supported - self.think = SUB_Remove; - self.nextthink = time; + RandomSelection_Add(world, i, "", 1, 1); + } + + return RandomSelection_chosen_float; } -void barricade_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) +void SpawnMonsters(float tm) { - if not(attacker.flags & FL_MONSTER) return; + float whichmon; - self.health -= damage; - - WaypointSprite_UpdateHealth(self.sprite, self.health); + whichmon = RandomMonster(); - if(self.health < 1) - barricade_die(); + TD_SpawnMonster(tm, monster_type2string(whichmon)); } -void spawn_barricade() +entity PickGenerator(float tm) { - 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"); + entity head; - droptofloor(); + RandomSelection_Init(); + for(head = world;(head = findflags(head, flags, FL_GENERATOR)); ) + if(head.team != tm) + RandomSelection_Add(head, 0, string_null, 1, 1); - self.movetype = MOVETYPE_NONE; + return RandomSelection_chosen_ent; } float td_checkfuel(entity ent, string tur) @@ -291,8 +273,7 @@ void spawnturret(entity spawnedby, entity own, string turet, vector orig) self.playerid = own.playerid; self.angles_y = spawnedby.v_angle_y; spawnedby.turret_cnt += 1; - self.colormap = spawnedby.colormap; - self.colormod = '1 1 1'; + self.team = own.team; switch(turet) { @@ -301,7 +282,6 @@ void spawnturret(entity spawnedby, entity own, string turet, vector orig) case "walker": spawnfunc_turret_walker(); break; case "flac": spawnfunc_turret_flac(); break; case "towerbuff": spawnfunc_turret_fusionreactor(); break; - case "barricade": spawn_barricade(); break; default: Send_Notification(NOTIF_ONE, spawnedby, MSG_INFO, INFO_TD_INVALID); remove(self); self = oldself; return; } @@ -310,352 +290,185 @@ void spawnturret(entity spawnedby, entity own, string turet, vector orig) self = oldself; } -void buffturret (entity tur, float buff) +void spawn_td_fuel(float fuel_size) { - 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; + 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 AnnounceSpawn(string anounce) +// round handling +#define TD_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0)) +#define TD_ALIVE_TEAMS_OK() (TD_ALIVE_TEAMS() == 2) +void TD_RoundStart() { - entity e; - Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_TD_ANNOUNCE_SPAWN, anounce); - - FOR_EACH_REALCLIENT(e) soundto(MSG_ONE, e, CHAN_AUTO, "kh/alarm.wav", VOL_BASE, ATTN_NONE); + allowed_to_spawn = TRUE; } -entity PickSpawn (float strngth, float type) +void TD_count_alive_monsters() { - entity e; - RandomSelection_Init(); - for(e = world;(e = find(e, classname, "monster_swarm")); ) + entity head; + + total_alive = 0; + + FOR_EACH_MONSTER(head) { - if(flyspawns_count > 0 && type == SWARM_FLY && e.spawntype != SWARM_FLY) continue; - if(waterspawns_count > 0 && type == SWARM_SWIM && e.spawntype != SWARM_SWIM) continue; + if(head.health <= 0) continue; - RandomSelection_Add(e, 0, string_null, 1, 1); + ++total_alive; + + switch(head.team) + { + case NUM_TEAM_1: + ++redalive; + case NUM_TEAM_2: + ++bluealive; + } } +} - return RandomSelection_chosen_ent; +float TD_GetWinnerTeam() +{ + float winner_team = 0; + if(redalive >= 1) + winner_team = NUM_TEAM_1; + if(bluealive >= 1) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_2; + } + if(winner_team) + return winner_team; + return -1; // no monster left } -void TD_SpawnMonster(string mnster, float strngth, float type) +float TD_CheckWinner() { - entity e, mon; + if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0) + { + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER); + round_handler_Init(5, 1, 180); + return 1; + } - e = PickSpawn(strngth, type); + TD_count_alive_monsters(); - if(e == world) // couldn't find anything for our class, so check for normal spawns - e = PickSpawn(SWARM_NORMAL, SWARM_NORMAL); - - if(e == world) + if(total_alive < max_alive && time >= last_check && total_killed < max_monsters) { - dprint("Warning: couldn't find any monster_swarm spawnpoints, no monsters will spawn!\n"); - return; + SpawnMonsters(NUM_TEAM_1); + SpawnMonsters(NUM_TEAM_2); + + last_check = time + 0.5; } - - mon = spawnmonster(mnster, e, e, e.origin, FALSE, 2); - if(e.target2) + + if(total_killed < 1) + return 0; // nothing has died, can't be a tie + + if(TD_ALIVE_TEAMS() > 1) + return 0; + + float winner_team = TD_GetWinnerTeam(); + if(winner_team > 0) { - if(random() <= 0.5 && e.target) - mon.target2 = e.target; - else - mon.target2 = e.target2; + 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_)); + TeamScore_AddToTeam(winner_team, ST_SCORE, +1); } - else - mon.target2 = e.target; -} - -float Monster_GetStrength(float mnster) -{ - switch(mnster) + else if(winner_team == -1) { - default: - case MONSTER_BRUISER: - case MONSTER_ZOMBIE: - case MONSTER_SPIDER: - case MONSTER_SLIME: - case MONSTER_CERBERUS: - case MONSTER_WYVERN: - case MONSTER_STINGRAY: - return SWARM_WEAK; - case MONSTER_KNIGHT: - case MONSTER_BRUTE: - case MONSTER_SHAMBLER: - case MONSTER_MAGE: - case MONSTER_ANIMUS: - return SWARM_STRONG; - default: return SWARM_NORMAL; + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED); } + + round_handler_Init(5, 1, 180); + return 1; } -string monster_type2string(float mnster) +float TD_CheckTeams() { - switch(mnster) - { - case MONSTER_ZOMBIE: return "zombie"; - case MONSTER_BRUTE: return "brute"; - case MONSTER_ANIMUS: return "animus"; - case MONSTER_SHAMBLER: return "shambler"; - case MONSTER_BRUISER: return "bruiser"; - case MONSTER_WYVERN: return "wyvern"; - case MONSTER_CERBERUS: return "cerberus"; - case MONSTER_SLIME: return "slime"; - case MONSTER_KNIGHT: return "knight"; - case MONSTER_STINGRAY: return "stingray"; - case MONSTER_MAGE: return "mage"; - case MONSTER_SPIDER: return "spider"; - default: return ""; - } + allowed_to_spawn = TRUE; + + return TRUE; } -float Monster_GetType(float mnster) +// spawnfuncs +void spawnfunc_td_generator() { - switch(mnster) + if not(g_td) { remove(self); return; } + if not(self.team) { - default: - case MONSTER_BRUISER: - case MONSTER_ZOMBIE: - case MONSTER_SPIDER: - case MONSTER_SLIME: - case MONSTER_CERBERUS: - case MONSTER_BRUTE: - case MONSTER_SHAMBLER: - case MONSTER_MAGE: - case MONSTER_KNIGHT: - case MONSTER_ANIMUS: - return SWARM_NORMAL; - case MONSTER_WYVERN: - return SWARM_FLY; - case MONSTER_STINGRAY: - return SWARM_SWIM; + td_debug("Generator without a team, removing it.\n"); + remove(self); + return; } -} - -float RandomMonster() -{ - RandomSelection_Init(); - float i; + precache_sound("onslaught/generator_underattack.wav"); + precache_sound("onslaught/ons_hit1.wav"); + precache_sound("onslaught/ons_hit2.wav"); + precache_sound("weapons/rocket_impact.wav"); - for(i = MONSTER_FIRST + 1; i < MONSTER_LAST; ++i) - if(td_moncount[i] > 0) - if(i == MONSTER_STINGRAY || i == MONSTER_SHAMBLER || i == MONSTER_SLIME) - RandomSelection_Add(world, i, "", 0.2, 0.2); - else - RandomSelection_Add(world, i, "", 1, 1); + if not(self.health) + self.health = 1000; + + self.max_health = self.health; + self.classname = "td_generator"; + self.flags = FL_GENERATOR; - return RandomSelection_chosen_float; + setsize(self, GENERATOR_MIN, GENERATOR_MAX); + + setorigin(self, self.origin + '0 0 20'); + droptofloor(); + + generator_link(td_generator_setup); } -void combat_phase() +void spawnfunc_td_waypoint() { - float mstrength, montype, whichmon; - - current_phase = PHASE_COMBAT; - - if(monster_count <= 0) + if not(g_td) { remove(self); return; } + if not(self.team) { - wave_end(FALSE); + td_debug("Tower Defense waypoint without a team, removing it.\n"); + remove(self); return; } - self.think = combat_phase; + setsize(self, '-6 -6 -6', '6 6 6'); - whichmon = RandomMonster(); - mstrength = Monster_GetStrength(whichmon); - montype = Monster_GetType(whichmon); - - if(current_monsters <= max_current && whichmon) + if not(self.noalign) { - TD_SpawnMonster(monster_type2string(whichmon), mstrength, montype); - self.nextthink = time + spawn_delay; + setorigin(self, self.origin + '0 0 20'); + droptofloor(); } - else - self.nextthink = time + 6; -} - -void queue_monsters(float maxmonsters) -{ - float mc = 9; // note: shambler + slime = 1 - - if(waterspawns_count > 0) - mc += 1; - if(flyspawns_count > 0) - mc += 1; - - DistributeEvenly_Init(maxmonsters, mc); - - float i; - - for(i = MONSTER_FIRST + 1; i < MONSTER_LAST; ++i) - { - if(i == MONSTER_WYVERN) - if(flyspawns_count < 1) - continue; - - if(i == MONSTER_STINGRAY) - if(waterspawns_count < 1) - continue; - - if(i == MONSTER_SLIME) - td_moncount[i] = DistributeEvenly_Get(0.7); - else if(i == MONSTER_SHAMBLER) - td_moncount[i] = DistributeEvenly_Get(0.3); - else - td_moncount[i] = 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 = findflags(gen, flags, FL_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) - Send_Notification(NOTIF_ALL, world, MSG_ANNCE, ANNCE_PREPARE); - else if(cphase_updates == 3) - Send_Notification(NOTIF_ALL, world, MSG_ANNCE, ANNCE_NUM_3); - else if(cphase_updates == 4) - Send_Notification(NOTIF_ALL, world, MSG_ANNCE, ANNCE_NUM_2); - else if(cphase_updates == 5) - Send_Notification(NOTIF_ALL, world, MSG_ANNCE, ANNCE_NUM_1); - else if(cphase_updates == 6) - { - Send_Notification(NOTIF_ALL, world, MSG_ANNCE, ANNCE_BEGIN); - combat_phase_begin(); - } - - if(cphase_updates >= 6) - return; - - self.think = combat_phase_announce; - self.nextthink = time + 1; + self.classname = "td_waypoint"; + self.think = td_waypoint_think; + self.nextthink = time + 0.1; } -void build_phase() +void spawnfunc_td_controller() { - entity head; - float n_players = 0, gen_washealed = FALSE, mcount, mskill; - - current_phase = PHASE_BUILD; - - for(head = world;(head = findflags(head, flags, FL_GENERATOR)); ) - { - if(head.health < head.max_health) - { - gen_washealed = TRUE; - pointparticles(particleeffectnum("healing_fx"), head.origin, '0 0 0', 1); - head.health = head.max_health; - WaypointSprite_UpdateHealth(head.sprite, head.health); - head.SendFlags |= GSF_STATUS; - } - head.takedamage = DAMAGE_NO; - } - - 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; - } - - mcount = autocvar_g_td_monster_count_increment * wave_count; - mskill = n_players * 0.02; - - totalmonsters += mcount; - monster_skill += autocvar_g_td_monsters_skill_increment; - monster_skill += mskill; - - if(monster_skill < 1) monster_skill = 1; - if(totalmonsters < 1) totalmonsters = ((autocvar_g_td_monster_count > 0) ? autocvar_g_td_monster_count : 10); - if(wave_count < 1) wave_count = 1; - - Send_Notification(NOTIF_ALL, world, MSG_MULTI, MULTI_TD_PHASE_BUILD, wave_count, totalmonsters, build_time); - - 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); - } - - 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; + if not(g_td) { remove(self); return; } } -void wave_end(float starting) +void spawnfunc_td_spawnpoint() { - 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 not(g_td) { remove(self); return; } - if(wave_count >= max_waves) - { - gensurvived = TRUE; - return; - } + self.classname = "td_spawnpoint"; - if not(starting) - wave_count += 1; - - self.think = build_phase; - self.nextthink = time + 3; + self.effects = EF_STARDUST; } +// initialization stuff 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(2, SFL_SORT_PRIO_SECONDARY, SFL_SORT_PRIO_SECONDARY, TRUE); + ScoreInfo_SetLabel_TeamScore(ST_TD_DESTROYS, "destroyed", SFL_SORT_PRIO_PRIMARY); + ScoreInfo_SetLabel_PlayerScore(SP_TD_DESTROYS,"destroyed", SFL_SORT_PRIO_PRIMARY); ScoreRules_basics_end(); } @@ -682,117 +495,79 @@ void td_DelayedInit() void td_Initialize() { InitializeEntity(world, td_DelayedInit, INITPRIO_GAMETYPE); + + round_handler_Spawn(TD_CheckTeams, TD_CheckWinner, TD_RoundStart); + round_handler_Init(5, 10, 180); } -MUTATOR_HOOKFUNCTION(td_TurretValidateTarget) +// mutator hooks +MUTATOR_HOOKFUNCTION(td_TurretSpawn) { - if(time < game_starttime || current_phase != PHASE_COMBAT || gameover) - { - turret_target = world; - return FALSE; // battle hasn't started - } - - 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) + if(self.realowner == world) 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; + self.bot_attack = FALSE; return FALSE; } -MUTATOR_HOOKFUNCTION(td_PlayerSpawn) +MUTATOR_HOOKFUNCTION(td_MonsterSpawn) { - self.bot_attack = FALSE; + if(!self.team || !self.realowner) + { + td_debug(strcat("Removed monster ", self.netname, " with team ", ftos(self.team), "\n")); + WaypointSprite_Kill(self.sprite); + if(self.weaponentity) remove(self.weaponentity); + remove(self); + return FALSE; + } - Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_TD_PROTECT); + self.candrop = FALSE; + self.bot_attack = FALSE; + self.ammo_fuel = bound(20, 20 * self.level, 100); + self.target_range = 300; + self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_CORPSE | DPCONTENTS_MONSTERCLIP; 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) +MUTATOR_HOOKFUNCTION(td_MonsterDies) { - 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 && IS_PLAYER(frag_target) && IS_PLAYER(frag_attacker)) + vector backuporigin; + entity oldself; + + total_killed++; + + backuporigin = self.origin; + oldself = self; + self = spawn(); + + self.gravity = 1; + setorigin(self, backuporigin + '0 0 5'); + spawn_td_fuel(oldself.ammo_fuel); + self.touch = M_Item_Touch; + if(self == world) { - frag_attacker.typehitsound += 1; - frag_damage = 0; + self = oldself; + return FALSE; } - - if(frag_attacker.turrcaps_flags & TFL_TURRCAPS_ISTURRET && IS_PLAYER(frag_target)) - 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; - + SUB_SetFade(self, time + 5, 1); + + self = oldself; + return FALSE; } -MUTATOR_HOOKFUNCTION(td_MonsterCheckBossFlag) -{ - // No minibosses in tower defense - return TRUE; -} - -MUTATOR_HOOKFUNCTION(td_MonsterMove) +MUTATOR_HOOKFUNCTION(td_MonsterThink) { - entity head; - float n_players = 0; - - FOR_EACH_PLAYER(head) { ++n_players; } - if(n_players < 1) return TRUE; + if(time <= game_starttime && round_handler_IsActive()) + return TRUE; + + if(IS_PLAYER(self.enemy)) + self.enemy = world; if not(self.enemy) // don't change targets while attacking - if((vlen(monster_target.origin - self.origin) <= 100 && monster_target.classname == "td_waypoint") || (vlen(monster_target.origin - self.origin) <= 200 && (self.flags & FL_FLY) && monster_target.classname == "td_waypoint")) + if(vlen(monster_target.origin - self.origin) <= 100) { if(monster_target.target2) { @@ -807,150 +582,81 @@ MUTATOR_HOOKFUNCTION(td_MonsterMove) monster_target = find(world, targetname, self.target2); if(monster_target == world) - monster_target = PickGenerator(); - } - - monster_speed_run = (m_speed_run + random() * 4) * monster_skill; - monster_speed_walk = (m_speed_walk + random() * 4) * monster_skill; - - return FALSE; -} - -MUTATOR_HOOKFUNCTION(td_MonsterSpawn) -{ - if(self.realowner == world) // nothing spawned it, so kill it - { - WaypointSprite_Kill(self.sprite); - remove(self.weaponentity); - remove(self); - return TRUE; + monster_target = PickGenerator(self.team); + + if(monster_target == world) + return TRUE; // no generators or waypoints?! } - current_monsters += 1; - - self.spawnshieldtime = time + autocvar_g_td_monsters_spawnshield_time; + td_debug(sprintf("Monster target: %s. Monster target2: %s. Monster target entity: %s.\n", self.target, self.target2, etos(monster_target))); - self.drop_size = bound(5, self.health * 0.05, autocvar_g_pickup_fuel_max); + monster_speed_run = (150 + random() * 4) * monster_skill; + monster_speed_walk = (100 + random() * 4) * monster_skill; - self.target_range = 600; - - self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_CORPSE | DPCONTENTS_MONSTERCLIP; - - td_moncount[self.monsterid] -= 1; - - return TRUE; -} - -MUTATOR_HOOKFUNCTION(td_MonsterDies) -{ - entity oldself; - vector backuporigin; - - monster_count -= 1; - current_monsters -= 1; - monsters_killed += 1; - - if(IS_PLAYER(frag_attacker)) - { - PlayerScore_Add(frag_attacker, SP_TD_SCORE, autocvar_g_td_kill_points); - PlayerScore_Add(frag_attacker, SP_TD_KILLS, 1); - } - else if(IS_PLAYER(frag_attacker.realowner)) - if(frag_attacker.turrcaps_flags & TFL_TURRCAPS_ISTURRET) - { - 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 + autocvar_g_monsters_drop_time, 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; - } + entity e; for(e = world;(e = findflags(e, monster_attack, TRUE)); ) { if(ignore_turrets) if(e.turrcaps_flags & TFL_TURRCAPS_ISTURRET) continue; + + if(e.flags & FL_MONSTER) + continue; // don't attack other monsters? if(monster_isvalidtarget(e, self)) - if((vlen(trace_endpos - self.origin) < 200 && e.turrcaps_flags & TFL_TURRCAPS_ISTURRET) || (vlen(trace_endpos - self.origin) < 200 && !(e.flags & FL_GENERATOR)) || (vlen(trace_endpos - self.origin) < 500 && e.flags & FL_GENERATOR)) - { self.enemy = e; - } } return TRUE; } -MUTATOR_HOOKFUNCTION(td_SetStartItems) +MUTATOR_HOOKFUNCTION(td_PlayerSpawn) { - start_ammo_fuel = 150; // to be nice... + self.monster_attack = FALSE; + self.bot_attack = FALSE; + + if(self.newfuel) + { + self.ammo_fuel = self.newfuel; + self.newfuel = 0; + } return FALSE; } -MUTATOR_HOOKFUNCTION(td_SetModname) -{ - // TODO: find out why td_Initialize doesn't work for TD stats... - addstat(STAT_CURRENT_WAVE, AS_FLOAT, stat_current_wave); - addstat(STAT_TOTALWAVES, AS_FLOAT, stat_totalwaves); - - return FALSE; -} - -MUTATOR_HOOKFUNCTION(td_TurretSpawn) +MUTATOR_HOOKFUNCTION(td_Damage) { - if(self.realowner == world) - return TRUE; // wasn't spawned by a player + if(IS_PLAYER(frag_attacker)) + if(frag_target.flags & FL_MONSTER) + frag_damage = 0; - self.bot_attack = FALSE; - buffturret(self, 0.5); + if(IS_PLAYER(frag_target)) + { + frag_damage = 0; + if(frag_attacker != frag_target) + frag_force = '0 0 0'; + } 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? + vector org; + makevectors(self.v_angle); - WarpZone_TraceLine(self.origin, self.origin + v_forward * 100, MOVE_HITMODEL, self); + + org = self.origin + self.view_ofs + v_forward * 100; + + tracebox(self.origin + self.view_ofs, '-16 -16 -16', '16 16 16', org, MOVE_NORMAL, self); entity targ = trace_ent; if(targ.owner.realowner == self) targ = targ.owner; @@ -959,7 +665,7 @@ MUTATOR_HOOKFUNCTION(td_PlayerCommand) { if(argv(1) == "list") { - Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_LIST, "mlrs walker plasma towerbuff flac barricade"); + Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_LIST, "mlrs walker plasma towerbuff flac"); return TRUE; } if(!IS_PLAYER(self) || self.health <= 0) @@ -982,58 +688,6 @@ MUTATOR_HOOKFUNCTION(td_PlayerCommand) return TRUE; } - if(cmd_name == "repairturret") - { - if((targ.playerid != self.playerid || targ.realowner != self) || !(targ.turrcaps_flags & TFL_TURRCAPS_ISTURRET)) - { - Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_AIM_REPAIR); - return TRUE; - } - if(self.ammo_fuel < autocvar_g_td_turret_repair_cost) - { - Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_NOFUEL_REPAIR, autocvar_g_td_turret_repair_cost); - return TRUE; - } - if(targ.health >= targ.max_health) - { - Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_MAXHEALTH); - return TRUE; - } - - self.ammo_fuel -= autocvar_g_td_turret_repair_cost; - targ.SendFlags |= TNSF_STATUS; - targ.health = bound(1, targ.health + 100, targ.max_health); - WaypointSprite_UpdateHealth(targ.sprite, targ.health); - Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_REPAIR); - - return TRUE; - } - if(cmd_name == "buffturret") - { - if((targ.playerid != self.playerid || targ.realowner != self) || !(targ.turrcaps_flags & TFL_TURRCAPS_ISTURRET)) - { - Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_AIM_UPGRADE); - return TRUE; - } - if(self.ammo_fuel < autocvar_g_td_turret_upgrade_cost) - { - Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_NOFUEL_UPGRADE, autocvar_g_td_turret_upgrade_cost); - return TRUE; - } - if(targ.turret_buff >= 5) - { - Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_MAXPOWER); - return TRUE; - } - - self.ammo_fuel -= autocvar_g_td_turret_upgrade_cost; - targ.SendFlags |= TNSF_STATUS; - buffturret(targ, 1.2); - WaypointSprite_UpdateHealth(targ.sprite, targ.health); - Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_UPGRADE); - - return TRUE; - } if(cmd_name == "turretremove") { if((targ.turrcaps_flags & TFL_TURRCAPS_ISTURRET) && (targ.playerid == self.playerid || targ.realowner == self)) @@ -1054,6 +708,8 @@ MUTATOR_HOOKFUNCTION(td_PlayerCommand) MUTATOR_HOOKFUNCTION(td_ClientConnect) { + self.newfuel = 75; + entity t; self.turret_cnt = 0; @@ -1062,33 +718,80 @@ MUTATOR_HOOKFUNCTION(td_ClientConnect) if(t.playerid == self.playerid) { t.realowner = self; - self.turret_cnt += 1; + self.turret_cnt += 1; // nice try + } + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(td_DisableVehicles) +{ + return TRUE; +} + +MUTATOR_HOOKFUNCTION(td_SetModname) +{ + g_cloaked = 1; + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(td_TurretValidateTarget) +{ + if(time < game_starttime || (time <= game_starttime && round_handler_IsActive()) || gameover) + { + turret_target = world; + return FALSE; // battle hasn't started } + 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; + + if(!IsDifferentTeam(turret_target, turret)) + turret_target = world; + return FALSE; } -MUTATOR_DEFINITION(gamemode_td) +MUTATOR_HOOKFUNCTION(td_TurretDies) { + if(self.realowner) + self.realowner.turret_cnt -= 1; + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(td_GetTeamCount) +{ + ret_float = 2; + + return FALSE; +} + +MUTATOR_DEFINITION(gamemode_towerdefense) +{ + MUTATOR_HOOK(TurretSpawn, td_TurretSpawn, CBC_ORDER_ANY); 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(MonsterMove, td_MonsterThink, 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(SetModname, td_SetModname, 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(PlayerDamage_Calculate, td_Damage, CBC_ORDER_ANY); MUTATOR_HOOK(SV_ParseClientCommand, td_PlayerCommand, CBC_ORDER_ANY); MUTATOR_HOOK(ClientConnect, td_ClientConnect, CBC_ORDER_ANY); - + MUTATOR_HOOK(VehicleSpawn, td_DisableVehicles, CBC_ORDER_ANY); + MUTATOR_HOOK(SetModname, td_SetModname, CBC_ORDER_ANY); + MUTATOR_HOOK(TurretValidateTarget, td_TurretValidateTarget, CBC_ORDER_ANY); + MUTATOR_HOOK(TurretDies, td_TurretDies, CBC_ORDER_ANY); + MUTATOR_HOOK(GetTeamCount, td_GetTeamCount, CBC_ORDER_ANY); + MUTATOR_ONADD { if(time > 1) // game loads at time 1 diff --git a/qcsrc/server/mutators/gamemode_towerdefense.qh b/qcsrc/server/mutators/gamemode_towerdefense.qh index 8864ee75a..30b08d6f3 100644 --- a/qcsrc/server/mutators/gamemode_towerdefense.qh +++ b/qcsrc/server/mutators/gamemode_towerdefense.qh @@ -1,60 +1,6 @@ -// Counters -float monster_count, totalmonsters; -float current_monsters; -float waterspawns_count, flyspawns_count; -float wave_count, max_waves; -float max_turrets; +float FL_GENERATOR = 2048; -// 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; -float FL_GENERATOR = 2048; \ No newline at end of file +#define SP_TD_DESTROYS 4 +#define ST_TD_DESTROYS 1 \ No newline at end of file diff --git a/qcsrc/server/mutators/mutators.qh b/qcsrc/server/mutators/mutators.qh index f58fdcccb..808d91c14 100644 --- a/qcsrc/server/mutators/mutators.qh +++ b/qcsrc/server/mutators/mutators.qh @@ -7,7 +7,7 @@ MUTATOR_DECLARATION(gamemode_keepaway); MUTATOR_DECLARATION(gamemode_ctf); MUTATOR_DECLARATION(gamemode_nexball); MUTATOR_DECLARATION(gamemode_onslaught); -MUTATOR_DECLARATION(gamemode_td); +MUTATOR_DECLARATION(gamemode_towerdefense); MUTATOR_DECLARATION(gamemode_domination); MUTATOR_DECLARATION(gamemode_lms); diff --git a/qcsrc/server/teamplay.qc b/qcsrc/server/teamplay.qc index e2805b913..6c9f64bcb 100644 --- a/qcsrc/server/teamplay.qc +++ b/qcsrc/server/teamplay.qc @@ -108,10 +108,11 @@ void InitGameplayMode() if(g_td) { - fraglimit_override = 0; // no limits in TD - it's a survival mode + ActivateTeamplay(); + fraglimit_override = 0; // not supported by TD leadlimit_override = 0; timelimit_override = 0; - MUTATOR_ADD(gamemode_td); + MUTATOR_ADD(gamemode_towerdefense); } if(g_lms) -- 2.39.2