X-Git-Url: https://de.git.xonotic.org/?a=blobdiff_plain;f=qcsrc%2Fcommon%2Fgamemodes%2Fgamemode%2Fassault%2Fassault.qc;h=c3e582a0f3be5516627627881aa90fb66196572a;hb=4e21f418ad9e6287efb942c1fa2861a51981110a;hp=e7134182e497b95facf46c75d680bc5079dd7348;hpb=55a4614bcbabe7335c9a7c25bbb6458400b3a424;p=xonotic%2Fxonotic-data.pk3dir.git diff --git a/qcsrc/common/gamemodes/gamemode/assault/assault.qc b/qcsrc/common/gamemodes/gamemode/assault/assault.qc index e7134182e..c3e582a0f 100644 --- a/qcsrc/common/gamemodes/gamemode/assault/assault.qc +++ b/qcsrc/common/gamemodes/gamemode/assault/assault.qc @@ -1,648 +1 @@ #include "assault.qh" - -// TODO: split into sv_assault -#ifdef SVQC -.entity sprite; -#define AS_ROUND_DELAY 5 - -IntrusiveList g_assault_destructibles; -IntrusiveList g_assault_objectivedecreasers; -IntrusiveList g_assault_objectives; -STATIC_INIT(g_assault) -{ - g_assault_destructibles = IL_NEW(); - g_assault_objectivedecreasers = IL_NEW(); - g_assault_objectives = IL_NEW(); -} - -// random functions -void assault_objective_use(entity this, entity actor, entity trigger) -{ - // activate objective - SetResourceAmountExplicit(this, RESOURCE_HEALTH, 100); - //print("^2Activated objective ", this.targetname, "=", etos(this), "\n"); - //print("Activator is ", actor.classname, "\n"); - - IL_EACH(g_assault_objectivedecreasers, it.target == this.targetname, - { - target_objective_decrease_activate(it); - }); -} - -vector target_objective_spawn_evalfunc(entity this, entity player, entity spot, vector current) -{ - float hlth = GetResourceAmount(this, RESOURCE_HEALTH); - if (hlth < 0 || hlth >= 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(entity this) -{ - SetResourceAmountExplicit(this, RESOURCE_HEALTH, ASSAULT_VALUE_INACTIVE); -} - -// decrease the health of targeted objectives -void assault_objective_decrease_use(entity this, entity actor, entity trigger) -{ - if(actor.team != assault_attacker_team) - { - // wrong team triggered decrease - return; - } - - if(trigger.assault_sprite) - { - WaypointSprite_Disown(trigger.assault_sprite, waypointsprite_deadlifetime); - if(trigger.classname == "func_assault_destructible") - trigger.sprite = NULL; // TODO: just unsetting it?! - } - else - return; // already activated! cannot activate again! - - float hlth = GetResourceAmount(this.enemy, RESOURCE_HEALTH); - if (hlth < ASSAULT_VALUE_INACTIVE) - { - if (hlth - this.dmg > 0.5) - { - GameRules_scoring_add_team(actor, SCORE, this.dmg); - TakeResource(this.enemy, RESOURCE_HEALTH, this.dmg); - } - else - { - GameRules_scoring_add_team(actor, SCORE, hlth); - GameRules_scoring_add_team(actor, ASSAULT_OBJECTIVES, 1); - SetResourceAmountExplicit(this.enemy, RESOURCE_HEALTH, -1); - - if(this.enemy.message) - FOREACH_CLIENT(IS_PLAYER(it), { centerprint(it, this.enemy.message); }); - - SUB_UseTargets(this.enemy, this, trigger); - } - } -} - -void assault_setenemytoobjective(entity this) -{ - IL_EACH(g_assault_objectives, it.targetname == this.target, - { - if(this.enemy == NULL) - this.enemy = it; - else - objerror(this, "more than one objective as target - fix the map!"); - break; - }); - - if(this.enemy == NULL) - objerror(this, "no objective as target - fix the map!"); -} - -bool assault_decreaser_sprite_visible(entity this, entity player, entity view) -{ - if(GetResourceAmount(this.assault_decreaser.enemy, RESOURCE_HEALTH) >= ASSAULT_VALUE_INACTIVE) - return false; - - return true; -} - -void target_objective_decrease_activate(entity this) -{ - entity spr; - this.owner = NULL; - FOREACH_ENTITY_STRING(target, this.targetname, - { - if(it.assault_sprite != NULL) - { - WaypointSprite_Disown(it.assault_sprite, waypointsprite_deadlifetime); - if(it.classname == "func_assault_destructible") - it.sprite = NULL; // TODO: just unsetting it?! - } - - spr = WaypointSprite_SpawnFixed(WP_AssaultDefend, 0.5 * (it.absmin + it.absmax), it, assault_sprite, RADARICON_OBJECTIVE); - spr.assault_decreaser = this; - spr.waypointsprite_visible_for_player = assault_decreaser_sprite_visible; - spr.classname = "sprite_waypoint"; - WaypointSprite_UpdateRule(spr, assault_attacker_team, SPRITERULE_TEAMPLAY); - if(it.classname == "func_assault_destructible") - { - WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultDestroy, WP_AssaultDestroy); - WaypointSprite_UpdateMaxHealth(spr, it.max_health); - WaypointSprite_UpdateHealth(spr, GetResourceAmount(it, RESOURCE_HEALTH)); - it.sprite = spr; - } - else - WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultPush, WP_AssaultPush); - }); -} - -void target_objective_decrease_findtarget(entity this) -{ - assault_setenemytoobjective(this); -} - -void target_assault_roundend_reset(entity this) -{ - //print("round end reset\n"); - ++this.cnt; // up round counter - this.winning = false; // up round -} - -void target_assault_roundend_use(entity this, entity actor, entity trigger) -{ - this.winning = 1; // round has been won by attackers -} - -void assault_roundstart_use(entity this, entity actor, entity trigger) -{ - SUB_UseTargets(this, this, trigger); - - //(Re)spawn all turrets - IL_EACH(g_turrets, true, - { - // Swap turret teams - if(it.team == NUM_TEAM_1) - it.team = NUM_TEAM_2; - else - it.team = NUM_TEAM_1; - - // Doubles as teamchange - turret_respawn(it); - }); -} -void assault_roundstart_use_this(entity this) -{ - assault_roundstart_use(this, NULL, NULL); -} - -void assault_wall_think(entity this) -{ - if(GetResourceAmount(this.enemy, RESOURCE_HEALTH) < 0) - { - this.model = ""; - this.solid = SOLID_NOT; - } - else - { - this.model = this.mdl; - this.solid = SOLID_BSP; - } - - this.nextthink = time + 0.2; -} - -// trigger new round -// reset objectives, toggle spawnpoints, reset triggers, ... -void assault_new_round(entity this) -{ - //bprint("ASSAULT: new round\n"); - - // up round counter - this.winning = this.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; - - IL_EACH(g_saved_team, !IS_CLIENT(it), - { - if(it.team_saved == NUM_TEAM_1) - it.team_saved = NUM_TEAM_2; - else if(it.team_saved == NUM_TEAM_2) - it.team_saved = NUM_TEAM_1; - }); - - // reset the level with a countdown - cvar_set("timelimit", ftos(ceil(time - AS_ROUND_DELAY - game_starttime) / 60)); - ReadyRestart_force(); // sets game_starttime -} - -entity as_round; -.entity ent_winning; -void as_round_think() -{ - game_stopped = false; - assault_new_round(as_round.ent_winning); - delete(as_round); - as_round = NULL; -} - -// Assault winning condition: If the attackers triggered a round end (by fulfilling all objectives) -// they win. Otherwise the defending team wins once the timelimit passes. -int WinningCondition_Assault() -{ - if(as_round) - return WINNING_NO; - - WinningConditionHelper(NULL); // set worldstatus - - int status = WINNING_NO; - // as the timelimit has not yet passed just assume the defending team will win - if(assault_attacker_team == NUM_TEAM_1) - { - SetWinners(team, NUM_TEAM_2); - } - else - { - SetWinners(team, NUM_TEAM_1); - } - - entity ent; - ent = find(NULL, classname, "target_assault_roundend"); - if(ent) - { - if(ent.winning) // round end has been triggered by attacking team - { - bprint("Assault: round completed.\n"); - SetWinners(team, assault_attacker_team); - - TeamScore_AddToTeam(assault_attacker_team, ST_ASSAULT_OBJECTIVES, 666 - TeamScore_AddToTeam(assault_attacker_team, ST_ASSAULT_OBJECTIVES, 0)); - - if(ent.cnt == 1 || autocvar_g_campaign) // this was the second round - { - status = WINNING_YES; - } - else - { - Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ASSAULT_OBJ_DESTROYED, ceil(time - game_starttime)); - as_round = new(as_round); - as_round.think = as_round_think; - as_round.ent_winning = ent; - as_round.nextthink = time + AS_ROUND_DELAY; - game_stopped = true; - - // make sure timelimit isn't hit while the game is blocked - if(autocvar_timelimit > 0) - if(time + AS_ROUND_DELAY >= game_starttime + autocvar_timelimit * 60) - cvar_set("timelimit", ftos(autocvar_timelimit + AS_ROUND_DELAY / 60)); - } - } - } - - return status; -} - -// spawnfuncs -spawnfunc(info_player_attacker) -{ - if (!g_assault) { delete(this); return; } - - this.team = NUM_TEAM_1; // red, gets swapped every round - spawnfunc_info_player_deathmatch(this); -} - -spawnfunc(info_player_defender) -{ - if (!g_assault) { delete(this); return; } - - this.team = NUM_TEAM_2; // blue, gets swapped every round - spawnfunc_info_player_deathmatch(this); -} - -spawnfunc(target_objective) -{ - if (!g_assault) { delete(this); return; } - - this.classname = "target_objective"; - IL_PUSH(g_assault_objectives, this); - this.use = assault_objective_use; - this.reset = assault_objective_reset; - this.reset(this); - this.spawn_evalfunc = target_objective_spawn_evalfunc; -} - -spawnfunc(target_objective_decrease) -{ - if (!g_assault) { delete(this); return; } - - this.classname = "target_objective_decrease"; - IL_PUSH(g_assault_objectivedecreasers, this); - - if(!this.dmg) - this.dmg = 101; - - this.use = assault_objective_decrease_use; - SetResourceAmountExplicit(this, RESOURCE_HEALTH, ASSAULT_VALUE_INACTIVE); - this.max_health = ASSAULT_VALUE_INACTIVE; - this.enemy = NULL; - - InitializeEntity(this, target_objective_decrease_findtarget, INITPRIO_FINDTARGET); -} - -// destructible walls that can be used to trigger target_objective_decrease -bool destructible_heal(entity targ, entity inflictor, float amount, float limit) -{ - float true_limit = ((limit != RESOURCE_LIMIT_NONE) ? limit : targ.max_health); - float hlth = GetResourceAmount(targ, RESOURCE_HEALTH); - if (hlth <= 0 || hlth >= true_limit) - return false; - - GiveResourceWithLimit(targ, RESOURCE_HEALTH, amount, true_limit); - if(targ.sprite) - { - WaypointSprite_UpdateHealth(targ.sprite, GetResourceAmount(targ, RESOURCE_HEALTH)); - } - func_breakable_colormod(targ); - return true; -} - -spawnfunc(func_breakable); -spawnfunc(func_assault_destructible) -{ - if (!g_assault) { delete(this); return; } - - this.spawnflags = 3; - this.classname = "func_assault_destructible"; - this.event_heal = destructible_heal; - IL_PUSH(g_assault_destructibles, this); - - if(assault_attacker_team == NUM_TEAM_1) - this.team = NUM_TEAM_2; - else - this.team = NUM_TEAM_1; - - spawnfunc_func_breakable(this); -} - -spawnfunc(func_assault_wall) -{ - if (!g_assault) { delete(this); return; } - - this.classname = "func_assault_wall"; - this.mdl = this.model; - _setmodel(this, this.mdl); - this.solid = SOLID_BSP; - setthink(this, assault_wall_think); - this.nextthink = time; - InitializeEntity(this, assault_setenemytoobjective, INITPRIO_FINDTARGET); -} - -spawnfunc(target_assault_roundend) -{ - if (!g_assault) { delete(this); return; } - - this.winning = 0; // round not yet won by attackers - this.classname = "target_assault_roundend"; - this.use = target_assault_roundend_use; - this.cnt = 0; // first round - this.reset = target_assault_roundend_reset; -} - -spawnfunc(target_assault_roundstart) -{ - if (!g_assault) { delete(this); return; } - - assault_attacker_team = NUM_TEAM_1; - this.classname = "target_assault_roundstart"; - this.use = assault_roundstart_use; - this.reset2 = assault_roundstart_use_this; - InitializeEntity(this, assault_roundstart_use_this, INITPRIO_FINDTARGET); -} - -// legacy bot code -void havocbot_goalrating_ast_targets(entity this, float ratingscale) -{ - IL_EACH(g_assault_destructibles, it.bot_attack, - { - if (it.target == "") - continue; - - bool found = false; - entity destr = it; - IL_EACH(g_assault_objectivedecreasers, it.targetname == destr.target, - { - float hlth = GetResourceAmount(it.enemy, RESOURCE_HEALTH); - if (hlth > 0 && hlth < ASSAULT_VALUE_INACTIVE) - { - found = true; - break; - } - }); - - if(!found) - continue; - - vector p = 0.5 * (it.absmin + it.absmax); - - // Find and rate waypoints around it - found = false; - entity best = NULL; - float bestvalue = 99999999999; - entity des = it; - for(float radius = 0; radius < 1500 && !found; radius += 500) - { - FOREACH_ENTITY_RADIUS(p, radius, it.classname == "waypoint" && !(it.wpflags & WAYPOINTFLAG_GENERATED), - { - if(checkpvs(it.origin, des)) - { - found = true; - if(it.cnt < bestvalue) - { - best = it; - bestvalue = it.cnt; - } - } - }); - } - - if(best) - { - /// dprint("waypoints around target were found\n"); - // te_lightning2(NULL, '0 0 0', best.origin); - // te_knightspike(best.origin); - - navigation_routerating(this, best, ratingscale, 4000); - best.cnt += 1; - - this.havocbot_attack_time = 0; - - if(checkpvs(this.origin + this.view_ofs, it)) - if(checkpvs(this.origin + this.view_ofs, best)) - { - // dprint("increasing attack time for this target\n"); - this.havocbot_attack_time = time + 2; - } - } - }); -} - -void havocbot_role_ast_offense(entity this) -{ - if(IS_DEAD(this)) - { - this.havocbot_attack_time = 0; - havocbot_ast_reset_role(this); - return; - } - - // Set the role timeout if necessary - if (!this.havocbot_role_timeout) - this.havocbot_role_timeout = time + 120; - - if (time > this.havocbot_role_timeout) - { - havocbot_ast_reset_role(this); - return; - } - - if(this.havocbot_attack_time>time) - return; - - if (navigation_goalrating_timeout(this)) - { - navigation_goalrating_start(this); - havocbot_goalrating_enemyplayers(this, 20000, this.origin, 650); - havocbot_goalrating_ast_targets(this, 20000); - havocbot_goalrating_items(this, 15000, this.origin, 10000); - navigation_goalrating_end(this); - - navigation_goalrating_timeout_set(this); - } -} - -void havocbot_role_ast_defense(entity this) -{ - if(IS_DEAD(this)) - { - this.havocbot_attack_time = 0; - havocbot_ast_reset_role(this); - return; - } - - // Set the role timeout if necessary - if (!this.havocbot_role_timeout) - this.havocbot_role_timeout = time + 120; - - if (time > this.havocbot_role_timeout) - { - havocbot_ast_reset_role(this); - return; - } - - if(this.havocbot_attack_time>time) - return; - - if (navigation_goalrating_timeout(this)) - { - navigation_goalrating_start(this); - havocbot_goalrating_enemyplayers(this, 20000, this.origin, 3000); - havocbot_goalrating_ast_targets(this, 20000); - havocbot_goalrating_items(this, 15000, this.origin, 10000); - navigation_goalrating_end(this); - - navigation_goalrating_timeout_set(this); - } -} - -void havocbot_role_ast_setrole(entity this, float role) -{ - switch(role) - { - case HAVOCBOT_AST_ROLE_DEFENSE: - this.havocbot_role = havocbot_role_ast_defense; - this.havocbot_role_flags = HAVOCBOT_AST_ROLE_DEFENSE; - this.havocbot_role_timeout = 0; - break; - case HAVOCBOT_AST_ROLE_OFFENSE: - this.havocbot_role = havocbot_role_ast_offense; - this.havocbot_role_flags = HAVOCBOT_AST_ROLE_OFFENSE; - this.havocbot_role_timeout = 0; - break; - } -} - -void havocbot_ast_reset_role(entity this) -{ - if(IS_DEAD(this)) - return; - - if(this.team == assault_attacker_team) - havocbot_role_ast_setrole(this, HAVOCBOT_AST_ROLE_OFFENSE); - else - havocbot_role_ast_setrole(this, HAVOCBOT_AST_ROLE_DEFENSE); -} - -// mutator hooks -MUTATOR_HOOKFUNCTION(as, PlayerSpawn) -{ - entity player = M_ARGV(0, entity); - - if(player.team == assault_attacker_team) - Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_ASSAULT_ATTACKING); - else - Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_ASSAULT_DEFENDING); -} - -MUTATOR_HOOKFUNCTION(as, TurretSpawn) -{ - entity turret = M_ARGV(0, entity); - - if(!turret.team || turret.team == FLOAT_MAX) - turret.team = 5; // this gets reversed when match starts? -} - -MUTATOR_HOOKFUNCTION(as, VehicleInit) -{ - entity veh = M_ARGV(0, entity); - - veh.nextthink = time + 0.5; -} - -MUTATOR_HOOKFUNCTION(as, HavocBot_ChooseRole) -{ - entity bot = M_ARGV(0, entity); - - havocbot_ast_reset_role(bot); - return true; -} - -MUTATOR_HOOKFUNCTION(as, PlayHitsound) -{ - entity frag_victim = M_ARGV(0, entity); - - return (frag_victim.classname == "func_assault_destructible"); -} - -MUTATOR_HOOKFUNCTION(as, TeamBalance_CheckAllowedTeams) -{ - // assault always has 2 teams - M_ARGV(0, float) = BIT(0) | BIT(1); - return true; -} - -MUTATOR_HOOKFUNCTION(as, CheckRules_World) -{ - M_ARGV(0, float) = WinningCondition_Assault(); - return true; -} - -MUTATOR_HOOKFUNCTION(as, ReadLevelCvars) -{ - // incompatible - warmup_stage = 0; - sv_ready_restart_after_countdown = 0; -} - -MUTATOR_HOOKFUNCTION(as, OnEntityPreSpawn) -{ - entity ent = M_ARGV(0, entity); - - switch(ent.classname) - { - case "info_player_team1": - case "info_player_team2": - case "info_player_team3": - case "info_player_team4": - return true; - } -} - -MUTATOR_HOOKFUNCTION(as, ReadyRestart_Deny) -{ - // readyrestart not supported (yet) - return true; -} -#endif