]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/common/monsters/monster/mage.qc
Merge branch 'master' into Mario/monsters
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / monsters / monster / mage.qc
index 88120a0ea7550acbb93cd46e9b94022165b18ba2..57465865ccdca6b2edc4349081a4238550b6f3a0 100644 (file)
@@ -12,6 +12,7 @@ METHOD(MageSpike, wr_think, void(MageSpike thiswep, entity actor, .entity weapon
     if (!IS_PLAYER(actor) || weapon_prepareattack(thiswep, actor, weaponentity, false, 0.2)) {
         if (!actor.target_range) actor.target_range = autocvar_g_monsters_target_range;
         actor.enemy = Monster_FindTarget(actor);
+        monster_makevectors(actor, actor.enemy);
         W_SetupShot_Dir(actor, weaponentity, v_forward, false, 0, SND_MageSpike_FIRE, CH_WEAPON_B, 0, DEATH_MONSTER_MAGE.m_id);
        if (!IS_PLAYER(actor)) w_shotdir = normalize((actor.enemy.origin + '0 0 10') - actor.origin);
         M_Mage_Attack_Spike(actor, w_shotdir);
@@ -36,7 +37,8 @@ CLASS(OffhandMageTeleport, OffhandWeapon)
         player.OffhandMageTeleport_key_pressed = key_pressed;
     }
 ENDCLASS(OffhandMageTeleport)
-OffhandMageTeleport OFFHAND_MAGE_TELEPORT; STATIC_INIT(OFFHAND_MAGE_TELEPORT) { OFFHAND_MAGE_TELEPORT = NEW(OffhandMageTeleport); }
+OffhandMageTeleport OFFHAND_MAGE_TELEPORT;
+STATIC_INIT(OFFHAND_MAGE_TELEPORT) { OFFHAND_MAGE_TELEPORT = NEW(OffhandMageTeleport); }
 
 float autocvar_g_monster_mage_health;
 float autocvar_g_monster_mage_damageforcescale = 0.5;
@@ -45,16 +47,22 @@ float autocvar_g_monster_mage_attack_spike_radius;
 float autocvar_g_monster_mage_attack_spike_delay;
 float autocvar_g_monster_mage_attack_spike_accel;
 float autocvar_g_monster_mage_attack_spike_decel;
+float autocvar_g_monster_mage_attack_spike_chance = 0.45;
 float autocvar_g_monster_mage_attack_spike_turnrate;
 float autocvar_g_monster_mage_attack_spike_speed_max;
 float autocvar_g_monster_mage_attack_spike_smart;
 float autocvar_g_monster_mage_attack_spike_smart_trace_min;
 float autocvar_g_monster_mage_attack_spike_smart_trace_max;
 float autocvar_g_monster_mage_attack_spike_smart_mindist;
+float autocvar_g_monster_mage_attack_push_chance = 0.7;
 float autocvar_g_monster_mage_attack_push_damage;
 float autocvar_g_monster_mage_attack_push_radius;
 float autocvar_g_monster_mage_attack_push_delay;
 float autocvar_g_monster_mage_attack_push_force;
+float autocvar_g_monster_mage_attack_teleport_chance = 0.2;
+float autocvar_g_monster_mage_attack_teleport_delay = 2;
+float autocvar_g_monster_mage_attack_teleport_random = 0.4;
+float autocvar_g_monster_mage_attack_teleport_random_range = 1200;
 float autocvar_g_monster_mage_heal_self;
 float autocvar_g_monster_mage_heal_allies;
 float autocvar_g_monster_mage_heal_minhealth;
@@ -87,31 +95,31 @@ bool M_Mage_Defend_Heal_Check(entity this, entity targ)
 {
        if(targ == NULL)
                return false;
-       if(GetResourceAmount(targ, RESOURCE_HEALTH) <= 0)
+       if(GetResource(targ, RES_HEALTH) <= 0)
                return false;
        if(DIFF_TEAM(targ, this) && targ != this.monster_follow)
                return false;
        if(STAT(FROZEN, targ))
                return false;
        if(!IS_PLAYER(targ))
-               return (IS_MONSTER(targ) && GetResourceAmount(targ, RESOURCE_HEALTH) < targ.max_health);
+               return (IS_MONSTER(targ) && GetResource(targ, RES_HEALTH) < targ.max_health);
        if(targ.items & ITEM_Shield.m_itemid)
                return false;
 
        switch(this.skin)
        {
-               case 0: return (GetResourceAmount(targ, RESOURCE_HEALTH) < autocvar_g_balance_health_regenstable);
+               case 0: return (GetResource(targ, RES_HEALTH) < autocvar_g_balance_health_regenstable);
                case 1:
                {
-                       return ((GetResourceAmount(targ, RESOURCE_CELLS) && GetResourceAmount(targ, RESOURCE_CELLS) < g_pickup_cells_max)
-                               ||  (GetResourceAmount(targ, RESOURCE_PLASMA) && GetResourceAmount(targ, RESOURCE_PLASMA) < g_pickup_plasma_max)
-                               ||  (GetResourceAmount(targ, RESOURCE_ROCKETS) && GetResourceAmount(targ, RESOURCE_ROCKETS) < g_pickup_rockets_max)
-                               ||  (GetResourceAmount(targ, RESOURCE_BULLETS) && GetResourceAmount(targ, RESOURCE_BULLETS) < g_pickup_nails_max)
-                               ||  (GetResourceAmount(targ, RESOURCE_SHELLS) && GetResourceAmount(targ, RESOURCE_SHELLS) < g_pickup_shells_max)
+                       return ((GetResource(targ, RES_CELLS) && GetResource(targ, RES_CELLS) < g_pickup_cells_max)
+                               ||  (GetResource(targ, RES_PLASMA) && GetResource(targ, RES_PLASMA) < g_pickup_plasma_max)
+                               ||  (GetResource(targ, RES_ROCKETS) && GetResource(targ, RES_ROCKETS) < g_pickup_rockets_max)
+                               ||  (GetResource(targ, RES_BULLETS) && GetResource(targ, RES_BULLETS) < g_pickup_nails_max)
+                               ||  (GetResource(targ, RES_SHELLS) && GetResource(targ, RES_SHELLS) < g_pickup_shells_max)
                                        );
                }
-               case 2: return (GetResourceAmount(targ, RESOURCE_ARMOR) < autocvar_g_balance_armor_regenstable);
-               case 3: return (GetResourceAmount(targ, RESOURCE_HEALTH) > 0);
+               case 2: return (GetResource(targ, RES_ARMOR) < autocvar_g_balance_armor_regenstable);
+               case 3: return (GetResource(targ, RES_HEALTH) > 0);
        }
 
        return false;
@@ -144,7 +152,7 @@ void M_Mage_Attack_Spike_Touch(entity this, entity toucher)
 // copied from W_Seeker_Think
 void M_Mage_Attack_Spike_Think(entity this)
 {
-       if (time > this.ltime || (this.enemy && GetResourceAmount(this.enemy, RESOURCE_HEALTH) <= 0) || GetResourceAmount(this.owner, RESOURCE_HEALTH) <= 0) {
+       if (time > this.ltime || (this.enemy && GetResource(this.enemy, RES_HEALTH) <= 0) || GetResource(this.owner, RES_HEALTH) <= 0) {
                this.projectiledeathtype |= HITTYPE_SPLASH;
                M_Mage_Attack_Spike_Explode(this, NULL);
        }
@@ -223,7 +231,7 @@ void M_Mage_Attack_Spike(entity this, vector dir)
 
 void M_Mage_Defend_Heal(entity this)
 {
-       float washealed = false;
+       bool washealed = false;
 
        FOREACH_ENTITY_RADIUS(this.origin, autocvar_g_monster_mage_heal_range, M_Mage_Defend_Heal_Check(this, it),
        {
@@ -241,25 +249,25 @@ void M_Mage_Defend_Heal(entity this)
                                }
                                case 1:
                                {
-                                       if(GetResourceAmount(this, RESOURCE_CELLS)) GiveResourceWithLimit(it, RESOURCE_CELLS, 1, g_pickup_cells_max);
-                                       if(GetResourceAmount(this, RESOURCE_PLASMA)) GiveResourceWithLimit(it, RESOURCE_PLASMA, 1, g_pickup_plasma_max);
-                                       if(GetResourceAmount(this, RESOURCE_ROCKETS)) GiveResourceWithLimit(it, RESOURCE_ROCKETS, 1, g_pickup_rockets_max);
-                                       if(GetResourceAmount(this, RESOURCE_SHELLS)) GiveResourceWithLimit(it, RESOURCE_SHELLS, 2, g_pickup_shells_max);
-                                       if(GetResourceAmount(this, RESOURCE_BULLETS)) GiveResourceWithLimit(it, RESOURCE_BULLETS, 5, g_pickup_nails_max);
+                                       if(GetResource(it, RES_CELLS)) GiveResourceWithLimit(it, RES_CELLS, 1, g_pickup_cells_max);
+                                       if(GetResource(it, RES_PLASMA)) GiveResourceWithLimit(it, RES_PLASMA, 1, g_pickup_plasma_max);
+                                       if(GetResource(it, RES_ROCKETS)) GiveResourceWithLimit(it, RES_ROCKETS, 1, g_pickup_rockets_max);
+                                       if(GetResource(it, RES_SHELLS)) GiveResourceWithLimit(it, RES_SHELLS, 2, g_pickup_shells_max);
+                                       if(GetResource(it, RES_BULLETS)) GiveResourceWithLimit(it, RES_BULLETS, 5, g_pickup_nails_max);
                                        // TODO: fuel?
                                        fx = EFFECT_AMMO_REGEN;
                                        break;
                                }
                                case 2:
-                                       if(GetResourceAmount(it, RESOURCE_ARMOR) < autocvar_g_balance_armor_regenstable)
+                                       if(GetResource(it, RES_ARMOR) < autocvar_g_balance_armor_regenstable)
                                        {
-                                               GiveResourceWithLimit(it, RESOURCE_ARMOR, autocvar_g_monster_mage_heal_allies, autocvar_g_balance_armor_regenstable);
+                                               GiveResourceWithLimit(it, RES_ARMOR, autocvar_g_monster_mage_heal_allies, autocvar_g_balance_armor_regenstable);
                                                fx = EFFECT_ARMOR_REPAIR;
                                        }
                                        break;
                                case 3:
                                        float hp = ((it == this) ? autocvar_g_monster_mage_heal_self : autocvar_g_monster_mage_heal_allies);
-                                       TakeResource(it, RESOURCE_HEALTH, hp); // TODO: use regular damage functions? needs a way to bypass friendly fire checks
+                                       TakeResource(it, RES_HEALTH, hp); // TODO: use regular damage functions? needs a way to bypass friendly fire checks
                                        fx = EFFECT_RAGE;
                                        break;
                        }
@@ -269,16 +277,17 @@ void M_Mage_Defend_Heal(entity this)
                else
                {
                        Send_Effect(EFFECT_HEALING, it.origin, '0 0 0', 1);
-                       Heal(it, this, autocvar_g_monster_mage_heal_allies, RESOURCE_LIMIT_NONE);
+                       Heal(it, this, autocvar_g_monster_mage_heal_allies, RES_LIMIT_NONE);
                        if(!(it.spawnflags & MONSTERFLAG_INVINCIBLE) && it.sprite)
-                               WaypointSprite_UpdateHealth(it.sprite, GetResourceAmount(it, RESOURCE_HEALTH));
+                               WaypointSprite_UpdateHealth(it.sprite, GetResource(it, RES_HEALTH));
                }
        });
 
        if(washealed)
        {
-               setanim(this, this.anim_shoot, true, true, true);
+               setanim(this, this.anim_melee, true, true, true);
                this.attack_finished_single[0] = time + (autocvar_g_monster_mage_heal_delay);
+               this.state = MONSTER_ATTACK_MELEE;
                this.anim_finished = time + 1.5;
        }
 }
@@ -290,8 +299,10 @@ void M_Mage_Attack_Push(entity this)
                                                NULL, NULL, (autocvar_g_monster_mage_attack_push_force), DEATH_MONSTER_MAGE.m_id, DMG_NOWEP, this.enemy);
        Send_Effect(EFFECT_TE_EXPLOSION, this.origin, '0 0 0', 1);
 
-       setanim(this, this.anim_shoot, true, true, true);
+       setanim(this, this.anim_duckjump, true, true, true);
        this.attack_finished_single[0] = time + (autocvar_g_monster_mage_attack_push_delay);
+       this.anim_finished = time + 1;
+       this.state = MONSTER_ATTACK_MELEE; // prevent moving while firing spike
 }
 
 void M_Mage_Attack_Teleport(entity this, entity targ)
@@ -299,13 +310,33 @@ void M_Mage_Attack_Teleport(entity this, entity targ)
        if(!targ) return;
        if(vdist(targ.origin - this.origin, >, 1500)) return;
 
+       if(autocvar_g_monster_mage_attack_teleport_random && random() <= autocvar_g_monster_mage_attack_teleport_random)
+       {
+               vector oldpos = this.origin;
+               vector extrasize = '1 1 1' * autocvar_g_monster_mage_attack_teleport_random_range;
+               if(MoveToRandomLocationWithinBounds(this, this.absmin - extrasize, this.absmax + extrasize,
+                                                                                       DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, 
+                                                                                       Q3SURFACEFLAG_SKY, 10, 64, 256, true))
+               {
+                       vector a = vectoangles(targ.origin - this.origin);
+                       this.angles = '0 1 0' * a.y;
+                       this.fixangle = true;
+                       Send_Effect(EFFECT_SPAWN_NEUTRAL, oldpos, '0 0 0', 1);
+                       Send_Effect(EFFECT_SPAWN_NEUTRAL, this.origin, '0 0 0', 1);
+                       this.attack_finished_single[0] = time + autocvar_g_monster_mage_attack_teleport_delay;
+                       return;
+               }
+       }
+
+       if(!IS_ONGROUND(targ)) return;
+
        makevectors(targ.angles);
-       tracebox(targ.origin + ((v_forward * -1) * 200), this.mins, this.maxs, this.origin, MOVE_NOMONSTERS, this);
+       tracebox(CENTER_OR_VIEWOFS(targ), this.mins, this.maxs, CENTER_OR_VIEWOFS(targ) + ((v_forward * -1) * 200), MOVE_NOMONSTERS, this);
 
        if(trace_fraction < 1)
                return;
 
-       vector newpos = targ.origin + ((v_forward * -1) * 200);
+       vector newpos = trace_endpos;
 
        Send_Effect(EFFECT_SPAWN_NEUTRAL, this.origin, '0 0 0', 1);
        Send_Effect(EFFECT_SPAWN_NEUTRAL, newpos, '0 0 0', 1);
@@ -319,23 +350,23 @@ void M_Mage_Attack_Teleport(entity this, entity targ)
        this.fixangle = true;
        this.velocity *= 0.5;
 
-       this.attack_finished_single[0] = time + 0.2;
+       this.attack_finished_single[0] = time + autocvar_g_monster_mage_attack_teleport_delay;
 }
 
 void M_Mage_Defend_Shield_Remove(entity this)
 {
        this.effects &= ~(EF_ADDITIVE | EF_BLUE);
-       SetResourceAmountExplicit(this, RESOURCE_ARMOR, autocvar_g_monsters_armor_blockpercent);
+       SetResourceExplicit(this, RES_ARMOR, autocvar_g_monsters_armor_blockpercent);
 }
 
 void M_Mage_Defend_Shield(entity this)
 {
        this.effects |= (EF_ADDITIVE | EF_BLUE);
        this.mage_shield_delay = time + (autocvar_g_monster_mage_shield_delay);
-       SetResourceAmountExplicit(this, RESOURCE_ARMOR, autocvar_g_monster_mage_shield_blockpercent);
+       SetResourceExplicit(this, RES_ARMOR, autocvar_g_monster_mage_shield_blockpercent);
        this.mage_shield_time = time + (autocvar_g_monster_mage_shield_time);
        setanim(this, this.anim_shoot, true, true, true);
-       this.attack_finished_single[0] = time + 1;
+       this.attack_finished_single[0] = time + 1; // give just a short cooldown on attacking
        this.anim_finished = time + 1;
 }
 
@@ -345,7 +376,7 @@ bool M_Mage_Attack(int attack_type, entity actor, entity targ, .entity weaponent
        {
                case MONSTER_ATTACK_MELEE:
                {
-                       if(random() <= 0.7)
+                       if(random() <= autocvar_g_monster_mage_attack_push_chance)
                        {
                                Weapon wep = WEP_MAGE_SPIKE;
 
@@ -357,29 +388,25 @@ bool M_Mage_Attack(int attack_type, entity actor, entity targ, .entity weaponent
                }
                case MONSTER_ATTACK_RANGED:
                {
-                       if(!actor.mage_spike)
+                       if(random() <= autocvar_g_monster_mage_attack_teleport_chance)
                        {
-                               if(random() <= 0.4)
-                               {
-                                       OffhandWeapon off = OFFHAND_MAGE_TELEPORT;
-                                       off.offhand_think(off, actor, true);
-                                       return true;
-                               }
-                               else
-                               {
-                                       setanim(actor, actor.anim_shoot, true, true, true);
-                                       actor.attack_finished_single[0] = time + (autocvar_g_monster_mage_attack_spike_delay);
-                                       actor.anim_finished = time + 1;
-                                       Weapon wep = WEP_MAGE_SPIKE;
-                                       wep.wr_think(wep, actor, weaponentity, 1);
-                                       return true;
-                               }
+                               OffhandWeapon off = OFFHAND_MAGE_TELEPORT;
+                               actor.OffhandMageTeleport_key_pressed = 0;
+                               off.offhand_think(off, actor, 1);
+                               return true;
                        }
-
-                       if(actor.mage_spike)
+                       else if(!actor.mage_spike && random() <= autocvar_g_monster_mage_attack_spike_chance)
+                       {
+                               setanim(actor, actor.anim_shoot, true, true, true);
+                               actor.attack_finished_single[0] = time + (autocvar_g_monster_mage_attack_spike_delay);
+                               actor.anim_finished = time + 1;
+                               actor.state = MONSTER_ATTACK_MELEE; // prevent moving while firing spike
+                               Weapon wep = WEP_MAGE_SPIKE;
+                               wep.wr_think(wep, actor, weaponentity, 1);
                                return true;
-                       else
-                               return false;
+                       }
+
+                       return false;
                }
        }
 
@@ -419,16 +446,16 @@ METHOD(Mage, mr_think, bool(Mage thismon, entity actor))
        });
     }
 
-    if(GetResourceAmount(actor, RESOURCE_HEALTH) < (autocvar_g_monster_mage_heal_minhealth) || need_help)
+    if(GetResource(actor, RES_HEALTH) < (autocvar_g_monster_mage_heal_minhealth) || need_help)
     if(time >= actor.attack_finished_single[0])
     if(random() < 0.5)
         M_Mage_Defend_Heal(actor);
 
-    if(time >= actor.mage_shield_time && GetResourceAmount(actor, RESOURCE_ARMOR))
+    if(time >= actor.mage_shield_time && GetResource(actor, RES_ARMOR))
         M_Mage_Defend_Shield_Remove(actor);
 
     if(actor.enemy)
-    if(GetResourceAmount(actor, RESOURCE_HEALTH) < actor.max_health)
+    if(GetResource(actor, RES_HEALTH) < actor.max_health)
     if(time >= actor.mage_shield_delay)
     if(random() < 0.5)
         M_Mage_Defend_Shield(actor);
@@ -445,7 +472,7 @@ METHOD(Mage, mr_pain, float(Mage this, entity actor, float damage_take, entity a
 METHOD(Mage, mr_death, bool(Mage this, entity actor))
 {
     TC(Mage, this);
-    setanim(actor, actor.anim_die1, false, true, true);
+    setanim(actor, ((random() > 0.5) ? actor.anim_die2 : actor.anim_die1), false, true, true);
     return true;
 }
 
@@ -455,12 +482,22 @@ METHOD(Mage, mr_anim, bool(Mage this, entity actor))
 {
     TC(Mage, this);
     vector none = '0 0 0';
-    actor.anim_die1 = animfixfps(actor, '4 1 0.5', none); // 2 seconds
-    actor.anim_walk = animfixfps(actor, '1 1 1', none);
     actor.anim_idle = animfixfps(actor, '0 1 1', none);
-    actor.anim_pain1 = animfixfps(actor, '3 1 2', none); // 0.5 seconds
+    actor.anim_walk = animfixfps(actor, '1 1 1', none);
+    actor.anim_run = animfixfps(actor, '1 1 1', none);
     actor.anim_shoot = animfixfps(actor, '2 1 5', none); // analyze models and set framerate
-    actor.anim_run = animfixfps(actor, '5 1 1', none);
+    actor.anim_duckjump = animfixfps(actor, '4 1 5', none); // analyze models and set framerate
+    actor.anim_melee = animfixfps(actor, '5 1 5', none); // analyze models and set framerate
+    //actor.anim_fire1 = animfixfps(actor, '3 1 5', none); // analyze models and set framerate
+    //actor.anim_fire2 = animfixfps(actor, '4 1 5', none); // analyze models and set framerate
+    //actor.anim_fire3 = animfixfps(actor, '5 1 5', none); // analyze models and set framerate
+    actor.anim_pain1 = animfixfps(actor, '6 1 2', none); // 0.5 seconds
+    actor.anim_pain2 = animfixfps(actor, '7 1 2', none); // 0.5 seconds
+    //actor.anim_pain3 = animfixfps(actor, '8 1 2', none); // 0.5 seconds
+    actor.anim_die1 = animfixfps(actor, '9 1 0.5', none); // 2 seconds
+    actor.anim_die2 = animfixfps(actor, '10 1 0.5', none); // 2 seconds
+    //actor.anim_dead1 = animfixfps(actor, '11 1 0.5', none); // 2 seconds
+    //actor.anim_dead2 = animfixfps(actor, '12 1 0.5', none); // 2 seconds
     return true;
 }
 #endif
@@ -469,7 +506,7 @@ METHOD(Mage, mr_anim, bool(Mage this, entity actor))
 METHOD(Mage, mr_setup, bool(Mage this, entity actor))
 {
     TC(Mage, this);
-    if(!GetResourceAmount(this, RESOURCE_HEALTH)) SetResourceAmountExplicit(actor, RESOURCE_HEALTH, autocvar_g_monster_mage_health);
+    if(!GetResource(this, RES_HEALTH)) SetResourceExplicit(actor, RES_HEALTH, autocvar_g_monster_mage_health);
     if(!actor.speed) { actor.speed = (autocvar_g_monster_mage_speed_walk); }
     if(!actor.speed2) { actor.speed2 = (autocvar_g_monster_mage_speed_run); }
     if(!actor.stopspeed) { actor.stopspeed = (autocvar_g_monster_mage_speed_stop); }