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