]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/mode_onslaught.qc
new waypointsprite system, onslaught not yet supported
[divverent/nexuiz.git] / data / qcsrc / server / mode_onslaught.qc
1 .entity sprite;
2 .string target2;
3 .float iscaptured;
4 .float islinked;
5 .float isgenneighbor_red;
6 .float isgenneighbor_blue;
7 .float iscpneighbor_red;
8 .float iscpneighbor_blue;
9 .float isshielded;
10 .float lasthealth;
11
12 float ons_sprite_cp_red, ons_sprite_cp_blue, ons_sprite_cp_neut;
13 float ons_sprite_cp_atck_red, ons_sprite_cp_atck_blue, ons_sprite_cp_atck_neut;
14 float ons_sprite_cp_dfnd_red, ons_sprite_cp_dfnd_blue;
15 float ons_sprite_gen_red, ons_sprite_gen_blue, ons_sprite_gen_shielded;
16
17 void onslaught_updatelinks()
18 {
19         local entity l, links;
20         local float stop, t1, t2, t3, t4;
21         // first check if the game has ended
22         dprint("--- updatelinks ---\n");
23         links = findchain(classname, "onslaught_link");
24         // mark generators as being shielded and networked
25         l = findchain(classname, "onslaught_generator");
26         while (l)
27         {
28                 if (l.iscaptured)
29                         dprint(etos(l), " (generator) belongs to team ", ftos(l.team), "\n");
30                 else
31                         dprint(etos(l), " (generator) is destroyed\n");
32                 l.islinked = l.iscaptured;
33                 l.isshielded = l.iscaptured;
34                 l = l.chain;
35         }
36         // mark points as shielded and not networked
37         l = findchain(classname, "onslaught_controlpoint");
38         while (l)
39         {
40                 l.islinked = FALSE;
41                 l.isshielded = TRUE;
42                 l.isgenneighbor_red = FALSE;
43                 l.isgenneighbor_blue = FALSE;
44                 l.iscpneighbor_red = FALSE;
45                 l.iscpneighbor_blue = FALSE;
46                 dprint(etos(l), " (point) belongs to team ", ftos(l.team), "\n");
47                 l = l.chain;
48         }
49         // flow power outward from the generators through the network
50         l = links;
51         while (l)
52         {
53                 dprint(etos(l), " (link) connects ", etos(l.goalentity), " with ", etos(l.enemy), "\n");
54                 l = l.chain;
55         }
56         stop = FALSE;
57         while (!stop)
58         {
59                 stop = TRUE;
60                 l = links;
61                 while (l)
62                 {
63                         // if both points are captured by the same team, and only one of
64                         // them is powered, mark the other one as powered as well
65                         if (l.enemy.iscaptured && l.goalentity.iscaptured)
66                         if (l.enemy.islinked != l.goalentity.islinked)
67                         if (l.enemy.team == l.goalentity.team)
68                         {
69                                 if (!l.goalentity.islinked)
70                                 {
71                                         stop = FALSE;
72                                         l.goalentity.islinked = TRUE;
73                                         dprint(etos(l), " (link) is marking ", etos(l.goalentity), " (point) because its team matches ", etos(l.enemy), " (point)\n");
74                                 }
75                                 else if (!l.enemy.islinked)
76                                 {
77                                         stop = FALSE;
78                                         l.enemy.islinked = TRUE;
79                                         dprint(etos(l), " (link) is marking ", etos(l.enemy), " (point) because its team matches ", etos(l.goalentity), " (point)\n");
80                                 }
81                         }
82                         l = l.chain;
83                 }
84         }
85         // now that we know which points are powered we can mark their neighbors
86         // as unshielded if team differs
87         l = links;
88         while (l)
89         {
90                 if (l.goalentity.team != l.enemy.team)
91                 {
92                         if (l.goalentity.islinked)
93                         {
94                                 dprint(etos(l), " (link) is unshielding ", etos(l.enemy), " (point) because its team does not match ", etos(l.goalentity), " (point)\n");
95                                 l.enemy.isshielded = FALSE;
96                                 if(l.goalentity.classname == "onslaught_generator")
97                                 {
98                                         if(l.goalentity.team == COLOR_TEAM1)
99                                                 l.enemy.isgenneighbor_red = TRUE;
100                                         else if(l.goalentity.team == COLOR_TEAM2)
101                                                 l.enemy.isgenneighbor_blue = TRUE;
102                                 }
103                                 else
104                                 {
105                                         if(l.goalentity.team == COLOR_TEAM1)
106                                                 l.enemy.iscpneighbor_red = TRUE;
107                                         else if(l.goalentity.team == COLOR_TEAM2)
108                                                 l.enemy.iscpneighbor_blue = TRUE;
109                                 }
110                         }
111                         if (l.enemy.islinked)
112                         {
113                                 dprint(etos(l), " (link) is unshielding ", etos(l.goalentity), " (point) because its team does not match ", etos(l.enemy), " (point)\n");
114                                 l.goalentity.isshielded = FALSE;
115                                 if(l.enemy.classname == "onslaught_generator")
116                                 {
117                                         if(l.enemy.team == COLOR_TEAM1)
118                                                 l.goalentity.isgenneighbor_red = TRUE;
119                                         else if(l.enemy.team == COLOR_TEAM2)
120                                                 l.goalentity.isgenneighbor_blue = TRUE;
121                                 }
122                                 else
123                                 {
124                                         if(l.enemy.team == COLOR_TEAM1)
125                                                 l.goalentity.iscpneighbor_red = TRUE;
126                                         else if(l.enemy.team == COLOR_TEAM2)
127                                                 l.goalentity.iscpneighbor_blue = TRUE;
128                                 }
129                         }
130                 }
131                 l = l.chain;
132         }
133         // now update the takedamage and alpha variables on generator shields
134         l = findchain(classname, "onslaught_generator");
135         while (l)
136         {
137                 if (l.isshielded)
138                 {
139                         dprint(etos(l), " (generator) is shielded\n");
140                         l.enemy.alpha = 1;
141                         l.takedamage = DAMAGE_NO;
142                         l.bot_attack = FALSE;
143                 }
144                 else
145                 {
146                         dprint(etos(l), " (generator) is not shielded\n");
147                         l.enemy.alpha = -1;
148                         l.takedamage = DAMAGE_AIM;
149                         l.bot_attack = TRUE;
150                 }
151                 l = l.chain;
152         }
153         // now update the takedamage and alpha variables on control point icons
154         l = findchain(classname, "onslaught_controlpoint");
155         while (l)
156         {
157                 if (l.isshielded)
158                 {
159                         dprint(etos(l), " (point) is shielded\n");
160                         l.enemy.alpha = 1;
161                         if (l.goalentity)
162                         {
163                                 l.goalentity.takedamage = DAMAGE_NO;
164                                 l.goalentity.bot_attack = FALSE;
165                         }
166                 }
167                 else
168                 {
169                         dprint(etos(l), " (point) is not shielded\n");
170                         l.enemy.alpha = -1;
171                         if (l.goalentity)
172                         {
173                                 l.goalentity.takedamage = DAMAGE_AIM;
174                                 l.goalentity.bot_attack = TRUE;
175                         }
176                 }
177                 l = l.chain;
178         }
179         // count generators owned by each team
180         t1 = t2 = t3 = t4 = 0;
181         l = findchain(classname, "onslaught_generator");
182         while (l)
183         {
184                 if (l.iscaptured)
185                 {
186                         if (l.team == COLOR_TEAM1) t1 = 1;
187                         if (l.team == COLOR_TEAM2) t2 = 1;
188                         if (l.team == COLOR_TEAM3) t3 = 1;
189                         if (l.team == COLOR_TEAM4) t4 = 1;
190                 }
191                 l = l.chain;
192         }
193         // see if multiple teams remain (if not, it's game over)
194         if (t1 + t2 + t3 + t4 < 2)
195                 dprint("--- game over ---\n");
196         else
197                 dprint("--- done updating links ---\n");
198 };
199
200 float onslaught_controlpoint_can_be_linked(entity cp, float t)
201 {
202         if(t == COLOR_TEAM1)
203         {
204                 if(cp.isgenneighbor_red)
205                         return 2;
206                 if(cp.iscpneighbor_red)
207                         return 1;
208         }
209         else if(t == COLOR_TEAM2)
210         {
211                 if(cp.isgenneighbor_blue)
212                         return 2;
213                 if(cp.iscpneighbor_blue)
214                         return 1;
215         }
216         return 0;
217 /*
218         entity e;
219         // check to see if this player has a legitimate claim to capture this
220         // control point - more specifically that there is a captured path of
221         // points leading back to the team generator
222         e = findchain(classname, "onslaught_link");
223         while (e)
224         {
225                 if (e.goalentity == cp)
226                 {
227                         dprint(etos(e), " (link) connects to ", etos(e.enemy), " (point)");
228                         if (e.enemy.islinked)
229                         {
230                                 dprint(" which is linked");
231                                 if (e.enemy.team == t)
232                                 {
233                                         dprint(" and has the correct team!\n");
234                                         return 1;
235                                 }
236                                 else
237                                         dprint(" but has the wrong team\n");
238                         }
239                         else
240                                 dprint("\n");
241                 }
242                 else if (e.enemy == cp)
243                 {
244                         dprint(etos(e), " (link) connects to ", etos(e.goalentity), " (point)");
245                         if (e.goalentity.islinked)
246                         {
247                                 dprint(" which is linked");
248                                 if (e.goalentity.team == t)
249                                 {
250                                         dprint(" and has a team!\n");
251                                         return 1;
252                                 }
253                                 else
254                                         dprint(" but has the wrong team\n");
255                         }
256                         else
257                                 dprint("\n");
258                 }
259                 e = e.chain;
260         }
261         return 0;
262 */
263 }
264
265 float onslaught_controlpoint_attackable(entity cp, float t)
266 // -2: SAME TEAM, attackable by enemy!
267 // -1: SAME TEAM!
268 // 0:  off limits
269 // 1:  attack it
270 // 2:  touch it
271 // 3:  attack it (HIGH PRIO)
272 // 4:  touch it (HIGH PRIO)
273 {
274         float a;
275
276         if(cp.isshielded)
277         {
278                 return 0;
279         }
280         else if(cp.goalentity)
281         {
282                 // if there's already an icon built, nothing happens
283                 if(cp.team == t)
284                 {
285                         a = onslaught_controlpoint_can_be_linked(cp, COLOR_TEAM1 + COLOR_TEAM2 - t);
286                         if(a) // attackable by enemy?
287                                 return -2; // EMERGENCY!
288                         return -1;
289                 }
290                 // we know it can be linked, so no need to check
291                 // but...
292                 a = onslaught_controlpoint_can_be_linked(cp, t);
293                 if(a == 2) // near our generator?
294                         return 3; // EMERGENCY!
295                 return 1;
296         }
297         else
298         {
299                 // free point
300                 if(onslaught_controlpoint_can_be_linked(cp, t))
301                 {
302                         a = onslaught_controlpoint_can_be_linked(cp, COLOR_TEAM1 + COLOR_TEAM2 - t);
303                         if(a == 2)
304                                 return 4; // GET THIS ONE NOW!
305                         else
306                                 return 2; // TOUCH ME
307                 }
308         }
309         return 0;
310 }
311
312 void onslaught_generator_think()
313 {
314         local float d;
315         local entity e;
316         self.nextthink = ceil(time + 1);
317         if (cvar("timelimit"))
318         if (time > cvar("timelimit") * 60 - 60)
319         {
320                 // self.max_health / 300 gives 5 minutes of overtime.
321                 // control points reduce the overtime duration.
322                 sound(self, CHAN_TRIGGER, "sound/onslaught/generator_decay.wav", VOL_BASE, ATTN_NORM);
323                 d = 1;
324                 e = findchain(classname, "onslaught_controlpoint");
325                 while (e)
326                 {
327                         if (e.team != self.team)
328                         if (e.islinked)
329                                 d = d + 1;
330                         e = e.chain;
331                 }
332                 d = d * self.max_health / 300;
333                 Damage(self, self, self, d, DEATH_HURTTRIGGER, self.origin, '0 0 0');
334         }
335 };
336
337 void onslaught_generator_deaththink()
338 {
339         local vector org;
340         if (self.count > 0)
341         {
342                 self.nextthink = time + 0.1;
343                 self.count = self.count - 1;
344                 org = randompos(self.origin + self.mins + '8 8 8', self.origin + self.maxs + '-8 -8 -8');
345                 pointparticles(particleeffectnum("onslaught_generator_smallexplosion"), org, '0 0 0', 1);
346                 sound(self, CHAN_TRIGGER, "sound/weapons/grenade_impact.wav", VOL_BASE, ATTN_NORM);
347         }
348         else
349         {
350                 org = self.origin;
351                 pointparticles(particleeffectnum("onslaught_generator_finalexplosion"), org, '0 0 0', 1);
352                 sound(self, CHAN_TRIGGER, "sound/weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM);
353         }
354 };
355
356 void onslaught_generator_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
357 {
358         if (damage <= 0)
359                 return;
360         if (attacker != self)
361         {
362                 if (self.isshielded)
363                 {
364                         // this is protected by a shield, so ignore the damage
365                         if (time > self.pain_finished)
366                         if (attacker.classname == "player")
367                         {
368                                 play2(attacker, "sound/onslaught/damageblockedbyshield.wav");
369                                 self.pain_finished = time + 1;
370                         }
371                         return;
372                 }
373                 if (time > self.pain_finished)
374                 {
375                         self.pain_finished = time + 5;
376                         bprint(ColoredTeamName(self.team), " generator under attack!\n");
377                         play2team(self.team, "sound/onslaught/generator_underattack.wav");
378                 }
379         }
380         self.health = self.health - damage;
381         // choose an animation frame based on health
382         self.frame = 10 * bound(0, (1 - self.health / self.max_health), 1);
383         // see if the generator is still functional, or dying
384         if (self.health > 0)
385         {
386                 float h, lh;
387                 lh = ceil(self.lasthealth / 100) * 100;
388                 h = ceil(self.health / 100) * 100;
389                 if(lh != h)
390                         bprint(ColoredTeamName(self.team), " generator has less than ", ftos(h), " health remaining\n");
391                 self.lasthealth = self.health;
392         }
393         else
394         {
395                 if (attacker == self)
396                         bprint(ColoredTeamName(self.team), " generator spontaneously exploded due to overtime!\n");
397                 else
398                 {
399                         string t;
400                         t = ColoredTeamName(attacker.team);
401                         bprint(ColoredTeamName(self.team), " generator destroyed by ", t, "!\n");
402                 }
403                 self.iscaptured = FALSE;
404                 self.islinked = FALSE;
405                 self.isshielded = FALSE;
406                 self.takedamage = DAMAGE_NO; // can't be hurt anymore
407                 self.event_damage = SUB_Null; // won't do anything if hurt
408                 self.count = 30; // 30 explosions
409                 self.think = onslaught_generator_deaththink; // explosion sequence
410                 self.nextthink = time; // start exploding immediately
411                 self.think(); // do the first explosion now
412                 onslaught_updatelinks();
413         }
414 };
415
416 // update links after a delay
417 void onslaught_generator_delayed()
418 {
419         onslaught_updatelinks();
420         // now begin normal thinking
421         self.think = onslaught_generator_think;
422         self.nextthink = time;
423 };
424
425 #ifdef YOU_HAVE_FIXED_ONSLAUGHT_WAYPOINTS
426 float onslaught_generator_waypointsprite_for_player(entity e)
427 {
428         if(e.classname == "player")
429                 if(e.team == self.owner.team)
430                 {
431                         if(self.owner.team == COLOR_TEAM1)
432                                 return ons_sprite_gen_red;
433                         else if(self.owner.team == COLOR_TEAM2)
434                                 return ons_sprite_gen_blue;
435                 }
436         if(self.owner.isshielded)
437                 return ons_sprite_gen_shielded;
438         if(self.owner.team == COLOR_TEAM1)
439                 return ons_sprite_gen_red;
440         else if(self.owner.team == COLOR_TEAM2)
441                 return ons_sprite_gen_blue;
442         return 0;
443 }
444
445 float onslaught_controlpoint_waypointsprite_for_player(entity e)
446 {
447         float a;
448         if(e.classname == "player")
449         {
450                 a = onslaught_controlpoint_attackable(self.owner, e.team);
451                 if(a == 3 || a == 4) // ATTACK/TOUCH THIS ONE NOW
452                 {
453                         if(self.owner.team == COLOR_TEAM1)
454                                 return ons_sprite_cp_atck_red;
455                         else if(self.owner.team == COLOR_TEAM2)
456                                 return ons_sprite_cp_atck_blue;
457                         else
458                                 return ons_sprite_cp_atck_neut;
459                 }
460                 else if(a == -2) // DEFEND THIS ONE NOW
461                 {
462                         if(self.owner.team == COLOR_TEAM1)
463                                 return ons_sprite_cp_dfnd_red;
464                         else if(self.owner.team == COLOR_TEAM2)
465                                 return ons_sprite_cp_dfnd_blue;
466                 }
467                 else if(self.owner.team == e.team || a == -1 || a == 1) // own point, or fire at it
468                 {
469                         if(self.owner.team == COLOR_TEAM1)
470                                 return ons_sprite_cp_red;
471                         else if(self.owner.team == COLOR_TEAM2)
472                                 return ons_sprite_cp_blue;
473                 }
474                 else if(a == 2) // touch it
475                         return ons_sprite_cp_neut;
476         }
477         else
478         {
479                 if(self.owner.team == COLOR_TEAM1)
480                         return ons_sprite_cp_red;
481                 else if(self.owner.team == COLOR_TEAM2)
482                         return ons_sprite_cp_blue;
483                 else
484                         return ons_sprite_cp_neut;
485         }
486         return 0;
487 }
488 #endif
489
490 /*QUAKED spawnfunc_onslaught_generator (0 .5 .8) (-32 -32 -24) (32 32 64)
491 Base generator.
492
493 spawnfunc_onslaught_link entities can target this.
494
495 keys:
496 "team" - team that owns this generator (5 = red, 14 = blue, etc), MUST BE SET.
497 "targetname" - name that spawnfunc_onslaught_link entities will use to target this.
498 */
499 void spawnfunc_onslaught_generator()
500 {
501         if (!g_onslaught)
502         {
503                 remove(self);
504                 return;
505         }
506
507 #ifdef YOU_HAVE_FIXED_ONSLAUGHT_WAYPOINTS
508         if(!ons_sprite_cp_blue)
509         {
510                 precache_model("models/sprites/ons-cp-blue.sp2");
511                 setmodel(self, "models/sprites/ons-cp-blue.sp2");
512                 ons_sprite_cp_blue = self.modelindex;
513                 precache_model("models/sprites/ons-cp-red.sp2");
514                 setmodel(self, "models/sprites/ons-cp-red.sp2");
515                 ons_sprite_cp_red = self.modelindex;
516                 precache_model("models/sprites/ons-cp-neut.sp2");
517                 setmodel(self, "models/sprites/ons-cp-neut.sp2");
518                 ons_sprite_cp_neut = self.modelindex;
519                 precache_model("models/sprites/ons-cp-dfnd-blue.sp2");
520                 setmodel(self, "models/sprites/ons-cp-dfnd-blue.sp2");
521                 ons_sprite_cp_dfnd_blue = self.modelindex;
522                 precache_model("models/sprites/ons-cp-dfnd-red.sp2");
523                 setmodel(self, "models/sprites/ons-cp-dfnd-red.sp2");
524                 ons_sprite_cp_dfnd_red = self.modelindex;
525                 precache_model("models/sprites/ons-cp-atck-blue.sp2");
526                 setmodel(self, "models/sprites/ons-cp-atck-blue.sp2");
527                 ons_sprite_cp_atck_blue = self.modelindex;
528                 precache_model("models/sprites/ons-cp-atck-red.sp2");
529                 setmodel(self, "models/sprites/ons-cp-atck-red.sp2");
530                 ons_sprite_cp_atck_red = self.modelindex;
531                 precache_model("models/sprites/ons-cp-atck-neut.sp2");
532                 setmodel(self, "models/sprites/ons-cp-atck-neut.sp2");
533                 ons_sprite_cp_atck_neut = self.modelindex;
534                 precache_model("models/sprites/ons-gen-blue.sp2");
535                 setmodel(self, "models/sprites/ons-gen-blue.sp2");
536                 ons_sprite_gen_blue = self.modelindex;
537                 precache_model("models/sprites/ons-gen-red.sp2");
538                 setmodel(self, "models/sprites/ons-gen-red.sp2");
539                 ons_sprite_gen_red = self.modelindex;
540                 precache_model("models/sprites/ons-gen-shielded.sp2");
541                 setmodel(self, "models/sprites/ons-gen-shielded.sp2");
542                 ons_sprite_gen_shielded = self.modelindex;
543         }
544 #endif
545
546         local entity e;
547         precache_model("models/onslaught/generator.md3");
548         precache_model("models/onslaught/generator_shield.md3");
549         precache_sound("sound/onslaught/generator_decay.wav");
550         precache_sound("sound/weapons/grenade_impact.wav");
551         precache_sound("sound/weapons/rocket_impact.wav");
552         precache_sound("sound/onslaught/generator_underattack.wav");
553         if (!self.team)
554                 objerror("team must be set");
555         self.team_saved = self.team;
556         self.colormap = 1024 + (self.team - 1) * 17;
557         self.solid = SOLID_BSP;
558         self.movetype = MOVETYPE_NONE;
559         self.lasthealth = self.max_health = self.health = cvar("g_onslaught_gen_health");
560         setmodel(self, "models/onslaught/generator.md3");
561         //setsize(self, '-32 -32 -24', '32 32 64');
562         setorigin(self, self.origin);
563         self.takedamage = DAMAGE_AIM;
564         self.bot_attack = TRUE;
565         self.event_damage = onslaught_generator_damage;
566         self.iscaptured = TRUE;
567         self.islinked = TRUE;
568         self.isshielded = TRUE;
569         // spawn shield model which indicates whether this can be damaged
570         self.enemy = e = spawn();
571         e.solid = SOLID_NOT;
572         e.movetype = MOVETYPE_NONE;
573         e.effects = EF_ADDITIVE;
574         setmodel(e, "models/onslaught/generator_shield.md3");
575         //setsize(e, '-32 -32 0', '32 32 128');
576         setorigin(e, self.origin);
577         e.colormap = self.colormap;
578         e.team = self.team;
579         self.think = onslaught_generator_delayed;
580         self.nextthink = time + 0.2;
581
582 #ifdef YOU_HAVE_FIXED_ONSLAUGHT_WAYPOINTS
583         WaypointSprite_SpawnFixed(string_null, e.origin + '0 0 1' * e.maxs_z, self, sprite);
584         self.sprite.waypointsprite_for_player = onslaught_generator_waypointsprite_for_player;
585 #endif
586 };
587
588 void onslaught_generator_reset()
589 {
590         self.team = self.team_saved;
591         self.lasthealth = self.max_health = self.health = cvar("g_onslaught_gen_health");
592         self.takedamage = DAMAGE_AIM;
593         self.bot_attack = TRUE;
594         self.iscaptured = TRUE;
595         self.islinked = TRUE;
596         self.isshielded = TRUE;
597         self.enemy.solid = SOLID_NOT;
598         self.think = onslaught_generator_delayed;
599         self.nextthink = time + 0.2;
600 }
601
602 void onslaught_controlpoint_icon_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
603 {
604         entity oself;
605         if (damage <= 0)
606                 return;
607         if (self.owner.isshielded)
608         {
609                 // this is protected by a shield, so ignore the damage
610                 if (time > self.pain_finished)
611                 if (attacker.classname == "player")
612                 {
613                         play2(attacker, "sound/onslaught/damageblockedbyshield.wav");
614                         self.pain_finished = time + 1;
615                 }
616                 return;
617         }
618         if (time > self.pain_finished)
619         if (attacker.classname == "player")
620         {
621                 play2team(self.team, "sound/onslaught/controlpoint_underattack.wav");
622                 self.pain_finished = time + 5;
623         }
624         self.health = self.health - damage;
625         self.alpha = self.health / self.max_health;
626         self.pain_finished = time + 1;
627         // colormod flash when shot
628         self.colormod = '2 2 2';
629         if (self.health < 0)
630         {
631                 sound(self, CHAN_TRIGGER, "sound/weapons/grenade_impact.wav", VOL_BASE, ATTN_NORM);
632                 pointparticles(particleeffectnum("onslaught_controlpoint_explosion"), self.origin, '0 0 0', 1);
633                 {
634                         string t;
635                         t = ColoredTeamName(attacker.team);
636                         bprint(ColoredTeamName(self.team), " ", self.message, " control point destroyed by ", t, "\n");
637                 }
638                 self.owner.goalentity = world;
639                 self.owner.islinked = FALSE;
640                 self.owner.iscaptured = FALSE;
641                 self.owner.team = 0;
642                 self.owner.colormap = 1024;
643                 onslaught_updatelinks();
644
645                 // Use targets now (somebody make sure this is in the right place..)
646                 oself = self;
647                 self = self.owner;
648                 activator = self.owner;
649                 SUB_UseTargets ();
650                 self = oself;
651
652                 remove(self);
653         }
654 };
655
656 void onslaught_controlpoint_icon_think()
657 {
658         self.nextthink = time + 0.1;
659         if (time > self.pain_finished + 1)
660         {
661                 self.health = self.health + self.count;
662                 if (self.health >= self.max_health)
663                         self.health = self.max_health;
664         }
665         self.alpha = self.health / self.max_health;
666         // colormod flash when shot
667         self.colormod = '1 1 1' * (2 - bound(0, (self.pain_finished - time) / 10, 1));
668 };
669
670 void onslaught_controlpoint_icon_buildthink()
671 {
672         local entity oself;
673
674         self.nextthink = time + 0.1;
675         self.health = self.health + self.count;
676         if (self.health >= self.max_health)
677         {
678                 self.health = self.max_health;
679                 self.count = self.count * 0.2; // slow repair rate from now on
680                 self.think = onslaught_controlpoint_icon_think;
681                 sound(self, CHAN_TRIGGER, "sound/onslaught/controlpoint_built.wav", VOL_BASE, ATTN_NORM);
682                 bprint(ColoredTeamName(self.team), " captured ", self.owner.message, " control point\n");
683                 self.owner.iscaptured = TRUE;
684                 onslaught_updatelinks();
685
686                 // Use targets now (somebody make sure this is in the right place..)
687                 oself = self;
688                 self = self.owner;
689                 activator = self;
690                 SUB_UseTargets ();
691                 self = oself;
692         }
693         self.alpha = self.health / self.max_health;
694         // colormod flash when shot
695         self.colormod = '1 1 1' * (2 - bound(0, (self.pain_finished - time) / 10, 1));
696 };
697
698 void onslaught_controlpoint_touch()
699 {
700         local entity e;
701         float a;
702         if (other.classname != "player")
703                 return;
704         a = onslaught_controlpoint_attackable(self, other.team);
705         if(a != 2 && a != 4)
706                 return;
707         // we've verified that this player has a legitimate claim to this point,
708         // so start building the captured point icon (which only captures this
709         // point if it successfully builds without being destroyed first)
710         self.goalentity = e = spawn();
711         e.owner = self;
712         e.max_health = cvar("g_onslaught_cp_health");
713         e.health = e.max_health * 0.1;
714         e.alpha = e.health / e.max_health;
715         e.solid = SOLID_BBOX;
716         e.movetype = MOVETYPE_NONE;
717         setmodel(e, "models/onslaught/controlpoint_icon.md3");
718         setsize(e, '-32 -32 -32', '32 32 32');
719         setorigin(e, self.origin + '0 0 96');
720         e.takedamage = DAMAGE_AIM;
721         e.bot_attack = TRUE;
722         e.event_damage = onslaught_controlpoint_icon_damage;
723         e.team = other.team;
724         e.colormap = 1024 + (e.team - 1) * 17;
725         e.think = onslaught_controlpoint_icon_buildthink;
726         e.nextthink = time + 0.1;
727         e.count = e.max_health / 50; // how long it takes to build
728         sound(e, CHAN_TRIGGER, "sound/onslaught/controlpoint_build.wav", VOL_BASE, ATTN_NORM);
729         self.team = e.team;
730         self.colormap = e.colormap;
731 };
732
733 /*QUAKED spawnfunc_onslaught_controlpoint (0 .5 .8) (-32 -32 0) (32 32 128)
734 Control point.  Be sure to give this enough clearance so that the shootable part has room to exist
735
736 This should link to an spawnfunc_onslaught_controlpoint entity or spawnfunc_onslaught_generator entity.
737
738 keys:
739 "targetname" - name that spawnfunc_onslaught_link entities will use to target this.
740 "target" - target any entities that are tied to this control point, such as vehicles and buildable structure entities.
741 "message" - name of this control point (should reflect the location in the map, such as "center bridge", "north tower", etc)
742 */
743 void spawnfunc_onslaught_controlpoint()
744 {
745         local entity e;
746         if (!g_onslaught)
747         {
748                 remove(self);
749                 return;
750         }
751         precache_model("models/onslaught/controlpoint_pad.md3");
752         precache_model("models/onslaught/controlpoint_shield.md3");
753         precache_model("models/onslaught/controlpoint_icon.md3");
754         precache_sound("sound/onslaught/controlpoint_build.wav");
755         precache_sound("sound/onslaught/controlpoint_built.wav");
756         precache_sound("sound/weapons/grenade_impact.wav");
757         precache_sound("sound/onslaught/damageblockedbyshield.wav");
758         precache_sound("sound/onslaught/controlpoint_underattack.wav");
759         self.solid = SOLID_BSP;
760         self.movetype = MOVETYPE_NONE;
761         setmodel(self, "models/onslaught/controlpoint_pad.md3");
762         //setsize(self, '-32 -32 0', '32 32 8');
763         setorigin(self, self.origin);
764         self.touch = onslaught_controlpoint_touch;
765         self.team = 0;
766         self.colormap = 1024;
767         self.iscaptured = FALSE;
768         self.islinked = FALSE;
769         self.isshielded = TRUE;
770         // spawn shield model which indicates whether this can be damaged
771         self.enemy = e = spawn();
772         e.solid = SOLID_NOT;
773         e.movetype = MOVETYPE_NONE;
774         e.effects = EF_ADDITIVE;
775         setmodel(e, "models/onslaught/controlpoint_shield.md3");
776         //setsize(e, '-32 -32 0', '32 32 128');
777         setorigin(e, self.origin);
778         e.colormap = self.colormap;
779         onslaught_updatelinks();
780
781         waypoint_spawnforitem(self);
782
783 #ifdef YOU_HAVE_FIXED_ONSLAUGHT_WAYPOINTS
784         WaypointSprite_SpawnFixed(string_null, e.origin + '0 0 1' * e.maxs_z, self, sprite);
785         self.sprite.waypointsprite_for_player = onslaught_controlpoint_waypointsprite_for_player;
786 #endif
787 };
788
789 void onslaught_controlpoint_reset()
790 {
791         if(self.goalentity && self.goalentity != world)
792                 remove(self.goalentity);
793         self.goalentity = world;
794         self.team = 0;
795         self.colormap = 1024;
796         self.iscaptured = FALSE;
797         self.islinked = FALSE;
798         self.isshielded = TRUE;
799         self.enemy.solid = SOLID_NOT;
800         self.enemy.colormap = self.colormap;
801         self.think = self.enemy.think = SUB_Null;
802         self.nextthink = 0; // don't like SUB_Null :P
803         
804         onslaught_updatelinks();
805
806         activator = self;
807         SUB_UseTargets(); // to reset the structures, playerspawns etc.
808 }
809
810 void onslaught_link_delayed()
811 {
812         self.goalentity = find(world, targetname, self.target);
813         self.enemy = find(world, targetname, self.target2);
814         if (!self.goalentity)
815                 objerror("can not find target\n");
816         if (!self.enemy)
817                 objerror("can not find target2\n");
818         dprint(etos(self.goalentity), " linked with ", etos(self.enemy), "\n");
819 }
820
821 /*QUAKED spawnfunc_onslaught_link (0 .5 .8) (-16 -16 -16) (16 16 16)
822 Link between control points.
823
824 This entity targets two different spawnfunc_onslaught_controlpoint or spawnfunc_onslaught_generator entities, and suppresses shielding on both if they are owned by different teams.
825
826 keys:
827 "target" - first control point.
828 "target2" - second control point.
829 */
830 void spawnfunc_onslaught_link()
831 {
832         if (!g_onslaught)
833         {
834                 remove(self);
835                 return;
836         }
837         if (self.target == "" || self.target2 == "")
838                 objerror("target and target2 must be set\n");
839         self.think = onslaught_link_delayed;
840         self.nextthink = time + 0.1;
841 };