assault mode: print some time warnings
[divverent/nexuiz.git] / data / qcsrc / server / assault.qc
1 //=============================================================================\r
2 \r
3 /*QUAKED info_player_attacker (1 0 0) (-16 -16 -24) (16 16 45) INITIAL\r
4 Normal attacker spawning location for Nexuiz Asssault\r
5 -------- KEYS --------\r
6 angle : direction in which player will look when spawning in the game. Does not apply to bots.\r
7 target : this should point to a target_objective to decide when this spawning point is active.\r
8 nobots : when set to 1, bots will never use this spawn point to respawn in the game.\r
9 nohumans : when set to 1, human players will never use this spawn point to respawn in the game.\r
10 notfree : when set to 1, entity will not spawn in "Free for all" and "Tournament" modes.\r
11 notteam : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes.\r
12 notsingle : when set to 1, entity will not spawn in Single Player mode (bot play mode).\r
13 -------- SPAWNFLAGS --------\r
14 INITIAL : makes the spawnpoint the initial place for the player to spawn at the beginning of the game.*/
15 void info_player_attacker() {
16         info_player_deathmatch();
17         self.team = COLOR_TEAM1; // red, gets swapped every round
18 }
19
20 //=============================================================================\r
21 \r
22 /*QUAKED info_player_defender (0 1 0) (-16 -16 -24) (16 16 45) INITIAL\r
23 Normal defender spawning location for Nexuiz Asssault\r
24 -------- KEYS --------\r
25 angle : direction in which player will look when spawning in the game. Does not apply to bots.\r
26 target : this should point to a target_objective to decide when this spawning point is active.\r
27 nobots : when set to 1, bots will never use this spawn point to respawn in the game.\r
28 nohumans : when set to 1, human players will never use this spawn point to respawn in the game.\r
29 notfree : when set to 1, entity will not spawn in "Free for all" and "Tournament" modes.\r
30 notteam : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes.\r
31 notsingle : when set to 1, entity will not spawn in Single Player mode (bot play mode).\r
32 -------- SPAWNFLAGS --------\r
33 INITIAL : makes the spawnpoint the initial place for the player to spawn at the beginning of the game.*/
34 void info_player_defender() {
35         info_player_deathmatch();
36         self.team = COLOR_TEAM2; // blue, gets swapped every round
37 }
38
39 // reset this objective. Used when spawning an objective
40 // and when a new round starts
41 void assault_objective_reset() {
42         self.health = ASSAULT_VALUE_INACTIVE;
43 }
44
45 void assault_objective_use() {
46         // activate objective
47         self.health = 100; 
48         self.nextthink = time + 0.1;
49 }
50
51 void assault_objective_think() {
52         local entity oldself;   
53         if(self.health < 0) {
54                 //self.effects = 0;
55                 local entity ent;
56                 ent = find(world, targetname, self.target);
57                 while(ent) {
58                         oldself = self;
59                         self = ent;
60                         self.use();
61                         self = oldself;
62                         ent = find(ent, targetname, self.target);
63                         
64                 }
65         } else {
66                 //self.effects = EF_STARDUST;
67                 self.nextthink = time + 0.1;
68         }
69         
70 }
71 //=============================================================================\r
72 \r
73 /*QUAKED target_objective (0 .5 0) (-8 -8 -8) (8 8 8)\r
74 Objective controller for Nexuiz Assault. When active it has 100 health. If it falls below 0 then
75 it'll trigger the next targeted entity (usually the next objective or target_assault_roundend etc.)\r
76 -------- KEYS --------\r
77 targetname : point to e.g. next objective*/
78 void target_objective() {
79         self.classname = "target_objective";
80         self.think = assault_objective_think;
81         self.use = assault_objective_use;
82         assault_objective_reset();
83 }
84
85 float assault_objective_decrease_customizeforclient() {
86         if(!self.spawnflags)
87                 return FALSE;
88
89         if(self.cnt == 0) {
90                 if(other.team == assault_attacker_team)
91                         if(self.spawnflags == 1)
92                                 setmodel(self, "models/sprites/push.sp2");
93                         else
94                                 setmodel(self, "models/sprites/destroy.sp2");
95                 else
96                         setmodel(self, "models/sprites/defend.sp2");
97         } else {
98                 return FALSE;
99         }
100         return TRUE;
101 }
102
103
104 void assault_objective_decrease_think() {
105
106         local entity objective;
107         local float found;
108         found = 0;
109         objective = find(world, targetname, self.target);
110         while(objective && found == 0) {
111                 if(objective.classname == "target_objective") {
112                         found = 1;
113                         if(objective.health < ASSAULT_VALUE_INACTIVE) { // targeted objective is active
114                                 if(self.cnt == 1 && self.max_health >= ASSAULT_VALUE_INACTIVE) { 
115                                         // decrease was fired already, but objective did recover (round reset)
116                                         self.cnt = 0;
117                                 }
118                         } else { // objective isn't active
119                                 self.cnt = 1;
120                         }
121                         self.max_health = objective.health; // save current objective status for next think
122                 }
123         }
124
125         if(!self.spawnflags) {
126                 local entity ent;
127                 ent = find(world, target, self.targetname);
128                 if(ent) {
129                         if(ent.classname == "func_assault_destructible")
130                                 self.spawnflags = 2;
131                         else
132                                 self.spawnflags = 1;
133                 }
134         }
135
136         self.nextthink = time + 0.2;
137 }
138
139
140 // decrease the health of targeted objectives
141 void assault_objective_decrease_use() {
142
143         if(self.cnt > 0)
144                 return;
145
146         if(activator.team != assault_attacker_team)
147                 return;
148
149         local entity ent;
150         ent = find(world, targetname, self.target);
151         while(ent) {
152                 if(ent.health > 0 && ent.health < ASSAULT_VALUE_INACTIVE)
153                         ent.health = ent.health - self.dmg;
154                 ent = find(ent, targetname, self.target);
155         }
156
157         self.cnt = 1;
158 }
159
160 //=============================================================================\r
161 \r
162 /*QUAKED target_objective_decrease (0 .5 0) (-8 -8 -8) (8 8 8)\r
163 When triggered decreases health of the targeted target_objective.
164 -------- KEYS --------\r
165 targetname : point to a target_objective entity*/
166 void target_objective_decrease() {
167
168         self.classname = "target_objective_decrease";
169
170         precache_model("models/sprites/defend.sp2");
171         precache_model("models/sprites/destroy.sp2");
172         precache_model("models/sprites/push.sp2");
173
174         if(!self.dmg) {
175                 self.dmg = 101;
176         }
177         self.cnt = 0; // not used yet
178         self.use = assault_objective_decrease_use;
179         self.mdl = "models/sprites/here.sp2";
180         self.effects = EF_NODEPTHTEST;
181         self.health = ASSAULT_VALUE_INACTIVE;
182         self.max_health = ASSAULT_VALUE_INACTIVE;
183         self.think = assault_objective_decrease_think;
184         self.customizeentityforclient = assault_objective_decrease_customizeforclient;
185         self.nextthink = time;
186 }
187
188
189 void assault_destructible_reset() {
190         self.health = self.max_health;
191         self.model = self.mdl;
192         self.solid = SOLID_BSP;
193         self.colormod = '1 1 1';
194         self.cnt = 0; // not active
195         if(self.spawnflags)
196                 self.use();
197 }
198
199 void assault_destructible_use() {
200         self.cnt = 1; // mark active
201         self.takedamage = DAMAGE_YES;
202 }
203
204 void assault_destructible_destroy() {
205         local entity oldself;
206         
207         self.model = "";
208         self.takedamage = DAMAGE_NO;
209         self.solid = SOLID_NOT;
210         local entity ent;
211         ent = find(world, targetname, self.target);
212         while(ent) {
213                 oldself = self;
214                 self = ent;
215                 self.use();
216                 self = oldself;
217                 ent = find(ent, targetname, self.target);
218         }
219 }
220
221 void assault_destructible_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) {
222
223         if(self.cnt > 0 && assault_attacker_team == attacker.team) {
224                 self.health = self.health - damage;
225                 if(self.health / self.max_health < 0.25)
226                         self.colormod = '1 0 0';
227                 else if(self.health / self.max_health < 0.375)
228                         self.colormod = '1 0.25 0';
229                 else if(self.health / self.max_health < 0.50)
230                         self.colormod = '1 0.5 0';
231                 else if(self.health / self.max_health < 0.625)
232                         self.colormod = '1 0.75 0';
233                 else if(self.health / self.max_health < 0.75)
234                         self.colormod = '1 1 0';
235                 else
236                         self.colormod = '1 1 1';
237         }
238
239         if(self.health < 0) {
240                 activator = attacker;
241                 assault_destructible_destroy();
242         }
243 }
244
245 // destructible walls that can be used to trigger target_objective_decrease
246 void func_assault_destructible() {
247         if(!self.health)
248                 self.health = 100;
249
250         self.max_health = self.health;
251         
252         self.cnt = 0; // not yet activated
253
254         self.classname = "func_assault_destructible";
255         self.mdl = self.model;
256         setmodel(self, self.mdl);
257
258         self.solid = SOLID_BSP;
259         self.use = assault_destructible_use;
260         self.event_damage = assault_destructible_damage;
261
262 }
263
264 void assault_wall_think() {
265         local entity ent;
266         local float notvisible;
267         notvisible = 0;
268         ent = find(world, targetname, self.target);
269         while(ent) {
270                 if(ent.classname == "target_objective" && ent.health < 0)
271                         notvisible = 1;
272                 ent = find(ent, targetname, self.target);
273         }
274
275         if(notvisible) {
276                 self.model = "";
277                 self.solid = SOLID_NOT;
278         } else {
279                 self.model = self.mdl;
280                 self.solid = SOLID_BSP;
281         }
282
283         self.nextthink = time + 0.2;
284 }
285
286 void func_assault_wall() {
287         self.classname = "func_assault_wall";
288         self.mdl = self.model;
289         setmodel(self, self.mdl);
290         self.solid = SOLID_BSP;
291         self.think = assault_wall_think;
292         self.nextthink = time;
293 }
294
295 // trigger new round
296 // reset objectives, toggle spawnpoints, reset triggers, ...
297 void assault_new_round() {
298         
299         // up round counter
300         self.winning = self.winning + 1;
301         // set end time for next round
302         self.cnt = time + self.health;
303
304         // swap spawn point teams
305         local entity ent;
306         local entity oldself;
307
308         // reward attackers for winning the round
309         ent = find(world, classname, "player");
310         while(ent) {
311                 if(ent.team == assault_attacker_team) {
312                         UpdateFrags(ent, 10);
313                 }
314                 ent = find(ent, classname, "player");
315         }
316
317         // swap attacker/defender roles
318         if(assault_attacker_team == COLOR_TEAM1) {
319                 assault_attacker_team = COLOR_TEAM2;
320         } else {
321                 assault_attacker_team = COLOR_TEAM1;
322         }
323
324         ent = find(world, classname, "info_player_deathmatch");
325         while (ent)
326         {
327                 oldself = self;
328                 self = ent;
329                 if(self.team == COLOR_TEAM1) {
330                         self.team = COLOR_TEAM2;
331                 } else {
332                         self.team = COLOR_TEAM1;
333                 }
334                 self = oldself;
335
336                 ent = find(ent, classname, "info_player_deathmatch");
337         } 
338
339         // reset all objectives
340         ent = find(world, classname, "target_objective");
341         while (ent)
342         {
343                 oldself = self;
344                 self = ent;
345                 assault_objective_reset();
346                 self = oldself;
347
348                 ent = find(ent, classname, "target_objective");
349         } 
350
351         // reset all target_object_decrease
352         ent = find(world, classname, "target_objective_decrease");
353         while (ent)
354         {
355                 ent.cnt = 0;
356                 ent = find(ent, classname, "target_objective_decrease");
357         } 
358
359         // reset all func_assault_destructible
360         ent = find(world, classname, "func_assault_destructible");
361         while (ent)
362         {
363                 oldself = self;
364                 self = ent;
365                 assault_destructible_reset();
366                 self = oldself;
367                 ent = find(ent, classname, "func_assault_destructible");
368         }
369
370         ent = find(world, classname, "target_assault_roundstart");
371         while (ent)
372         {
373                 oldself = self;
374                 self = ent;
375                 self.use();
376                 self = oldself;
377                 ent = find(ent, classname, "target_assault_roundstart");
378         }
379
380         // actually restart round... how to do that?
381         ent = find(world, classname, "player");
382         while(ent) {
383                 oldself = self;
384                 self = ent;
385                 PutClientInServer();
386                 self = oldself;
387                 ent = find(ent, classname, "player");
388         }
389
390
391 }
392
393 void assault_print_time_warning(string s) {
394         local entity ent;
395         ent = find(world, classname, "player");
396         while(ent) {
397                 centerprint(ent, s);
398                 ent = find(ent, classname, "player");
399         }       
400 }
401
402 void assault_roundend_think() {
403         if(time > self.cnt)
404                 assault_new_round();
405
406         local float timeleft;
407         timeleft = self.cnt - time;
408         timeleft = ceil(timeleft);
409
410         // reset time notification if the values don't make sense
411         if(timeleft > self.nextstep)
412                 self.nextstep = 9999999;
413
414         if(timeleft <= 300 && self.nextstep > 300) {
415                 assault_print_time_warning("5 minutes left");
416                 self.nextstep = 300;
417         }
418
419         if(timeleft <= 240 && self.nextstep > 240) {
420                 assault_print_time_warning("4 minutes left");
421                 self.nextstep = 240;
422         }
423
424         if(timeleft <= 180 && self.nextstep > 180) {
425                 assault_print_time_warning("3 minutes left");
426                 self.nextstep = 180;
427         }
428
429         if(timeleft <= 120 && self.nextstep > 120) {
430                 assault_print_time_warning("2 minutes left");
431                 self.nextstep = 120;
432         }
433
434         if(timeleft <= 60 && self.nextstep > 60) {
435                 assault_print_time_warning("one minute left");
436                 self.nextstep = 60;
437         }
438
439         if(timeleft <= 10) {
440                 assault_print_time_warning(ftos(timeleft));
441         }
442
443         self.nextthink = time + 0.25;
444 }
445
446
447
448 void target_assault_roundend() {
449         if(!self.health)
450                 self.health = 300; // 5 minutes
451         self.winning = 0; // round counter
452         self.cnt = time + self.max_health; // time when this round ends
453         self.classname = "target_assault_roundend";
454         self.nextstep = 9999999; // used to store what time warning was last issued;
455         self.use = assault_new_round;
456         self.think = assault_roundend_think;
457         self.nextthink = time;
458 }
459
460 void assault_roundstart_use() {
461         local entity ent;
462         local entity oldself;
463         ent = find(world, targetname, self.target);
464         while(ent) {
465                 oldself = self;
466                 self = ent;
467                 self.use();
468                 self = oldself;
469                 ent = find(ent, targetname, self.target);
470         }
471 }
472
473 void target_assault_roundstart() {
474         assault_attacker_team = COLOR_TEAM1;
475         self.classname = "target_assault_roundstart";
476         self.use = assault_roundstart_use;
477         self.think = assault_roundstart_use;
478         self.nextthink = time + 0.1;
479 }
480
481