From: Mario Date: Thu, 7 Mar 2013 04:04:50 +0000 (+1100) Subject: Add missing files X-Git-Tag: xonotic-v0.8.0~241^2^2~468 X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=0a7b3d276c506fc2f4b656ead1c722af6b25f8ec;p=xonotic%2Fxonotic-data.pk3dir.git Add missing files --- diff --git a/qcsrc/server/mutators/gamemode_towerdefense.qc b/qcsrc/server/mutators/gamemode_towerdefense.qc new file mode 100644 index 000000000..fc3075078 --- /dev/null +++ b/qcsrc/server/mutators/gamemode_towerdefense.qc @@ -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 index 000000000..b199fd5e8 --- /dev/null +++ b/qcsrc/server/mutators/gamemode_towerdefense.qh @@ -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