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{
42 if (!this.candrop || autocvar_g_monsters_drop_time <= 0)
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)
105 return false;
106
107 if (targ == this
108 || (IS_VEHICLE(targ) && !(this.monsterdef.spawnflags & MON_FLAG_RANGED)) // melee vs vehicle is useless
109 || game_stopped || time < game_starttime // monsters do nothing before match has started
110 || targ.takedamage == DAMAGE_NO
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 // if any of the above checks fail, target is not valid
121 return false;
122
123 vector targ_origin = (targ.absmin + targ.absmax) * 0.5;
124 traceline(this.origin + this.view_ofs, targ_origin, MOVE_NOMONSTERS, this); // TODO: maybe we can rely a bit on PVS data instead?
125
126 if (trace_fraction < 1 && trace_ent != targ)
127 return false; // solid
128
130 && this.enemy != targ
131 && !monster_facing(this, targ))
132 return false;
133
134 return true; // this target is valid!
135}
136
138{
139 if (MUTATOR_CALLHOOK(MonsterFindTarget))
140 return this.enemy; // Handled by a mutator
141
142 entity closest_target = NULL;
143 vector my_center = CENTER_OR_VIEWOFS(this);
144
145 float trange;
146 // find the closest acceptable target to pass to
147 IL_EACH(g_monster_targets, it.monster_attack,
148 {
149 trange = this.target_range;
150 if (PHYS_INPUT_BUTTON_CROUCH(it))
151 trange *= 0.75; // TODO cvar this
152 vector theirmid = (it.absmin + it.absmax) * 0.5;
153 if (vdist(theirmid - this.origin, >, trange) || !Monster_ValidTarget(this, it, false))
154 continue;
155
156 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
157 vector targ_center = CENTER_OR_VIEWOFS(it);
158
159 if (closest_target)
160 {
161 vector closest_target_center = CENTER_OR_VIEWOFS(closest_target);
162 if (vlen2(my_center - targ_center) < vlen2(my_center - closest_target_center))
163 closest_target = it;
164 }
165 else
166 closest_target = it;
167 });
168
169 return closest_target;
170}
171
173{
174 if (teamplay && this.team)
175 this.colormap = 1024 + (this.team - 1) * 17;
176 else if (IS_PLAYER(this.realowner))
177 this.colormap = this.realowner.colormap;
178 else
179 {
181 this.colormap = 1126;
182 else if (this.monster_skill <= MONSTER_SKILL_MEDIUM)
183 this.colormap = 1075;
184 else if (this.monster_skill <= MONSTER_SKILL_HARD)
185 this.colormap = 1228;
186 else if (this.monster_skill <= MONSTER_SKILL_INSANE)
187 this.colormap = 1092;
188 else if (this.monster_skill <= MONSTER_SKILL_NIGHTMARE)
189 this.colormap = 1160;
190 else
191 this.colormap = 1024;
192 }
193
194 if (this.colormap > 0)
195 this.glowmod = colormapPaletteColor(this.colormap & 0x0F, false);
196 else
197 this.glowmod = '1 1 1';
198}
199
200void monster_changeteam(entity this, int newteam)
201{
202 if (!teamplay)
203 return;
204
205 this.team = newteam;
206 if (!this.monster_attack)
208 this.monster_attack = true; // new team, activate attacking
210
211 if (this.sprite)
212 {
213 WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DANGER, ((newteam) ? Team_ColorRGB(newteam) : '1 0 0'));
214
215 this.sprite.team = newteam;
216 this.sprite.SendFlags |= 1;
217 }
218}
219
220.void(entity) monster_delayedfunc;
222{
223 // TODO: maybe do check for facing here
224 if (Monster_ValidTarget(this.owner, this.owner.enemy, false))
225 {
226 monster_makevectors(this.owner, this.owner.enemy);
227 this.monster_delayedfunc(this.owner);
228 }
229
230 if (this.cnt > 1)
231 {
232 --this.cnt;
234 this.nextthink = time + this.count;
235 }
236 else
237 {
238 setthink(this, SUB_Remove);
239 this.nextthink = time;
240 }
241}
242
243void Monster_Delay(entity this, int repeat_count, float defer_amnt, void(entity) func)
244{
245 // deferred attacking, checks if monster is still alive and target is still valid before attacking
247
249 e.nextthink = time + defer_amnt;
250 e.count = defer_amnt;
251 e.owner = this;
252 e.monster_delayedfunc = func;
253 e.cnt = repeat_count;
254}
255
256
257// ==============
258// Monster sounds
259// ==============
260
261string get_monster_model_datafilename(string m, float sk, string fil)
262{
263 if (m)
264 m = strcat(m, "_");
265 else
266 m = "models/monsters/*_";
267 if (sk >= 0)
268 m = strcat(m, ftos(sk));
269 else
270 m = strcat(m, "*");
271 return strcat(m, ".", fil);
272}
273
275{
276 float fh = fopen(f, FILE_READ);
277 if (fh < 0)
278 return;
279 string s;
280 while ((s = fgets(fh)))
281 {
282 if (tokenize_console(s) != 3)
283 {
284 //LOG_DEBUG("Invalid sound info line: ", s); // probably a comment, no need to spam warnings
285 continue;
286 }
287 PrecacheGlobalSound(strcat(argv(1), " ", argv(2)));
288 }
289 fclose(fh);
290}
291
293{
294 string m = this.monsterdef.m_model.model_str();
295
296 float globhandle = search_begin(strcat(m, "_*.sounds"), true, false);
297 if (globhandle < 0)
298 return;
299
300 float n = search_getsize(globhandle);
301 string f;
302 for (int i = 0; i < n; ++i)
303 {
304 //print(search_getfilename(globhandle, i), "\n");
305 f = search_getfilename(globhandle, i);
307 }
308 search_end(globhandle);
309}
310
312{
313#define _MSOUND(m) strfree(this.monstersound_##m);
315#undef _MSOUND
316}
317
318.string Monster_Sound_SampleField(string type)
319{
321 switch (type)
322 {
323 #define _MSOUND(m) \
324 case #m: \
325 return monstersound_##m;
327 #undef _MSOUND
328 }
330 return string_null;
331}
332
333bool Monster_Sounds_Load(entity this, string f, int first)
334{
335 float fh = fopen(f, FILE_READ);
336 if (fh < 0)
337 {
338 //LOG_DEBUG("Monster sound file not found: ", f); // no biggie, monster has no sounds, let's not spam it
339 return false;
340 }
341 var .string field;
342 string s;
343 while ((s = fgets(fh)))
344 {
345 if (tokenize_console(s) != 3)
346 continue;
349 continue;
350 strcpy(this.(field), strcat(argv(1), " ", argv(2)));
351 }
352 fclose(fh);
353 return true;
354}
355
358{
359 if (this.skin == this.skin_for_monstersound)
360 return;
361
362 this.skin_for_monstersound = this.skin;
364 if (!Monster_Sounds_Load(this, get_monster_model_datafilename(this.model, this.skin, "sounds"), 0))
365 Monster_Sounds_Load(this, get_monster_model_datafilename(this.model, 0, "sounds"), 0);
366}
367
368void Monster_Sound(entity this, .string samplefield, float sound_delay, bool delaytoo, float chan)
369{
371 || (delaytoo && time < this.msound_delay)) // too early
372 return;
373
374 string sample = this.(samplefield);
375 if (sample != "")
376 sample = GlobalSound_sample(sample, random());
377 float myscale = (this.scale ? this.scale : 1); // safety net
378 sound7(this, chan, sample, VOL_BASE, ATTEN_NORM, 100 / myscale, 0);
379
380 this.msound_delay = time + sound_delay;
381}
382
383
384// =======================
385// Monster attack handlers
386// =======================
387
388bool Monster_Attack_Melee(entity this, entity targ, float damg, vector anim, float er, float animtime, int deathtype, bool dostop)
389{
390 if (dostop && IS_MONSTER(this))
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.realowner,
404 damg * MONSTER_SKILLMOD(this),
405 deathtype,
406 DMG_NOWEP,
407 trace_ent.origin,
408 normalize(trace_ent.origin - this.origin)
409 );
410
411 return true;
412}
413
415{
416 if ((this.state && IS_MONSTER(this)) // already attacking
417 || !IS_ONGROUND(this) // not on the ground
418 || GetResource(this, RES_HEALTH) <= 0 || IS_DEAD(this) // called when dead?
419 || time < this.attack_finished_single[0]) // still attacking
420 return false;
421
422 vector old = this.velocity;
423 this.velocity = vel;
424 tracetoss(this, this);
425 this.velocity = old;
426
427 return trace_ent == this.enemy;
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;
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 /*
493 this.anim_idle = animfixfps(this, '0 1 0.01', '0 0 0');
494 this.anim_walk = animfixfps(this, '1 1 0.01', '0 0 0');
495 this.anim_run = animfixfps(this, '2 1 0.01', '0 0 0');
496 this.anim_fire1 = animfixfps(this, '3 1 0.01', '0 0 0');
497 this.anim_fire2 = animfixfps(this, '4 1 0.01', '0 0 0');
498 this.anim_melee = animfixfps(this, '5 1 0.01', '0 0 0');
499 this.anim_pain1 = animfixfps(this, '6 1 0.01', '0 0 0');
500 this.anim_pain2 = animfixfps(this, '7 1 0.01', '0 0 0');
501 this.anim_die1 = animfixfps(this, '8 1 0.01', '0 0 0');
502 this.anim_die2 = animfixfps(this, '9 1 0.01', '0 0 0');
503 */
504
505 // then get the real values
506 Monster mon = this.monsterdef;
507 mon.mr_anim(mon, this);
508}
509
511{
512 if (!toucher)
513 return;
514
515 if (toucher.monster_attack && this.enemy != toucher && !IS_MONSTER(toucher)
516 && time >= this.spawn_time
517 && Monster_ValidTarget(this, toucher, true))
518 this.enemy = toucher;
519}
520
522{
524 {
525 // already performed initial setup, just reapply statuses as needed
527 this.effects |= EF_RED;
528 return;
529 }
530
531 if (MUTATOR_CALLHOOK(MonsterCheckBossFlag, this))
532 {
533 // prevent other code from identifying the monster as a miniboss
535 return;
536 }
537
538 // g_monsters_miniboss_chance cvar or spawnflags 64 causes a monster to be a miniboss
540 {
542 this.effects |= EF_RED;
543 this.spawnflags |= MONSTERFLAG_MINIBOSS; // identifier for other code
544 }
545}
546
548{
549 if (this.deadflag == DEAD_DEAD // don't call when monster isn't dead
550 && MUTATOR_CALLHOOK(MonsterRespawn, this))
551 return true; // enabled by a mutator
552
554 return false;
555
556 return true;
557}
558
560{
561 Monster_Spawn(this, true, this.monsterdef);
562}
563
564.vector pos1;
565.vector pos2;
566
568{
569 if (Monster_Respawn_Check(this))
570 {
573 this.nextthink = time + this.respawntime;
574 this.monster_lifetime = 0;
577 {
578 this.pos1 = this.origin;
579 this.pos2 = this.angles;
580 }
581 this.event_damage = func_null;
582 this.event_heal = func_null;
583 this.takedamage = DAMAGE_NO;
584 setorigin(this, this.pos1);
585 this.angles = this.pos2;
586 SetResourceExplicit(this, RES_HEALTH, this.max_health);
587 setmodel(this, MDL_Null);
588 }
589 else
590 {
591 // number of monsters spawned with mobspawn command
592 --totalspawned;
593
594 SUB_SetFade(this, time + 3, 1);
595 }
596}
597
598void Monster_Use(entity this, entity actor, entity trigger)
599{
600 if (Monster_ValidTarget(this, actor, true))
601 this.enemy = actor;
602}
603
606{
607 vector ang;
608 ang.y = rint(random() * 500);
609 ang.x = this.angles.x;
610 ang.z = this.angles.z;
612 vector pos = targetorigin + v_forward * this.wander_distance;
613 if (((this.flags & FL_FLY) && (this.spawnflags & MONSTERFLAG_FLY_VERTICAL)) || (this.flags & FL_SWIM))
614 {
615 pos.z = random() * 200;
616 if (random() >= 0.5)
617 pos.z = -pos.z;
618 }
619 // truncate movement so we don't do walking into walls animations
620 traceline(this.origin + this.view_ofs, pos, MOVE_NORMAL, this);
621 return trace_endpos;
622}
623
625{
626 // enemy is always preferred target
627 if (this.enemy)
628 {
629 vector targ_origin = (this.enemy.absmin + this.enemy.absmax) * 0.5;
630 targ_origin = WarpZone_RefSys_TransformOrigin(this.enemy, this, targ_origin); // origin of target as seen by the monster (us)
631
632 /*
633 WarpZone_TrailParticles(NULL, particleeffectnum(EFFECT_RED_PASS), this.origin, targ_origin);
634 print("Trace origin: ", vtos(targ_origin), "\n");
635 print("Target origin: ", vtos(this.enemy.origin), "\n");
636 print("My origin: ", vtos(this.origin), "\n");
637 */
638
640 this.last_trace = time + 1.2;
641 if (this.monster_moveto)
642 return this.monster_moveto; // assumes code is properly setting this when monster has an enemy
643 else
644 return targ_origin;
645
646 /*
647 makevectors(this.angles);
648 this.monster_movestate = MONSTER_MOVE_ENEMY;
649 this.last_trace = time + 1.2;
650 return this.enemy.origin;
651 */
652 }
653
654 switch (this.monster_moveflags)
655 {
658 this.last_trace = time + 0.3;
659 if (this.monster_follow)
660 {
661 if (vdist(this.origin - this.monster_follow.origin, <, this.wander_distance))
662 this.last_trace = time + this.wander_delay;
663 return Monster_WanderTarget(this, this.monster_follow.origin);
664 }
665 else
666 return this.origin;
667
670 this.last_trace = time + 2;
671 return this.pos1;
672
674 if (this.monster_moveto)
675 {
676 this.last_trace = time + 0.5;
677 return this.monster_moveto;
678 }
679 else
680 {
682 this.last_trace = time + 2;
683 }
684 return this.origin;
685
686 default:
689 if (this.monster_moveto)
690 {
691 this.last_trace = time + 0.5;
692 return this.monster_moveto;
693 }
694 else if (targ)
695 {
696 this.last_trace = time + 0.5;
697 return targ.origin;
698 }
699 else
700 {
701 this.last_trace = time + this.wander_delay;
702 return Monster_WanderTarget(this, this.origin);
703 }
704 }
705}
706
707// Check for water/slime/lava and dangerous edges
708// (only when the bot is on the ground or jumping intentionally)
709// returns a number > 0 for danger
710// based on havocbot_checkdanger()
712{
713 float s;
714
715 if (this.flags & (FL_FLY | FL_SWIM))
716 {
717 // Look ahead
718 traceline(this.origin + this.view_ofs, dst_ahead, true, NULL);
719
720 // Only care about the skybox if it's below
721 // bones_was_here: does this even matter when flying/swimming?
722 if (trace_endpos.z < this.origin.z + this.mins.z
724 return 1;
725
726 s = pointcontents(trace_endpos + '0 0 1');
727 if (s != CONTENT_SOLID)
728 {
729 if (s == CONTENT_LAVA
730 || s == CONTENT_SLIME)
731 return 3;
732
733 if (tracebox_hits_trigger_hurt(dst_ahead, this.mins, this.maxs, trace_endpos))
734 {
735 // the traceline check isn't enough but is good as optimization,
736 // when not true (most of the time) this tracebox call is avoided
737 tracebox(this.origin + this.view_ofs, this.mins, this.maxs, dst_ahead, true, this);
738 if (tracebox_hits_trigger_hurt(dst_ahead, this.mins, this.maxs, trace_endpos))
739 return 4;
740 }
741 }
742 }
743 else
744 {
745 vector dst_down = dst_ahead - '0 0 3000';
746
747 // Look downwards
748 traceline(dst_ahead, dst_down, true, NULL);
749 //te_lightning2(NULL, this.origin + this.view_ofs, dst_ahead); // Draw "ahead" look
750 //te_lightning2(NULL, dst_ahead, trace_endpos); // Draw "downwards" look
751 if (trace_endpos.z < this.origin.z + this.mins.z)
752 {
754 return 1;
755
756 // If following an enemy ignore probably-non-fatal falls,
757 // if wandering only ignore small falls.
758 if (trace_endpos.z < (this.origin.z + this.mins.z) - (this.enemy ? 1024 : 100))
759 return 2;
760
761 s = pointcontents(trace_endpos + '0 0 1');
762 if (s != CONTENT_SOLID)
763 {
764 if (s == CONTENT_LAVA
765 || s == CONTENT_SLIME)
766 return 3;
767
768 if (tracebox_hits_trigger_hurt(dst_ahead, this.mins, this.maxs, trace_endpos))
769 {
770 // the traceline check isn't enough but is good as optimization,
771 // when not true (most of the time) this tracebox call is avoided
772 tracebox(dst_ahead, this.mins, this.maxs, dst_down, true, this);
773 if (tracebox_hits_trigger_hurt(dst_ahead, this.mins, this.maxs, trace_endpos))
774 return 4;
775 }
776 }
777 }
778 }
779
780 return 0;
781}
782
783.entity draggedby;
784void Monster_Move(entity this, float runspeed, float walkspeed, float stpspeed)
785{
786 // update goal entity if lost
787 if (this.target2 && this.target2 != "" && this.goalentity.targetname != this.target2)
788 this.goalentity = find(NULL, targetname, this.target2);
789
790 if (StatusEffects_active(STATUSEFFECT_Frozen, this))
791 {
792 movelib_brake_simple(this, stpspeed);
793 setanim(this, this.anim_idle, true, false, false);
794 return; // no physics while frozen!
795 }
796
797 if (this.flags & FL_SWIM)
798 {
800 {
801 if (time >= this.last_trace)
802 {
803 this.last_trace = time + 0.4;
804
805 Damage(this, NULL, NULL,
806 2,
807 DEATH_DROWN.m_id,
808 DMG_NOWEP,
809 this.origin,
810 '0 0 0'
811 );
812 this.angles = '90 90 0';
813 if (random() < 0.5)
814 {
815 this.velocity.y += random() * 50;
816 this.velocity.x -= random() * 50;
817 }
818 else
819 {
820 this.velocity.y -= random() * 50;
821 this.velocity.x += random() * 50;
822 }
823 this.velocity.z += random() * 150;
824 }
825
826
828 //this.velocity.z = -200;
829
830 return;
831 }
832 else if (this.move_movetype == MOVETYPE_BOUNCE)
833 {
834 this.angles.x = 0;
836 }
837 }
838
839 entity targ = this.goalentity;
840
841 if (MUTATOR_CALLHOOK(MonsterMove, this, runspeed, walkspeed, targ)
842 || game_stopped
843 || this.draggedby != NULL
847 || time < this.spawn_time)
848 {
849 runspeed = walkspeed = 0;
850 if (time >= this.spawn_time)
851 setanim(this, this.anim_idle, true, false, false);
852 movelib_brake_simple(this, stpspeed);
853 return;
854 }
855
856 targ = M_ARGV(3, entity);
857 runspeed = bound(0, M_ARGV(1, float) * MONSTER_SKILLMOD(this), runspeed * 2.5); // limit maxspeed to prevent craziness
858 walkspeed = bound(0, M_ARGV(2, float) * MONSTER_SKILLMOD(this), walkspeed * 2.5); // limit maxspeed to prevent craziness
859
860 if (this.monster_follow)
863 this.monster_follow = NULL;
864
865 if (this.state == MONSTER_ATTACK_RANGED && IS_ONGROUND(this))
866 {
867 this.state = 0;
868 settouch(this, Monster_Touch);
869 }
870
871 if (this.state && time >= this.attack_finished_single[0])
872 this.state = 0; // attack is over
873
874 // Select destination
875 if (this.state != MONSTER_ATTACK_MELEE // don't move if set
876 && (time >= this.last_trace || this.enemy)) // update enemy or rider instantly
877 this.moveto = Monster_Move_Target(this, targ);
878
879 if (!this.enemy)
880 Monster_Sound(this, monstersound_idle, 7, true, CH_VOICE);
881
882 if (this.state == MONSTER_ATTACK_MELEE)
883 this.moveto = this.origin;
884
885 if (this.enemy && this.enemy.vehicle)
886 runspeed = 0;
887
888 if (!(this.spawnflags & MONSTERFLAG_FLY_VERTICAL) && !(this.flags & FL_SWIM))
889 this.moveto.z = this.origin.z;
890
891 // Steer towards destination
892 this.steerto = steerlib_attract2(this, ((this.monster_face) ? this.monster_face : this.moveto), 0.5, 500, 0.95);
893 vector real_angle = vectoangles(this.steerto) - this.angles;
894
895 if (this.state != MONSTER_ATTACK_MELEE)
896 this.angles.y += bound(-25, shortangle_f(real_angle.y, this.angles.y), 25);
897
898 // Update velocity
900 float vz = this.velocity.z;
901
902 // Check for danger ahead
903 float bboxwidth = min(this.maxs.x - this.mins.x, this.maxs.y - this.mins.y);
904 int danger = Monster_CheckDanger(this, this.origin + this.view_ofs
905 + (vdist(this.velocity, >, bboxwidth * 5) ? this.velocity * 0.2 : v_forward * bboxwidth));
906
907 if (!danger && !turret_closetotarget(this, this.moveto, 16))
908 {
909 bool do_run = (this.enemy || this.monster_moveto);
910 movelib_move_simple(this, v_forward, (do_run ? runspeed : walkspeed), 0.4);
911
912 if (time > this.pain_finished && time > this.anim_finished
913 && !this.state)
914 {
915 if (vdist(this.velocity, >, 10))
916 setanim(this, (do_run ? this.anim_run : this.anim_walk), true, false, false);
917 else
918 setanim(this, this.anim_idle, true, false, false);
919 }
920 }
921 else
922 {
923 entity e = this.goalentity; //find(NULL, targetname, this.target2);
924 if (e.target2 && e.target2 != "")
925 this.target2 = e.target2;
926 else if (e.target && e.target != "") // compatibility
927 this.target2 = e.target;
928
929 movelib_brake_simple(this, stpspeed);
930 if (time > this.anim_finished && time > this.pain_finished
931 && !this.state
932 && vdist(this.velocity, <=, 30))
933 setanim(this, this.anim_idle, true, false, false);
934 }
935
936 if (!(this.flags & (FL_FLY | FL_SWIM)))
937 this.velocity.z = vz;
938
939 // If this path is dangerous try to find a different one next frame
940 if (danger)
941 {
942 this.last_trace = time + 0.3;
943 this.moveto = Monster_WanderTarget(this, this.origin);
944 }
945}
946
948{
949 if (!this || IS_CLIENT(this))
950 return; // don't remove it?
951
952 if (!MUTATOR_CALLHOOK(MonsterRemove, this))
953 Send_Effect(EFFECT_ITEM_PICKUP, this.origin, '0 0 0', 1);
954
955 for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
956 {
957 .entity weaponentity = weaponentities[slot];
958 if (this.(weaponentity))
959 delete(this.(weaponentity));
960 }
962 delete(this);
963}
964
966{
967 this.nextthink = time;
968
969 Monster mon = REGISTRY_GET(Monsters, this.monsterid);
970 mon.mr_deadthink(mon, this);
971
972 if (this.monster_lifetime != 0 && time >= this.monster_lifetime)
973 {
974 Monster_Dead_Fade(this);
975 return;
976 }
977}
978
979void Monster_Appear(entity this, entity actor, entity trigger)
980{
981 this.enemy = actor;
982 Monster_Spawn(this, false, this.monsterdef);
983}
984
985bool Monster_Appear_Check(entity this, Monster monster_id)
986{
987 if (!(this.spawnflags & MONSTERFLAG_APPEAR))
988 return false;
989
990 setthink(this, func_null);
991 this.monsterdef = monster_id; // set so this monster is properly registered (otherwise, normal initialization is used)
992 this.nextthink = 0;
993 this.use = Monster_Appear;
994 this.flags = FL_MONSTER; // set so this monster can get butchered
995
996 return true;
997}
998
1000{
1002 {
1003 Monster_Remove(this);
1004 return;
1005 }
1006
1007 setorigin(this, this.pos1);
1008 this.angles = this.pos2;
1009
1010 SetResourceExplicit(this, RES_HEALTH, this.max_health);
1011 this.velocity = '0 0 0';
1012 this.enemy = NULL;
1013 this.goalentity = NULL;
1014 this.attack_finished_single[0] = 0;
1015 this.moveto = this.origin;
1016}
1017
1018void Monster_Dead_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
1019{
1020 TakeResource(this, RES_HEALTH, damage);
1021
1022 Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, this, attacker);
1023
1024 if (GetResource(this, RES_HEALTH) <= -50) // 100 health until gone?
1025 {
1026 Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, this, attacker);
1027
1028 --totalspawned; // number of monsters spawned with mobspawn command
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
1048
1049 if (IS_PLAYER(attacker)
1052
1053 if (gibbed)
1054 --totalspawned; // number of monsters spawned with mobspawn command
1055
1056 if (!gibbed && this.mdl_dead && this.mdl_dead != "")
1057 _setmodel(this, this.mdl_dead);
1058
1059 this.event_damage = (gibbed ? func_null : Monster_Dead_Damage);
1060 this.event_heal = func_null;
1061 this.solid = SOLID_CORPSE;
1062 this.takedamage = DAMAGE_AIM;
1063 this.deadflag = DEAD_DEAD;
1064 this.enemy = NULL;
1066 this.moveto = this.origin;
1067 settouch(this, Monster_Touch); // reset incase monster was pouncing
1068 this.reset = func_null;
1069 this.state = 0;
1070 this.attack_finished_single[0] = 0;
1071 this.effects = 0;
1073
1074 if (!(this.flags & (FL_FLY | FL_SWIM)))
1075 this.velocity = '0 0 0';
1076
1078
1079 Monster mon = this.monsterdef;
1080 mon.mr_death(mon, this);
1081}
1082
1083void Monster_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
1084{
1085 if (((this.spawnflags & MONSTERFLAG_INVINCIBLE) && deathtype != DEATH_KILL.m_id && !ITEM_DAMAGE_NEEDKILL(deathtype))
1086 //|| (time < this.pain_finished && deathtype != DEATH_KILL.m_id)
1087 || (StatusEffects_active(STATUSEFFECT_SpawnShield, this) && deathtype != DEATH_KILL.m_id)
1088 || (deathtype == DEATH_FALL.m_id && this.draggedby != NULL))
1089 return;
1090
1091 vector v = healtharmor_applydamage(100, GetResource(this, RES_ARMOR) / 100, deathtype, damage);
1092 float take = v.x;
1093 //float save = v.y;
1094
1095 Monster mon = this.monsterdef;
1096 take = mon.mr_pain(mon, this, take, attacker, deathtype);
1097
1098 if (take)
1099 {
1100 TakeResource(this, RES_HEALTH, take);
1101 Monster_Sound(this, monstersound_pain, 1.2, true, CH_PAIN);
1102 }
1103
1104 if (this.sprite)
1105 WaypointSprite_UpdateHealth(this.sprite, GetResource(this, RES_HEALTH));
1106
1107 this.dmg_time = time;
1108
1109 if (deathtype != DEATH_DROWN.m_id && deathtype != DEATH_FIRE.m_id && sound_allowed(MSG_BROADCAST, attacker))
1110 spamsound(this, CH_PAIN, SND_BODYIMPACT1, VOL_BASE, ATTEN_NORM); // FIXME: PLACEHOLDER
1111
1112 this.velocity += force * this.damageforcescale;
1113
1114 if (deathtype != DEATH_DROWN.m_id && take)
1115 {
1116 Violence_GibSplash_At(hitloc, force, 2, bound(0, take, 200) / 16, this, attacker);
1117 if (take > 50)
1118 Violence_GibSplash_At(hitloc, force * -0.1, 3, 1, this, attacker);
1119 if (take > 100)
1120 Violence_GibSplash_At(hitloc, force * -0.2, 3, 1, this, attacker);
1121 }
1122
1123 if (GetResource(this, RES_HEALTH) <= 0)
1124 {
1125 if (deathtype == DEATH_KILL.m_id)
1126 this.candrop = false; // killed by mobkill command
1127
1128 // TODO: fix this?
1129 SUB_UseTargets(this, attacker, this.enemy);
1130 this.target2 = this.oldtarget2; // reset to original target on death, incase we respawn
1131
1132 Monster_Dead(this, attacker, (GetResource(this, RES_HEALTH) <= -100 || deathtype == DEATH_KILL.m_id));
1133
1135
1136 MUTATOR_CALLHOOK(MonsterDies, this, attacker, deathtype);
1137
1138 if (GetResource(this, RES_HEALTH) <= -100 || deathtype == DEATH_KILL.m_id) // check if we're already gibbed
1139 {
1140 Violence_GibSplash(this, 1, 0.5, attacker);
1141
1142 setthink(this, SUB_Remove);
1143 this.nextthink = time + 0.1;
1144 }
1145 }
1146}
1147
1148bool Monster_Heal(entity targ, entity inflictor, float amount, float limit)
1149{
1150 float true_limit = (limit != RES_LIMIT_NONE) ? limit : targ.max_health;
1151 if (GetResource(targ, RES_HEALTH) <= 0 || GetResource(targ, RES_HEALTH) >= true_limit)
1152 return false;
1153
1154 GiveResourceWithLimit(targ, RES_HEALTH, amount, true_limit);
1155 if (targ.sprite)
1156 WaypointSprite_UpdateHealth(targ.sprite, GetResource(targ, RES_HEALTH));
1157 return true;
1158}
1159
1160// don't check for enemies, just keep walking in a straight line
1161void Monster_Move_2D(entity this, float mspeed, bool allow_jumpoff)
1162{
1164 || time < game_starttime || time < this.spawn_time
1166 {
1167 mspeed = 0;
1168 if (time >= this.spawn_time)
1169 setanim(this, this.anim_idle, true, false, false);
1170 movelib_brake_simple(this, 0.6);
1171 return;
1172 }
1173
1174 makevectors(this.angles);
1175 vector a = CENTER_OR_VIEWOFS(this);
1176 vector b = CENTER_OR_VIEWOFS(this) + v_forward * 32;
1177
1178 traceline(a, b, MOVE_NORMAL, this);
1179
1180 bool reverse = false;
1181 if (trace_fraction != 1.0)
1182 reverse = true;
1184 reverse = false;
1186 reverse = true;
1187
1188 if (!allow_jumpoff && IS_ONGROUND(this))
1189 {
1190 traceline(b, b - '0 0 32', MOVE_NORMAL, this);
1191 if (trace_fraction == 1.0)
1192 reverse = true;
1193 }
1194
1195 if (reverse)
1196 {
1197 this.angles.y = anglemods(this.angles.y - 180);
1198 makevectors(this.angles);
1199 }
1200
1201 movelib_move_simple_gravity(this, v_forward, mspeed, 1);
1202
1203 if (time > this.pain_finished && time > this.attack_finished_single[0])
1204 {
1205 if (vdist(this.velocity, >, 10))
1206 setanim(this, this.anim_walk, true, false, false);
1207 else
1208 setanim(this, this.anim_idle, true, false, false);
1209 }
1210}
1211
1213{
1214 int deadbits = (this.anim_state & (ANIMSTATE_DEAD1 | ANIMSTATE_DEAD2));
1215 if (IS_DEAD(this))
1216 {
1217 if (!deadbits)
1218 deadbits = (random() < 0.5) // Decide on which death animation to use.
1221 }
1222 else
1223 deadbits = 0; // Clear a previous death animation.
1224
1225 int animbits = deadbits;
1226 if (StatusEffects_active(STATUSEFFECT_Frozen, this))
1227 animbits |= ANIMSTATE_FROZEN;
1228 if (IS_DUCKED(this))
1229 animbits |= ANIMSTATE_DUCK; // not that monsters can crouch currently...
1230 animdecide_setstate(this, animbits, false);
1232
1233 /* // weapon entities for monsters?
1234 if (this.weaponentity)
1235 {
1236 updateanim(this.weaponentity);
1237 if (!this.weaponentity.animstate_override)
1238 setanim(this.weaponentity, this.weaponentity.anim_idle, true, false, false);
1239 }
1240 */
1241}
1242
1244{
1245 if (this.enemy)
1246 {
1247 vector targ_origin = ((this.enemy.absmin + this.enemy.absmax) * 0.5);
1248 targ_origin = WarpZone_RefSys_TransformOrigin(this.enemy, this, targ_origin); // origin of target as seen by the monster (us)
1249 WarpZone_TraceLine(this.origin, targ_origin, MOVE_NOMONSTERS, this);
1250
1251 // cases where the enemy may have changed their state (don't need to check everything here)
1252 // TODO: mutator hook
1253 if (IS_DEAD(this.enemy) || GetResource(this.enemy, RES_HEALTH) < 1
1254 || STAT(FROZEN, this.enemy)
1255 || (this.enemy.flags & FL_NOTARGET)
1256 || (this.enemy.alpha < 0.5 && this.enemy.alpha != 0)
1257 || this.enemy.takedamage == DAMAGE_NO
1258 || vdist(this.origin - targ_origin, >, this.target_range)
1259 || (trace_fraction < 1 && trace_ent != this.enemy))
1260 this.enemy = NULL;
1261 else
1262 return;
1263 }
1264
1265 this.enemy = Monster_FindTarget(this);
1266 if (this.enemy)
1267 {
1268 WarpZone_RefSys_Copy(this.enemy, this);
1269 WarpZone_RefSys_AddInverse(this.enemy, this); // wz1^-1 ... wzn^-1 receiver
1270 // update move target immediately?
1271 this.moveto = WarpZone_RefSys_TransformOrigin(this.enemy, this, 0.5 * (this.enemy.absmin + this.enemy.absmax));
1272 this.monster_moveto = '0 0 0';
1273 this.monster_face = '0 0 0';
1274
1275 Monster_Sound(this, monstersound_sight, 0, false, CH_VOICE);
1276 }
1277}
1278
1280{
1281 setthink(this, Monster_Think);
1282 this.nextthink = time;
1283
1284 if (this.monster_lifetime && time >= this.monster_lifetime)
1285 {
1286 Damage(this, this, this,
1287 GetResource(this, RES_HEALTH) + this.max_health,
1288 DEATH_KILL.m_id,
1289 DMG_NOWEP,
1290 this.origin,
1291 this.origin
1292 );
1293 return;
1294 }
1295
1296 // TODO: mutator hook to control monster thinking
1297 if (StatusEffects_active(STATUSEFFECT_Frozen, this))
1298 this.enemy = NULL; // TODO: save enemy, and attack when revived?
1299 else if (time >= this.last_enemycheck)
1300 {
1301 Monster_Enemy_Check(this);
1302 this.last_enemycheck = time + 1; // check for enemies every second
1303 }
1304
1305 Monster mon = this.monsterdef;
1306 if (mon.mr_think(mon, this))
1307 {
1308 Monster_Move(this, this.speed2, this.speed, this.stopspeed);
1309
1310 .entity weaponentity = weaponentities[0]; // TODO?
1311 Monster_Attack_Check(this, this.enemy, weaponentity);
1312 }
1313
1314 Monster_Anim(this);
1315
1317}
1318
1320{
1321 Monster mon = this.monsterdef;
1322 mon.mr_setup(mon, this);
1323
1324 // ensure some basic needs are met
1325 if (!GetResource(this, RES_HEALTH))
1326 SetResourceExplicit(this, RES_HEALTH, 100);
1327 if (!GetResource(this, RES_ARMOR))
1328 SetResourceExplicit(this, RES_ARMOR, bound(0.2, 0.5 * MONSTER_SKILLMOD(this), 0.9));
1329 if (!this.target_range) this.target_range = autocvar_g_monsters_target_range;
1334
1336
1337 if (!(this.spawnflags & MONSTERFLAG_RESPAWNED))
1338 {
1339 SetResourceExplicit(this, RES_HEALTH, GetResource(this, RES_HEALTH) * MONSTER_SKILLMOD(this));
1340
1341 if (!this.skin)
1342 this.skin = rint(random() * 4);
1343 }
1344
1345 this.max_health = GetResource(this, RES_HEALTH);
1346 this.pain_finished = this.nextthink;
1347 this.last_enemycheck = this.spawn_time + random(); // slight delay
1348
1349 if (!this.wander_delay)
1350 this.wander_delay = 2;
1351 if (!this.wander_distance)
1352 this.wander_distance = 600;
1353
1356
1357 if (teamplay)
1358 {
1359 if (!this.monster_attack)
1361 this.monster_attack = true; // we can have monster enemies in team games
1362 }
1363
1364 Monster_Sound(this, monstersound_spawn, 0, false, CH_VOICE);
1365
1367 {
1368 entity wp = WaypointSprite_Spawn(WP_Monster, 0, 1024, this, '0 0 1' * (this.maxs.z + 15), NULL, this.team, this, sprite, true, RADARICON_DANGER);
1369 wp.wp_extra = this.monsterdef.monsterid;
1370 wp.colormod = (this.team ? Team_ColorRGB(this.team) : '1 0 0');
1371 if (!(this.spawnflags & MONSTERFLAG_INVINCIBLE))
1372 {
1374 WaypointSprite_UpdateHealth(this.sprite, GetResource(this, RES_HEALTH));
1375 }
1376 }
1377
1378 setthink(this, Monster_Think);
1379 this.nextthink = time;
1380
1381 if (MUTATOR_CALLHOOK(MonsterSpawn, this))
1382 return false;
1383
1384 return true;
1385}
1386
1388bool Monster_Spawn(entity this, bool check_appear, Monster mon)
1389{
1390 if (!mon || mon == MON_Null) // invalid monster
1391 return false;
1393 {
1394 Monster_Remove(this);
1395 return false;
1396 }
1397
1398 if (!(this.spawnflags & MONSTERFLAG_RESPAWNED) && !(this.flags & FL_MONSTER))
1399 {
1400 IL_PUSH(g_monsters, this);
1401 if (this.mdl && this.mdl != "")
1402 precache_model(this.mdl);
1403 if (this.mdl_dead && this.mdl_dead != "")
1404 precache_model(this.mdl_dead);
1405 }
1406
1407 if (check_appear && Monster_Appear_Check(this, mon))
1408 return true; // return true so the monster isn't removed
1409
1410 if (!this.monster_skill)
1411 this.monster_skill = cvar("g_monsters_skill");
1412
1413 // support for quake style removing monsters based on skill
1417 {
1418 Monster_Remove(this);
1419 return false;
1420 }
1421
1422 if (this.team && !teamplay)
1423 this.team = 0;
1424
1425 if (!(this.spawnflags & MONSTERFLAG_SPAWNED) // naturally spawned monster
1426 && !(this.spawnflags & MONSTERFLAG_RESPAWNED)) // don't count re-spawning monsters either
1428
1429 if (this.mdl && this.mdl != "")
1430 _setmodel(this, this.mdl);
1431 else
1432 setmodel(this, mon.m_model);
1433
1434 if (!this.m_name || this.m_name == "")
1435 this.m_name = mon.m_name;
1436
1437 if (this.statuseffects && this.statuseffects.owner == this)
1438 {
1441 }
1442 else
1443 this.statuseffects = NULL;
1444
1445 this.flags = FL_MONSTER;
1446 this.classname = "monster";
1447 this.takedamage = DAMAGE_AIM;
1448 if (!this.bot_attack)
1449 IL_PUSH(g_bot_targets, this);
1450 this.bot_attack = true;
1451 this.iscreature = true;
1452 this.teleportable = true;
1453 if (!this.damagedbycontents)
1455 this.damagedbycontents = true;
1456 this.monsterdef = mon;
1457 this.event_damage = Monster_Damage;
1458 this.event_heal = Monster_Heal;
1459 settouch(this, Monster_Touch);
1460 this.use = Monster_Use;
1461 this.solid = SOLID_BBOX;
1463 StatusEffects_apply(STATUSEFFECT_SpawnShield, this, time + autocvar_g_monsters_spawnshieldtime, 0);
1464 this.enemy = NULL;
1465 this.velocity = '0 0 0';
1466 this.moveto = this.origin;
1467 this.pos1 = this.origin;
1468 this.pos2 = this.angles;
1469 this.reset = Monster_Reset;
1470 this.netname = mon.netname;
1471 this.monster_attackfunc = mon.monster_attackfunc;
1472 this.candrop = true;
1473 this.oldtarget2 = this.target2;
1474 this.deadflag = DEAD_NO;
1475 this.spawn_time = time;
1476 this.gravity = 1;
1477 this.monster_moveto = '0 0 0';
1478 this.monster_face = '0 0 0';
1480
1481 if (!this.noalign) this.noalign = (mon.spawnflags & (MONSTER_TYPE_FLY | MONSTER_TYPE_SWIM));
1482 if (!this.scale) this.scale = 1;
1483 if (autocvar_g_monsters_edit) this.grab = 1;
1486 if (mon.spawnflags & MONSTER_TYPE_SWIM) this.flags |= FL_SWIM;
1487
1490
1491 if (mon.spawnflags & MONSTER_TYPE_FLY)
1492 {
1493 this.flags |= FL_FLY;
1495 }
1496
1498 this.scale *= 1.3;
1499
1500 setsize(this, RoundPerfectVector(mon.m_mins * this.scale), RoundPerfectVector(mon.m_maxs * this.scale));
1501 this.view_ofs = '0 0 0.35' * this.maxs.z;
1502
1503 Monster_UpdateModel(this);
1504
1505 if (!Monster_Spawn_Setup(this))
1506 {
1507 Monster_Remove(this);
1508 return false;
1509 }
1510
1511 if (!this.noalign)
1512 {
1513 setorigin(this, this.origin + '0 0 20');
1514 tracebox(this.origin + '0 0 64', this.mins, this.maxs, this.origin - '0 0 10000', MOVE_WORLDONLY, this);
1515 setorigin(this, trace_endpos);
1516 }
1517
1518 if (!nudgeoutofsolid_OrFallback(this))
1519 {
1520 // Stuck and not fixable
1521 Monster_Remove(this);
1522 return false;
1523 }
1524
1525 if (!(this.spawnflags & MONSTERFLAG_RESPAWNED))
1526 monster_setupcolors(this);
1527
1528 CSQCMODEL_AUTOINIT(this);
1529
1530 return true;
1531}
1532
1533#undef ALLMONSTERSOUNDS
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:44
string m_name
human readable name
Definition monster.qh:42
virtual void mr_deadthink()
(SERVER) logic to run every frame after the monster has died
Definition monster.qh:70
virtual void mr_death()
(SERVER) called when monster dies
Definition monster.qh:76
vector m_mins
hitbox size
Definition monster.qh:53
virtual void mr_setup()
(SERVER) setup monster data
Definition monster.qh:58
virtual void mr_anim()
(BOTH?) sets animations for monster
Definition monster.qh:90
virtual void mr_pain()
(SERVER) called when monster is damaged
Definition monster.qh:82
int spawnflags
attributes
Definition monster.qh:39
virtual void mr_think()
(SERVER) logic to run every frame
Definition monster.qh:64
vector m_maxs
hitbox size
Definition monster.qh:55
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:241
#define IS_DEAD(s)
Definition player.qh:244
#define PHYS_INPUT_BUTTON_CHAT(s)
Definition player.qh:161
vector v_angle
Definition player.qh:236
float waterlevel
Definition player.qh:225
#define IS_DUCKED(s)
Definition player.qh:212
#define IS_PLAYER(s)
Definition player.qh:242
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:1413
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:493
float damagedbycontents
Definition damage.qh:45
IntrusiveList g_damagedbycontents
Definition damage.qh:143
#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:120
RES_ARMOR
Definition ent_cs.qc:130
ent angles
Definition ent_cs.qc:121
model
Definition ent_cs.qc:139
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:78
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:741
vector WarpZone_RefSys_TransformOrigin(entity from, entity to, vector org)
Definition common.qc:763
void WarpZone_RefSys_Copy(entity me, entity from)
Definition common.qc:795
entity goalentity
Definition viewloc.qh:16
bool autocvar_g_campaign
Definition menu.qc:752
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:21
const int MONSTER_TYPE_FLY
Definition monster.qh:6
const int MON_FLAG_RANGED
monster shoots projectiles
Definition monster.qh:10
const int MONSTER_RESPAWN_DEATHPOINT
re-spawn where we died
Definition monster.qh:5
vector anim_walk
Definition monster.qh:31
const int MONSTER_TYPE_SWIM
Definition monster.qh:7
const int MONSTER_SIZE_QUAKE
Definition monster.qh:14
void movelib_brake_simple(entity this, float force)
Definition movelib.qc:163
vector moveto
Definition movelib.qc:4
#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:123
bool noalign
Definition items.qh:37
float respawntime
Definition items.qh:31
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:139
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)
Setup the basic required properties for a monster.
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
indicates whether an entity can be attacked by monsters
#define ALLMONSTERSOUNDS
const int MONSTERFLAG_NORESPAWN
monster doesn't respawn (revive)
#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
ALLMONSTERSOUNDS bool GetMonsterSoundSampleField_notFound
const int MONSTER_MOVE_FOLLOW
monster will follow if in range, or stand still
vector monster_moveto
custom destination for monster (reset to '0 0 0' when you're done!)
float anim_finished
will be phased out when we have proper animations system
const int MONSTERSKILL_NOTHARD
monster will not spawn on skill >= 3
entity monster_follow
monster follow target
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
used only as a movestate, for monsters
float wander_delay
monster logic delay between moving while idle
int monster_skill
const int MONSTERFLAG_SPAWNED
flag for spawned monsters
float wander_distance
distance to move between wander delays for monsters
bool autocvar_g_monsters_typefrag
bool autocvar_g_monsters_sounds
Definition sv_monsters.qh:7
const int MONSTERFLAG_INFRONT
only check for enemies infront of us, for monsters
float spawn_time
delay monster thinking until spawn animation has completed
float last_trace
monster logic delay between target tracing
const int MONSTERSKILL_NOTMEDIUM
monster will not spawn on skill 2
int monsters_killed
IntrusiveList g_monster_targets
float autocvar_g_monsters_damageforcescale
float stopspeed
const int MONSTERFLAG_APPEAR
delay monster spawn until triggered
int monsters_total
const int MONSTER_MOVE_SPAWNLOC
monster will move to its spawn location when not attacking
const int MONSTERFLAG_MINIBOSS
monster spawns as mini-boss (also has a chance of naturally becoming one)
const int MONSTER_MOVE_WANDER
monster will ignore owner & wander around
float autocvar_g_monsters_miniboss_chance
float autocvar_g_monsters_spawnshieldtime
float autocvar_g_monsters
Definition sv_monsters.qh:5
vector monster_face
custom looking direction for monster (reset to '0 0 0' when you're done!)
float autocvar_g_monsters_target_range
bool candrop
toggle to allow disabling monster item drops
const int MONSTER_SKILL_INSANE
float attack_range
melee attack if closer, ranged attack if further away (TODO: separate ranged attack range?...
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
monster run speed
const int MONSTER_SKILL_HARD
const int MONSTER_SKILL_NIGHTMARE
float autocvar_g_monsters_miniboss_healthboost
const int MONSTERFLAG_INVINCIBLE
monster doesn't take damage (may be used for map objects & temporary monsters)
int autocvar_g_monsters_score_kill
bool autocvar_g_monsters_target_infront
const int MONSTERFLAG_RESPAWNED
flag for respawned monsters
string oldtarget2
a copy of the original follow target string for monsters
const int MONSTERSKILL_NOTEASY
monster will not spawn on skill <= 1
const int MONSTER_ATTACK_RANGED
int monster_movestate
monster move target priority
const int MONSTERFLAG_FLY_VERTICAL
monster can fly/swim vertically
float last_enemycheck
for checking monster enemy
IntrusiveList g_monsters
bool autocvar_g_monsters_teams
const int MONSTER_MOVE_NOMOVE
monster simply stands still
float autocvar_g_monsters_target_infront_range
float msound_delay
temporary antilag system
float monster_lifetime
monster dies instantly after this delay, set from spawn
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:23
#define IS_VEHICLE(v)
Definition utils.qh:24
#define CENTER_OR_VIEWOFS(ent)
Definition utils.qh:31
#define vdist(v, cmp, f)
Vector distance comparison, avoids sqrt()
Definition vector.qh:8
ERASEABLE vector RoundPerfectVector(vector v)
Definition vector.qh:209
#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:424
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]