1 // ================================================================
2 // Official capture the flag game mode coding, reworked by Samual
3 // Last updated: September, 2012
4 // ================================================================
6 void ctf_FakeTimeLimit(entity e, float t)
9 WriteByte(MSG_ONE, 3); // svc_updatestat
10 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
12 WriteCoord(MSG_ONE, autocvar_timelimit);
14 WriteCoord(MSG_ONE, (t + 1) / 60);
17 void ctf_EventLog(string mode, float flagteam, entity actor) // use an alias for easy changing and quick editing later
19 if(autocvar_sv_eventlog)
20 GameLogEcho(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != world) ? ftos(actor.playerid) : "")));
21 //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
24 void ctf_CaptureRecord(entity flag, entity player)
26 float cap_record = ctf_captimerecord;
27 float cap_time = (time - flag.ctf_pickuptime);
28 string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
31 if(ctf_oneflag) { Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname); }
32 else if(!ctf_captimerecord) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_TIME_), player.netname, (cap_time * 100)); }
33 else if(cap_time < cap_record) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_BROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
34 else { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_UNBROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
36 // write that shit in the database
37 if(!ctf_oneflag) // but not in 1-flag mode
38 if((!ctf_captimerecord) || (cap_time < cap_record))
40 ctf_captimerecord = cap_time;
41 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
42 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
43 write_recordmarker(player, (time - cap_time), cap_time);
47 void ctf_FlagcarrierWaypoints(entity player)
49 WaypointSprite_Spawn("flagcarrier", 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_FLAGCARRIER(player.team));
50 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON) * 2);
51 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
52 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
55 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
57 float current_distance = vlen((('1 0 0' * to_x) + ('0 1 0' * to_y)) - (('1 0 0' * from_x) + ('0 1 0' * from_y))); // for the sake of this check, exclude Z axis
58 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
59 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
60 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
63 if(current_height) // make sure we can actually do this arcing path
65 targpos = (to + ('0 0 1' * current_height));
66 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
67 if(trace_fraction < 1)
69 //print("normal arc line failed, trying to find new pos...");
70 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
71 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
72 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
73 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
74 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
77 else { targpos = to; }
79 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
81 vector desired_direction = normalize(targpos - from);
82 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
83 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
86 float ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
88 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
90 // directional tracing only
92 makevectors(passer_angle);
94 // find the closest point on the enemy to the center of the attack
95 float ang; // angle between shotdir and h
96 float h; // hypotenuse, which is the distance between attacker to head
97 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
99 h = vlen(head_center - passer_center);
100 ang = acos(dotproduct(normalize(head_center - passer_center), v_forward));
103 vector nearest_on_line = (passer_center + a * v_forward);
104 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
106 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
107 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
109 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
114 else { return TRUE; }
118 // =======================
119 // CaptureShield Functions
120 // =======================
122 float ctf_CaptureShield_CheckStatus(entity p)
124 float s, s2, s3, s4, se, se2, se3, se4, sr, ser;
126 float players_worseeq, players_total;
128 if(ctf_captureshield_max_ratio <= 0)
131 s = PlayerScore_Add(p, SP_CTF_CAPS, 0);
132 s2 = PlayerScore_Add(p, SP_CTF_PICKUPS, 0);
133 s3 = PlayerScore_Add(p, SP_CTF_RETURNS, 0);
134 s4 = PlayerScore_Add(p, SP_CTF_FCKILLS, 0);
136 sr = ((s - s2) + (s3 + s4));
138 if(sr >= -ctf_captureshield_min_negscore)
141 players_total = players_worseeq = 0;
146 se = PlayerScore_Add(e, SP_CTF_CAPS, 0);
147 se2 = PlayerScore_Add(e, SP_CTF_PICKUPS, 0);
148 se3 = PlayerScore_Add(e, SP_CTF_RETURNS, 0);
149 se4 = PlayerScore_Add(e, SP_CTF_FCKILLS, 0);
151 ser = ((se - se2) + (se3 + se4));
158 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
159 // use this rule here
161 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
167 void ctf_CaptureShield_Update(entity player, float wanted_status)
169 float updated_status = ctf_CaptureShield_CheckStatus(player);
170 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
172 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
173 player.ctf_captureshielded = updated_status;
177 float ctf_CaptureShield_Customize()
179 if(self.enemy.active != ACTIVE_ACTIVE) { return TRUE; }
180 if(!other.ctf_captureshielded) { return FALSE; }
181 if(CTF_SAMETEAM(self, other)) { return FALSE; }
186 void ctf_CaptureShield_Touch()
188 if(self.enemy.active != ACTIVE_ACTIVE)
190 vector mymid = (self.absmin + self.absmax) * 0.5;
191 vector othermid = (other.absmin + other.absmax) * 0.5;
193 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
194 if(IS_REAL_CLIENT(other)) { Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_INACTIVE); }
199 if(!other.ctf_captureshielded) { return; }
200 if(CTF_SAMETEAM(self, other)) { return; }
202 vector mymid = (self.absmin + self.absmax) * 0.5;
203 vector othermid = (other.absmin + other.absmax) * 0.5;
205 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
206 if(IS_REAL_CLIENT(other)) { Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
209 void ctf_CaptureShield_Spawn(entity flag)
211 entity shield = spawn();
214 shield.team = self.team;
215 shield.touch = ctf_CaptureShield_Touch;
216 shield.customizeentityforclient = ctf_CaptureShield_Customize;
217 shield.classname = "ctf_captureshield";
218 shield.effects = EF_ADDITIVE;
219 shield.movetype = MOVETYPE_NOCLIP;
220 shield.solid = SOLID_TRIGGER;
221 shield.avelocity = '7 0 11';
224 setorigin(shield, self.origin);
225 setmodel(shield, "models/ctf/shield.md3");
226 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
230 // ====================
231 // Drop/Pass/Throw Code
232 // ====================
234 void ctf_Handle_Drop(entity flag, entity player, float droptype)
237 player = (player ? player : flag.pass_sender);
240 flag.movetype = MOVETYPE_TOSS;
241 flag.takedamage = DAMAGE_YES;
242 flag.angles = '0 0 0';
243 flag.health = flag.max_flag_health;
244 flag.ctf_droptime = time;
245 flag.ctf_dropper = player;
246 flag.ctf_status = FLAG_DROPPED;
248 // messages and sounds
249 Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_LOST_) : INFO_CTF_LOST_NEUTRAL), player.netname);
250 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
251 ctf_EventLog("dropped", player.team, player);
254 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
255 PlayerScore_Add(player, SP_CTF_DROPS, 1);
258 if(autocvar_g_ctf_flag_dropped_waypoint)
259 WaypointSprite_Spawn("flagdropped", 0, 0, flag, FLAG_WAYPOINT_OFFSET, world, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, TRUE, RADARICON_FLAG, WPCOLOR_DROPPEDFLAG(flag.team));
261 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
263 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
264 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
267 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
269 if(droptype == DROP_PASS)
271 flag.pass_distance = 0;
272 flag.pass_sender = world;
273 flag.pass_target = world;
277 void ctf_Handle_Retrieve(entity flag, entity player)
279 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
280 entity sender = flag.pass_sender;
282 // transfer flag to player
284 flag.owner.flagcarried = flag;
289 setattachment(flag, player.vehicle, "");
290 setorigin(flag, VEHICLE_FLAG_OFFSET);
291 flag.scale = VEHICLE_FLAG_SCALE;
295 setattachment(flag, player, "");
296 setorigin(flag, FLAG_CARRY_OFFSET);
298 flag.movetype = MOVETYPE_NONE;
299 flag.takedamage = DAMAGE_NO;
300 flag.solid = SOLID_NOT;
301 flag.angles = '0 0 0';
302 flag.ctf_status = FLAG_CARRY;
304 // messages and sounds
305 sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
306 ctf_EventLog("receive", flag.team, player);
308 FOR_EACH_REALPLAYER(tmp_player)
310 if(tmp_player == sender)
311 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_SENT_) : CENTER_CTF_PASS_SENT_NEUTRAL), player.netname);
312 else if(tmp_player == player)
313 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_RECEIVED_) : CENTER_CTF_PASS_RECEIVED_NEUTRAL), sender.netname);
314 else if(SAME_TEAM(tmp_player, sender))
315 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_OTHER_) : CENTER_CTF_PASS_OTHER_NEUTRAL), sender.netname, player.netname);
318 // create new waypoint
319 ctf_FlagcarrierWaypoints(player);
321 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
322 player.throw_antispam = sender.throw_antispam;
324 flag.pass_distance = 0;
325 flag.pass_sender = world;
326 flag.pass_target = world;
329 void ctf_Handle_Throw(entity player, entity receiver, float droptype)
331 entity flag = player.flagcarried;
332 vector targ_origin, flag_velocity;
334 if(!flag) { return; }
335 if((droptype == DROP_PASS) && !receiver) { return; }
337 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
340 setattachment(flag, world, "");
341 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
342 flag.owner.flagcarried = world;
344 flag.solid = SOLID_TRIGGER;
345 flag.ctf_dropper = player;
346 flag.ctf_droptime = time;
348 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
355 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
356 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
357 WarpZone_RefSys_Copy(flag, receiver);
358 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
359 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
361 flag.pass_distance = vlen((('1 0 0' * targ_origin_x) + ('0 1 0' * targ_origin_y)) - (('1 0 0' * player.origin_x) + ('0 1 0' * player.origin_y))); // for the sake of this check, exclude Z axis
362 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, FALSE);
365 flag.movetype = MOVETYPE_FLY;
366 flag.takedamage = DAMAGE_NO;
367 flag.pass_sender = player;
368 flag.pass_target = receiver;
369 flag.ctf_status = FLAG_PASSING;
372 sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
373 WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), player.origin, targ_origin);
374 ctf_EventLog("pass", flag.team, player);
380 makevectors((player.v_angle_y * '0 1 0') + (bound(autocvar_g_ctf_throw_angle_min, player.v_angle_x, autocvar_g_ctf_throw_angle_max) * '1 0 0'));
382 flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((player.items & IT_STRENGTH) ? autocvar_g_ctf_throw_strengthmultiplier : 1)));
383 flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, FALSE);
384 ctf_Handle_Drop(flag, player, droptype);
390 flag.velocity = '0 0 0'; // do nothing
397 flag.velocity = W_CalculateProjectileVelocity(player.velocity, (('0 0 1' * autocvar_g_ctf_drop_velocity_up) + ((('0 1 0' * crandom()) + ('1 0 0' * crandom())) * autocvar_g_ctf_drop_velocity_side)), FALSE);
398 ctf_Handle_Drop(flag, player, droptype);
403 // kill old waypointsprite
404 WaypointSprite_Ping(player.wps_flagcarrier);
405 WaypointSprite_Kill(player.wps_flagcarrier);
407 if(player.wps_enemyflagcarrier)
408 WaypointSprite_Kill(player.wps_enemyflagcarrier);
411 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
419 void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
421 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
422 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
423 entity player_team_flag = world, tmp_entity;
424 float old_time, new_time;
426 if(!player) { return; } // without someone to give the reward to, we can't possibly cap
429 if(CTF_SAMETEAM(player, flag)) { return; }
431 else if(CTF_DIFFTEAM(player, flag)) { return; }
434 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
435 if(SAME_TEAM(tmp_entity, player))
437 player_team_flag = tmp_entity;
441 player.throw_prevtime = time;
442 player.throw_count = 0;
444 nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
446 // messages and sounds
447 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((enemy_flag.team) ? APP_TEAM_ENT_4(enemy_flag, CENTER_CTF_CAPTURE_) : CENTER_CTF_CAPTURE_NEUTRAL));
448 ctf_CaptureRecord(enemy_flag, player);
449 sound(player, CH_TRIGGER, ((ctf_oneflag) ? player_team_flag.snd_flag_capture : ((DIFF_TEAM(player, flag)) ? enemy_flag.snd_flag_capture : flag.snd_flag_capture)), VOL_BASE, ATTEN_NONE);
453 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
454 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
459 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
460 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
462 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
463 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
464 if(!old_time || new_time < old_time)
465 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
468 pointparticles(particleeffectnum(flag.capeffect), flag.origin, '0 0 0', 1);
469 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
472 if(capturetype == CAPTURE_NORMAL)
474 WaypointSprite_Kill(player.wps_flagcarrier);
475 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
477 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
478 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
482 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
483 ctf_RespawnFlag(enemy_flag);
486 void ctf_Handle_Return(entity flag, entity player)
488 // messages and sounds
489 if(player.flags & FL_MONSTER)
491 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_MONSTER_), player.monster_name);
495 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_RETURN_));
496 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_), player.netname);
498 sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
499 ctf_EventLog("return", flag.team, player);
502 if(IS_PLAYER(player))
504 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
505 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
507 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
510 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
514 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
515 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
516 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
520 if(player.flagcarried == flag)
521 WaypointSprite_Kill(player.wps_flagcarrier);
524 ctf_RespawnFlag(flag);
527 void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
530 float pickup_dropped_score; // used to calculate dropped pickup score
531 entity tmp_entity; // temporary entity
533 // attach the flag to the player
535 player.flagcarried = flag;
538 setattachment(flag, player.vehicle, "");
539 setorigin(flag, VEHICLE_FLAG_OFFSET);
540 flag.scale = VEHICLE_FLAG_SCALE;
544 setattachment(flag, player, "");
545 setorigin(flag, FLAG_CARRY_OFFSET);
549 flag.movetype = MOVETYPE_NONE;
550 flag.takedamage = DAMAGE_NO;
551 flag.solid = SOLID_NOT;
552 flag.angles = '0 0 0';
553 flag.ctf_status = FLAG_CARRY;
557 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
558 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
562 // messages and sounds
563 Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_PICKUP_) : INFO_CTF_PICKUP_NEUTRAL), player.netname);
564 if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
565 if(!flag.team) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL); }
566 else if(CTF_DIFFTEAM(player, flag)) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PICKUP_)); }
567 else { Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_TEAM : CENTER_CTF_PICKUP_TEAM_ENEMY), Team_ColorCode(flag.team)); }
569 Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, ((flag.team) ? APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_) : CHOICE_CTF_PICKUP_TEAM_NEUTRAL), Team_ColorCode(player.team), player.netname);
572 FOR_EACH_PLAYER(tmp_entity)
573 if(tmp_entity != player)
574 if(DIFF_TEAM(player, tmp_entity))
575 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname);
578 FOR_EACH_PLAYER(tmp_entity)
579 if(tmp_entity != player)
580 if(CTF_SAMETEAM(flag, tmp_entity))
581 if(SAME_TEAM(player, tmp_entity))
582 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_), Team_ColorCode(player.team), player.netname);
584 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, ((SAME_TEAM(flag, player)) ? CHOICE_CTF_PICKUP_ENEMY_TEAM : CHOICE_CTF_PICKUP_ENEMY), Team_ColorCode(player.team), player.netname);
586 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
589 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
590 nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
595 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
596 ctf_EventLog("steal", flag.team, player);
602 pickup_dropped_score = (autocvar_g_ctf_flag_return_time ? bound(0, ((flag.ctf_droptime + autocvar_g_ctf_flag_return_time) - time) / autocvar_g_ctf_flag_return_time, 1) : 1);
603 pickup_dropped_score = floor((autocvar_g_ctf_score_pickup_dropped_late * (1 - pickup_dropped_score) + autocvar_g_ctf_score_pickup_dropped_early * pickup_dropped_score) + 0.5);
604 dprint("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
605 PlayerTeamScore_AddScore(player, pickup_dropped_score);
606 ctf_EventLog("pickup", flag.team, player);
614 if(pickuptype == PICKUP_BASE)
616 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
617 if((player.speedrunning) && (ctf_captimerecord))
618 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
622 pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1);
625 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
626 ctf_FlagcarrierWaypoints(player);
627 WaypointSprite_Ping(player.wps_flagcarrier);
631 // ===================
632 // Main Flag Functions
633 // ===================
635 void ctf_CheckFlagReturn(entity flag, float returntype)
637 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
639 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
641 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
645 case RETURN_DROPPED: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_DROPPED_) : INFO_CTF_FLAGRETURN_DROPPED_NEUTRAL)); break;
646 case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_DAMAGED_) : INFO_CTF_FLAGRETURN_DAMAGED_NEUTRAL)); break;
647 case RETURN_SPEEDRUN: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_) : INFO_CTF_FLAGRETURN_SPEEDRUN_NEUTRAL), ctf_captimerecord); break;
648 case RETURN_NEEDKILL: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_NEEDKILL_) : INFO_CTF_FLAGRETURN_NEEDKILL_NEUTRAL)); break;
652 { Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_TIMEOUT_) : INFO_CTF_FLAGRETURN_TIMEOUT_NEUTRAL)); break; }
654 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
655 ctf_EventLog("returned", flag.team, world);
656 ctf_RespawnFlag(flag);
661 float ctf_Stalemate_Customize()
663 // make spectators see what the player would see
665 e = WaypointSprite_getviewentity(other);
666 wp_owner = self.owner;
669 if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return FALSE; }
670 if(SAME_TEAM(wp_owner, e)) { return FALSE; }
671 if(!IS_PLAYER(e)) { return FALSE; }
676 void ctf_CheckStalemate(void)
679 float stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
682 entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
684 // build list of stale flags
685 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
687 if(autocvar_g_ctf_stalemate)
688 if(tmp_entity.ctf_status != FLAG_BASE)
689 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time)
691 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
692 ctf_staleflaglist = tmp_entity;
694 switch(tmp_entity.team)
696 case NUM_TEAM_1: ++stale_red_flags; break;
697 case NUM_TEAM_2: ++stale_blue_flags; break;
698 case NUM_TEAM_3: ++stale_yellow_flags; break;
699 case NUM_TEAM_4: ++stale_pink_flags; break;
700 default: ++stale_neutral_flags; break;
706 stale_flags = (stale_neutral_flags >= 1);
708 stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
710 if(ctf_oneflag && stale_flags == 1)
711 ctf_stalemate = TRUE;
712 else if(stale_flags == ctf_teams)
713 ctf_stalemate = TRUE;
714 else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
715 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
716 else if(stale_flags < ctf_teams && autocvar_g_ctf_stalemate_endcondition == 1)
717 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
719 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
722 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
724 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
726 WaypointSprite_Spawn(((ctf_oneflag) ? "flagcarrier" : "enemyflagcarrier"), 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, world, 0, tmp_entity.owner, wps_enemyflagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_ENEMYFC(tmp_entity.owner.team));
727 tmp_entity.owner.wps_enemyflagcarrier.customizeentityforclient = ctf_Stalemate_Customize;
731 if (!wpforenemy_announced)
733 FOR_EACH_REALPLAYER(tmp_entity)
734 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
736 wpforenemy_announced = TRUE;
741 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
743 if(ITEM_DAMAGE_NEEDKILL(deathtype))
745 if(autocvar_g_ctf_flag_return_damage_delay)
747 self.ctf_flagdamaged = TRUE;
752 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
756 if(autocvar_g_ctf_flag_return_damage)
758 // reduce health and check if it should be returned
759 self.health = self.health - damage;
760 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
770 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
773 if(self == ctf_worldflaglist) // only for the first flag
774 FOR_EACH_CLIENT(tmp_entity)
775 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
778 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
779 dprint("wtf the flag got squashed?\n");
780 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
781 if(!trace_startsolid || self.noalign) // can we resize it without getting stuck?
782 setsize(self, FLAG_MIN, FLAG_MAX); }
784 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
788 self.angles = '0 0 0';
796 switch(self.ctf_status)
800 if(autocvar_g_ctf_dropped_capture_radius)
802 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
803 if(tmp_entity.ctf_status == FLAG_DROPPED)
804 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
805 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
806 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
813 if(autocvar_g_ctf_flag_dropped_floatinwater)
815 vector midpoint = ((self.absmin + self.absmax) * 0.5);
816 if(pointcontents(midpoint) == CONTENT_WATER)
818 self.velocity = self.velocity * 0.5;
820 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
821 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
823 { self.movetype = MOVETYPE_FLY; }
825 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
827 if(autocvar_g_ctf_flag_return_dropped)
829 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
832 ctf_CheckFlagReturn(self, RETURN_DROPPED);
836 if(self.ctf_flagdamaged)
838 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
839 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
842 else if(autocvar_g_ctf_flag_return_time)
844 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
845 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
853 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
856 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
860 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
864 if(autocvar_g_ctf_stalemate)
866 if(time >= wpforenemy_nextthink)
868 ctf_CheckStalemate();
869 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
872 if(CTF_SAMETEAM(self, self.owner) && self.team)
874 if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
875 ctf_Handle_Throw(self.owner, world, DROP_THROW);
876 else if(vlen(self.owner.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_carried_radius)
877 ctf_Handle_Return(self, self.owner);
884 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
885 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
886 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
888 if((self.pass_target == world)
889 || (self.pass_target.deadflag != DEAD_NO)
890 || (self.pass_target.flagcarried)
891 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
892 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
893 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
895 // give up, pass failed
896 ctf_Handle_Drop(self, world, DROP_PASS);
900 // still a viable target, go for it
901 ctf_CalculatePassVelocity(self, targ_origin, self.origin, TRUE);
906 default: // this should never happen
908 dprint("ctf_FlagThink(): Flag exists with no status?\n");
916 if(gameover) { return; }
917 if(self.active != ACTIVE_ACTIVE) { return; }
919 entity toucher = other, tmp_entity;
920 float is_not_monster = (!(toucher.flags & FL_MONSTER)), num_perteam = 0;
922 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
923 if(ITEM_TOUCH_NEEDKILL())
925 if(!autocvar_g_ctf_flag_return_damage_delay)
928 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
933 FOR_EACH_PLAYER(tmp_entity) if(SAME_TEAM(toucher, tmp_entity)) { ++num_perteam; }
935 // special touch behaviors
936 if(toucher.frozen) { return; }
937 else if(toucher.vehicle_flags & VHF_ISVEHICLE)
939 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
940 toucher = toucher.owner; // the player is actually the vehicle owner, not other
942 return; // do nothing
944 else if(toucher.flags & FL_MONSTER)
946 if(!autocvar_g_ctf_allow_monster_touch)
947 return; // do nothing
949 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
951 if(time > self.wait) // if we haven't in a while, play a sound/effect
953 pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
954 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM);
955 self.wait = time + FLAG_TOUCHRATE;
959 else if(toucher.deadflag != DEAD_NO) { return; }
961 switch(self.ctf_status)
967 if(CTF_DIFFTEAM(toucher, self) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
968 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
969 else if(!self.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
970 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the neutral flag
972 else if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && is_not_monster)
973 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
974 else if(CTF_DIFFTEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
975 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
981 if(CTF_SAMETEAM(toucher, self) && (autocvar_g_ctf_flag_return || num_perteam <= 1) && self.team) // automatically return if there's only 1 player on the team
982 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
983 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
984 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
990 dprint("Someone touched a flag even though it was being carried?\n");
996 if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
998 if(DIFF_TEAM(toucher, self.pass_sender))
999 ctf_Handle_Return(self, toucher);
1001 ctf_Handle_Retrieve(self, toucher);
1008 .float last_respawn;
1009 void ctf_RespawnFlag(entity flag)
1011 // check for flag respawn being called twice in a row
1012 if(flag.last_respawn > time - 0.5)
1013 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
1015 flag.last_respawn = time;
1017 // reset the player (if there is one)
1018 if((flag.owner) && (flag.owner.flagcarried == flag))
1020 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
1021 WaypointSprite_Kill(flag.wps_flagcarrier);
1023 flag.owner.flagcarried = world;
1025 if(flag.speedrunning)
1026 ctf_FakeTimeLimit(flag.owner, -1);
1029 if((flag.owner) && (flag.owner.vehicle))
1030 flag.scale = FLAG_SCALE;
1032 if(flag.ctf_status == FLAG_DROPPED)
1033 { WaypointSprite_Kill(flag.wps_flagdropped); }
1036 setattachment(flag, world, "");
1037 setorigin(flag, flag.ctf_spawnorigin);
1039 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
1040 flag.takedamage = DAMAGE_NO;
1041 flag.health = flag.max_flag_health;
1042 flag.solid = SOLID_TRIGGER;
1043 flag.velocity = '0 0 0';
1044 flag.angles = flag.mangle;
1045 flag.flags = FL_ITEM | FL_NOTARGET;
1047 flag.ctf_status = FLAG_BASE;
1049 flag.pass_distance = 0;
1050 flag.pass_sender = world;
1051 flag.pass_target = world;
1052 flag.ctf_dropper = world;
1053 flag.ctf_pickuptime = 0;
1054 flag.ctf_droptime = 0;
1055 flag.ctf_flagdamaged = 0;
1057 ctf_CheckStalemate();
1063 if(IS_PLAYER(self.owner))
1064 ctf_Handle_Throw(self.owner, world, DROP_RESET);
1066 ctf_RespawnFlag(self);
1071 if(self.ctf_status != FLAG_BASE) { return; }
1076 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1079 if(SAME_TEAM(flag, self))
1080 if(flag.active == ACTIVE_ACTIVE)
1084 if(self.active == ACTIVE_ACTIVE)
1087 dprint("ctf_Use: Unable to deactivate flag (not enough active flags on the map)\n");
1091 self.active = ((self.active) ? ACTIVE_NOT : ACTIVE_ACTIVE);
1093 if(self.active == ACTIVE_ACTIVE)
1094 WaypointSprite_Ping(self.wps_flagbase);
1097 float ctf_FlagWaypoint_Customize()
1099 if(self.owner.active != ACTIVE_ACTIVE) { return FALSE; }
1103 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
1106 waypoint_spawnforitem_force(self, self.origin);
1107 self.nearestwaypointtimeout = 0; // activate waypointing again
1108 self.bot_basewaypoint = self.nearestwaypoint;
1111 string basename = "base";
1115 case NUM_TEAM_1: basename = "redbase"; break;
1116 case NUM_TEAM_2: basename = "bluebase"; break;
1117 case NUM_TEAM_3: basename = "yellowbase"; break;
1118 case NUM_TEAM_4: basename = "pinkbase"; break;
1119 default: basename = "neutralbase"; break;
1122 WaypointSprite_SpawnFixed(basename, self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, ((self.team) ? Team_ColorRGB(self.team) : '1 1 1'));
1123 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, ((self.team) ? colormapPaletteColor(self.team - 1, FALSE) : '1 1 1'));
1124 self.wps_flagbase.customizeentityforclient = ctf_FlagWaypoint_Customize;
1126 // captureshield setup
1127 ctf_CaptureShield_Spawn(self);
1130 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
1133 self = flag; // for later usage with droptofloor()
1136 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
1137 ctf_worldflaglist = flag;
1139 setattachment(flag, world, "");
1141 flag.netname = sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber));
1142 flag.team = teamnumber;
1143 flag.classname = "item_flag_team";
1144 flag.target = "###item###"; // wut?
1145 flag.flags = FL_ITEM | FL_NOTARGET;
1146 flag.solid = SOLID_TRIGGER;
1147 flag.takedamage = DAMAGE_NO;
1148 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1149 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
1150 flag.health = flag.max_flag_health;
1151 flag.event_damage = ctf_FlagDamage;
1152 flag.pushable = TRUE;
1153 flag.teleportable = TELEPORT_NORMAL;
1154 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1155 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1156 flag.velocity = '0 0 0';
1157 flag.mangle = flag.angles;
1158 flag.reset = ctf_Reset;
1160 flag.touch = ctf_FlagTouch;
1161 flag.think = ctf_FlagThink;
1162 flag.nextthink = time + FLAG_THINKRATE;
1163 flag.ctf_status = FLAG_BASE;
1166 if(flag.model == "") { flag.model = ((teamnumber == NUM_TEAM_1) ? autocvar_g_ctf_flag_red_model : ((teamnumber == NUM_TEAM_2) ? autocvar_g_ctf_flag_blue_model : ((teamnumber == NUM_TEAM_3) ? autocvar_g_ctf_flag_yellow_model : ((teamnumber == NUM_TEAM_4) ? autocvar_g_ctf_flag_pink_model : autocvar_g_ctf_flag_neutral_model)))); }
1167 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1168 if(!flag.skin) { flag.skin = ((teamnumber == NUM_TEAM_1) ? autocvar_g_ctf_flag_red_skin : ((teamnumber == NUM_TEAM_2) ? autocvar_g_ctf_flag_blue_skin : ((teamnumber == NUM_TEAM_3) ? autocvar_g_ctf_flag_yellow_skin : ((teamnumber == NUM_TEAM_4) ? autocvar_g_ctf_flag_pink_skin : autocvar_g_ctf_flag_neutral_skin)))); }
1169 if(flag.toucheffect == "") { flag.toucheffect = ((teamnumber == NUM_TEAM_1) ? "redflag_touch" : ((teamnumber == NUM_TEAM_2) ? "blueflag_touch" : ((teamnumber == NUM_TEAM_3) ? "yellowflag_touch" : ((teamnumber == NUM_TEAM_4) ? "pinkflag_touch" : "neutralflag_touch")))); }
1170 if(flag.passeffect == "") { flag.passeffect = ((teamnumber == NUM_TEAM_1) ? "red_pass" : ((teamnumber == NUM_TEAM_2) ? "blue_pass" : ((teamnumber == NUM_TEAM_3) ? "yellow_pass" : ((teamnumber == NUM_TEAM_4) ? "pink_pass" : "neutral_pass")))); }
1171 if(flag.capeffect == "") { flag.capeffect = ((teamnumber == NUM_TEAM_1) ? "red_cap" : ((teamnumber == NUM_TEAM_2) ? "blue_cap" : ((teamnumber == NUM_TEAM_3) ? "yellow_cap" : ((teamnumber == NUM_TEAM_4) ? "pink_cap" : "")))); } // neutral flag cant be capped itself
1172 if(!flag.active) { flag.active = ACTIVE_ACTIVE; }
1175 if(flag.snd_flag_taken == "") { flag.snd_flag_taken = ((teamnumber == NUM_TEAM_1) ? "ctf/red_taken.wav" : ((teamnumber == NUM_TEAM_2) ? "ctf/blue_taken.wav" : ((teamnumber == NUM_TEAM_3) ? "ctf/yellow_taken.wav" : ((teamnumber == NUM_TEAM_4) ? "ctf/pink_taken.wav" : "ctf/neutral_taken.wav")))); }
1176 if(flag.snd_flag_returned == "") { flag.snd_flag_returned = ((teamnumber == NUM_TEAM_1) ? "ctf/red_returned.wav" : ((teamnumber == NUM_TEAM_2) ? "ctf/blue_returned.wav" : ((teamnumber == NUM_TEAM_3) ? "ctf/yellow_returned.wav" : ((teamnumber == NUM_TEAM_4) ? "ctf/pink_returned.wav" : "")))); } // neutral flag can't be returned by players
1177 if(flag.snd_flag_capture == "") { flag.snd_flag_capture = ((teamnumber == NUM_TEAM_1) ? "ctf/red_capture.wav" : ((teamnumber == NUM_TEAM_2) ? "ctf/blue_capture.wav" : ((teamnumber == NUM_TEAM_3) ? "ctf/yellow_capture.wav" : ((teamnumber == NUM_TEAM_4) ? "ctf/pink_capture.wav" : "")))); } // again can't be captured
1178 if(flag.snd_flag_respawn == "") { flag.snd_flag_respawn = "ctf/flag_respawn.wav"; } // if there is ever a team-based sound for this, update the code to match.
1179 if(flag.snd_flag_dropped == "") { flag.snd_flag_dropped = ((teamnumber == NUM_TEAM_1) ? "ctf/red_dropped.wav" : ((teamnumber == NUM_TEAM_2) ? "ctf/blue_dropped.wav" : ((teamnumber == NUM_TEAM_3) ? "ctf/yellow_dropped.wav" : ((teamnumber == NUM_TEAM_4) ? "ctf/pink_dropped.wav" : "ctf/neutral_dropped.wav")))); }
1180 if(flag.snd_flag_touch == "") { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
1181 if(flag.snd_flag_pass == "") { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
1184 precache_sound(flag.snd_flag_taken);
1185 precache_sound(flag.snd_flag_returned);
1186 precache_sound(flag.snd_flag_capture);
1187 precache_sound(flag.snd_flag_respawn);
1188 precache_sound(flag.snd_flag_dropped);
1189 precache_sound(flag.snd_flag_touch);
1190 precache_sound(flag.snd_flag_pass);
1191 precache_model(flag.model);
1192 precache_model("models/ctf/shield.md3");
1193 precache_model("models/ctf/shockwavetransring.md3");
1196 setmodel(flag, flag.model); // precision set below
1197 setsize(flag, FLAG_MIN, FLAG_MAX);
1198 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1200 if(autocvar_g_ctf_flag_glowtrails)
1202 flag.glow_color = ((teamnumber == NUM_TEAM_1) ? 251 : ((teamnumber == NUM_TEAM_2) ? 210 : ((teamnumber == NUM_TEAM_3) ? 110 : ((teamnumber == NUM_TEAM_4) ? 145 : 254))));
1203 flag.glow_size = 25;
1204 flag.glow_trail = 1;
1207 flag.effects |= EF_LOWPRECISION;
1208 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1209 if(autocvar_g_ctf_dynamiclights)
1213 case NUM_TEAM_1: flag.effects |= EF_RED; break;
1214 case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1215 case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1216 case NUM_TEAM_4: flag.effects |= EF_RED; break;
1217 default: flag.effects |= EF_DIMLIGHT; break;
1222 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1224 flag.dropped_origin = flag.origin;
1225 flag.noalign = TRUE;
1226 flag.movetype = MOVETYPE_NONE;
1228 else // drop to floor, automatically find a platform and set that as spawn origin
1230 flag.noalign = FALSE;
1233 flag.movetype = MOVETYPE_TOSS;
1236 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1244 // NOTE: LEGACY CODE, needs to be re-written!
1246 void havocbot_calculate_middlepoint()
1250 vector fo = '0 0 0';
1253 f = ctf_worldflaglist;
1258 f = f.ctf_worldflagnext;
1262 havocbot_ctf_middlepoint = s * (1.0 / n);
1263 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1267 entity havocbot_ctf_find_flag(entity bot)
1270 f = ctf_worldflaglist;
1273 if (CTF_SAMETEAM(bot, f))
1275 f = f.ctf_worldflagnext;
1280 entity havocbot_ctf_find_enemy_flag(entity bot)
1283 f = ctf_worldflaglist;
1288 if(CTF_DIFFTEAM(bot, f))
1295 else if(!bot.flagcarried)
1299 else if (CTF_DIFFTEAM(bot, f))
1301 f = f.ctf_worldflagnext;
1306 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1314 FOR_EACH_PLAYER(head)
1316 if(DIFF_TEAM(head, bot) || head.deadflag != DEAD_NO || head == bot)
1319 if(vlen(head.origin - org) < tc_radius)
1326 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1329 head = ctf_worldflaglist;
1332 if (CTF_SAMETEAM(self, head))
1334 head = head.ctf_worldflagnext;
1337 navigation_routerating(head, ratingscale, 10000);
1340 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1343 head = ctf_worldflaglist;
1346 if (CTF_SAMETEAM(self, head))
1348 head = head.ctf_worldflagnext;
1353 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1356 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1359 head = ctf_worldflaglist;
1364 if(CTF_DIFFTEAM(self, head))
1368 if(self.flagcarried)
1371 else if(!self.flagcarried)
1375 else if(CTF_DIFFTEAM(self, head))
1377 head = head.ctf_worldflagnext;
1380 navigation_routerating(head, ratingscale, 10000);
1383 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1385 if (!bot_waypoints_for_items)
1387 havocbot_goalrating_ctf_enemyflag(ratingscale);
1393 head = havocbot_ctf_find_enemy_flag(self);
1398 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1401 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1405 mf = havocbot_ctf_find_flag(self);
1407 if(mf.ctf_status == FLAG_BASE)
1411 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1414 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1417 head = ctf_worldflaglist;
1420 // flag is out in the field
1421 if(head.ctf_status != FLAG_BASE)
1422 if(head.tag_entity==world) // dropped
1426 if(vlen(org-head.origin)<df_radius)
1427 navigation_routerating(head, ratingscale, 10000);
1430 navigation_routerating(head, ratingscale, 10000);
1433 head = head.ctf_worldflagnext;
1437 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1441 head = findchainfloat(bot_pickup, TRUE);
1444 // gather health and armor only
1446 if (head.health || head.armorvalue)
1447 if (vlen(head.origin - org) < sradius)
1449 // get the value of the item
1450 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1452 navigation_routerating(head, t * ratingscale, 500);
1458 void havocbot_ctf_reset_role(entity bot)
1460 float cdefense, cmiddle, coffense;
1461 entity mf, ef, head;
1464 if(bot.deadflag != DEAD_NO)
1467 if(vlen(havocbot_ctf_middlepoint)==0)
1468 havocbot_calculate_middlepoint();
1471 if (bot.flagcarried)
1473 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1477 mf = havocbot_ctf_find_flag(bot);
1478 ef = havocbot_ctf_find_enemy_flag(bot);
1480 // Retrieve stolen flag
1481 if(mf.ctf_status!=FLAG_BASE)
1483 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1487 // If enemy flag is taken go to the middle to intercept pursuers
1488 if(ef.ctf_status!=FLAG_BASE)
1490 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1494 // if there is only me on the team switch to offense
1496 FOR_EACH_PLAYER(head)
1497 if(SAME_TEAM(head, bot))
1502 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1506 // Evaluate best position to take
1507 // Count mates on middle position
1508 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1510 // Count mates on defense position
1511 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1513 // Count mates on offense position
1514 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1516 if(cdefense<=coffense)
1517 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1518 else if(coffense<=cmiddle)
1519 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1521 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1524 void havocbot_role_ctf_carrier()
1526 if(self.deadflag != DEAD_NO)
1528 havocbot_ctf_reset_role(self);
1532 if (self.flagcarried == world)
1534 havocbot_ctf_reset_role(self);
1538 if (self.bot_strategytime < time)
1540 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1542 navigation_goalrating_start();
1544 havocbot_goalrating_ctf_enemybase(50000);
1546 havocbot_goalrating_ctf_ourbase(50000);
1549 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1551 navigation_goalrating_end();
1553 if (self.navigation_hasgoals)
1554 self.havocbot_cantfindflag = time + 10;
1555 else if (time > self.havocbot_cantfindflag)
1557 // Can't navigate to my own base, suicide!
1558 // TODO: drop it and wander around
1559 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1565 void havocbot_role_ctf_escort()
1569 if(self.deadflag != DEAD_NO)
1571 havocbot_ctf_reset_role(self);
1575 if (self.flagcarried)
1577 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1581 // If enemy flag is back on the base switch to previous role
1582 ef = havocbot_ctf_find_enemy_flag(self);
1583 if(ef.ctf_status==FLAG_BASE)
1585 self.havocbot_role = self.havocbot_previous_role;
1586 self.havocbot_role_timeout = 0;
1590 // If the flag carrier reached the base switch to defense
1591 mf = havocbot_ctf_find_flag(self);
1592 if(mf.ctf_status!=FLAG_BASE)
1593 if(vlen(ef.origin - mf.dropped_origin) < 300)
1595 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1599 // Set the role timeout if necessary
1600 if (!self.havocbot_role_timeout)
1602 self.havocbot_role_timeout = time + random() * 30 + 60;
1605 // If nothing happened just switch to previous role
1606 if (time > self.havocbot_role_timeout)
1608 self.havocbot_role = self.havocbot_previous_role;
1609 self.havocbot_role_timeout = 0;
1613 // Chase the flag carrier
1614 if (self.bot_strategytime < time)
1616 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1617 navigation_goalrating_start();
1618 havocbot_goalrating_ctf_enemyflag(30000);
1619 havocbot_goalrating_ctf_ourstolenflag(40000);
1620 havocbot_goalrating_items(10000, self.origin, 10000);
1621 navigation_goalrating_end();
1625 void havocbot_role_ctf_offense()
1630 if(self.deadflag != DEAD_NO)
1632 havocbot_ctf_reset_role(self);
1636 if (self.flagcarried)
1638 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1643 mf = havocbot_ctf_find_flag(self);
1644 ef = havocbot_ctf_find_enemy_flag(self);
1647 if(mf.ctf_status!=FLAG_BASE)
1650 pos = mf.tag_entity.origin;
1654 // Try to get it if closer than the enemy base
1655 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1657 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1662 // Escort flag carrier
1663 if(ef.ctf_status!=FLAG_BASE)
1666 pos = ef.tag_entity.origin;
1670 if(vlen(pos-mf.dropped_origin)>700)
1672 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1677 // About to fail, switch to middlefield
1680 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1684 // Set the role timeout if necessary
1685 if (!self.havocbot_role_timeout)
1686 self.havocbot_role_timeout = time + 120;
1688 if (time > self.havocbot_role_timeout)
1690 havocbot_ctf_reset_role(self);
1694 if (self.bot_strategytime < time)
1696 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1697 navigation_goalrating_start();
1698 havocbot_goalrating_ctf_ourstolenflag(50000);
1699 havocbot_goalrating_ctf_enemybase(20000);
1700 havocbot_goalrating_items(5000, self.origin, 1000);
1701 havocbot_goalrating_items(1000, self.origin, 10000);
1702 navigation_goalrating_end();
1706 // Retriever (temporary role):
1707 void havocbot_role_ctf_retriever()
1711 if(self.deadflag != DEAD_NO)
1713 havocbot_ctf_reset_role(self);
1717 if (self.flagcarried)
1719 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1723 // If flag is back on the base switch to previous role
1724 mf = havocbot_ctf_find_flag(self);
1725 if(mf.ctf_status==FLAG_BASE)
1727 havocbot_ctf_reset_role(self);
1731 if (!self.havocbot_role_timeout)
1732 self.havocbot_role_timeout = time + 20;
1734 if (time > self.havocbot_role_timeout)
1736 havocbot_ctf_reset_role(self);
1740 if (self.bot_strategytime < time)
1745 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1746 navigation_goalrating_start();
1747 havocbot_goalrating_ctf_ourstolenflag(50000);
1748 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1749 havocbot_goalrating_ctf_enemybase(30000);
1750 havocbot_goalrating_items(500, self.origin, rt_radius);
1751 navigation_goalrating_end();
1755 void havocbot_role_ctf_middle()
1759 if(self.deadflag != DEAD_NO)
1761 havocbot_ctf_reset_role(self);
1765 if (self.flagcarried)
1767 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1771 mf = havocbot_ctf_find_flag(self);
1772 if(mf.ctf_status!=FLAG_BASE)
1774 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1778 if (!self.havocbot_role_timeout)
1779 self.havocbot_role_timeout = time + 10;
1781 if (time > self.havocbot_role_timeout)
1783 havocbot_ctf_reset_role(self);
1787 if (self.bot_strategytime < time)
1791 org = havocbot_ctf_middlepoint;
1792 org_z = self.origin_z;
1794 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1795 navigation_goalrating_start();
1796 havocbot_goalrating_ctf_ourstolenflag(50000);
1797 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1798 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1799 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1800 havocbot_goalrating_items(2500, self.origin, 10000);
1801 havocbot_goalrating_ctf_enemybase(2500);
1802 navigation_goalrating_end();
1806 void havocbot_role_ctf_defense()
1810 if(self.deadflag != DEAD_NO)
1812 havocbot_ctf_reset_role(self);
1816 if (self.flagcarried)
1818 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1822 // If own flag was captured
1823 mf = havocbot_ctf_find_flag(self);
1824 if(mf.ctf_status!=FLAG_BASE)
1826 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1830 if (!self.havocbot_role_timeout)
1831 self.havocbot_role_timeout = time + 30;
1833 if (time > self.havocbot_role_timeout)
1835 havocbot_ctf_reset_role(self);
1838 if (self.bot_strategytime < time)
1843 org = mf.dropped_origin;
1844 mp_radius = havocbot_ctf_middlepoint_radius;
1846 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1847 navigation_goalrating_start();
1849 // if enemies are closer to our base, go there
1850 entity head, closestplayer = world;
1851 float distance, bestdistance = 10000;
1852 FOR_EACH_PLAYER(head)
1854 if(head.deadflag!=DEAD_NO)
1857 distance = vlen(org - head.origin);
1858 if(distance<bestdistance)
1860 closestplayer = head;
1861 bestdistance = distance;
1866 if(DIFF_TEAM(closestplayer, self))
1867 if(vlen(org - self.origin)>1000)
1868 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1869 havocbot_goalrating_ctf_ourbase(30000);
1871 havocbot_goalrating_ctf_ourstolenflag(20000);
1872 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1873 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1874 havocbot_goalrating_items(10000, org, mp_radius);
1875 havocbot_goalrating_items(5000, self.origin, 10000);
1876 navigation_goalrating_end();
1880 void havocbot_role_ctf_setrole(entity bot, float role)
1882 dprint(strcat(bot.netname," switched to "));
1885 case HAVOCBOT_CTF_ROLE_CARRIER:
1887 bot.havocbot_role = havocbot_role_ctf_carrier;
1888 bot.havocbot_role_timeout = 0;
1889 bot.havocbot_cantfindflag = time + 10;
1890 bot.bot_strategytime = 0;
1892 case HAVOCBOT_CTF_ROLE_DEFENSE:
1894 bot.havocbot_role = havocbot_role_ctf_defense;
1895 bot.havocbot_role_timeout = 0;
1897 case HAVOCBOT_CTF_ROLE_MIDDLE:
1899 bot.havocbot_role = havocbot_role_ctf_middle;
1900 bot.havocbot_role_timeout = 0;
1902 case HAVOCBOT_CTF_ROLE_OFFENSE:
1904 bot.havocbot_role = havocbot_role_ctf_offense;
1905 bot.havocbot_role_timeout = 0;
1907 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1908 dprint("retriever");
1909 bot.havocbot_previous_role = bot.havocbot_role;
1910 bot.havocbot_role = havocbot_role_ctf_retriever;
1911 bot.havocbot_role_timeout = time + 10;
1912 bot.bot_strategytime = 0;
1914 case HAVOCBOT_CTF_ROLE_ESCORT:
1916 bot.havocbot_previous_role = bot.havocbot_role;
1917 bot.havocbot_role = havocbot_role_ctf_escort;
1918 bot.havocbot_role_timeout = time + 30;
1919 bot.bot_strategytime = 0;
1930 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1934 float t = 0, t2 = 0, t3 = 0;
1936 // initially clear items so they can be set as necessary later.
1937 self.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
1938 | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
1939 | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
1940 | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
1941 | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
1942 | CTF_FLAG_NEUTRAL | CTF_SHIELDED);
1944 // scan through all the flags and notify the client about them
1945 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1947 if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
1948 if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
1949 if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
1950 if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
1951 if(flag.team == 0) { t = CTF_NEUTRAL_FLAG_CARRYING; t2 = CTF_NEUTRAL_FLAG_TAKEN; t3 = CTF_NEUTRAL_FLAG_LOST; self.ctf_flagstatus |= CTF_FLAG_NEUTRAL; }
1953 switch(flag.ctf_status)
1958 if((flag.owner == self) || (flag.pass_sender == self))
1959 self.ctf_flagstatus |= t; // carrying: self is currently carrying the flag
1961 self.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
1966 self.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
1972 // item for stopping players from capturing the flag too often
1973 if(self.ctf_captureshielded)
1974 self.ctf_flagstatus |= CTF_SHIELDED;
1976 // update the health of the flag carrier waypointsprite
1977 if(self.wps_flagcarrier)
1978 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
1983 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1985 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1987 if(frag_target == frag_attacker) // damage done to yourself
1989 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1990 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1992 else // damage done to everyone else
1994 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1995 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1998 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
2000 if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON)))
2001 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
2003 frag_target.wps_helpme_time = time;
2004 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
2006 // todo: add notification for when flag carrier needs help?
2011 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
2013 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
2015 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
2016 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
2019 if(frag_target.flagcarried)
2021 entity tmp_entity = frag_target.flagcarried;
2022 ctf_Handle_Throw(frag_target, world, DROP_NORMAL);
2023 tmp_entity.ctf_dropper = world;
2029 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
2032 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2035 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
2037 entity flag; // temporary entity for the search method
2039 if(self.flagcarried)
2040 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
2042 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2044 if(flag.pass_sender == self) { flag.pass_sender = world; }
2045 if(flag.pass_target == self) { flag.pass_target = world; }
2046 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
2052 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
2054 if(self.flagcarried)
2055 if(!autocvar_g_ctf_portalteleport)
2056 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
2061 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
2063 if(MUTATOR_RETURNVALUE || gameover) { return FALSE; }
2065 entity player = self;
2067 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2069 // pass the flag to a team mate
2070 if(autocvar_g_ctf_pass)
2072 entity head, closest_target = world;
2073 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, TRUE);
2075 while(head) // find the closest acceptable target to pass to
2077 if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
2078 if(head != player && SAME_TEAM(head, player))
2079 if(!head.speedrunning && !head.vehicle)
2081 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2082 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2083 vector passer_center = CENTER_OR_VIEWOFS(player);
2085 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2087 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2089 if(IS_BOT_CLIENT(head))
2091 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2092 ctf_Handle_Throw(head, player, DROP_PASS);
2096 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2097 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2099 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2102 else if(player.flagcarried)
2106 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2107 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
2108 { closest_target = head; }
2110 else { closest_target = head; }
2117 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return TRUE; }
2120 // throw the flag in front of you
2121 if(autocvar_g_ctf_throw && player.flagcarried)
2123 if(player.throw_count == -1)
2125 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2127 player.throw_prevtime = time;
2128 player.throw_count = 1;
2129 ctf_Handle_Throw(player, world, DROP_THROW);
2134 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2140 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2141 else { player.throw_count += 1; }
2142 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2144 player.throw_prevtime = time;
2145 ctf_Handle_Throw(player, world, DROP_THROW);
2154 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
2156 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2158 self.wps_helpme_time = time;
2159 WaypointSprite_HelpMePing(self.wps_flagcarrier);
2161 else // create a normal help me waypointsprite
2163 WaypointSprite_Spawn("helpme", waypointsprite_deployed_lifetime, waypointsprite_limitedrange, self, FLAG_WAYPOINT_OFFSET, world, self.team, self, wps_helpme, FALSE, RADARICON_HELPME, '1 0.5 0');
2164 WaypointSprite_Ping(self.wps_helpme);
2170 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
2172 if(vh_player.flagcarried)
2174 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2176 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
2180 setattachment(vh_player.flagcarried, vh_vehicle, "");
2181 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
2182 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2183 //vh_player.flagcarried.angles = '0 0 0';
2191 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
2193 if(vh_player.flagcarried)
2195 setattachment(vh_player.flagcarried, vh_player, "");
2196 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
2197 vh_player.flagcarried.scale = FLAG_SCALE;
2198 vh_player.flagcarried.angles = '0 0 0';
2205 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
2207 if(self.flagcarried)
2209 Send_Notification(NOTIF_ALL, world, MSG_INFO, ((self.flagcarried.team) ? APP_TEAM_ENT_4(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_) : INFO_CTF_FLAGRETURN_ABORTRUN_NEUTRAL));
2210 ctf_RespawnFlag(self.flagcarried);
2217 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
2219 entity flag; // temporary entity for the search method
2221 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2223 switch(flag.ctf_status)
2228 // lock the flag, game is over
2229 flag.movetype = MOVETYPE_NONE;
2230 flag.takedamage = DAMAGE_NO;
2231 flag.solid = SOLID_NOT;
2232 flag.nextthink = FALSE; // stop thinking
2234 //dprint("stopping the ", flag.netname, " from moving.\n");
2242 // do nothing for these flags
2251 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
2253 havocbot_ctf_reset_role(self);
2257 MUTATOR_HOOKFUNCTION(ctf_GetTeamCount)
2259 //ret_float = ctf_teams;
2260 ret_string = "ctf_team";
2264 MUTATOR_HOOKFUNCTION(ctf_SpectateCopy)
2266 self.ctf_flagstatus = other.ctf_flagstatus;
2275 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
2276 CTF Starting point for a player in team one (Red).
2277 Keys: "angle" viewing angle when spawning. */
2278 void spawnfunc_info_player_team1()
2280 if(g_assault) { remove(self); return; }
2282 self.team = NUM_TEAM_1; // red
2283 spawnfunc_info_player_deathmatch();
2287 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
2288 CTF Starting point for a player in team two (Blue).
2289 Keys: "angle" viewing angle when spawning. */
2290 void spawnfunc_info_player_team2()
2292 if(g_assault) { remove(self); return; }
2294 self.team = NUM_TEAM_2; // blue
2295 spawnfunc_info_player_deathmatch();
2298 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
2299 CTF Starting point for a player in team three (Yellow).
2300 Keys: "angle" viewing angle when spawning. */
2301 void spawnfunc_info_player_team3()
2303 if(g_assault) { remove(self); return; }
2305 self.team = NUM_TEAM_3; // yellow
2306 spawnfunc_info_player_deathmatch();
2310 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
2311 CTF Starting point for a player in team four (Purple).
2312 Keys: "angle" viewing angle when spawning. */
2313 void spawnfunc_info_player_team4()
2315 if(g_assault) { remove(self); return; }
2317 self.team = NUM_TEAM_4; // purple
2318 spawnfunc_info_player_deathmatch();
2321 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2322 CTF flag for team one (Red).
2324 "angle" Angle the flag will point (minus 90 degrees)...
2325 "model" model to use, note this needs red and blue as skins 0 and 1...
2326 "noise" sound played when flag is picked up...
2327 "noise1" sound played when flag is returned by a teammate...
2328 "noise2" sound played when flag is captured...
2329 "noise3" sound played when flag is lost in the field and respawns itself...
2330 "noise4" sound played when flag is dropped by a player...
2331 "noise5" sound played when flag touches the ground... */
2332 void spawnfunc_item_flag_team1()
2334 if(!g_ctf) { remove(self); return; }
2336 ctf_FlagSetup(NUM_TEAM_1, self);
2339 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2340 CTF flag for team two (Blue).
2342 "angle" Angle the flag will point (minus 90 degrees)...
2343 "model" model to use, note this needs red and blue as skins 0 and 1...
2344 "noise" sound played when flag is picked up...
2345 "noise1" sound played when flag is returned by a teammate...
2346 "noise2" sound played when flag is captured...
2347 "noise3" sound played when flag is lost in the field and respawns itself...
2348 "noise4" sound played when flag is dropped by a player...
2349 "noise5" sound played when flag touches the ground... */
2350 void spawnfunc_item_flag_team2()
2352 if(!g_ctf) { remove(self); return; }
2354 ctf_FlagSetup(NUM_TEAM_2, self);
2357 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2358 CTF flag for team three (Yellow).
2360 "angle" Angle the flag will point (minus 90 degrees)...
2361 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2362 "noise" sound played when flag is picked up...
2363 "noise1" sound played when flag is returned by a teammate...
2364 "noise2" sound played when flag is captured...
2365 "noise3" sound played when flag is lost in the field and respawns itself...
2366 "noise4" sound played when flag is dropped by a player...
2367 "noise5" sound played when flag touches the ground... */
2368 void spawnfunc_item_flag_team3()
2370 if(!g_ctf) { remove(self); return; }
2372 ctf_FlagSetup(NUM_TEAM_3, self);
2375 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2376 CTF flag for team four (Pink).
2378 "angle" Angle the flag will point (minus 90 degrees)...
2379 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2380 "noise" sound played when flag is picked up...
2381 "noise1" sound played when flag is returned by a teammate...
2382 "noise2" sound played when flag is captured...
2383 "noise3" sound played when flag is lost in the field and respawns itself...
2384 "noise4" sound played when flag is dropped by a player...
2385 "noise5" sound played when flag touches the ground... */
2386 void spawnfunc_item_flag_team4()
2388 if(!g_ctf) { remove(self); return; }
2390 ctf_FlagSetup(NUM_TEAM_4, self);
2393 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2396 "angle" Angle the flag will point (minus 90 degrees)...
2397 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2398 "noise" sound played when flag is picked up...
2399 "noise1" sound played when flag is returned by a teammate...
2400 "noise2" sound played when flag is captured...
2401 "noise3" sound played when flag is lost in the field and respawns itself...
2402 "noise4" sound played when flag is dropped by a player...
2403 "noise5" sound played when flag touches the ground... */
2404 void spawnfunc_item_flag_neutral()
2406 if(!g_ctf) { remove(self); return; }
2408 ctf_FlagSetup(0, self);
2411 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2412 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2413 Note: If you use spawnfunc_ctf_team entities you must define at least 2! However, unlike domination, you don't need to make a blank one too.
2415 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2416 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2417 void spawnfunc_ctf_team()
2419 if(!g_ctf) { remove(self); return; }
2421 self.classname = "ctf_team";
2422 self.team = self.cnt + 1;
2425 // compatibility for quake maps
2426 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2427 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2428 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2429 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2430 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2431 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2439 void ctf_ScoreRules(float teams)
2441 CheckAllowedTeams(world);
2442 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, TRUE);
2443 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2444 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2445 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2446 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2447 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2448 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2449 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2450 ScoreRules_basics_end();
2453 // code from here on is just to support maps that don't have flag and team entities
2454 void ctf_SpawnTeam (string teamname, float teamcolor)
2459 self.classname = "ctf_team";
2460 self.netname = teamname;
2461 self.cnt = teamcolor;
2463 spawnfunc_ctf_team();
2468 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2473 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2475 if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2476 if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2477 if(tmp_entity.team == 0) { ctf_oneflag = TRUE; }
2480 ctf_teams = bound(2, ctf_teams, 4);
2482 // if no teams are found, spawn defaults
2483 if(find(world, classname, "ctf_team") == world)
2485 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2486 ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
2487 ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
2489 ctf_SpawnTeam("Yellow", NUM_TEAM_3 - 1);
2491 ctf_SpawnTeam("Pink", NUM_TEAM_4 - 1);
2494 ctf_ScoreRules(ctf_teams);
2497 void ctf_Initialize()
2499 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2501 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2502 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2503 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2505 addstat(STAT_CTF_FLAGSTATUS, AS_INT, ctf_flagstatus);
2507 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2511 MUTATOR_DEFINITION(gamemode_ctf)
2513 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2514 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2515 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2516 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2517 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2518 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2519 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2520 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2521 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2522 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2523 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2524 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2525 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2526 MUTATOR_HOOK(HavocBot_ChooseRole, ctf_BotRoles, CBC_ORDER_ANY);
2527 MUTATOR_HOOK(GetTeamCount, ctf_GetTeamCount, CBC_ORDER_ANY);
2528 MUTATOR_HOOK(SpectateCopy, ctf_SpectateCopy, CBC_ORDER_ANY);
2532 if(time > 1) // game loads at time 1
2533 error("This is a game type and it cannot be added at runtime.");
2537 MUTATOR_ONROLLBACK_OR_REMOVE
2539 // we actually cannot roll back ctf_Initialize here
2540 // BUT: we don't need to! If this gets called, adding always
2546 print("This is a game type and it cannot be removed at runtime.");