From: Mario Date: Tue, 7 May 2013 08:44:25 +0000 (+1000) Subject: Merge branch 'master' into Mario/monsters X-Git-Tag: xonotic-v0.8.0~241^2^2~248 X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=78776096e605e2cbcddeb7e4a022e4a38c8c67f6;p=xonotic%2Fxonotic-data.pk3dir.git Merge branch 'master' into Mario/monsters --- 78776096e605e2cbcddeb7e4a022e4a38c8c67f6 diff --cc qcsrc/client/View.qc index 6e71bb181,d27fd4d98..d75635037 --- a/qcsrc/client/View.qc +++ b/qcsrc/client/View.qc @@@ -1073,12 -1073,15 +1073,17 @@@ void CSQC_UpdateView(float w, float h //else { - if(gametype == MAPINFO_TYPE_FREEZETAG) + if(getstati(STAT_FROZEN)) + drawfill('0 0 0', eX * vid_conwidth + eY * vid_conheight, '0.25 0.90 1', autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE); + if(getstatf(STAT_REVIVE_PROGRESS)) { - DrawCircleClippedPic(eX * 0.5 * vid_conwidth + eY * 0.6 * vid_conheight, 0.1 * vid_conheight, "gfx/crosshair_ring.tga", getstatf(STAT_REVIVE_PROGRESS), '0.25 0.90 1', autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE); - drawstring_aspect(eY * 0.64 * vid_conheight, "Revival progress", eX * vid_conwidth + eY * 0.025 * vid_conheight, '1 1 1', 1, DRAWFLAG_NORMAL); + if(getstati(STAT_FROZEN)) + drawfill('0 0 0', eX * vid_conwidth + eY * vid_conheight, '0.25 0.90 1', autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE); + if(getstatf(STAT_REVIVE_PROGRESS)) + { + DrawCircleClippedPic(eX * 0.5 * vid_conwidth + eY * 0.6 * vid_conheight, 0.1 * vid_conheight, "gfx/crosshair_ring.tga", getstatf(STAT_REVIVE_PROGRESS), '0.25 0.90 1', autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE); + drawstring_aspect(eY * 0.64 * vid_conheight, _("Revival progress"), eX * vid_conwidth + eY * 0.025 * vid_conheight, '1 1 1', 1, DRAWFLAG_NORMAL); + } } if(autocvar_r_letterbox == 0) diff --cc qcsrc/common/constants.qh index 510a498cc,cbda9dcdd..2450b25aa --- a/qcsrc/common/constants.qh +++ b/qcsrc/common/constants.qh @@@ -179,13 -177,8 +179,14 @@@ const float STAT_SECRETS_TOTAL = 70 const float STAT_SECRETS_FOUND = 71; const float STAT_RESPAWN_TIME = 72; + const float STAT_ROUNDSTARTTIME = 73; +const float STAT_CURRENT_WAVE = 73; +const float STAT_TOTALWAVES = 74; + +const float STAT_MONSTERS_TOTAL = 75; +const float STAT_MONSTERS_KILLED = 76; + // mod stats (1xx) const float STAT_REDALIVE = 100; const float STAT_BLUEALIVE = 101; diff --cc qcsrc/common/notifications.qh index 231a04d30,d1aeba001..93907ecb4 --- a/qcsrc/common/notifications.qh +++ b/qcsrc/common/notifications.qh @@@ -704,29 -623,8 +715,28 @@@ void Send_Notification_WOVA MSG_MULTI_NOTIF(1, ITEM_WEAPON_NOAMMO, NO_MSG, INFO_ITEM_WEAPON_NOAMMO, CENTER_ITEM_WEAPON_NOAMMO) \ MSG_MULTI_NOTIF(1, ITEM_WEAPON_PRIMORSEC, NO_MSG, INFO_ITEM_WEAPON_PRIMORSEC, CENTER_ITEM_WEAPON_PRIMORSEC) \ MSG_MULTI_NOTIF(1, ITEM_WEAPON_UNAVAILABLE, NO_MSG, INFO_ITEM_WEAPON_UNAVAILABLE, CENTER_ITEM_WEAPON_UNAVAILABLE) \ - MSG_MULTI_NOTIF(1, MULTI_ARENA_BEGIN, ANNCE_BEGIN, NO_MSG, CENTER_ARENA_BEGIN) \ MSG_MULTI_NOTIF(1, MULTI_COUNTDOWN_BEGIN, ANNCE_BEGIN, NO_MSG, CENTER_COUNTDOWN_BEGIN) \ MSG_MULTI_NOTIF(1, MULTI_MINSTA_FINDAMMO, ANNCE_NUM_10, NO_MSG, CENTER_MINSTA_FINDAMMO_FIRST) \ + MSG_MULTI_NOTIF(1, MULTI_TD_AIM_REMOVE, NO_MSG, INFO_TD_AIM_REMOVE, CENTER_TD_AIM_REMOVE) \ + MSG_MULTI_NOTIF(1, MULTI_TD_AIM_REPAIR, NO_MSG, INFO_TD_AIM_REPAIR, CENTER_TD_AIM_REPAIR) \ + MSG_MULTI_NOTIF(1, MULTI_TD_AIM_UPGRADE, NO_MSG, INFO_TD_AIM_UPGRADE, CENTER_TD_AIM_UPGRADE) \ + MSG_MULTI_NOTIF(1, MULTI_TD_CANTSPAWN, NO_MSG, INFO_TD_CANTSPAWN, CENTER_TD_CANTSPAWN) \ + MSG_MULTI_NOTIF(1, MULTI_TD_DISABLED, NO_MSG, INFO_TD_DISABLED, CENTER_TD_DISABLED) \ + MSG_MULTI_NOTIF(1, MULTI_TD_GENDESTROYED, NO_MSG, INFO_TD_GENDESTROYED, CENTER_TD_GENDESTROYED) \ + MSG_MULTI_NOTIF(1, MULTI_TD_LIST, NO_MSG, INFO_TD_LIST, CENTER_TD_LIST) \ + MSG_MULTI_NOTIF(1, MULTI_TD_MAXHEALTH, NO_MSG, INFO_TD_MAXHEALTH, CENTER_TD_MAXHEALTH) \ + MSG_MULTI_NOTIF(1, MULTI_TD_MAXPOWER, NO_MSG, INFO_TD_MAXPOWER, CENTER_TD_MAXPOWER) \ + MSG_MULTI_NOTIF(1, MULTI_TD_MAXTURRETS, NO_MSG, INFO_TD_MAXTURRETS, CENTER_TD_MAXTURRETS) \ + MSG_MULTI_NOTIF(1, MULTI_TD_NOFUEL, NO_MSG, INFO_TD_NOFUEL, CENTER_TD_NOFUEL) \ + MSG_MULTI_NOTIF(1, MULTI_TD_NOFUEL_REPAIR, NO_MSG, INFO_TD_NOFUEL_REPAIR, CENTER_TD_NOFUEL_REPAIR) \ + MSG_MULTI_NOTIF(1, MULTI_TD_NOFUEL_UPGRADE, NO_MSG, INFO_TD_NOFUEL_UPGRADE, CENTER_TD_NOFUEL_UPGRADE) \ + MSG_MULTI_NOTIF(1, MULTI_TD_PHASE_BUILD, NO_MSG, INFO_TD_PHASE_BUILD, CENTER_TD_PHASE_BUILD) \ + MSG_MULTI_NOTIF(1, MULTI_TD_PHASE_COMBAT, NO_MSG, INFO_TD_PHASE_COMBAT, CENTER_TD_PHASE_COMBAT) \ + MSG_MULTI_NOTIF(1, MULTI_TD_REMOVE, NO_MSG, INFO_TD_REMOVE, CENTER_TD_REMOVE) \ + MSG_MULTI_NOTIF(1, MULTI_TD_REPAIR, NO_MSG, INFO_TD_REPAIR, CENTER_TD_REPAIR) \ + MSG_MULTI_NOTIF(1, MULTI_TD_SPAWN, NO_MSG, INFO_TD_SPAWN, CENTER_TD_SPAWN) \ + MSG_MULTI_NOTIF(1, MULTI_TD_UPGRADE, NO_MSG, INFO_TD_UPGRADE, CENTER_TD_UPGRADE) \ + MSG_MULTI_NOTIF(1, MULTI_TD_VICTORY, NO_MSG, INFO_TD_VICTORY, CENTER_TD_VICTORY) \ MSG_MULTI_NOTIF(1, WEAPON_ACCORDEON_MURDER, NO_MSG, INFO_WEAPON_ACCORDEON_MURDER, NO_MSG) \ MSG_MULTI_NOTIF(1, WEAPON_ACCORDEON_SUICIDE, NO_MSG, INFO_WEAPON_ACCORDEON_SUICIDE, CENTER_DEATH_SELF_GENERIC) \ MSG_MULTI_NOTIF(1, WEAPON_CRYLINK_MURDER, NO_MSG, INFO_WEAPON_CRYLINK_MURDER, NO_MSG) \ diff --cc qcsrc/server/cl_client.qc index 85e2344fb,075280c26..24e7048ae --- a/qcsrc/server/cl_client.qc +++ b/qcsrc/server/cl_client.qc @@@ -1235,19 -1188,11 +1193,11 @@@ void ClientKill_TeamChange (float targe void ClientKill (void) { - if (gameover) - return; + if(gameover) return; + if(self.player_blocked) return; - if(self.freezetag_frozen) return; ++ if(self.frozen) return; - if((g_arena || g_ca) && ((champion && champion.classname == "player" && player_count > 1) || player_count == 1)) // don't allow a kill in this case either - { - // do nothing - } - else if(self.frozen == 1) - { - // do nothing - } - else - ClientKill_TeamChange(0); + ClientKill_TeamChange(0); } void CTS_ClientKill (entity e) // silent version of ClientKill, used when player finishes a CTS run. Useful to prevent cheating by running back to the start line and starting out with more speed diff --cc qcsrc/server/cl_weapons.qc index ca1f83bfe,0014e9182..0f65bb914 --- a/qcsrc/server/cl_weapons.qc +++ b/qcsrc/server/cl_weapons.qc @@@ -356,7 -350,19 +352,19 @@@ void W_ThrowWeapon(vector velo, vector Send_Notification(NOTIF_ONE, self, MSG_MULTI, ITEM_WEAPON_DROP, a, w); } - // Bringed back weapon frame + float forbidWeaponUse() + { + if(time < game_starttime && !autocvar_sv_ready_restart_after_countdown) + return 1; + if(round_handler_IsActive() && !round_handler_IsRoundStarted()) + return 1; + if(self.player_blocked) + return 1; - if(self.freezetag_frozen) ++ if(self.frozen) + return 1; + return 0; + } + void W_WeaponFrame() { vector fo, ri, up; diff --cc qcsrc/server/defs.qh index 1ff324467,fb93b7c78..e2707277c --- a/qcsrc/server/defs.qh +++ b/qcsrc/server/defs.qh @@@ -587,9 -585,7 +585,10 @@@ float serverflags .float player_blocked; -.float freezetag_frozen; +.float frozen; // for freeze attacks +.float revive_progress; +.float revive_speed; // NOTE: multiplier (anything above 1 is instaheal) ++.entity iceblock; .entity muzzle_flash; .float misc_bulletcounter; // replaces uzi & hlac bullet counter. diff --cc qcsrc/server/g_damage.qc index 468e83a92,6687f18eb..8473062c8 --- a/qcsrc/server/g_damage.qc +++ b/qcsrc/server/g_damage.qc @@@ -566,68 -544,6 +544,68 @@@ void Obituary(entity attacker, entity i if(targ.killcount) { targ.killcount = 0; } } +void Ice_Think() +{ + setorigin(self, self.owner.origin - '0 0 16'); + self.nextthink = time; +} + +void Freeze (entity targ, float freeze_time, float frozen_type, float show_waypoint) +{ + float monster = (targ.flags & FL_MONSTER); + float player = (targ.flags & FL_CLIENT); + + if(!player && !monster) // only specified entities can be freezed + return; + + if(targ.frozen) + return; + + targ.frozen = frozen_type; + targ.revive_progress = 0; + targ.health = 1; + targ.revive_speed = freeze_time; + + entity ice; + ice = spawn(); + ice.owner = targ; + ice.classname = "ice"; + ice.scale = targ.scale; + ice.think = Ice_Think; + ice.nextthink = time; + ice.frame = floor(random() * 21); // ice model has 20 different looking frames + setmodel(ice, "models/ice/ice.md3"); ++ ice.alpha = 1; ++ ice.colormod = Team_ColorRGB(self.team); ++ ice.glowmod = ice.colormod; ++ targ.iceblock = ice; + + entity oldself; + oldself = self; + self = ice; + Ice_Think(); + self = oldself; + + RemoveGrapplingHook(targ); + + // add waypoint + if(show_waypoint) + WaypointSprite_Spawn("frozen", 0, 0, targ, '0 0 64', world, targ.team, targ, waypointsprite_attached, TRUE, RADARICON_WAYPOINT, '0.25 0.90 1'); +} + +void Unfreeze (entity targ) +{ + targ.frozen = 0; + targ.revive_progress = 0; + targ.health = ((targ.classname == STR_PLAYER) ? autocvar_g_balance_health_start : targ.max_health); + + WaypointSprite_Kill(targ.waypointsprite_attached); + + // remove the ice block - entity ice; - for(ice = world; (ice = find(ice, classname, "ice")); ) if(ice.owner == targ) - { - remove(ice); - break; - } ++ remove(targ.iceblock); ++ targ.iceblock = world; +} + // these are updated by each Damage call for use in button triggering and such entity damage_targ; entity damage_inflictor; diff --cc qcsrc/server/g_world.qc index 3faf84e4a,1af52b132..653bb1f41 --- a/qcsrc/server/g_world.qc +++ b/qcsrc/server/g_world.qc @@@ -262,11 -263,10 +263,11 @@@ void cvar_changes_init( BADCVAR("g_domination"); BADCVAR("g_domination_default_teams"); BADCVAR("g_freezetag"); + BADCVAR("g_freezetag_teams"); BADCVAR("g_keepaway"); BADCVAR("g_keyhunt"); + BADCVAR("g_td"); BADCVAR("g_keyhunt_teams"); - BADCVAR("g_keyhunt_teams"); BADCVAR("g_lms"); BADCVAR("g_nexball"); BADCVAR("g_onslaught"); @@@ -805,24 -806,6 +807,10 @@@ void spawnfunc_worldspawn (void addstat(STAT_NEX_CHARGEPOOL, AS_FLOAT, nex_chargepool_ammo); addstat(STAT_HAGAR_LOAD, AS_INT, hagar_load); - - if(g_ca || g_freezetag) - { - addstat(STAT_REDALIVE, AS_INT, redalive_stat); - addstat(STAT_BLUEALIVE, AS_INT, bluealive_stat); - addstat(STAT_YELLOWALIVE, AS_INT, yellowalive_stat); - addstat(STAT_PINKALIVE, AS_INT, pinkalive_stat); - } - - if(g_td) - { - addstat(STAT_CURRENT_WAVE, AS_FLOAT, stat_current_wave); - addstat(STAT_TOTALWAVES, AS_FLOAT, stat_totalwaves); - } + + // freeze attacks + addstat(STAT_FROZEN, AS_INT, frozen); + addstat(STAT_REVIVE_PROGRESS, AS_FLOAT, revive_progress); // g_movementspeed hack addstat(STAT_MOVEVARS_AIRSPEEDLIMIT_NONQW, AS_FLOAT, stat_sv_airspeedlimit_nonqw); diff --cc qcsrc/server/mutators/gamemode_assault.qc index 000000000,bf2594785..94e104728 mode 000000,100644..100644 --- a/qcsrc/server/mutators/gamemode_assault.qc +++ b/qcsrc/server/mutators/gamemode_assault.qc @@@ -1,0 -1,614 +1,614 @@@ + // random functions + void assault_objective_use() + { + // activate objective + self.health = 100; + //print("^2Activated objective ", self.targetname, "=", etos(self), "\n"); + //print("Activator is ", activator.classname, "\n"); + + entity oldself; + oldself = self; + + for(self = world; (self = find(self, target, oldself.targetname)); ) + { + if(self.classname == "target_objective_decrease") + target_objective_decrease_activate(); + } + + self = oldself; + } + + vector target_objective_spawn_evalfunc(entity player, entity spot, vector current) + { + if(self.health < 0 || self.health >= ASSAULT_VALUE_INACTIVE) + return '-1 0 0'; + return current; + } + + // reset this objective. Used when spawning an objective + // and when a new round starts + void assault_objective_reset() + { + self.health = ASSAULT_VALUE_INACTIVE; + } + + // decrease the health of targeted objectives + void assault_objective_decrease_use() + { + if(activator.team != assault_attacker_team) + { + // wrong team triggered decrease + return; + } + + if(other.assault_sprite) + { + WaypointSprite_Disown(other.assault_sprite, waypointsprite_deadlifetime); + if(other.classname == "func_assault_destructible") + other.sprite = world; + } + else + return; // already activated! cannot activate again! + + if(self.enemy.health < ASSAULT_VALUE_INACTIVE) + { + if(self.enemy.health - self.dmg > 0.5) + { + PlayerTeamScore_Add(activator, SP_SCORE, ST_SCORE, self.dmg); + self.enemy.health = self.enemy.health - self.dmg; + } + else + { + PlayerTeamScore_Add(activator, SP_SCORE, ST_SCORE, self.enemy.health); + PlayerTeamScore_Add(activator, SP_ASSAULT_OBJECTIVES, ST_ASSAULT_OBJECTIVES, 1); + self.enemy.health = -1; + + entity oldself, oldactivator; + + oldself = self; + self = oldself.enemy; + if(self.message) + { + entity player; + string s; + FOR_EACH_PLAYER(player) + { + s = strcat(self.message, "\n"); + centerprint(player, s); + } + } + + oldactivator = activator; + activator = oldself; + SUB_UseTargets(); + activator = oldactivator; + self = oldself; + } + } + } + + void assault_setenemytoobjective() + { + entity objective; + for(objective = world; (objective = find(objective, targetname, self.target)); ) + { + if(objective.classname == "target_objective") + { + if(self.enemy == world) + self.enemy = objective; + else + objerror("more than one objective as target - fix the map!"); + break; + } + } + + if(self.enemy == world) + objerror("no objective as target - fix the map!"); + } + + float assault_decreaser_sprite_visible(entity e) + { + entity decreaser; + + decreaser = self.assault_decreaser; + + if(decreaser.enemy.health >= ASSAULT_VALUE_INACTIVE) + return FALSE; + + return TRUE; + } + + void target_objective_decrease_activate() + { + entity ent, spr; + self.owner = world; + for(ent = world; (ent = find(ent, target, self.targetname)); ) + { + if(ent.assault_sprite != world) + { + WaypointSprite_Disown(ent.assault_sprite, waypointsprite_deadlifetime); + if(ent.classname == "func_assault_destructible") + ent.sprite = world; + } + + spr = WaypointSprite_SpawnFixed("", 0.5 * (ent.absmin + ent.absmax), ent, assault_sprite, RADARICON_OBJECTIVE, '1 0.5 0'); + spr.assault_decreaser = self; + spr.waypointsprite_visible_for_player = assault_decreaser_sprite_visible; + spr.classname = "sprite_waypoint"; + WaypointSprite_UpdateRule(spr, assault_attacker_team, SPRITERULE_TEAMPLAY); + if(ent.classname == "func_assault_destructible") + { + WaypointSprite_UpdateSprites(spr, "as-defend", "as-destroy", "as-destroy"); + WaypointSprite_UpdateMaxHealth(spr, ent.max_health); + WaypointSprite_UpdateHealth(spr, ent.health); + ent.sprite = spr; + } + else + WaypointSprite_UpdateSprites(spr, "as-defend", "as-push", "as-push"); + } + } + + void target_objective_decrease_findtarget() + { + assault_setenemytoobjective(); + } + + void target_assault_roundend_reset() + { + //print("round end reset\n"); + self.cnt = self.cnt + 1; // up round counter + self.winning = 0; // up round + } + + void target_assault_roundend_use() + { + self.winning = 1; // round has been won by attackers + } + + void assault_roundstart_use() + { + activator = self; + SUB_UseTargets(); + + #ifdef TTURRETS_ENABLED + entity ent, oldself; + + //(Re)spawn all turrets + oldself = self; + ent = find(world, classname, "turret_main"); + while(ent) { + // Swap turret teams - if(ent.team == COLOR_TEAM1) - ent.team = COLOR_TEAM2; ++ if(ent.team == NUM_TEAM_1) ++ ent.team = NUM_TEAM_2; + else - ent.team = COLOR_TEAM1; ++ ent.team = NUM_TEAM_1; + + self = ent; + + // Dubbles as teamchange + turret_stdproc_respawn(); + + ent = find(ent, classname, "turret_main"); + } + self = oldself; + #endif + } + + void assault_wall_think() + { + if(self.enemy.health < 0) + { + self.model = ""; + self.solid = SOLID_NOT; + } + else + { + self.model = self.mdl; + self.solid = SOLID_BSP; + } + + self.nextthink = time + 0.2; + } + + // trigger new round + // reset objectives, toggle spawnpoints, reset triggers, ... + void vehicles_clearrturn(); + void vehicles_spawn(); + void assault_new_round() + { + entity oldself; + //bprint("ASSAULT: new round\n"); + + oldself = self; + // Eject players from vehicles + FOR_EACH_PLAYER(self) + { + if(self.vehicle) + vehicles_exit(VHEF_RELESE); + } + + self = findchainflags(vehicle_flags, VHF_ISVEHICLE); + while(self) + { + vehicles_clearrturn(); + vehicles_spawn(); + self = self.chain; + } + + self = oldself; + + // up round counter + self.winning = self.winning + 1; + + // swap attacker/defender roles - if(assault_attacker_team == COLOR_TEAM1) - assault_attacker_team = COLOR_TEAM2; ++ if(assault_attacker_team == NUM_TEAM_1) ++ assault_attacker_team = NUM_TEAM_2; + else - assault_attacker_team = COLOR_TEAM1; ++ assault_attacker_team = NUM_TEAM_1; + + entity ent; + for(ent = world; (ent = nextent(ent)); ) + { + if(clienttype(ent) == CLIENTTYPE_NOTACLIENT) + { - if(ent.team_saved == COLOR_TEAM1) - ent.team_saved = COLOR_TEAM2; - else if(ent.team_saved == COLOR_TEAM2) - ent.team_saved = COLOR_TEAM1; ++ if(ent.team_saved == NUM_TEAM_1) ++ ent.team_saved = NUM_TEAM_2; ++ else if(ent.team_saved == NUM_TEAM_2) ++ ent.team_saved = NUM_TEAM_1; + } + } + + // reset the level with a countdown + cvar_set("timelimit", ftos(ceil(time - game_starttime) / 60)); + ReadyRestart_force(); // sets game_starttime + } + + // spawnfuncs + void spawnfunc_info_player_attacker() + { + if not(g_assault) { remove(self); return; } + - self.team = COLOR_TEAM1; // red, gets swapped every round ++ self.team = NUM_TEAM_1; // red, gets swapped every round + spawnfunc_info_player_deathmatch(); + } + + void spawnfunc_info_player_defender() + { + if not(g_assault) { remove(self); return; } + - self.team = COLOR_TEAM2; // blue, gets swapped every round ++ self.team = NUM_TEAM_2; // blue, gets swapped every round + spawnfunc_info_player_deathmatch(); + } + + void spawnfunc_target_objective() + { + if not(g_assault) { remove(self); return; } + + self.classname = "target_objective"; + self.use = assault_objective_use; + assault_objective_reset(); + self.reset = assault_objective_reset; + self.spawn_evalfunc = target_objective_spawn_evalfunc; + } + + void spawnfunc_target_objective_decrease() + { + if not(g_assault) { remove(self); return; } + + self.classname = "target_objective_decrease"; + + if(!self.dmg) + self.dmg = 101; + + self.use = assault_objective_decrease_use; + self.health = ASSAULT_VALUE_INACTIVE; + self.max_health = ASSAULT_VALUE_INACTIVE; + self.enemy = world; + + InitializeEntity(self, target_objective_decrease_findtarget, INITPRIO_FINDTARGET); + } + + // destructible walls that can be used to trigger target_objective_decrease + void spawnfunc_func_assault_destructible() + { + if not(g_assault) { remove(self); return; } + + self.spawnflags = 3; + self.classname = "func_assault_destructible"; + - if(assault_attacker_team == COLOR_TEAM1) - self.team = COLOR_TEAM2; ++ if(assault_attacker_team == NUM_TEAM_1) ++ self.team = NUM_TEAM_2; + else - self.team = COLOR_TEAM1; ++ self.team = NUM_TEAM_1; + + spawnfunc_func_breakable(); + } + + void spawnfunc_func_assault_wall() + { + if not(g_assault) { remove(self); return; } + + self.classname = "func_assault_wall"; + self.mdl = self.model; + setmodel(self, self.mdl); + self.solid = SOLID_BSP; + self.think = assault_wall_think; + self.nextthink = time; + InitializeEntity(self, assault_setenemytoobjective, INITPRIO_FINDTARGET); + } + + void spawnfunc_target_assault_roundend() + { + if not(g_assault) { remove(self); return; } + + self.winning = 0; // round not yet won by attackers + self.classname = "target_assault_roundend"; + self.use = target_assault_roundend_use; + self.cnt = 0; // first round + self.reset = target_assault_roundend_reset; + } + + void spawnfunc_target_assault_roundstart() + { + if not(g_assault) { remove(self); return; } + - assault_attacker_team = COLOR_TEAM1; ++ assault_attacker_team = NUM_TEAM_1; + self.classname = "target_assault_roundstart"; + self.use = assault_roundstart_use; + self.reset2 = assault_roundstart_use; + InitializeEntity(self, assault_roundstart_use, INITPRIO_FINDTARGET); + } + + // legacy bot code + void havocbot_goalrating_ast_targets(float ratingscale) + { + entity ad, best, wp, tod; + float radius, found, bestvalue; + vector p; + + ad = findchain(classname, "func_assault_destructible"); + + for (; ad; ad = ad.chain) + { + if (ad.target == "") + continue; + + if not(ad.bot_attack) + continue; + + found = FALSE; + for(tod = world; (tod = find(tod, targetname, ad.target)); ) + { + if(tod.classname == "target_objective_decrease") + { + if(tod.enemy.health > 0 && tod.enemy.health < ASSAULT_VALUE_INACTIVE) + { + // dprint(etos(ad),"\n"); + found = TRUE; + break; + } + } + } + + if(!found) + { + /// dprint("target not found\n"); + continue; + } + /// dprint("target #", etos(ad), " found\n"); + + + p = 0.5 * (ad.absmin + ad.absmax); + // dprint(vtos(ad.origin), " ", vtos(ad.absmin), " ", vtos(ad.absmax),"\n"); + // te_knightspike(p); + // te_lightning2(world, '0 0 0', p); + + // Find and rate waypoints around it + found = FALSE; + best = world; + bestvalue = 99999999999; + for(radius=0; radius<1500 && !found; radius+=500) + { + for(wp=findradius(p, radius); wp; wp=wp.chain) + { + if(!(wp.wpflags & WAYPOINTFLAG_GENERATED)) + if(wp.classname=="waypoint") + if(checkpvs(wp.origin, ad)) + { + found = TRUE; + if(wp.cnt self.havocbot_role_timeout) + { + havocbot_ast_reset_role(self); + return; + } + + if(self.havocbot_attack_time>time) + return; + + if (self.bot_strategytime < time) + { + navigation_goalrating_start(); + havocbot_goalrating_enemyplayers(20000, self.origin, 650); + havocbot_goalrating_ast_targets(20000); + havocbot_goalrating_items(15000, self.origin, 10000); + navigation_goalrating_end(); + + self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; + } + } + + void havocbot_role_ast_defense() + { + if(self.deadflag != DEAD_NO) + { + self.havocbot_attack_time = 0; + havocbot_ast_reset_role(self); + return; + } + + // Set the role timeout if necessary + if (!self.havocbot_role_timeout) + self.havocbot_role_timeout = time + 120; + + if (time > self.havocbot_role_timeout) + { + havocbot_ast_reset_role(self); + return; + } + + if(self.havocbot_attack_time>time) + return; + + if (self.bot_strategytime < time) + { + navigation_goalrating_start(); + havocbot_goalrating_enemyplayers(20000, self.origin, 3000); + havocbot_goalrating_ast_targets(20000); + havocbot_goalrating_items(15000, self.origin, 10000); + navigation_goalrating_end(); + + self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; + } + } + + void havocbot_role_ast_setrole(entity bot, float role) + { + switch(role) + { + case HAVOCBOT_AST_ROLE_DEFENSE: + bot.havocbot_role = havocbot_role_ast_defense; + bot.havocbot_role_flags = HAVOCBOT_AST_ROLE_DEFENSE; + bot.havocbot_role_timeout = 0; + break; + case HAVOCBOT_AST_ROLE_OFFENSE: + bot.havocbot_role = havocbot_role_ast_offense; + bot.havocbot_role_flags = HAVOCBOT_AST_ROLE_OFFENSE; + bot.havocbot_role_timeout = 0; + break; + } + } + + void havocbot_ast_reset_role(entity bot) + { + if(self.deadflag != DEAD_NO) + return; + + if(bot.team == assault_attacker_team) + havocbot_role_ast_setrole(bot, HAVOCBOT_AST_ROLE_OFFENSE); + else + havocbot_role_ast_setrole(bot, HAVOCBOT_AST_ROLE_DEFENSE); + } + + // mutator hooks + MUTATOR_HOOKFUNCTION(assault_PlayerSpawn) + { + if(self.team == assault_attacker_team) + centerprint(self, "You are attacking!"); + else + centerprint(self, "You are defending!"); + + return FALSE; + } + + MUTATOR_HOOKFUNCTION(assault_TurretSpawn) + { + if not (self.team) + self.team = 14; + + return FALSE; + } + + MUTATOR_HOOKFUNCTION(assault_VehicleSpawn) + { + self.nextthink = time + 0.5; + + return FALSE; + } + + MUTATOR_HOOKFUNCTION(assault_BotRoles) + { + havocbot_ast_reset_role(self); + return TRUE; + } + + // scoreboard setup + void assault_ScoreRules() + { + ScoreRules_basics(2, SFL_SORT_PRIO_SECONDARY, SFL_SORT_PRIO_SECONDARY, TRUE); + ScoreInfo_SetLabel_TeamScore( ST_ASSAULT_OBJECTIVES, "objectives", SFL_SORT_PRIO_PRIMARY); + ScoreInfo_SetLabel_PlayerScore(SP_ASSAULT_OBJECTIVES, "objectives", SFL_SORT_PRIO_PRIMARY); + ScoreRules_basics_end(); + } + + MUTATOR_DEFINITION(gamemode_assault) + { + MUTATOR_HOOK(PlayerSpawn, assault_PlayerSpawn, CBC_ORDER_ANY); + MUTATOR_HOOK(TurretSpawn, assault_TurretSpawn, CBC_ORDER_ANY); + MUTATOR_HOOK(VehicleSpawn, assault_VehicleSpawn, CBC_ORDER_ANY); + MUTATOR_HOOK(HavocBot_ChooseRule, assault_BotRoles, 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."); + assault_ScoreRules(); + } + + MUTATOR_ONROLLBACK_OR_REMOVE + { + // we actually cannot roll back assault_Initialize here + // BUT: we don't need to! If this gets called, adding always + // succeeds. + } + + MUTATOR_ONREMOVE + { + print("This is a game type and it cannot be removed at runtime."); + return -1; + } + + return 0; + } diff --cc qcsrc/server/mutators/gamemode_freezetag.qc index 51d9ca531,980a9b20d..c5afea7de --- a/qcsrc/server/mutators/gamemode_freezetag.qc +++ b/qcsrc/server/mutators/gamemode_freezetag.qc @@@ -1,64 -1,144 +1,134 @@@ - void freezetag_Initialize() + .float freezetag_frozen_time; + .float freezetag_frozen_timeout; + .float freezetag_revive_progress; -.entity freezetag_ice; + #define ICE_MAX_ALPHA 1 + #define ICE_MIN_ALPHA 0.1 + float freezetag_teams; + + void freezetag_count_alive_players() { - precache_model("models/ice/ice.md3"); - warmup = time + autocvar_g_start_delay + autocvar_g_freezetag_warmup; - ScoreRules_freezetag(); + entity e; + total_players = redalive = bluealive = yellowalive = pinkalive = 0; + FOR_EACH_PLAYER(e) { + if(e.team == NUM_TEAM_1 && e.health >= 1) + { + ++total_players; - if (!e.freezetag_frozen) ++redalive; ++ if (e.frozen != 1) ++redalive; + } + else if(e.team == NUM_TEAM_2 && e.health >= 1) + { + ++total_players; - if (!e.freezetag_frozen) ++bluealive; ++ if (e.frozen != 1) ++bluealive; + } + else if(e.team == NUM_TEAM_3 && e.health >= 1) + { + ++total_players; - if (!e.freezetag_frozen) ++yellowalive; ++ if (e.frozen != 1) ++yellowalive; + } + else if(e.team == NUM_TEAM_4 && e.health >= 1) + { + ++total_players; - if (!e.freezetag_frozen) ++pinkalive; ++ if (e.frozen != 1) ++pinkalive; + } + } + FOR_EACH_REALCLIENT(e) { + e.redalive_stat = redalive; + e.bluealive_stat = bluealive; + e.yellowalive_stat = yellowalive; + e.pinkalive_stat = pinkalive; + } } + #define FREEZETAG_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0)) + #define FREEZETAG_ALIVE_TEAMS_OK() (FREEZETAG_ALIVE_TEAMS() == freezetag_teams) - void freezetag_CheckWinner() + float prev_total_players; + float freezetag_CheckTeams() { - if(time <= game_starttime) // game didn't even start yet! nobody can win in that case. - return; + if(FREEZETAG_ALIVE_TEAMS_OK()) + { + if(prev_total_players > 0) + Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS); + prev_total_players = -1; + return 1; + } + if(prev_total_players != total_players) + { + float p1 = 0, p2 = 0, p3 = 0, p4 = 0; + if(!redalive) p1 = NUM_TEAM_1; + if(!bluealive) p2 = NUM_TEAM_2; + if(freezetag_teams >= 3) + if(!yellowalive) p3 = NUM_TEAM_3; + if(freezetag_teams >= 4) + if(!pinkalive) p4 = NUM_TEAM_4; + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_MISSING_TEAMS, p1, p2, p3, p4); + prev_total_players = total_players; + } + return 0; + } - if(next_round || (time > warmup - autocvar_g_freezetag_warmup && time < warmup)) - return; // already waiting for next round to start + float freezetag_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(yellowalive >= 1) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_3; + } + if(pinkalive >= 1) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_4; + } + if(winner_team) + return winner_team; + return -1; // no player left + } - if((redalive >= 1 && bluealive >= 1) - || (redalive >= 1 && yellowalive >= 1) - || (redalive >= 1 && pinkalive >= 1) - || (bluealive >= 1 && yellowalive >= 1) - || (bluealive >= 1 && pinkalive >= 1) - || (yellowalive >= 1 && pinkalive >= 1)) - return; // we still have active players on two or more teams, nobody won yet + float freezetag_CheckWinner() + { + entity e; + 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); + FOR_EACH_PLAYER(e) + e.freezetag_frozen_timeout = 0; + round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit); + return 1; + } - entity e, winner; - winner = world; + if(FREEZETAG_ALIVE_TEAMS() > 1) + return 0; - FOR_EACH_PLAYER(e) + float winner_team; + winner_team = freezetag_getWinnerTeam(); + if(winner_team > 0) { - if(e.frozen != 1 && e.health >= 1) // here's one player from the winning team... good - { - winner = e; - break; // break, we found the winner - } + 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); } - - if(winner != world) // just in case a winner wasn't found + else if(winner_team == -1) { - Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner.team, CENTER_FREEZETAG_ROUND_WIN_)); - Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner.team, INFO_FREEZETAG_ROUND_WIN_)); - TeamScore_AddToTeam(winner.team, ST_SCORE, +1); + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED); } - next_round = time + 5; - } - - // this is needed to allow the player to turn his view around (fixangle can't - // be used to freeze his view, as that also changes the angles), while not - // turning that ice object with the player - void freezetag_Ice_Think() - { - setorigin(self, self.owner.origin - '0 0 16'); - self.nextthink = time; + FOR_EACH_PLAYER(e) + e.freezetag_frozen_timeout = 0; + round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit); + return 1; } - void freezetag_Freeze(entity attacker) -// this is needed to allow the player to turn his view around (fixangle can't -// be used to freeze his view, as that also changes the angles), while not -// turning that ice object with the player -void freezetag_Ice_Think() -{ - setorigin(self, self.owner.origin - '0 0 16'); - self.nextthink = time; -} - + void freezetag_Add_Score(entity attacker) { - if(self.frozen) - return; - - Freeze(self, 0, 1, TRUE); - if(attacker == self) { // you froze your own dumb self @@@ -72,13 -152,59 +142,29 @@@ PlayerScore_Add(self, SP_SCORE, -1); PlayerScore_Add(attacker, SP_SCORE, +1); } - else - { - // nothing - got frozen by the game type rules themselves - } + // else nothing - got frozen by the game type rules themselves } + void freezetag_Freeze(entity attacker) + { - if(self.freezetag_frozen) ++ if(self.frozen) + return; - self.freezetag_frozen = 1; - self.freezetag_frozen_time = time; - self.freezetag_revive_progress = 0; - self.health = 1; - if(autocvar_g_freezetag_frozen_maxtime > 0) - self.freezetag_frozen_timeout = time + autocvar_g_freezetag_frozen_maxtime; - ++ ++ Freeze(self, 0, 1, TRUE); ++ + freezetag_count_alive_players(); + - entity ice; - ice = spawn(); - ice.owner = self; - ice.classname = "freezetag_ice"; - ice.think = freezetag_Ice_Think; - ice.nextthink = time; - ice.frame = floor(random() * 21); // ice model has 20 different looking frames - ice.alpha = ICE_MAX_ALPHA; - ice.colormod = Team_ColorRGB(self.team); - ice.glowmod = ice.colormod; - setmodel(ice, "models/ice/ice.md3"); - - self.freezetag_ice = ice; - - RemoveGrapplingHook(self); - - // add waypoint - WaypointSprite_Spawn("freezetag_frozen", 0, 0, self, '0 0 64', world, self.team, self, waypointsprite_attached, TRUE, RADARICON_WAYPOINT, '0.25 0.90 1'); - + freezetag_Add_Score(attacker); + } + + void freezetag_Unfreeze(entity attacker) + { - self.freezetag_frozen = 0; + self.freezetag_frozen_time = 0; + self.freezetag_frozen_timeout = 0; - self.freezetag_revive_progress = 0; - - remove(self.freezetag_ice); - self.freezetag_ice = world; - - if(self.waypointsprite_attached) - WaypointSprite_Kill(self.waypointsprite_attached); ++ ++ Unfreeze(self); + } - // ================ // Bot player logic // ================ @@@ -212,21 -323,37 +283,37 @@@ MUTATOR_HOOKFUNCTION(freezetag_RemovePl MUTATOR_HOOKFUNCTION(freezetag_PlayerDies) { - if(self.frozen != 1) + if(round_handler_IsActive()) + if(round_handler_CountdownRunning()) { - if(self.team == NUM_TEAM_1) - --redalive; - else if(self.team == NUM_TEAM_2) - --bluealive; - else if(self.team == NUM_TEAM_3) - --yellowalive; - else if(self.team == NUM_TEAM_4) - --pinkalive; - --totalalive; - if(self.freezetag_frozen) ++ if(self.frozen == 1) + freezetag_Unfreeze(world); + freezetag_count_alive_players(); + return 1; // let the player die so that he can respawn whenever he wants + } - freezetag_Freeze(frag_attacker); + // Cases DEATH_TEAMCHANGE and DEATH_AUTOTEAMCHANGE are needed to fix a bug whe + // you succeed changing team through the menu: you both really die (gibbing) and get frozen + if(ITEM_DAMAGE_NEEDKILL(frag_deathtype) + || frag_deathtype == DEATH_TEAMCHANGE || frag_deathtype == DEATH_AUTOTEAMCHANGE) + { + // let the player die, he will be automatically frozen when he respawns - if(!self.freezetag_frozen) ++ if(self.frozen != 1) + { + freezetag_Add_Score(frag_attacker); + freezetag_count_alive_players(); + } + else + freezetag_Unfreeze(world); // remove ice + self.freezetag_frozen_timeout = -2; // freeze on respawn + return 1; } - if(self.freezetag_frozen) ++ if(self.frozen) + return 1; + + freezetag_Freeze(frag_attacker); + if(frag_attacker == frag_target || frag_attacker == world) { if(frag_target.classname == STR_PLAYER) @@@ -242,10 -369,8 +329,6 @@@ Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_FREEZE, frag_target.netname, frag_attacker.netname); } -- frag_target.health = 1; // "respawn" the player :P - - freezetag_CheckWinner(); -- return 1; } @@@ -268,6 -397,20 +355,20 @@@ MUTATOR_HOOKFUNCTION(freezetag_PlayerSp return 1; } + MUTATOR_HOOKFUNCTION(freezetag_reset_map_players) + { + FOR_EACH_PLAYER(self) + { - if (self.freezetag_frozen) ++ if (self.frozen) + freezetag_Unfreeze(world); + self.freezetag_frozen_timeout = -1; + PutClientInServer(); + self.freezetag_frozen_timeout = 0; + } + freezetag_count_alive_players(); + return 1; + } + MUTATOR_HOOKFUNCTION(freezetag_GiveFragsForKill) { frag_score = 0; // no frags counted in Freeze Tag @@@ -277,69 -421,88 +379,88 @@@ MUTATOR_HOOKFUNCTION(freezetag_PlayerPreThink) { float n; - vector revive_extra_size; - revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size; - + if(gameover) + return 1; + - if(self.freezetag_frozen) ++ if(self.frozen == 1) + { + // keep health = 1 + self.pauseregen_finished = time + autocvar_g_balance_pause_health_regen; + } + + if(round_handler_IsActive()) + if(!round_handler_IsRoundStarted()) + return 1; + entity o; o = world; - n = 0; - FOR_EACH_PLAYER(other) if(self != other) + if(self.freezetag_frozen_timeout > 0 && time < self.freezetag_frozen_timeout) - self.freezetag_ice.alpha = ICE_MIN_ALPHA + (ICE_MAX_ALPHA - ICE_MIN_ALPHA) * (self.freezetag_frozen_timeout - time) / (self.freezetag_frozen_timeout - self.freezetag_frozen_time); ++ self.iceblock.alpha = ICE_MIN_ALPHA + (ICE_MAX_ALPHA - ICE_MIN_ALPHA) * (self.freezetag_frozen_timeout - time) / (self.freezetag_frozen_timeout - self.freezetag_frozen_time); + + if(self.freezetag_frozen_timeout > 0 && time >= self.freezetag_frozen_timeout) + n = -1; + else { - if(other.frozen != 1) + vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size; + n = 0; + FOR_EACH_PLAYER(other) if(self != other) { - if(other.team == self.team) - if(other.freezetag_frozen == 0) ++ if(!other.frozen) { - if(boxesoverlap(self.absmin - revive_extra_size, self.absmax + revive_extra_size, other.absmin, other.absmax)) + if(other.team == self.team) { - if(!o) - o = other; - ++n; + if(boxesoverlap(self.absmin - revive_extra_size, self.absmax + revive_extra_size, other.absmin, other.absmax)) + { + if(!o) + o = other; - if(self.freezetag_frozen) ++ if(self.frozen == 1) + other.reviving = TRUE; + ++n; + } } } } } - if(n && self.freezetag_frozen) // OK, there is at least one teammate reviving us + if(n && self.frozen == 1) // OK, there is at least one teammate reviving us { - self.revive_progress = bound(0, self.revive_progress + frametime * autocvar_g_freezetag_revive_speed, 1); - self.freezetag_revive_progress = bound(0, self.freezetag_revive_progress + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1); - self.health = max(1, self.freezetag_revive_progress * autocvar_g_balance_health_start); ++ self.revive_progress = bound(0, self.revive_progress + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1); + self.health = max(1, self.revive_progress * autocvar_g_balance_health_start); - if(self.freezetag_revive_progress >= 1) + if(self.revive_progress >= 1) { - Unfreeze(self); + freezetag_Unfreeze(self); + freezetag_count_alive_players(); + + if(n == -1) + { + Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_FREEZETAG_AUTO_REVIVED, autocvar_g_freezetag_frozen_maxtime); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_AUTO_REVIVED, self.netname, autocvar_g_freezetag_frozen_maxtime); + return 1; + } // EVERY team mate nearby gets a point (even if multiple!) - FOR_EACH_PLAYER(other) if(self != other) + FOR_EACH_PLAYER(other) { - if(!other.frozen) + if(other.reviving) { - if(other.team == self.team) - { - if(boxesoverlap(self.absmin - revive_extra_size, self.absmax + revive_extra_size, other.absmin, other.absmax)) - { - PlayerScore_Add(other, SP_FREEZETAG_REVIVALS, +1); - PlayerScore_Add(other, SP_SCORE, +1); - } - } + PlayerScore_Add(other, SP_FREEZETAG_REVIVALS, +1); + PlayerScore_Add(other, SP_SCORE, +1); } } Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_FREEZETAG_REVIVED, o.netname); Send_Notification(NOTIF_ONE, o, MSG_CENTER, CENTER_FREEZETAG_REVIVE, self.netname); - Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_REVIVE, self.netname, o.netname); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_REVIVED, self.netname, o.netname); } - // now find EVERY teammate within reviving radius, set their revive_progress values correct - FOR_EACH_PLAYER(other) if(self != other) + FOR_EACH_PLAYER(other) { - if(!other.frozen) + if(other.reviving) { - if(other.team == self.team) - { - if(boxesoverlap(self.absmin - revive_extra_size, self.absmax + revive_extra_size, other.absmin, other.absmax)) - other.revive_progress = self.revive_progress; - } - other.freezetag_revive_progress = self.freezetag_revive_progress; ++ other.revive_progress = self.revive_progress; + other.reviving = FALSE; } } } @@@ -356,19 -519,40 +477,16 @@@ return 1; } -MUTATOR_HOOKFUNCTION(freezetag_PlayerPhysics) -{ - if(self.freezetag_frozen) - { - self.movement = '0 0 0'; - self.disableclientprediction = 1; - } - return 1; -} - MUTATOR_HOOKFUNCTION(freezetag_PlayerDamage_Calculate) { - if(g_freezetag) - { - if(frag_target.frozen == 1 && frag_deathtype != DEATH_HURTTRIGGER) - { - frag_damage = 0; - frag_force = frag_force * autocvar_g_freezetag_frozen_force; - } - } - return 1; - if(frag_target.freezetag_frozen && frag_deathtype != DEATH_HURTTRIGGER) ++ if(frag_target.frozen == 1 && frag_deathtype != DEATH_HURTTRIGGER) + { + frag_damage = 0; + frag_force = frag_force * autocvar_g_freezetag_frozen_force; + } + return 1; } -MUTATOR_HOOKFUNCTION(freezetag_ForbidThrowCurrentWeapon) -{ - if (self.freezetag_frozen) - return 1; - return 0; -} - -MUTATOR_HOOKFUNCTION(freezetag_ItemTouch) -{ - if (other.freezetag_frozen) - return 1; - return 0; -} - MUTATOR_HOOKFUNCTION(freezetag_BotRoles) { if not(self.deadflag) @@@ -382,16 -566,56 +500,50 @@@ return TRUE; } + MUTATOR_HOOKFUNCTION(freezetag_SpectateCopy) + { - self.freezetag_frozen = other.freezetag_frozen; - self.freezetag_revive_progress = other.freezetag_revive_progress; ++ self.frozen = other.frozen; ++ self.revive_progress = other.revive_progress; + return 0; + } + + MUTATOR_HOOKFUNCTION(freezetag_GetTeamCount) + { + freezetag_teams = autocvar_g_freezetag_teams_override; + if(freezetag_teams < 2) + freezetag_teams = autocvar_g_freezetag_teams; + freezetag_teams = bound(2, freezetag_teams, 4); + ret_float = freezetag_teams; + return 0; + } + + void freezetag_Initialize() + { + precache_model("models/ice/ice.md3"); + ScoreRules_freezetag(); + + round_handler_Spawn(freezetag_CheckTeams, freezetag_CheckWinner, func_null); + round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit); + + addstat(STAT_REDALIVE, AS_INT, redalive_stat); + addstat(STAT_BLUEALIVE, AS_INT, bluealive_stat); + addstat(STAT_YELLOWALIVE, AS_INT, yellowalive_stat); + addstat(STAT_PINKALIVE, AS_INT, pinkalive_stat); - - addstat(STAT_FROZEN, AS_INT, freezetag_frozen); - addstat(STAT_REVIVE_PROGRESS, AS_FLOAT, freezetag_revive_progress); + } + MUTATOR_DEFINITION(gamemode_freezetag) { MUTATOR_HOOK(MakePlayerObserver, freezetag_RemovePlayer, CBC_ORDER_ANY); MUTATOR_HOOK(ClientDisconnect, freezetag_RemovePlayer, CBC_ORDER_ANY); MUTATOR_HOOK(PlayerDies, freezetag_PlayerDies, CBC_ORDER_ANY); MUTATOR_HOOK(PlayerSpawn, freezetag_PlayerSpawn, CBC_ORDER_ANY); + MUTATOR_HOOK(reset_map_players, freezetag_reset_map_players, CBC_ORDER_ANY); MUTATOR_HOOK(GiveFragsForKill, freezetag_GiveFragsForKill, CBC_ORDER_FIRST); MUTATOR_HOOK(PlayerPreThink, freezetag_PlayerPreThink, CBC_ORDER_FIRST); - MUTATOR_HOOK(PlayerPhysics, freezetag_PlayerPhysics, CBC_ORDER_FIRST); MUTATOR_HOOK(PlayerDamage_Calculate, freezetag_PlayerDamage_Calculate, CBC_ORDER_ANY); - MUTATOR_HOOK(ForbidThrowCurrentWeapon, freezetag_ForbidThrowCurrentWeapon, CBC_ORDER_ANY); - MUTATOR_HOOK(ItemTouch, freezetag_ItemTouch, CBC_ORDER_ANY); MUTATOR_HOOK(HavocBot_ChooseRule, freezetag_BotRoles, CBC_ORDER_ANY); + MUTATOR_HOOK(SpectateCopy, freezetag_SpectateCopy, CBC_ORDER_ANY); + MUTATOR_HOOK(GetTeamCount, freezetag_GetTeamCount, CBC_ORDER_EXCLUSIVE); MUTATOR_ONADD { diff --cc qcsrc/server/mutators/gamemode_towerdefense.qc index d74ccc752,000000000..7e02ac171 mode 100644,000000..100644 --- a/qcsrc/server/mutators/gamemode_towerdefense.qc +++ b/qcsrc/server/mutators/gamemode_towerdefense.qc @@@ -1,1116 -1,0 +1,1119 @@@ +// 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 == 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; + + wave_end(TRUE); +} + +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); + + self.solid = SOLID_NOT; + self.takedamage = DAMAGE_NO; + self.event_damage = func_null; + self.enemy = world; + td_gencount -= 1; + + WaypointSprite_Kill(self.sprite); +} + +void td_generator_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) +{ + if(IS_PLAYER(attacker) || attacker.turrcaps_flags & TFL_TURRCAPS_ISTURRET || attacker.vehicle_flags & VHF_ISVEHICLE || self.takedamage == DAMAGE_NO) + return; + + if (time > self.pain_finished) + { + self.pain_finished = time + 10; + play2all("onslaught/generator_underattack.wav"); + } + + if (random() < 0.5) + spamsound(self, CH_TRIGGER, "onslaught/ons_hit1.wav", VOL_BASE, ATTN_NORM); + else + spamsound(self, CH_TRIGGER, "onslaught/ons_hit2.wav", VOL_BASE, ATTN_NORM); + + 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(); + + self.SendFlags |= GSF_STATUS; +} + +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.netname = "Generator"; + self.SendFlags = GSF_SETUP; + + WaypointSprite_SpawnFixed(self.netname, self.origin + '0 0 90', self, sprite, RADARICON_OBJECTIVE, '1 0.5 0'); + WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health); + WaypointSprite_UpdateHealth(self.sprite, self.health); +} + +void spawnfunc_td_generator() +{ + 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(); + + 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; +} + +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; +} + +float td_checkfuel(entity ent, string tur) +{ + float turcost = cvar(strcat("g_td_turret_", tur, "_cost")); + + if(ent.ammo_fuel < turcost) + { + Send_Notification(NOTIF_ONE, ent, MSG_MULTI, MULTI_TD_NOFUEL); + return FALSE; + } + + ent.ammo_fuel -= turcost; + + return TRUE; +} + +void spawnturret(entity spawnedby, entity own, string turet, vector orig) +{ + if not(IS_PLAYER(spawnedby)) { dprint("Warning: A non-player entity tried to spawn a turret\n"); return; } + if not(td_checkfuel(spawnedby, turet)) { return; } + + entity oldself; + + oldself = self; + self = spawn(); + + setorigin(self, orig); + self.spawnflags = TSL_NO_RESPAWN; + self.monster_attack = TRUE; + self.realowner = own; + self.playerid = own.playerid; + self.angles_y = spawnedby.v_angle_y; + spawnedby.turret_cnt += 1; + self.colormap = spawnedby.colormap; + self.colormod = '1 1 1'; + + switch(turet) + { + case "plasma": spawnfunc_turret_plasma(); break; + case "mlrs": spawnfunc_turret_mlrs(); break; + 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; + } + + Send_Notification(NOTIF_ONE, spawnedby, MSG_MULTI, MULTI_TD_SPAWN); + + 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) +{ + 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); +} + +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, 2); + if(e.target2) + { + if(random() <= 0.5 && e.target) + mon.target2 = e.target; + else + mon.target2 = e.target2; + } + else + mon.target2 = e.target; +} + +float Monster_GetStrength(float mnster) +{ + switch(mnster) + { + 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; + } +} + +string monster_type2string(float mnster) +{ + 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 ""; + } +} + +float Monster_GetType(float mnster) +{ + switch(mnster) + { + 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; + } +} + +float RandomMonster() +{ + RandomSelection_Init(); + + if(n_animuses) RandomSelection_Add(world, MONSTER_ANIMUS, "", 1, 1); + if(n_mages) RandomSelection_Add(world, MONSTER_MAGE, "", 1, 1); + if(n_knights) RandomSelection_Add(world, MONSTER_KNIGHT, "", 1, 1); + if(n_zombies) RandomSelection_Add(world, MONSTER_ZOMBIE, "", 1, 1); + if(n_spiders) RandomSelection_Add(world, MONSTER_SPIDER, "", 1, 1); + if(n_brutes) RandomSelection_Add(world, MONSTER_BRUTE, "", 1, 1); + if(n_cerberuses) RandomSelection_Add(world, MONSTER_CERBERUS, "", 1, 1); + if(n_bruisers) RandomSelection_Add(world, MONSTER_BRUISER, "", 1, 1); + if(n_shamblers) RandomSelection_Add(world, MONSTER_SHAMBLER, "", 0.2, 0.2); + if(n_slimes) RandomSelection_Add(world, MONSTER_SLIME, "", 0.2, 0.2); + if(n_wyverns && flyspawns_count) RandomSelection_Add(world, MONSTER_WYVERN, "", 1, 1); + if(n_stingrays && waterspawns_count) RandomSelection_Add(world, MONSTER_STINGRAY, "", 0.2, 0.2); + + return RandomSelection_chosen_float; +} + +void combat_phase() +{ + float mstrength, montype, whichmon; + + 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(monster_type2string(whichmon), mstrength, montype); + self.nextthink = time + spawn_delay; + } + 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); + n_animuses = DistributeEvenly_Get(1); + n_brutes = DistributeEvenly_Get(1); + n_cerberuses = DistributeEvenly_Get(1); + n_bruisers = DistributeEvenly_Get(1); + n_mages = DistributeEvenly_Get(1); + n_knights = DistributeEvenly_Get(1); + n_zombies = DistributeEvenly_Get(1); + n_spiders = DistributeEvenly_Get(1); + n_slimes = DistributeEvenly_Get(0.7); + n_shamblers = DistributeEvenly_Get(0.3); + if(flyspawns_count > 0) + n_wyverns = DistributeEvenly_Get(1); + if(waterspawns_count > 0) + n_stingrays = 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; +} + +void build_phase() +{ + 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; +} + +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_Initialize() +{ + InitializeEntity(world, td_DelayedInit, INITPRIO_GAMETYPE); ++ ++ addstat(STAT_CURRENT_WAVE, AS_FLOAT, stat_current_wave); ++ addstat(STAT_TOTALWAVES, AS_FLOAT, stat_totalwaves); +} + +MUTATOR_HOOKFUNCTION(td_TurretValidateTarget) +{ + 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) + 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; + + Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_TD_PROTECT); + + 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 && IS_PLAYER(frag_target) && IS_PLAYER(frag_attacker)) + { + frag_attacker.typehitsound += 1; + frag_damage = 0; + } + + 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; + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(td_MonsterCheckBossFlag) +{ + // No minibosses in tower defense + return TRUE; +} + +MUTATOR_HOOKFUNCTION(td_MonsterMove) +{ + entity head; + float n_players = 0; + + FOR_EACH_PLAYER(head) { ++n_players; } + if(n_players < 1) return TRUE; + + 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(monster_target.target2) + { + if(random() > 0.5) + self.target2 = monster_target.target2; + else + self.target2 = monster_target.target; + } + else + self.target2 = monster_target.target; + + 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; + } + + current_monsters += 1; + + self.spawnshieldtime = time + autocvar_g_td_monsters_spawnshield_time; + + self.drop_size = bound(5, self.health * 0.05, autocvar_g_pickup_fuel_max); + + self.target_range = 600; + + self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_CORPSE | DPCONTENTS_MONSTERCLIP; + + switch(self.monsterid) + { + case MONSTER_ZOMBIE: n_zombies -= 1; break; + case MONSTER_BRUTE: n_brutes -= 1; break; + case MONSTER_ANIMUS: n_animuses -= 1; break; + case MONSTER_SHAMBLER: n_shamblers -= 1; break; + case MONSTER_BRUISER: n_bruisers -= 1; break; + case MONSTER_WYVERN: n_wyverns -= 1; break; + case MONSTER_CERBERUS: n_cerberuses -= 1; break; + case MONSTER_SLIME: n_slimes -= 1; break; + case MONSTER_KNIGHT: n_knights -= 1; break; + case MONSTER_STINGRAY: n_stingrays -= 1; break; + case MONSTER_MAGE: n_mages -= 1; break; + case MONSTER_SPIDER: n_spiders -= 1; break; + } + + 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; + } + + 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)) + 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) +{ + 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; + buffturret(self, 0.5); + + 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.origin + v_forward * 100, MOVE_HITMODEL, self); + entity targ = trace_ent; + if(targ.owner.realowner == self) + targ = targ.owner; + + if(cmd_name == "turretspawn") + { + if(argv(1) == "list") + { + Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_LIST, "mlrs walker plasma towerbuff flac barricade"); + return TRUE; + } + if(!IS_PLAYER(self) || self.health <= 0) + { + Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_CANTSPAWN); + return TRUE; + } + if(max_turrets <= 0) + { + Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_DISABLED); + return TRUE; + } + if(self.turret_cnt >= max_turrets) + { + Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_MAXTURRETS, max_turrets); + return TRUE; + } + + spawnturret(self, self, argv(1), trace_endpos); + + 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)) + { + self.turret_cnt -= 1; + Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_REMOVE); + WaypointSprite_Kill(targ.sprite); + remove(targ.tur_head); + remove(targ); + return TRUE; + } + Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_AIM_REMOVE); + return TRUE; + } + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(td_ClientConnect) +{ + entity t; + + self.turret_cnt = 0; + + for(t = world; (t = findflags(t, turrcaps_flags, TFL_TURRCAPS_ISTURRET)); ) + if(t.playerid == self.playerid) + { + t.realowner = self; + self.turret_cnt += 1; + } + + 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_HOOK(ClientConnect, td_ClientConnect, 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_Initialize(); + } + + MUTATOR_ONROLLBACK_OR_REMOVE + { + // we actually cannot roll back td_Initialize here + // BUT: we don't need to! If this gets called, adding always + // succeeds. + } + + MUTATOR_ONREMOVE + { + error("This is a game type and it cannot be removed at runtime."); + return -1; + } + + return FALSE; +} diff --cc qcsrc/server/mutators/mutators.qh index 55c453f06,dab42bd65..20b64c324 --- a/qcsrc/server/mutators/mutators.qh +++ b/qcsrc/server/mutators/mutators.qh @@@ -4,8 -7,8 +7,9 @@@ MUTATOR_DECLARATION(gamemode_keepaway) MUTATOR_DECLARATION(gamemode_ctf); MUTATOR_DECLARATION(gamemode_nexball); MUTATOR_DECLARATION(gamemode_onslaught); +MUTATOR_DECLARATION(gamemode_td); MUTATOR_DECLARATION(gamemode_domination); + MUTATOR_DECLARATION(gamemode_lms); MUTATOR_DECLARATION(mutator_dodging); MUTATOR_DECLARATION(mutator_invincibleprojectiles); diff --cc qcsrc/server/progs.src index ab8ea3c79,b3cc22dda..e8f388e6c --- a/qcsrc/server/progs.src +++ b/qcsrc/server/progs.src @@@ -40,8 -43,8 +43,9 @@@ mutators/gamemode_domination.q mutators/gamemode_keyhunt.qh // TODO fix this mutators/gamemode_keepaway.qh mutators/gamemode_nexball.qh + mutators/gamemode_lms.qh mutators/mutator_dodging.qh +mutators/gamemode_towerdefense.qh //// tZork Turrets //// tturrets/include/turrets_early.qh @@@ -218,11 -216,14 +223,16 @@@ anticheat.q cheats.qc playerstats.qc + round_handler.qc + ../common/explosion_equation.qc +monsters/monsters.qh + mutators/base.qc + mutators/gamemode_assault.qc + mutators/gamemode_arena.qc + mutators/gamemode_ca.qc mutators/gamemode_ctf.qc mutators/gamemode_domination.qc mutators/gamemode_freezetag.qc @@@ -230,7 -231,7 +240,8 @@@ mutators/gamemode_keyhunt.q mutators/gamemode_keepaway.qc mutators/gamemode_nexball.qc mutators/gamemode_onslaught.qc +mutators/gamemode_towerdefense.qc + mutators/gamemode_lms.qc mutators/mutator_invincibleproj.qc mutators/mutator_new_toys.qc mutators/mutator_nix.qc diff --cc qcsrc/server/t_items.qc index 392c05ce1,40abd18b1..e8b183b8f --- a/qcsrc/server/t_items.qc +++ b/qcsrc/server/t_items.qc @@@ -768,6 -764,6 +764,8 @@@ void Item_Touch (void if (other.classname != "player") return; ++ if (other.frozen) ++ return; if (other.deadflag) return; if (self.solid != SOLID_TRIGGER)