+++ /dev/null
-#include "gamemode_assault.qh"
-
-#include "gamemode.qh"
-
-.entity sprite;
-
-// random functions
-void assault_objective_use()
-{SELFPARAM();
- // activate objective
- self.health = 100;
- //print("^2Activated objective ", self.targetname, "=", etos(self), "\n");
- //print("Activator is ", activator.classname, "\n");
-
- for (entity e = world; (e = find(e, target, this.targetname)); )
- {
- if (e.classname == "target_objective_decrease")
- {
- WITH(entity, self, e, target_objective_decrease_activate());
- }
- }
-
- setself(this);
-}
-
-vector target_objective_spawn_evalfunc(entity player, entity spot, vector current)
-{SELFPARAM();
- 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()
-{SELFPARAM();
- self.health = ASSAULT_VALUE_INACTIVE;
-}
-
-// decrease the health of targeted objectives
-void assault_objective_decrease_use()
-{SELFPARAM();
- 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 oldactivator, head;
-
- setself(this.enemy);
- if(self.message)
- FOR_EACH_PLAYER(head)
- centerprint(head, self.message);
-
- oldactivator = activator;
- activator = this;
- SUB_UseTargets();
- activator = oldactivator;
- setself(this);
- }
- }
-}
-
-void assault_setenemytoobjective()
-{SELFPARAM();
- 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)
-{SELFPARAM();
- entity decreaser;
-
- decreaser = self.assault_decreaser;
-
- if(decreaser.enemy.health >= ASSAULT_VALUE_INACTIVE)
- return false;
-
- return true;
-}
-
-void target_objective_decrease_activate()
-{SELFPARAM();
- 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(WP_Assault, 0.5 * (ent.absmin + ent.absmax), ent, assault_sprite, RADARICON_OBJECTIVE);
- 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, WP_AssaultDefend, WP_AssaultDestroy, WP_AssaultDestroy);
- WaypointSprite_UpdateMaxHealth(spr, ent.max_health);
- WaypointSprite_UpdateHealth(spr, ent.health);
- ent.sprite = spr;
- }
- else
- WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultPush, WP_AssaultPush);
- }
-}
-
-void target_objective_decrease_findtarget()
-{
- assault_setenemytoobjective();
-}
-
-void target_assault_roundend_reset()
-{SELFPARAM();
- //print("round end reset\n");
- self.cnt = self.cnt + 1; // up round counter
- self.winning = 0; // up round
-}
-
-void target_assault_roundend_use()
-{SELFPARAM();
- self.winning = 1; // round has been won by attackers
-}
-
-void assault_roundstart_use()
-{SELFPARAM();
- activator = self;
- SUB_UseTargets();
-
- //(Re)spawn all turrets
- for(entity ent = NULL; (ent = find(ent, classname, "turret_main")); ) {
- // Swap turret teams
- if(ent.team == NUM_TEAM_1)
- ent.team = NUM_TEAM_2;
- else
- ent.team = NUM_TEAM_1;
-
- // Dubbles as teamchange
- WITH(entity, self, ent, turret_respawn());
- }
-}
-
-void assault_wall_think()
-{SELFPARAM();
- 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_clearreturn(entity veh);
-void vehicles_spawn();
-void assault_new_round()
-{SELFPARAM();
- //bprint("ASSAULT: new round\n");
-
- // Eject players from vehicles
- entity e;
- FOR_EACH_PLAYER(e)
- {
- if(e.vehicle)
- {
- WITH(entity, self, e, vehicles_exit(VHEF_RELEASE));
- }
- }
-
- for (entity e_ = findchainflags(vehicle_flags, VHF_ISVEHICLE); e_; e_ = e_.chain)
- {
- setself(e_);
- vehicles_clearreturn(self);
- vehicles_spawn();
- }
-
- setself(this);
-
- // up round counter
- self.winning = self.winning + 1;
-
- // swap attacker/defender roles
- if(assault_attacker_team == NUM_TEAM_1)
- assault_attacker_team = NUM_TEAM_2;
- else
- assault_attacker_team = NUM_TEAM_1;
-
- entity ent;
- for(ent = world; (ent = nextent(ent)); )
- {
- if(clienttype(ent) == CLIENTTYPE_NOTACLIENT)
- {
- 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
-spawnfunc(info_player_attacker)
-{
- if (!g_assault) { remove(self); return; }
-
- self.team = NUM_TEAM_1; // red, gets swapped every round
- spawnfunc_info_player_deathmatch(this);
-}
-
-spawnfunc(info_player_defender)
-{
- if (!g_assault) { remove(self); return; }
-
- self.team = NUM_TEAM_2; // blue, gets swapped every round
- spawnfunc_info_player_deathmatch(this);
-}
-
-spawnfunc(target_objective)
-{
- if (!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;
-}
-
-spawnfunc(target_objective_decrease)
-{
- if (!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
-spawnfunc(func_breakable);
-spawnfunc(func_assault_destructible)
-{
- if (!g_assault) { remove(self); return; }
-
- self.spawnflags = 3;
- self.classname = "func_assault_destructible";
-
- if(assault_attacker_team == NUM_TEAM_1)
- self.team = NUM_TEAM_2;
- else
- self.team = NUM_TEAM_1;
-
- spawnfunc_func_breakable(this);
-}
-
-spawnfunc(func_assault_wall)
-{
- if (!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);
-}
-
-spawnfunc(target_assault_roundend)
-{
- if (!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;
-}
-
-spawnfunc(target_assault_roundstart)
-{
- if (!g_assault) { remove(self); return; }
-
- 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)
-{SELFPARAM();
- 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 (!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<bestvalue)
- {
- best = wp;
- bestvalue = wp.cnt;
- }
- }
- }
- }
-
- if(best)
- {
- /// dprint("waypoints around target were found\n");
- // te_lightning2(world, '0 0 0', best.origin);
- // te_knightspike(best.origin);
-
- navigation_routerating(best, ratingscale, 4000);
- best.cnt += 1;
-
- self.havocbot_attack_time = 0;
-
- if(checkpvs(self.view_ofs,ad))
- if(checkpvs(self.view_ofs,best))
- {
- // dprint("increasing attack time for this target\n");
- self.havocbot_attack_time = time + 2;
- }
- }
- }
-}
-
-void havocbot_role_ast_offense()
-{SELFPARAM();
- 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, 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()
-{SELFPARAM();
- 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)
-{SELFPARAM();
- 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(as, PlayerSpawn)
-{SELFPARAM();
- if(self.team == assault_attacker_team)
- Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_ASSAULT_ATTACKING);
- else
- Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_ASSAULT_DEFENDING);
-
- return false;
-}
-
-MUTATOR_HOOKFUNCTION(as, TurretSpawn)
-{SELFPARAM();
- if(!self.team || self.team == MAX_SHOT_DISTANCE)
- self.team = 5; // this gets reversed when match starts?
-
- return false;
-}
-
-MUTATOR_HOOKFUNCTION(as, VehicleSpawn)
-{SELFPARAM();
- self.nextthink = time + 0.5;
-
- return false;
-}
-
-MUTATOR_HOOKFUNCTION(as, HavocBot_ChooseRole)
-{SELFPARAM();
- havocbot_ast_reset_role(self);
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(as, PlayHitsound)
-{
- return (frag_victim.classname == "func_assault_destructible");
-}
-
-MUTATOR_HOOKFUNCTION(as, GetTeamCount)
-{
- // assault always has 2 teams
- c1 = c2 = 0;
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(as, CheckRules_World)
-{
- ret_float = WinningCondition_Assault();
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(as, ReadLevelCvars)
-{
- // no assault warmups
- warmup_stage = 0;
- return false;
-}
-
-MUTATOR_HOOKFUNCTION(as, OnEntityPreSpawn)
-{
- switch(self.classname)
- {
- case "info_player_team1":
- case "info_player_team2":
- case "info_player_team3":
- case "info_player_team4":
- return true;
- }
-
- return false;
-}
-
-// 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();
-}
-
-REGISTER_MUTATOR(as, g_assault)
-{
- ActivateTeamplay();
- have_team_spawns = -1; // request team spawns
-
- 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
- {
- LOG_INFO("This is a game type and it cannot be removed at runtime.");
- return -1;
- }
-
- return 0;
-}