]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/common/monsters/sv_monsters.qc
Merge branch 'master' into Mario/monsters
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / monsters / sv_monsters.qc
index e762603d5bb6a9dd7d7fcf25a64399e323314270..319b699a706372cc3aca360df4259d50dfac907c 100644 (file)
@@ -97,7 +97,7 @@ bool Monster_ValidTarget(entity this, entity targ, bool skipfacing)
        || (game_stopped)
        || (targ.items & IT_INVISIBILITY)
        || (IS_SPEC(targ) || IS_OBSERVER(targ)) // don't attack spectators
-       || (!IS_VEHICLE(targ) && (IS_DEAD(targ) || IS_DEAD(this) || targ.health <= 0 || this.health <= 0))
+       || (!IS_VEHICLE(targ) && (IS_DEAD(targ) || IS_DEAD(this) || GetResource(targ, RES_HEALTH) <= 0 || GetResource(this, RES_HEALTH) <= 0))
        || (this.monster_follow == targ || targ.monster_follow == this)
        || (!IS_VEHICLE(targ) && (targ.flags & FL_NOTARGET))
        || (!autocvar_g_monsters_typefrag && PHYS_INPUT_BUTTON_CHAT(targ))
@@ -172,15 +172,15 @@ void monster_setupcolors(entity this)
        else
        {
                if(this.monster_skill <= MONSTER_SKILL_EASY)
-                       this.colormap = 1029;
+                       this.colormap = 1126;
                else if(this.monster_skill <= MONSTER_SKILL_MEDIUM)
-                       this.colormap = 1027;
+                       this.colormap = 1075;
                else if(this.monster_skill <= MONSTER_SKILL_HARD)
-                       this.colormap = 1038;
+                       this.colormap = 1228;
                else if(this.monster_skill <= MONSTER_SKILL_INSANE)
-                       this.colormap = 1028;
+                       this.colormap = 1092;
                else if(this.monster_skill <= MONSTER_SKILL_NIGHTMARE)
-                       this.colormap = 1032;
+                       this.colormap = 1160;
                else
                        this.colormap = 1024;
        }
@@ -214,7 +214,11 @@ void monster_changeteam(entity this, int newteam)
 void Monster_Delay_Action(entity this)
 {
        // TODO: maybe do check for facing here
-       if(Monster_ValidTarget(this.owner, this.owner.enemy, false)) { this.monster_delayedfunc(this.owner); }
+       if(Monster_ValidTarget(this.owner, this.owner.enemy, false))
+       {
+               monster_makevectors(this.owner, this.owner.enemy);
+               this.monster_delayedfunc(this.owner);
+       }
 
        if(this.cnt > 1)
        {
@@ -362,7 +366,6 @@ void Monster_Sound(entity this, .string samplefield, float sound_delay, bool del
        string sample = this.(samplefield);
        if (sample != "") sample = GlobalSound_sample(sample, random());
        float myscale = ((this.scale) ? this.scale : 1); // safety net
-       // TODO: change volume depending on size too?
        sound7(this, chan, sample, VOL_BASE, ATTEN_NORM, 100 / myscale, 0);
 
        this.msound_delay = time + sound_delay;
@@ -384,8 +387,6 @@ bool Monster_Attack_Melee(entity this, entity targ, float damg, vector anim, flo
        else
                this.attack_finished_single[0] = this.anim_finished = time + animtime;
 
-       monster_makevectors(this, targ);
-
        traceline(this.origin + this.view_ofs, this.origin + v_forward * er, 0, this);
 
        if(trace_ent.takedamage)
@@ -400,7 +401,7 @@ bool Monster_Attack_Leap_Check(entity this, vector vel)
                return false; // already attacking
        if(!IS_ONGROUND(this))
                return false; // not on the ground
-       if(this.health <= 0 || IS_DEAD(this))
+       if(GetResource(this, RES_HEALTH) <= 0 || IS_DEAD(this))
                return false; // called when dead?
        if(time < this.attack_finished_single[0])
                return false; // still attacking
@@ -450,6 +451,7 @@ void Monster_Attack_Check(entity this, entity targ, .entity weaponentity)
 
        if(vdist(targ.origin - this.origin, <=, this.attack_range))
        {
+               monster_makevectors(this, targ);
                int attack_success = this.monster_attackfunc(MONSTER_ATTACK_MELEE, this, targ, weaponentity);
                if(attack_success == 1)
                        Monster_Sound(this, monstersound_melee, 0, false, CH_VOICE);
@@ -459,6 +461,7 @@ void Monster_Attack_Check(entity this, entity targ, .entity weaponentity)
 
        if(vdist(targ.origin - this.origin, >, this.attack_range))
        {
+               monster_makevectors(this, targ);
                int attack_success = this.monster_attackfunc(MONSTER_ATTACK_RANGED, this, targ, weaponentity);
                if(attack_success == 1)
                        Monster_Sound(this, monstersound_melee, 0, false, CH_VOICE);
@@ -493,7 +496,7 @@ void Monster_UpdateModel(entity this)
 
 void Monster_Touch(entity this, entity toucher)
 {
-       if(toucher == NULL) { return; }
+       if(!toucher) { return; }
 
        if(toucher.monster_attack && this.enemy != toucher && !IS_MONSTER(toucher) && time >= this.spawn_time)
        if(Monster_ValidTarget(this, toucher, true))
@@ -510,7 +513,7 @@ void Monster_Miniboss_Check(entity this)
        // g_monsters_miniboss_chance cvar or spawnflags 64 causes a monster to be a miniboss
        if ((this.spawnflags & MONSTERFLAG_MINIBOSS) || (chance < autocvar_g_monsters_miniboss_chance))
        {
-               this.health += autocvar_g_monsters_miniboss_healthboost;
+               GiveResource(this, RES_HEALTH, autocvar_g_monsters_miniboss_healthboost);
                this.effects |= EF_RED;
                if(!this.weapon)
                        this.weapon = WEP_VORTEX.m_id;
@@ -551,10 +554,11 @@ void Monster_Dead_Fade(entity this)
                        this.pos2 = this.angles;
                }
                this.event_damage = func_null;
+               this.event_heal = func_null;
                this.takedamage = DAMAGE_NO;
                setorigin(this, this.pos1);
                this.angles = this.pos2;
-               this.health = this.max_health;
+               SetResourceExplicit(this, RES_HEALTH, this.max_health);
                setmodel(this, MDL_Null);
        }
        else
@@ -571,7 +575,6 @@ void Monster_Use(entity this, entity actor, entity trigger)
        if(Monster_ValidTarget(this, actor, true)) { this.enemy = actor; }
 }
 
-.float pass_distance;
 vector Monster_Move_Target(entity this, entity targ)
 {
        // enemy is always preferred target
@@ -582,17 +585,16 @@ vector Monster_Move_Target(entity this, entity targ)
                WarpZone_TraceLine(this.origin, targ_origin, MOVE_NOMONSTERS, this);
 
                // cases where the enemy may have changed their state (don't need to check everything here)
-               if((!this.enemy)
-                       || (IS_DEAD(this.enemy) || this.enemy.health < 1)
+               if(    (IS_DEAD(this.enemy) || GetResource(this.enemy, RES_HEALTH) < 1)
                        || (STAT(FROZEN, this.enemy))
                        || (this.enemy.flags & FL_NOTARGET)
                        || (this.enemy.alpha < 0.5 && this.enemy.alpha != 0)
                        || (this.enemy.takedamage == DAMAGE_NO)
                        || (vdist(this.origin - targ_origin, >, this.target_range))
-                       || ((trace_fraction < 1) && (trace_ent != this.enemy)))
+                       || ((trace_fraction < 1) && (trace_ent != this.enemy))
+                       )
                {
                        this.enemy = NULL;
-                       //this.pass_distance = 0;
                }
 
                if(this.enemy)
@@ -682,7 +684,6 @@ vector Monster_Move_Target(entity this, entity targ)
 }
 
 .entity draggedby;
-.entity target2;
 
 void Monster_Move(entity this, float runspeed, float walkspeed, float stpspeed)
 {
@@ -786,17 +787,19 @@ void Monster_Move(entity this, float runspeed, float walkspeed, float stpspeed)
        fixedmakevectors(this.angles);
        float vz = this.velocity_z;
 
-       if(!turret_closetotarget(this, this.moveto))
+       if(!turret_closetotarget(this, this.moveto, 16))
        {
                bool do_run = (this.enemy || this.monster_moveto);
                movelib_move_simple(this, v_forward, ((do_run) ? runspeed : walkspeed), 0.4);
 
-               if(time > this.pain_finished && time > this.anim_finished) // TODO: use anim_finished instead!?
+               if(time > this.pain_finished && time > this.anim_finished)
                if(!this.state)
-               if(vdist(this.velocity, >, 10))
-                       setanim(this, ((do_run) ? this.anim_run : this.anim_walk), true, false, false);
-               else
-                       setanim(this, this.anim_idle, true, false, false);
+               {
+                       if(vdist(this.velocity, >, 10))
+                               setanim(this, ((do_run) ? this.anim_run : this.anim_walk), true, false, false);
+                       else
+                               setanim(this, this.anim_idle, true, false, false);
+               }
        }
        else
        {
@@ -890,9 +893,9 @@ void Monster_Reset(entity this)
        setorigin(this, this.pos1);
        this.angles = this.pos2;
 
-       Unfreeze(this); // remove any icy remains
+       Unfreeze(this, false); // remove any icy remains
 
-       this.health = this.max_health;
+       SetResourceExplicit(this, RES_HEALTH, this.max_health);
        this.velocity = '0 0 0';
        this.enemy = NULL;
        this.goalentity = NULL;
@@ -902,11 +905,11 @@ void Monster_Reset(entity this)
 
 void Monster_Dead_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
 {
-       this.health -= damage;
+       TakeResource(this, RES_HEALTH, damage);
 
        Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, this, attacker);
 
-       if(this.health <= -50) // 100 health until gone?
+       if(GetResource(this, RES_HEALTH) <= -50) // 100 health until gone?
        {
                Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, this, attacker);
 
@@ -926,10 +929,7 @@ void Monster_Dead(entity this, entity attacker, float gibbed)
        this.monster_lifetime = time + 5;
 
        if(STAT(FROZEN, this))
-       {
-               Unfreeze(this); // remove any icy remains
-               this.health = 0; // reset by Unfreeze
-       }
+               Unfreeze(this, false); // remove any icy remains
 
        monster_dropitem(this, attacker);
 
@@ -952,6 +952,7 @@ void Monster_Dead(entity this, entity attacker, float gibbed)
                _setmodel(this, this.mdl_dead);
 
        this.event_damage       = ((gibbed) ? func_null : Monster_Dead_Damage);
+       this.event_heal         = func_null;
        this.solid                      = SOLID_CORPSE;
        this.takedamage         = DAMAGE_AIM;
        this.deadflag           = DEAD_DEAD;
@@ -997,7 +998,7 @@ void Monster_Damage(entity this, entity inflictor, entity attacker, float damage
        if(deathtype == DEATH_FALL.m_id && this.draggedby != NULL)
                return;
 
-       vector v = healtharmor_applydamage(100, this.armorvalue / 100, deathtype, damage);
+       vector v = healtharmor_applydamage(100, GetResource(this, RES_ARMOR) / 100, deathtype, damage);
        float take = v.x;
        //float save = v.y;
 
@@ -1006,12 +1007,12 @@ void Monster_Damage(entity this, entity inflictor, entity attacker, float damage
 
        if(take)
        {
-               this.health -= take;
+               TakeResource(this, RES_HEALTH, take);
                Monster_Sound(this, monstersound_pain, 1.2, true, CH_PAIN);
        }
 
        if(this.sprite)
-               WaypointSprite_UpdateHealth(this.sprite, this.health);
+               WaypointSprite_UpdateHealth(this.sprite, GetResource(this, RES_HEALTH));
 
        this.dmg_time = time;
 
@@ -1029,7 +1030,7 @@ void Monster_Damage(entity this, entity inflictor, entity attacker, float damage
                        Violence_GibSplash_At(hitloc, force * -0.2, 3, 1, this, attacker);
        }
 
-       if(this.health <= 0)
+       if(GetResource(this, RES_HEALTH) <= 0)
        {
                if(deathtype == DEATH_KILL.m_id)
                        this.candrop = false; // killed by mobkill command
@@ -1038,13 +1039,13 @@ void Monster_Damage(entity this, entity inflictor, entity attacker, float damage
                SUB_UseTargets(this, attacker, this.enemy);
                this.target2 = this.oldtarget2; // reset to original target on death, incase we respawn
 
-               Monster_Dead(this, attacker, (this.health <= -100 || deathtype == DEATH_KILL.m_id));
+               Monster_Dead(this, attacker, (GetResource(this, RES_HEALTH) <= -100 || deathtype == DEATH_KILL.m_id));
 
                WaypointSprite_Kill(this.sprite);
 
                MUTATOR_CALLHOOK(MonsterDies, this, attacker, deathtype);
 
-               if(this.health <= -100 || deathtype == DEATH_KILL.m_id) // check if we're already gibbed
+               if(GetResource(this, RES_HEALTH) <= -100 || deathtype == DEATH_KILL.m_id) // check if we're already gibbed
                {
                        Violence_GibSplash(this, 1, 0.5, attacker);
 
@@ -1054,6 +1055,18 @@ void Monster_Damage(entity this, entity inflictor, entity attacker, float damage
        }
 }
 
+bool Monster_Heal(entity targ, entity inflictor, float amount, float limit)
+{
+       float true_limit = ((limit != RES_LIMIT_NONE) ? limit : targ.max_health);
+       if(GetResource(targ, RES_HEALTH) <= 0 || GetResource(targ, RES_HEALTH) >= true_limit)
+               return false;
+
+       GiveResourceWithLimit(targ, RES_HEALTH, amount, true_limit);
+       if(targ.sprite)
+               WaypointSprite_UpdateHealth(targ.sprite, GetResource(targ, RES_HEALTH));
+       return true;
+}
+
 // don't check for enemies, just keep walking in a straight line
 void Monster_Move_2D(entity this, float mspeed, bool allow_jumpoff)
 {
@@ -1080,15 +1093,12 @@ void Monster_Move_2D(entity this, float mspeed, bool allow_jumpoff)
        if(trace_ent && IS_MONSTER(trace_ent))
                reverse = true;
 
-       // TODO: fix this... tracing is broken if the floor is thin
-       /*
-       if(!allow_jumpoff)
+       if(!allow_jumpoff && IS_ONGROUND(this))
        {
-               a = b - '0 0 32';
-               traceline(b, a, MOVE_WORLDONLY, this);
+               traceline(b, b - '0 0 32', MOVE_NORMAL, this);
                if(trace_fraction == 1.0)
                        reverse = true;
-       } */
+       }
 
        if(reverse)
        {
@@ -1099,10 +1109,12 @@ void Monster_Move_2D(entity this, float mspeed, bool allow_jumpoff)
        movelib_move_simple_gravity(this, v_forward, mspeed, 1);
 
        if(time > this.pain_finished && time > this.attack_finished_single[0])
-       if(vdist(this.velocity, >, 10))
-               setanim(this, this.anim_walk, true, false, false);
-       else
-               setanim(this, this.anim_idle, true, false, false);
+       {
+               if(vdist(this.velocity, >, 10))
+                       setanim(this, this.anim_walk, true, false, false);
+               else
+                       setanim(this, this.anim_idle, true, false, false);
+       }
 }
 
 void Monster_Anim(entity this)
@@ -1144,36 +1156,34 @@ void Monster_Anim(entity this)
 
 void Monster_Frozen_Think(entity this)
 {
-       if(STAT(FROZEN, this) == 2)
+       if (STAT(FROZEN, this) == FROZEN_TEMP_REVIVING)
        {
                STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) + this.ticrate * this.revive_speed, 1);
-               this.health = max(1, STAT(REVIVE_PROGRESS, this) * this.max_health);
+               SetResourceExplicit(this, RES_HEALTH, max(1, STAT(REVIVE_PROGRESS, this) * this.max_health));
                this.iceblock.alpha = bound(0.2, 1 - STAT(REVIVE_PROGRESS, this), 1);
 
                if(!(this.spawnflags & MONSTERFLAG_INVINCIBLE) && this.sprite)
-                       WaypointSprite_UpdateHealth(this.sprite, this.health);
+                       WaypointSprite_UpdateHealth(this.sprite, GetResource(this, RES_HEALTH));
 
                if(STAT(REVIVE_PROGRESS, this) >= 1)
-                       Unfreeze(this);
+                       Unfreeze(this, false);
        }
-       else if(STAT(FROZEN, this) == 3)
+       else if (STAT(FROZEN, this) == FROZEN_TEMP_DYING)
        {
                STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) - this.ticrate * this.revive_speed, 1);
-               this.health = max(0, autocvar_g_nades_ice_health + (this.max_health-autocvar_g_nades_ice_health) * STAT(REVIVE_PROGRESS, this) );
+               SetResourceExplicit(this, RES_HEALTH, max(0, autocvar_g_nades_ice_health + (this.max_health-autocvar_g_nades_ice_health) * STAT(REVIVE_PROGRESS, this)));
 
                if(!(this.spawnflags & MONSTERFLAG_INVINCIBLE) && this.sprite)
-                       WaypointSprite_UpdateHealth(this.sprite, this.health);
+                       WaypointSprite_UpdateHealth(this.sprite, GetResource(this, RES_HEALTH));
 
-               if(this.health < 1)
+               if(GetResource(this, RES_HEALTH) < 1)
                {
-                       Unfreeze(this);
-                       this.health = 0;
+                       Unfreeze(this, false);
                        if(this.event_damage)
                                this.event_damage(this, this, this.frozen_by, 1, DEATH_NADE_ICE_FREEZE.m_id, DMG_NOWEP, this.origin, '0 0 0');
                }
-
                else if ( STAT(REVIVE_PROGRESS, this) <= 0 )
-                       Unfreeze(this);
+                       Unfreeze(this, false);
        }
        // otherwise, no revival!
 
@@ -1182,21 +1192,20 @@ void Monster_Frozen_Think(entity this)
 
 void Monster_Enemy_Check(entity this)
 {
-       if(!this.enemy)
+       if(this.enemy)
+               return;
+
+       this.enemy = Monster_FindTarget(this);
+       if(this.enemy)
        {
-               this.enemy = Monster_FindTarget(this);
-               if(this.enemy)
-               {
-                       WarpZone_RefSys_Copy(this.enemy, this);
-                       WarpZone_RefSys_AddInverse(this.enemy, this); // wz1^-1 ... wzn^-1 receiver
-                       // update move target immediately?
-                       this.moveto = WarpZone_RefSys_TransformOrigin(this.enemy, this, (0.5 * (this.enemy.absmin + this.enemy.absmax)));
-                       this.monster_moveto = '0 0 0';
-                       this.monster_face = '0 0 0';
-
-                       //this.pass_distance = vlen((('1 0 0' * this.enemy.origin_x) + ('0 1 0' * this.enemy.origin_y)) - (('1 0 0' *  this.origin_x) + ('0 1 0' *  this.origin_y)));
-                       Monster_Sound(this, monstersound_sight, 0, false, CH_VOICE);
-               }
+               WarpZone_RefSys_Copy(this.enemy, this);
+               WarpZone_RefSys_AddInverse(this.enemy, this); // wz1^-1 ... wzn^-1 receiver
+               // update move target immediately?
+               this.moveto = WarpZone_RefSys_TransformOrigin(this.enemy, this, (0.5 * (this.enemy.absmin + this.enemy.absmax)));
+               this.monster_moveto = '0 0 0';
+               this.monster_face = '0 0 0';
+
+               Monster_Sound(this, monstersound_sight, 0, false, CH_VOICE);
        }
 }
 
@@ -1207,7 +1216,7 @@ void Monster_Think(entity this)
 
        if(this.monster_lifetime && time >= this.monster_lifetime)
        {
-               Damage(this, this, this, this.health + this.max_health, DEATH_KILL.m_id, DMG_NOWEP, this.origin, this.origin);
+               Damage(this, this, this, GetResource(this, RES_HEALTH) + this.max_health, DEATH_KILL.m_id, DMG_NOWEP, this.origin, this.origin);
                return;
        }
 
@@ -1239,8 +1248,8 @@ bool Monster_Spawn_Setup(entity this)
        mon.mr_setup(mon, this);
 
        // ensure some basic needs are met
-       if(!this.health) { this.health = 100; }
-       if(!this.armorvalue) { this.armorvalue = bound(0.2, 0.5 * MONSTER_SKILLMOD(this), 0.9); }
+       if(!GetResource(this, RES_HEALTH)) { SetResourceExplicit(this, RES_HEALTH, 100); }
+       if(!GetResource(this, RES_ARMOR)) { SetResourceExplicit(this, RES_ARMOR, bound(0.2, 0.5 * MONSTER_SKILLMOD(this), 0.9)); }
        if(!this.target_range) { this.target_range = autocvar_g_monsters_target_range; }
        if(!this.respawntime) { this.respawntime = autocvar_g_monsters_respawn_delay; }
        if(!this.monster_moveflags) { this.monster_moveflags = MONSTER_MOVE_WANDER; }
@@ -1250,13 +1259,13 @@ bool Monster_Spawn_Setup(entity this)
        if(!(this.spawnflags & MONSTERFLAG_RESPAWNED))
        {
                Monster_Miniboss_Check(this);
-               this.health *= MONSTER_SKILLMOD(this);
+               SetResourceExplicit(this, RES_HEALTH, GetResource(this, RES_HEALTH) * MONSTER_SKILLMOD(this));
 
                if(!this.skin)
                        this.skin = rint(random() * 4);
        }
 
-       this.max_health = this.health;
+       this.max_health = GetResource(this, RES_HEALTH);
        this.pain_finished = this.nextthink;
        this.last_enemycheck = this.spawn_time + random(); // slight delay
 
@@ -1286,7 +1295,7 @@ bool Monster_Spawn_Setup(entity this)
                if(!(this.spawnflags & MONSTERFLAG_INVINCIBLE))
                {
                        WaypointSprite_UpdateMaxHealth(this.sprite, this.max_health);
-                       WaypointSprite_UpdateHealth(this.sprite, this.health);
+                       WaypointSprite_UpdateHealth(this.sprite, GetResource(this, RES_HEALTH));
                }
        }
 
@@ -1307,7 +1316,7 @@ bool Monster_Spawn(entity this, bool check_appear, int mon_id)
 
        if(!autocvar_g_monsters) { Monster_Remove(this); return false; }
 
-       if(!(this.spawnflags & MONSTERFLAG_RESPAWNED))
+       if(!(this.spawnflags & MONSTERFLAG_RESPAWNED) && !(this.flags & FL_MONSTER))
        {
                IL_PUSH(g_monsters, this);
                if(this.mdl && this.mdl != "")
@@ -1338,6 +1347,9 @@ bool Monster_Spawn(entity this, bool check_appear, int mon_id)
        else
                setmodel(this, mon.m_model);
 
+       if(!this.monster_name || this.monster_name == "")
+               this.monster_name = mon.monster_name;
+
        this.flags                              = FL_MONSTER;
        this.classname                  = "monster";
        this.takedamage                 = DAMAGE_AIM;
@@ -1351,6 +1363,7 @@ bool Monster_Spawn(entity this, bool check_appear, int mon_id)
        this.damagedbycontents  = true;
        this.monsterid                  = mon_id;
        this.event_damage               = Monster_Damage;
+       this.event_heal                 = Monster_Heal;
        settouch(this, Monster_Touch);
        this.use                                = Monster_Use;
        this.solid                              = SOLID_BBOX;
@@ -1364,10 +1377,8 @@ bool Monster_Spawn(entity this, bool check_appear, int mon_id)
        this.reset                              = Monster_Reset;
        this.netname                    = mon.netname;
        this.monster_attackfunc = mon.monster_attackfunc;
-       this.monster_name               = mon.monster_name;
        this.candrop                    = true;
        this.oldtarget2                 = this.target2;
-       //this.pass_distance            = 0;
        this.deadflag                   = DEAD_NO;
        this.spawn_time                 = time;
        this.gravity                    = 1;