X-Git-Url: http://de.git.xonotic.org/?p=xonotic%2Fxonotic-data.pk3dir.git;a=blobdiff_plain;f=qcsrc%2Fserver%2Fg_damage.qc;h=e14ecd6ce7808ebeeca7dd436dc1aa4d41db2127;hp=c2c2b85bc54bcf0678121697bc2334ae40ba69a5;hb=HEAD;hpb=bdb8dce93bf95c85d4b803ac61cbe5bd49910ccc diff --git a/qcsrc/server/g_damage.qc b/qcsrc/server/g_damage.qc deleted file mode 100644 index c2c2b85bc5..0000000000 --- a/qcsrc/server/g_damage.qc +++ /dev/null @@ -1,1279 +0,0 @@ -#include "g_damage.qh" - -#include -#include "bot/api.qh" -#include "g_hook.qh" -#include -#include "teamplay.qh" -#include "scores.qh" -#include "spawnpoints.qh" -#include "../common/state.qh" -#include "../common/physics/player.qh" -#include "../common/t_items.qh" -#include "resources.qh" -#include "../common/vehicles/all.qh" -#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" -#include "../common/constants.qh" -#include "../common/deathtypes/all.qh" -#include "../common/notifications/all.qh" -#include "../common/physics/movetypes/movetypes.qh" -#include "../common/playerstats.qh" -#include "../common/teams.qh" -#include "../common/util.qh" -#include -#include -#include "../lib/csqcmodel/sv_model.qh" -#include "../lib/warpzone/common.qh" - -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) -{ - // TODO route through PlayerScores instead - if(game_stopped) return; - - if(f < 0) - { - if(targ == attacker) - { - // suicide - GameRules_scoring_add(attacker, SUICIDES, 1); - } - else - { - // teamkill - GameRules_scoring_add(attacker, TEAMKILLS, 1); - } - } - else - { - // regular frag - GameRules_scoring_add(attacker, KILLS, 1); - if(!warmup_stage && targ.playerid) - PlayerStats_GameReport_Event_Player(attacker, sprintf("kills-%d", targ.playerid), 1); - } - - GameRules_scoring_add(targ, DEATHS, 1); - - // FIXME fix the mess this is (we have REAL points now!) - if(MUTATOR_CALLHOOK(GiveFragsForKill, attacker, targ, f, deathtype, attacker.(weaponentity))) - f = M_ARGV(2, float); - - attacker.totalfrags += f; - - if(f) - UpdateFrags(attacker, f); -} - -.entity kh_next; - -string AppendItemcodes(string s, entity player) -{ - for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) - { - .entity weaponentity = weaponentities[slot]; - int w = player.(weaponentity).m_weapon.m_id; - if(w == 0) - w = player.(weaponentity).cnt; // previous weapon - if(w != 0 || slot == 0) - s = strcat(s, ftos(w)); - } - if(time < player.strength_finished) - 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"); - return s; -} - -void LogDeath(string mode, int deathtype, entity killer, entity killed) -{ - string s; - if(!autocvar_sv_eventlog) - return; - s = strcat(":kill:", mode); - s = strcat(s, ":", ftos(killer.playerid)); - s = strcat(s, ":", ftos(killed.playerid)); - s = strcat(s, ":type=", Deathtype_Name(deathtype)); - s = strcat(s, ":items="); - s = AppendItemcodes(s, killer); - if(killed != killer) - { - s = strcat(s, ":victimitems="); - s = AppendItemcodes(s, killed); - } - GameLogEcho(s); -} - -void Obituary_SpecialDeath( - entity notif_target, - float murder, - int deathtype, - string s1, string s2, string s3, - float f1, float f2, float f3) -{ - if(!DEATH_ISSPECIAL(deathtype)) - { - backtrace("Obituary_SpecialDeath called without a special deathtype?\n"); - return; - } - - entity deathent = Deathtypes_from(deathtype - DT_FIRST); - if (!deathent) - { - backtrace("Obituary_SpecialDeath: Could not find deathtype entity!\n"); - return; - } - - if(g_cts && deathtype == DEATH_KILL.m_id) - return; // TODO: somehow put this in CTS gamemode file! - - Notification death_message = (murder) ? deathent.death_msgmurder : deathent.death_msgself; - if(death_message) - { - Send_Notification_WOCOVA( - NOTIF_ONE, - notif_target, - MSG_MULTI, - death_message, - s1, s2, s3, "", - f1, f2, f3, 0 - ); - Send_Notification_WOCOVA( - NOTIF_ALL_EXCEPT, - notif_target, - MSG_INFO, - death_message.nent_msginfo, - s1, s2, s3, "", - f1, f2, f3, 0 - ); - } -} - -float Obituary_WeaponDeath( - entity notif_target, - float murder, - int deathtype, - string s1, string s2, string s3, - float f1, float f2) -{ - Weapon death_weapon = DEATH_WEAPONOF(deathtype); - if (death_weapon == WEP_Null) - return false; - - w_deathtype = deathtype; - Notification death_message = ((murder) ? death_weapon.wr_killmessage(death_weapon) : death_weapon.wr_suicidemessage(death_weapon)); - w_deathtype = false; - - if (death_message) - { - Send_Notification_WOCOVA( - NOTIF_ONE, - notif_target, - MSG_MULTI, - death_message, - s1, s2, s3, "", - f1, f2, 0, 0 - ); - // send the info part to everyone - Send_Notification_WOCOVA( - NOTIF_ALL_EXCEPT, - notif_target, - MSG_INFO, - death_message.nent_msginfo, - s1, s2, s3, "", - f1, f2, 0, 0 - ); - } - else - { - LOG_TRACEF( - "Obituary_WeaponDeath(): ^1Deathtype ^7(%d)^1 has no notification for weapon %d!\n", - deathtype, - death_weapon - ); - } - - return true; -} - -bool frag_centermessage_override(entity attacker, entity targ, int deathtype, int kill_count_to_attacker, int kill_count_to_target) -{ - 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)); - return true; - } - - return MUTATOR_CALLHOOK(FragCenterMessage, attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target); -} - -void Obituary(entity attacker, entity inflictor, entity targ, int deathtype, .entity weaponentity) -{ - // Sanity check - if (!IS_PLAYER(targ)) { backtrace("Obituary called on non-player?!\n"); return; } - - // Declarations - float notif_firstblood = false; - float kill_count_to_attacker, kill_count_to_target; - - // Set final information for the death - targ.death_origin = targ.origin; - string deathlocation = (autocvar_notification_server_allows_location ? NearestLocation(targ.death_origin) : ""); - - #ifdef NOTIFICATIONS_DEBUG - Debug_Notification( - sprintf( - "Obituary(%s, %s, %s, %s = %d);\n", - attacker.netname, - inflictor.netname, - targ.netname, - Deathtype_Name(deathtype), - deathtype - ) - ); - #endif - - // ======= - // SUICIDE - // ======= - if(targ == attacker) - { - if(DEATH_ISSPECIAL(deathtype)) - { - if(deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id) - { - Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", targ.team, 0, 0); - } - else - { - switch(DEATH_ENT(deathtype)) - { - case DEATH_MIRRORDAMAGE: - { - Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0); - break; - } - - default: - { - Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0); - break; - } - } - } - } - else if (!Obituary_WeaponDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0)) - { - backtrace("SUICIDE: what the hell happened here?\n"); - return; - } - LogDeath("suicide", deathtype, targ, targ); - if(deathtype != DEATH_AUTOTEAMCHANGE.m_id) // special case: don't negate frags if auto switched - GiveFrags(attacker, targ, -1, deathtype, weaponentity); - } - - // ====== - // MURDER - // ====== - else if(IS_PLAYER(attacker)) - { - if(SAME_TEAM(attacker, targ)) - { - LogDeath("tk", deathtype, attacker, targ); - GiveFrags(attacker, targ, -1, deathtype, weaponentity); - - CS(attacker).killcount = 0; - - Send_Notification(NOTIF_ONE, attacker, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAG, targ.netname); - Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAGGED, attacker.netname); - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(targ.team, INFO_DEATH_TEAMKILL), targ.netname, attacker.netname, deathlocation, CS(targ).killcount); - - // In this case, the death message will ALWAYS be "foo was betrayed by bar" - // No need for specific death/weapon messages... - } - else - { - LogDeath("frag", deathtype, attacker, targ); - GiveFrags(attacker, targ, 1, deathtype, weaponentity); - - CS(attacker).taunt_soundtime = time + 1; - CS(attacker).killcount = CS(attacker).killcount + 1; - - attacker.killsound += 1; - - // TODO: improve SPREE_ITEM and KILL_SPREE_LIST - // 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)\ - {\ - PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_##counta, 1); \ - }\ - break; \ - } - switch(CS(attacker).killcount) - { - KILL_SPREE_LIST - default: break; - } - #undef SPREE_ITEM - - if(!warmup_stage && !checkrules_firstblood) - { - checkrules_firstblood = true; - notif_firstblood = true; // modify the current messages so that they too show firstblood information - PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD, 1); - PlayerStats_GameReport_Event_Player(targ, PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM, 1); - - // tell spree_inf and spree_cen that this is a first-blood and first-victim event - kill_count_to_attacker = -1; - kill_count_to_target = -2; - } - else - { - kill_count_to_attacker = CS(attacker).killcount; - kill_count_to_target = 0; - } - - if(targ.istypefrag) - { - Send_Notification( - NOTIF_ONE, - attacker, - MSG_CHOICE, - CHOICE_TYPEFRAG, - targ.netname, - kill_count_to_attacker, - (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping) - ); - Send_Notification( - NOTIF_ONE, - targ, - MSG_CHOICE, - CHOICE_TYPEFRAGGED, - attacker.netname, - kill_count_to_target, - GetResourceAmount(attacker, RESOURCE_HEALTH), - GetResourceAmount(attacker, RESOURCE_ARMOR), - (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping) - ); - } - else if(!frag_centermessage_override(attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target)) - { - Send_Notification( - NOTIF_ONE, - attacker, - MSG_CHOICE, - CHOICE_FRAG, - targ.netname, - kill_count_to_attacker, - (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping) - ); - Send_Notification( - NOTIF_ONE, - targ, - MSG_CHOICE, - CHOICE_FRAGGED, - attacker.netname, - kill_count_to_target, - GetResourceAmount(attacker, RESOURCE_HEALTH), - GetResourceAmount(attacker, RESOURCE_ARMOR), - (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping) - ); - } - - int f3 = 0; - if(deathtype == DEATH_BUFF.m_id) - f3 = buff_FirstFromFlags(STAT(BUFFS, attacker)).m_id; - - if (!Obituary_WeaponDeath(targ, true, deathtype, targ.netname, attacker.netname, deathlocation, CS(targ).killcount, kill_count_to_attacker)) - Obituary_SpecialDeath(targ, true, deathtype, targ.netname, attacker.netname, deathlocation, CS(targ).killcount, kill_count_to_attacker, f3); - } - } - - // ============= - // ACCIDENT/TRAP - // ============= - else - { - switch(DEATH_ENT(deathtype)) - { - // For now, we're just forcing HURTTRIGGER to behave as "DEATH_VOID" and giving it no special options... - // Later on you will only be able to make custom messages using DEATH_CUSTOM, - // and there will be a REAL DEATH_VOID implementation which mappers will use. - case DEATH_HURTTRIGGER: - { - Obituary_SpecialDeath(targ, false, deathtype, - targ.netname, - inflictor.message, - deathlocation, - CS(targ).killcount, - 0, - 0); - break; - } - - case DEATH_CUSTOM: - { - Obituary_SpecialDeath(targ, false, deathtype, - targ.netname, - ((strstrofs(deathmessage, "%", 0) < 0) ? strcat("%s ", deathmessage) : deathmessage), - deathlocation, - CS(targ).killcount, - 0, - 0); - break; - } - - default: - { - Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0); - break; - } - } - - LogDeath("accident", deathtype, targ, targ); - GiveFrags(targ, targ, -1, deathtype, weaponentity); - - if(GameRules_scoring_add(targ, SCORE, 0) == -5) - { - Send_Notification(NOTIF_ONE, targ, MSG_ANNCE, ANNCE_ACHIEVEMENT_BOTLIKE); - if (!warmup_stage) - { - PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_BOTLIKE, 1); - } - } - } - - // reset target kill count - CS(targ).killcount = 0; -} - -void Ice_Think(entity this) -{ - if(!STAT(FROZEN, this.owner) || this.owner.iceblock != this) - { - delete(this); - return; - } - setorigin(this, this.owner.origin - '0 0 16'); - this.nextthink = time; -} - -void Freeze(entity targ, float revivespeed, int frozen_type, bool show_waypoint) -{ - if(!IS_PLAYER(targ) && !IS_MONSTER(targ)) // TODO: only specified entities can be freezed - return; - - if(STAT(FROZEN, targ)) - return; - - 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)); - targ.revive_speed = revivespeed; - if(targ.bot_attack) - IL_REMOVE(g_bot_targets, targ); - targ.bot_attack = false; - targ.freeze_time = time; - - entity ice = new(ice); - ice.owner = targ; - ice.scale = targ.scale; - setthink(ice, Ice_Think); - ice.nextthink = time; - ice.frame = floor(random() * 21); // ice model has 20 different looking frames - setmodel(ice, MDL_ICE); - ice.alpha = 1; - ice.colormod = Team_ColorRGB(targ.team); - ice.glowmod = ice.colormod; - targ.iceblock = ice; - targ.revival_time = 0; - - Ice_Think(ice); - - RemoveGrapplingHooks(targ); - - FOREACH_CLIENT(IS_PLAYER(it), - { - for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) - { - .entity weaponentity = weaponentities[slot]; - if(it.(weaponentity).hook.aiment == targ) - RemoveHook(it.(weaponentity).hook); - } - }); - - // add 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, bool reset_health) -{ - if(!STAT(FROZEN, targ)) - return; - - if (reset_health && 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; - - STAT(FROZEN, targ) = 0; - STAT(REVIVE_PROGRESS, targ) = 0; - targ.revival_time = time; - if(!targ.bot_attack) - IL_PUSH(g_bot_targets, targ); - targ.bot_attack = true; - - WaypointSprite_Kill(targ.waypointsprite_attached); - - FOREACH_CLIENT(IS_PLAYER(it), - { - for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) - { - .entity weaponentity = weaponentities[slot]; - if(it.(weaponentity).hook.aiment == targ) - RemoveHook(it.(weaponentity).hook); - } - }); - - // remove the ice block - 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) -{ - float complainteamdamage = 0; - float mirrordamage = 0; - float mirrorforce = 0; - - if (game_stopped || (IS_CLIENT(targ) && CS(targ).killcount == FRAGS_SPECTATOR)) - return; - - entity attacker_save = attacker; - - // special rule: gravity bomb does not hit team mates (other than for disconnecting the hook) - if(DEATH_ISWEAPON(deathtype, WEP_HOOK) || DEATH_ISWEAPON(deathtype, WEP_TUBA)) - { - if(IS_PLAYER(targ) && SAME_TEAM(targ, attacker)) - { - return; - } - } - - if(deathtype == DEATH_KILL.m_id || deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id) - { - // exit the vehicle before killing (fixes a crash) - if(IS_PLAYER(targ) && targ.vehicle) - vehicles_exit(targ.vehicle, VHEF_RELEASE); - - // These are ALWAYS lethal - // No damage modification here - // Instead, prepare the victim for his death... - SetResourceAmountExplicit(targ, RESOURCE_ARMOR, 0); - targ.spawnshieldtime = 0; - SetResourceAmountExplicit(targ, RESOURCE_HEALTH, 0.9); // this is < 1 - targ.flags -= targ.flags & FL_GODMODE; - damage = 100000; - } - else if(deathtype == DEATH_MIRRORDAMAGE.m_id || deathtype == DEATH_NOAMMO.m_id) - { - // no processing - } - else - { - // nullify damage if teamplay is on - if(deathtype != DEATH_TELEFRAG.m_id) - if(IS_PLAYER(attacker)) - { - if(IS_PLAYER(targ) && targ != attacker && (IS_INDEPENDENT_PLAYER(attacker) || IS_INDEPENDENT_PLAYER(targ))) - { - damage = 0; - force = '0 0 0'; - } - else if(SAME_TEAM(attacker, targ)) - { - if(autocvar_teamplay_mode == 1) - damage = 0; - else if(attacker != targ) - { - if(autocvar_teamplay_mode == 3) - damage = 0; - else if(autocvar_teamplay_mode == 4) - { - if(IS_PLAYER(targ) && !IS_DEAD(targ)) - { - attacker.dmg_team = attacker.dmg_team + damage; - complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold; - if(complainteamdamage > 0) - mirrordamage = autocvar_g_mirrordamage * complainteamdamage; - mirrorforce = autocvar_g_mirrordamage * vlen(force); - damage = autocvar_g_friendlyfire * damage; - // mirrordamage will be used LATER - - if(autocvar_g_mirrordamage_virtual) - { - vector v = healtharmor_applydamage(GetResourceAmount(attacker, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, mirrordamage); - attacker.dmg_take += v.x; - attacker.dmg_save += v.y; - attacker.dmg_inflictor = inflictor; - mirrordamage = v.z; - mirrorforce = 0; - } - - if(autocvar_g_friendlyfire_virtual) - { - vector v = healtharmor_applydamage(GetResourceAmount(targ, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, damage); - targ.dmg_take += v.x; - targ.dmg_save += v.y; - targ.dmg_inflictor = inflictor; - damage = 0; - if(!autocvar_g_friendlyfire_virtual_force) - force = '0 0 0'; - } - } - else if(!targ.canteamdamage) - damage = 0; - } - } - } - } - - if (!DEATH_ISSPECIAL(deathtype)) - { - damage *= g_weapondamagefactor; - mirrordamage *= g_weapondamagefactor; - complainteamdamage *= g_weapondamagefactor; - force = force * g_weaponforcefactor; - mirrorforce *= g_weaponforcefactor; - } - - // should this be changed at all? If so, in what way? - MUTATOR_CALLHOOK(Damage_Calculate, inflictor, attacker, targ, deathtype, damage, mirrordamage, force, attacker.(weaponentity)); - damage = M_ARGV(4, float); - mirrordamage = M_ARGV(5, float); - force = M_ARGV(6, vector); - - if(IS_PLAYER(targ) && damage > 0 && attacker) - { - for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) - { - .entity went = weaponentities[slot]; - if(targ.(went).hook && targ.(went).hook.aiment == attacker) - RemoveHook(targ.(went).hook); - } - } - - if(STAT(FROZEN, targ)) - if(deathtype != DEATH_HURTTRIGGER.m_id && deathtype != DEATH_TEAMCHANGE.m_id && deathtype != DEATH_AUTOTEAMCHANGE.m_id) - { - if(autocvar_g_frozen_revive_falldamage > 0) - if(deathtype == DEATH_FALL.m_id) - if(damage >= autocvar_g_frozen_revive_falldamage) - { - Unfreeze(targ, false); - SetResourceAmount(targ, RESOURCE_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); - } - - damage = 0; - force *= autocvar_g_frozen_force; - } - - if(STAT(FROZEN, targ) && deathtype == DEATH_HURTTRIGGER.m_id && !autocvar_g_frozen_damage_trigger) - { - Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1); - - entity spot = SelectSpawnPoint (targ, false); - - if(spot) - { - damage = 0; - targ.deadflag = DEAD_NO; - - targ.angles = spot.angles; - - targ.effects = 0; - targ.effects |= EF_TELEPORT_BIT; - - targ.angles_z = 0; // never spawn tilted even if the spot says to - targ.fixangle = true; // turn this way immediately - targ.velocity = '0 0 0'; - targ.avelocity = '0 0 0'; - targ.punchangle = '0 0 0'; - targ.punchvector = '0 0 0'; - targ.oldvelocity = targ.velocity; - - targ.spawnorigin = spot.origin; - setorigin(targ, spot.origin + '0 0 1' * (1 - targ.mins.z - 24)); - // don't reset back to last position, even if new position is stuck in solid - targ.oldorigin = targ.origin; - - Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1); - } - } - - if(!MUTATOR_IS_ENABLED(mutator_instagib)) - { - // apply strength multiplier - if (attacker.items & ITEM_Strength.m_itemid) - { - if(targ == attacker) - { - damage = damage * autocvar_g_balance_powerup_strength_selfdamage; - force = force * autocvar_g_balance_powerup_strength_selfforce; - } - else - { - damage = damage * autocvar_g_balance_powerup_strength_damage; - force = force * autocvar_g_balance_powerup_strength_force; - } - } - - // apply invincibility multiplier - if (targ.items & ITEM_Shield.m_itemid) - { - damage = damage * autocvar_g_balance_powerup_invincible_takedamage; - if (targ != attacker) - { - force = force * autocvar_g_balance_powerup_invincible_takeforce; - } - } - } - - if (targ == attacker) - damage = damage * autocvar_g_balance_selfdamagepercent; // Partial damage if the attacker hits himself - - // count the damage - if(attacker) - if(!IS_DEAD(targ)) - if(deathtype != DEATH_BUFF.m_id) - if(targ.takedamage == DAMAGE_AIM) - if(targ != attacker) - { - entity victim; - if(IS_VEHICLE(targ) && targ.owner) - victim = targ.owner; - else - victim = targ; - - if(IS_PLAYER(victim) || (IS_TURRET(victim) && victim.active == ACTIVE_ACTIVE) || IS_MONSTER(victim) || MUTATOR_CALLHOOK(PlayHitsound, victim, attacker)) - { - if(DIFF_TEAM(victim, attacker) && !STAT(FROZEN, victim)) - { - if(damage > 0) - { - if(deathtype != DEATH_FIRE.m_id) - { - if(PHYS_INPUT_BUTTON_CHAT(victim)) - attacker.typehitsound += 1; - else - attacker.damage_dealt += damage; - } - - damage_goodhits += 1; - damage_gooddamage += damage; - - if (!DEATH_ISSPECIAL(deathtype)) - { - if(IS_PLAYER(targ)) // don't do this for vehicles - if(IsFlying(victim)) - yoda = 1; - } - } - } - else if(IS_PLAYER(attacker)) - { - if(deathtype != DEATH_FIRE.m_id) - { - attacker.typehitsound += 1; - } - if(complainteamdamage > 0) - if(time > CS(attacker).teamkill_complain) - { - CS(attacker).teamkill_complain = time + 5; - CS(attacker).teamkill_soundtime = time + 0.4; - CS(attacker).teamkill_soundsource = targ; - } - } - } - } - } - - // apply push - if (targ.damageforcescale) - if (force) - if (!IS_PLAYER(targ) || time >= targ.spawnshieldtime || targ == attacker) - { - vector farce = damage_explosion_calcpush(targ.damageforcescale * force, targ.velocity, autocvar_g_balance_damagepush_speedfactor); - if(targ.move_movetype == MOVETYPE_PHYSICS) - { - entity farcent = new(farce); - farcent.enemy = targ; - farcent.movedir = farce * 10; - if(targ.mass) - farcent.movedir = farcent.movedir * targ.mass; - farcent.origin = hitloc; - farcent.forcetype = FORCETYPE_FORCEATPOS; - farcent.nextthink = time + 0.1; - setthink(farcent, SUB_Remove); - } - else - { - targ.velocity = targ.velocity + farce; - } - UNSET_ONGROUND(targ); - UpdateCSQCProjectile(targ); - } - // apply damage - if (damage != 0 || (targ.damageforcescale && force)) - if (targ.event_damage) - targ.event_damage (targ, inflictor, attacker, damage, deathtype, weaponentity, hitloc, force); - - // apply mirror damage if any - if(!autocvar_g_mirrordamage_onlyweapons || DEATH_WEAPONOF(deathtype) != WEP_Null) - if(mirrordamage > 0 || mirrorforce > 0) - { - attacker = attacker_save; - - force = normalize(attacker.origin + attacker.view_ofs - hitloc) * mirrorforce; - Damage(attacker, inflictor, attacker, mirrordamage, DEATH_MIRRORDAMAGE.m_id, weaponentity, attacker.origin, force); - } -} - -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) - // Returns total damage applies to creatures -{ - entity targ; - vector force; - float total_damage_to_creatures; - entity next; - float tfloordmg; - float tfloorforce; - - float stat_damagedone; - - if(RadiusDamage_running) - { - backtrace("RadiusDamage called recursively! Expect stuff to go HORRIBLY wrong."); - return 0; - } - - RadiusDamage_running = 1; - - tfloordmg = autocvar_g_throughfloor_damage; - tfloorforce = autocvar_g_throughfloor_force; - - total_damage_to_creatures = 0; - - if(deathtype != (WEP_HOOK.m_id | HITTYPE_SECONDARY | HITTYPE_BOUNCE)) // only send gravity bomb damage once - if(DEATH_WEAPONOF(deathtype) != WEP_TUBA) // do not send tuba damage (bandwidth hog) - { - force = inflictorvelocity; - if(force == '0 0 0') - force = '0 0 -1'; - else - force = normalize(force); - if(forceintensity >= 0) - Damage_DamageInfo(inflictororigin, coredamage, edgedamage, rad, forceintensity * force, deathtype, 0, attacker); - else - Damage_DamageInfo(inflictororigin, coredamage, edgedamage, -rad, (-forceintensity) * force, deathtype, 0, attacker); - } - - stat_damagedone = 0; - - targ = WarpZone_FindRadius (inflictororigin, rad + MAX_DAMAGEEXTRARADIUS, false); - while (targ) - { - next = targ.chain; - if ((targ != inflictor) || inflictorselfdamage) - if (((cantbe != targ) && !mustbe) || (mustbe == targ)) - if (targ.takedamage) - { - vector nearest; - vector diff; - float power; - - // LordHavoc: measure distance to nearest point on target (not origin) - // (this guarentees 100% damage on a touch impact) - nearest = targ.WarpZone_findradius_nearest; - diff = targ.WarpZone_findradius_dist; - // round up a little on the damage to ensure full damage on impacts - // and turn the distance into a fraction of the radius - power = 1 - ((vlen (diff) - bound(MIN_DAMAGEEXTRARADIUS, targ.damageextraradius, MAX_DAMAGEEXTRARADIUS)) / rad); - //bprint(" "); - //bprint(ftos(power)); - //if (targ == attacker) - // print(ftos(power), "\n"); - if (power > 0) - { - float finaldmg; - if (power > 1) - power = 1; - finaldmg = coredamage * power + edgedamage * (1 - power); - if (finaldmg > 0) - { - float a; - float c; - vector hitloc; - vector myblastorigin; - vector center; - - myblastorigin = WarpZone_TransformOrigin(targ, inflictororigin); - - // if it's a player, use the view origin as reference - center = CENTER_OR_VIEWOFS(targ); - - force = normalize(center - myblastorigin); - force = force * (finaldmg / coredamage) * forceintensity; - hitloc = nearest; - - if(deathtype & WEP_BLASTER.m_id) - force *= WEP_CVAR_BOTH(blaster, !(deathtype & HITTYPE_SECONDARY), force_zscale); - - if(targ != directhitentity) - { - float hits; - float total; - float hitratio; - float mininv_f, mininv_d; - - // test line of sight to multiple positions on box, - // and do damage if any of them hit - hits = 0; - - // we know: max stddev of hitratio = 1 / (2 * sqrt(n)) - // so for a given max stddev: - // n = (1 / (2 * max stddev of hitratio))^2 - - mininv_d = (finaldmg * (1-tfloordmg)) / autocvar_g_throughfloor_damage_max_stddev; - mininv_f = (vlen(force) * (1-tfloorforce)) / autocvar_g_throughfloor_force_max_stddev; - - if(autocvar_g_throughfloor_debug) - LOG_INFOF("THROUGHFLOOR: D=%f F=%f max(dD)=1/%f max(dF)=1/%f", finaldmg, vlen(force), mininv_d, mininv_f); - - - total = 0.25 * (max(mininv_f, mininv_d) ** 2); - - if(autocvar_g_throughfloor_debug) - LOG_INFOF(" steps=%f", total); - - - if (IS_PLAYER(targ)) - total = ceil(bound(autocvar_g_throughfloor_min_steps_player, total, autocvar_g_throughfloor_max_steps_player)); - else - total = ceil(bound(autocvar_g_throughfloor_min_steps_other, total, autocvar_g_throughfloor_max_steps_other)); - - if(autocvar_g_throughfloor_debug) - LOG_INFOF(" steps=%f dD=%f dF=%f", total, finaldmg * (1-tfloordmg) / (2 * sqrt(total)), vlen(force) * (1-tfloorforce) / (2 * sqrt(total))); - - for(c = 0; c < total; ++c) - { - //traceline(targ.WarpZone_findradius_findorigin, nearest, MOVE_NOMONSTERS, inflictor); - WarpZone_TraceLine(inflictororigin, WarpZone_UnTransformOrigin(targ, nearest), MOVE_NOMONSTERS, inflictor); - if (trace_fraction == 1 || trace_ent == targ) - { - ++hits; - if (hits > 1) - hitloc = hitloc + nearest; - else - hitloc = nearest; - } - nearest.x = targ.origin.x + targ.mins.x + random() * targ.size.x; - nearest.y = targ.origin.y + targ.mins.y + random() * targ.size.y; - nearest.z = targ.origin.z + targ.mins.z + random() * targ.size.z; - } - - nearest = hitloc * (1 / max(1, hits)); - hitratio = (hits / total); - a = bound(0, tfloordmg + (1-tfloordmg) * hitratio, 1); - finaldmg = finaldmg * a; - a = bound(0, tfloorforce + (1-tfloorforce) * hitratio, 1); - force = force * a; - - if(autocvar_g_throughfloor_debug) - LOG_INFOF(" D=%f F=%f", finaldmg, vlen(force)); - } - - //if (targ == attacker) - //{ - // print("hits ", ftos(hits), " / ", ftos(total)); - // print(" finaldmg ", ftos(finaldmg), " force ", vtos(force)); - // print(" (", ftos(a), ")\n"); - //} - if(finaldmg || force) - { - if(targ.iscreature) - { - total_damage_to_creatures += finaldmg; - - if(accuracy_isgooddamage(attacker, targ)) - stat_damagedone += finaldmg; - } - - if(targ == directhitentity || DEATH_ISSPECIAL(deathtype)) - Damage(targ, inflictor, attacker, finaldmg, deathtype, weaponentity, nearest, force); - else - Damage(targ, inflictor, attacker, finaldmg, deathtype | HITTYPE_SPLASH, weaponentity, nearest, force); - } - } - } - } - targ = next; - } - - RadiusDamage_running = 0; - - if(!DEATH_ISSPECIAL(deathtype)) - accuracy_add(attacker, DEATH_WEAPONOF(deathtype).m_id, 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) -{ - return RadiusDamageForSource(inflictor, (inflictor.origin + (inflictor.mins + inflictor.maxs) * 0.5), inflictor.velocity, attacker, coredamage, edgedamage, rad, cantbe, mustbe, false, forceintensity, 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) -{ - return (time < e.fire_endtime); -} - -float Fire_AddDamage(entity e, entity o, float d, float t, float dt) -{ - float dps; - float maxtime, mintime, maxdamage, mindamage, maxdps, mindps, totaldamage, totaltime; - - if(IS_PLAYER(e)) - { - if(IS_DEAD(e)) - return -1; - } - else - { - if(!e.fire_burner) - { - // print("adding a fire burner to ", e.classname, "\n"); - e.fire_burner = new(fireburner); - setthink(e.fire_burner, fireburner_think); - e.fire_burner.nextthink = time; - e.fire_burner.owner = e; - } - } - - t = max(t, 0.1); - dps = d / t; - if(Fire_IsBurning(e)) - { - mintime = e.fire_endtime - time; - maxtime = max(mintime, t); - - mindps = e.fire_damagepersec; - maxdps = max(mindps, dps); - - if(maxtime > mintime || maxdps > mindps) - { - // Constraints: - - // damage we have right now - mindamage = mindps * mintime; - - // damage we want to get - maxdamage = mindamage + d; - - // but we can't exceed maxtime * maxdps! - totaldamage = min(maxdamage, maxtime * maxdps); - - // LEMMA: - // Look at: - // totaldamage = min(mindamage + d, maxtime * maxdps) - // We see: - // totaldamage <= maxtime * maxdps - // ==> totaldamage / maxdps <= maxtime. - // We also see: - // totaldamage / mindps = min(mindamage / mindps + d, maxtime * maxdps / mindps) - // >= min(mintime, maxtime) - // ==> totaldamage / maxdps >= mintime. - - /* - // how long do we damage then? - // at least as long as before - // but, never exceed maxdps - totaltime = max(mintime, totaldamage / maxdps); // always <= maxtime due to lemma - */ - - // alternate: - // at most as long as maximum allowed - // but, never below mindps - totaltime = min(maxtime, totaldamage / mindps); // always >= mintime due to lemma - - // assuming t > mintime, dps > mindps: - // we get d = t * dps = maxtime * maxdps - // totaldamage = min(maxdamage, maxtime * maxdps) = min(... + d, maxtime * maxdps) = maxtime * maxdps - // totaldamage / maxdps = maxtime - // totaldamage / mindps > totaldamage / maxdps = maxtime - // FROM THIS: - // a) totaltime = max(mintime, maxtime) = maxtime - // b) totaltime = min(maxtime, totaldamage / maxdps) = maxtime - - // assuming t <= mintime: - // we get maxtime = mintime - // a) totaltime = max(mintime, ...) >= mintime, also totaltime <= maxtime by the lemma, therefore totaltime = mintime = maxtime - // b) totaltime = min(maxtime, ...) <= maxtime, also totaltime >= mintime by the lemma, therefore totaltime = mintime = maxtime - - // assuming dps <= mindps: - // we get mindps = maxdps. - // With this, the lemma says that mintime <= totaldamage / mindps = totaldamage / maxdps <= maxtime. - // a) totaltime = max(mintime, totaldamage / maxdps) = totaldamage / maxdps - // b) totaltime = min(maxtime, totaldamage / mindps) = totaldamage / maxdps - - e.fire_damagepersec = totaldamage / totaltime; - e.fire_endtime = time + totaltime; - if(totaldamage > 1.2 * mindamage) - { - e.fire_deathtype = dt; - if(e.fire_owner != o) - { - e.fire_owner = o; - e.fire_hitsound = false; - } - } - if(accuracy_isgooddamage(o, e)) - accuracy_add(o, DEATH_WEAPONOF(dt).m_id, 0, max(0, totaldamage - mindamage)); - return max(0, totaldamage - mindamage); // can never be negative, but to make sure - } - else - return 0; - } - else - { - e.fire_damagepersec = dps; - e.fire_endtime = time + t; - e.fire_deathtype = dt; - e.fire_owner = o; - e.fire_hitsound = false; - if(accuracy_isgooddamage(o, e)) - accuracy_add(o, DEATH_WEAPONOF(dt).m_id, 0, d); - return d; - } -} - -void Fire_ApplyDamage(entity e) -{ - float t, d, hi, ty; - entity o; - - if (!Fire_IsBurning(e)) - return; - - for(t = 0, o = e.owner; o.owner && t < 16; o = o.owner, ++t); - if(IS_NOT_A_CLIENT(o)) - o = e.fire_owner; - - // water and slime stop fire - if(e.waterlevel) - if(e.watertype != CONTENT_LAVA) - e.fire_endtime = 0; - - // ice stops fire - if(STAT(FROZEN, e)) - e.fire_endtime = 0; - - t = min(frametime, e.fire_endtime - time); - d = e.fire_damagepersec * t; - - hi = e.fire_owner.damage_dealt; - ty = e.fire_owner.typehitsound; - Damage(e, e, e.fire_owner, d, e.fire_deathtype, DMG_NOWEP, e.origin, '0 0 0'); - if(e.fire_hitsound && e.fire_owner) - { - e.fire_owner.damage_dealt = hi; - e.fire_owner.typehitsound = ty; - } - e.fire_hitsound = true; - - 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); - d = autocvar_g_balance_firetransfer_damage * e.fire_damagepersec * t; - Fire_AddDamage(it, o, d, t, DEATH_FIRE.m_id); - } - }); - } - -} - -void Fire_ApplyEffect(entity e) -{ - if(Fire_IsBurning(e)) - e.effects |= EF_FLAME; - else - e.effects &= ~EF_FLAME; -} - -void fireburner_think(entity this) -{ - // for players, this is done in the regular loop - if(wasfreed(this.owner)) - { - delete(this); - return; - } - Fire_ApplyEffect(this.owner); - if(!Fire_IsBurning(this.owner)) - { - this.owner.fire_burner = NULL; - delete(this); - return; - } - Fire_ApplyDamage(this.owner); - this.nextthink = time; -}