]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/monsters/m_monsters.qc
Merge commit 'origin/master' into diabolik/erebusnyxplayermodel
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / monsters / m_monsters.qc
1 /* ALL MONSTERS SHOULD BE 1 0 0 IN COLOR */
2
3 // name =[framenum,     nexttime, nextthink] {code}
4 // expands to:
5 // name ()
6 // {
7 //              self.frame=framenum;
8 //              self.nextthink = time + nexttime;
9 //              self.think = nextthink
10 //              <code>
11 // };
12
13 .float ismonster;
14
15 .float modelindex2;
16
17 /*
18 ================
19 monster_use
20
21 Using a monster makes it angry at the current activator
22 LordHavoc: using a monster with the spawnflag 'Appear' makes it appear
23 ================
24 */
25 void() monster_use =
26 {
27         if (self.enemy)
28                 return;
29         if (self.health < 1)
30                 return;
31         if (self.mdl)
32         if (self.spawnflags & MONSTER_APPEAR)
33         {
34                 self.nextthink = time + 0.1;
35                 self.spawnflags = self.spawnflags - MONSTER_APPEAR;
36                 self.solid = SOLID_SLIDEBOX;
37                 self.takedamage = DAMAGE_AIM;
38                 //self.movetype = MOVETYPE_STEP;
39                 self.model = self.mdl;
40                 self.mdl = "";
41                 self.modelindex = self.modelindex2;
42                 self.modelindex2 = 0;
43                 //setorigin(self, self.origin + '0 0 1');
44                 spawn_tdeath(self.origin, self, self.origin);
45                 return;
46         }
47
48 #if 0
49         if (activator.items & IT_INVISIBILITY)
50                 return;
51 #endif
52         if (activator.flags & FL_NOTARGET)
53                 return;
54         if (activator.classname != "player")
55                 return;
56
57         // delay reaction so if the monster is teleported, its sound is still heard
58         self.enemy = activator;
59         self.nextthink = time + 0.1;
60         self.think = FoundTarget;
61 };
62
63 void() monster_appearsetup =
64 {
65         if ((self.spawnflags & MONSTER_APPEAR) == 0)
66                 return;
67         self.mdl = self.model;
68         self.modelindex2 = self.modelindex;
69         self.modelindex = 0;
70         self.solid = SOLID_NOT;
71         self.takedamage = DAMAGE_NO;
72         //self.movetype = MOVETYPE_NONE;
73         self.nextthink = -1;
74         self.model = "";
75 };
76
77 /*
78 ================
79 monster_setalpha
80
81 Sets relative alpha of monster in skill 4 mode.
82 ================
83 */
84 void(float a) monster_setalpha =
85 {
86         if (skill < 4 || self.classname == "monster_hellfish")
87         {
88                 self.alpha = 1.0;
89                 return;
90         }
91
92         if (skill >= 5)
93         {
94                 // randomly forget enemy, this makes monsters randomly return to their normal ghostlike state
95                 if (a == 0)
96                 if (self.enemy)
97                 if (random() < 0.1)
98                         self.enemy = world;
99                 // randomly blink (playing the same alarming sound as if attacking)
100                 if (self.enemy == world)
101                 {
102                         a = 0;
103                         if (time >= 0.3) // don't blink during the init process because it might become permanent
104                         if (random() < 0.005)
105                         {
106                                 // blink for an instant, this causes the appear sound, alarming the player as if under attack
107                                 sound(self, CHAN_AUTO, "wizard/wsight.wav", 1, ATTN_NORM);
108                                 a = 1;
109                         }
110                 }
111                 // if ghosted, become non-solid and immune to damage
112                 if (a <= 0 || self.enemy == world)
113                 {
114                         self.solid = SOLID_NOT;
115                         self.takedamage = DAMAGE_NO;
116                 }
117                 else
118                 {
119                         // if unghosting, make sure we have an enemy, otherwise stay ghosted (even if blinking) so we can't be shot while blinking
120                         if (self.solid != SOLID_SLIDEBOX)
121                                 sound(self, CHAN_AUTO, "wizard/wsight.wav", 1, ATTN_NORM);
122                         self.solid = SOLID_SLIDEBOX;
123                         self.takedamage = DAMAGE_AIM;
124                 }
125         }
126         self.alpha = SKILL4_MINALPHA + (1 - SKILL4_MINALPHA) * bound(0, a, 1);
127 };
128
129 /*
130 ================
131 monster_death_use
132
133 When a mosnter dies, it fires all of its targets with the current
134 enemy as activator.
135 ================
136 */
137 void() monster_death_use =
138 {
139 // fall to ground
140         if (self.flags & FL_FLY)
141                 self.flags = self.flags - FL_FLY;
142         if (self.flags & FL_SWIM)
143                 self.flags = self.flags - FL_SWIM;
144
145         if (!self.target)
146                 return;
147
148         activator = self.enemy;
149         SUB_UseTargets ();
150 };
151
152
153 void() monsterinwall =
154 {
155         local entity e;
156         if (!cvar("developer"))
157                 return;
158         // this is handy for level designers,
159         // puts a spikey ball where the error is...
160         e = spawn();
161         setorigin(e, self.origin);
162         setmodel (e, "models/ebomb.mdl");
163         e.movetype = MOVETYPE_NONE;
164         e.solid = SOLID_NOT;
165         e.think = SUB_Null;
166         e.nextthink = -1;
167         e.scale = 16;
168 };
169
170 //============================================================================
171
172 void() walkmonster_start_go =
173 {
174         self.origin_z = self.origin_z + 1; // raise off floor a bit
175
176         tracebox(self.origin, self.mins, self.maxs, self.origin, TRUE, self);
177         if (trace_startsolid)
178         {
179                 dprint("walkmonster in wall at: ");
180                 dprint(vtos(self.origin));
181                 dprint("\n");
182                 monsterinwall();
183                 droptofloor();
184         }
185         else
186         {
187                 droptofloor();
188                 if (!walkmove(0,0))
189                 {
190                         dprint("walkmonster in wall at: ");
191                         dprint(vtos(self.origin));
192                         dprint("\n");
193                         monsterinwall();
194                 }
195         }
196
197         //self.cantrigger = TRUE;
198
199         self.takedamage = DAMAGE_AIM;
200
201         self.ideal_yaw = self.angles * '0 1 0';
202         if (!self.yaw_speed)
203                 self.yaw_speed = 20;
204         self.view_ofs = '0 0 25';
205         self.use = monster_use;
206
207         self.flags = self.flags | FL_MONSTER;
208
209         if (monsterwander)
210                 self.spawnflags = self.spawnflags | MONSTER_WANDER;
211
212         if (self.target)
213         {
214                 self.goalentity = self.movetarget = find(world, targetname, self.target);
215                 self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin);
216                 if (!self.movetarget)
217                 {
218                         dprint("Monster can't find target at ");
219                         dprint(vtos(self.origin));
220                         dprint("\n");
221                 }
222                 // this used to be an objerror
223                 if (self.movetarget.classname == "path_corner")
224                         self.th_walk ();
225                 else
226                 {
227                         if ((self.spawnflags & MONSTER_WANDER) && (!self.monsterawaitingteleport) && (self.spawnflags & 3) == 0 && (world.model != "maps/e1m7.bsp"))
228                         {
229                                 monster_spawnwanderpath();
230                                 self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin);
231                                 self.th_walk ();
232                         }
233                         else
234                         {
235                                 self.pausetime = 99999999;
236                                 self.th_stand ();
237                         }
238                 }
239         }
240         else
241         {
242                 if ((self.spawnflags & MONSTER_WANDER) && (!self.monsterawaitingteleport) && (self.spawnflags & 3) == 0 && (world.model != "maps/e1m7.bsp"))
243                 {
244                         monster_spawnwanderpath();
245                         self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin);
246                         self.th_walk ();
247                 }
248                 else
249                 {
250                         self.pausetime = 99999999;
251                         self.th_stand ();
252                 }
253         }
254
255 // spread think times so they don't all happen at same time
256         self.nextthink = self.nextthink + random()*0.5 + 0.1;
257         self.iscreature = TRUE;
258
259         force_retouch = 2; // mainly to detect teleports
260
261         monster_appearsetup();
262 };
263
264
265 void() walkmonster_start =
266 {
267         self.candrown = 1; // this is turned off by some monsters like zombies
268         // delay drop to floor to make sure all doors have been spawned
269         // spread think times so they don't all happen at same time
270         self.nextthink = time + random()*0.5 + 0.3;
271         self.think = walkmonster_start_go;
272         total_monsters = total_monsters + 1;
273         self.bot_attack = TRUE;
274         self.frags = 2; // actually just used to get havocbots to attack it...
275         self.bleedfunc = genericbleedfunc;
276         self.ismonster = TRUE;
277
278         monster_setalpha (0);
279 };
280
281
282
283 void() flymonster_start_go =
284 {
285         self.takedamage = DAMAGE_AIM;
286
287         self.ideal_yaw = self.angles * '0 1 0';
288         if (!self.yaw_speed)
289                 self.yaw_speed = 10;
290         self.view_ofs = '0 0 25';
291         self.use = monster_use;
292
293         self.flags = self.flags | FL_FLY;
294         self.flags = self.flags | FL_MONSTER;
295
296         if (!walkmove(0,0))
297         {
298                 dprint("flymonster in wall at: ");
299                 dprint(vtos(self.origin));
300                 dprint("\n");
301                 monsterinwall();
302         }
303
304         //self.cantrigger = TRUE;
305
306         if (monsterwander)
307                 self.spawnflags = self.spawnflags | MONSTER_WANDER;
308
309         if (self.target)
310         {
311                 self.goalentity = self.movetarget = find(world, targetname, self.target);
312                 if (!self.movetarget)
313                 {
314                         dprint("Monster can't find target at ");
315                         dprint(vtos(self.origin));
316                         dprint("\n");
317                 }
318                 // this used to be an objerror
319                 if (self.movetarget.classname == "path_corner")
320                         self.th_walk ();
321                 else
322                 {
323                         if ((self.spawnflags & MONSTER_WANDER) && (!self.monsterawaitingteleport) && (self.spawnflags & 3) == 0 && (world.model != "maps/e1m7.bsp"))
324                         {
325                                 monster_spawnwanderpath();
326                                 self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin);
327                                 self.th_walk ();
328                         }
329                         else
330                         {
331                                 self.pausetime = 99999999;
332                                 self.th_stand ();
333                         }
334                 }
335         }
336         else
337         {
338                 if ((self.spawnflags & MONSTER_WANDER) && (!self.monsterawaitingteleport) && (self.spawnflags & 3) == 0 && (world.model != "maps/e1m7.bsp"))
339                 {
340                         monster_spawnwanderpath();
341                         self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin);
342                         self.th_walk ();
343                 }
344                 else
345                 {
346                         self.pausetime = 99999999;
347                         self.th_stand ();
348                 }
349         }
350         self.iscreature = TRUE;
351
352         force_retouch = 2; // mainly to detect teleports
353
354         monster_appearsetup();
355 };
356
357 void() flymonster_start =
358 {
359         self.candrown = 1;
360         // spread think times so they don't all happen at same time
361         self.nextthink = time + random()*0.5 + 0.1;
362         self.think = flymonster_start_go;
363         total_monsters = total_monsters + 1;
364         self.bot_attack = TRUE;
365         self.frags = 2; // actually just used to get havocbots to attack it...
366         self.bleedfunc = genericbleedfunc;
367         self.ismonster = TRUE;
368
369         monster_setalpha (0);
370 };
371
372
373 void() swimmonster_start_go =
374 {
375         if (deathmatch)
376         {
377                 remove(self);
378                 return;
379         }
380
381         //self.cantrigger = TRUE;
382
383         self.takedamage = DAMAGE_AIM;
384
385         self.ideal_yaw = self.angles * '0 1 0';
386         if (!self.yaw_speed)
387                 self.yaw_speed = 10;
388         self.view_ofs = '0 0 10';
389         self.use = monster_use;
390
391         self.flags = self.flags | FL_SWIM;
392         self.flags = self.flags | FL_MONSTER;
393
394         if (monsterwander)
395                 self.spawnflags = self.spawnflags | MONSTER_WANDER;
396
397         if (self.target)
398         {
399                 self.goalentity = self.movetarget = find(world, targetname, self.target);
400                 if (!self.movetarget)
401                 {
402                         dprint("Monster can't find target at ");
403                         dprint(vtos(self.origin));
404                         dprint("\n");
405                 }
406                 // this used to be an objerror
407                 if (self.movetarget.classname == "path_corner")
408                         self.th_walk ();
409                 else
410                 {
411                         if ((self.spawnflags & MONSTER_WANDER) && (!self.monsterawaitingteleport) && (self.spawnflags & 3) == 0 && (world.model != "maps/e1m7.bsp"))
412                         {
413                                 monster_spawnwanderpath();
414                                 self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin);
415                                 self.th_walk ();
416                         }
417                         else
418                         {
419                                 self.pausetime = 99999999;
420                                 self.th_stand ();
421                         }
422                 }
423         }
424         else
425         {
426                 if ((self.spawnflags & MONSTER_WANDER) && (!self.monsterawaitingteleport) && (self.spawnflags & 3) == 0 && (world.model != "maps/e1m7.bsp"))
427                 {
428                         monster_spawnwanderpath();
429                         self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin);
430                         self.th_walk ();
431                 }
432                 else
433                 {
434                         self.pausetime = 99999999;
435                         self.th_stand ();
436                 }
437         }
438         self.iscreature = TRUE;
439
440         force_retouch = 2; // mainly to detect teleports
441
442         monster_appearsetup();
443 };
444
445 void() swimmonster_start =
446 {
447         // spread think times so they don't all happen at same time
448         self.candrown = 0;
449         self.nextthink = time + random()*0.5 + 0.1;
450         self.think = swimmonster_start_go;
451         total_monsters = total_monsters + 1;
452         self.bot_attack = TRUE;
453         self.frags = 2; // actually just used to get havocbots to attack it...
454         self.bleedfunc = genericbleedfunc;
455         self.ismonster = TRUE;
456
457         monster_setalpha(0);
458 };
459
460 void(vector org, float bodydamage, float armordamage, vector force, float damgtype) genericbleedfunc =
461 {
462         local vector v;
463         v = '0 0 0' - force * 0.05;
464         if (armordamage > 0)
465                 te_spark(org, v, armordamage * 3);
466         if (bodydamage > 0)
467                 te_blood(org, v, bodydamage);
468 }