From: Mario Date: Tue, 11 Aug 2015 12:47:32 +0000 (+1000) Subject: Reintroduce the T-virus X-Git-Tag: xonotic-v0.8.2~1997^2~4 X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=9ca8996c2022c821fe36f687b84034bec8269b55;p=xonotic%2Fxonotic-data.pk3dir.git Reintroduce the T-virus --- diff --git a/qcsrc/client/csqcmodel_hooks.qc b/qcsrc/client/csqcmodel_hooks.qc index 1981c171d..979809f87 100644 --- a/qcsrc/client/csqcmodel_hooks.qc +++ b/qcsrc/client/csqcmodel_hooks.qc @@ -635,8 +635,12 @@ void CSQCModel_Hook_PreDraw(bool isplayer) if(self.isplayermodel) // this checks if it's a player MODEL! { - CSQCPlayer_ModelAppearance_Apply(self.entnum == player_localnum + 1); - CSQCPlayer_LOD_Apply(); + if(isplayer) + { + CSQCPlayer_ModelAppearance_Apply(self.entnum == player_localnum + 1); + CSQCPlayer_LOD_Apply(); + } + if(!isplayer) { skeleton_loadinfo(self); diff --git a/qcsrc/common/animdecide.qc b/qcsrc/common/animdecide.qc index 0cba5d7f3..bc8f96de5 100644 --- a/qcsrc/common/animdecide.qc +++ b/qcsrc/common/animdecide.qc @@ -10,42 +10,6 @@ #include "../server/defs.qh" #endif -// player animation data for this model -// each vector is as follows: -// _x = startframe -// _y = numframes -// _z = framerate -.vector anim_die1; // player dies -.vector anim_die2; // player dies differently -.vector anim_draw; // player pulls out a weapon -.vector anim_duckwalk; // player walking while crouching -.vector anim_duckjump; // player jumping from a crouch -.vector anim_duckidle; // player idling while crouching -.vector anim_idle; // player standing -.vector anim_jump; // player jump -.vector anim_pain1; // player flinches from pain -.vector anim_pain2; // player flinches from pain, differently -.vector anim_shoot; // player shoots -.vector anim_taunt; // player taunts others (FIXME: no code references this) -.vector anim_run; // player running forward -.vector anim_runbackwards; // player running backward -.vector anim_strafeleft; // player shuffling left quickly -.vector anim_straferight; // player shuffling right quickly -.vector anim_forwardright; // player running forward and right -.vector anim_forwardleft; // player running forward and left -.vector anim_backright; // player running backward and right -.vector anim_backleft; // player running back and left -.vector anim_melee; // player doing the melee action -.vector anim_duck; // player doing the melee action -.vector anim_duckwalkbackwards; -.vector anim_duckwalkstrafeleft; -.vector anim_duckwalkstraferight; -.vector anim_duckwalkforwardright; -.vector anim_duckwalkforwardleft; -.vector anim_duckwalkbackright; -.vector anim_duckwalkbackleft; -.float animdecide_modelindex; - void animdecide_load_if_needed(entity e) { if(e.modelindex == e.animdecide_modelindex) diff --git a/qcsrc/common/animdecide.qh b/qcsrc/common/animdecide.qh index 9dc7cf7db..f1bdcb7e9 100644 --- a/qcsrc/common/animdecide.qh +++ b/qcsrc/common/animdecide.qh @@ -8,6 +8,42 @@ void animdecide_load_if_needed(entity e); void animdecide_setimplicitstate(entity e, float onground); void animdecide_setframes(entity e, bool support_blending, .int fld_frame, .int fld_frame1time, .int fld_frame2, .int fld_frame2time); +// player animation data for this model +// each vector is as follows: +// _x = startframe +// _y = numframes +// _z = framerate +.vector anim_die1; // player dies +.vector anim_die2; // player dies differently +.vector anim_draw; // player pulls out a weapon +.vector anim_duckwalk; // player walking while crouching +.vector anim_duckjump; // player jumping from a crouch +.vector anim_duckidle; // player idling while crouching +.vector anim_idle; // player standing +.vector anim_jump; // player jump +.vector anim_pain1; // player flinches from pain +.vector anim_pain2; // player flinches from pain, differently +.vector anim_shoot; // player shoots +.vector anim_taunt; // player taunts others (FIXME: no code references this) +.vector anim_run; // player running forward +.vector anim_runbackwards; // player running backward +.vector anim_strafeleft; // player shuffling left quickly +.vector anim_straferight; // player shuffling right quickly +.vector anim_forwardright; // player running forward and right +.vector anim_forwardleft; // player running forward and left +.vector anim_backright; // player running backward and right +.vector anim_backleft; // player running back and left +.vector anim_melee; // player doing the melee action +.vector anim_duck; // player doing the melee action +.vector anim_duckwalkbackwards; +.vector anim_duckwalkstrafeleft; +.vector anim_duckwalkstraferight; +.vector anim_duckwalkforwardright; +.vector anim_duckwalkforwardleft; +.vector anim_duckwalkbackright; +.vector anim_duckwalkbackleft; +.float animdecide_modelindex; + // please network this one .int anim_state; .float anim_time; diff --git a/qcsrc/common/monsters/all.inc b/qcsrc/common/monsters/all.inc index d30f29894..67b510a7d 100644 --- a/qcsrc/common/monsters/all.inc +++ b/qcsrc/common/monsters/all.inc @@ -1,3 +1,8 @@ +#ifndef MENUQC +#include "../animdecide.qh" +vector animfixfps(entity e, vector a, vector b); +#endif + #include "monster/zombie.qc" #include "monster/spider.qc" #include "monster/mage.qc" diff --git a/qcsrc/common/monsters/all.qc b/qcsrc/common/monsters/all.qc index 37dbcb325..f866f0dbc 100644 --- a/qcsrc/common/monsters/all.qc +++ b/qcsrc/common/monsters/all.qc @@ -6,7 +6,7 @@ entity monster_info[MON_MAXCOUNT]; entity dummy_monster_info; -void register_monster(int id, float(float) func, int monsterflags, vector min_s, vector max_s, string modelname, string shortname, string mname) +void register_monster(int id, float(float) func, int(float) attackfunc, int monsterflags, vector min_s, vector max_s, string modelname, string shortname, string mname) { entity e; monster_info[id - 1] = e = spawn(); @@ -15,6 +15,7 @@ void register_monster(int id, float(float) func, int monsterflags, vector min_s, e.netname = shortname; e.monster_name = mname; e.monster_func = func; + e.monster_attackfunc = attackfunc; e.mdl = modelname; e.spawnflags = monsterflags; e.mins = min_s; @@ -30,6 +31,7 @@ void register_monsters_done() dummy_monster_info.netname = ""; dummy_monster_info.monster_name = "Monster"; dummy_monster_info.monster_func = m_null; + dummy_monster_info.monster_attackfunc = m_null; dummy_monster_info.mdl = ""; dummy_monster_info.mins = '-0 -0 -0'; dummy_monster_info.maxs = '0 0 0'; diff --git a/qcsrc/common/monsters/all.qh b/qcsrc/common/monsters/all.qh index ce02312f1..570254e92 100644 --- a/qcsrc/common/monsters/all.qh +++ b/qcsrc/common/monsters/all.qh @@ -8,8 +8,10 @@ const int MR_SETUP = 1; // (SERVER) setup monster data const int MR_THINK = 2; // (SERVER) logic to run every frame const int MR_DEATH = 3; // (SERVER) called when monster dies const int MR_PRECACHE = 4; // (BOTH) precaches models/sounds used by this monster +const int MR_PAIN = 5; // (SERVER) called when monster is damaged +const int MR_ANIM = 6; // (BOTH?) sets animations for monster -// functions: +// functions entity get_monsterinfo(float id); // special spawn flags @@ -21,49 +23,65 @@ const int MON_FLAG_SUPERMONSTER = 256; // incredibly powerful monster const int MON_FLAG_RANGED = 512; // monster shoots projectiles const int MON_FLAG_MELEE = 1024; -// entity properties of monsterinfo: +// entity properties of monsterinfo .float monsterid; // MON_... .string netname; // short name .string monster_name; // human readable name -.float(float) monster_func; // m_... +.float(float) monster_func; // M_... +.float(float attack_type) monster_attackfunc; // attack function .string mdl; // currently a copy of the model .string model; // full name of model .int spawnflags; .vector mins, maxs; // monster hitbox size +// animations +.vector anim_blockend; +.vector anim_blockstart; +.vector anim_melee1; +.vector anim_melee2; +.vector anim_melee3; +.vector anim_pain3; +.vector anim_pain4; +.vector anim_pain5; +.vector anim_walk; +.vector anim_spawn; + // other useful macros #define MON_ACTION(monstertype,mrequest) (get_monsterinfo(monstertype)).monster_func(mrequest) -#define M_NAME(monstertype) (get_monsterinfo(monstertype)).monster_name // ===================== // Monster Registration // ===================== float m_null(float dummy); -void register_monster(float id, float(float) func, float monsterflags, vector min_s, vector max_s, string modelname, string shortname, string mname); +void register_monster(float id, float(float) func, float(float) attackfunc, float monsterflags, vector min_s, vector max_s, string modelname, string shortname, string mname); void register_monsters_done(); -const int MON_MAXCOUNT = 24; +const int MON_MAXCOUNT = 24; // increase as necessary, limit is infinite, but keep loops small! const int MON_FIRST = 1; int MON_COUNT; int MON_LAST; -#define REGISTER_MONSTER_2(id,func,monsterflags,min_s,max_s,modelname,shortname,mname) \ +#define REGISTER_MONSTER_2(id,func,attackfunc,monsterflags,min_s,max_s,modelname,shortname,mname) \ int id; \ float func(float); \ + float attackfunc(float); \ void RegisterMonsters_##id() \ { \ MON_LAST = (id = MON_FIRST + MON_COUNT); \ ++MON_COUNT; \ - register_monster(id,func,monsterflags,min_s,max_s,modelname,shortname,mname); \ + register_monster(id,func,attackfunc,monsterflags,min_s,max_s,modelname,shortname,mname); \ } \ ACCUMULATE_FUNCTION(RegisterMonsters, RegisterMonsters_##id) -#ifdef MENUQC -#define REGISTER_MONSTER(id,func,monsterflags,min_s,max_s,modelname,shortname,mname) \ - REGISTER_MONSTER_2(MON_##id,m_null,monsterflags,min_s,max_s,modelname,shortname,mname) +#ifdef SVQC +#define REGISTER_MONSTER(id,func,attackfunc,monsterflags,min_s,max_s,modelname,shortname,mname) \ + REGISTER_MONSTER_2(MON_##id,func,attackfunc,monsterflags,min_s,max_s,modelname,shortname,mname) +#elif defined(CSQC) +#define REGISTER_MONSTER(id,func,attackfunc,monsterflags,min_s,max_s,modelname,shortname,mname) \ + REGISTER_MONSTER_2(MON_##id,func,m_null,monsterflags,min_s,max_s,modelname,shortname,mname) #else -#define REGISTER_MONSTER(id,func,monsterflags,min_s,max_s,modelname,shortname,mname) \ - REGISTER_MONSTER_2(MON_##id,func,monsterflags,min_s,max_s,modelname,shortname,mname) +#define REGISTER_MONSTER(id,func,attackfunc,monsterflags,min_s,max_s,modelname,shortname,mname) \ + REGISTER_MONSTER_2(MON_##id,m_null,m_null,monsterflags,min_s,max_s,modelname,shortname,mname) #endif #include "all.inc" diff --git a/qcsrc/common/monsters/monster/mage.qc b/qcsrc/common/monsters/monster/mage.qc index 1aab262fd..9ca63bd96 100644 --- a/qcsrc/common/monsters/monster/mage.qc +++ b/qcsrc/common/monsters/monster/mage.qc @@ -1,7 +1,7 @@ #ifdef REGISTER_MONSTER REGISTER_MONSTER( /* MON_##id */ MAGE, -/* function */ m_mage, +/* functions */ M_Mage, M_Mage_Attack, /* spawnflags */ MON_FLAG_MELEE | MON_FLAG_RANGED, /* mins,maxs */ '-36 -36 -24', '36 36 50', /* model */ "mage.dpm", @@ -12,6 +12,7 @@ REGISTER_MONSTER( #else #ifdef SVQC float autocvar_g_monster_mage_health; +float autocvar_g_monster_mage_damageforcescale = 0.5; float autocvar_g_monster_mage_attack_spike_damage; float autocvar_g_monster_mage_attack_spike_radius; float autocvar_g_monster_mage_attack_spike_delay; @@ -39,26 +40,29 @@ float autocvar_g_monster_mage_speed_stop; float autocvar_g_monster_mage_speed_run; float autocvar_g_monster_mage_speed_walk; +/* const float mage_anim_idle = 0; const float mage_anim_walk = 1; const float mage_anim_attack = 2; const float mage_anim_pain = 3; const float mage_anim_death = 4; const float mage_anim_run = 5; +*/ -void() mage_heal; -void() mage_shield; +void() M_Mage_Defend_Heal; +void() M_Mage_Defend_Shield; .entity mage_spike; -.float shield_ltime; +.float mage_shield_delay; +.float mage_shield_time; -float friend_needshelp(entity e) +float M_Mage_Defend_Heal_Check(entity e) { if(e == world) return false; if(e.health <= 0) return false; - if(DIFF_TEAM(e, self) && e != self.monster_owner) + if(DIFF_TEAM(e, self) && e != self.monster_follow) return false; if(e.frozen) return false; @@ -78,7 +82,7 @@ float friend_needshelp(entity e) return false; } -void mage_spike_explode() +void M_Mage_Attack_Spike_Explode() { self.event_damage = func_null; @@ -92,15 +96,15 @@ void mage_spike_explode() remove (self); } -void mage_spike_touch() +void M_Mage_Attack_Spike_Touch() { PROJECTILE_TOUCH; - mage_spike_explode(); + M_Mage_Attack_Spike_Explode(); } // copied from W_Seeker_Think -void mage_spike_think() +void M_Mage_Attack_Spike_Think() { entity e; vector desireddir, olddir, newdir, eorg; @@ -111,7 +115,7 @@ void mage_spike_think() if (time > self.ltime || self.enemy.health <= 0 || self.owner.health <= 0) { self.projectiledeathtype |= HITTYPE_SPLASH; - mage_spike_explode(); + M_Mage_Attack_Spike_Explode(); } spd = vlen(self.velocity); @@ -163,7 +167,7 @@ void mage_spike_think() UpdateCSQCProjectile(self); } -void mage_attack_spike() +void M_Mage_Attack_Spike() { entity missile; vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin); @@ -172,7 +176,7 @@ void mage_attack_spike() missile = spawn (); missile.owner = missile.realowner = self; - missile.think = mage_spike_think; + missile.think = M_Mage_Attack_Spike_Think; missile.ltime = time + 7; missile.nextthink = time; missile.solid = SOLID_BBOX; @@ -183,19 +187,19 @@ void mage_attack_spike() missile.velocity = dir * 400; missile.avelocity = '300 300 300'; missile.enemy = self.enemy; - missile.touch = mage_spike_touch; + missile.touch = M_Mage_Attack_Spike_Touch; self.mage_spike = missile; CSQCProjectile(missile, true, PROJECTILE_MAGE_SPIKE, true); } -void mage_heal() +void M_Mage_Defend_Heal() { entity head; float washealed = false; - for(head = findradius(self.origin, (autocvar_g_monster_mage_heal_range)); head; head = head.chain) if(friend_needshelp(head)) + for(head = findradius(self.origin, (autocvar_g_monster_mage_heal_range)); head; head = head.chain) if(M_Mage_Defend_Heal_Check(head)) { washealed = true; string fx = ""; @@ -234,29 +238,30 @@ void mage_heal() { pointparticles(particleeffectnum("healing_fx"), head.origin, '0 0 0', 1); head.health = bound(0, head.health + (autocvar_g_monster_mage_heal_allies), head.max_health); - if(!(head.spawnflags & MONSTERFLAG_INVINCIBLE)) + if(!(head.spawnflags & MONSTERFLAG_INVINCIBLE) && head.sprite) WaypointSprite_UpdateHealth(head.sprite, head.health); } } if(washealed) { - self.frame = mage_anim_attack; + setanim(self, self.anim_shoot, true, true, true); self.attack_finished_single = time + (autocvar_g_monster_mage_heal_delay); + self.anim_finished = time + 1.5; } } -void mage_push() +void M_Mage_Attack_Push() { sound(self, CH_SHOTS, "weapons/tagexp1.wav", 1, ATTEN_NORM); RadiusDamage (self, self, (autocvar_g_monster_mage_attack_push_damage), (autocvar_g_monster_mage_attack_push_damage), (autocvar_g_monster_mage_attack_push_radius), world, world, (autocvar_g_monster_mage_attack_push_force), DEATH_MONSTER_MAGE, self.enemy); pointparticles(particleeffectnum("TE_EXPLOSION"), self.origin, '0 0 0', 1); - self.frame = mage_anim_attack; + setanim(self, self.anim_shoot, true, true, true); self.attack_finished_single = time + (autocvar_g_monster_mage_attack_push_delay); } -void mage_teleport() +void M_Mage_Attack_Teleport() { if(vlen(self.enemy.origin - self.origin) >= 500) return; @@ -273,25 +278,24 @@ void mage_teleport() self.attack_finished_single = time + 0.2; } -void mage_shield_remove() +void M_Mage_Defend_Shield_Remove() { self.effects &= ~(EF_ADDITIVE | EF_BLUE); - self.armorvalue = 0; - self.m_armor_blockpercent = autocvar_g_monsters_armor_blockpercent; + self.armorvalue = autocvar_g_monsters_armor_blockpercent; } -void mage_shield() +void M_Mage_Defend_Shield() { self.effects |= (EF_ADDITIVE | EF_BLUE); - self.lastshielded = time + (autocvar_g_monster_mage_shield_delay); - self.m_armor_blockpercent = (autocvar_g_monster_mage_shield_blockpercent); - self.armorvalue = self.health; - self.shield_ltime = time + (autocvar_g_monster_mage_shield_time); - self.frame = mage_anim_attack; + self.mage_shield_delay = time + (autocvar_g_monster_mage_shield_delay); + self.armorvalue = (autocvar_g_monster_mage_shield_blockpercent); + self.mage_shield_time = time + (autocvar_g_monster_mage_shield_time); + setanim(self, self.anim_shoot, true, true, true); self.attack_finished_single = time + 1; + self.anim_finished = time + 1; } -float mage_attack(float attack_type) +float M_Mage_Attack(float attack_type) { switch(attack_type) { @@ -299,7 +303,7 @@ float mage_attack(float attack_type) { if(random() <= 0.7) { - mage_push(); + M_Mage_Attack_Push(); return true; } @@ -311,14 +315,15 @@ float mage_attack(float attack_type) { if(random() <= 0.4) { - mage_teleport(); + M_Mage_Attack_Teleport(); return true; } else { - self.frame = mage_anim_attack; + setanim(self, self.anim_shoot, true, true, true); self.attack_finished_single = time + (autocvar_g_monster_mage_attack_spike_delay); - defer(0.2, mage_attack_spike); + self.anim_finished = time + 1; + Monster_Delay(1, 0, 0.2, M_Mage_Attack_Spike); return true; } } @@ -333,28 +338,24 @@ float mage_attack(float attack_type) return false; } -void spawnfunc_monster_mage() -{ - self.classname = "monster_mage"; - - if(!monster_initialize(MON_MAGE)) { remove(self); return; } -} +void spawnfunc_monster_mage() { Monster_Spawn(MON_MAGE); } -// compatibility with old spawns -void spawnfunc_monster_shalrath() { spawnfunc_monster_mage(); } +#endif // SVQC -float m_mage(float req) +bool M_Mage(int req) { switch(req) { + #ifdef SVQC case MR_THINK: { entity head; - float need_help = false; + bool need_help = false; - for(head = findradius(self.origin, (autocvar_g_monster_mage_heal_range)); head; head = head.chain) + for(head = world; (head = findfloat(head, iscreature, true)); ) if(head != self) - if(friend_needshelp(head)) + if(vlen(head.origin - self.origin) <= (autocvar_g_monster_mage_heal_range)) + if(M_Mage_Defend_Heal_Check(head)) { need_help = true; break; @@ -363,32 +364,53 @@ float m_mage(float req) if(self.health < (autocvar_g_monster_mage_heal_minhealth) || need_help) if(time >= self.attack_finished_single) if(random() < 0.5) - mage_heal(); + M_Mage_Defend_Heal(); - if(time >= self.shield_ltime && self.armorvalue) - mage_shield_remove(); + if(time >= self.mage_shield_time && self.armorvalue) + M_Mage_Defend_Shield_Remove(); if(self.enemy) if(self.health < self.max_health) - if(time >= self.lastshielded) + if(time >= self.mage_shield_delay) if(random() < 0.5) - mage_shield(); + M_Mage_Defend_Shield(); - monster_move((autocvar_g_monster_mage_speed_run), (autocvar_g_monster_mage_speed_walk), (autocvar_g_monster_mage_speed_stop), mage_anim_walk, mage_anim_run, mage_anim_idle); + return true; + } + case MR_PAIN: + { return true; } case MR_DEATH: { - self.frame = mage_anim_death; + setanim(self, self.anim_die1, false, true, true); + return true; + } + #endif + #ifndef MENUQC + case MR_ANIM: + { + vector none = '0 0 0'; + self.anim_die1 = animfixfps(self, '4 1 0.5', none); // 2 seconds + self.anim_walk = animfixfps(self, '1 1 1', none); + self.anim_idle = animfixfps(self, '0 1 1', none); + self.anim_pain1 = animfixfps(self, '3 1 2', none); // 0.5 seconds + self.anim_shoot = animfixfps(self, '2 1 5', none); // analyze models and set framerate + self.anim_run = animfixfps(self, '5 1 1', none); + return true; } + #endif + #ifdef SVQC case MR_SETUP: { if(!self.health) self.health = (autocvar_g_monster_mage_health); + if(!self.speed) { self.speed = (autocvar_g_monster_mage_speed_walk); } + if(!self.speed2) { self.speed2 = (autocvar_g_monster_mage_speed_run); } + if(!self.stopspeed) { self.stopspeed = (autocvar_g_monster_mage_speed_stop); } + if(!self.damageforcescale) { self.damageforcescale = (autocvar_g_monster_mage_damageforcescale); } self.monster_loot = spawnfunc_item_health_large; - self.monster_attackfunc = mage_attack; - self.frame = mage_anim_walk; return true; } @@ -399,25 +421,10 @@ float m_mage(float req) precache_sound ("weapons/tagexp1.wav"); return true; } + #endif } return true; } -#endif // SVQC -#ifdef CSQC -float m_mage(float req) -{ - switch(req) - { - case MR_PRECACHE: - { - return true; - } - } - - return true; -} - -#endif // CSQC #endif // REGISTER_MONSTER diff --git a/qcsrc/common/monsters/monster/shambler.qc b/qcsrc/common/monsters/monster/shambler.qc index 1858b9bd1..4a48afef7 100644 --- a/qcsrc/common/monsters/monster/shambler.qc +++ b/qcsrc/common/monsters/monster/shambler.qc @@ -1,7 +1,7 @@ #ifdef REGISTER_MONSTER REGISTER_MONSTER( /* MON_##id */ SHAMBLER, -/* function */ m_shambler, +/* functions */ M_Shambler, M_Shambler_Attack, /* spawnflags */ MONSTER_SIZE_BROKEN | MON_FLAG_SUPERMONSTER | MON_FLAG_MELEE | MON_FLAG_RANGED, /* mins,maxs */ '-41 -41 -31', '41 41 65', /* model */ "shambler.mdl", @@ -12,9 +12,12 @@ REGISTER_MONSTER( #else #ifdef SVQC float autocvar_g_monster_shambler_health; +float autocvar_g_monster_shambler_damageforcescale = 0.1; float autocvar_g_monster_shambler_attack_smash_damage; +float autocvar_g_monster_shambler_attack_smash_range; float autocvar_g_monster_shambler_attack_claw_damage; float autocvar_g_monster_shambler_attack_lightning_damage; +float autocvar_g_monster_shambler_attack_lightning_damage_zap = 15; float autocvar_g_monster_shambler_attack_lightning_force; float autocvar_g_monster_shambler_attack_lightning_radius; float autocvar_g_monster_shambler_attack_lightning_radius_zap; @@ -24,6 +27,7 @@ float autocvar_g_monster_shambler_speed_stop; float autocvar_g_monster_shambler_speed_run; float autocvar_g_monster_shambler_speed_walk; +/* const float shambler_anim_stand = 0; const float shambler_anim_walk = 1; const float shambler_anim_run = 2; @@ -33,33 +37,36 @@ const float shambler_anim_swingl = 5; const float shambler_anim_magic = 6; const float shambler_anim_pain = 7; const float shambler_anim_death = 8; +*/ .float shambler_lastattack; // delay attacks separately -void shambler_smash() +void M_Shambler_Attack_Smash() { makevectors(self.angles); pointparticles(particleeffectnum("explosion_medium"), (self.origin + (v_forward * 150)) - ('0 0 1' * self.maxs.z), '0 0 0', 1); sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM); - tracebox(self.origin + v_forward * 50, self.mins * 0.5, self.maxs * 0.5, self.origin + v_forward * 500, MOVE_NORMAL, self); + // RadiusDamage does NOT support custom starting location, which means we must use this hack... + + tracebox(self.origin + v_forward * 50, self.mins * 0.5, self.maxs * 0.5, self.origin + v_forward * autocvar_g_monster_shambler_attack_smash_range, MOVE_NORMAL, self); if(trace_ent.takedamage) - Damage(trace_ent, self, self, (autocvar_g_monster_shambler_attack_smash_damage) * Monster_SkillModifier(), DEATH_MONSTER_SHAMBLER_SMASH, trace_ent.origin, normalize(trace_ent.origin - self.origin)); + Damage(trace_ent, self, self, (autocvar_g_monster_shambler_attack_smash_damage) * MONSTER_SKILLMOD(self), DEATH_MONSTER_SHAMBLER_SMASH, trace_ent.origin, normalize(trace_ent.origin - self.origin)); } -void shambler_swing() +void M_Shambler_Attack_Swing() { float r = (random() < 0.5); - monster_melee(self.enemy, (autocvar_g_monster_shambler_attack_claw_damage), ((r) ? shambler_anim_swingr : shambler_anim_swingl), self.attack_range, 0.8, DEATH_MONSTER_SHAMBLER_CLAW, true); - if(r) + if(r && Monster_Attack_Melee(self.enemy, (autocvar_g_monster_shambler_attack_claw_damage), ((r) ? self.anim_melee2 : self.anim_melee3), self.attack_range, 0.8, DEATH_MONSTER_SHAMBLER_CLAW, true)) { - defer(0.5, shambler_swing); + Monster_Delay(1, 0, 0.5, M_Shambler_Attack_Swing); self.attack_finished_single += 0.5; + self.anim_finished = self.attack_finished_single; } } -void shambler_lightning_explode() +void M_Shambler_Attack_Lightning_Explode() { entity head; @@ -79,14 +86,14 @@ void shambler_lightning_explode() for(head = findradius(self.origin, (autocvar_g_monster_shambler_attack_lightning_radius_zap)); head; head = head.chain) if(head != self.realowner) if(head.takedamage) { te_csqc_lightningarc(self.origin, head.origin); - Damage(head, self, self.realowner, (autocvar_g_monster_shambler_attack_lightning_damage) * Monster_SkillModifier(), DEATH_MONSTER_SHAMBLER_ZAP, head.origin, '0 0 0'); + Damage(head, self, self.realowner, (autocvar_g_monster_shambler_attack_lightning_damage_zap) * MONSTER_SKILLMOD(self), DEATH_MONSTER_SHAMBLER_ZAP, head.origin, '0 0 0'); } self.think = SUB_Remove; self.nextthink = time + 0.2; } -void shambler_lightning_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) +void M_Shambler_Attack_Lightning_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) { if (self.health <= 0) return; @@ -100,25 +107,25 @@ void shambler_lightning_damage(entity inflictor, entity attacker, float damage, W_PrepareExplosionByDamage(attacker, self.use); } -void shambler_lightning_touch() +void M_Shambler_Attack_Lightning_Touch() { PROJECTILE_TOUCH; self.use (); } -void shambler_lightning_think() +void M_Shambler_Attack_Lightning_Think() { self.nextthink = time; if (time > self.cnt) { other = world; - shambler_lightning_explode(); + M_Shambler_Attack_Lightning_Explode(); return; } } -void shambler_lightning() +void M_Shambler_Attack_Lightning() { entity gren; @@ -138,14 +145,14 @@ void shambler_lightning() gren.cnt = time + 5; gren.nextthink = time; - gren.think = shambler_lightning_think; - gren.use = shambler_lightning_explode; - gren.touch = shambler_lightning_touch; + gren.think = M_Shambler_Attack_Lightning_Think; + gren.use = M_Shambler_Attack_Lightning_Explode; + gren.touch = M_Shambler_Attack_Lightning_Touch; gren.takedamage = DAMAGE_YES; gren.health = 50; gren.damageforcescale = 0; - gren.event_damage = shambler_lightning_damage; + gren.event_damage = M_Shambler_Attack_Lightning_Damage; gren.damagedbycontents = true; gren.missile_flags = MIF_SPLASH | MIF_ARC; W_SetupProjVelocity_Explicit(gren, v_forward, v_up, (autocvar_g_monster_shambler_attack_lightning_speed), (autocvar_g_monster_shambler_attack_lightning_speed_up), 0, 0, false); @@ -156,33 +163,39 @@ void shambler_lightning() CSQCProjectile(gren, true, PROJECTILE_SHAMBLER_LIGHTNING, true); } -float shambler_attack(float attack_type) +float M_Shambler_Attack(float attack_type) { switch(attack_type) { case MONSTER_ATTACK_MELEE: { - shambler_swing(); + M_Shambler_Attack_Swing(); return true; } case MONSTER_ATTACK_RANGED: { + float randomness = random(), enemy_len = vlen(self.enemy.origin - self.origin); + if(time >= self.shambler_lastattack) // shambler doesn't attack much if(self.flags & FL_ONGROUND) - if(random() <= 0.5 && vlen(self.enemy.origin - self.origin) <= 500) + if(randomness <= 0.5 && enemy_len <= autocvar_g_monster_shambler_attack_smash_range) { - self.frame = shambler_anim_smash; - defer(0.7, shambler_smash); + setanim(self, self.anim_melee2, true, true, false); + Monster_Delay(1, 0, 0.7, M_Shambler_Attack_Smash); self.attack_finished_single = time + 1.1; - self.shambler_lastattack = time + 3; + self.anim_finished = time + 1.1; + self.state = MONSTER_ATTACK_MELEE; // kinda a melee attack + self.shambler_lastattack = time + 3 + random() * 1.5; return true; } - else if(random() <= 0.1) // small chance, don't want this spammed + else if(randomness <= 0.1 && enemy_len >= autocvar_g_monster_shambler_attack_smash_range * 1.5) // small chance, don't want this spammed { - self.frame = shambler_anim_magic; + setanim(self, self.anim_shoot, true, true, false); + self.state = MONSTER_ATTACK_MELEE; // maybe we should rename this to something more general self.attack_finished_single = time + 1.1; - self.shambler_lastattack = time + 3; - defer(0.6, shambler_lightning); + self.anim_finished = 1.1; + self.shambler_lastattack = time + 3 + random() * 1.5; + Monster_Delay(1, 0, 0.6, M_Shambler_Attack_Lightning); return true; } @@ -193,36 +206,63 @@ float shambler_attack(float attack_type) return false; } -void spawnfunc_monster_shambler() -{ - self.classname = "monster_shambler"; - - if(!monster_initialize(MON_SHAMBLER)) { remove(self); return; } -} +void spawnfunc_monster_shambler() { Monster_Spawn(MON_SHAMBLER); } +#endif // SVQC -float m_shambler(float req) +bool M_Shambler(int req) { switch(req) { + #ifdef SVQC case MR_THINK: { - monster_move((autocvar_g_monster_shambler_speed_run), (autocvar_g_monster_shambler_speed_walk), (autocvar_g_monster_shambler_speed_stop), shambler_anim_run, shambler_anim_walk, shambler_anim_stand); + return true; + } + case MR_PAIN: + { + self.pain_finished = time + 0.5; + setanim(self, self.anim_pain1, true, true, false); return true; } case MR_DEATH: { - self.frame = shambler_anim_death; + setanim(self, self.anim_die1, false, true, true); return true; } + #endif + #ifndef MENUQC + case MR_ANIM: + { + vector none = '0 0 0'; + self.anim_die1 = animfixfps(self, '8 1 0.5', none); // 2 seconds + self.anim_walk = animfixfps(self, '1 1 1', none); + self.anim_idle = animfixfps(self, '0 1 1', none); + self.anim_pain1 = animfixfps(self, '7 1 2', none); // 0.5 seconds + self.anim_melee1 = animfixfps(self, '3 1 5', none); // analyze models and set framerate + self.anim_melee2 = animfixfps(self, '4 1 5', none); // analyze models and set framerate + self.anim_melee3 = animfixfps(self, '5 1 5', none); // analyze models and set framerate + self.anim_shoot = animfixfps(self, '6 1 5', none); // analyze models and set framerate + self.anim_run = animfixfps(self, '2 1 1', none); + + return true; + } + #endif + #ifdef SVQC case MR_SETUP: { if(!self.health) self.health = (autocvar_g_monster_shambler_health); if(!self.attack_range) self.attack_range = 150; + if(!self.speed) { self.speed = (autocvar_g_monster_shambler_speed_walk); } + if(!self.speed2) { self.speed2 = (autocvar_g_monster_shambler_speed_run); } + if(!self.stopspeed) { self.stopspeed = (autocvar_g_monster_shambler_speed_stop); } + if(!self.damageforcescale) { self.damageforcescale = (autocvar_g_monster_shambler_damageforcescale); } self.monster_loot = spawnfunc_item_health_mega; - self.monster_attackfunc = shambler_attack; - self.frame = shambler_anim_stand; - self.weapon = WEP_VORTEX; + self.weapon = WEP_ELECTRO; // matches attacks better than WEP_VORTEX + + setanim(self, self.anim_shoot, false, true, true); + self.spawn_time = self.animstate_endtime; + self.spawnshieldtime = self.spawn_time; return true; } @@ -231,25 +271,10 @@ float m_shambler(float req) precache_model("models/monsters/shambler.mdl"); return true; } + #endif } return true; } -#endif // SVQC -#ifdef CSQC -float m_shambler(float req) -{ - switch(req) - { - case MR_PRECACHE: - { - return true; - } - } - - return true; -} - -#endif // CSQC #endif // REGISTER_MONSTER diff --git a/qcsrc/common/monsters/monster/spider.qc b/qcsrc/common/monsters/monster/spider.qc index 427f63107..550a563f7 100644 --- a/qcsrc/common/monsters/monster/spider.qc +++ b/qcsrc/common/monsters/monster/spider.qc @@ -1,7 +1,7 @@ #ifdef REGISTER_MONSTER REGISTER_MONSTER( /* MON_##id */ SPIDER, -/* function */ m_spider, +/* functions */ M_Spider, M_Spider_Attack, /* spawnflags */ MON_FLAG_MELEE | MON_FLAG_RANGED, /* mins,maxs */ '-18 -18 -25', '18 18 30', /* model */ "spider.dpm", @@ -12,6 +12,7 @@ REGISTER_MONSTER( #else #ifdef SVQC float autocvar_g_monster_spider_health; +float autocvar_g_monster_spider_damageforcescale = 0.6; float autocvar_g_monster_spider_attack_bite_damage; float autocvar_g_monster_spider_attack_bite_delay; float autocvar_g_monster_spider_attack_web_damagetime; @@ -22,14 +23,16 @@ float autocvar_g_monster_spider_speed_stop; float autocvar_g_monster_spider_speed_run; float autocvar_g_monster_spider_speed_walk; +/* const float spider_anim_idle = 0; const float spider_anim_walk = 1; const float spider_anim_attack = 2; const float spider_anim_attack2 = 3; +*/ .float spider_web_delay; -void spider_web_explode() +void M_Spider_Attack_Web_Explode() { entity e; if(self) @@ -37,21 +40,21 @@ void spider_web_explode() pointparticles(particleeffectnum("electro_impact"), self.origin, '0 0 0', 1); RadiusDamage(self, self.realowner, 0, 0, 25, world, world, 25, self.projectiledeathtype, world); - for(e = findradius(self.origin, 25); e; e = e.chain) if(e != self) if(e.takedamage && e.deadflag == DEAD_NO) if(e.health > 0) if(e.monsterid != MON_SPIDER) + for(e = findradius(self.origin, 25); e; e = e.chain) if(e != self) if(e.takedamage && e.deadflag == DEAD_NO) if(e.health > 0) if(e.monsterid != self.realowner.monsterid) e.spider_slowness = time + (autocvar_g_monster_spider_attack_web_damagetime); remove(self); } } -void spider_web_touch() +void M_Spider_Attack_Web_Touch() { PROJECTILE_TOUCH; - spider_web_explode(); + M_Spider_Attack_Web_Explode(); } -void spider_shootweb() +void M_Spider_Attack_Web() { monster_makevectors(self.enemy); @@ -60,7 +63,7 @@ void spider_shootweb() entity proj = spawn (); proj.classname = "plasma"; proj.owner = proj.realowner = self; - proj.use = spider_web_touch; + proj.use = M_Spider_Attack_Web_Explode; proj.think = adaptor_think2use_hittype_splash; proj.bot_dodge = true; proj.bot_dodgerating = 0; @@ -73,7 +76,7 @@ void spider_shootweb() //proj.glow_color = 45; proj.movetype = MOVETYPE_BOUNCE; W_SetupProjVelocity_Explicit(proj, v_forward, v_up, (autocvar_g_monster_spider_attack_web_speed), (autocvar_g_monster_spider_attack_web_speed_up), 0, 0, false); - proj.touch = spider_web_touch; + proj.touch = M_Spider_Attack_Web_Touch; setsize(proj, '-4 -4 -4', '4 4 4'); proj.takedamage = DAMAGE_NO; proj.damageforcescale = 0; @@ -89,21 +92,22 @@ void spider_shootweb() CSQCProjectile(proj, true, PROJECTILE_ELECTRO, true); } -float spider_attack(float attack_type) +float M_Spider_Attack(float attack_type) { switch(attack_type) { case MONSTER_ATTACK_MELEE: { - return monster_melee(self.enemy, (autocvar_g_monster_spider_attack_bite_damage), ((random() > 0.5) ? spider_anim_attack : spider_anim_attack2), self.attack_range, (autocvar_g_monster_spider_attack_bite_delay), DEATH_MONSTER_SPIDER, true); + return Monster_Attack_Melee(self.enemy, (autocvar_g_monster_spider_attack_bite_damage), ((random() > 0.5) ? self.anim_melee : self.anim_shoot), self.attack_range, (autocvar_g_monster_spider_attack_bite_delay), DEATH_MONSTER_SPIDER, true); } case MONSTER_ATTACK_RANGED: { if(time >= self.spider_web_delay) { - self.frame = spider_anim_attack2; + setanim(self, self.anim_shoot, true, true, true); self.attack_finished_single = time + (autocvar_g_monster_spider_attack_web_delay); - spider_shootweb(); + self.anim_finished = time + 1; + M_Spider_Attack_Web(); self.spider_web_delay = time + 3; return true; } @@ -115,35 +119,52 @@ float spider_attack(float attack_type) return false; } -void spawnfunc_monster_spider() -{ - self.classname = "monster_spider"; - - if(!monster_initialize(MON_SPIDER)) { remove(self); return; } -} +void spawnfunc_monster_spider() { Monster_Spawn(MON_SPIDER); } +#endif // SVQC -float m_spider(float req) +bool M_Spider(int req) { switch(req) { + #ifdef SVQC case MR_THINK: { - monster_move((autocvar_g_monster_spider_speed_run), (autocvar_g_monster_spider_speed_walk), (autocvar_g_monster_spider_speed_stop), spider_anim_walk, spider_anim_walk, spider_anim_idle); + return true; + } + case MR_PAIN: + { return true; } case MR_DEATH: { - self.frame = spider_anim_attack; + setanim(self, self.anim_melee, false, true, true); self.angles_x = 180; return true; } + #endif + #ifndef MENUQC + case MR_ANIM: + { + vector none = '0 0 0'; + self.anim_walk = animfixfps(self, '1 1 1', none); + self.anim_idle = animfixfps(self, '0 1 1', none); + self.anim_melee = animfixfps(self, '2 1 5', none); // analyze models and set framerate + self.anim_shoot = animfixfps(self, '3 1 5', none); // analyze models and set framerate + self.anim_run = animfixfps(self, '1 1 1', none); + + return true; + } + #endif + #ifdef SVQC case MR_SETUP: { if(!self.health) self.health = (autocvar_g_monster_spider_health); + if(!self.speed) { self.speed = (autocvar_g_monster_spider_speed_walk); } + if(!self.speed2) { self.speed2 = (autocvar_g_monster_spider_speed_run); } + if(!self.stopspeed) { self.stopspeed = (autocvar_g_monster_spider_speed_stop); } + if(!self.damageforcescale) { self.damageforcescale = (autocvar_g_monster_spider_damageforcescale); } self.monster_loot = spawnfunc_item_health_medium; - self.monster_attackfunc = spider_attack; - self.frame = spider_anim_idle; return true; } @@ -153,25 +174,10 @@ float m_spider(float req) precache_sound ("weapons/electro_fire2.wav"); return true; } + #endif } return true; } -#endif // SVQC -#ifdef CSQC -float m_spider(float req) -{ - switch(req) - { - case MR_PRECACHE: - { - return true; - } - } - - return true; -} - -#endif // CSQC #endif // REGISTER_MONSTER diff --git a/qcsrc/common/monsters/monster/wyvern.qc b/qcsrc/common/monsters/monster/wyvern.qc index b6736caa4..28c98c129 100644 --- a/qcsrc/common/monsters/monster/wyvern.qc +++ b/qcsrc/common/monsters/monster/wyvern.qc @@ -1,7 +1,7 @@ #ifdef REGISTER_MONSTER REGISTER_MONSTER( /* MON_##id */ WYVERN, -/* function */ m_wyvern, +/* functions */ M_Wyvern, M_Wyvern_Attack, /* spawnflags */ MONSTER_TYPE_FLY | MONSTER_SIZE_BROKEN | MON_FLAG_RANGED, /* mins,maxs */ '-20 -20 -58', '20 20 20', /* model */ "wizard.mdl", @@ -12,6 +12,7 @@ REGISTER_MONSTER( #else #ifdef SVQC float autocvar_g_monster_wyvern_health; +float autocvar_g_monster_wyvern_damageforcescale = 0.6; float autocvar_g_monster_wyvern_attack_fireball_damage; float autocvar_g_monster_wyvern_attack_fireball_edgedamage; float autocvar_g_monster_wyvern_attack_fireball_damagetime; @@ -22,13 +23,15 @@ float autocvar_g_monster_wyvern_speed_stop; float autocvar_g_monster_wyvern_speed_run; float autocvar_g_monster_wyvern_speed_walk; +/* const float wyvern_anim_hover = 0; const float wyvern_anim_fly = 1; const float wyvern_anim_magic = 2; const float wyvern_anim_pain = 3; const float wyvern_anim_death = 4; +*/ -void wyvern_fireball_explode() +void M_Wyvern_Attack_Fireball_Explode() { entity e; if(self) @@ -38,20 +41,20 @@ void wyvern_fireball_explode() RadiusDamage(self, self.realowner, (autocvar_g_monster_wyvern_attack_fireball_damage), (autocvar_g_monster_wyvern_attack_fireball_edgedamage), (autocvar_g_monster_wyvern_attack_fireball_force), world, world, (autocvar_g_monster_wyvern_attack_fireball_radius), self.projectiledeathtype, world); for(e = world; (e = findfloat(e, takedamage, DAMAGE_AIM)); ) if(vlen(e.origin - self.origin) <= (autocvar_g_monster_wyvern_attack_fireball_radius)) - Fire_AddDamage(e, self, 5 * Monster_SkillModifier(), (autocvar_g_monster_wyvern_attack_fireball_damagetime), self.projectiledeathtype); + Fire_AddDamage(e, self, 5 * MONSTER_SKILLMOD(self), (autocvar_g_monster_wyvern_attack_fireball_damagetime), self.projectiledeathtype); remove(self); } } -void wyvern_fireball_touch() +void M_Wyvern_Attack_Fireball_Touch() { PROJECTILE_TOUCH; - wyvern_fireball_explode(); + M_Wyvern_Attack_Fireball_Explode(); } -void wyvern_fireball() +void M_Wyvern_Attack_Fireball() { entity missile = spawn(); vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin); @@ -68,13 +71,13 @@ void wyvern_fireball() missile.velocity = dir * (autocvar_g_monster_wyvern_attack_fireball_speed); missile.avelocity = '300 300 300'; missile.nextthink = time + 5; - missile.think = wyvern_fireball_explode; + missile.think = M_Wyvern_Attack_Fireball_Explode; missile.enemy = self.enemy; - missile.touch = wyvern_fireball_touch; + missile.touch = M_Wyvern_Attack_Fireball_Touch; CSQCProjectile(missile, true, PROJECTILE_FIREMINE, true); } -float wyvern_attack(float attack_type) +float M_Wyvern_Attack(float attack_type) { switch(attack_type) { @@ -82,8 +85,9 @@ float wyvern_attack(float attack_type) case MONSTER_ATTACK_RANGED: { self.attack_finished_single = time + 1.2; + self.anim_finished = time + 1.2; - wyvern_fireball(); + M_Wyvern_Attack_Fireball(); return true; } @@ -92,40 +96,58 @@ float wyvern_attack(float attack_type) return false; } -void spawnfunc_monster_wyvern() -{ - self.classname = "monster_wyvern"; - - if(!monster_initialize(MON_WYVERN)) { remove(self); return; } -} +void spawnfunc_monster_wyvern() { Monster_Spawn(MON_WYVERN); } -// compatibility with old spawns -void spawnfunc_monster_wizard() { spawnfunc_monster_wyvern(); } +#endif // SVQC -float m_wyvern(float req) +bool M_Wyvern(int req) { switch(req) { + #ifdef SVQC case MR_THINK: { - monster_move((autocvar_g_monster_wyvern_speed_run), (autocvar_g_monster_wyvern_speed_walk), (autocvar_g_monster_wyvern_speed_stop), wyvern_anim_fly, wyvern_anim_hover, wyvern_anim_hover); + return true; + } + case MR_PAIN: + { + self.pain_finished = time + 0.5; + setanim(self, self.anim_pain1, true, true, false); return true; } case MR_DEATH: { - self.frame = wyvern_anim_death; + setanim(self, self.anim_die1, false, true, true); self.velocity_x = -200 + 400 * random(); self.velocity_y = -200 + 400 * random(); self.velocity_z = 100 + 100 * random(); return true; } + #endif + #ifndef MENUQC + case MR_ANIM: + { + vector none = '0 0 0'; + self.anim_die1 = animfixfps(self, '4 1 0.5', none); // 2 seconds + self.anim_walk = animfixfps(self, '1 1 1', none); + self.anim_idle = animfixfps(self, '0 1 1', none); + self.anim_pain1 = animfixfps(self, '3 1 2', none); // 0.5 seconds + self.anim_shoot = animfixfps(self, '2 1 5', none); // analyze models and set framerate + self.anim_run = animfixfps(self, '1 1 1', none); + + return true; + } + #endif + #ifdef SVQC case MR_SETUP: { if(!self.health) self.health = (autocvar_g_monster_wyvern_health); + if(!self.speed) { self.speed = (autocvar_g_monster_wyvern_speed_walk); } + if(!self.speed2) { self.speed2 = (autocvar_g_monster_wyvern_speed_run); } + if(!self.stopspeed) { self.stopspeed = (autocvar_g_monster_wyvern_speed_stop); } + if(!self.damageforcescale) { self.damageforcescale = (autocvar_g_monster_wyvern_damageforcescale); } self.monster_loot = spawnfunc_item_cells; - self.monster_attackfunc = wyvern_attack; - self.frame = wyvern_anim_hover; return true; } @@ -134,25 +156,10 @@ float m_wyvern(float req) precache_model("models/monsters/wizard.mdl"); return true; } + #endif } return true; } -#endif // SVQC -#ifdef CSQC -float m_wyvern(float req) -{ - switch(req) - { - case MR_PRECACHE: - { - return true; - } - } - - return true; -} - -#endif // CSQC #endif // REGISTER_MONSTER diff --git a/qcsrc/common/monsters/monster/zombie.qc b/qcsrc/common/monsters/monster/zombie.qc index 411e1c686..c1bf45389 100644 --- a/qcsrc/common/monsters/monster/zombie.qc +++ b/qcsrc/common/monsters/monster/zombie.qc @@ -1,7 +1,7 @@ #ifdef REGISTER_MONSTER REGISTER_MONSTER( /* MON_##id */ ZOMBIE, -/* function */ m_zombie, +/* functions */ M_Zombie, M_Zombie_Attack, /* spawnflags */ MON_FLAG_MELEE, /* mins,maxs */ '-18 -18 -25', '18 18 47', /* model */ "zombie.dpm", @@ -12,6 +12,7 @@ REGISTER_MONSTER( #else #ifdef SVQC float autocvar_g_monster_zombie_health; +float autocvar_g_monster_zombie_damageforcescale = 0.55; float autocvar_g_monster_zombie_attack_melee_damage; float autocvar_g_monster_zombie_attack_melee_delay; float autocvar_g_monster_zombie_attack_leap_damage; @@ -22,6 +23,7 @@ float autocvar_g_monster_zombie_speed_stop; float autocvar_g_monster_zombie_speed_run; float autocvar_g_monster_zombie_speed_walk; +/* const float zombie_anim_attackleap = 0; const float zombie_anim_attackrun1 = 1; const float zombie_anim_attackrun2 = 2; @@ -53,8 +55,9 @@ const float zombie_anim_runforward = 27; const float zombie_anim_runforwardleft = 28; const float zombie_anim_runforwardright = 29; const float zombie_anim_spawn = 30; +*/ -void zombie_attack_leap_touch() +void M_Zombie_Attack_Leap_Touch() { if (self.health <= 0) return; @@ -65,93 +68,128 @@ void zombie_attack_leap_touch() { angles_face = vectoangles(self.moveto - self.origin); angles_face = normalize(angles_face) * (autocvar_g_monster_zombie_attack_leap_force); - Damage(other, self, self, (autocvar_g_monster_zombie_attack_leap_damage) * Monster_SkillModifier(), DEATH_MONSTER_ZOMBIE_JUMP, other.origin, angles_face); - self.touch = MonsterTouch; // instantly turn it off to stop damage spam + Damage(other, self, self, (autocvar_g_monster_zombie_attack_leap_damage) * MONSTER_SKILLMOD(self), DEATH_MONSTER_ZOMBIE_JUMP, other.origin, angles_face); + self.touch = Monster_Touch; // instantly turn it off to stop damage spam + self.state = 0; } if (trace_dphitcontents) - self.touch = MonsterTouch; + { + self.state = 0; + self.touch = Monster_Touch; + } } -void zombie_blockend() +void M_Zombie_Defend_Block_End() { if(self.health <= 0) return; - self.frame = zombie_anim_blockend; - self.armorvalue = 0; - self.m_armor_blockpercent = autocvar_g_monsters_armor_blockpercent; + setanim(self, self.anim_blockend, false, true, true); + self.armorvalue = autocvar_g_monsters_armor_blockpercent; } -float zombie_block() +float M_Zombie_Defend_Block() { - self.frame = zombie_anim_blockstart; - self.armorvalue = 100; - self.m_armor_blockpercent = 0.9; - self.state = MONSTER_STATE_ATTACK_MELEE; // freeze monster + self.armorvalue = 0.9; + self.state = MONSTER_ATTACK_MELEE; // freeze monster self.attack_finished_single = time + 2.1; + self.anim_finished = self.attack_finished_single; + setanim(self, self.anim_blockstart, false, true, true); - defer(2, zombie_blockend); + Monster_Delay(1, 0, 2, M_Zombie_Defend_Block_End); return true; } -float zombie_attack(float attack_type) +float M_Zombie_Attack(float attack_type) { switch(attack_type) { case MONSTER_ATTACK_MELEE: { - float rand = random(), chosen_anim; + if(random() < 0.3 && self.health < 75 && self.enemy.health > 10) + return M_Zombie_Defend_Block(); + + float rand = random(); + vector chosen_anim; if(rand < 0.33) - chosen_anim = zombie_anim_attackstanding1; + chosen_anim = self.anim_melee1; else if(rand < 0.66) - chosen_anim = zombie_anim_attackstanding2; + chosen_anim = self.anim_melee2; else - chosen_anim = zombie_anim_attackstanding3; + chosen_anim = self.anim_melee3; - if(random() < 0.3 && self.health < 75 && self.enemy.health > 10) - return zombie_block(); - - return monster_melee(self.enemy, (autocvar_g_monster_zombie_attack_melee_damage), chosen_anim, self.attack_range, (autocvar_g_monster_zombie_attack_melee_delay), DEATH_MONSTER_ZOMBIE_MELEE, true); + return Monster_Attack_Melee(self.enemy, (autocvar_g_monster_zombie_attack_melee_damage), chosen_anim, self.attack_range, (autocvar_g_monster_zombie_attack_melee_delay), DEATH_MONSTER_ZOMBIE_MELEE, true); } case MONSTER_ATTACK_RANGED: { makevectors(self.angles); - return monster_leap(zombie_anim_attackleap, zombie_attack_leap_touch, v_forward * (autocvar_g_monster_zombie_attack_leap_speed) + '0 0 200', (autocvar_g_monster_zombie_attack_leap_delay)); + return Monster_Attack_Leap(self.anim_shoot, M_Zombie_Attack_Leap_Touch, v_forward * (autocvar_g_monster_zombie_attack_leap_speed) + '0 0 200', (autocvar_g_monster_zombie_attack_leap_delay)); } } return false; } -void spawnfunc_monster_zombie() -{ - self.classname = "monster_zombie"; - - if(!monster_initialize(MON_ZOMBIE)) { remove(self); return; } -} +void spawnfunc_monster_zombie() { Monster_Spawn(MON_ZOMBIE); } +#endif // SVQC -float m_zombie(float req) +bool M_Zombie(int req) { switch(req) { + #ifdef SVQC case MR_THINK: { - monster_move((autocvar_g_monster_zombie_speed_run), (autocvar_g_monster_zombie_speed_walk), (autocvar_g_monster_zombie_speed_stop), zombie_anim_runforward, zombie_anim_runforward, zombie_anim_idle); + if(time >= self.spawn_time) + self.damageforcescale = autocvar_g_monster_zombie_damageforcescale; + return true; + } + case MR_PAIN: + { + self.pain_finished = time + 0.34; + setanim(self, ((random() > 0.5) ? self.anim_pain1 : self.anim_pain2), true, true, false); return true; } case MR_DEATH: { - self.armorvalue = 0; - self.m_armor_blockpercent = autocvar_g_monsters_armor_blockpercent; - self.frame = ((random() > 0.5) ? zombie_anim_deathback1 : zombie_anim_deathfront1); + self.armorvalue = autocvar_g_monsters_armor_blockpercent; + + setanim(self, ((random() > 0.5) ? self.anim_die1 : self.anim_die2), false, true, true); + return true; + } + #endif + #ifndef MENUQC + case MR_ANIM: + { + vector none = '0 0 0'; + self.anim_die1 = animfixfps(self, '9 1 0.5', none); // 2 seconds + self.anim_die2 = animfixfps(self, '12 1 0.5', none); // 2 seconds + self.anim_spawn = animfixfps(self, '30 1 3', none); + self.anim_walk = animfixfps(self, '27 1 1', none); + self.anim_idle = animfixfps(self, '19 1 1', none); + self.anim_pain1 = animfixfps(self, '20 1 2', none); // 0.5 seconds + self.anim_pain2 = animfixfps(self, '22 1 2', none); // 0.5 seconds + self.anim_melee1 = animfixfps(self, '4 1 5', none); // analyze models and set framerate + self.anim_melee2 = animfixfps(self, '4 1 5', none); // analyze models and set framerate + self.anim_melee3 = animfixfps(self, '4 1 5', none); // analyze models and set framerate + self.anim_shoot = animfixfps(self, '0 1 5', none); // analyze models and set framerate + self.anim_run = animfixfps(self, '27 1 1', none); + self.anim_blockstart = animfixfps(self, '8 1 1', none); + self.anim_blockend = animfixfps(self, '7 1 1', none); + return true; } + #endif + #ifdef SVQC case MR_SETUP: { if(!self.health) self.health = (autocvar_g_monster_zombie_health); + if(!self.speed) { self.speed = (autocvar_g_monster_zombie_speed_walk); } + if(!self.speed2) { self.speed2 = (autocvar_g_monster_zombie_speed_run); } + if(!self.stopspeed) { self.stopspeed = (autocvar_g_monster_zombie_speed_stop); } if(self.spawnflags & MONSTERFLAG_NORESPAWN) self.spawnflags &= ~MONSTERFLAG_NORESPAWN; // zombies always respawn @@ -159,11 +197,12 @@ float m_zombie(float req) self.spawnflags |= MONSTER_RESPAWN_DEATHPOINT; self.monster_loot = spawnfunc_item_health_medium; - self.monster_attackfunc = zombie_attack; - self.frame = zombie_anim_spawn; - self.spawn_time = time + 2.1; self.spawnshieldtime = self.spawn_time; self.respawntime = 0.2; + self.damageforcescale = 0.0001; // no push while spawning + + setanim(self, self.anim_spawn, false, true, true); + self.spawn_time = self.animstate_endtime; return true; } @@ -172,25 +211,10 @@ float m_zombie(float req) precache_model("models/monsters/zombie.dpm"); return true; } + #endif } return true; } -#endif // SVQC -#ifdef CSQC -float m_zombie(float req) -{ - switch(req) - { - case MR_PRECACHE: - { - return true; - } - } - - return true; -} - -#endif // CSQC #endif // REGISTER_MONSTER diff --git a/qcsrc/common/monsters/spawn.qc b/qcsrc/common/monsters/spawn.qc index 5bfef1b8f..4a84f9435 100644 --- a/qcsrc/common/monsters/spawn.qc +++ b/qcsrc/common/monsters/spawn.qc @@ -11,19 +11,13 @@ #endif entity spawnmonster (string monster, float monster_id, entity spawnedby, entity own, vector orig, float respwn, float invincible, float moveflag) { - // ensure spawnfunc database is initialized - //initialize_field_db(); - + float i; entity e = spawn(); - float i; e.spawnflags = MONSTERFLAG_SPAWNED; - if(!respwn) - e.spawnflags |= MONSTERFLAG_NORESPAWN; - - if(invincible) - e.spawnflags |= MONSTERFLAG_INVINCIBLE; + if(!respwn) { e.spawnflags |= MONSTERFLAG_NORESPAWN; } + if(invincible) { e.spawnflags |= MONSTERFLAG_INVINCIBLE; } setorigin(e, orig); @@ -31,12 +25,11 @@ entity spawnmonster (string monster, float monster_id, entity spawnedby, entity { RandomSelection_Init(); for(i = MON_FIRST; i <= MON_LAST; ++i) - RandomSelection_Add(world, 0, (get_monsterinfo(i)).netname, 1, 1); + RandomSelection_Add(world, i, string_null, 1, 1); - monster = RandomSelection_chosen_string; + monster_id = RandomSelection_chosen_float; } - - if(monster != "") + else if(monster != "") { float found = 0; entity mon; @@ -51,13 +44,9 @@ entity spawnmonster (string monster, float monster_id, entity spawnedby, entity } } if(!found) - monster = (get_monsterinfo(MON_FIRST)).netname; + monster_id = ((monster_id > 0) ? monster_id : MON_FIRST); } - if(monster == "") - if(monster_id) - monster = (get_monsterinfo(monster_id)).netname; - e.realowner = spawnedby; if(moveflag) @@ -69,19 +58,16 @@ entity spawnmonster (string monster, float monster_id, entity spawnedby, entity e.team = spawnedby.team; // colors handled in spawn code if(autocvar_g_monsters_owners) - e.monster_owner = own; // using .owner makes the monster non-solid for its master + e.monster_follow = own; // using .owner makes the monster non-solid for its master - e.angles = spawnedby.angles; + e.angles_y = spawnedby.angles_y; } - - //monster = strcat("$ spawnfunc_monster_", monster); - + + // Monster_Spawn checks if monster is valid entity oldself = self; self = e; - monster_initialize(monster_id); + Monster_Spawn(monster_id); self = oldself; - //target_spawn_edit_entity(e, monster, world, world, world, world, world); - return e; } diff --git a/qcsrc/common/monsters/sv_monsters.qc b/qcsrc/common/monsters/sv_monsters.qc index 239f8fe9a..b6e6fd1c8 100644 --- a/qcsrc/common/monsters/sv_monsters.qc +++ b/qcsrc/common/monsters/sv_monsters.qc @@ -15,6 +15,7 @@ #include "../deathtypes.qh" #include "../../server/mutators/mutators_include.qh" #include "../../server/tturrets/include/turrets_early.qh" + #include "../../server/steerlib.qh" #include "../../server/vehicles/vehicle.qh" #include "../../server/campaign.qh" #include "../../server/command/common.qh" @@ -25,10 +26,11 @@ #include "../../server/tturrets/include/turrets.qh" #endif -// ========================= -// SVQC Monster Properties -// ========================= - +void monsters_setstatus() +{ + self.stat_monsters_total = monsters_total; + self.stat_monsters_killed = monsters_killed; +} void monster_dropitem() { @@ -61,109 +63,87 @@ void monster_dropitem() } } -float Monster_SkillModifier() -{ - float t = 0.5+self.monster_skill*((1.2-0.3)/10); - - return t; -} - -float monster_isvalidtarget (entity targ, entity ent) +void monster_makevectors(entity e) { - if(!targ || !ent) - return false; // someone doesn't exist - - if(targ == ent) - return false; // don't attack ourselves - - //traceline(ent.origin, targ.origin, MOVE_NORMAL, ent); - - //if(trace_ent != targ) - //return false; - - if(targ.vehicle_flags & VHF_ISVEHICLE) - if(!((get_monsterinfo(ent.monsterid)).spawnflags & MON_FLAG_RANGED)) - return false; // melee attacks are useless against vehicles - - if(time < game_starttime) - return false; // monsters do nothing before the match has started - - if(targ.takedamage == DAMAGE_NO) - return false; // enemy can't be damaged - - if(targ.items & IT_INVISIBILITY) - return false; // enemy is invisible - - if(substring(targ.classname, 0, 10) == "onslaught_") - return false; // don't attack onslaught targets - - if(IS_SPEC(targ) || IS_OBSERVER(targ)) - return false; // enemy is a spectator - - if(!(targ.vehicle_flags & VHF_ISVEHICLE)) - if(targ.deadflag != DEAD_NO || ent.deadflag != DEAD_NO || targ.health <= 0 || ent.health <= 0) - return false; // enemy/self is dead + if(self.flags & FL_MONSTER) + { + vector v; - if(ent.monster_owner == targ) - return false; // don't attack our master + v = e.origin + (e.mins + e.maxs) * 0.5; + self.v_angle = vectoangles(v - (self.origin + self.view_ofs)); + self.v_angle_x = -self.v_angle_x; + } - if(targ.monster_owner == ent) - return false; // don't attack our pet + makevectors(self.v_angle); +} - if(!(targ.vehicle_flags & VHF_ISVEHICLE)) - if(targ.flags & FL_NOTARGET) - return false; // enemy can't be targeted +// =============== +// Target handling +// =============== - if(!autocvar_g_monsters_typefrag) - if(targ.BUTTON_CHAT) - return false; // no typefragging! +bool Monster_ValidTarget(entity mon, entity player) +{ + // ensure we're not checking nonexistent monster/target + if(!mon || !player) { return false; } + + if((player == mon) + || (autocvar_g_monsters_lineofsight && !checkpvs(mon.origin + mon.view_ofs, player)) // enemy cannot be seen + || ((player.vehicle_flags & VHF_ISVEHICLE) && !((get_monsterinfo(mon.monsterid)).spawnflags & MON_FLAG_RANGED)) // melee vs vehicle is useless + || (time < game_starttime) // monsters do nothing before match has started + || (player.takedamage == DAMAGE_NO) + || (player.items & IT_INVISIBILITY) + || (IS_SPEC(player) || IS_OBSERVER(player)) // don't attack spectators + || (!(player.vehicle_flags & VHF_ISVEHICLE) && (player.deadflag != DEAD_NO || mon.deadflag != DEAD_NO || player.health <= 0 || mon.health <= 0)) + || (mon.monster_follow == player || player.monster_follow == mon) + || (!(player.vehicle_flags & VHF_ISVEHICLE) && (player.flags & FL_NOTARGET)) + || (!autocvar_g_monsters_typefrag && player.BUTTON_CHAT) + || (SAME_TEAM(player, mon)) + || (player.frozen) + || (player.alpha != 0 && player.alpha < 0.5) + ) + { + // if any of the above checks fail, target is not valid + return false; + } - if(SAME_TEAM(targ, ent)) - return false; // enemy is on our team + traceline(mon.origin + self.view_ofs, player.origin, 0, mon); - if (targ.frozen) - return false; // ignore frozen + if((trace_fraction < 1) && (trace_ent != player)) + return false; - if(autocvar_g_monsters_target_infront || (ent.spawnflags & MONSTERFLAG_INFRONT)) - if(ent.enemy != targ) + if(autocvar_g_monsters_target_infront || (mon.spawnflags & MONSTERFLAG_INFRONT)) + if(mon.enemy != player) { float dot; - makevectors (ent.angles); - dot = normalize (targ.origin - ent.origin) * v_forward; + makevectors (mon.angles); + dot = normalize (player.origin - mon.origin) * v_forward; - if(dot <= 0.3) - return false; + if(dot <= 0.3) { return false; } } - return true; + return true; // this target is valid! } -entity FindTarget (entity ent) +entity Monster_FindTarget(entity mon) { - if(MUTATOR_CALLHOOK(MonsterFindTarget)) { return ent.enemy; } // Handled by a mutator + if(MUTATOR_CALLHOOK(MonsterFindTarget)) { return mon.enemy; } // Handled by a mutator entity head, closest_target = world; - head = findradius(ent.origin, ent.target_range); - //head = WarpZone_FindRadius(ent.origin, ent.target_range, true); + head = findradius(mon.origin, mon.target_range); while(head) // find the closest acceptable target to pass to { if(head.monster_attack) - if(monster_isvalidtarget(head, ent)) + if(Monster_ValidTarget(mon, head)) { // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc) vector head_center = CENTER_OR_VIEWOFS(head); - //vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head)); - vector ent_center = CENTER_OR_VIEWOFS(ent); - - traceline(ent_center, head_center, MOVE_NORMAL, ent); + vector ent_center = CENTER_OR_VIEWOFS(mon); - if(trace_ent == head) if(closest_target) { vector closest_target_center = CENTER_OR_VIEWOFS(closest_target); - //vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target)); if(vlen(ent_center - head_center) < vlen(ent_center - closest_target_center)) { closest_target = head; } } @@ -176,17 +156,84 @@ entity FindTarget (entity ent) return closest_target; } -void MonsterTouch () +void monster_setupcolors(entity mon) { - if(other == world) - return; + if(IS_PLAYER(mon.realowner)) + mon.colormap = mon.realowner.colormap; + else if(teamplay && mon.team) + mon.colormap = 1024 + (mon.team - 1) * 17; + else + { + if(mon.monster_skill <= MONSTER_SKILL_EASY) + mon.colormap = 1029; + else if(mon.monster_skill <= MONSTER_SKILL_MEDIUM) + mon.colormap = 1027; + else if(mon.monster_skill <= MONSTER_SKILL_HARD) + mon.colormap = 1038; + else if(mon.monster_skill <= MONSTER_SKILL_INSANE) + mon.colormap = 1028; + else if(mon.monster_skill <= MONSTER_SKILL_NIGHTMARE) + mon.colormap = 1032; + else + mon.colormap = 1024; + } +} - if(self.enemy != other) - if(!(other.flags & FL_MONSTER)) - if(monster_isvalidtarget(other, self)) - self.enemy = other; +void monster_changeteam(entity ent, float newteam) +{ + if(!teamplay) { return; } + + ent.team = newteam; + ent.monster_attack = true; // new team, activate attacking + monster_setupcolors(ent); + + if(ent.sprite) + { + WaypointSprite_UpdateTeamRadar(ent.sprite, RADARICON_DANGER, ((newteam) ? Team_ColorRGB(newteam) : '1 0 0')); + + ent.sprite.team = newteam; + ent.sprite.SendFlags |= 1; + } +} + +void Monster_Delay_Action() +{ + entity oldself = self; + self = self.owner; + if(Monster_ValidTarget(self, self.enemy)) { oldself.use(); } + + if(oldself.cnt > 0) + { + oldself.cnt -= 1; + oldself.think = Monster_Delay_Action; + oldself.nextthink = time + oldself.respawn_time; + } + else + { + oldself.think = SUB_Remove; + oldself.nextthink = time; + } +} + +void Monster_Delay(float repeat_count, float repeat_defer, float defer_amnt, void() func) +{ + // deferred attacking, checks if monster is still alive and target is still valid before attacking + entity e = spawn(); + + e.think = Monster_Delay_Action; + e.nextthink = time + defer_amnt; + e.count = defer_amnt; + e.owner = self; + e.use = func; + e.cnt = repeat_count; + e.respawn_time = repeat_defer; } + +// ============== +// Monster sounds +// ============== + string get_monster_model_datafilename(string m, float sk, string fil) { if(m) @@ -200,7 +247,7 @@ string get_monster_model_datafilename(string m, float sk, string fil) return strcat(m, ".", fil); } -void PrecacheMonsterSounds(string f) +void Monster_Sound_Precache(string f) { float fh; string s; @@ -219,7 +266,7 @@ void PrecacheMonsterSounds(string f) fclose(fh); } -void precache_monstersounds() +void Monster_Sounds_Precache() { string m = (get_monsterinfo(self.monsterid)).model; float globhandle, n, i; @@ -233,19 +280,19 @@ void precache_monstersounds() { //print(search_getfilename(globhandle, i), "\n"); f = search_getfilename(globhandle, i); - PrecacheMonsterSounds(f); + Monster_Sound_Precache(f); } search_end(globhandle); } -void ClearMonsterSounds() +void Monster_Sounds_Clear() { #define _MSOUND(m) if(self.monstersound_##m) { strunzone(self.monstersound_##m); self.monstersound_##m = string_null; } ALLMONSTERSOUNDS #undef _MSOUND } -.string GetMonsterSoundSampleField(string type) +.string Monster_Sound_SampleField(string type) { GetMonsterSoundSampleField_notFound = 0; switch(type) @@ -258,7 +305,7 @@ void ClearMonsterSounds() return string_null; } -float LoadMonsterSounds(string f, float first) +float Monster_Sounds_Load(string f, float first) { float fh; string s; @@ -267,13 +314,13 @@ float LoadMonsterSounds(string f, float first) if(fh < 0) { dprint("Monster sound file not found: ", f, "\n"); - return 0; + return false; } while((s = fgets(fh))) { if(tokenize_console(s) != 3) continue; - field = GetMonsterSoundSampleField(argv(0)); + field = Monster_Sound_SampleField(argv(0)); if(GetMonsterSoundSampleField_notFound) continue; if (self.(field)) @@ -281,25 +328,21 @@ float LoadMonsterSounds(string f, float first) self.(field) = strzone(strcat(argv(1), " ", argv(2))); } fclose(fh); - return 1; + return true; } .int skin_for_monstersound; -void UpdateMonsterSounds() +void Monster_Sounds_Update() { - entity mon = get_monsterinfo(self.monsterid); + if(self.skin == self.skin_for_monstersound) { return; } - if(self.skin == self.skin_for_monstersound) - return; self.skin_for_monstersound = self.skin; - ClearMonsterSounds(); - //LoadMonsterSounds("sound/monsters/default.sounds", 1); - if(!autocvar_g_debug_defaultsounds) - if(!LoadMonsterSounds(get_monster_model_datafilename(mon.model, self.skin, "sounds"), 0)) - LoadMonsterSounds(get_monster_model_datafilename(mon.model, 0, "sounds"), 0); + Monster_Sounds_Clear(); + if(!Monster_Sounds_Load(get_monster_model_datafilename(self.model, self.skin, "sounds"), 0)) + Monster_Sounds_Load(get_monster_model_datafilename(self.model, 0, "sounds"), 0); } -void MonsterSound(.string samplefield, float sound_delay, float delaytoo, float chan) +void Monster_Sound(.string samplefield, float sound_delay, float delaytoo, float chan) { if(!autocvar_g_monsters_sounds) { return; } @@ -311,45 +354,121 @@ void MonsterSound(.string samplefield, float sound_delay, float delaytoo, float self.msound_delay = time + sound_delay; } -void monster_makevectors(entity e) + +// ======================= +// Monster attack handlers +// ======================= + +float Monster_Attack_Melee(entity targ, float damg, vector anim, float er, float animtime, int deathtype, float dostop) { - vector v; + if(dostop && (self.flags & FL_MONSTER)) { self.state = MONSTER_ATTACK_MELEE; } - v = e.origin + (e.mins + e.maxs) * 0.5; - self.v_angle = vectoangles(v - (self.origin + self.view_ofs)); - self.v_angle_x = -self.v_angle.x; + setanim(self, anim, false, true, false); - makevectors(self.v_angle); + if(self.animstate_endtime > time && (self.flags & FL_MONSTER)) + self.attack_finished_single = self.anim_finished = self.animstate_endtime; + else + self.attack_finished_single = self.anim_finished = time + animtime; + + monster_makevectors(targ); + + traceline(self.origin + self.view_ofs, self.origin + v_forward * er, 0, self); + + if(trace_ent.takedamage) + Damage(trace_ent, self, self, damg * MONSTER_SKILLMOD(self), deathtype, trace_ent.origin, normalize(trace_ent.origin - self.origin)); + + return true; } -float monster_melee(entity targ, float damg, float anim, float er, float anim_finished, int deathtype, float dostop) +float Monster_Attack_Leap_Check(vector vel) { - if (self.health <= 0) - return false; // attacking while dead?! + if(self.state && (self.flags & FL_MONSTER)) + return false; // already attacking + if(!(self.flags & FL_ONGROUND)) + return false; // not on the ground + if(self.health <= 0) + return false; // called when dead? + if(time < self.attack_finished_single) + return false; // still attacking - if(dostop) - { - self.velocity_x = 0; - self.velocity_y = 0; - self.state = MONSTER_STATE_ATTACK_MELEE; - } + vector old = self.velocity; - self.frame = anim; + self.velocity = vel; + tracetoss(self, self); + self.velocity = old; + if (trace_ent != self.enemy) + return false; - if(anim_finished != 0) - self.attack_finished_single = time + anim_finished; + return true; +} - monster_makevectors(targ); +bool Monster_Attack_Leap(vector anm, void() touchfunc, vector vel, float animtime) +{ + if(!Monster_Attack_Leap_Check(vel)) + return false; - traceline(self.origin + self.view_ofs, self.origin + v_forward * er, 0, self); + setanim(self, anm, false, true, false); - if(trace_ent.takedamage) - Damage(trace_ent, self, self, damg * Monster_SkillModifier(), deathtype, trace_ent.origin, normalize(trace_ent.origin - self.origin)); + if(self.animstate_endtime > time && (self.flags & FL_MONSTER)) + self.attack_finished_single = self.anim_finished = self.animstate_endtime; + else + self.attack_finished_single = self.anim_finished = time + animtime; + + if(self.flags & FL_MONSTER) + self.state = MONSTER_ATTACK_RANGED; + self.touch = touchfunc; + self.origin_z += 1; + self.velocity = vel; + self.flags &= ~FL_ONGROUND; return true; } -void Monster_CheckMinibossFlag () +void Monster_Attack_Check(entity e, entity targ) +{ + if((e == world || targ == world) + || (!e.monster_attackfunc) + || (time < e.attack_finished_single) + ) { return; } + + float targ_vlen = vlen(targ.origin - e.origin); + + if(targ_vlen <= e.attack_range) + { + float attack_success = e.monster_attackfunc(MONSTER_ATTACK_MELEE); + if(attack_success == 1) + Monster_Sound(monstersound_melee, 0, false, CH_VOICE); + else if(attack_success > 0) + return; + } + + if(targ_vlen > e.attack_range) + { + float attack_success = e.monster_attackfunc(MONSTER_ATTACK_RANGED); + if(attack_success == 1) + Monster_Sound(monstersound_melee, 0, false, CH_VOICE); + else if(attack_success > 0) + return; + } +} + + +// ====================== +// Main monster functions +// ====================== + +void Monster_Touch() +{ + if(other == world) { return; } + + if(other.monster_attack) + if(self.enemy != other) + if(!(self.flags & FL_MONSTER)) + if(Monster_ValidTarget(self, other)) + self.enemy = other; +} + +void Monster_Miniboss_Check() { if(MUTATOR_CALLHOOK(MonsterCheckBossFlag)) return; @@ -366,34 +485,22 @@ void Monster_CheckMinibossFlag () } } -float Monster_CanRespawn(entity ent) +float Monster_Respawn_Check() { - other = ent; - if(ent.deadflag == DEAD_DEAD) // don't call when monster isn't dead - if(MUTATOR_CALLHOOK(MonsterRespawn)) - return true; // enabled by a mutator - - if(ent.spawnflags & MONSTERFLAG_NORESPAWN) - return false; - - if(!autocvar_g_monsters_respawn) - return false; + if(self.spawnflags & MONSTERFLAG_NORESPAWN) { return false; } + if(!autocvar_g_monsters_respawn) { return false; } return true; } -void monster_respawn() -{ - // is this function really needed? - monster_initialize(self.monsterid); -} +void Monster_Respawn() { Monster_Spawn(self.monsterid); } -void Monster_Fade () +void Monster_Dead_Fade() { - if(Monster_CanRespawn(self)) + if(Monster_Respawn_Check()) { self.spawnflags |= MONSTERFLAG_RESPAWNED; - self.think = monster_respawn; + self.think = Monster_Respawn; self.nextthink = time + self.respawntime; self.monster_lifetime = 0; self.deadflag = DEAD_RESPAWNING; @@ -418,84 +525,12 @@ void Monster_Fade () } } -float Monster_CanJump (vector vel) -{ - if(self.state) - return false; // already attacking - if(!(self.flags & FL_ONGROUND)) - return false; // not on the ground - if(self.health <= 0) - return false; // called when dead? - if(time < self.attack_finished_single) - return false; // still attacking - - vector old = self.velocity; - - self.velocity = vel; - tracetoss(self, self); - self.velocity = old; - if (trace_ent != self.enemy) - return false; - - return true; -} - -float monster_leap (float anm, void() touchfunc, vector vel, float anim_finished) -{ - if(!Monster_CanJump(vel)) - return false; - - self.frame = anm; - self.state = MONSTER_STATE_ATTACK_LEAP; - self.touch = touchfunc; - self.origin_z += 1; - self.velocity = vel; - self.flags &= ~FL_ONGROUND; - - self.attack_finished_single = time + anim_finished; - - return true; -} - -void monster_checkattack(entity e, entity targ) -{ - if(e == world) - return; - if(targ == world) - return; - - if(!e.monster_attackfunc) - return; - - if(time < e.attack_finished_single) - return; - - if(vlen(targ.origin - e.origin) <= e.attack_range) - if(e.monster_attackfunc(MONSTER_ATTACK_MELEE)) - { - MonsterSound(monstersound_melee, 0, false, CH_VOICE); - return; - } - - if(vlen(targ.origin - e.origin) > e.attack_range) - if(e.monster_attackfunc(MONSTER_ATTACK_RANGED)) - { - MonsterSound(monstersound_ranged, 0, false, CH_VOICE); - return; - } -} - -void monster_use () +void Monster_Use() { - if(!self.enemy) - if(self.health > 0) - if(monster_isvalidtarget(activator, self)) - self.enemy = activator; + if(Monster_ValidTarget(self, activator)) { self.enemy = activator; } } -.float last_trace; -.float last_enemycheck; // for checking enemy -vector monster_pickmovetarget(entity targ) +vector Monster_Move_Target(entity targ) { // enemy is always preferred target if(self.enemy) @@ -508,7 +543,7 @@ vector monster_pickmovetarget(entity targ) || (self.enemy.deadflag != DEAD_NO || self.enemy.health < 1) || (self.enemy.frozen) || (self.enemy.flags & FL_NOTARGET) - || (self.enemy.alpha < 0.5) + || (self.enemy.alpha < 0.5 && self.enemy.alpha != 0) || (self.enemy.takedamage == DAMAGE_NO) || (vlen(self.origin - targ_origin) > self.target_range) || ((trace_fraction < 1) && (trace_ent != self.enemy))) @@ -527,7 +562,10 @@ vector monster_pickmovetarget(entity targ) self.monster_movestate = MONSTER_MOVE_ENEMY; self.last_trace = time + 1.2; - return targ_origin; + if(self.monster_moveto) + return self.monster_moveto; // assumes code is properly setting this when monster has an enemy + else + return targ_origin; } /*makevectors(self.angles); @@ -538,11 +576,11 @@ vector monster_pickmovetarget(entity targ) switch(self.monster_moveflags) { - case MONSTER_MOVE_OWNER: + case MONSTER_MOVE_FOLLOW: { - self.monster_movestate = MONSTER_MOVE_OWNER; + self.monster_movestate = MONSTER_MOVE_FOLLOW; self.last_trace = time + 0.3; - return (self.monster_owner) ? self.monster_owner.origin : self.origin; + return (self.monster_follow) ? self.monster_follow.origin : self.origin; } case MONSTER_MOVE_SPAWNLOC: { @@ -552,8 +590,16 @@ vector monster_pickmovetarget(entity targ) } case MONSTER_MOVE_NOMOVE: { - self.monster_movestate = MONSTER_MOVE_NOMOVE; - self.last_trace = time + 2; + if(self.monster_moveto) + { + self.last_trace = time + 0.5; + return self.monster_moveto; + } + else + { + self.monster_movestate = MONSTER_MOVE_NOMOVE; + self.last_trace = time + 2; + } return self.origin; } default: @@ -562,7 +608,12 @@ vector monster_pickmovetarget(entity targ) vector pos; self.monster_movestate = MONSTER_MOVE_WANDER; - if(targ) + if(self.monster_moveto) + { + self.last_trace = time + 0.5; + pos = self.monster_moveto; + } + else if(targ) { self.last_trace = time + 0.5; pos = targ.origin; @@ -588,11 +639,11 @@ vector monster_pickmovetarget(entity targ) } } -void monster_CalculateVelocity(entity mon, vector to, vector from, float turnrate, float movespeed) +void Monster_CalculateVelocity(entity mon, vector to, vector from, float turnrate, float movespeed) { float current_distance = vlen((('1 0 0' * to.x) + ('0 1 0' * to.y)) - (('1 0 0' * from.x) + ('0 1 0' * from.y))); // for the sake of this check, exclude Z axis float initial_height = 0; //min(50, (targ_distance * tanh(20))); - float current_height = (initial_height * min(1, (current_distance / self.pass_distance))); + float current_height = (initial_height * min(1, (self.pass_distance) ? (current_distance / self.pass_distance) : current_distance)); //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n"); vector targpos; @@ -622,12 +673,9 @@ void monster_CalculateVelocity(entity mon, vector to, vector from, float turnrat //mon.angles = vectoangles(mon.velocity); } -void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_run, float manim_walk, float manim_idle) +void Monster_Move(float runspeed, float walkspeed, float stpspeed) { - //fixedmakevectors(self.angles); - - if(self.target2) - self.goalentity = find(world, targetname, self.target2); + if(self.target2) { self.goalentity = find(world, targetname, self.target2); } entity targ; @@ -637,10 +685,11 @@ void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_ self.health = max(1, self.revive_progress * self.max_health); self.iceblock.alpha = bound(0.2, 1 - self.revive_progress, 1); - WaypointSprite_UpdateHealth(self.sprite, self.health); + if(!(self.spawnflags & MONSTERFLAG_INVINCIBLE) && self.sprite) + WaypointSprite_UpdateHealth(self.sprite, self.health); - movelib_beak_simple(stopspeed); - self.frame = manim_idle; + movelib_beak_simple(stpspeed); + setanim(self, self.anim_idle, true, false, false); self.enemy = world; self.nextthink = time + self.ticrate; @@ -655,10 +704,11 @@ void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_ self.revive_progress = bound(0, self.revive_progress - self.ticrate * self.revive_speed, 1); self.health = max(0, autocvar_g_nades_ice_health + (self.max_health-autocvar_g_nades_ice_health) * self.revive_progress ); - WaypointSprite_UpdateHealth(self.sprite, self.health); + if(!(self.spawnflags & MONSTERFLAG_INVINCIBLE) && self.sprite) + WaypointSprite_UpdateHealth(self.sprite, self.health); - movelib_beak_simple(stopspeed); - self.frame = manim_idle; + movelib_beak_simple(stpspeed); + setanim(self, self.anim_idle, true, false, false); self.enemy = world; self.nextthink = time + self.ticrate; @@ -667,7 +717,8 @@ void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_ { Unfreeze(self); self.health = 0; - self.event_damage(self, self.frozen_by, 1, DEATH_NADE_ICE_FREEZE, self.origin, '0 0 0'); + if(self.event_damage) + self.event_damage(self, self.frozen_by, 1, DEATH_NADE_ICE_FREEZE, self.origin, '0 0 0'); } else if ( self.revive_progress <= 0 ) @@ -682,7 +733,6 @@ void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_ { if(time >= self.last_trace) { - self.fish_wasdrowning = true; self.last_trace = time + 0.4; Damage (self, world, world, 2, DEATH_DROWN, self.origin, '0 0 0'); @@ -706,9 +756,8 @@ void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_ return; } - else if(self.fish_wasdrowning) + else if(self.movetype == MOVETYPE_BOUNCE) { - self.fish_wasdrowning = false; self.angles_x = 0; self.movetype = MOVETYPE_WALK; } @@ -720,18 +769,25 @@ void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_ monster_speed_run = runspeed; monster_speed_walk = walkspeed; - if(MUTATOR_CALLHOOK(MonsterMove) || gameover || self.draggedby != world || (round_handler_IsActive() && !round_handler_IsRoundStarted()) || time < game_starttime || (autocvar_g_campaign && !campaign_bots_may_start) || time < self.spawn_time) + if((MUTATOR_CALLHOOK(MonsterMove)) + || (gameover) + || (self.draggedby != world) + || (round_handler_IsActive() && !round_handler_IsRoundStarted()) + || (time < game_starttime) + || (autocvar_g_campaign && !campaign_bots_may_start) + || (time < self.spawn_time) + ) { runspeed = walkspeed = 0; if(time >= self.spawn_time) - self.frame = manim_idle; - movelib_beak_simple(stopspeed); + setanim(self, self.anim_idle, true, false, false); + movelib_beak_simple(stpspeed); return; } targ = monster_target; - runspeed = bound(0, monster_speed_run * Monster_SkillModifier(), runspeed * 2); // limit maxspeed to prevent craziness - walkspeed = bound(0, monster_speed_walk * Monster_SkillModifier(), walkspeed * 2); // limit maxspeed to prevent craziness + runspeed = bound(0, monster_speed_run * MONSTER_SKILLMOD(self), runspeed * 2.5); // limit maxspeed to prevent craziness + walkspeed = bound(0, monster_speed_walk * MONSTER_SKILLMOD(self), walkspeed * 2.5); // limit maxspeed to prevent craziness if(time < self.spider_slowness) { @@ -741,71 +797,68 @@ void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_ if(teamplay) if(autocvar_g_monsters_teams) - if(DIFF_TEAM(self.monster_owner, self)) - self.monster_owner = world; + if(DIFF_TEAM(self.monster_follow, self)) + self.monster_follow = world; if(time >= self.last_enemycheck) { if(!self.enemy) { - self.enemy = FindTarget(self); + self.enemy = Monster_FindTarget(self); if(self.enemy) { WarpZone_RefSys_Copy(self.enemy, self); WarpZone_RefSys_AddInverse(self.enemy, self); // wz1^-1 ... wzn^-1 receiver self.moveto = WarpZone_RefSys_TransformOrigin(self.enemy, self, (0.5 * (self.enemy.absmin + self.enemy.absmax))); - - self.pass_distance = vlen((('1 0 0' * self.enemy.origin.x) + ('0 1 0' * self.enemy.origin.y)) - (('1 0 0' * self.origin.x) + ('0 1 0' * self.origin.y))); - MonsterSound(monstersound_sight, 0, false, CH_VOICE); + self.monster_moveto = '0 0 0'; + self.monster_face = '0 0 0'; + + self.pass_distance = vlen((('1 0 0' * self.enemy.origin_x) + ('0 1 0' * self.enemy.origin_y)) - (('1 0 0' * self.origin_x) + ('0 1 0' * self.origin_y))); + Monster_Sound(monstersound_sight, 0, false, CH_VOICE); } } self.last_enemycheck = time + 1; // check for enemies every second } - if(self.state == MONSTER_STATE_ATTACK_MELEE && time >= self.attack_finished_single) + if(self.state == MONSTER_ATTACK_RANGED && (self.flags & FL_ONGROUND)) + { self.state = 0; + self.touch = Monster_Touch; + } - if(self.state != MONSTER_STATE_ATTACK_MELEE) // don't move if set - if(time >= self.last_trace || self.enemy) // update enemy instantly - self.moveto = monster_pickmovetarget(targ); + if(self.state && time >= self.attack_finished_single) + self.state = 0; // attack is over - if(!self.enemy) - MonsterSound(monstersound_idle, 7, true, CH_VOICE); + if(self.state != MONSTER_ATTACK_MELEE) // don't move if set + if(time >= self.last_trace || self.enemy) // update enemy or rider instantly + self.moveto = Monster_Move_Target(targ); - if(self.state == MONSTER_STATE_ATTACK_LEAP && (self.flags & FL_ONGROUND)) - { - self.state = 0; - self.touch = MonsterTouch; - } + if(!self.enemy) + Monster_Sound(monstersound_idle, 7, true, CH_VOICE); - if(self.state == MONSTER_STATE_ATTACK_MELEE) + if(self.state == MONSTER_ATTACK_MELEE) self.moveto = self.origin; if(self.enemy && self.enemy.vehicle) runspeed = 0; - if(!(((self.flags & FL_FLY) && (self.spawnflags & MONSTERFLAG_FLY_VERTICAL)) || (self.flags & FL_SWIM))) - //v_forward = normalize(self.moveto - self.origin); - //else - self.moveto_z = self.origin.z; + if(!(self.spawnflags & MONSTERFLAG_FLY_VERTICAL) && !(self.flags & FL_SWIM)) + self.moveto_z = self.origin_z; - if(vlen(self.origin - self.moveto) > 64) + if(vlen(self.origin - self.moveto) > 100) { + float do_run = !!(self.enemy); if((self.flags & FL_ONGROUND) || ((self.flags & FL_FLY) || (self.flags & FL_SWIM))) - monster_CalculateVelocity(self, self.moveto, self.origin, true, ((self.enemy) ? runspeed : walkspeed)); - - /*&if(self.flags & FL_FLY || self.flags & FL_SWIM) - movelib_move_simple(v_forward, ((self.enemy) ? runspeed : walkspeed), 0.6); - else - movelib_move_simple_gravity(v_forward, ((self.enemy) ? runspeed : walkspeed), 0.6); */ + Monster_CalculateVelocity(self, self.moveto, self.origin, true, ((do_run) ? runspeed : walkspeed)); - if(time > self.pain_finished) - if(time > self.attack_finished_single) + if(time > self.pain_finished) // TODO: use anim_finished instead! + if(!self.state) + if(time > self.anim_finished) if(vlen(self.velocity) > 10) - self.frame = ((self.enemy) ? manim_run : manim_walk); + setanim(self, ((do_run) ? self.anim_run : self.anim_walk), true, false, false); else - self.frame = manim_idle; + setanim(self, self.anim_idle, true, false, false); } else { @@ -815,18 +868,19 @@ void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_ else if(e.target) self.target2 = e.target; - movelib_beak_simple(stopspeed); - if(time > self.attack_finished_single) + movelib_beak_simple(stpspeed); + if(time > self.anim_finished) if(time > self.pain_finished) - if (vlen(self.velocity) <= 30) - self.frame = manim_idle; + if(!self.state) + if(vlen(self.velocity) <= 30) + setanim(self, self.anim_idle, true, false, false); } - self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95); + self.steerto = steerlib_attract2(((self.monster_face) ? self.monster_face : self.moveto), 0.5, 500, 0.95); vector real_angle = vectoangles(self.steerto) - self.angles; float turny = 25; - if(self.state == MONSTER_STATE_ATTACK_MELEE) + if(self.state == MONSTER_ATTACK_MELEE) turny = 0; if(turny) { @@ -834,55 +888,42 @@ void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_ self.angles_y += turny; } - monster_checkattack(self, self.enemy); + Monster_Attack_Check(self, self.enemy); } -void monster_remove(entity mon) +void Monster_Remove(entity mon) { - if(!mon) - return; // nothing to remove - - pointparticles(particleeffectnum("item_pickup"), mon.origin, '0 0 0', 1); + if(!mon) { return; } - if(mon.weaponentity) - remove(mon.weaponentity); - - if(mon.iceblock) - remove(mon.iceblock); + if(!MUTATOR_CALLHOOK(MonsterRemove)) + pointparticles(particleeffectnum("item_pickup"), mon.origin, '0 0 0', 1); + if(mon.weaponentity) { remove(mon.weaponentity); } + if(mon.iceblock) { remove(mon.iceblock); } WaypointSprite_Kill(mon.sprite); - remove(mon); } -void monster_dead_think() +void Monster_Dead_Think() { self.nextthink = time + self.ticrate; - CSQCMODEL_AUTOUPDATE(); - if(self.monster_lifetime != 0) if(time >= self.monster_lifetime) { - Monster_Fade(); + Monster_Dead_Fade(); return; } } -void monsters_setstatus() -{ - self.stat_monsters_total = monsters_total; - self.stat_monsters_killed = monsters_killed; -} - void Monster_Appear() { self.enemy = activator; self.spawnflags &= ~MONSTERFLAG_APPEAR; // otherwise, we get an endless loop - monster_initialize(self.monsterid); + Monster_Spawn(self.monsterid); } -float Monster_CheckAppearFlags(entity ent, float monster_id) +float Monster_Appear_Check(entity ent, float monster_id) { if(!(ent.spawnflags & MONSTERFLAG_APPEAR)) return false; @@ -896,7 +937,7 @@ float Monster_CheckAppearFlags(entity ent, float monster_id) return true; } -void monsters_reset() +void Monster_Reset() { setorigin(self, self.pos1); self.angles = self.pos2; @@ -911,7 +952,7 @@ void monsters_reset() self.moveto = self.origin; } -void monsters_corpse_damage (entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) +void Monster_Dead_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) { self.health -= damage; @@ -930,9 +971,9 @@ void monsters_corpse_damage (entity inflictor, entity attacker, float damage, in } } -void monster_die(entity attacker, float gibbed) +void Monster_Dead(entity attacker, float gibbed) { - self.think = monster_dead_think; + self.think = Monster_Dead_Think; self.nextthink = time; self.monster_lifetime = time + 5; @@ -944,7 +985,7 @@ void monster_die(entity attacker, float gibbed) monster_dropitem(); - MonsterSound(monstersound_death, 0, false, CH_VOICE); + Monster_Sound(monstersound_death, 0, false, CH_VOICE); if(!(self.spawnflags & MONSTERFLAG_SPAWNED) && !(self.spawnflags & MONSTERFLAG_RESPAWNED)) monsters_killed += 1; @@ -959,38 +1000,41 @@ void monster_die(entity attacker, float gibbed) totalspawned -= 1; } - if(self.candrop && self.weapon) - W_ThrowNewWeapon(self, self.weapon, 0, self.origin, randomvec() * 150 + '0 0 325'); - - self.event_damage = ((gibbed) ? func_null : monsters_corpse_damage); + self.event_damage = ((gibbed) ? func_null : Monster_Dead_Damage); self.solid = SOLID_CORPSE; self.takedamage = DAMAGE_AIM; self.deadflag = DEAD_DEAD; self.enemy = world; self.movetype = MOVETYPE_TOSS; self.moveto = self.origin; - self.touch = MonsterTouch; // reset incase monster was pouncing + self.touch = Monster_Touch; // reset incase monster was pouncing self.reset = func_null; self.state = 0; self.attack_finished_single = 0; + self.effects = 0; if(!((self.flags & FL_FLY) || (self.flags & FL_SWIM))) self.velocity = '0 0 0'; + CSQCModel_UnlinkEntity(); + MON_ACTION(self.monsterid, MR_DEATH); + + if(self.candrop && self.weapon) + W_ThrowNewWeapon(self, self.weapon, 0, self.origin, randomvec() * 150 + '0 0 325'); } -void monsters_damage (entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) +void Monster_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) { - if(self.frozen && deathtype != DEATH_KILL && deathtype != DEATH_NADE_ICE_FREEZE) - return; - if((self.spawnflags & MONSTERFLAG_INVINCIBLE) && deathtype != DEATH_KILL) return; - if(time < self.pain_finished && deathtype != DEATH_KILL) + if(self.frozen && deathtype != DEATH_KILL && deathtype != DEATH_NADE_ICE_FREEZE) return; + //if(time < self.pain_finished && deathtype != DEATH_KILL) + //return; + if(time < self.spawnshieldtime && deathtype != DEATH_KILL) return; @@ -1000,13 +1044,24 @@ void monsters_damage (entity inflictor, entity attacker, float damage, int death vector v; float take, save; - v = healtharmor_applydamage(self.armorvalue, self.m_armor_blockpercent, deathtype, damage); - take = v.x; - save = v.y; + v = healtharmor_applydamage(100, self.armorvalue / 100, deathtype, damage); + take = v_x; + save = v_y; - self.health -= take; + damage_take = take; + frag_attacker = attacker; + frag_deathtype = deathtype; + MON_ACTION(self.monsterid, MR_PAIN); + take = damage_take; + + if(take) + { + self.health -= take; + Monster_Sound(monstersound_pain, 1.2, true, CH_PAIN); + } - WaypointSprite_UpdateHealth(self.sprite, self.health); + if(self.sprite) + WaypointSprite_UpdateHealth(self.sprite, self.health); self.dmg_time = time; @@ -1015,7 +1070,7 @@ void monsters_damage (entity inflictor, entity attacker, float damage, int death self.velocity += force * self.damageforcescale; - if(deathtype != DEATH_DROWN) + if(deathtype != DEATH_DROWN && take) { Violence_GibSplash_At(hitloc, force, 2, bound(0, take, 200) / 16, self, attacker); if (take > 50) @@ -1035,7 +1090,7 @@ void monsters_damage (entity inflictor, entity attacker, float damage, int death SUB_UseTargets(); self.target2 = self.oldtarget2; // reset to original target on death, incase we respawn - monster_die(attacker, (self.health <= -100 || deathtype == DEATH_KILL)); + Monster_Dead(attacker, (self.health <= -100 || deathtype == DEATH_KILL)); WaypointSprite_Kill(self.sprite); @@ -1053,49 +1108,65 @@ void monsters_damage (entity inflictor, entity attacker, float damage, int death } } -void monster_setupcolors(entity mon) +// don't check for enemies, just keep walking in a straight line +void Monster_Move_2D(float mspeed, float allow_jumpoff) { - if(IS_PLAYER(mon.monster_owner)) - mon.colormap = mon.monster_owner.colormap; - else if(teamplay && mon.team) - mon.colormap = 1024 + (mon.team - 1) * 17; - else + if(gameover || (round_handler_IsActive() && !round_handler_IsRoundStarted()) || self.draggedby != world || time < game_starttime || (autocvar_g_campaign && !campaign_bots_may_start) || time < self.spawn_time) { - if(mon.monster_skill <= MONSTER_SKILL_EASY) - mon.colormap = 1029; - else if(mon.monster_skill <= MONSTER_SKILL_MEDIUM) - mon.colormap = 1027; - else if(mon.monster_skill <= MONSTER_SKILL_HARD) - mon.colormap = 1038; - else if(mon.monster_skill <= MONSTER_SKILL_INSANE) - mon.colormap = 1028; - else if(mon.monster_skill <= MONSTER_SKILL_NIGHTMARE) - mon.colormap = 1032; - else - mon.colormap = 1024; + mspeed = 0; + if(time >= self.spawn_time) + setanim(self, self.anim_idle, true, false, false); + movelib_beak_simple(0.6); + return; } -} - -void monster_changeteam(entity ent, float newteam) -{ - if(!teamplay) { return; } - ent.team = newteam; - ent.monster_attack = true; // new team, activate attacking - monster_setupcolors(ent); - - if(ent.sprite) + float reverse = FALSE; + vector a, b; + + makevectors(self.angles); + a = self.origin + '0 0 16'; + b = self.origin + '0 0 16' + v_forward * 32; + + traceline(a, b, MOVE_NORMAL, self); + + if(trace_fraction != 1.0) { - WaypointSprite_UpdateTeamRadar(ent.sprite, RADARICON_DANGER, ((newteam) ? Team_ColorRGB(newteam) : '1 0 0')); - - ent.sprite.team = newteam; - ent.sprite.SendFlags |= 1; + reverse = TRUE; + + if(trace_ent) + if(IS_PLAYER(trace_ent) && !(trace_ent.items & IT_STRENGTH)) + reverse = FALSE; + } + + // TODO: fix this... tracing is broken if the floor is thin + /* + if(!allow_jumpoff) + { + a = b - '0 0 32'; + traceline(b, a, MOVE_WORLDONLY, self); + if(trace_fraction == 1.0) + reverse = TRUE; + } */ + + if(reverse) + { + self.angles_y = anglemods(self.angles_y - 180); + makevectors(self.angles); } + + movelib_move_simple_gravity(v_forward, mspeed, 1); + + if(time > self.pain_finished) + if(time > self.attack_finished_single) + if(vlen(self.velocity) > 10) + setanim(self, self.anim_walk, true, false, false); + else + setanim(self, self.anim_idle, true, false, false); } -void monster_think() +void Monster_Think() { - self.think = monster_think; + self.think = Monster_Think; self.nextthink = self.ticrate; if(self.monster_lifetime) @@ -1105,53 +1176,64 @@ void monster_think() return; } - MON_ACTION(self.monsterid, MR_THINK); + if(MON_ACTION(self.monsterid, MR_THINK)) + Monster_Move(self.speed2, self.speed, self.stopspeed); CSQCMODEL_AUTOUPDATE(); } -float monster_spawn() +float Monster_Spawn_Setup() { MON_ACTION(self.monsterid, MR_SETUP); + // ensure some basic needs are met + if(!self.health) { self.health = 100; } + if(!self.armorvalue) { self.armorvalue = bound(0.2, 0.5 * MONSTER_SKILLMOD(self), 0.9); } + if(!self.target_range) { self.target_range = autocvar_g_monsters_target_range; } + if(!self.respawntime) { self.respawntime = autocvar_g_monsters_respawn_delay; } + if(!self.monster_moveflags) { self.monster_moveflags = MONSTER_MOVE_WANDER; } + if(!self.attack_range) { self.attack_range = autocvar_g_monsters_attack_range; } + if(!self.damageforcescale) { self.damageforcescale = autocvar_g_monsters_damageforcescale; } + if(!(self.spawnflags & MONSTERFLAG_RESPAWNED)) { - Monster_CheckMinibossFlag(); - self.health *= Monster_SkillModifier(); + Monster_Miniboss_Check(); + self.health *= MONSTER_SKILLMOD(self); + + if(!self.skin) + self.skin = rint(random() * 4); } self.max_health = self.health; self.pain_finished = self.nextthink; - if(IS_PLAYER(self.monster_owner)) + if(IS_PLAYER(self.monster_follow)) self.effects |= EF_DIMLIGHT; - if(!(self.spawnflags & MONSTERFLAG_RESPAWNED)) - if(!self.skin) - self.skin = rint(random() * 4); - - if(!self.attack_range) - self.attack_range = autocvar_g_monsters_attack_range; - if(!self.wander_delay) { self.wander_delay = 2; } if(!self.wander_distance) { self.wander_distance = 600; } - precache_monstersounds(); - UpdateMonsterSounds(); + Monster_Sounds_Precache(); + Monster_Sounds_Update(); if(teamplay) self.monster_attack = true; // we can have monster enemies in team games - MonsterSound(monstersound_spawn, 0, false, CH_VOICE); + Monster_Sound(monstersound_spawn, 0, false, CH_VOICE); - WaypointSprite_Spawn(M_NAME(self.monsterid), 0, 1024, self, '0 0 1' * (self.maxs.z + 15), world, self.team, self, sprite, true, RADARICON_DANGER, ((self.team) ? Team_ColorRGB(self.team) : '1 0 0')); - if(!(self.spawnflags & MONSTERFLAG_INVINCIBLE)) + if(autocvar_g_monsters_healthbars) { - WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health); - WaypointSprite_UpdateHealth(self.sprite, self.health); + WaypointSprite_Spawn(self.monster_name, 0, 1024, self, '0 0 1' * (self.maxs_z + 15), world, self.team, + self, sprite, true, RADARICON_DANGER, ((self.team) ? Team_ColorRGB(self.team) : '1 0 0')); + + if(!(self.spawnflags & MONSTERFLAG_INVINCIBLE)) + { + WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health); + WaypointSprite_UpdateHealth(self.sprite, self.health); + } } - self.think = monster_think; + self.think = Monster_Think; self.nextthink = time + self.ticrate; if(MUTATOR_CALLHOOK(MonsterSpawn)) @@ -1160,21 +1242,24 @@ float monster_spawn() return true; } -float monster_initialize(float mon_id) +bool Monster_Spawn(int mon_id) { - if(!autocvar_g_monsters) { return false; } - if(!(self.spawnflags & MONSTERFLAG_RESPAWNED)) { MON_ACTION(mon_id, MR_PRECACHE); } - if(Monster_CheckAppearFlags(self, mon_id)) { return true; } // return true so the monster isn't removed - + // setup the basic required properties for a monster entity mon = get_monsterinfo(mon_id); + if(!mon.monsterid) { return false; } // invalid monster + + if(!autocvar_g_monsters) { Monster_Remove(self); return false; } + + self.mdl = mon.model; + if(Monster_Appear_Check(self, mon_id)) { return true; } // return true so the monster isn't removed if(!self.monster_skill) self.monster_skill = cvar("g_monsters_skill"); // support for quake style removing monsters based on skill - if(self.monster_skill == MONSTER_SKILL_EASY) if(self.spawnflags & MONSTERSKILL_NOTEASY) { return false; } - if(self.monster_skill == MONSTER_SKILL_MEDIUM) if(self.spawnflags & MONSTERSKILL_NOTMEDIUM) { return false; } - if(self.monster_skill == MONSTER_SKILL_HARD) if(self.spawnflags & MONSTERSKILL_NOTHARD) { return false; } + if(self.monster_skill == MONSTER_SKILL_EASY) if(self.spawnflags & MONSTERSKILL_NOTEASY) { Monster_Remove(self); return false; } + if(self.monster_skill == MONSTER_SKILL_MEDIUM) if(self.spawnflags & MONSTERSKILL_NOTMEDIUM) { Monster_Remove(self); return false; } + if(self.monster_skill == MONSTER_SKILL_HARD) if(self.spawnflags & MONSTERSKILL_NOTHARD) { Monster_Remove(self); return false; } if(self.team && !teamplay) self.team = 0; @@ -1183,19 +1268,18 @@ float monster_initialize(float mon_id) if(!(self.spawnflags & MONSTERFLAG_RESPAWNED)) // don't count re-spawning monsters either monsters_total += 1; - setmodel(self, mon.model); - //setsize(self, mon.mins, mon.maxs); + setmodel(self, self.mdl); self.flags = FL_MONSTER; + self.classname = "monster"; self.takedamage = DAMAGE_AIM; self.bot_attack = true; self.iscreature = true; self.teleportable = true; self.damagedbycontents = true; self.monsterid = mon_id; - self.damageforcescale = 0; - self.event_damage = monsters_damage; - self.touch = MonsterTouch; - self.use = monster_use; + self.event_damage = Monster_Damage; + self.touch = Monster_Touch; + self.use = Monster_Use; self.solid = SOLID_BBOX; self.movetype = MOVETYPE_WALK; self.spawnshieldtime = time + autocvar_g_monsters_spawnshieldtime; @@ -1204,11 +1288,12 @@ float monster_initialize(float mon_id) self.moveto = self.origin; self.pos1 = self.origin; self.pos2 = self.angles; - self.reset = monsters_reset; + self.reset = Monster_Reset; self.netname = mon.netname; - self.monster_name = M_NAME(mon_id); + self.monster_attackfunc = mon.monster_attackfunc; + self.monster_name = mon.monster_name; self.candrop = true; - self.view_ofs = '0 0 1' * (self.maxs.z * 0.5); + self.view_ofs = '0 0 0.7' * (self.maxs_z * 0.5); self.oldtarget2 = self.target2; self.pass_distance = 0; self.deadflag = DEAD_NO; @@ -1216,22 +1301,15 @@ float monster_initialize(float mon_id) self.spawn_time = time; self.spider_slowness = 0; self.gravity = 1; + self.monster_moveto = '0 0 0'; + self.monster_face = '0 0 0'; self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP; - - if(!self.scale) - self.scale = 1; - - if(autocvar_g_monsters_edit) - self.grab = 1; // owner may carry their monster - - if(autocvar_g_fullbrightplayers) - self.effects |= EF_FULLBRIGHT; - - if(autocvar_g_nodepthtestplayers) - self.effects |= EF_NODEPTHTEST; - - if(mon.spawnflags & MONSTER_TYPE_SWIM) - self.flags |= FL_SWIM; + + if(!self.scale) { self.scale = 1; } + if(autocvar_g_monsters_edit) { self.grab = 1; } + if(autocvar_g_fullbrightplayers) { self.effects |= EF_FULLBRIGHT; } + if(autocvar_g_nodepthtestplayers) { self.effects |= EF_NODEPTHTEST; } + if(mon.spawnflags & MONSTER_TYPE_SWIM) { self.flags |= FL_SWIM; } if(mon.spawnflags & MONSTER_TYPE_FLY) { @@ -1245,22 +1323,13 @@ float monster_initialize(float mon_id) setsize(self, mon.mins * self.scale, mon.maxs * self.scale); - if(!self.ticrate) - self.ticrate = autocvar_g_monsters_think_delay; + self.ticrate = bound(sys_frametime, ((!self.ticrate) ? autocvar_g_monsters_think_delay : self.ticrate), 60); - self.ticrate = bound(sys_frametime, self.ticrate, 60); - - if(!self.m_armor_blockpercent) - self.m_armor_blockpercent = 0.5; - - if(!self.target_range) - self.target_range = autocvar_g_monsters_target_range; - - if(!self.respawntime) - self.respawntime = autocvar_g_monsters_respawn_delay; - - if(!self.monster_moveflags) - self.monster_moveflags = MONSTER_MOVE_WANDER; + if(!Monster_Spawn_Setup()) + { + Monster_Remove(self); + return false; + } if(!self.noalign) { @@ -1269,9 +1338,6 @@ float monster_initialize(float mon_id) setorigin(self, trace_endpos); } - if(!monster_spawn()) - return false; - if(!(self.spawnflags & MONSTERFLAG_RESPAWNED)) monster_setupcolors(self); diff --git a/qcsrc/common/monsters/sv_monsters.qh b/qcsrc/common/monsters/sv_monsters.qh index 697c07b72..d1d224363 100644 --- a/qcsrc/common/monsters/sv_monsters.qh +++ b/qcsrc/common/monsters/sv_monsters.qh @@ -1,55 +1,100 @@ #ifndef SV_MONSTERS_H #define SV_MONSTERS_H -.string spawnmob; -.float monster_attack; +// stats networking +.int stat_monsters_killed; +.int stat_monsters_total; +int monsters_total; +int monsters_killed; + +// monster properties +.int monster_movestate; // move target priority +.entity monster_follow; // follow target +.float wander_delay; // logic delay between moving while idle +.float wander_distance; // distance to move between wander delays +.float monster_lifetime; // monster dies instantly after this delay, set from spawn +.float attack_range; // melee attack if closer, ranged attack if further away (TODO: separate ranged attack range?) +.float spawn_time; // delay monster thinking until spawn animation has completed +.bool candrop; // toggle to allow disabling monster item drops +.int monster_movestate; // will be phased out +.int monster_moveflags; +.string oldtarget2; // a copy of the original follow target string +.float last_trace; // logic delay between target tracing +.float last_enemycheck; // for checking enemy +.float anim_finished; // will be phased out when we have proper animations system +.vector monster_moveto; // custom destination for monster (reset to '0 0 0' when you're done!) +.vector monster_face; // custom looking direction for monster (reset to '0 0 0' when you're done!) +.float speed2; // run speed +.float stopspeed; +.int oldskin; +.string mdl_dead; // dead model for goombas + +#define MONSTER_SKILLMOD(mon) (0.5 + mon.monster_skill * ((1.2 - 0.3) / 10)) + +// other properties +.bool monster_attack; // indicates whether an entity can be attacked by monsters +.float spider_slowness; // effect time of slowness inflicted by spiders + +// monster state declarations +const int MONSTER_MOVE_FOLLOW = 1; // monster will follow if in range, or stand still +const int MONSTER_MOVE_WANDER = 2; // monster will ignore owner & wander around +const int MONSTER_MOVE_SPAWNLOC = 3; // monster will move to its spawn location when not attacking +const int MONSTER_MOVE_NOMOVE = 4; // monster simply stands still +const int MONSTER_MOVE_ENEMY = 5; // used only as a movestate +const int MONSTER_ATTACK_MELEE = 6; +const int MONSTER_ATTACK_RANGED = 7; -.entity monster_owner; // new monster owner entity, fixes non-solid monsters +// skill declarations +const int MONSTER_SKILL_EASY = 1; +const int MONSTER_SKILL_MEDIUM = 3; +const int MONSTER_SKILL_HARD = 5; +const int MONSTER_SKILL_INSANE = 7; +const int MONSTER_SKILL_NIGHTMARE = 10; -.float stat_monsters_killed; // stats -.float stat_monsters_total; -float monsters_total; -float monsters_killed; -void monsters_setstatus(); // monsters.qc -.float monster_moveflags; // checks where to move when not attacking +const int MONSTERSKILL_NOTEASY = 256; // monster will not spawn on skill <= 1 +const int MONSTERSKILL_NOTMEDIUM = 512; // monster will not spawn on skill 2 +const int MONSTERSKILL_NOTHARD = 1024; // monster will not spawn on skill >= 3 -.float wander_delay; -.float wander_distance; +// spawn flags +const int MONSTERFLAG_APPEAR = 2; // delay spawn until triggered +const int MONSTERFLAG_NORESPAWN = 4; +const int MONSTERFLAG_FLY_VERTICAL = 8; // fly/swim vertically +const int MONSTERFLAG_INFRONT = 32; // only check for enemies infront of us +const int MONSTERFLAG_MINIBOSS = 64; // monster spawns as mini-boss (also has a chance of naturally becoming one) +const int MONSTERFLAG_INVINCIBLE = 128; // monster doesn't take damage (may be used for map objects & temporary monsters) +const int MONSTERFLAG_SPAWNED = 16384; // flag for spawned monsters +const int MONSTERFLAG_RESPAWNED = 32768; // flag for re-spawned monsters +// compatibility with old maps (soon to be removed) .float monster_lifetime; +.int monster_skill; -.float spider_slowness; // special spider timer +// functions used elsewhere +void Monster_Remove(entity mon); -void monster_remove(entity mon); // removes a monster +void monsters_setstatus(); -.float(float attack_type) monster_attackfunc; -const int MONSTER_ATTACK_MELEE = 1; -const int MONSTER_ATTACK_RANGED = 2; +bool Monster_Spawn(int mon_id); -.float monster_skill; -const float MONSTER_SKILL_EASY = 1; -const float MONSTER_SKILL_MEDIUM = 3; -const float MONSTER_SKILL_HARD = 5; -const float MONSTER_SKILL_INSANE = 7; -const float MONSTER_SKILL_NIGHTMARE = 10; +void monster_setupcolors(entity mon); + +void Monster_Touch(); -.float fish_wasdrowning; // used to reset a drowning fish's angles if it reaches water again +void Monster_Move_2D(float mspeed, float allow_jumpoff); -.float candrop; +void Monster_Delay(float repeat_count, float repeat_defer, float defer_amnt, void() func); -.float attack_range; +float Monster_Attack_Melee(entity targ, float damg, vector anim, float er, float animtime, int deathtype, float dostop); -.float spawn_time; // stop monster from moving around right after spawning +bool Monster_Attack_Leap(vector anm, void() touchfunc, vector vel, float animtime); -.string oldtarget2; -.float lastshielded; +entity Monster_FindTarget(entity mon); -.vector oldangles; +void monster_makevectors(entity e); -.float m_armor_blockpercent; +void Monster_Sound(.string samplefield, float sound_delay, float delaytoo, float chan); // monster sounds -// copied from player sounds .float msound_delay; // temporary antilag system #define ALLMONSTERSOUNDS \ _MSOUND(death) \ @@ -58,7 +103,8 @@ const float MONSTER_SKILL_NIGHTMARE = 10; _MSOUND(melee) \ _MSOUND(pain) \ _MSOUND(spawn) \ - _MSOUND(idle) + _MSOUND(idle) \ + _MSOUND(attack) #define _MSOUND(m) .string monstersound_##m; ALLMONSTERSOUNDS @@ -66,36 +112,4 @@ ALLMONSTERSOUNDS float GetMonsterSoundSampleField_notFound; -const int MONSTERSKILL_NOTEASY = 256; // monster will not spawn on skill <= 1 -const int MONSTERSKILL_NOTMEDIUM = 512; // monster will not spawn on skill 2 -const int MONSTERSKILL_NOTHARD = 1024; // monster will not spawn on skill >= 3 - -// new flags -const int MONSTERFLAG_APPEAR = 2; // delay spawn until triggered -const int MONSTERFLAG_NORESPAWN = 4; -const int MONSTERFLAG_FLY_VERTICAL = 8; // fly/swim vertically -const int MONSTERFLAG_INFRONT = 32; // only check for enemies infront of us -const int MONSTERFLAG_MINIBOSS = 64; // monster spawns as mini-boss (also has a chance of naturally becoming one) -const int MONSTERFLAG_INVINCIBLE = 128; // monster doesn't take damage (may be used for map objects & temporary monsters) -const int MONSTERFLAG_SPAWNED = 16384; // flag for spawned monsters -const int MONSTERFLAG_RESPAWNED = 32768; // flag for re-spawned monsters - -.int monster_movestate; // used to tell what the monster is currently doing -const int MONSTER_MOVE_OWNER = 1; // monster will move to owner if in range, or stand still -const int MONSTER_MOVE_WANDER = 2; // monster will ignore owner & wander around -const int MONSTER_MOVE_SPAWNLOC = 3; // monster will move to its spawn location when not attacking -const int MONSTER_MOVE_NOMOVE = 4; // monster simply stands still -const int MONSTER_MOVE_ENEMY = 5; // used only as a movestate - -const int MONSTER_STATE_ATTACK_LEAP = 1; -const int MONSTER_STATE_ATTACK_MELEE = 2; - -float monster_initialize(float mon_id); -float monster_leap (float anm, void() touchfunc, vector vel, float anim_finished); -void monster_makevectors(entity e); -float monster_melee(entity targ, float damg, float anim, float er, float anim_finished, int deathtype, float dostop); -void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_run, float manim_walk, float manim_idle); -void monster_setupcolors(entity mon); -float Monster_SkillModifier(); -void MonsterTouch (); #endif diff --git a/qcsrc/server/autocvars.qh b/qcsrc/server/autocvars.qh index 5e1c01a11..f93011e0b 100644 --- a/qcsrc/server/autocvars.qh +++ b/qcsrc/server/autocvars.qh @@ -730,6 +730,7 @@ bool autocvar_g_monsters_sounds; float autocvar_g_monsters_think_delay; int autocvar_g_monsters_max; int autocvar_g_monsters_max_perplayer; +float autocvar_g_monsters_damageforcescale = 0.8; float autocvar_g_monsters_target_range; bool autocvar_g_monsters_target_infront; float autocvar_g_monsters_attack_range; @@ -745,6 +746,8 @@ bool autocvar_g_monsters_teams; float autocvar_g_monsters_respawn_delay; bool autocvar_g_monsters_respawn; float autocvar_g_monsters_armor_blockpercent; +float autocvar_g_monsters_healthbars; +float autocvar_g_monsters_lineofsight; float autocvar_g_touchexplode_radius; float autocvar_g_touchexplode_damage; float autocvar_g_touchexplode_edgedamage; diff --git a/qcsrc/server/command/cmd.qc b/qcsrc/server/command/cmd.qc index 828c86302..5a0527c89 100644 --- a/qcsrc/server/command/cmd.qc +++ b/qcsrc/server/command/cmd.qc @@ -26,10 +26,6 @@ #include "../../common/teams.qh" #include "../../common/util.qh" -#include "../../common/monsters/all.qh" -#include "../../common/monsters/spawn.qh" -#include "../../common/monsters/sv_monsters.qh" - #include "../../warpzonelib/common.qh" void ClientKill_TeamChange (float targetteam); // 0 = don't change, -1 = auto, -2 = spec @@ -220,163 +216,6 @@ void ClientCommand_join(float request) } } -void ClientCommand_mobedit(float request, float argc) -{ - switch(request) - { - case CMD_REQUEST_COMMAND: - { - if(argv(1) && argv(2)) - { - makevectors(self.v_angle); - WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * 100, MOVE_NORMAL, self); - - if(!autocvar_g_monsters_edit) { sprint(self, "Monster property editing is not enabled.\n"); return; } - if(trace_ent.flags & FL_MONSTER) - { - if(trace_ent.realowner != self) { sprint(self, "That monster does not belong to you.\n"); return; } - switch(argv(1)) - { - case "skin": - { - if(trace_ent.monsterid != MON_MAGE) - trace_ent.skin = stof(argv(2)); - return; - } - case "movetarget": - { - trace_ent.monster_moveflags = stof(argv(2)); - return; - } - } - } - } - } - default: - sprint(self, "Incorrect parameters for ^2mobedit^7\n"); - case CMD_REQUEST_USAGE: - { - sprint(self, "\nUsage:^3 cmd mobedit [argument]\n"); - sprint(self, " Where 'argument' can be skin or movetarget.\n"); - sprint(self, " Aim at your monster to edit its properties.\n"); - return; - } - } -} - -void ClientCommand_mobkill(float request) -{ - switch(request) - { - case CMD_REQUEST_COMMAND: - { - makevectors(self.v_angle); - WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * 100, MOVE_NORMAL, self); - - if(trace_ent.flags & FL_MONSTER) - { - if(trace_ent.realowner != self) - { - sprint(self, "That monster does not belong to you.\n"); - return; - } - sprint(self, strcat("Your pet '", trace_ent.monster_name, "' has been brutally mutilated.\n")); - Damage (trace_ent, world, world, trace_ent.health + trace_ent.max_health + 200, DEATH_KILL, trace_ent.origin, '0 0 0'); - return; - } - } - - default: - sprint(self, "Incorrect parameters for ^2mobkill^7\n"); - case CMD_REQUEST_USAGE: - { - sprint(self, "\nUsage:^3 cmd mobkill\n"); - sprint(self, " Aim at your monster to kill it.\n"); - return; - } - } -} - -void ClientCommand_mobspawn(float request, float argc) -{ - switch(request) - { - case CMD_REQUEST_COMMAND: - { - entity e; - string tospawn; - float moveflag, monstercount = 0; - - moveflag = (argv(2) ? stof(argv(2)) : 1); // follow owner if not defined - tospawn = strtolower(argv(1)); - - if(tospawn == "list") - { - sprint(self, monsterlist_reply); - return; - } - - FOR_EACH_MONSTER(e) - { - if(e.realowner == self) - ++monstercount; - } - - if(autocvar_g_monsters_max <= 0 || autocvar_g_monsters_max_perplayer <= 0) { sprint(self, "Monster spawning is disabled.\n"); return; } - else if(!IS_PLAYER(self)) { sprint(self, "You can't spawn monsters while spectating.\n"); return; } - else if(MUTATOR_CALLHOOK(AllowMobSpawning)) { sprint(self, "Monster spawning is currently disabled by a mutator.\n"); return; } - else if(!autocvar_g_monsters) { Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_MONSTERS_DISABLED); return; } - else if(self.vehicle) { sprint(self, "You can't spawn monsters while driving a vehicle.\n"); return; } - else if(self.frozen) { sprint(self, "You can't spawn monsters while frozen.\n"); return; } - else if(autocvar_g_campaign) { sprint(self, "You can't spawn monsters in campaign mode.\n"); return; } - else if(self.deadflag != DEAD_NO) { sprint(self, "You can't spawn monsters while dead.\n"); return; } - else if(monstercount >= autocvar_g_monsters_max_perplayer) { sprint(self, "You have spawned too many monsters, kill some before trying to spawn any more.\n"); return; } - else if(totalspawned >= autocvar_g_monsters_max) { sprint(self, "The global maximum monster count has been reached, kill some before trying to spawn any more.\n"); return; } - else if(tospawn != "") - { - float found = 0, i; - entity mon; - - for(i = MON_FIRST; i <= MON_LAST; ++i) - { - mon = get_monsterinfo(i); - if(mon.netname == tospawn) - { - found = true; - break; - } - } - - if(found || tospawn == "random") - { - totalspawned += 1; - - makevectors(self.v_angle); - WarpZone_TraceBox (CENTER_OR_VIEWOFS(self), PL_MIN, PL_MAX, CENTER_OR_VIEWOFS(self) + v_forward * 150, true, self); - //WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * 150, MOVE_NORMAL, self); - - e = spawnmonster(tospawn, 0, self, self, trace_endpos, false, false, moveflag); - - sprint(self, strcat("Spawned ", e.monster_name, "\n")); - - return; - } - } - } - - default: - sprint(self, "Incorrect parameters for ^2mobspawn^7\n"); - case CMD_REQUEST_USAGE: - { - sprint(self, "\nUsage:^3 cmd mobspawn [movetype]\n"); - sprint(self, " See 'cmd mobspawn list' for available monsters.\n"); - sprint(self, " Argument 'random' spawns a random monster.\n"); - sprint(self, " Monster will follow the owner if second argument is not defined.\n"); - return; - } - } -} - void ClientCommand_physics(float request, float argc) { switch(request) @@ -806,9 +645,6 @@ void ClientCommand_(float request) CLIENT_COMMAND("clientversion", ClientCommand_clientversion(request, arguments), "Release version of the game") \ CLIENT_COMMAND("mv_getpicture", ClientCommand_mv_getpicture(request, arguments), "Retrieve mapshot picture from the server") \ CLIENT_COMMAND("join", ClientCommand_join(request), "Become a player in the game") \ - CLIENT_COMMAND("mobedit", ClientCommand_mobedit(request, arguments), "Edit your monster's properties") \ - CLIENT_COMMAND("mobkill", ClientCommand_mobkill(request), "Kills your monster") \ - CLIENT_COMMAND("mobspawn", ClientCommand_mobspawn(request, arguments), "Spawn monsters infront of yourself") \ CLIENT_COMMAND("physics", ClientCommand_physics(request, arguments), "Change physics set") \ CLIENT_COMMAND("ready", ClientCommand_ready(request), "Qualify as ready to end warmup stage (or restart server if allowed)") \ CLIENT_COMMAND("say", ClientCommand_say(request, arguments, command), "Print a message to chat to all players") \ diff --git a/qcsrc/server/command/common.qc b/qcsrc/server/command/common.qc index 0bdc6672f..a1b0d9924 100644 --- a/qcsrc/server/command/common.qc +++ b/qcsrc/server/command/common.qc @@ -312,6 +312,147 @@ void CommonCommand_cvar_purechanges(float request, entity caller) } } +void CommonCommand_editmob(float request, entity caller, float argc) +{ + switch(request) + { + case CMD_REQUEST_COMMAND: + { + if(autocvar_g_campaign) { print_to(caller, "Monster editing is disabled in singleplayer"); return; } + // no checks for g_monsters here, as it may be toggled mid match which existing monsters + + if(caller) + { + makevectors(self.v_angle); + WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * 100, MOVE_NORMAL, self); + } + + entity mon = trace_ent; + float is_visible = (mon.flags & FL_MONSTER); + string argument = argv(2); + + switch(argv(1)) + { + case "name": + { + if(!caller) { print_to(caller, "Only players can edit monsters"); return; } + if(!argument) { break; } // escape to usage + if(!autocvar_g_monsters_edit) { print_to(caller, "Monster editing is disabled"); return; } + if(mon.realowner != caller && autocvar_g_monsters_edit < 2) { print_to(caller, "This monster does not belong to you"); return; } + if(!is_visible) { print_to(caller, "You must look at your monster to edit it"); return; } + + string mon_oldname = mon.monster_name; + + mon.monster_name = argument; + if(mon.sprite) { WaypointSprite_UpdateSprites(mon.sprite, strzone(mon.monster_name), string_null, string_null); } + print_to(caller, sprintf("Your pet '%s' is now known as '%s'", mon_oldname, mon.monster_name)); + return; + } + case "spawn": + { + if(!caller) { print_to(caller, "Only players can spawn monsters"); return; } + if(!argv(2)) { break; } // escape to usage + + float moveflag, tmp_moncount = 0; + string arg_lower = strtolower(argument); + moveflag = (argv(3)) ? stof(argv(3)) : 1; // follow owner if not defined + ret_string = "Monster spawning is currently disabled by a mutator"; + + if(arg_lower == "list") { print_to(caller, monsterlist_reply); return; } + + FOR_EACH_MONSTER(mon) { if(mon.realowner == caller) ++tmp_moncount; } + + if(!autocvar_g_monsters) { print_to(caller, "Monsters are disabled"); return; } + if(autocvar_g_monsters_max <= 0 || autocvar_g_monsters_max_perplayer <= 0) { print_to(caller, "Monster spawning is disabled"); return; } + if(!IS_PLAYER(caller)) { print_to(caller, "You must be playing to spawn a monster"); return; } + if(MUTATOR_CALLHOOK(AllowMobSpawning)) { print_to(caller, ret_string); return; } + if(caller.vehicle) { print_to(caller, "You can't spawn monsters while driving a vehicle"); return; } + if(caller.frozen) { print_to(caller, "You can't spawn monsters while frozen"); return; } + if(caller.deadflag != DEAD_NO) { print_to(caller, "You can't spawn monsters while dead"); return; } + if(tmp_moncount >= autocvar_g_monsters_max) { print_to(caller, "The maximum monster count has been reached"); return; } + if(tmp_moncount >= autocvar_g_monsters_max_perplayer) { print_to(caller, "You can't spawn any more monsters"); return; } + + float i = 0, found = false; + for(i = MON_FIRST; i <= MON_LAST; ++i) + { + mon = get_monsterinfo(i); + if(mon.netname == arg_lower) { found = true; break; } + } + + if(!found && arg_lower != "random") { print_to(caller, "Invalid monster"); return; } + + totalspawned += 1; + WarpZone_TraceBox (CENTER_OR_VIEWOFS(caller), caller.mins, caller.maxs, CENTER_OR_VIEWOFS(caller) + v_forward * 150, true, caller); + mon = spawnmonster(arg_lower, 0, caller, caller, trace_endpos, false, false, moveflag); + print_to(caller, strcat("Spawned ", mon.monster_name)); + return; + } + case "kill": + { + if(!caller) { print_to(caller, "Only players can kill monsters"); return; } + if(mon.realowner != caller && autocvar_g_monsters_edit < 2) { print_to(caller, "This monster does not belong to you"); return; } + if(!is_visible) { print_to(caller, "You must look at your monster to edit it"); return; } + + Damage (mon, world, world, mon.health + mon.max_health + 200, DEATH_KILL, mon.origin, '0 0 0'); + print_to(caller, strcat("Your pet '", mon.monster_name, "' has been brutally mutilated")); + return; + } + case "skin": + { + if(!caller) { print_to(caller, "Only players can edit monsters"); return; } + if(!argument) { break; } // escape to usage + if(!autocvar_g_monsters_edit) { print_to(caller, "Monster editing is disabled"); return; } + if(!is_visible) { print_to(caller, "You must look at your monster to edit it"); return; } + if(mon.realowner != caller && autocvar_g_monsters_edit < 2) { print_to(caller, "This monster does not belong to you"); return; } + if(mon.monsterid == MON_MAGE) { print_to(caller, "Mage skins can't be changed"); return; } // TODO + + mon.skin = stof(argument); + print_to(caller, strcat("Monster skin successfully changed to ", ftos(mon.skin))); + return; + } + case "movetarget": + { + if(!caller) { print_to(caller, "Only players can edit monsters"); return; } + if(!argument) { break; } // escape to usage + if(!autocvar_g_monsters_edit) { print_to(caller, "Monster editing is disabled"); return; } + if(!is_visible) { print_to(caller, "You must look at your monster to edit it"); return; } + if(mon.realowner != caller && autocvar_g_monsters_edit < 2) { print_to(caller, "This monster does not belong to you"); return; } + + mon.monster_moveflags = stof(argument); + print_to(caller, strcat("Monster move target successfully changed to ", ftos(mon.monster_moveflags))); + return; + } + case "butcher": + { + if(caller) { print_to(caller, "This command is not available to players"); return; } + if(g_invasion) { print_to(caller, "This command does not work during an invasion!"); return; } + + float tmp_remcount = 0; + entity tmp_entity; + + FOR_EACH_MONSTER(tmp_entity) { Monster_Remove(tmp_entity); ++tmp_remcount; } + + monsters_total = monsters_killed = totalspawned = 0; + + print_to(caller, (tmp_remcount) ? sprintf("Killed %d monster%s", tmp_remcount, (tmp_remcount == 1) ? "" : "s") : "No monsters to kill"); + return; + } + } + } + + default: + case CMD_REQUEST_USAGE: + { + print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " editmob command [arguments]")); + print_to(caller, " Where 'command' can be butcher spawn skin movetarget kill name"); + print_to(caller, " spawn, skin, movetarget and name require 'arguments'"); + print_to(caller, " spawn also takes arguments list and random"); + print_to(caller, " Monster will follow owner if third argument of spawn command is not defined"); + return; + } + } +} + void CommonCommand_info(float request, entity caller, float argc) { switch(request) @@ -560,7 +701,8 @@ void CommonCommand_timeout(float request, entity caller) // DEAR GOD THIS COMMAN { if(caller) { caller.allowed_timeouts -= 1; } - bprint(GetCallerName(caller), " ^7called a timeout", (caller ? strcat(" (", ftos(caller.allowed_timeouts), " timeout(s) left)") : ""), "!\n"); // write a bprint who started the timeout (and how many they have left) + // write a bprint who started the timeout (and how many they have left) + bprint(GetCallerName(caller), " ^7called a timeout", (caller ? strcat(" (", ftos(caller.allowed_timeouts), " timeout(s) left)") : ""), "!\n"); timeout_status = TIMEOUT_LEADTIME; timeout_caller = caller; diff --git a/qcsrc/server/command/common.qh b/qcsrc/server/command/common.qh index 0925d7137..9a011fb85 100644 --- a/qcsrc/server/command/common.qh +++ b/qcsrc/server/command/common.qh @@ -2,6 +2,7 @@ #define COMMAND_COMMON_H #include "vote.qh" +#include "../../common/monsters/spawn.qh" #include "../../common/command/generic.qh" #include "../../common/command/command.qh" @@ -92,6 +93,8 @@ void CommonCommand_cvar_changes(float request, entity caller); void CommonCommand_cvar_purechanges(float request, entity caller); +void CommonCommand_editmob(float request, entity caller, float argc); + void CommonCommand_info(float request, entity caller, float argc); void CommonCommand_ladder(float request, entity caller); @@ -123,6 +126,7 @@ void CommonCommand_who(float request, entity caller, float argc); #define COMMON_COMMANDS(request,caller,arguments,command) \ COMMON_COMMAND("cvar_changes", CommonCommand_cvar_changes(request, caller), "Prints a list of all changed server cvars") \ COMMON_COMMAND("cvar_purechanges", CommonCommand_cvar_purechanges(request, caller), "Prints a list of all changed gameplay cvars") \ + COMMON_COMMAND("editmob", CommonCommand_editmob(request, caller, arguments), "Modifies a monster or all monsters") \ COMMON_COMMAND("info", CommonCommand_info(request, caller, arguments), "Request for unique server information set up by admin") \ COMMON_COMMAND("ladder", CommonCommand_ladder(request, caller), "Get information about top players if supported") \ COMMON_COMMAND("lsmaps", CommonCommand_lsmaps(request, caller), "List maps which can be used with the current game mode") \ diff --git a/qcsrc/server/command/sv_cmd.qc b/qcsrc/server/command/sv_cmd.qc index 0bf2df320..5f5af018d 100644 --- a/qcsrc/server/command/sv_cmd.qc +++ b/qcsrc/server/command/sv_cmd.qc @@ -176,47 +176,6 @@ void GameCommand_adminmsg(float request, float argc) } } -void GameCommand_mobbutcher(float request) -{ - switch(request) - { - case CMD_REQUEST_COMMAND: - { - if(autocvar_g_campaign) { print("This command doesn't work in campaign mode.\n"); return; } - if(g_invasion) { print("This command doesn't work during an invasion.\n"); return; } - - float removed_count = 0; - entity head; - - FOR_EACH_MONSTER(head) - { - monster_remove(head); - ++removed_count; - } - - monsters_total = 0; // reset stats? - monsters_killed = 0; - - totalspawned = 0; - - if(removed_count <= 0) - print("No monsters to kill\n"); - else - printf("Killed %d monster%s\n", removed_count, ((removed_count == 1) ? "" : "s")); - - return; // never fall through to usage - } - - default: - case CMD_REQUEST_USAGE: - { - print("\nUsage:^3 sv_cmd mobbutcher\n"); - print(" No arguments required.\n"); - return; - } - } -} - void GameCommand_allready(float request) { switch(request) @@ -1816,7 +1775,6 @@ void GameCommand_(float request) // Do not hard code aliases for these, instead create them in commands.cfg... also: keep in alphabetical order, please ;) #define SERVER_COMMANDS(request,arguments,command) \ SERVER_COMMAND("adminmsg", GameCommand_adminmsg(request, arguments), "Send an admin message to a client directly") \ - SERVER_COMMAND("mobbutcher", GameCommand_mobbutcher(request), "Instantly removes all monsters on the map") \ SERVER_COMMAND("allready", GameCommand_allready(request), "Restart the server and reset the players") \ SERVER_COMMAND("allspec", GameCommand_allspec(request, arguments), "Force all players to spectate") \ SERVER_COMMAND("anticheat", GameCommand_anticheat(request, arguments), "Create an anticheat report for a client") \ diff --git a/qcsrc/server/mutators/base.qh b/qcsrc/server/mutators/base.qh index 199cb62fb..d042a434d 100644 --- a/qcsrc/server/mutators/base.qh +++ b/qcsrc/server/mutators/base.qh @@ -175,10 +175,9 @@ MUTATOR_HOOKABLE(MonsterDies); // INPUT: // entity frag_attacker; -MUTATOR_HOOKABLE(MonsterRespawn); - // called when a monster wants to respawn - // INPUT: -// entity other; +MUTATOR_HOOKABLE(MonsterRemove); + // called when a monster is being removed + // returning true hides removal effect MUTATOR_HOOKABLE(MonsterDropItem); // called when a monster is dropping loot diff --git a/qcsrc/server/mutators/gamemode_invasion.qc b/qcsrc/server/mutators/gamemode_invasion.qc index c5f71ea1f..9870c8801 100644 --- a/qcsrc/server/mutators/gamemode_invasion.qc +++ b/qcsrc/server/mutators/gamemode_invasion.qc @@ -126,7 +126,7 @@ float Invasion_CheckWinner() if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0) { FOR_EACH_MONSTER(head) - monster_remove(head); + Monster_Remove(head); Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER); Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER); @@ -203,7 +203,7 @@ float Invasion_CheckWinner() } FOR_EACH_MONSTER(head) - monster_remove(head); + Monster_Remove(head); if(teamplay) { @@ -296,7 +296,7 @@ MUTATOR_HOOKFUNCTION(invasion_MonsterSpawn) self.monster_skill = inv_monsterskill; if((get_monsterinfo(self.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER) - Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_INVASION_SUPERMONSTER, M_NAME(self.monsterid)); + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_INVASION_SUPERMONSTER, self.monster_name); self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP;