#include "gamemode_domination.qh" #ifndef GAMEMODE_DOMINATION_H #define GAMEMODE_DOMINATION_H #define autocvar_g_domination_point_limit cvar("g_domination_point_limit") bool autocvar_g_domination_roundbased; int autocvar_g_domination_roundbased_point_limit; int autocvar_g_domination_point_leadlimit; void dom_Initialize(); REGISTER_MUTATOR(dom, false) { MUTATOR_ONADD { if (time > 1) // game loads at time 1 error("This is a game type and it cannot be added at runtime."); dom_Initialize(); int fraglimit_override = autocvar_g_domination_point_limit; if (autocvar_g_domination_roundbased && autocvar_g_domination_roundbased_point_limit) fraglimit_override = autocvar_g_domination_roundbased_point_limit; ActivateTeamplay(); SetLimits(fraglimit_override, autocvar_g_domination_point_leadlimit, autocvar_timelimit_override, -1); have_team_spawns = -1; // request team spawns } MUTATOR_ONREMOVE { LOG_INFO("This is a game type and it cannot be removed at runtime."); return -1; } return 0; } // score rule declarations const float ST_DOM_TICKS = 1; const float SP_DOM_TICKS = 4; const float SP_DOM_TAKES = 5; const float ST_DOM_CAPS = 1; const float SP_DOM_CAPS = 4; // pps: points per second .float dom_total_pps = _STAT(DOM_TOTAL_PPS); .float dom_pps_red = _STAT(DOM_PPS_RED); .float dom_pps_blue = _STAT(DOM_PPS_BLUE); .float dom_pps_yellow = _STAT(DOM_PPS_YELLOW); .float dom_pps_pink = _STAT(DOM_PPS_PINK); float total_pps; float pps_red; float pps_blue; float pps_yellow; float pps_pink; // capture declarations .float enemy_playerid; .entity sprite; .float captime; // misc globals float domination_roundbased; float domination_teams; #endif #ifdef IMPLEMENTATION #include bool g_domination; int autocvar_g_domination_default_teams; bool autocvar_g_domination_disable_frags; int autocvar_g_domination_point_amt; bool autocvar_g_domination_point_fullbright; float autocvar_g_domination_round_timelimit; float autocvar_g_domination_warmup; float autocvar_g_domination_point_rate; int autocvar_g_domination_teams_override; void dom_EventLog(string mode, float team_before, entity actor) // use an alias for easy changing and quick editing later { if(autocvar_sv_eventlog) GameLogEcho(strcat(":dom:", mode, ":", ftos(team_before), ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : ""))); } void set_dom_state(entity e) { e.dom_total_pps = total_pps; e.dom_pps_red = pps_red; e.dom_pps_blue = pps_blue; if(domination_teams >= 3) e.dom_pps_yellow = pps_yellow; if(domination_teams >= 4) e.dom_pps_pink = pps_pink; } void dompoint_captured(entity this) { float old_delay, old_team, real_team; // now that the delay has expired, switch to the latest team to lay claim to this point entity head = this.owner; real_team = this.cnt; this.cnt = -1; dom_EventLog("taken", this.team, this.dmg_inflictor); this.dmg_inflictor = NULL; this.goalentity = head; this.model = head.mdl; this.modelindex = head.dmg; this.skin = head.skin; float points, wait_time; if (autocvar_g_domination_point_amt) points = autocvar_g_domination_point_amt; else points = this.frags; if (autocvar_g_domination_point_rate) wait_time = autocvar_g_domination_point_rate; else wait_time = this.wait; if(domination_roundbased) bprint(sprintf("^3%s^3%s\n", head.netname, this.message)); else Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_DOMINATION_CAPTURE_TIME, head.netname, this.message, points, wait_time); if(this.enemy.playerid == this.enemy_playerid) PlayerScore_Add(this.enemy, SP_DOM_TAKES, 1); else this.enemy = NULL; if (head.noise != "") if(this.enemy) _sound(this.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM); else _sound(this, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM); if (head.noise1 != "") play2all(head.noise1); this.delay = time + wait_time; // do trigger work old_delay = this.delay; old_team = this.team; this.team = real_team; this.delay = 0; SUB_UseTargets (this, this, NULL); this.delay = old_delay; this.team = old_team; entity msg = WP_DomNeut; switch(this.team) { case NUM_TEAM_1: msg = WP_DomRed; break; case NUM_TEAM_2: msg = WP_DomBlue; break; case NUM_TEAM_3: msg = WP_DomYellow; break; case NUM_TEAM_4: msg = WP_DomPink; break; } WaypointSprite_UpdateSprites(this.sprite, msg, WP_Null, WP_Null); total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0; FOREACH_ENTITY_CLASS("dom_controlpoint", true, LAMBDA( if (autocvar_g_domination_point_amt) points = autocvar_g_domination_point_amt; else points = it.frags; if (autocvar_g_domination_point_rate) wait_time = autocvar_g_domination_point_rate; else wait_time = it.wait; switch(it.goalentity.team) { case NUM_TEAM_1: pps_red += points/wait_time; break; case NUM_TEAM_2: pps_blue += points/wait_time; break; case NUM_TEAM_3: pps_yellow += points/wait_time; break; case NUM_TEAM_4: pps_pink += points/wait_time; break; } total_pps += points/wait_time; )); WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, colormapPaletteColor(this.goalentity.team - 1, 0)); WaypointSprite_Ping(this.sprite); this.captime = time; FOREACH_CLIENT(IS_REAL_CLIENT(it), LAMBDA(set_dom_state(it))); } void AnimateDomPoint(entity this) { if(this.pain_finished > time) return; this.pain_finished = time + this.t_width; if(this.nextthink > this.pain_finished) this.nextthink = this.pain_finished; this.frame = this.frame + 1; if(this.frame > this.t_length) this.frame = 0; } void dompointthink(entity this) { float fragamt; this.nextthink = time + 0.1; //this.frame = this.frame + 1; //if(this.frame > 119) // this.frame = 0; AnimateDomPoint(this); // give points if (gameover || this.delay > time || time < game_starttime) // game has ended, don't keep giving points return; if(autocvar_g_domination_point_rate) this.delay = time + autocvar_g_domination_point_rate; else this.delay = time + this.wait; // give credit to the team // NOTE: this defaults to 0 if (!domination_roundbased) if (this.goalentity.netname != "") { if(autocvar_g_domination_point_amt) fragamt = autocvar_g_domination_point_amt; else fragamt = this.frags; TeamScore_AddToTeam(this.goalentity.team, ST_SCORE, fragamt); TeamScore_AddToTeam(this.goalentity.team, ST_DOM_TICKS, fragamt); // give credit to the individual player, if he is still there if (this.enemy.playerid == this.enemy_playerid) { PlayerScore_Add(this.enemy, SP_SCORE, fragamt); PlayerScore_Add(this.enemy, SP_DOM_TICKS, fragamt); } else this.enemy = NULL; } } void dompointtouch(entity this, entity toucher) { if (!IS_PLAYER(toucher)) return; if (toucher.health < 1) return; if(round_handler_IsActive() && !round_handler_IsRoundStarted()) return; if(time < this.captime + 0.3) return; // only valid teams can claim it entity head = find(NULL, classname, "dom_team"); while (head && head.team != toucher.team) head = find(head, classname, "dom_team"); if (!head || head.netname == "" || head == this.goalentity) return; // delay capture this.team = this.goalentity.team; // this stores the PREVIOUS team! this.cnt = toucher.team; this.owner = head; // team to switch to after the delay this.dmg_inflictor = toucher; // this.state = 1; // this.delay = time + cvar("g_domination_point_capturetime"); //this.nextthink = time + cvar("g_domination_point_capturetime"); //this.think = dompoint_captured; // go to neutral team in the mean time head = find(NULL, classname, "dom_team"); while (head && head.netname != "") head = find(head, classname, "dom_team"); if(head == NULL) return; WaypointSprite_UpdateSprites(this.sprite, WP_DomNeut, WP_Null, WP_Null); WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, '0 1 1'); WaypointSprite_Ping(this.sprite); this.goalentity = head; this.model = head.mdl; this.modelindex = head.dmg; this.skin = head.skin; this.enemy = toucher; // individual player scoring this.enemy_playerid = toucher.playerid; dompoint_captured(this); } void dom_controlpoint_setup(entity this) { entity head; // find the spawnfunc_dom_team representing unclaimed points head = find(NULL, classname, "dom_team"); while(head && head.netname != "") head = find(head, classname, "dom_team"); if (!head) objerror(this, "no spawnfunc_dom_team with netname \"\" found\n"); // copy important properties from spawnfunc_dom_team entity this.goalentity = head; _setmodel(this, head.mdl); // precision already set this.skin = head.skin; this.cnt = -1; if(this.message == "") this.message = " has captured a control point"; if(this.frags <= 0) this.frags = 1; if(this.wait <= 0) this.wait = 5; float points, waittime; if (autocvar_g_domination_point_amt) points = autocvar_g_domination_point_amt; else points = this.frags; if (autocvar_g_domination_point_rate) waittime = autocvar_g_domination_point_rate; else waittime = this.wait; total_pps += points/waittime; if(!this.t_width) this.t_width = 0.02; // frame animation rate if(!this.t_length) this.t_length = 239; // maximum frame setthink(this, dompointthink); this.nextthink = time; settouch(this, dompointtouch); this.solid = SOLID_TRIGGER; this.flags = FL_ITEM; setsize(this, '-32 -32 -32', '32 32 32'); setorigin(this, this.origin + '0 0 20'); droptofloor(this); waypoint_spawnforitem(this); WaypointSprite_SpawnFixed(WP_DomNeut, this.origin + '0 0 32', this, sprite, RADARICON_DOMPOINT); } float total_controlpoints; void Domination_count_controlpoints() { total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0; FOREACH_ENTITY_CLASS("dom_controlpoint", true, { ++total_controlpoints; redowned += (it.goalentity.team == NUM_TEAM_1); blueowned += (it.goalentity.team == NUM_TEAM_2); yellowowned += (it.goalentity.team == NUM_TEAM_3); pinkowned += (it.goalentity.team == NUM_TEAM_4); }); } float Domination_GetWinnerTeam() { float winner_team = 0; if(redowned == total_controlpoints) winner_team = NUM_TEAM_1; if(blueowned == total_controlpoints) { if(winner_team) return 0; winner_team = NUM_TEAM_2; } if(yellowowned == total_controlpoints) { if(winner_team) return 0; winner_team = NUM_TEAM_3; } if(pinkowned == total_controlpoints) { if(winner_team) return 0; winner_team = NUM_TEAM_4; } if(winner_team) return winner_team; return -1; // no control points left? } #define DOM_OWNED_CONTROLPOINTS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0)) #define DOM_OWNED_CONTROLPOINTS_OK() (DOM_OWNED_CONTROLPOINTS() < total_controlpoints) float Domination_CheckWinner() { if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0) { Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER); Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER); round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit); return 1; } Domination_count_controlpoints(); float winner_team = Domination_GetWinnerTeam(); if(winner_team == -1) return 0; if(winner_team > 0) { Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN)); Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN)); TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1); } else if(winner_team == -1) { Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED); Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED); } round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit); return 1; } float Domination_CheckPlayers() { return 1; } void Domination_RoundStart() { FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(it.player_blocked = false)); } //go to best items, or control points you don't own void havocbot_role_dom(entity this) { if(IS_DEAD(this)) return; if (this.bot_strategytime < time) { this.bot_strategytime = time + autocvar_bot_ai_strategyinterval; navigation_goalrating_start(this); havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000); havocbot_goalrating_items(this, 8000, this.origin, 8000); //havocbot_goalrating_enemyplayers(this, 3000, this.origin, 2000); //havocbot_goalrating_waypoints(1, this.origin, 1000); navigation_goalrating_end(this); } } MUTATOR_HOOKFUNCTION(dom, GetTeamCount) { // fallback? M_ARGV(0, float) = domination_teams; string ret_string = "dom_team"; entity head = find(NULL, classname, ret_string); while(head) { if(head.netname != "") { switch(head.team) { case NUM_TEAM_1: c1 = 0; break; case NUM_TEAM_2: c2 = 0; break; case NUM_TEAM_3: c3 = 0; break; case NUM_TEAM_4: c4 = 0; break; } } head = find(head, classname, ret_string); } M_ARGV(1, string) = string_null; return true; } MUTATOR_HOOKFUNCTION(dom, reset_map_players) { total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0; FOREACH_CLIENT(IS_PLAYER(it), LAMBDA( PutClientInServer(it); if(domination_roundbased) it.player_blocked = 1; if(IS_REAL_CLIENT(it)) set_dom_state(it); )); return true; } MUTATOR_HOOKFUNCTION(dom, PlayerSpawn) { entity player = M_ARGV(0, entity); if(domination_roundbased) if(!round_handler_IsRoundStarted()) player.player_blocked = 1; else player.player_blocked = 0; } MUTATOR_HOOKFUNCTION(dom, ClientConnect) { entity player = M_ARGV(0, entity); set_dom_state(player); } MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole) { entity bot = M_ARGV(0, entity); bot.havocbot_role = havocbot_role_dom; return true; } /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32) Control point for Domination gameplay. */ spawnfunc(dom_controlpoint) { if(!g_domination) { remove(this); return; } setthink(this, dom_controlpoint_setup); this.nextthink = time + 0.1; this.reset = dom_controlpoint_setup; if(!this.scale) this.scale = 0.6; this.effects = this.effects | EF_LOWPRECISION; if (autocvar_g_domination_point_fullbright) this.effects |= EF_FULLBRIGHT; } /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32) Team declaration for Domination gameplay, this allows you to decide what team names and control point models are used in your map. Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two can have netname set! The nameless team owns all control points at start. Keys: "netname" Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc) "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue) "model" Model to use for control points owned by this team (for example "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver keycard) "skin" Skin of the model to use (for team skins on a single model) "noise" Sound to play when this team captures a point. (this is a localized sound, like a small alarm or other effect) "noise1" Narrator speech to play when this team captures a point. (this is a global sound, like "Red team has captured a control point") */ spawnfunc(dom_team) { if(!g_domination || autocvar_g_domination_teams_override >= 2) { remove(this); return; } precache_model(this.model); if (this.noise != "") precache_sound(this.noise); if (this.noise1 != "") precache_sound(this.noise1); this.classname = "dom_team"; _setmodel(this, this.model); // precision not needed this.mdl = this.model; this.dmg = this.modelindex; this.model = ""; this.modelindex = 0; // this would have to be changed if used in quakeworld if(this.cnt) this.team = this.cnt + 1; // WHY are these different anyway? } // scoreboard setup void ScoreRules_dom(int teams) { if(domination_roundbased) { ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true); ScoreInfo_SetLabel_TeamScore (ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY); ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0); ScoreRules_basics_end(); } else { float sp_domticks, sp_score; sp_score = sp_domticks = 0; if(autocvar_g_domination_disable_frags) sp_domticks = SFL_SORT_PRIO_PRIMARY; else sp_score = SFL_SORT_PRIO_PRIMARY; ScoreRules_basics(teams, sp_score, sp_score, true); ScoreInfo_SetLabel_TeamScore (ST_DOM_TICKS, "ticks", sp_domticks); ScoreInfo_SetLabel_PlayerScore(SP_DOM_TICKS, "ticks", sp_domticks); ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0); ScoreRules_basics_end(); } } // code from here on is just to support maps that don't have control point and team entities void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, Sound capsound, string capnarration, string capmessage) { TC(Sound, capsound); entity e = new_pure(dom_team); e.netname = strzone(teamname); e.cnt = teamcolor; e.model = pointmodel; e.skin = pointskin; e.noise = strzone(Sound_fixpath(capsound)); e.noise1 = strzone(capnarration); e.message = strzone(capmessage); // this code is identical to spawnfunc_dom_team _setmodel(e, e.model); // precision not needed e.mdl = e.model; e.dmg = e.modelindex; e.model = ""; e.modelindex = 0; // this would have to be changed if used in quakeworld e.team = e.cnt + 1; //eprint(e); } void dom_spawnpoint(vector org) { entity e = spawn(); e.classname = "dom_controlpoint"; setthink(e, spawnfunc_dom_controlpoint); e.nextthink = time; setorigin(e, org); spawnfunc_dom_controlpoint(e); } // spawn some default teams if the map is not set up for domination void dom_spawnteams(int teams) { TC(int, teams); dom_spawnteam(Team_ColoredFullName(NUM_TEAM_1), NUM_TEAM_1-1, "models/domination/dom_red.md3", 0, SND_DOM_CLAIM, "", "Red team has captured a control point"); dom_spawnteam(Team_ColoredFullName(NUM_TEAM_2), NUM_TEAM_2-1, "models/domination/dom_blue.md3", 0, SND_DOM_CLAIM, "", "Blue team has captured a control point"); if(teams >= 3) dom_spawnteam(Team_ColoredFullName(NUM_TEAM_3), NUM_TEAM_3-1, "models/domination/dom_yellow.md3", 0, SND_DOM_CLAIM, "", "Yellow team has captured a control point"); if(teams >= 4) dom_spawnteam(Team_ColoredFullName(NUM_TEAM_4), NUM_TEAM_4-1, "models/domination/dom_pink.md3", 0, SND_DOM_CLAIM, "", "Pink team has captured a control point"); dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, SND_Null, "", ""); } void dom_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up. { // if no teams are found, spawn defaults if(find(NULL, classname, "dom_team") == NULL || autocvar_g_domination_teams_override >= 2) { LOG_TRACE("No \"dom_team\" entities found on this map, creating them anyway.\n"); domination_teams = bound(2, ((autocvar_g_domination_teams_override < 2) ? autocvar_g_domination_default_teams : autocvar_g_domination_teams_override), 4); dom_spawnteams(domination_teams); } CheckAllowedTeams(NULL); //domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2); int teams = 0; if(c1 >= 0) teams |= BIT(0); if(c2 >= 0) teams |= BIT(1); if(c3 >= 0) teams |= BIT(2); if(c4 >= 0) teams |= BIT(3); domination_teams = teams; domination_roundbased = autocvar_g_domination_roundbased; ScoreRules_dom(domination_teams); if(domination_roundbased) { round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart); round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit); } } void dom_Initialize() { g_domination = true; InitializeEntity(NULL, dom_DelayedInit, INITPRIO_GAMETYPE); } #endif