]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/g_damage.qc
Merge branch 'master' into Mario/stats_eloranking
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / g_damage.qc
index dcbc20342df2294a3e60be5426316d3362be9d0f..e14ecd6ce7808ebeeca7dd436dc1aa4d41db2127 100644 (file)
@@ -3,7 +3,8 @@
 #include <common/effects/all.qh>
 #include "bot/api.qh"
 #include "g_hook.qh"
-#include "mutators/_mod.qh"
+#include <server/mutators/_mod.qh>
+#include "teamplay.qh"
 #include "scores.qh"
 #include "spawnpoints.qh"
 #include "../common/state.qh"
@@ -14,6 +15,7 @@
 #include "../common/items/_mod.qh"
 #include "../common/mutators/mutator/waypoints/waypointsprites.qh"
 #include "../common/mutators/mutator/instagib/sv_instagib.qh"
+#include "../common/mutators/mutator/buffs/buffs.qh"
 #include "weapons/accuracy.qh"
 #include "weapons/csqcprojectile.qh"
 #include "weapons/selection.qh"
@@ -24,6 +26,7 @@
 #include "../common/playerstats.qh"
 #include "../common/teams.qh"
 #include "../common/util.qh"
+#include <common/gamemodes/rules.qh>
 #include <common/weapons/_all.qh>
 #include "../lib/csqcmodel/sv_model.qh"
 #include "../lib/warpzone/common.qh"
@@ -33,7 +36,7 @@ void UpdateFrags(entity player, int f)
        GameRules_scoring_add_team(player, SCORE, f);
 }
 
-void GiveFrags (entity attacker, entity targ, float f, int deathtype, .entity weaponentity)
+void GiveFrags(entity attacker, entity targ, float f, int deathtype, .entity weaponentity)
 {
        // TODO route through PlayerScores instead
        if(game_stopped) return;
@@ -61,51 +64,8 @@ void GiveFrags (entity attacker, entity targ, float f, int deathtype, .entity we
 
        GameRules_scoring_add(targ, DEATHS, 1);
 
-       if(targ != attacker) // not for suicides
-       if(g_weaponarena_random)
-       {
-               // after a frag, exchange the current weapon (or the culprit, if detectable) by a new random weapon
-               Weapon culprit = DEATH_WEAPONOF(deathtype);
-               if(!culprit) culprit = attacker.(weaponentity).m_weapon;
-               else if(!(STAT(WEAPONS, attacker) & (culprit.m_wepset))) culprit = attacker.(weaponentity).m_weapon;
-
-               if(g_weaponarena_random_with_blaster && culprit == WEP_BLASTER) // WEAPONTODO: Shouldn't this be in a mutator?
-               {
-                       // no exchange
-               }
-               else
-               {
-                       if(!GiveFrags_randomweapons)
-                       {
-                               GiveFrags_randomweapons = new(GiveFrags_randomweapons);
-                       }
-
-                       if(warmup_stage)
-                               STAT(WEAPONS, GiveFrags_randomweapons) = WARMUP_START_WEAPONS;
-                       else
-                               STAT(WEAPONS, GiveFrags_randomweapons) = start_weapons;
-
-                       // all others (including the culprit): remove
-                       STAT(WEAPONS, GiveFrags_randomweapons) &= ~STAT(WEAPONS, attacker);
-                       STAT(WEAPONS, GiveFrags_randomweapons) &= ~(culprit.m_wepset);
-
-                       // among the remaining ones, choose one by random
-                       STAT(WEAPONS, GiveFrags_randomweapons) = W_RandomWeapons(GiveFrags_randomweapons, STAT(WEAPONS, GiveFrags_randomweapons), 1);
-
-                       if(STAT(WEAPONS, GiveFrags_randomweapons))
-                       {
-                               STAT(WEAPONS, attacker) |= STAT(WEAPONS, GiveFrags_randomweapons);
-                               STAT(WEAPONS, attacker) &= ~(culprit.m_wepset);
-                       }
-               }
-
-               // after a frag, choose another random weapon set
-               if (!(STAT(WEAPONS, attacker) & WepSet_FromWeapon(attacker.(weaponentity).m_weapon)))
-                       W_SwitchWeapon_Force(attacker, w_getbestweapon(attacker, weaponentity), weaponentity);
-       }
-
        // FIXME fix the mess this is (we have REAL points now!)
-       if(MUTATOR_CALLHOOK(GiveFragsForKill, attacker, targ, f))
+       if(MUTATOR_CALLHOOK(GiveFragsForKill, attacker, targ, f, deathtype, attacker.(weaponentity)))
                f = M_ARGV(2, float);
 
        attacker.totalfrags += f;
@@ -114,8 +74,6 @@ void GiveFrags (entity attacker, entity targ, float f, int deathtype, .entity we
                UpdateFrags(attacker, f);
 }
 
-.entity kh_next;
-
 string AppendItemcodes(string s, entity player)
 {
        for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
@@ -131,12 +89,11 @@ string AppendItemcodes(string s, entity player)
                s = strcat(s, "S");
        if(time < player.invincible_finished)
                s = strcat(s, "I");
-       if(player.flagcarried != NULL)
-               s = strcat(s, "F");
        if(PHYS_INPUT_BUTTON_CHAT(player))
                s = strcat(s, "T");
-       if(player.kh_next)
-               s = strcat(s, "K");
+       // TODO: include these codes as a flag on the item itself
+       MUTATOR_CALLHOOK(LogDeath_AppendItemCodes, player, s);
+       s = M_ARGV(1, string);
        return s;
 }
 
@@ -256,14 +213,13 @@ bool frag_centermessage_override(entity attacker, entity targ, int deathtype, in
        if(deathtype == DEATH_FIRE.m_id)
        {
                Send_Notification(NOTIF_ONE, attacker, MSG_CHOICE, CHOICE_FRAG_FIRE, targ.netname, kill_count_to_attacker, (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping));
-               Send_Notification(NOTIF_ONE, targ, MSG_CHOICE, CHOICE_FRAGGED_FIRE, attacker.netname, kill_count_to_target, GetResourceAmount(attacker, RESOURCE_HEALTH), GetResourceAmount(attacker, RESOURCE_ARMOR), (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping));
+               Send_Notification(NOTIF_ONE, targ, MSG_CHOICE, CHOICE_FRAGGED_FIRE, attacker.netname, kill_count_to_target, GetResource(attacker, RES_HEALTH), GetResource(attacker, RES_ARMOR), (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping));
                return true;
        }
 
        return MUTATOR_CALLHOOK(FragCenterMessage, attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target);
 }
 
-entity buff_FirstFromFlags(int _buffs);
 void Obituary(entity attacker, entity inflictor, entity targ, int deathtype, .entity weaponentity)
 {
        // Sanity check
@@ -362,14 +318,11 @@ void Obituary(entity attacker, entity inflictor, entity targ, int deathtype, .en
                        // these 2 macros are spread over multiple files
                        #define SPREE_ITEM(counta,countb,center,normal,gentle) \
                                case counta: \
-                               { \
                                        Send_Notification(NOTIF_ONE, attacker, MSG_ANNCE, ANNCE_KILLSTREAK_##countb); \
-                                       if (!warmup_stage)\
-                                       {\
+                                       if (!warmup_stage) \
                                                PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_##counta, 1); \
-                                       }\
-                                       break; \
-                               }
+                                       break;
+
                        switch(CS(attacker).killcount)
                        {
                                KILL_SPREE_LIST
@@ -412,8 +365,8 @@ void Obituary(entity attacker, entity inflictor, entity targ, int deathtype, .en
                                        CHOICE_TYPEFRAGGED,
                                        attacker.netname,
                                        kill_count_to_target,
-                                       GetResourceAmount(attacker, RESOURCE_HEALTH),
-                                       GetResourceAmount(attacker, RESOURCE_ARMOR),
+                                       GetResource(attacker, RES_HEALTH),
+                                       GetResource(attacker, RES_ARMOR),
                                        (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping)
                                );
                        }
@@ -435,8 +388,8 @@ void Obituary(entity attacker, entity inflictor, entity targ, int deathtype, .en
                                        CHOICE_FRAGGED,
                                        attacker.netname,
                                        kill_count_to_target,
-                                       GetResourceAmount(attacker, RESOURCE_HEALTH),
-                                       GetResourceAmount(attacker, RESOURCE_ARMOR),
+                                       GetResource(attacker, RES_HEALTH),
+                                       GetResource(attacker, RES_ARMOR),
                                        (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping)
                                );
                        }
@@ -519,9 +472,9 @@ void Ice_Think(entity this)
        this.nextthink = time;
 }
 
-void Freeze (entity targ, float revivespeed, float frozen_type, float show_waypoint)
+void Freeze(entity targ, float revivespeed, int frozen_type, bool show_waypoint)
 {
-       if(!IS_PLAYER(targ) && !IS_MONSTER(targ)) // only specified entities can be freezed
+       if(!IS_PLAYER(targ) && !IS_MONSTER(targ)) // TODO: only specified entities can be freezed
                return;
 
        if(STAT(FROZEN, targ))
@@ -530,8 +483,8 @@ void Freeze (entity targ, float revivespeed, float frozen_type, float show_waypo
        float targ_maxhealth = ((IS_MONSTER(targ)) ? targ.max_health : start_health);
 
        STAT(FROZEN, targ) = frozen_type;
-       STAT(REVIVE_PROGRESS, targ) = ((frozen_type == 3) ? 1 : 0);
-       SetResourceAmount(targ, RESOURCE_HEALTH, ((frozen_type == 3) ? targ_maxhealth : 1));
+       STAT(REVIVE_PROGRESS, targ) = ((frozen_type == FROZEN_TEMP_DYING) ? 1 : 0);
+       SetResource(targ, RES_HEALTH, ((frozen_type == FROZEN_TEMP_DYING) ? targ_maxhealth : 1));
        targ.revive_speed = revivespeed;
        if(targ.bot_attack)
                IL_REMOVE(g_bot_targets, targ);
@@ -566,20 +519,19 @@ void Freeze (entity targ, float revivespeed, float frozen_type, float show_waypo
        });
 
        // add waypoint
-       if(show_waypoint)
+       if(MUTATOR_CALLHOOK(Freeze, targ, revivespeed, frozen_type) || show_waypoint)
                WaypointSprite_Spawn(WP_Frozen, 0, 0, targ, '0 0 64', NULL, targ.team, targ, waypointsprite_attached, true, RADARICON_WAYPOINT);
 }
 
-void Unfreeze (entity targ)
+void Unfreeze(entity targ, bool reset_health)
 {
        if(!STAT(FROZEN, targ))
                return;
 
-       if(STAT(FROZEN, targ) && STAT(FROZEN, targ) != 3) // only reset health if target was frozen
-       {
-               SetResourceAmount(targ, RESOURCE_HEALTH, ((IS_PLAYER(targ)) ? start_health : targ.max_health));
-               targ.pauseregen_finished = time + autocvar_g_balance_pause_health_regen;
-       }
+       if (reset_health && STAT(FROZEN, targ) != FROZEN_TEMP_DYING)
+               SetResource(targ, RES_HEALTH, ((IS_PLAYER(targ)) ? start_health : targ.max_health));
+
+       targ.pauseregen_finished = time + autocvar_g_balance_pause_health_regen;
 
        STAT(FROZEN, targ) = 0;
        STAT(REVIVE_PROGRESS, targ) = 0;
@@ -604,9 +556,11 @@ void Unfreeze (entity targ)
        if(targ.iceblock)
                delete(targ.iceblock);
        targ.iceblock = NULL;
+
+       MUTATOR_CALLHOOK(Unfreeze, targ);
 }
 
-void Damage (entity targ, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
+void Damage(entity targ, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
 {
        float complainteamdamage = 0;
        float mirrordamage = 0;
@@ -635,9 +589,9 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, int d
                // These are ALWAYS lethal
                // No damage modification here
                // Instead, prepare the victim for his death...
-               SetResourceAmount(targ, RESOURCE_ARMOR, 0);
+               SetResourceExplicit(targ, RES_ARMOR, 0);
                targ.spawnshieldtime = 0;
-               SetResourceAmount(targ, RESOURCE_HEALTH, 0.9); // this is < 1
+               SetResourceExplicit(targ, RES_HEALTH, 0.9); // this is < 1
                targ.flags -= targ.flags & FL_GODMODE;
                damage = 100000;
        }
@@ -678,7 +632,7 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, int d
 
                                                        if(autocvar_g_mirrordamage_virtual)
                                                        {
-                                                               vector v  = healtharmor_applydamage(attacker.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, mirrordamage);
+                                                               vector v  = healtharmor_applydamage(GetResource(attacker, RES_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, mirrordamage);
                                                                attacker.dmg_take += v.x;
                                                                attacker.dmg_save += v.y;
                                                                attacker.dmg_inflictor = inflictor;
@@ -688,7 +642,7 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, int d
 
                                                        if(autocvar_g_friendlyfire_virtual)
                                                        {
-                                                               vector v = healtharmor_applydamage(targ.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, damage);
+                                                               vector v = healtharmor_applydamage(GetResource(targ, RES_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, damage);
                                                                targ.dmg_take += v.x;
                                                                targ.dmg_save += v.y;
                                                                targ.dmg_inflictor = inflictor;
@@ -729,15 +683,12 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, int d
                    }
                }
 
-               if(STAT(FROZEN, targ))
-               if(deathtype != DEATH_HURTTRIGGER.m_id && deathtype != DEATH_TEAMCHANGE.m_id && deathtype != DEATH_AUTOTEAMCHANGE.m_id)
+               if(deathtype != DEATH_HURTTRIGGER.m_id && deathtype != DEATH_TEAMCHANGE.m_id && deathtype != DEATH_AUTOTEAMCHANGE.m_id && STAT(FROZEN, targ))
                {
-                       if(autocvar_g_frozen_revive_falldamage > 0)
-                       if(deathtype == DEATH_FALL.m_id)
-                       if(damage >= autocvar_g_frozen_revive_falldamage)
+                       if(autocvar_g_frozen_revive_falldamage > 0 && deathtype == DEATH_FALL.m_id && damage >= autocvar_g_frozen_revive_falldamage)
                        {
-                               Unfreeze(targ);
-                               SetResourceAmount(targ, RESOURCE_HEALTH, autocvar_g_frozen_revive_falldamage_health);
+                               Unfreeze(targ, false);
+                               SetResource(targ, RES_HEALTH, autocvar_g_frozen_revive_falldamage_health);
                                Send_Effect(EFFECT_ICEORGLASS, targ.origin, '0 0 0', 3);
                                Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, targ.netname);
                                Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF);
@@ -747,7 +698,7 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, int d
                        force *= autocvar_g_frozen_force;
                }
 
-               if(STAT(FROZEN, targ) && deathtype == DEATH_HURTTRIGGER.m_id && !autocvar_g_frozen_damage_trigger)
+               if(IS_PLAYER(targ) && STAT(FROZEN, targ) && deathtype == DEATH_HURTTRIGGER.m_id && !autocvar_g_frozen_damage_trigger)
                {
                        Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1);
 
@@ -851,7 +802,9 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, int d
                                }
                                else if(IS_PLAYER(attacker))
                                {
-                                       if(deathtype != DEATH_FIRE.m_id)
+                                       // if enemy gets frozen in this frame and receives other damage don't
+                                       // play the typehitsound e.g. when hit by multiple bullets of the shotgun
+                                       if (deathtype != DEATH_FIRE.m_id && (!STAT(FROZEN, victim) || time > victim.freeze_time))
                                        {
                                                attacker.typehitsound += 1;
                                        }
@@ -909,7 +862,7 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, int d
 }
 
 float RadiusDamageForSource (entity inflictor, vector inflictororigin, vector inflictorvelocity, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe,
-                                                               float inflictorselfdamage, float forceintensity, int deathtype, .entity weaponentity, entity directhitentity)
+                                                               float inflictorselfdamage, float forceintensity, float forcezscale, int deathtype, .entity weaponentity, entity directhitentity)
        // Returns total damage applies to creatures
 {
        entity  targ;
@@ -996,8 +949,10 @@ float RadiusDamageForSource (entity inflictor, vector inflictororigin, vector in
                                        force = force * (finaldmg / coredamage) * forceintensity;
                                        hitloc = nearest;
 
-                                       if(deathtype & WEP_BLASTER.m_id)
-                                               force *= WEP_CVAR_BOTH(blaster, !(deathtype & HITTYPE_SECONDARY), force_zscale);
+                                       // apply special scaling along the z axis if set
+                                       // NOTE: 0 value is not allowed for compatibility, in the case of weapon cvars not being set
+                                       if(forcezscale)
+                                               force.z *= forcezscale;
 
                                        if(targ != directhitentity)
                                        {
@@ -1080,9 +1035,9 @@ float RadiusDamageForSource (entity inflictor, vector inflictororigin, vector in
                                                }
 
                                                if(targ == directhitentity || DEATH_ISSPECIAL(deathtype))
-                                                       Damage (targ, inflictor, attacker, finaldmg, deathtype, weaponentity, nearest, force);
+                                                       Damage(targ, inflictor, attacker, finaldmg, deathtype, weaponentity, nearest, force);
                                                else
-                                                       Damage (targ, inflictor, attacker, finaldmg, deathtype | HITTYPE_SPLASH, weaponentity, nearest, force);
+                                                       Damage(targ, inflictor, attacker, finaldmg, deathtype | HITTYPE_SPLASH, weaponentity, nearest, force);
                                        }
                                }
                        }
@@ -1093,14 +1048,29 @@ float RadiusDamageForSource (entity inflictor, vector inflictororigin, vector in
        RadiusDamage_running = 0;
 
        if(!DEATH_ISSPECIAL(deathtype))
-               accuracy_add(attacker, DEATH_WEAPONOF(deathtype).m_id, 0, min(coredamage, stat_damagedone));
+               accuracy_add(attacker, DEATH_WEAPONOF(deathtype), 0, min(coredamage, stat_damagedone));
 
        return total_damage_to_creatures;
 }
 
-float RadiusDamage (entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float forceintensity, int deathtype, .entity weaponentity, entity directhitentity)
+float RadiusDamage(entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float forceintensity, int deathtype, .entity weaponentity, entity directhitentity)
 {
-       return RadiusDamageForSource (inflictor, (inflictor.origin + (inflictor.mins + inflictor.maxs) * 0.5), inflictor.velocity, attacker, coredamage, edgedamage, rad, cantbe, mustbe, false, forceintensity, deathtype, weaponentity, directhitentity);
+       return RadiusDamageForSource(inflictor, (inflictor.origin + (inflictor.mins + inflictor.maxs) * 0.5), inflictor.velocity, attacker, coredamage, edgedamage, rad, 
+                                                                       cantbe, mustbe, false, forceintensity, 1, deathtype, weaponentity, directhitentity);
+}
+
+bool Heal(entity targ, entity inflictor, float amount, float limit)
+{
+       if(game_stopped || (IS_CLIENT(targ) && CS(targ).killcount == FRAGS_SPECTATOR) || STAT(FROZEN, targ) || IS_DEAD(targ))
+               return false;
+
+       bool healed = false;
+       if(targ.event_heal)
+               healed = targ.event_heal(targ, inflictor, amount, limit);
+       // TODO: additional handling? what if the healing kills them? should this abort if healing would do so etc
+       // TODO: healing fx!
+       // TODO: armor healing?
+       return healed;
 }
 
 float Fire_IsBurning(entity e)
@@ -1208,7 +1178,7 @@ float Fire_AddDamage(entity e, entity o, float d, float t, float dt)
                                }
                        }
                        if(accuracy_isgooddamage(o, e))
-                               accuracy_add(o, DEATH_WEAPONOF(dt).m_id, 0, max(0, totaldamage - mindamage));
+                               accuracy_add(o, DEATH_WEAPONOF(dt), 0, max(0, totaldamage - mindamage));
                        return max(0, totaldamage - mindamage); // can never be negative, but to make sure
                }
                else
@@ -1222,7 +1192,7 @@ float Fire_AddDamage(entity e, entity o, float d, float t, float dt)
                e.fire_owner = o;
                e.fire_hitsound = false;
                if(accuracy_isgooddamage(o, e))
-                       accuracy_add(o, DEATH_WEAPONOF(dt).m_id, 0, d);
+                       accuracy_add(o, DEATH_WEAPONOF(dt), 0, d);
                return d;
        }
 }
@@ -1261,11 +1231,11 @@ void Fire_ApplyDamage(entity e)
        }
        e.fire_hitsound = true;
 
-       if(!IS_INDEPENDENT_PLAYER(e))
-       if(!STAT(FROZEN, e))
-               FOREACH_CLIENT(IS_PLAYER(it) && it != e, {
-                       if(!IS_DEAD(it))
-                       if(!IS_INDEPENDENT_PLAYER(it))
+       if(!IS_INDEPENDENT_PLAYER(e) && !STAT(FROZEN, e))
+       {
+               IL_EACH(g_damagedbycontents, it.damagedbycontents && it != e,
+               {
+                       if(!IS_DEAD(it) && it.takedamage && !IS_INDEPENDENT_PLAYER(it))
                        if(boxesoverlap(e.absmin, e.absmax, it.absmin, it.absmax))
                        {
                                t = autocvar_g_balance_firetransfer_time * (e.fire_endtime - time);
@@ -1273,6 +1243,7 @@ void Fire_ApplyDamage(entity e)
                                Fire_AddDamage(it, o, d, t, DEATH_FIRE.m_id);
                        }
                });
+       }
 }
 
 void Fire_ApplyEffect(entity e)