Merge remote-tracking branch 'origin/divVerent/allow-override-item-model'
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / assault.qc
1 void spawnfunc_func_breakable();
2 void target_objective_decrease_activate();
3 .entity assault_decreaser;
4 .entity assault_sprite;
5
6 void spawnfunc_info_player_attacker() {
7         if(!g_assault)
8         {
9                 remove(self);
10                 return;
11         }
12         self.team = COLOR_TEAM1; // red, gets swapped every round
13         spawnfunc_info_player_deathmatch();
14 }
15
16 void spawnfunc_info_player_defender() {
17         if(!g_assault)
18         {
19                 remove(self);
20                 return;
21         }
22         self.team = COLOR_TEAM2; // blue, gets swapped every round
23         spawnfunc_info_player_deathmatch();
24 }
25
26 // reset this objective. Used when spawning an objective
27 // and when a new round starts
28 void assault_objective_reset() {
29         self.health = ASSAULT_VALUE_INACTIVE;
30 }
31
32 void assault_objective_use() {
33         // activate objective
34         self.health = 100;
35         //print("^2Activated objective ", self.targetname, "=", etos(self), "\n");
36         //print("Activator is ", activator.classname, "\n");
37
38         entity oldself;
39         oldself = self;
40
41         for(self = world; (self = find(self, target, oldself.targetname)); )
42         {
43                 if(self.classname == "target_objective_decrease")
44                         target_objective_decrease_activate();
45         }
46
47         self = oldself;
48 }
49
50 vector target_objective_spawn_evalfunc(entity player, entity spot, vector current)
51 {
52         if(self.health < 0 || self.health >= ASSAULT_VALUE_INACTIVE)
53                 return '-1 0 0';
54         return current;
55 }
56
57 void spawnfunc_target_objective() {
58         if(!g_assault)
59         {
60                 remove(self);
61                 return;
62         }
63         self.classname = "target_objective";
64         self.use = assault_objective_use;
65         assault_objective_reset();
66         self.reset = assault_objective_reset;
67         self.spawn_evalfunc = target_objective_spawn_evalfunc;
68 }
69
70
71 // decrease the health of targeted objectives
72 void assault_objective_decrease_use() {
73         if(activator.team != assault_attacker_team) {
74                 // wrong team triggered decrease
75                 return;
76         }
77
78         if(other.assault_sprite)
79         {
80                 WaypointSprite_Disown(other.assault_sprite, waypointsprite_deadlifetime);
81                 if(other.classname == "func_assault_destructible")
82                         other.sprite = world;
83         }
84         else
85                 return; // already activated! cannot activate again!
86
87         if(self.enemy.health < ASSAULT_VALUE_INACTIVE)
88         {
89                 if(self.enemy.health - self.dmg > 0.5)
90                 {
91                         PlayerTeamScore_Add(activator, SP_SCORE, ST_SCORE, self.dmg);
92                         self.enemy.health = self.enemy.health - self.dmg;
93                 }
94                 else
95                 {
96                         PlayerTeamScore_Add(activator, SP_SCORE, ST_SCORE, self.enemy.health);
97                         PlayerTeamScore_Add(activator, SP_ASSAULT_OBJECTIVES, ST_ASSAULT_OBJECTIVES, 1);
98                         self.enemy.health = -1;
99
100                         entity oldself, oldactivator;
101
102                         oldself = self;
103                         self = oldself.enemy;
104                                 if(self.message)
105                                 {
106                                         entity player;
107                                         string s;
108                                         FOR_EACH_PLAYER(player)
109                                         {
110                                                 s = strcat(self.message, "\n");
111                                                 centerprint(player, s);
112                                         }
113                                 }
114
115                                 oldactivator = activator;
116                                 activator = oldself;
117                                         SUB_UseTargets();
118                                 activator = oldactivator;
119                         self = oldself;
120                 }
121         }
122 }
123
124 void assault_setenemytoobjective()
125 {
126         entity objective;
127         for(objective = world; (objective = find(objective, targetname, self.target)); ) {
128                 if(objective.classname == "target_objective") {
129                         if(self.enemy == world)
130                                 self.enemy = objective;
131                         else
132                                 objerror("more than one objective as target - fix the map!");
133                         break;
134                 }
135         }
136
137         if(self.enemy == world)
138                 objerror("no objective as target - fix the map!");
139 }
140
141 float assault_decreaser_sprite_visible(entity e)
142 {
143         entity decreaser;
144
145         decreaser = self.assault_decreaser;
146
147         if(decreaser.enemy.health >= ASSAULT_VALUE_INACTIVE)
148                 return FALSE;
149
150         return TRUE;
151 }
152
153 void target_objective_decrease_activate()
154 {
155         entity ent, spr;
156         self.owner = world;
157         for(ent = world; (ent = find(ent, target, self.targetname)); )
158         {
159                 if(ent.assault_sprite != world)
160                 {
161                         WaypointSprite_Disown(ent.assault_sprite, waypointsprite_deadlifetime);
162                         if(ent.classname == "func_assault_destructible")
163                                 ent.sprite = world;
164                 }
165
166                 spr = WaypointSprite_SpawnFixed("<placeholder>", 0.5 * (ent.absmin + ent.absmax), ent, assault_sprite, RADARICON_OBJECTIVE, '1 0.5 0');
167                 spr.assault_decreaser = self;
168                 spr.waypointsprite_visible_for_player = assault_decreaser_sprite_visible;
169                 spr.classname = "sprite_waypoint";
170                 WaypointSprite_UpdateRule(spr, assault_attacker_team, SPRITERULE_TEAMPLAY);
171                 if(ent.classname == "func_assault_destructible")
172                 {
173                         WaypointSprite_UpdateSprites(spr, "as-defend", "as-destroy", "as-destroy");
174                         WaypointSprite_UpdateMaxHealth(spr, ent.max_health);
175                         WaypointSprite_UpdateHealth(spr, ent.health);
176                         ent.sprite = spr;
177                 }
178                 else
179                         WaypointSprite_UpdateSprites(spr, "as-defend", "as-push", "as-push");
180         }
181 }
182
183 void target_objective_decrease_findtarget()
184 {
185         assault_setenemytoobjective();
186 }
187
188 //=============================================================================
189
190 void spawnfunc_target_objective_decrease() {
191         if(!g_assault)
192         {
193                 remove(self);
194                 return;
195         }
196
197         self.classname = "target_objective_decrease";
198
199         if(!self.dmg) {
200                 self.dmg = 101;
201         }
202         self.use = assault_objective_decrease_use;
203         self.health = ASSAULT_VALUE_INACTIVE;
204         self.max_health = ASSAULT_VALUE_INACTIVE;
205         self.enemy = world;
206
207         InitializeEntity(self, target_objective_decrease_findtarget, INITPRIO_FINDTARGET);
208 }
209
210 // destructible walls that can be used to trigger target_objective_decrease
211 void spawnfunc_func_assault_destructible() {
212         if(!g_assault)
213         {
214                 remove(self);
215                 return;
216         }
217         self.spawnflags = 3;
218         self.classname = "func_assault_destructible";
219         if(assault_attacker_team == COLOR_TEAM1) {
220                 self.team = COLOR_TEAM2;
221         } else {
222                 self.team = COLOR_TEAM1;
223         }
224         spawnfunc_func_breakable();
225 }
226
227 void assault_wall_think() {
228         if(self.enemy.health < 0) {
229                 self.model = "";
230                 self.solid = SOLID_NOT;
231         } else {
232                 self.model = self.mdl;
233                 self.solid = SOLID_BSP;
234         }
235
236         self.nextthink = time + 0.2;
237 }
238
239 void spawnfunc_func_assault_wall() {
240         if(!g_assault)
241         {
242                 remove(self);
243                 return;
244         }
245         self.classname = "func_assault_wall";
246         self.mdl = self.model;
247         setmodel(self, self.mdl);
248         self.solid = SOLID_BSP;
249         self.think = assault_wall_think;
250         self.nextthink = time;
251         InitializeEntity(self, assault_setenemytoobjective, INITPRIO_FINDTARGET);
252 }
253
254
255 void target_assault_roundend_reset() {
256         //print("round end reset\n");
257         self.cnt = self.cnt + 1; // up round counter
258         self.winning = 0; // up round
259 }
260
261 void target_assault_roundend_use() {
262         self.winning = 1; // round has been won by attackers
263 }
264
265 void spawnfunc_target_assault_roundend() {
266         if(!g_assault)
267         {
268                 remove(self);
269                 return;
270         }
271         self.winning = 0; // round not yet won by attackers
272         self.classname = "target_assault_roundend";
273         self.use = target_assault_roundend_use;
274         self.cnt = 0; // first round
275         self.reset = target_assault_roundend_reset;
276 }
277
278 void assault_roundstart_use() {
279
280         activator = self;
281         SUB_UseTargets();
282
283
284 #ifdef TTURRETS_ENABLED
285         entity ent, oldself;
286
287         //(Re)spawn all turrets
288         oldself = self;
289         ent = find(world, classname, "turret_main");
290         while(ent) {
291                 // Swap turret teams
292                 if(ent.team == COLOR_TEAM1)
293                         ent.team = COLOR_TEAM2;
294                 else
295                         ent.team = COLOR_TEAM1;
296
297                 self = ent;
298
299                 // Dubbles as teamchange
300                 turret_stdproc_respawn();
301
302                 ent = find(ent, classname, "turret_main");
303         }
304         self = oldself;
305 #endif
306
307
308 }
309
310 void spawnfunc_target_assault_roundstart() {
311         if(!g_assault)
312         {
313                 remove(self);
314                 return;
315         }
316         assault_attacker_team = COLOR_TEAM1;
317         self.classname = "target_assault_roundstart";
318         self.use = assault_roundstart_use;
319         self.reset2 = assault_roundstart_use;
320         InitializeEntity(self, assault_roundstart_use, INITPRIO_FINDTARGET);
321 }
322
323 // trigger new round
324 // reset objectives, toggle spawnpoints, reset triggers, ...
325 void vehicles_clearrturn();
326 void vehicles_spawn();
327 void assault_new_round()
328 {
329     entity oldself;
330         //bprint("ASSAULT: new round\n");
331
332         oldself = self;
333         // Eject players from vehicles
334     FOR_EACH_PLAYER(self)
335     {
336         if(self.vehicle)
337             vehicles_exit(VHEF_RELESE);
338     }
339
340     self = findchainflags(vehicle_flags, VHF_ISVEHICLE);
341     while(self)
342     {
343         vehicles_clearrturn();
344         vehicles_spawn();
345         self = self.chain;
346     }
347
348     self = oldself;
349
350         // up round counter
351         self.winning = self.winning + 1;
352
353         // swap attacker/defender roles
354         if(assault_attacker_team == COLOR_TEAM1) {
355                 assault_attacker_team = COLOR_TEAM2;
356         } else {
357                 assault_attacker_team = COLOR_TEAM1;
358         }
359
360
361         entity ent;
362         for(ent = world; (ent = nextent(ent)); )
363         {
364                 if(clienttype(ent) == CLIENTTYPE_NOTACLIENT)
365                 {
366                         if(ent.team_saved == COLOR_TEAM1)
367                                 ent.team_saved = COLOR_TEAM2;
368                         else if(ent.team_saved == COLOR_TEAM2)
369                                 ent.team_saved = COLOR_TEAM1;
370                 }
371         }
372
373         // reset the level with a countdown
374         cvar_set("timelimit", ftos(ceil(time - game_starttime) / 60));
375         ReadyRestart_force(); // sets game_starttime
376 }