]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/gamemode_rts.qc
Remove unused FOR_EACH_MONSTERS definition. Attempt to fix monster .target checking...
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / gamemode_rts.qc
1 // Real-Time Strategy
2 // Gamemode by Mario
3
4 // basically a fusion reactor with a new classname
5 void spawnfunc_healing_tower()
6 {
7         self.spawnflags = TSL_NO_RESPAWN; // healing towers don't respawn?
8         self.netname = "Monster Healing Tower"; // not used by waypoints...
9         spawnfunc_turret_fusionreactor();
10         self.classname = "healing_tower";
11         self.target_range = 1000;
12         self.shot_dmg = 30;
13 }
14
15 void rts_waypoint_think()
16 {
17         float goalcount = 0;
18         entity e;
19         
20         self.nextthink = time + 0.1;
21         
22         for(e = world; (e = findentity(e, goalentity, self)); )
23         {
24                 ++goalcount;
25         }
26         
27         if(goalcount < 1)
28         {
29                 WaypointSprite_Kill(self.sprite);
30                 remove(self);
31                 return;
32         }
33 }
34
35 void Monster_LevelUp(entity e)
36 {
37         if(self.level >= 5)
38                 return; // max level is 5 for now
39         e.speed += 0.25;
40         e.max_health += 20;
41         e.health = e.max_health;
42         e.level += 1;
43         WaypointSprite_UpdateHealth(e.sprite, e.health);
44 }
45
46 MUTATOR_HOOKFUNCTION(rts_PlayerSpawn)
47 {
48         if(self.rts_viewangle)
49                 self.angles_x = self.rts_viewangle;
50         else
51                 self.angles_x = 30;
52                 
53         self.effects |= EF_NODRAW;
54         self.oldorigin = self.origin;
55         self.monster_attack = FALSE;
56         self.last_click = time;
57         self.takedamage = DAMAGE_NO;
58         self.flags |= FL_NOTARGET;
59         self.movetype = MOVETYPE_NOCLIP;
60         stuffcmd(self, "cl_cmd settemp cl_prydoncursor 1\n");
61         return FALSE;
62 }
63
64 MUTATOR_HOOKFUNCTION(rts_FilterItem)
65 {
66         // no items... yet
67         return TRUE;
68 }
69
70 MUTATOR_HOOKFUNCTION(rts_SetStartItems)
71 {
72         WEPSET_COPY_AW(start_weapons, 0);
73         
74         return FALSE;
75 }
76
77 MUTATOR_HOOKFUNCTION(rts_PlayerThink)
78 {
79         if(self.classname != "player")
80                 return FALSE; // dont do any checks for spectators
81                 
82         switch(self.impulse)
83         {
84                 case 10:
85         case 15:        
86         case 18:
87             self.oldorigin_z += 50;
88             break;
89         case 12:
90         case 16:
91         case 19:
92                         self.oldorigin_z -= 50;
93                         break;
94         }
95         self.hasweapon_complain_spam = time + 9999999999; // no spam
96                 
97         entity head, wp = world;
98         if(!self.cursor_trace_ent && self.BUTTON_ATCK && time >= self.last_click)
99         {       
100                 FOR_EACH_MONSTER(head)
101                 {
102                         if(head.owner != self) continue;
103                         
104                         head.selected = FALSE;
105                         
106                         if(!self.enemy)
107                                 head.owner = world;
108                 }
109         }
110         if(self.cursor_trace_ent.flags & FL_MONSTER && self.BUTTON_ATCK && time >= self.last_click)
111         {
112                 if(self.cursor_trace_ent.owner != self && self.cursor_trace_ent.owner != world)
113                         return FALSE; // someone else owns it
114                 else if(self.cursor_trace_ent.team != self.team)
115                         return FALSE; // not our team
116                 else if(self.cursor_trace_ent.selected)
117                 {
118                         self.cursor_trace_ent.selected = FALSE;
119                         self.cursor_trace_ent.owner = world;
120                         self.last_click = time + 0.5; // prevent spamming
121                 }
122                 else
123                 {
124                         self.cursor_trace_ent.owner = self;
125                         self.cursor_trace_ent.selected = TRUE;
126                         self.last_click = time + 0.5; // prevent spamming
127                 }
128         }
129         if(self.BUTTON_ATCK2)
130         {
131                 entity e = self.cursor_trace_ent;
132                 
133                 if not(e)
134                 {
135                         entity t;
136                         for(t = world; (t = findflags(t, turrcaps_flags, TFL_TURRCAPS_ISTURRET)); )
137                         {
138                                 if(vlen(self.cursor_trace_endpos - t.origin) < 80)
139                                 {
140                                         if(IsDifferentTeam(e, t))
141                                         {
142                                                 e = t;
143                                                 break; // don't bother checking any other turrets
144                                         }
145                                 }
146                         }
147                 }
148                 
149                 if(e)
150                 if not(e.takedamage)
151                         e = world;
152                 
153                 if not(e)
154                 {
155                         wp = spawn();
156                         wp.classname = "monster_waypoint"; // set so we can kill this later
157                         wp.owner = self; // hmm...
158                         wp.think = rts_waypoint_think;
159                         wp.nextthink = time;
160                         WaypointSprite_Spawn("Here", 1, 0, wp, '0 0 10', world, self.team, wp, sprite, FALSE, RADARICON_DANGER, ((teamplay) ? TeamColor(self.team) : '1 0 0'));
161                         setorigin(wp, self.cursor_trace_endpos);
162                 }
163                 
164                 FOR_EACH_MONSTER(head)
165                 {
166                         if(head.owner != self) continue;
167                         if not(head.selected) continue;
168                         
169                         if(e)
170                         {
171                                 float sheight = ((e.sprite_height) ? e.sprite_height + 20 : 80);
172                                 if(IsDifferentTeam(e, self))
173                                 {
174                                         WaypointSprite_Spawn("Attacking", 1, 0, e, '0 0 1' * sheight, world, self.team, self, sprite, FALSE, RADARICON_DANGER, ((teamplay) ? TeamColor(self.team) : '1 0 0'));
175                                         head.goalentity = world;
176                                         head.enemy = e;
177                                 }
178                                 else if(e.flags & FL_MONSTER)
179                                 {
180                                         WaypointSprite_Spawn("Following", 1, 0, e, '0 0 1' * sheight, world, self.team, self, sprite, FALSE, RADARICON_DANGER, ((teamplay) ? TeamColor(self.team) : '1 0 0'));
181                                         head.goalentity = e;
182                                 }
183                                 else // its not a monster or an enemy, so revert to waypoint
184                                 {
185                                         head.goalentity = wp;
186                                         head.enemy = world;
187                                 }
188                                         
189                         }
190                         else
191                         {
192                                 head.goalentity = wp;
193                                 head.enemy = world;
194                         }
195                 }
196         }
197
198         return FALSE;
199 }
200
201 MUTATOR_HOOKFUNCTION(rts_MonsterSpawn)
202 {
203         // new monster
204         if not(self.monster_respawned)
205         {
206                 self.level = 0;
207                 self.speed = 1;
208         }
209         
210         self.spawnflags = MONSTERFLAG_NORESPAWN;
211         
212         self.goalentity = world;
213         self.enemy = world;
214         self.moveto = self.origin;
215                 
216         self.respawntime = 10; // default to 10 seconds for now
217         self.effects |= EF_SELECTABLE;
218         self.monster_moveflags = MONSTER_MOVE_NOMOVE;
219         
220         WaypointSprite_Kill(self.sprite);
221         self.sprite = world;
222         self.heal_delay = -1; // this is reset when monster takes damage
223         
224         return FALSE;
225 }
226
227 MUTATOR_HOOKFUNCTION(rts_MonsterThink)
228 {
229         vector color = ((self.team) ? TeamColor(self.team) : '1 1 1');
230                 
231         if(self.health >= self.max_health)
232                 self.heal_delay = -1;   
233         else if(time >= self.heal_delay)
234         {
235                 self.health = min(self.health + 5, self.max_health);
236                 WaypointSprite_UpdateHealth(self.sprite, self.health);
237                 self.heal_delay = time + 2;
238         }
239                 
240         monster_speed_run = 150 * self.speed;
241         monster_speed_walk = 150 * self.speed;
242         
243         if(monster_target.classname == "player")
244                 monster_target = world;
245                 
246         if not(IsDifferentTeam(monster_target, self))
247         {
248                 // following a fellow teammate, so attack their enemy
249                 if(monster_target.deadflag != DEAD_NO || monster_target.health < 1)
250                         monster_target = world; // teammate died
251                         
252                 if(monster_target.enemy)
253                 {
254                         self.enemy = monster_target.enemy;
255                         monster_target = world; // don't follow anymore?
256                 }
257         }
258         
259         if(self.selected)
260                 self.colormod = color * 10;
261         else
262                 self.colormod = color;
263                 
264         if(monster_target)
265                 self.enemy = world; // don't ignore our owner's commands
266         
267         if not(self.sprite)
268         {
269                 WaypointSprite_Spawn(self.netname, 0, 0, self, '0 0 1' * self.sprite_height, world, self.team, self, sprite, FALSE, RADARICON_DANGER, ((teamplay) ? TeamColor(self.team) : '1 0 0'));
270                 WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
271                 WaypointSprite_UpdateHealth(self.sprite, self.health);
272         }
273         
274         if(self.owner)
275         if not(self.selected)
276                 self.owner = world;
277         
278         if not(IsDifferentTeam(self, self.enemy))
279                 self.enemy = world; // no same team fighting
280         
281         self.last_trace = time; // realtime moving?
282                 
283         return FALSE;
284 }
285
286 MUTATOR_HOOKFUNCTION(rts_MonsterDies)
287 {
288         entity e;
289         
290         if(IsDifferentTeam(frag_attacker, frag_target) && frag_attacker.team)
291                 TeamScore_AddToTeam(frag_attacker.team, ST_SCORE, 1);
292         
293         // need to keep the monster selected to get the points... hmm (TODO: realowners?)
294         if(frag_attacker.owner.classname == "player")
295         {
296                 PlayerScore_Add(frag_attacker.owner, SP_SCORE, 5);
297                 PlayerScore_Add(frag_attacker.owner, SP_KILLS, 1);
298         }
299                 
300         if(frag_attacker.flags & FL_MONSTER)
301         {
302                 frag_attacker.monster_score += 5;
303                 if(frag_attacker.monster_score == 25)
304                         Monster_LevelUp(frag_attacker);
305         }
306         
307         for(e = world; (e = findentity(e, goalentity, self)); )
308         {
309                 e.goalentity = world; // fix teammates if they still see us as a valid target
310         }
311
312         self.effects &~= EF_SELECTABLE;
313         self.selected = FALSE;
314         
315         self.goalentity = world;
316         self.enemy = world;
317         
318         return FALSE;
319 }
320
321 MUTATOR_HOOKFUNCTION(rts_MonsterRespawn)
322 {
323         if(other.team)
324                 return TRUE;
325                 
326         return FALSE; // if no team is set, don't respawn
327 }
328
329 MUTATOR_HOOKFUNCTION(rts_MonsterTarget)
330 {
331         // don't search for enemies, they are given to us
332         return TRUE;
333 }
334
335 MUTATOR_HOOKFUNCTION(rts_MonsterBossFlag)
336 {
337         // no minibosses in RTS
338         return TRUE;
339 }
340
341 MUTATOR_HOOKFUNCTION(rts_PlayerDamage)
342 {
343         if(frag_target.classname == "player")
344                 frag_damage = 0; // don't damage the invincible players...
345                 
346         if((frag_target.flags & FL_MONSTER) && frag_target.goalentity)
347                 frag_target.enemy = world; // don't attack the attacker, we're probably pulling back
348                 
349         if((frag_target.flags & FL_MONSTER) && !IsDifferentTeam(frag_target, frag_attacker))
350                 frag_damage = 0; // no team damage
351                 
352         if((frag_target.flags & FL_MONSTER) && frag_damage > 0)
353                 frag_target.heal_delay = time + 2; // reset delay whenever hurt
354                 
355         return FALSE;
356 }
357
358 MUTATOR_HOOKFUNCTION(rts_PlayerPhysics)
359 {
360         if(self.classname != "player")
361                 return FALSE;
362                 
363         self.origin_z = self.oldorigin_z;
364         self.stat_sv_maxspeed *= 4; // lol
365         
366         return FALSE;
367 }
368
369 MUTATOR_HOOKFUNCTION(rts_PlayerDies)
370 {
371         // prevent changing teams with selected monsters
372         entity head;
373         FOR_EACH_MONSTER(head)
374         {
375                 if(head.owner != self) continue;
376                 if not(head.selected) continue;
377                 
378                 if(IsDifferentTeam(self, head))
379                 {
380                         head.selected = FALSE;
381                         head.owner = world;
382                 }
383         }
384         
385         return FALSE;
386 }
387
388 void rts_ScoreRules()
389 {
390         ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, TRUE);
391         ScoreRules_basics_end();
392 }
393
394 void rts_DelayedInit()
395 {
396         rts_ScoreRules();
397 }
398
399 void rts_Initialize()
400 {
401         InitializeEntity(world, rts_DelayedInit, INITPRIO_GAMETYPE);
402 }
403
404 MUTATOR_DEFINITION(gamemode_rts)
405 {
406         MUTATOR_HOOK(PlayerPhysics, rts_PlayerPhysics, CBC_ORDER_ANY);
407         MUTATOR_HOOK(PlayerSpawn, rts_PlayerSpawn, CBC_ORDER_ANY);
408         MUTATOR_HOOK(SetStartItems, rts_SetStartItems, CBC_ORDER_ANY);
409         MUTATOR_HOOK(FilterItem, rts_FilterItem, CBC_ORDER_ANY);
410         MUTATOR_HOOK(MonsterSpawn, rts_MonsterSpawn, CBC_ORDER_ANY);
411         MUTATOR_HOOK(PlayerPreThink, rts_PlayerThink, CBC_ORDER_ANY);
412         MUTATOR_HOOK(MonsterMove, rts_MonsterThink, CBC_ORDER_ANY);
413         MUTATOR_HOOK(MonsterFindTarget, rts_MonsterTarget, CBC_ORDER_ANY);
414         MUTATOR_HOOK(MonsterDies, rts_MonsterDies, CBC_ORDER_ANY);
415         MUTATOR_HOOK(MonsterRespawn, rts_MonsterRespawn, CBC_ORDER_ANY);
416         MUTATOR_HOOK(MonsterCheckBossFlag, rts_MonsterBossFlag, CBC_ORDER_ANY);
417         MUTATOR_HOOK(PlayerDamage_Calculate, rts_PlayerDamage, CBC_ORDER_ANY);
418         MUTATOR_HOOK(PlayerDies, rts_PlayerDies, CBC_ORDER_ANY);
419         
420         MUTATOR_ONADD
421         {
422                 if(time > 1) // game loads at time 1
423                         error("This is a game type and it cannot be added at runtime.");        
424                 cvar_settemp("g_monsters", "1");
425                 
426                 rts_Initialize();
427         }
428
429         MUTATOR_ONREMOVE
430         {
431                 error("This is a game type and it cannot be removed at runtime.");
432         }
433
434         return FALSE;
435 }