]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/ctf.qc
Clean up/optimize racer code a bit. Make it use a simpler phys path when idle. Tweak...
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / ctf.qc
1 #define FLAG_MIN (PL_MIN + '0 0 -13')
2 #define FLAG_MAX (PL_MAX + '0 0 -13')
3
4 .entity basewaypoint;
5 .entity sprite;
6 entity ctf_worldflaglist; // CTF flags in the map
7 .entity ctf_worldflagnext;
8 .float dropperid;
9 .float ctf_droptime;
10
11 .float next_take_time;                  // the next time a player can pick up a flag (time + blah)
12                                                                 /// I used this, in part, to fix the looping score bug. - avirox
13 //float FLAGSCORE_PICKUP        =  1;
14 //float FLAGSCORE_RETURN        =  5; // returned by owner team
15 //float FLAGSCORE_RETURNROGUE   = 10; // returned by rogue team
16 //float FLAGSCORE_CAPTURE       =  5;
17
18 #define FLAG_CARRY_POS '-15 0 7'
19
20 .float ctf_captureshielded; // set to 1 if the player is too bad to be allowed to capture
21
22 float captureshield_min_negscore; // punish at -20 points
23 float captureshield_max_ratio; // punish at most 30% of each team
24 float captureshield_force; // push force of the shield
25
26 float ctf_captureshield_shielded(entity p)
27 {
28         float s, se;
29         entity e;
30         float players_worseeq, players_total;
31
32         if(captureshield_max_ratio <= 0)
33                 return FALSE;
34
35         s = PlayerScore_Add(p, SP_SCORE, 0);
36         if(s >= -captureshield_min_negscore)
37                 return FALSE;
38
39         players_total = players_worseeq = 0;
40         FOR_EACH_PLAYER(e)
41         {
42                 if(e.team != p.team)
43                         continue;
44                 se = PlayerScore_Add(e, SP_SCORE, 0);
45                 if(se <= s)
46                         ++players_worseeq;
47                 ++players_total;
48         }
49
50         // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
51         // use this rule here
52
53         if(players_worseeq >= players_total * captureshield_max_ratio)
54                 return FALSE;
55
56         return TRUE;
57 }
58
59 void ctf_captureshield_update(entity p, float dir)
60 {
61         float should;
62         if(dir == p.ctf_captureshielded) // 0: shield only, 1: unshield only
63         {
64                 should = ctf_captureshield_shielded(p);
65                 if(should != dir)
66                 {
67                         if(should)
68                         {
69                                 Send_CSQC_Centerprint_Generic(other, CPID_CTF_CAPTURESHIELD, "^3You are ^4shielded^3 from the flag\n^3for ^1too many unsuccessful attempts^3 to capture.\n\n^3Get some defensive scores before trying again.", 5, 0);
70                                 // TODO csqc notifier for this
71                         }
72                         else
73                         {
74                                 Send_CSQC_Centerprint_Generic(p, CPID_CTF_CAPTURESHIELD, "^3You are now free.\n\n^3Feel free to ^1try to capture^3 the flag again\n^3if you think you will succeed.", 5, 0);
75                                 // TODO csqc notifier for this
76                         }
77                         p.ctf_captureshielded = should;
78                 }
79         }
80 }
81
82 float ctf_captureshield_customize()
83 {
84         if not(other.ctf_captureshielded)
85                 return FALSE;
86         if(self.team == other.team)
87                 return FALSE;
88         return TRUE;
89 }
90
91 .float ctf_captureshield_touch_msgtime;
92 void ctf_captureshield_touch()
93 {
94         if not(other.ctf_captureshielded)
95                 return;
96         if(self.team == other.team)
97                 return;
98         vector mymid;
99         vector othermid;
100         mymid = (self.absmin + self.absmax) * 0.5;
101         othermid = (other.absmin + other.absmax) * 0.5;
102         Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * captureshield_force);
103         if (time - other.ctf_captureshield_touch_msgtime > 2)
104                 Send_CSQC_Centerprint_Generic(other, CPID_CTF_CAPTURESHIELD, "^3You are ^4shielded^3 from the flag\n^3for ^1too many unsuccessful attempts^3 to capture.\n\n^3Get some defensive scores before trying again.", 5, 0);
105         other.ctf_captureshield_touch_msgtime = time;
106 }
107
108 void ctf_flag_spawnstuff()
109 {
110         entity e;
111         e = spawn();
112         e.enemy = self;
113         e.team = self.team;
114         e.touch = ctf_captureshield_touch;
115         e.customizeentityforclient = ctf_captureshield_customize;
116         e.classname = "ctf_captureshield";
117         e.effects = EF_ADDITIVE;
118         e.movetype = MOVETYPE_NOCLIP;
119         e.solid = SOLID_TRIGGER;
120         e.avelocity = '7 0 11';
121         setorigin(e, self.origin);
122         setmodel(e, "models/ctf/shield.md3");
123         e.scale = 0.5;
124         setsize(e, e.scale * e.mins, e.scale * e.maxs);
125
126         waypoint_spawnforitem_force(self, self.origin);
127         self.nearestwaypointtimeout = 0; // activate waypointing again
128         self.basewaypoint = self.nearestwaypoint;
129
130         if(self.team == COLOR_TEAM1)
131                 WaypointSprite_SpawnFixed("redbase", self.origin + '0 0 61', self, sprite, RADARICON_FLAG, colormapPaletteColor(COLOR_TEAM1 - 1, FALSE));
132         else
133                 WaypointSprite_SpawnFixed("bluebase", self.origin + '0 0 61', self, sprite, RADARICON_FLAG, colormapPaletteColor(COLOR_TEAM2 - 1, FALSE));
134 }
135
136 float ctf_score_value(string parameter)
137 {
138         return cvar(strcat("g_ctf_personal", parameter));
139 }
140
141 void FakeTimeLimit(entity e, float t)
142 {
143         msg_entity = e;
144         WriteByte(MSG_ONE, 3); // svc_updatestat
145         WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
146         if(t < 0)
147                 WriteCoord(MSG_ONE, autocvar_timelimit);
148         else
149                 WriteCoord(MSG_ONE, (t + 1) / 60);
150 }
151
152 float   flagcaptimerecord;
153 .float  flagpickuptime;
154 //.float  iscommander;
155 //.float  ctf_state;
156
157 void() FlagThink;
158 void() FlagTouch;
159
160 void place_flag()
161 {
162         if(self.classname != "item_flag_team")
163         {
164                 backtrace("PlaceFlag a non-flag");
165                 return;
166         }
167
168         setattachment(self, world, "");
169         self.mdl = self.model;
170         self.flags = FL_ITEM | FL_NOTARGET;
171         self.solid = SOLID_TRIGGER;
172         self.movetype = MOVETYPE_NONE;
173         self.velocity = '0 0 0';
174         self.origin_z = self.origin_z + 6;
175         self.think = FlagThink;
176         self.touch = FlagTouch;
177         self.nextthink = time + 0.1;
178         self.cnt = FLAG_BASE;
179         self.mangle = self.angles;
180         self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
181         //self.effects = self.effects | EF_DIMLIGHT;
182         if(self.noalign)
183         {
184                 self.dropped_origin = self.origin;
185         }
186         else
187         {
188                 droptofloor();
189                 self.movetype = MOVETYPE_TOSS;
190         }
191
192         InitializeEntity(self, ctf_flag_spawnstuff, INITPRIO_SETLOCATION);
193 }
194
195 void LogCTF(string mode, float flagteam, entity actor)
196 {
197         string s;
198         if(!autocvar_sv_eventlog)
199                 return;
200         s = strcat(":ctf:", mode);
201         s = strcat(s, ":", ftos(flagteam));
202         if(actor != world)
203                 s = strcat(s, ":", ftos(actor.playerid));
204         GameLogEcho(s);
205 }
206
207 void RegenFlag(entity e)
208 {
209         if(e.classname != "item_flag_team")
210         {
211                 backtrace("RegenFlag a non-flag");
212                 return;
213         }
214
215         if(e.waypointsprite_attachedforcarrier)
216                 WaypointSprite_DetachCarrier(e);
217
218         setattachment(e, world, "");
219         e.damageforcescale = 0;
220         e.takedamage = DAMAGE_NO;
221         e.movetype = MOVETYPE_NONE;
222         if(!e.noalign)
223                 e.movetype = MOVETYPE_TOSS;
224         e.velocity = '0 0 0';
225         e.solid = SOLID_TRIGGER;
226         // TODO: play a sound here
227         setorigin(e, e.dropped_origin);
228         e.angles = e.mangle;
229         e.cnt = FLAG_BASE;
230         e.owner = world;
231         e.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND and any other junk
232 }
233
234 void ReturnFlag(entity e)
235 {
236         if(e.classname != "item_flag_team")
237         {
238                 backtrace("ReturnFlag a non-flag");
239                 return;
240         }
241
242         if (e.owner)
243         if (e.owner.flagcarried == e)
244         {
245                 WaypointSprite_DetachCarrier(e.owner);
246                 e.owner.flagcarried = world;
247
248                 if(e.speedrunning)
249                         FakeTimeLimit(e.owner, -1);
250         }
251         e.owner = world;
252         RegenFlag(e);
253 }
254
255 void DropFlag(entity e, entity penalty_receiver, entity attacker)
256 {
257         entity p;
258
259         if(e.classname != "item_flag_team")
260         {
261                 backtrace("DropFlag a non-flag");
262                 return;
263         }
264
265         if(e.speedrunning)
266         {
267                 ReturnFlag(e);
268                 return;
269         }
270
271         if (!e.owner)
272         {
273                 dprint("FLAG: drop - no owner?!?!\n");
274                 return;
275         }
276         p = e.owner;
277         if (p.flagcarried != e)
278         {
279                 dprint("FLAG: drop - owner is not carrying this flag??\n");
280                 return;
281         }
282         //bprint(p.netname, "^7 lost the ", e.netname, "\n");
283         Send_KillNotification (p.netname, e.netname, "", INFO_LOSTFLAG, MSG_INFO);
284
285         if(penalty_receiver)
286                 UpdateFrags(penalty_receiver, -ctf_score_value("penalty_suicidedrop"));
287         else
288                 UpdateFrags(p, -ctf_score_value("penalty_drop"));
289         PlayerScore_Add(p, SP_CTF_DROPS, +1);
290         ctf_captureshield_update(p, 0); // shield only
291         e.playerid = attacker.playerid;
292         e.ctf_droptime = time;
293         WaypointSprite_Spawn("flagdropped", 0, 0, e, '0 0 1' * 61, world, COLOR_TEAM1 + COLOR_TEAM2 - e.team, e, waypointsprite_attachedforcarrier, FALSE, RADARICON_FLAG, '0 1 1');
294         WaypointSprite_Ping(e.waypointsprite_attachedforcarrier);
295         
296         if(p.waypointsprite_attachedforcarrier)
297         {
298                 WaypointSprite_DetachCarrier(p);
299         }
300         else
301         {
302                 bprint("\{1}^1Flag carrier had no flag sprite?!?\n");
303                 backtrace("Flag carrier had no flag sprite?!?");
304         }
305         LogCTF("dropped", p.team, p);
306         sound (p, CH_TRIGGER, self.noise4, VOL_BASE, ATTN_NONE);
307
308         setattachment(e, world, "");
309         e.damageforcescale = autocvar_g_balance_ctf_damageforcescale;
310         e.takedamage = DAMAGE_YES;
311
312         if (p.flagcarried == e)
313                 p.flagcarried = world;
314         e.owner = world;
315
316         e.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND and any other junk
317         e.solid = SOLID_TRIGGER;
318         e.movetype = MOVETYPE_TOSS;
319         // setsize(e, '-16 -16 0', '16 16 74');
320         setorigin(e, p.origin - '0 0 24' + '0 0 37');
321         e.cnt = FLAG_DROPPED;
322         e.velocity = '0 0 300';
323         e.pain_finished = time + autocvar_g_ctf_flag_returntime;//30;
324
325         trace_startsolid = FALSE;
326         tracebox(e.origin, e.mins, e.maxs, e.origin, TRUE, e);
327         if(trace_startsolid)
328                 dprint("FLAG FALLTHROUGH will happen SOON\n");
329 }
330
331 void FlagThink()
332 {
333         entity e;
334
335         self.nextthink = time + 0.1;
336
337         // sorry, we have to reset the flag size if it got squished by something
338         if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX)
339         {
340                 // if we can grow back, grow back
341                 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
342                 if(!trace_startsolid)
343                         setsize(self, FLAG_MIN, FLAG_MAX);
344         }
345
346         if(self == ctf_worldflaglist) // only for the first flag
347         {
348                 FOR_EACH_CLIENT(e)
349                         ctf_captureshield_update(e, 1); // release shield only
350         }
351
352         if(self.speedrunning)
353         if(self.cnt == FLAG_CARRY)
354         {
355                 if(self.owner)
356                 if(flagcaptimerecord)
357                 if(time >= self.flagpickuptime + flagcaptimerecord)
358                 {
359                         bprint("The ", self.netname, " became impatient after ", ftos_decimals(flagcaptimerecord, 2), " seconds and returned itself\n");
360
361                         sound (self, CH_TRIGGER, self.noise3, VOL_BASE, ATTN_NONE);
362                         self.owner.impulse = 141; // returning!
363
364                         e = self;
365                         self = self.owner;
366                         ReturnFlag(e);
367                         ImpulseCommands();
368                         self = e;
369                         return;
370                 }
371         }
372
373         if (self.cnt == FLAG_BASE)
374                 return;
375
376         if (self.cnt == FLAG_DROPPED)
377         {
378                 // flag fallthrough? FIXME remove this if bug is really fixed now
379                 if(self.origin_z < -131072)
380                 {
381                         dprint("FLAG FALLTHROUGH just happened\n");
382                         self.pain_finished = 0;
383                 }
384                 setattachment(self, world, "");
385                 if (time > self.pain_finished)
386                 {
387                         bprint("The ", self.netname, " has returned to base\n");
388                         sound (self, CH_TRIGGER, self.noise3, VOL_BASE, ATTN_NONE);
389                         LogCTF("returned", self.team, world);
390                         ReturnFlag(self);
391                 }
392                 return;
393         }
394
395         e = self.owner;
396         if (e.classname != "player" || (e.deadflag) || (e.flagcarried != self))
397         {
398                 dprint("CANNOT HAPPEN - player dead and STILL had a flag!\n");
399                 DropFlag(self, world, world);
400                 return;
401         }
402 }
403
404 float ctf_usekey()
405 {
406         if(self.flagcarried)
407         {
408                 DropFlag(self.flagcarried, self, world);
409                 return TRUE;
410         }
411         return FALSE;
412 }
413
414 void flag_cap_ring_spawn(vector org)
415 {
416         shockwave_spawn("models/ctf/shockwavetransring.md3", org - '0 0 15', -0.8, 0, 1);
417 }
418
419 // TODO add FlagDamage, replace weird hurttrigger handling inside trigger_hurt code by it
420 void FlagTouch()
421 {
422         if(gameover) return;
423
424         float t;
425         entity player;
426         string s, s0, h0, h1;
427
428         if (self.cnt == FLAG_CARRY)
429                 return;
430
431         if (self.cnt == FLAG_DROPPED)
432         {
433                 if(ITEM_TOUCH_NEEDKILL())
434                 {
435                         self.pain_finished = 0; // return immediately
436                         return;
437                 }
438         }
439
440         if (other.classname != "player")
441                 return;
442         if (other.health < 1) // ignore dead players
443                 return;
444
445         if (self.cnt == FLAG_BASE)
446         if (other.team == self.team)
447         if (other.flagcarried) // he's got a flag
448         if (other.flagcarried.team != self.team) // capture
449         {
450                 if (other.flagcarried == world)
451                 {
452                         return;
453                 }
454                 if(autocvar_g_ctf_captimerecord_always || player_count - currentbots <= 1) // at most one human
455                 {
456                         t = time - other.flagcarried.flagpickuptime;
457                         s = ftos_decimals(t, 2);
458                         s0 = ftos_decimals(flagcaptimerecord, 2);
459                         h0 = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
460                         h1 = other.netname;
461                         if(h0 == h1)
462                                 h0 = "their";
463                         else
464                                 h0 = strcat(h0, "^7's"); // h0: display text for previous netname
465                         if (flagcaptimerecord == 0)
466                         {
467                                 s = strcat(" in ", s, " seconds");
468                                 flagcaptimerecord = t;
469                                 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(t));
470                                 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), h1);
471                                 write_recordmarker(other, time - t, t);
472                         }
473                         else if (t < flagcaptimerecord)
474                         {
475                                 s = strcat(" in ", s, " seconds, breaking ", h0, " previous record of ", s0, " seconds");
476                                 flagcaptimerecord = t;
477                                 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(t));
478                                 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), h1);
479                                 write_recordmarker(other, time - t, t);
480                         }
481                         else
482                         {
483                                 s = strcat(" in ", s, " seconds, failing to break ", h0, " record of ", s0, " seconds");
484                         }
485                 }
486                 else
487                         s = "";
488
489                 Send_KillNotification (other.netname, other.flagcarried.netname, s, INFO_CAPTUREFLAG, MSG_INFO);
490
491                 PlayerTeamScore_Add(other, SP_CTF_CAPS, ST_CTF_CAPS, 1);
492                 LogCTF("capture", other.flagcarried.team, other);
493                 // give credit to the individual player
494                 UpdateFrags(other, ctf_score_value("score_capture"));
495
496                 if (autocvar_g_ctf_flag_capture_effects) {
497                         if (other.team == COLOR_TEAM1) { // red team scores effect
498                                 pointparticles(particleeffectnum("red_ground_quake"), self.origin, '0 0 0', 1);
499                                 flag_cap_ring_spawn(self.origin);
500                         }
501                         if (other.team == COLOR_TEAM2) { // blue team scores effect
502                                 pointparticles(particleeffectnum("blue_ground_quake"), self.origin, '0 0 0', 1);
503                                 flag_cap_ring_spawn(self.origin);
504                         }
505                 }
506
507                 sound (other, CH_TRIGGER, self.noise2, VOL_BASE, ATTN_NONE);
508                 WaypointSprite_DetachCarrier(other);
509                 if(self.speedrunning)
510                         FakeTimeLimit(other, -1);
511                 RegenFlag (other.flagcarried);
512                 other.flagcarried = world;
513                 other.next_take_time = time + 1;
514         }
515         if (self.cnt == FLAG_BASE)
516         if (other.team == COLOR_TEAM1 || other.team == COLOR_TEAM2) // only red and blue team can steal flags
517         if (other.team != self.team)
518         if (!other.flagcarried)
519         if (!other.ctf_captureshielded)
520         {
521                 if (other.next_take_time > time)
522                         return;
523
524                 if (autocvar_g_ctf_flag_pickup_effects) // pickup effect
525                         pointparticles(particleeffectnum("smoke_ring"), 0.5 * (self.absmin + self.absmax), '0 0 0', 1);
526
527                 // pick up
528                 self.flagpickuptime = time; // used for timing runs
529                 self.speedrunning = other.speedrunning; // if speedrunning, flag will self-return and teleport the owner back after the record
530                 if(other.speedrunning)
531                 if(flagcaptimerecord)
532                         FakeTimeLimit(other, time + flagcaptimerecord);
533                 self.solid = SOLID_NOT;
534                 setorigin(self, self.origin); // relink
535                 self.owner = other;
536                 other.flagcarried = self;
537                 self.cnt = FLAG_CARRY;
538                 self.angles = '0 0 0';
539                 //bprint(other.netname, "^7 got the ", self.netname, "\n");
540                 Send_KillNotification (other.netname, self.netname, "", INFO_GOTFLAG, MSG_INFO);
541                 UpdateFrags(other, ctf_score_value("score_pickup_base"));
542                 self.dropperid = other.playerid;
543                 PlayerScore_Add(other, SP_CTF_PICKUPS, 1);
544                 LogCTF("steal", self.team, other);
545                 sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NONE);
546
547                 FOR_EACH_PLAYER(player)
548                         if(player.team == self.team)
549                                 centerprint(player, "The enemy got your flag! Retrieve it!");
550
551                 self.movetype = MOVETYPE_NONE;
552                 setorigin(self, FLAG_CARRY_POS);
553                 setattachment(self, other, "");
554                 WaypointSprite_AttachCarrier("flagcarrier", other, RADARICON_FLAGCARRIER, '1 1 0');
555                 WaypointSprite_Ping(self.sprite);
556
557                 return;
558         }
559
560         if (self.cnt == FLAG_DROPPED)
561         {
562                 self.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND and any other junk
563                 if (other.team == self.team || (other.team != COLOR_TEAM1 && other.team != COLOR_TEAM2))
564                 {
565                         // return flag
566                         Send_KillNotification (other.netname, self.netname, "", INFO_RETURNFLAG, MSG_INFO);
567                         //bprint(other.netname, "^7 returned the ", self.netname, "\n");
568
569                         // punish the player who last had it
570                         FOR_EACH_PLAYER(player)
571                                 if(player.playerid == self.dropperid)
572                                 {
573                                         PlayerScore_Add(player, SP_SCORE, -ctf_score_value("penalty_returned"));
574                                         ctf_captureshield_update(player, 0); // shield only
575                                 }
576
577                         // punish the team who was last carrying it
578                         if(self.team == COLOR_TEAM1)
579                                 TeamScore_AddToTeam(COLOR_TEAM2, ST_SCORE, -ctf_score_value("penalty_returned"));
580                         else
581                                 TeamScore_AddToTeam(COLOR_TEAM1, ST_SCORE, -ctf_score_value("penalty_returned"));
582
583                         // reward the player who returned it
584                         if(other.playerid == self.playerid) // is this the guy who killed the FC last?
585                         {
586                                 if (other.team == COLOR_TEAM1 || other.team == COLOR_TEAM2)
587                                         UpdateFrags(other, ctf_score_value("score_return_by_killer"));
588                                 else
589                                         UpdateFrags(other, ctf_score_value("score_return_rogue_by_killer"));
590                         }
591                         else
592                         {
593                                 if (other.team == COLOR_TEAM1 || other.team == COLOR_TEAM2)
594                                         UpdateFrags(other, ctf_score_value("score_return"));
595                                 else
596                                         UpdateFrags(other, ctf_score_value("score_return_rogue"));
597                         }
598                         PlayerScore_Add(other, SP_CTF_RETURNS, 1);
599                         LogCTF("return", self.team, other);
600                         sound (other, CH_TRIGGER, self.noise1, VOL_BASE, ATTN_NONE);
601                         ReturnFlag(self);
602                 }
603                 else if (!other.flagcarried && (other.playerid != self.dropperid || time > self.ctf_droptime + autocvar_g_balance_ctf_delay_collect))
604                 {
605                         if(self.waypointsprite_attachedforcarrier)
606                                 WaypointSprite_DetachCarrier(self);
607
608                         if (autocvar_g_ctf_flag_pickup_effects) // field pickup effect
609                                 pointparticles(particleeffectnum("smoke_ring"), 0.5 * (self.absmin + self.absmax), '0 0 0', 1);
610
611                         // pick up
612                         self.solid = SOLID_NOT;
613                         setorigin(self, self.origin); // relink
614                         self.owner = other;
615                         other.flagcarried = self;
616                         self.cnt = FLAG_CARRY;
617                         Send_KillNotification (other.netname, self.netname, "", INFO_PICKUPFLAG, MSG_INFO);
618                         //bprint(other.netname, "^7 picked up the ", self.netname, "\n");
619
620                         float f;
621                         f = bound(0, (self.pain_finished - time) / autocvar_g_ctf_flag_returntime, 1);
622                         //print("factor is ", ftos(f), "\n");
623                         f = ctf_score_value("score_pickup_dropped_late") * (1-f)
624                           + ctf_score_value("score_pickup_dropped_early") * f;
625                         f = floor(f + 0.5);
626                         self.dropperid = other.playerid;
627                         //print("score is ", ftos(f), "\n");
628
629                         UpdateFrags(other, f);
630                         PlayerScore_Add(other, SP_CTF_PICKUPS, 1);
631                         LogCTF("pickup", self.team, other);
632                         sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NONE);
633
634                         FOR_EACH_PLAYER(player)
635                                 if(player.team == self.team)
636                                         centerprint(player, "The enemy got your flag! Retrieve it!");
637
638                         self.movetype = MOVETYPE_NONE;  // flag must have MOVETYPE_NONE here, otherwise it will drop through the floor...
639                         setorigin(self, FLAG_CARRY_POS);
640                         setattachment(self, other, "");
641                         self.damageforcescale = 0;
642                         self.takedamage = DAMAGE_NO;
643                         WaypointSprite_AttachCarrier("flagcarrier", other, RADARICON_FLAGCARRIER, '1 1 0');
644                 }
645         }
646 }
647
648 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
649 CTF Starting point for a player
650 in team one (Red).
651
652 Keys:
653 "angle"
654  viewing angle when spawning
655 */
656 void spawnfunc_info_player_team1()
657 {
658         if(g_assault)
659         {
660                 remove(self);
661                 return;
662         }
663         self.team = COLOR_TEAM1; // red
664         spawnfunc_info_player_deathmatch();
665 }
666 //self.team = 4;self.classname = "info_player_start";spawnfunc_info_player_start();}
667
668 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
669 CTF Starting point for a player in
670 team two (Blue).
671
672 Keys:
673 "angle"
674  viewing angle when spawning
675 */
676 void spawnfunc_info_player_team2()
677 {
678         if(g_assault)
679         {
680                 remove(self);
681                 return;
682         }
683         self.team = COLOR_TEAM2; // blue
684         spawnfunc_info_player_deathmatch();
685 }
686 //self.team = 13;self.classname = "info_player_start";spawnfunc_info_player_start();}
687
688 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
689 CTF Starting point for a player in
690 team three (Yellow).
691
692 Keys:
693 "angle"
694  viewing angle when spawning
695 */
696 void spawnfunc_info_player_team3()
697 {
698         if(g_assault)
699         {
700                 remove(self);
701                 return;
702         }
703         self.team = COLOR_TEAM3; // yellow
704         spawnfunc_info_player_deathmatch();
705 }
706
707
708 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
709 CTF Starting point for a player in
710 team four (Magenta).
711
712 Keys:
713 "angle"
714  viewing angle when spawning
715 */
716 void spawnfunc_info_player_team4()
717 {
718         if(g_assault)
719         {
720                 remove(self);
721                 return;
722         }
723         self.team = COLOR_TEAM4; // purple
724         spawnfunc_info_player_deathmatch();
725 }
726
727 void item_flag_reset()
728 {
729         DropFlag(self, world, world);
730         if(self.waypointsprite_attachedforcarrier)
731                 WaypointSprite_DetachCarrier(self);
732         ReturnFlag(self);
733 }
734
735 void item_flag_postspawn()
736 { // Check CTF Item Flag Post Spawn
737
738         // Flag Glow Trail Support
739         if(autocvar_g_ctf_flag_glowtrails)
740         { // Provide Flag Glow Trail
741                 if(self.team == COLOR_TEAM1)
742                         // Red
743                         self.glow_color = 251;
744                 else
745                 if(self.team == COLOR_TEAM2)
746                         // Blue
747                         self.glow_color = 210;
748
749                 self.glow_size = 25;
750                 self.glow_trail = 1;
751         }
752 }
753
754 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
755 CTF flag for team one (Red).
756 Multiple are allowed.
757
758 Keys:
759 "angle"
760  Angle the flag will point
761 (minus 90 degrees)
762 "model"
763  model to use, note this needs red and blue as skins 0 and 1
764  (default models/ctf/flag.md3)
765 "noise"
766  sound played when flag is picked up
767  (default ctf/take.wav)
768 "noise1"
769  sound played when flag is returned by a teammate
770  (default ctf/return.wav)
771 "noise2"
772  sound played when flag is captured
773  (default ctf/redcapture.wav)
774 "noise3"
775  sound played when flag is lost in the field and respawns itself
776  (default ctf/respawn.wav)
777 */
778
779 void spawnfunc_item_flag_team2();
780 void spawnfunc_item_flag_team1()
781 {
782         if (!g_ctf)
783         {
784                 remove(self);
785                 return;
786         }
787
788         if (g_ctf_reverse)
789         {
790                 float old_g_ctf_reverse = g_ctf_reverse;
791                 g_ctf_reverse = 0; // avoid an endless loop
792                 spawnfunc_item_flag_team2();
793                 g_ctf_reverse = old_g_ctf_reverse;
794                 return;
795         }
796
797         // link flag into ctf_worldflaglist
798         self.ctf_worldflagnext = ctf_worldflaglist;
799         ctf_worldflaglist = self;
800
801         self.classname = "item_flag_team";
802         self.team = COLOR_TEAM1; // color 4 team (red)
803         self.items = IT_KEY2; // gold key (redish enough)
804         self.netname = "^1RED^7 flag";
805         self.target = "###item###";
806         self.skin = autocvar_g_ctf_flag_red_skin;
807         if(self.spawnflags & 1)
808                 self.noalign = 1;
809         if (!self.model)
810                 self.model = autocvar_g_ctf_flag_red_model;
811         if (!self.noise)
812                 self.noise = "ctf/red_taken.wav";
813         if (!self.noise1)
814                 self.noise1 = "ctf/red_returned.wav";
815         if (!self.noise2)
816                 self.noise2 = "ctf/red_capture.wav"; // blue team scores by capturing the red flag
817         if (!self.noise3)
818                 self.noise3 = "ctf/flag_respawn.wav";
819         if (!self.noise4)
820                 self.noise4 = "ctf/red_dropped.wav";
821         precache_model (self.model);
822         setmodel (self, self.model); // precision set below
823         precache_sound (self.noise);
824         precache_sound (self.noise1);
825         precache_sound (self.noise2);
826         precache_sound (self.noise3);
827         precache_sound (self.noise4);
828         //setsize(self, '-16 -16 -37', '16 16 37');
829         setsize(self, FLAG_MIN, FLAG_MAX);
830         setorigin(self, self.origin + '0 0 37');
831         self.nextthink = time + 0.2; // start after doors etc
832         self.think = place_flag;
833
834         if(!self.scale)
835                 self.scale = 0.6;
836         //if(!self.glow_size)
837         //      self.glow_size = 50;
838
839         self.effects = self.effects | EF_LOWPRECISION;
840         if(autocvar_g_ctf_fullbrightflags)
841                 self.effects |= EF_FULLBRIGHT;
842         if(autocvar_g_ctf_dynamiclights)
843                 self.effects |= EF_RED;
844
845         // From Spidflisk
846         item_flag_postspawn();
847
848         precache_model("models/ctf/shield.md3");
849         precache_model("models/ctf/shockwavetransring.md3");
850
851         self.reset = item_flag_reset;
852 }
853
854 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -24) (48 48 64)
855 CTF flag for team two (Blue).
856 Multiple are allowed.
857
858 Keys:
859 "angle"
860  Angle the flag will point
861 (minus 90 degrees)
862 "model"
863  model to use, note this needs red and blue as skins 0 and 1
864  (default models/ctf/flag.md3)
865 "noise"
866  sound played when flag is picked up
867  (default ctf/take.wav)
868 "noise1"
869  sound played when flag is returned by a teammate
870  (default ctf/return.wav)
871 "noise2"
872  sound played when flag is captured
873  (default ctf/bluecapture.wav)
874 "noise3"
875  sound played when flag is lost in the field and respawns itself
876  (default ctf/respawn.wav)
877 */
878
879 void spawnfunc_item_flag_team2()
880 {
881         if (!g_ctf)
882         {
883                 remove(self);
884                 return;
885         }
886
887         if (g_ctf_reverse)
888         {
889                 float old_g_ctf_reverse = g_ctf_reverse;
890                 g_ctf_reverse = 0; // avoid an endless loop
891                 spawnfunc_item_flag_team1();
892                 g_ctf_reverse = old_g_ctf_reverse;
893                 return;
894         }
895
896         // link flag into ctf_worldflaglist
897         self.ctf_worldflagnext = ctf_worldflaglist;
898         ctf_worldflaglist = self;
899
900         self.classname = "item_flag_team";
901         self.team = COLOR_TEAM2; // color 13 team (blue)
902         self.items = IT_KEY1; // silver key (bluish enough)
903         self.netname = "^4BLUE^7 flag";
904         self.target = "###item###";
905         self.skin = autocvar_g_ctf_flag_blue_skin;
906         if(self.spawnflags & 1)
907                 self.noalign = 1;
908         if (!self.model)
909                 self.model = autocvar_g_ctf_flag_blue_model;
910         if (!self.noise)
911                 self.noise = "ctf/blue_taken.wav";
912         if (!self.noise1)
913                 self.noise1 = "ctf/blue_returned.wav";
914         if (!self.noise2)
915                 self.noise2 = "ctf/blue_capture.wav"; // blue team scores by capturing the red flag
916         if (!self.noise3)
917                 self.noise3 = "ctf/flag_respawn.wav";
918         if (!self.noise4)
919                 self.noise4 = "ctf/blue_dropped.wav";
920         precache_model (self.model);
921         setmodel (self, self.model); // precision set below
922         precache_sound (self.noise);
923         precache_sound (self.noise1);
924         precache_sound (self.noise2);
925         precache_sound (self.noise3);
926         precache_sound (self.noise4);
927         //setsize(self, '-16 -16 -37', '16 16 37');
928         setsize(self, FLAG_MIN, FLAG_MAX);
929         setorigin(self, self.origin + '0 0 37');
930         self.nextthink = time + 0.2; // start after doors etc
931         self.think = place_flag;
932
933         if(!self.scale)
934                 self.scale = 0.6;
935         //if(!self.glow_size)
936         //      self.glow_size = 50;
937
938         self.effects = self.effects | EF_LOWPRECISION;
939         if(autocvar_g_ctf_fullbrightflags)
940                 self.effects |= EF_FULLBRIGHT;
941         if(autocvar_g_ctf_dynamiclights)
942                 self.effects |= EF_BLUE;
943
944         // From Spidflisk
945         item_flag_postspawn();
946
947         precache_model("models/ctf/shield.md3");
948         precache_model("models/ctf/shockwavetransring.md3");
949
950         self.reset = item_flag_reset;
951 }
952
953
954 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
955 Team declaration for CTF gameplay, this allows you to decide what team
956 names and control point models are used in your map.
957
958 Note: If you use spawnfunc_ctf_team entities you must define at least 2!  However, unlike
959 domination, you don't need to make a blank one too.
960
961 Keys:
962 "netname"
963  Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)
964 "cnt"
965  Scoreboard color of the team (for example 4 is red and 13 is blue)
966
967 */
968
969 void spawnfunc_ctf_team()
970 {
971         if (!g_ctf)
972         {
973                 remove(self);
974                 return;
975         }
976         self.classname = "ctf_team";
977         self.team = self.cnt + 1;
978 }
979
980 // code from here on is just to support maps that don't have control point and team entities
981 void ctf_spawnteam (string teamname, float teamcolor)
982 {
983         entity oldself;
984         oldself = self;
985         self = spawn();
986         self.classname = "ctf_team";
987         self.netname = teamname;
988         self.cnt = teamcolor;
989
990         spawnfunc_ctf_team();
991
992         self = oldself;
993 }
994
995 // spawn some default teams if the map is not set up for ctf
996 void ctf_spawnteams()
997 {
998         float numteams;
999
1000         numteams = 2;//cvar("g_ctf_default_teams");
1001
1002         ctf_spawnteam("Red", COLOR_TEAM1 - 1);
1003         ctf_spawnteam("Blue", COLOR_TEAM2 - 1);
1004 }
1005
1006 void ctf_delayedinit()
1007 {
1008         // if no teams are found, spawn defaults
1009         if (find(world, classname, "ctf_team") == world)
1010                 ctf_spawnteams();
1011
1012         ScoreRules_ctf();
1013 }
1014
1015 void ctf_init()
1016 {
1017         InitializeEntity(world, ctf_delayedinit, INITPRIO_GAMETYPE);
1018         flagcaptimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
1019
1020         captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
1021         captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
1022         captureshield_force = autocvar_g_ctf_shield_force;
1023 }
1024
1025 void ctf_setstatus2(entity flag, float shift)
1026 {
1027         if (flag.cnt == FLAG_CARRY)
1028                 if (flag.owner == self)
1029                         self.items |= shift * 3;
1030                 else
1031                         self.items |= shift * 1;
1032         else if (flag.cnt == FLAG_DROPPED)
1033                 self.items |= shift * 2;
1034         else
1035         {
1036                 // no status bits
1037         }
1038 }
1039
1040 void ctf_setstatus()
1041 {
1042         self.items &~= IT_RED_FLAG_TAKEN;
1043         self.items &~= IT_RED_FLAG_LOST;
1044         self.items &~= IT_BLUE_FLAG_TAKEN;
1045         self.items &~= IT_BLUE_FLAG_LOST;
1046         self.items &~= IT_CTF_SHIELDED;
1047
1048         entity flag;
1049         float redflags, blueflags;
1050
1051         if(self.ctf_captureshielded)
1052                 self.items |= IT_CTF_SHIELDED;
1053
1054         redflags = 0;
1055         blueflags = 0;
1056
1057         for (flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext) if(flag.cnt != FLAG_BASE)
1058         {
1059                 if(flag.items & IT_KEY2) // blue
1060                         ++redflags;
1061                 else if(flag.items & IT_KEY1) // red
1062                         ++blueflags;
1063         }
1064
1065         // blinking magic: if there is more than one flag, show one of these in a clever way
1066         if(redflags)
1067                 redflags = mod(floor(time * redflags * 0.75), redflags);
1068         if(blueflags)
1069                 blueflags = mod(floor(time * blueflags * 0.75), blueflags);
1070
1071         for (flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext) if(flag.cnt != FLAG_BASE)
1072         {
1073                 if(flag.items & IT_KEY2) // blue
1074                 {
1075                         if(--redflags == -1) // happens exactly once (redflags is in 0..count-1, and will --'ed count times)
1076                                 ctf_setstatus2(flag, IT_RED_FLAG_TAKEN);
1077                 }
1078                 else if(flag.items & IT_KEY1) // red
1079                 {
1080                         if(--blueflags == -1) // happens exactly once
1081                                 ctf_setstatus2(flag, IT_BLUE_FLAG_TAKEN);
1082                 }
1083         }
1084 }
1085 /*
1086 entity ctf_team_has_commander(float cteam)
1087 {
1088         entity pl;
1089         if(cteam != COLOR_TEAM1 || cteam != COLOR_TEAM2)
1090                 return world;
1091
1092         FOR_EACH_REALPLAYER(pl) {
1093                 if(pl.team == cteam && pl.iscommander) {
1094                         return pl;
1095                 }
1096         }
1097         return world;
1098 }
1099
1100 void ctf_setstate(entity e, float st)
1101 {
1102         e.ctf_state = st;
1103         ++e.version;
1104 }
1105
1106 void ctf_new_commander(float cteam)
1107 {
1108         entity pl, plmax;
1109
1110         plmax = world;
1111         FOR_EACH_REALPLAYER(pl) {
1112                 if(pl.team == cteam) {
1113                         if(pl.iscommander) { // don't reassign if alreay there
1114                                 return;
1115                         }
1116                         if(plmax == world || plmax.frags < pl.frags) <<<<<<<<<<<<<<<<< BROKEN in new scoring system
1117                                 plmax = pl;
1118                 }
1119         }
1120         if(plmax == world) {
1121                 bprint(strcat(ColoredTeamName(cteam), " Team has no Commander!\n"));
1122                 return;
1123         }
1124
1125         plmax.iscommander = TRUE;
1126         ctf_setstate(plmax, 3);
1127         sprint(plmax, "^3You're the commander now!\n");
1128         centerprint(plmax, "^3You're the commander now!\n");
1129 }
1130
1131 void ctf_clientconnect()
1132 {
1133         self.iscommander = FALSE;
1134
1135         if(!self.team || self.classname != "player") {
1136                 ctf_setstate(self, -1);
1137         } else
1138                 ctf_setstate(self, 0);
1139
1140         self.team_saved = self.team;
1141
1142         if(self.team != 0 && self.classname == "player" && !ctf_team_has_commander(self.team)) {
1143                 ctf_new_commander(self.team);
1144         }
1145 }
1146
1147 void ctf_playerchanged()
1148 {
1149         if(!self.team || self.classname != "player") {
1150                 ctf_setstate(self, -1);
1151         } else if(self.ctf_state < 0 && self.classname == "player") {
1152                 ctf_setstate(self, 0);
1153         }
1154
1155         if(self.iscommander &&
1156            (self.classname != "player" || self.team != self.team_saved)
1157                 )
1158         {
1159                 self.iscommander = FALSE;
1160                 if(self.classname == "player")
1161                         ctf_setstate(self, 0);
1162                 else
1163                         ctf_setstate(self, -1);
1164                 ctf_new_commander(self.team_saved);
1165         }
1166
1167         self.team_saved = self.team;
1168
1169         ctf_new_commander(self.team);
1170 }
1171
1172 void ctf_clientdisconnect()
1173 {
1174         if(self.iscommander)
1175         {
1176                 ctf_new_commander(self.team);
1177         }
1178 }
1179
1180 entity GetPlayer(string);
1181 float ctf_clientcommand()
1182 {
1183         entity e;
1184         if(argv(0) == "order") {
1185                 if(!g_ctf) {
1186                         sprint(self, "This command is not supported in this gamemode.\n");
1187                         return TRUE;
1188                 }
1189                 if(!self.iscommander) {
1190                         sprint(self, "^1You are not the commander!\n");
1191                         return TRUE;
1192                 }
1193                 if(argv(2) == "") {
1194                         sprint(self, "Usage: order #player status   - (playernumber as in status)\n");
1195                         return TRUE;
1196                 }
1197                 e = GetPlayer(argv(1));
1198                 if(e == world) {
1199                         sprint(self, "Invalid player.\nUsage: order #player status   - (playernumber as in status)\n");
1200                         return TRUE;
1201                 }
1202                 if(e.team != self.team) {
1203                         sprint(self, "^3You can only give orders to your own team!\n");
1204                         return TRUE;
1205                 }
1206                 if(argv(2) == "attack") {
1207                         sprint(self, strcat("Ordering ", e.netname, " to attack!\n"));
1208                         sprint(e, "^1Attack!\n");
1209                         centerprint(e, "^7You've been ordered to^9\n^1Attack!\n");
1210                         ctf_setstate(e, 1);
1211                 } else if(argv(2) == "defend") {
1212                         sprint(self, strcat("Ordering ", e.netname, " to defend!\n"));
1213                         sprint(e, "^Defend!\n");
1214                         centerprint(e, "^7You've been ordered to^9\n^2Defend!\n");
1215                         ctf_setstate(e, 2);
1216                 } else {
1217                         sprint(self, "^7Invalid command, use ^3attack^7, or ^3defend^7.\n");
1218                 }
1219                 return TRUE;
1220         }
1221         return FALSE;
1222 }
1223 */