Xonotic QuakeC
The free, fast arena FPS with crisp movement and a wide array of weapons
sv_monsters.qc
Go to the documentation of this file.
1#include "sv_monsters.qh"
2
4#include <common/constants.qh>
13#include <common/stats.qh>
14#include <common/teams.qh>
17#include <common/util.qh>
23#include <server/campaign.qh>
24#include <server/cheats.qh>
25#include <server/client.qh>
27#include <server/damage.qh>
28#include <server/items/items.qh>
31#include <server/steerlib.qh>
33
35{
36 STAT(MONSTERS_TOTAL, this) = monsters_total;
37 STAT(MONSTERS_KILLED, this) = monsters_killed;
38}
39
40void monster_dropitem(entity this, entity attacker)
41{
43 return;
44
45 // TODO: mapper customization (different field?)
46 string itemlist = this.monster_loot;
47
50
51 MUTATOR_CALLHOOK(MonsterDropItem, this, itemlist, attacker);
52 itemlist = M_ARGV(1, string);
53
54 entity loot_itemdef = Item_RandomFromList(itemlist);
55 if (!loot_itemdef)
56 return;
57
58 entity e = spawn();
59 e.monster_item = true;
60 ITEM_SET_LOOT(e, true);
61 e.colormap = this.colormap;
62 e.itemdef = loot_itemdef;
63 setorigin(e, CENTER_OR_VIEWOFS(this));
64 e.velocity = randomvec() * 175 + '0 0 325';
66
68}
69
71{
72 // relies on target having an origin
73 makevectors(this.angles);
74 vector targ_org = targ.origin, my_org = this.origin;
76 {
77 targ_org = vec2(targ_org);
78 my_org = vec2(my_org);
79 }
80 float dot = normalize(targ_org - my_org) * v_forward;
81
83}
84
86{
87 if(IS_MONSTER(this))
88 {
89 vector v = targ.origin + (targ.mins + targ.maxs) * 0.5;
90 this.v_angle = vectoangles(v - (this.origin + this.view_ofs));
91 this.v_angle_x = -this.v_angle_x;
92 }
93
94 makevectors(this.v_angle);
95}
96
97// ===============
98// Target handling
99// ===============
100
101bool Monster_ValidTarget(entity this, entity targ, bool skipfacing)
102{
103 // ensure we're not checking nonexistent monster/target
104 if(!this || !targ) { return false; }
105
106 if((targ == this)
107 || (IS_VEHICLE(targ) && !(this.monsterdef.spawnflags & MON_FLAG_RANGED)) // melee vs vehicle is useless
108 || (time < game_starttime) // monsters do nothing before match has started
109 || (targ.takedamage == DAMAGE_NO)
110 || (game_stopped)
111 || (IS_SPEC(targ) || IS_OBSERVER(targ)) // don't attack spectators
112 || (!IS_VEHICLE(targ) && (IS_DEAD(targ) || IS_DEAD(this) || GetResource(targ, RES_HEALTH) <= 0 || GetResource(this, RES_HEALTH) <= 0))
113 || (this.monster_follow == targ || targ.monster_follow == this)
114 || (!IS_VEHICLE(targ) && (targ.flags & FL_NOTARGET))
116 || (SAME_TEAM(targ, this))
117 || (targ.alpha != 0 && targ.alpha < 0.5)
118 || (autocvar_g_monsters_lineofsight && !checkpvs(this.origin + this.view_ofs, targ)) // enemy cannot be seen
119 || (MUTATOR_CALLHOOK(MonsterValidTarget, this, targ))
120 )
121 {
122 // if any of the above checks fail, target is not valid
123 return false;
124 }
125
126 vector targ_origin = ((targ.absmin + targ.absmax) * 0.5);
127 traceline(this.origin + this.view_ofs, targ_origin, MOVE_NOMONSTERS, this); // TODO: maybe we can rely a bit on PVS data instead?
128
129 if(trace_fraction < 1 && trace_ent != targ)
130 return false; // solid
131
133 if(this.enemy != targ)
134 {
135 if(!monster_facing(this, targ))
136 return false;
137 }
138
139 return true; // this target is valid!
140}
141
143{
144 if(MUTATOR_CALLHOOK(MonsterFindTarget)) { return this.enemy; } // Handled by a mutator
145
146 entity closest_target = NULL;
147 vector my_center = CENTER_OR_VIEWOFS(this);
148
149 // find the closest acceptable target to pass to
150 IL_EACH(g_monster_targets, it.monster_attack,
151 {
152 float trange = this.target_range;
153 if(PHYS_INPUT_BUTTON_CROUCH(it))
154 trange *= 0.75; // TODO cvar this
155 vector theirmid = (it.absmin + it.absmax) * 0.5;
156 if(vdist(theirmid - this.origin, >, trange))
157 continue;
158 if(!Monster_ValidTarget(this, it, false))
159 continue;
160
161 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
162 vector targ_center = CENTER_OR_VIEWOFS(it);
163
164 if(closest_target)
165 {
166 vector closest_target_center = CENTER_OR_VIEWOFS(closest_target);
167 if(vlen2(my_center - targ_center) < vlen2(my_center - closest_target_center))
168 { closest_target = it; }
169 }
170 else { closest_target = it; }
171 });
172
173 return closest_target;
174}
175
177{
178 if(teamplay && this.team)
179 this.colormap = 1024 + (this.team - 1) * 17;
180 else if(IS_PLAYER(this.realowner))
181 this.colormap = this.realowner.colormap;
182 else
183 {
185 this.colormap = 1126;
186 else if(this.monster_skill <= MONSTER_SKILL_MEDIUM)
187 this.colormap = 1075;
188 else if(this.monster_skill <= MONSTER_SKILL_HARD)
189 this.colormap = 1228;
190 else if(this.monster_skill <= MONSTER_SKILL_INSANE)
191 this.colormap = 1092;
193 this.colormap = 1160;
194 else
195 this.colormap = 1024;
196 }
197
198 if(this.colormap > 0)
199 this.glowmod = colormapPaletteColor(this.colormap & 0x0F, false);
200 else
201 this.glowmod = '1 1 1';
202}
203
204void monster_changeteam(entity this, int newteam)
205{
206 if(!teamplay) { return; }
207
208 this.team = newteam;
209 if(!this.monster_attack)
211 this.monster_attack = true; // new team, activate attacking
213
214 if(this.sprite)
215 {
216 WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DANGER, ((newteam) ? Team_ColorRGB(newteam) : '1 0 0'));
217
218 this.sprite.team = newteam;
219 this.sprite.SendFlags |= 1;
220 }
221}
222
223.void(entity) monster_delayedfunc;
225{
226 // TODO: maybe do check for facing here
227 if(Monster_ValidTarget(this.owner, this.owner.enemy, false))
228 {
229 monster_makevectors(this.owner, this.owner.enemy);
230 this.monster_delayedfunc(this.owner);
231 }
232
233 if(this.cnt > 1)
234 {
235 this.cnt -= 1;
237 this.nextthink = time + this.count;
238 }
239 else
240 {
241 setthink(this, SUB_Remove);
242 this.nextthink = time;
243 }
244}
245
246void Monster_Delay(entity this, int repeat_count, float defer_amnt, void(entity) func)
247{
248 // deferred attacking, checks if monster is still alive and target is still valid before attacking
250
252 e.nextthink = time + defer_amnt;
253 e.count = defer_amnt;
254 e.owner = this;
255 e.monster_delayedfunc = func;
256 e.cnt = repeat_count;
257}
258
259
260// ==============
261// Monster sounds
262// ==============
263
264string get_monster_model_datafilename(string m, float sk, string fil)
265{
266 if(m)
267 m = strcat(m, "_");
268 else
269 m = "models/monsters/*_";
270 if(sk >= 0)
271 m = strcat(m, ftos(sk));
272 else
273 m = strcat(m, "*");
274 return strcat(m, ".", fil);
275}
276
278{
279 float fh;
280 string s;
281 fh = fopen(f, FILE_READ);
282 if(fh < 0)
283 return;
284 while((s = fgets(fh)))
285 {
286 if(tokenize_console(s) != 3)
287 {
288 //LOG_DEBUG("Invalid sound info line: ", s); // probably a comment, no need to spam warnings
289 continue;
290 }
291 PrecacheGlobalSound(strcat(argv(1), " ", argv(2)));
292 }
293 fclose(fh);
294}
295
297{
298 string m = this.monsterdef.m_model.model_str();
299 float globhandle, n, i;
300 string f;
301
302 globhandle = search_begin(strcat(m, "_*.sounds"), true, false);
303 if (globhandle < 0)
304 return;
305 n = search_getsize(globhandle);
306 for (i = 0; i < n; ++i)
307 {
308 //print(search_getfilename(globhandle, i), "\n");
309 f = search_getfilename(globhandle, i);
311 }
312 search_end(globhandle);
313}
314
316{
317#define _MSOUND(m) strfree(this.monstersound_##m);
319#undef _MSOUND
320}
321
322.string Monster_Sound_SampleField(string type)
323{
325 switch(type)
326 {
327#define _MSOUND(m) case #m: return monstersound_##m;
329#undef _MSOUND
330 }
332 return string_null;
333}
334
335bool Monster_Sounds_Load(entity this, string f, int first)
336{
337 string s;
338 var .string field;
339 float fh = fopen(f, FILE_READ);
340 if(fh < 0)
341 {
342 //LOG_DEBUG("Monster sound file not found: ", f); // no biggie, monster has no sounds, let's not spam it
343 return false;
344 }
345 while((s = fgets(fh)))
346 {
347 if(tokenize_console(s) != 3)
348 continue;
351 continue;
352 strcpy(this.(field), strcat(argv(1), " ", argv(2)));
353 }
354 fclose(fh);
355 return true;
356}
357
360{
361 if(this.skin == this.skin_for_monstersound) { return; }
362
363 this.skin_for_monstersound = this.skin;
365 if(!Monster_Sounds_Load(this, get_monster_model_datafilename(this.model, this.skin, "sounds"), 0))
366 Monster_Sounds_Load(this, get_monster_model_datafilename(this.model, 0, "sounds"), 0);
367}
368
369void Monster_Sound(entity this, .string samplefield, float sound_delay, bool delaytoo, float chan)
370{
371 if(!autocvar_g_monsters_sounds) { return; }
372
373 if(delaytoo)
374 if(time < this.msound_delay)
375 return; // too early
376 string sample = this.(samplefield);
377 if (sample != "") sample = GlobalSound_sample(sample, random());
378 float myscale = ((this.scale) ? this.scale : 1); // safety net
379 sound7(this, chan, sample, VOL_BASE, ATTEN_NORM, 100 / myscale, 0);
380
381 this.msound_delay = time + sound_delay;
382}
383
384
385// =======================
386// Monster attack handlers
387// =======================
388
389bool Monster_Attack_Melee(entity this, entity targ, float damg, vector anim, float er, float animtime, int deathtype, bool dostop)
390{
391 if(dostop && IS_MONSTER(this)) { this.state = MONSTER_ATTACK_MELEE; }
392
393 setanim(this, anim, false, true, false);
394
395 if(this.animstate_endtime > time && IS_MONSTER(this))
397 else
398 this.attack_finished_single[0] = this.anim_finished = time + animtime;
399
400 traceline(this.origin + this.view_ofs, this.origin + v_forward * er, 0, this);
401
402 if(trace_ent.takedamage)
403 Damage(trace_ent, this, this, damg * MONSTER_SKILLMOD(this), deathtype, DMG_NOWEP, trace_ent.origin, normalize(trace_ent.origin - this.origin));
404
405 return true;
406}
407
409{
410 if(this.state && IS_MONSTER(this))
411 return false; // already attacking
412 if(!IS_ONGROUND(this))
413 return false; // not on the ground
414 if(GetResource(this, RES_HEALTH) <= 0 || IS_DEAD(this))
415 return false; // called when dead?
416 if(time < this.attack_finished_single[0])
417 return false; // still attacking
418
419 vector old = this.velocity;
420
421 this.velocity = vel;
422 tracetoss(this, this);
423 this.velocity = old;
424 if(trace_ent != this.enemy)
425 return false;
426
427 return true;
428}
429
430bool Monster_Attack_Leap(entity this, vector anm, void(entity this, entity toucher) touchfunc, vector vel, float animtime)
431{
432 if(!Monster_Attack_Leap_Check(this, vel))
433 return false;
434
435 setanim(this, anm, false, true, false);
436
437 if(this.animstate_endtime > time && IS_MONSTER(this))
439 else
440 this.attack_finished_single[0] = this.anim_finished = time + animtime;
441
442 if(IS_MONSTER(this))
444 settouch(this, touchfunc);
445 this.origin_z += 1;
446 this.velocity = vel;
447 UNSET_ONGROUND(this);
448
449 return true;
450}
451
452void Monster_Attack_Check(entity this, entity targ, .entity weaponentity)
453{
454 int slot = weaponslot(weaponentity);
455
456 if((!this || !targ)
457 || (!this.monster_attackfunc)
458 || (game_stopped)
459 || (time < this.attack_finished_single[slot])
461 ) { return; }
462
463 if(vdist(targ.origin - this.origin, <=, this.attack_range))
464 {
465 monster_makevectors(this, targ);
466 int attack_success = this.monster_attackfunc(MONSTER_ATTACK_MELEE, this, targ, weaponentity);
467 if(attack_success == 1)
468 Monster_Sound(this, monstersound_melee, 0, false, CH_VOICE);
469 else if(attack_success > 0)
470 return;
471 }
472
473 if(vdist(targ.origin - this.origin, >, this.attack_range))
474 {
475 monster_makevectors(this, targ);
476 int attack_success = this.monster_attackfunc(MONSTER_ATTACK_RANGED, this, targ, weaponentity);
477 if(attack_success == 1)
478 Monster_Sound(this, monstersound_melee, 0, false, CH_VOICE);
479 else if(attack_success > 0)
480 return;
481 }
482}
483
484
485// ======================
486// Main monster functions
487// ======================
488
490{
491 // assume some defaults
492 /*this.anim_idle = animfixfps(this, '0 1 0.01', '0 0 0');
493 this.anim_walk = animfixfps(this, '1 1 0.01', '0 0 0');
494 this.anim_run = animfixfps(this, '2 1 0.01', '0 0 0');
495 this.anim_fire1 = animfixfps(this, '3 1 0.01', '0 0 0');
496 this.anim_fire2 = animfixfps(this, '4 1 0.01', '0 0 0');
497 this.anim_melee = animfixfps(this, '5 1 0.01', '0 0 0');
498 this.anim_pain1 = animfixfps(this, '6 1 0.01', '0 0 0');
499 this.anim_pain2 = animfixfps(this, '7 1 0.01', '0 0 0');
500 this.anim_die1 = animfixfps(this, '8 1 0.01', '0 0 0');
501 this.anim_die2 = animfixfps(this, '9 1 0.01', '0 0 0');*/
502
503 // then get the real values
504 Monster mon = this.monsterdef;
505 mon.mr_anim(mon, this);
506}
507
509{
510 if(!toucher) { return; }
511
512 if(toucher.monster_attack && this.enemy != toucher && !IS_MONSTER(toucher) && time >= this.spawn_time)
513 if(Monster_ValidTarget(this, toucher, true))
514 this.enemy = toucher;
515}
516
518{
520 {
521 // already performed initial setup, just reapply statuses as needed
523 this.effects |= EF_RED;
524 return;
525 }
526
527 if(MUTATOR_CALLHOOK(MonsterCheckBossFlag, this))
528 {
529 // prevent other code from identifying the monster as a miniboss
531 return;
532 }
533
534 // g_monsters_miniboss_chance cvar or spawnflags 64 causes a monster to be a miniboss
536 {
538 this.effects |= EF_RED;
539 this.spawnflags |= MONSTERFLAG_MINIBOSS; // identifier for other code
540 }
541}
542
544{
545 if(this.deadflag == DEAD_DEAD) // don't call when monster isn't dead
546 if(MUTATOR_CALLHOOK(MonsterRespawn, this))
547 return true; // enabled by a mutator
548
550 return false;
551
553 return false;
554
555 return true;
556}
557
558void Monster_Respawn(entity this) { Monster_Spawn(this, true, this.monsterdef); }
559
560.vector pos1, pos2;
561
563{
564 if(Monster_Respawn_Check(this))
565 {
568 this.nextthink = time + this.respawntime;
569 this.monster_lifetime = 0;
572 {
573 this.pos1 = this.origin;
574 this.pos2 = this.angles;
575 }
576 this.event_damage = func_null;
577 this.event_heal = func_null;
578 this.takedamage = DAMAGE_NO;
579 setorigin(this, this.pos1);
580 this.angles = this.pos2;
581 SetResourceExplicit(this, RES_HEALTH, this.max_health);
582 setmodel(this, MDL_Null);
583 }
584 else
585 {
586 // number of monsters spawned with mobspawn command
587 totalspawned -= 1;
588
589 SUB_SetFade(this, time + 3, 1);
590 }
591}
592
593void Monster_Use(entity this, entity actor, entity trigger)
594{
595 if(Monster_ValidTarget(this, actor, true)) { this.enemy = actor; }
596}
597
600{
601 vector pos, ang;
602
603 ang_y = rint(random() * 500);
604 ang_x = this.angles_x;
605 ang_z = this.angles_z;
607 pos = targetorigin + v_forward * this.wander_distance;
608 if(((this.flags & FL_FLY) && (this.spawnflags & MONSTERFLAG_FLY_VERTICAL)) || (this.flags & FL_SWIM))
609 {
610 pos.z = random() * 200;
611 if(random() >= 0.5)
612 pos.z *= -1;
613 }
614 // truncate movement so we don't do walking into walls animations
615 traceline(this.origin + this.view_ofs, pos, MOVE_NORMAL, this);
616 return trace_endpos;
617}
618
620{
621 // enemy is always preferred target
622 if(this.enemy)
623 {
624 vector targ_origin = ((this.enemy.absmin + this.enemy.absmax) * 0.5);
625 targ_origin = WarpZone_RefSys_TransformOrigin(this.enemy, this, targ_origin); // origin of target as seen by the monster (us)
626
627 if(this.enemy)
628 {
629 /*WarpZone_TrailParticles(NULL, particleeffectnum(EFFECT_RED_PASS), this.origin, targ_origin);
630 print("Trace origin: ", vtos(targ_origin), "\n");
631 print("Target origin: ", vtos(this.enemy.origin), "\n");
632 print("My origin: ", vtos(this.origin), "\n"); */
633
635 this.last_trace = time + 1.2;
636 if(this.monster_moveto)
637 return this.monster_moveto; // assumes code is properly setting this when monster has an enemy
638 else
639 return targ_origin;
640 }
641
642 /*makevectors(this.angles);
643 this.monster_movestate = MONSTER_MOVE_ENEMY;
644 this.last_trace = time + 1.2;
645 return this.enemy.origin; */
646 }
647
648 switch(this.monster_moveflags)
649 {
651 {
653 this.last_trace = time + 0.3;
654 if (this.monster_follow)
655 {
656 if (vdist(this.origin - this.monster_follow.origin, <, this.wander_distance))
657 this.last_trace = time + this.wander_delay;
658 return Monster_WanderTarget(this, this.monster_follow.origin);
659 }
660 else
661 return this.origin;
662 }
664 {
666 this.last_trace = time + 2;
667 return this.pos1;
668 }
670 {
671 if(this.monster_moveto)
672 {
673 this.last_trace = time + 0.5;
674 return this.monster_moveto;
675 }
676 else
677 {
679 this.last_trace = time + 2;
680 }
681 return this.origin;
682 }
683 default:
685 {
687 if(this.monster_moveto)
688 {
689 this.last_trace = time + 0.5;
690 return this.monster_moveto;
691 }
692 else if(targ)
693 {
694 this.last_trace = time + 0.5;
695 return targ.origin;
696 }
697 else
698 {
699 this.last_trace = time + this.wander_delay;
700 return Monster_WanderTarget(this, this.origin);
701 }
702 }
703 }
704}
705
706// Check for water/slime/lava and dangerous edges
707// (only when the bot is on the ground or jumping intentionally)
708// returns a number > 0 for danger
709// based on havocbot_checkdanger()
711{
712 float s;
713
714 if(this.flags & (FL_FLY | FL_SWIM))
715 {
716 // Look ahead
717 traceline(this.origin + this.view_ofs, dst_ahead, true, NULL);
718
719 // Only care about the skybox if it's below
720 // bones_was_here: does this even matter when flying/swimming?
721 if (trace_endpos.z < this.origin.z + this.mins.z
723 return 1;
724
725 s = pointcontents(trace_endpos + '0 0 1');
726 if (s != CONTENT_SOLID)
727 {
728 if (s == CONTENT_LAVA || s == CONTENT_SLIME)
729 return 3;
730
731 if (tracebox_hits_trigger_hurt(dst_ahead, this.mins, this.maxs, trace_endpos))
732 {
733 // the traceline check isn't enough but is good as optimization,
734 // when not true (most of the time) this tracebox call is avoided
735 tracebox(this.origin + this.view_ofs, this.mins, this.maxs, dst_ahead, true, this);
736 if (tracebox_hits_trigger_hurt(dst_ahead, this.mins, this.maxs, trace_endpos))
737 return 4;
738 }
739 }
740 }
741 else
742 {
743 vector dst_down = dst_ahead - '0 0 3000';
744
745 // Look downwards
746 traceline(dst_ahead, dst_down, true, NULL);
747 //te_lightning2(NULL, this.origin + this.view_ofs, dst_ahead); // Draw "ahead" look
748 //te_lightning2(NULL, dst_ahead, trace_endpos); // Draw "downwards" look
749 if (trace_endpos.z < this.origin.z + this.mins.z)
750 {
752 return 1;
753
754 // If following an enemy ignore probably-non-fatal falls,
755 // if wandering only ignore small falls.
756 if (trace_endpos.z < (this.origin.z + this.mins.z) - (this.enemy ? 1024 : 100))
757 return 2;
758
759 s = pointcontents(trace_endpos + '0 0 1');
760 if (s != CONTENT_SOLID)
761 {
762 if (s == CONTENT_LAVA || s == CONTENT_SLIME)
763 return 3;
764
765 if (tracebox_hits_trigger_hurt(dst_ahead, this.mins, this.maxs, trace_endpos))
766 {
767 // the traceline check isn't enough but is good as optimization,
768 // when not true (most of the time) this tracebox call is avoided
769 tracebox(dst_ahead, this.mins, this.maxs, dst_down, true, this);
770 if (tracebox_hits_trigger_hurt(dst_ahead, this.mins, this.maxs, trace_endpos))
771 return 4;
772 }
773 }
774 }
775 }
776
777 return 0;
778}
779
780.entity draggedby;
781void Monster_Move(entity this, float runspeed, float walkspeed, float stpspeed)
782{
783 // update goal entity if lost
784 if(this.target2 && this.target2 != "" && this.goalentity.targetname != this.target2)
785 this.goalentity = find(NULL, targetname, this.target2);
786
787 if(StatusEffects_active(STATUSEFFECT_Frozen, this))
788 {
789 movelib_brake_simple(this, stpspeed);
790 setanim(this, this.anim_idle, true, false, false);
791 return; // no physics while frozen!
792 }
793
794 if(this.flags & FL_SWIM)
795 {
797 {
798 if(time >= this.last_trace)
799 {
800 this.last_trace = time + 0.4;
801
802 Damage (this, NULL, NULL, 2, DEATH_DROWN.m_id, DMG_NOWEP, this.origin, '0 0 0');
803 this.angles = '90 90 0';
804 if(random() < 0.5)
805 {
806 this.velocity_y += random() * 50;
807 this.velocity_x -= random() * 50;
808 }
809 else
810 {
811 this.velocity_y -= random() * 50;
812 this.velocity_x += random() * 50;
813 }
814 this.velocity_z += random() * 150;
815 }
816
817
819 //this.velocity_z = -200;
820
821 return;
822 }
823 else if(this.move_movetype == MOVETYPE_BOUNCE)
824 {
825 this.angles_x = 0;
827 }
828 }
829
830 entity targ = this.goalentity;
831
832 if (MUTATOR_CALLHOOK(MonsterMove, this, runspeed, walkspeed, targ)
833 || game_stopped
834 || this.draggedby != NULL
838 || time < this.spawn_time)
839 {
840 runspeed = walkspeed = 0;
841 if(time >= this.spawn_time)
842 setanim(this, this.anim_idle, true, false, false);
843 movelib_brake_simple(this, stpspeed);
844 return;
845 }
846
847 targ = M_ARGV(3, entity);
848 runspeed = bound(0, M_ARGV(1, float) * MONSTER_SKILLMOD(this), runspeed * 2.5); // limit maxspeed to prevent craziness
849 walkspeed = bound(0, M_ARGV(2, float) * MONSTER_SKILLMOD(this), walkspeed * 2.5); // limit maxspeed to prevent craziness
850
851 if(this.monster_follow)
854 this.monster_follow = NULL;
855
856 if(this.state == MONSTER_ATTACK_RANGED && IS_ONGROUND(this))
857 {
858 this.state = 0;
859 settouch(this, Monster_Touch);
860 }
861
862 if(this.state && time >= this.attack_finished_single[0])
863 this.state = 0; // attack is over
864
865// Select destination
866 if(this.state != MONSTER_ATTACK_MELEE) // don't move if set
867 if(time >= this.last_trace || this.enemy) // update enemy or rider instantly
868 this.moveto = Monster_Move_Target(this, targ);
869
870 if(!this.enemy)
871 Monster_Sound(this, monstersound_idle, 7, true, CH_VOICE);
872
873 if(this.state == MONSTER_ATTACK_MELEE)
874 this.moveto = this.origin;
875
876 if(this.enemy && this.enemy.vehicle)
877 runspeed = 0;
878
879 if(!(this.spawnflags & MONSTERFLAG_FLY_VERTICAL) && !(this.flags & FL_SWIM))
880 this.moveto_z = this.origin_z;
881
882// Steer towards destination
883 this.steerto = steerlib_attract2(this, ((this.monster_face) ? this.monster_face : this.moveto), 0.5, 500, 0.95);
884 vector real_angle = vectoangles(this.steerto) - this.angles;
885 float turny = 25;
886 if(this.state == MONSTER_ATTACK_MELEE)
887 turny = 0;
888 if(turny)
889 {
890 turny = bound(turny * -1, shortangle_f(real_angle.y, this.angles.y), turny);
891 this.angles_y += turny;
892 }
893
894// Update velocity
896 float vz = this.velocity_z;
897
898 // Check for danger ahead
899 float bboxwidth = min(this.maxs_x - this.mins_x, this.maxs_y - this.mins_y);
900 int danger = Monster_CheckDanger(this, this.origin + this.view_ofs
901 + (vdist(this.velocity, >, bboxwidth * 5) ? this.velocity * 0.2 : v_forward * bboxwidth));
902
903 if(!danger && !turret_closetotarget(this, this.moveto, 16))
904 {
905 bool do_run = (this.enemy || this.monster_moveto);
906 movelib_move_simple(this, v_forward, ((do_run) ? runspeed : walkspeed), 0.4);
907
908 if(time > this.pain_finished && time > this.anim_finished)
909 if(!this.state)
910 {
911 if(vdist(this.velocity, >, 10))
912 setanim(this, ((do_run) ? this.anim_run : this.anim_walk), true, false, false);
913 else
914 setanim(this, this.anim_idle, true, false, false);
915 }
916 }
917 else
918 {
919 entity e = this.goalentity; //find(NULL, targetname, this.target2);
920 if(e.target2 && e.target2 != "")
921 this.target2 = e.target2;
922 else if(e.target && e.target != "") // compatibility
923 this.target2 = e.target;
924
925 movelib_brake_simple(this, stpspeed);
926 if(time > this.anim_finished && time > this.pain_finished)
927 if(!this.state)
928 if(vdist(this.velocity, <=, 30))
929 setanim(this, this.anim_idle, true, false, false);
930 }
931
932 if(!((this.flags & FL_FLY) || (this.flags & FL_SWIM)))
933 this.velocity_z = vz;
934
935// If this path is dangerous try to find a different one next frame
936 if (danger)
937 {
938 this.last_trace = time + 0.3;
939 this.moveto = Monster_WanderTarget(this, this.origin);
940 }
941}
942
944{
945 if(IS_CLIENT(this))
946 return; // don't remove it?
947
948 if(!this) { return; }
949
950 if(!MUTATOR_CALLHOOK(MonsterRemove, this))
951 Send_Effect(EFFECT_ITEM_PICKUP, this.origin, '0 0 0', 1);
952
953 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
954 {
955 .entity weaponentity = weaponentities[slot];
956 if(this.(weaponentity))
957 delete(this.(weaponentity));
958 }
960 delete(this);
961}
962
964{
965 this.nextthink = time;
966
967 Monster mon = REGISTRY_GET(Monsters, this.monsterid);
968 mon.mr_deadthink(mon, this);
969
970 if(this.monster_lifetime != 0)
971 if(time >= this.monster_lifetime)
972 {
973 Monster_Dead_Fade(this);
974 return;
975 }
976}
977
978void Monster_Appear(entity this, entity actor, entity trigger)
979{
980 this.enemy = actor;
981 Monster_Spawn(this, false, this.monsterdef);
982}
983
984bool Monster_Appear_Check(entity this, Monster monster_id)
985{
986 if(!(this.spawnflags & MONSTERFLAG_APPEAR))
987 return false;
988
989 setthink(this, func_null);
990 this.monsterdef = monster_id; // set so this monster is properly registered (otherwise, normal initialization is used)
991 this.nextthink = 0;
992 this.use = Monster_Appear;
993 this.flags = FL_MONSTER; // set so this monster can get butchered
994
995 return true;
996}
997
999{
1001 {
1002 Monster_Remove(this);
1003 return;
1004 }
1005
1006 setorigin(this, this.pos1);
1007 this.angles = this.pos2;
1008
1009 SetResourceExplicit(this, RES_HEALTH, this.max_health);
1010 this.velocity = '0 0 0';
1011 this.enemy = NULL;
1012 this.goalentity = NULL;
1013 this.attack_finished_single[0] = 0;
1014 this.moveto = this.origin;
1015}
1016
1017void Monster_Dead_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
1018{
1019 TakeResource(this, RES_HEALTH, damage);
1020
1021 Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, this, attacker);
1022
1023 if(GetResource(this, RES_HEALTH) <= -50) // 100 health until gone?
1024 {
1025 Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, this, attacker);
1026
1027 // number of monsters spawned with mobspawn command
1028 totalspawned -= 1;
1029
1030 setthink(this, SUB_Remove);
1031 this.nextthink = time + 0.1;
1032 this.event_damage = func_null;
1033 }
1034}
1035
1036void Monster_Dead(entity this, entity attacker, float gibbed)
1037{
1039 this.nextthink = time;
1040 this.monster_lifetime = time + 5;
1041
1042 monster_dropitem(this, attacker);
1043
1044 Monster_Sound(this, monstersound_death, 0, false, CH_VOICE);
1045
1047 monsters_killed += 1;
1048
1049 if(IS_PLAYER(attacker))
1052
1053 if(gibbed)
1054 {
1055 // number of monsters spawned with mobspawn command
1056 totalspawned -= 1;
1057 }
1058
1059 if(!gibbed && this.mdl_dead && this.mdl_dead != "")
1060 _setmodel(this, this.mdl_dead);
1061
1062 this.event_damage = ((gibbed) ? func_null : Monster_Dead_Damage);
1063 this.event_heal = func_null;
1064 this.solid = SOLID_CORPSE;
1065 this.takedamage = DAMAGE_AIM;
1066 this.deadflag = DEAD_DEAD;
1067 this.enemy = NULL;
1069 this.moveto = this.origin;
1070 settouch(this, Monster_Touch); // reset incase monster was pouncing
1071 this.reset = func_null;
1072 this.state = 0;
1073 this.attack_finished_single[0] = 0;
1074 this.effects = 0;
1076
1077 if(!((this.flags & FL_FLY) || (this.flags & FL_SWIM)))
1078 this.velocity = '0 0 0';
1079
1081
1082 Monster mon = this.monsterdef;
1083 mon.mr_death(mon, this);
1084}
1085
1086void Monster_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
1087{
1088 if((this.spawnflags & MONSTERFLAG_INVINCIBLE) && deathtype != DEATH_KILL.m_id && !ITEM_DAMAGE_NEEDKILL(deathtype))
1089 return;
1090
1091 //if(time < this.pain_finished && deathtype != DEATH_KILL.m_id)
1092 //return;
1093
1094 if(StatusEffects_active(STATUSEFFECT_SpawnShield, this) && deathtype != DEATH_KILL.m_id)
1095 return;
1096
1097 if(deathtype == DEATH_FALL.m_id && this.draggedby != NULL)
1098 return;
1099
1100 vector v = healtharmor_applydamage(100, GetResource(this, RES_ARMOR) / 100, deathtype, damage);
1101 float take = v.x;
1102 //float save = v.y;
1103
1104 Monster mon = this.monsterdef;
1105 take = mon.mr_pain(mon, this, take, attacker, deathtype);
1106
1107 if(take)
1108 {
1109 TakeResource(this, RES_HEALTH, take);
1110 Monster_Sound(this, monstersound_pain, 1.2, true, CH_PAIN);
1111 }
1112
1113 if(this.sprite)
1114 WaypointSprite_UpdateHealth(this.sprite, GetResource(this, RES_HEALTH));
1115
1116 this.dmg_time = time;
1117
1118 if(deathtype != DEATH_DROWN.m_id && deathtype != DEATH_FIRE.m_id && sound_allowed(MSG_BROADCAST, attacker))
1119 spamsound (this, CH_PAIN, SND_BODYIMPACT1, VOL_BASE, ATTEN_NORM); // FIXME: PLACEHOLDER
1120
1121 this.velocity += force * this.damageforcescale;
1122
1123 if(deathtype != DEATH_DROWN.m_id && take)
1124 {
1125 Violence_GibSplash_At(hitloc, force, 2, bound(0, take, 200) / 16, this, attacker);
1126 if (take > 50)
1127 Violence_GibSplash_At(hitloc, force * -0.1, 3, 1, this, attacker);
1128 if (take > 100)
1129 Violence_GibSplash_At(hitloc, force * -0.2, 3, 1, this, attacker);
1130 }
1131
1132 if(GetResource(this, RES_HEALTH) <= 0)
1133 {
1134 if(deathtype == DEATH_KILL.m_id)
1135 this.candrop = false; // killed by mobkill command
1136
1137 // TODO: fix this?
1138 SUB_UseTargets(this, attacker, this.enemy);
1139 this.target2 = this.oldtarget2; // reset to original target on death, incase we respawn
1140
1141 Monster_Dead(this, attacker, (GetResource(this, RES_HEALTH) <= -100 || deathtype == DEATH_KILL.m_id));
1142
1144
1145 MUTATOR_CALLHOOK(MonsterDies, this, attacker, deathtype);
1146
1147 if(GetResource(this, RES_HEALTH) <= -100 || deathtype == DEATH_KILL.m_id) // check if we're already gibbed
1148 {
1149 Violence_GibSplash(this, 1, 0.5, attacker);
1150
1151 setthink(this, SUB_Remove);
1152 this.nextthink = time + 0.1;
1153 }
1154 }
1155}
1156
1157bool Monster_Heal(entity targ, entity inflictor, float amount, float limit)
1158{
1159 float true_limit = ((limit != RES_LIMIT_NONE) ? limit : targ.max_health);
1160 if(GetResource(targ, RES_HEALTH) <= 0 || GetResource(targ, RES_HEALTH) >= true_limit)
1161 return false;
1162
1163 GiveResourceWithLimit(targ, RES_HEALTH, amount, true_limit);
1164 if(targ.sprite)
1165 WaypointSprite_UpdateHealth(targ.sprite, GetResource(targ, RES_HEALTH));
1166 return true;
1167}
1168
1169// don't check for enemies, just keep walking in a straight line
1170void Monster_Move_2D(entity this, float mspeed, bool allow_jumpoff)
1171{
1173 {
1174 mspeed = 0;
1175 if(time >= this.spawn_time)
1176 setanim(this, this.anim_idle, true, false, false);
1177 movelib_brake_simple(this, 0.6);
1178 return;
1179 }
1180
1181 makevectors(this.angles);
1182 vector a = CENTER_OR_VIEWOFS(this);
1183 vector b = CENTER_OR_VIEWOFS(this) + v_forward * 32;
1184
1185 traceline(a, b, MOVE_NORMAL, this);
1186
1187 bool reverse = false;
1188 if(trace_fraction != 1.0)
1189 reverse = true;
1191 reverse = false;
1193 reverse = true;
1194
1195 if(!allow_jumpoff && IS_ONGROUND(this))
1196 {
1197 traceline(b, b - '0 0 32', MOVE_NORMAL, this);
1198 if(trace_fraction == 1.0)
1199 reverse = true;
1200 }
1201
1202 if(reverse)
1203 {
1204 this.angles_y = anglemods(this.angles_y - 180);
1205 makevectors(this.angles);
1206 }
1207
1208 movelib_move_simple_gravity(this, v_forward, mspeed, 1);
1209
1210 if(time > this.pain_finished && time > this.attack_finished_single[0])
1211 {
1212 if(vdist(this.velocity, >, 10))
1213 setanim(this, this.anim_walk, true, false, false);
1214 else
1215 setanim(this, this.anim_idle, true, false, false);
1216 }
1217}
1218
1220{
1221 int deadbits = (this.anim_state & (ANIMSTATE_DEAD1 | ANIMSTATE_DEAD2));
1222 if(IS_DEAD(this))
1223 {
1224 if (!deadbits)
1225 {
1226 // Decide on which death animation to use.
1227 if(random() < 0.5)
1228 deadbits = ANIMSTATE_DEAD1;
1229 else
1230 deadbits = ANIMSTATE_DEAD2;
1231 }
1232 }
1233 else
1234 {
1235 // Clear a previous death animation.
1236 deadbits = 0;
1237 }
1238 int animbits = deadbits;
1239 if(StatusEffects_active(STATUSEFFECT_Frozen, this))
1240 animbits |= ANIMSTATE_FROZEN;
1241 if(IS_DUCKED(this))
1242 animbits |= ANIMSTATE_DUCK; // not that monsters can crouch currently...
1243 animdecide_setstate(this, animbits, false);
1245
1246 /* // weapon entities for monsters?
1247 if (this.weaponentity)
1248 {
1249 updateanim(this.weaponentity);
1250 if (!this.weaponentity.animstate_override)
1251 setanim(this.weaponentity, this.weaponentity.anim_idle, true, false, false);
1252 }
1253 */
1254}
1255
1257{
1258 if(this.enemy)
1259 {
1260 vector targ_origin = ((this.enemy.absmin + this.enemy.absmax) * 0.5);
1261 targ_origin = WarpZone_RefSys_TransformOrigin(this.enemy, this, targ_origin); // origin of target as seen by the monster (us)
1262 WarpZone_TraceLine(this.origin, targ_origin, MOVE_NOMONSTERS, this);
1263
1264 // cases where the enemy may have changed their state (don't need to check everything here)
1265 // TODO: mutator hook
1266 if( (IS_DEAD(this.enemy) || GetResource(this.enemy, RES_HEALTH) < 1)
1267 || (STAT(FROZEN, this.enemy))
1268 || (this.enemy.flags & FL_NOTARGET)
1269 || (this.enemy.alpha < 0.5 && this.enemy.alpha != 0)
1270 || (this.enemy.takedamage == DAMAGE_NO)
1271 || (vdist(this.origin - targ_origin, >, this.target_range))
1272 || ((trace_fraction < 1) && (trace_ent != this.enemy))
1273 )
1274 {
1275 this.enemy = NULL;
1276 }
1277 else
1278 {
1279 return;
1280 }
1281 }
1282
1283 this.enemy = Monster_FindTarget(this);
1284 if(this.enemy)
1285 {
1286 WarpZone_RefSys_Copy(this.enemy, this);
1287 WarpZone_RefSys_AddInverse(this.enemy, this); // wz1^-1 ... wzn^-1 receiver
1288 // update move target immediately?
1289 this.moveto = WarpZone_RefSys_TransformOrigin(this.enemy, this, (0.5 * (this.enemy.absmin + this.enemy.absmax)));
1290 this.monster_moveto = '0 0 0';
1291 this.monster_face = '0 0 0';
1292
1293 Monster_Sound(this, monstersound_sight, 0, false, CH_VOICE);
1294 }
1295}
1296
1298{
1299 setthink(this, Monster_Think);
1300 this.nextthink = time;
1301
1302 if(this.monster_lifetime && time >= this.monster_lifetime)
1303 {
1304 Damage(this, this, this, GetResource(this, RES_HEALTH) + this.max_health, DEATH_KILL.m_id, DMG_NOWEP, this.origin, this.origin);
1305 return;
1306 }
1307
1308 // TODO: mutator hook to control monster thinking
1309 if(StatusEffects_active(STATUSEFFECT_Frozen, this))
1310 {
1311 this.enemy = NULL; // TODO: save enemy, and attack when revived?
1312 }
1313 else if(time >= this.last_enemycheck)
1314 {
1315 Monster_Enemy_Check(this);
1316 this.last_enemycheck = time + 1; // check for enemies every second
1317 }
1318
1319 Monster mon = this.monsterdef;
1320 if(mon.mr_think(mon, this))
1321 {
1322 Monster_Move(this, this.speed2, this.speed, this.stopspeed);
1323
1324 .entity weaponentity = weaponentities[0]; // TODO?
1325 Monster_Attack_Check(this, this.enemy, weaponentity);
1326 }
1327
1328 Monster_Anim(this);
1329
1331}
1332
1334{
1335 Monster mon = this.monsterdef;
1336 mon.mr_setup(mon, this);
1337
1338 // ensure some basic needs are met
1339 if(!GetResource(this, RES_HEALTH)) { SetResourceExplicit(this, RES_HEALTH, 100); }
1340 if(!GetResource(this, RES_ARMOR)) { SetResourceExplicit(this, RES_ARMOR, bound(0.2, 0.5 * MONSTER_SKILLMOD(this), 0.9)); }
1341 if(!this.target_range) { this.target_range = autocvar_g_monsters_target_range; }
1346
1348
1349 if(!(this.spawnflags & MONSTERFLAG_RESPAWNED))
1350 {
1351 SetResourceExplicit(this, RES_HEALTH, GetResource(this, RES_HEALTH) * MONSTER_SKILLMOD(this));
1352
1353 if(!this.skin)
1354 this.skin = rint(random() * 4);
1355 }
1356
1357 this.max_health = GetResource(this, RES_HEALTH);
1358 this.pain_finished = this.nextthink;
1359 this.last_enemycheck = this.spawn_time + random(); // slight delay
1360
1361 if(!this.wander_delay) { this.wander_delay = 2; }
1362 if(!this.wander_distance) { this.wander_distance = 600; }
1363
1366
1367 if(teamplay)
1368 {
1369 if(!this.monster_attack)
1371 this.monster_attack = true; // we can have monster enemies in team games
1372 }
1373
1374 Monster_Sound(this, monstersound_spawn, 0, false, CH_VOICE);
1375
1377 {
1378 entity wp = WaypointSprite_Spawn(WP_Monster, 0, 1024, this, '0 0 1' * (this.maxs.z + 15), NULL, this.team, this, sprite, true, RADARICON_DANGER);
1379 wp.wp_extra = this.monsterdef.monsterid;
1380 wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 0 0');
1382 {
1384 WaypointSprite_UpdateHealth(this.sprite, GetResource(this, RES_HEALTH));
1385 }
1386 }
1387
1388 setthink(this, Monster_Think);
1389 this.nextthink = time;
1390
1391 if(MUTATOR_CALLHOOK(MonsterSpawn, this))
1392 return false;
1393
1394 return true;
1395}
1396
1397bool Monster_Spawn(entity this, bool check_appear, Monster mon)
1398{
1399 // setup the basic required properties for a monster
1400
1401 if(!mon || mon == MON_Null) { return false; } // invalid monster
1402 if(!autocvar_g_monsters) { Monster_Remove(this); return false; }
1403
1404 if(!(this.spawnflags & MONSTERFLAG_RESPAWNED) && !(this.flags & FL_MONSTER))
1405 {
1406 IL_PUSH(g_monsters, this);
1407 if(this.mdl && this.mdl != "")
1408 precache_model(this.mdl);
1409 if(this.mdl_dead && this.mdl_dead != "")
1410 precache_model(this.mdl_dead);
1411 }
1412
1413 if(check_appear && Monster_Appear_Check(this, mon)) { return true; } // return true so the monster isn't removed
1414
1415 if(!this.monster_skill)
1416 this.monster_skill = cvar("g_monsters_skill");
1417
1418 // support for quake style removing monsters based on skill
1419 if(this.monster_skill == MONSTER_SKILL_EASY) if(this.spawnflags & MONSTERSKILL_NOTEASY) { Monster_Remove(this); return false; }
1420 if(this.monster_skill == MONSTER_SKILL_MEDIUM) if(this.spawnflags & MONSTERSKILL_NOTMEDIUM) { Monster_Remove(this); return false; }
1421 if(this.monster_skill == MONSTER_SKILL_HARD) if(this.spawnflags & MONSTERSKILL_NOTHARD) { Monster_Remove(this); return false; }
1422
1423 if(this.team && !teamplay)
1424 this.team = 0;
1425
1426 if(!(this.spawnflags & MONSTERFLAG_SPAWNED)) // naturally spawned monster
1427 if(!(this.spawnflags & MONSTERFLAG_RESPAWNED)) // don't count re-spawning monsters either
1428 monsters_total += 1;
1429
1430 if(this.mdl && this.mdl != "")
1431 _setmodel(this, this.mdl);
1432 else
1433 setmodel(this, mon.m_model);
1434
1435 if(!this.m_name || this.m_name == "")
1436 this.m_name = mon.m_name;
1437
1438 if(this.statuseffects && this.statuseffects.owner == this)
1439 {
1442 }
1443 else
1444 this.statuseffects = NULL;
1445
1446 this.flags = FL_MONSTER;
1447 this.classname = "monster";
1448 this.takedamage = DAMAGE_AIM;
1449 if(!this.bot_attack)
1450 IL_PUSH(g_bot_targets, this);
1451 this.bot_attack = true;
1452 this.iscreature = true;
1453 this.teleportable = true;
1454 if(!this.damagedbycontents)
1456 this.damagedbycontents = true;
1457 this.monsterdef = mon;
1458 this.event_damage = Monster_Damage;
1459 this.event_heal = Monster_Heal;
1460 settouch(this, Monster_Touch);
1461 this.use = Monster_Use;
1462 this.solid = SOLID_BBOX;
1464 StatusEffects_apply(STATUSEFFECT_SpawnShield, this, time + autocvar_g_monsters_spawnshieldtime, 0);
1465 this.enemy = NULL;
1466 this.velocity = '0 0 0';
1467 this.moveto = this.origin;
1468 this.pos1 = this.origin;
1469 this.pos2 = this.angles;
1470 this.reset = Monster_Reset;
1471 this.netname = mon.netname;
1472 this.monster_attackfunc = mon.monster_attackfunc;
1473 this.candrop = true;
1474 this.oldtarget2 = this.target2;
1475 this.deadflag = DEAD_NO;
1476 this.spawn_time = time;
1477 this.gravity = 1;
1478 this.monster_moveto = '0 0 0';
1479 this.monster_face = '0 0 0';
1481
1482 if(!this.noalign) { this.noalign = (mon.spawnflags & (MONSTER_TYPE_FLY | MONSTER_TYPE_SWIM)); }
1483 if(!this.scale) { this.scale = 1; }
1484 if(autocvar_g_monsters_edit) { this.grab = 1; }
1487 if(mon.spawnflags & MONSTER_TYPE_SWIM) { this.flags |= FL_SWIM; }
1488
1491
1493 {
1494 this.flags |= FL_FLY;
1496 }
1497
1499 this.scale *= 1.3;
1500
1501 setsize(this, RoundPerfectVector(mon.m_mins * this.scale), RoundPerfectVector(mon.m_maxs * this.scale));
1502 this.view_ofs = '0 0 0.7' * (this.maxs_z * 0.5);
1503
1504 Monster_UpdateModel(this);
1505
1506 if(!Monster_Spawn_Setup(this))
1507 {
1508 Monster_Remove(this);
1509 return false;
1510 }
1511
1512 if(!this.noalign)
1513 {
1514 setorigin(this, this.origin + '0 0 20');
1515 tracebox(this.origin + '0 0 64', this.mins, this.maxs, this.origin - '0 0 10000', MOVE_WORLDONLY, this);
1516 setorigin(this, trace_endpos);
1517 }
1518
1519 if (!nudgeoutofsolid_OrFallback(this))
1520 {
1521 // Stuck and not fixable
1522 Monster_Remove(this);
1523 return false;
1524 }
1525
1526 if(!(this.spawnflags & MONSTERFLAG_RESPAWNED))
1527 monster_setupcolors(this);
1528
1529 CSQCMODEL_AUTOINIT(this);
1530
1531 return true;
1532}
ERASEABLE float anglemods(float v)
Definition angle.qc:13
ERASEABLE float shortangle_f(float ang1, float ang2)
Definition angle.qc:29
void fixedmakevectors(vector a)
float animstate_endtime
Definition anim.qh:38
#define setanim(...)
Definition anim.qh:45
void animdecide_setimplicitstate(entity e, float onground)
void animdecide_setstate(entity e, int newstate, float restart)
const int ANIMSTATE_FROZEN
const int ANIMSTATE_DEAD1
int anim_state
const int ANIMSTATE_DEAD2
const int ANIMSTATE_DUCK
float bot_attack
Definition api.qh:38
IntrusiveList g_bot_targets
Definition api.qh:149
#define MUTATOR_CALLHOOK(id,...)
Definition base.qh:143
string mdl_dead
Definition breakable.qc:27
int grab
Definition cheats.qh:26
var entity(vector mins, vector maxs,.entity tofield) findbox_tofield_OrFallback
float max_health
float pain_finished
float GetResource(entity e, Resource res_type)
Returns the current amount of resource the given entity has.
void TakeResource(entity receiver, Resource res_type, float amount)
Takes an entity some resource.
bool SetResourceExplicit(entity e, Resource res_type, float amount)
Sets the resource amount of an entity without calling any hooks.
string netname
short name
Definition monster.qh:42
string m_name
human readable name
Definition monster.qh:40
virtual void mr_deadthink()
(SERVER) logic to run every frame after the monster has died
Definition monster.qh:59
virtual void mr_death()
(SERVER) called when monster dies
Definition monster.qh:61
vector m_mins
hitbox size
Definition monster.qh:50
virtual void mr_setup()
(SERVER) setup monster data
Definition monster.qh:55
virtual void mr_anim()
(BOTH?) sets animations for monster
Definition monster.qh:65
virtual void mr_pain()
(SERVER) called when monster is damaged
Definition monster.qh:63
int spawnflags
attributes
Definition monster.qh:38
virtual void mr_think()
(SERVER) logic to run every frame
Definition monster.qh:57
vector m_maxs
hitbox size
Definition monster.qh:52
string netname
Definition powerups.qc:20
float cnt
Definition powerups.qc:24
float count
Definition powerups.qc:22
float gravity
Definition items.qh:17
entity owner
Definition main.qh:87
int team
Definition main.qh:188
#define colormapPaletteColor(c, isPants)
Definition color.qh:5
int spawnflags
Definition ammo.qh:15
string mdl
Definition item.qh:89
#define setmodel(this, m)
Definition model.qh:26
#define M_ARGV(x, type)
Definition events.qh:17
#define IS_CLIENT(s)
Definition player.qh:242
#define IS_DEAD(s)
Definition player.qh:245
#define PHYS_INPUT_BUTTON_CHAT(s)
Definition player.qh:159
vector v_angle
Definition player.qh:237
float waterlevel
Definition player.qh:226
#define IS_DUCKED(s)
Definition player.qh:210
#define IS_PLAYER(s)
Definition player.qh:243
string m_name
Definition scores.qh:142
float game_starttime
Definition stats.qh:82
float game_stopped
Definition stats.qh:81
vector healtharmor_applydamage(float a, float armorblock, int deathtype, float damage)
Definition util.qc:1313
const int FL_SWIM
Definition constants.qh:71
const int FL_NOTARGET
Definition constants.qh:76
const int FL_MONSTER
Definition constants.qh:74
const int FL_FLY
Definition constants.qh:70
string classname
float Q3SURFACEFLAG_SKY
float flags
const float MOVE_NOMONSTERS
entity trace_ent
float DPCONTENTS_BOTCLIP
float DPCONTENTS_SOLID
const float CONTENT_SOLID
const float MOVE_NORMAL
vector mins
const float EF_RED
const float SOLID_CORPSE
vector velocity
float DPCONTENTS_BODY
const float SOLID_BBOX
const float EF_FULLBRIGHT
float effects
const float FILE_READ
float DPCONTENTS_PLAYERCLIP
float skin
float time
vector trace_endpos
float checkpvs(vector viewpos, entity viewee)
vector maxs
float DPCONTENTS_MONSTERCLIP
float nextthink
float MOVE_WORLDONLY
float trace_dphitq3surfaceflags
float colormap
vector v_forward
const float EF_NODEPTHTEST
vector origin
float trace_fraction
const float CONTENT_LAVA
float dphitcontentsmask
const float CONTENT_SLIME
#define spawn
#define use
vector glowmod
#define CSQCMODEL_AUTOUPDATE(e)
#define CSQCMODEL_AUTOINIT(e)
void Damage(entity targ, entity inflictor, entity attacker, float damage, int deathtype,.entity weaponentity, vector hitloc, vector force)
Definition damage.qc:503
float damagedbycontents
Definition damage.qh:45
IntrusiveList g_damagedbycontents
Definition damage.qh:135
#define DMG_NOWEP
Definition damage.qh:104
int state
float damageforcescale
void SUB_Remove(entity this)
Remove entity.
Definition defer.qh:13
#define tokenize_console
float speed
Definition dynlight.qc:9
void Send_Effect(entity eff, vector eff_loc, vector eff_vel, int eff_cnt)
Definition all.qc:124
RES_ARMOR
Definition ent_cs.qc:130
ent angles
Definition ent_cs.qc:121
model
Definition ent_cs.qc:139
angles_y
Definition ent_cs.qc:119
solid
Definition ent_cs.qc:165
void Violence_GibSplash(entity source, float type, float amount, entity attacker)
Definition gibs.qc:53
void Violence_GibSplash_At(vector org, vector dir, float type, float amount, entity gibowner, entity attacker)
Definition gibs.qc:23
void PrecacheGlobalSound(string sample)
string GlobalSound_sample(string pair, float r)
bool tracebox_hits_trigger_hurt(vector start, vector e_min, vector e_max, vector end)
Definition hurt.qc:79
ERASEABLE entity IL_PUSH(IntrusiveList this, entity it)
Push to tail.
#define IL_EACH(this, cond, body)
entity Item_RandomFromList(string itemlist)
Takes a space-separated list of netnames, returns the itemdef of one of them (or NULL if none are ava...
Definition spawning.qc:71
bool Item_Initialise(entity item)
An optimised and generic way to initialise items (loot or permanent)
Definition spawning.qc:27
#define ITEM_SET_LOOT(item, loot)
Sets the item loot status.
Definition spawning.qh:45
void SUB_UseTargets(entity this, entity actor, entity trigger)
Definition triggers.qc:344
#define STAT(...)
Definition stats.qh:82
void WarpZone_TraceLine(vector org, vector end, float nomonsters, entity forent)
Definition common.qc:348
void WarpZone_RefSys_AddInverse(entity me, entity wz)
Definition common.qc:743
vector WarpZone_RefSys_TransformOrigin(entity from, entity to, vector org)
Definition common.qc:765
void WarpZone_RefSys_Copy(entity me, entity from)
Definition common.qc:797
entity goalentity
Definition viewloc.qh:16
bool autocvar_g_campaign
Definition menu.qc:747
entity anim
Definition menu.qh:25
string fgets(float fhandle)
void fclose(float fhandle)
float bound(float min, float value, float max)
float cvar(string name)
float fopen(string filename, float mode)
entity find(entity start,.string field, string match)
string search_getfilename(float handle, float num)
float random(void)
float search_getsize(float handle)
vector vectoangles(vector v)
float search_begin(string pattern, float caseinsensitive, float quiet)
vector randomvec(void)
float min(float f,...)
float rint(float f)
vector normalize(vector v)
string ftos(float f)
float MSG_BROADCAST
Definition menudefs.qc:55
string argv(float n)
void search_end(float handle)
entity monsterdef
Definition monster.qh:24
const int MONSTER_TYPE_FLY
Definition monster.qh:9
const int MON_FLAG_RANGED
Definition monster.qh:13
const int MONSTER_RESPAWN_DEATHPOINT
Definition monster.qh:8
vector anim_walk
Definition monster.qh:32
const int MONSTER_TYPE_SWIM
Definition monster.qh:10
const int MONSTER_SIZE_QUAKE
Definition monster.qh:17
void movelib_brake_simple(entity this, float force)
Definition movelib.qc:167
#define movelib_move_simple(e, newdir, velo, blendrate)
Definition movelib.qh:36
#define movelib_move_simple_gravity(e, newdir, velo, blendrate)
Definition movelib.qh:39
void set_movetype(entity this, int mt)
Definition movetypes.qc:4
const int MOVETYPE_WALK
Definition movetypes.qh:132
const int WATERLEVEL_WETFEET
Definition movetypes.qh:12
float move_movetype
Definition movetypes.qh:76
#define UNSET_ONGROUND(s)
Definition movetypes.qh:18
const int MOVETYPE_FLY
Definition movetypes.qh:134
const int MOVETYPE_TOSS
Definition movetypes.qh:135
const int MOVETYPE_BOUNCE
Definition movetypes.qh:139
#define IS_ONGROUND(s)
Definition movetypes.qh:16
var void func_null()
string string_null
Definition nil.qh:9
strcat(_("^F4Countdown stopped!"), "\n^BG", _("Teams are too unbalanced."))
#define new_pure(class)
purely logical entities (not linked to the area grid)
Definition oo.qh:67
#define NULL
Definition post.qh:14
#define makevectors
Definition post.qh:21
vector view_ofs
Definition progsdefs.qc:151
float DEAD_NO
Definition progsdefs.qc:274
float deadflag
Definition progsdefs.qc:149
float DEAD_RESPAWNING
Definition progsdefs.qc:278
float DEAD_DEAD
Definition progsdefs.qc:276
float scale
Definition projectile.qc:14
#define REGISTRY_GET(id, i)
Definition registry.qh:43
const int RES_LIMIT_NONE
Definition resources.qh:60
#define round_handler_IsActive()
#define round_handler_IsRoundStarted()
#define setthink(e, f)
vector
Definition self.qh:92
entity entity toucher
Definition self.qh:72
vector vector ang
Definition self.qh:92
#define settouch(e, f)
Definition self.qh:73
bool campaign_bots_may_start
campaign mode: bots shall spawn but wait for the player to spawn before they do anything in other gam...
Definition campaign.qh:26
bool autocvar_g_fullbrightplayers
Definition client.qh:17
bool autocvar_g_nodepthtestplayers
Definition client.qh:34
#define ITEM_DAMAGE_NEEDKILL(dt)
Definition items.qh:129
bool noalign
Definition items.qh:36
float respawntime
Definition items.qh:30
bool iscreature
Definition main.qh:46
string monster_loot
Definition events.qh:390
const int CH_PAIN
Definition sound.qh:18
const float VOL_BASE
Definition sound.qh:36
const int CH_VOICE
Definition sound.qh:10
const float ATTEN_NORM
Definition sound.qh:30
float spamsound(entity e, int chan, Sound samp, float vol, float _atten)
use this one if you might be causing spam (e.g.
Definition all.qc:124
bool sound_allowed(int to, entity e)
Definition all.qc:9
bool StatusEffects_active(StatusEffect this, entity actor)
void StatusEffects_apply(StatusEffect this, entity actor, float eff_time, int eff_flags)
StatusEffect statuseffects
Entity statuseffects.
void StatusEffects_update(entity e)
void StatusEffects_clearall(entity store)
vector steerlib_attract2(entity this, vector point, float min_influense, float max_distance, float max_influense)
Definition steerlib.qc:45
vector steerto
Definition steerlib.qh:3
#define strcpy(this, s)
Definition string.qh:52
void SUB_SetFade(entity ent, float vanish_time, float fading_time)
Definition subs.qc:77
const int DAMAGE_NO
Definition subs.qh:79
vector pos2
Definition subs.qh:50
vector pos1
Definition subs.qh:50
const int DAMAGE_AIM
Definition subs.qh:81
float takedamage
Definition subs.qh:78
entity sprite
Definition sv_assault.qc:11
entity enemy
Definition sv_ctf.qh:153
void CSQCModel_UnlinkEntity(entity e)
Definition sv_model.qc:129
void Monster_Delay(entity this, int repeat_count, float defer_amnt, void(entity) func)
void Monster_Remove(entity this)
void Monster_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype,.entity weaponentity, vector hitloc, vector force)
bool Monster_Heal(entity targ, entity inflictor, float amount, float limit)
void monster_setupcolors(entity this)
bool Monster_Attack_Leap_Check(entity this, vector vel)
bool Monster_Respawn_Check(entity this)
void Monster_Sound(entity this,.string samplefield, float sound_delay, bool delaytoo, float chan)
void Monster_Sounds_Clear(entity this)
void Monster_Respawn(entity this)
void monster_dropitem(entity this, entity attacker)
void Monster_Dead_Think(entity this)
void Monster_Move(entity this, float runspeed, float walkspeed, float stpspeed)
void Monster_Move_2D(entity this, float mspeed, bool allow_jumpoff)
void Monster_Use(entity this, entity actor, entity trigger)
vector Monster_Move_Target(entity this, entity targ)
bool Monster_Spawn(entity this, bool check_appear, Monster mon)
void monster_changeteam(entity this, int newteam)
void Monster_Reset(entity this)
void Monster_Dead_Fade(entity this)
string get_monster_model_datafilename(string m, float sk, string fil)
void Monster_Delay_Action(entity this)
void Monster_Sound_Precache(string f)
void Monster_Sounds_Update(entity this)
void Monster_Anim(entity this)
void Monster_Touch(entity this, entity toucher)
void Monster_Miniboss_Setup(entity this)
void Monster_Sounds_Precache(entity this)
entity draggedby
entity Monster_FindTarget(entity this)
void Monster_Dead(entity this, entity attacker, float gibbed)
bool Monster_ValidTarget(entity this, entity targ, bool skipfacing)
void monster_makevectors(entity this, entity targ)
bool Monster_Appear_Check(entity this, Monster monster_id)
bool Monster_Attack_Melee(entity this, entity targ, float damg, vector anim, float er, float animtime, int deathtype, bool dostop)
bool monster_facing(entity this, entity targ)
void Monster_UpdateModel(entity this)
int skin_for_monstersound
string Monster_Sound_SampleField(string type)
void Monster_Enemy_Check(entity this)
int Monster_CheckDanger(entity this, vector dst_ahead)
void Monster_Attack_Check(entity this, entity targ,.entity weaponentity)
bool Monster_Spawn_Setup(entity this)
void Monster_Appear(entity this, entity actor, entity trigger)
bool Monster_Attack_Leap(entity this, vector anm, void(entity this, entity toucher) touchfunc, vector vel, float animtime)
void monsters_setstatus(entity this)
vector Monster_WanderTarget(entity this, vector targetorigin)
Returns an origin that's near targetorigin and visible to this monster.
void Monster_Dead_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype,.entity weaponentity, vector hitloc, vector force)
void Monster_Think(entity this)
bool Monster_Sounds_Load(entity this, string f, int first)
bool monster_attack
#define ALLMONSTERSOUNDS
const int MONSTERFLAG_NORESPAWN
#define MONSTER_SKILLMOD(mon)
int autocvar_g_monsters_score_spawned
float autocvar_g_monsters_attack_range
int monster_moveflags
bool autocvar_g_monsters_respawn
const int MONSTER_SKILL_EASY
int totalspawned
number of monsters spawned with mobspawn command
const int MONSTER_MOVE_FOLLOW
vector monster_moveto
float anim_finished
const int MONSTERSKILL_NOTHARD
entity monster_follow
bool autocvar_g_monsters_quake_resize
bool autocvar_g_monsters_playerclip_collisions
const int MONSTER_SKILL_MEDIUM
bool autocvar_g_monsters_target_infront_2d
const int MONSTER_MOVE_ENEMY
float wander_delay
int monster_skill
const int MONSTERFLAG_SPAWNED
float wander_distance
bool autocvar_g_monsters_typefrag
bool autocvar_g_monsters_sounds
Definition sv_monsters.qh:7
const int MONSTERFLAG_INFRONT
float spawn_time
float last_trace
const int MONSTERSKILL_NOTMEDIUM
int monsters_killed
IntrusiveList g_monster_targets
float autocvar_g_monsters_damageforcescale
float stopspeed
const int MONSTERFLAG_APPEAR
int monsters_total
const int MONSTER_MOVE_SPAWNLOC
const int MONSTERFLAG_MINIBOSS
const int MONSTER_MOVE_WANDER
float autocvar_g_monsters_miniboss_chance
float autocvar_g_monsters_spawnshieldtime
float autocvar_g_monsters
Definition sv_monsters.qh:5
vector monster_face
float autocvar_g_monsters_target_range
bool candrop
const int MONSTER_SKILL_INSANE
float attack_range
ALLMONSTERSOUNDS float GetMonsterSoundSampleField_notFound
float autocvar_g_monsters_healthbars
float autocvar_g_monsters_respawn_delay
string autocvar_g_monsters_miniboss_loot
bool autocvar_g_monsters_lineofsight
const int MONSTER_ATTACK_MELEE
bool autocvar_g_monsters_edit
Definition sv_monsters.qh:6
float autocvar_g_monsters_drop_time
float speed2
const int MONSTER_SKILL_HARD
const int MONSTER_SKILL_NIGHTMARE
float autocvar_g_monsters_miniboss_healthboost
const int MONSTERFLAG_INVINCIBLE
int autocvar_g_monsters_score_kill
bool autocvar_g_monsters_target_infront
const int MONSTERFLAG_RESPAWNED
string oldtarget2
const int MONSTERSKILL_NOTEASY
const int MONSTER_ATTACK_RANGED
int monster_movestate
const int MONSTERFLAG_FLY_VERTICAL
float last_enemycheck
IntrusiveList g_monsters
bool autocvar_g_monsters_teams
const int MONSTER_MOVE_NOMOVE
float autocvar_g_monsters_target_infront_range
float msound_delay
float monster_lifetime
string target2
void GiveResource(entity receiver, Resource res_type, float amount)
Gives an entity some resource.
void GiveResourceWithLimit(entity receiver, Resource res_type, float amount, float limit)
Gives an entity some resource but not more than a limit.
#define GameRules_scoring_add(client, fld, value)
Definition sv_rules.qh:85
bool turret_closetotarget(entity this, vector targ, float range)
float dmg_time
#define SAME_TEAM(a, b)
Definition teams.qh:241
vector Team_ColorRGB(int teamid)
Definition teams.qh:76
bool teamplay
Definition teams.qh:59
#define DIFF_TEAM(a, b)
Definition teams.qh:242
float teleportable
entity realowner
string targetname
Definition triggers.qh:56
#define IS_OBSERVER(v)
Definition utils.qh:11
#define IS_SPEC(v)
Definition utils.qh:10
#define IS_MONSTER(v)
Definition utils.qh:21
#define IS_VEHICLE(v)
Definition utils.qh:22
#define CENTER_OR_VIEWOFS(ent)
Definition utils.qh:29
#define vdist(v, cmp, f)
Vector distance comparison, avoids sqrt()
Definition vector.qh:8
ERASEABLE vector RoundPerfectVector(vector v)
Definition vector.qh:206
#define vec2(...)
Definition vector.qh:90
void WaypointSprite_Kill(entity wp)
void WaypointSprite_UpdateTeamRadar(entity e, entity icon, vector col)
void WaypointSprite_UpdateMaxHealth(entity e, float f)
entity WaypointSprite_Spawn(entity spr, float _lifetime, float maxdistance, entity ref, vector ofs, entity showto, float t, entity own,.entity ownfield, float hideable, entity icon)
void WaypointSprite_UpdateHealth(entity e, float f)
vector anim_idle
Definition all.qh:399
const int MAX_WEAPONSLOTS
Definition weapon.qh:16
entity weaponentities[MAX_WEAPONSLOTS]
Definition weapon.qh:17
int weaponslot(.entity weaponentity)
Definition weapon.qh:19
float attack_finished_single[MAX_WEAPONSLOTS]
vector moveto
Definition zombie.qc:17