3 #include <common/effects/all.qh>
6 #include "mutators/_mod.qh"
8 #include "spawnpoints.qh"
9 #include "../common/state.qh"
10 #include "../common/physics/player.qh"
11 #include "../common/t_items.qh"
12 #include "resources.qh"
13 #include "../common/vehicles/all.qh"
14 #include "../common/items/_mod.qh"
15 #include "../common/mutators/mutator/waypoints/waypointsprites.qh"
16 #include "weapons/accuracy.qh"
17 #include "weapons/csqcprojectile.qh"
18 #include "weapons/selection.qh"
19 #include "../common/constants.qh"
20 #include "../common/deathtypes/all.qh"
21 #include "../common/notifications/all.qh"
22 #include "../common/physics/movetypes/movetypes.qh"
23 #include "../common/playerstats.qh"
24 #include "../common/teams.qh"
25 #include "../common/util.qh"
26 #include <common/weapons/_all.qh>
27 #include "../lib/csqcmodel/sv_model.qh"
28 #include "../lib/warpzone/common.qh"
30 void UpdateFrags(entity player, int f)
32 GameRules_scoring_add_team(player, SCORE, f);
35 void GiveFrags (entity attacker, entity targ, float f, int deathtype, .entity weaponentity)
37 // TODO route through PlayerScores instead
38 if(game_stopped) return;
45 GameRules_scoring_add(attacker, SUICIDES, 1);
50 GameRules_scoring_add(attacker, TEAMKILLS, 1);
56 GameRules_scoring_add(attacker, KILLS, 1);
57 if(!warmup_stage && targ.playerid)
58 PlayerStats_GameReport_Event_Player(attacker, sprintf("kills-%d", targ.playerid), 1);
61 GameRules_scoring_add(targ, DEATHS, 1);
63 if(targ != attacker) // not for suicides
64 if(g_weaponarena_random)
66 // after a frag, exchange the current weapon (or the culprit, if detectable) by a new random weapon
67 Weapon culprit = DEATH_WEAPONOF(deathtype);
68 if(!culprit) culprit = attacker.(weaponentity).m_weapon;
69 else if(!(attacker.weapons & (culprit.m_wepset))) culprit = attacker.(weaponentity).m_weapon;
71 if(g_weaponarena_random_with_blaster && culprit == WEP_BLASTER) // WEAPONTODO: Shouldn't this be in a mutator?
77 if(!GiveFrags_randomweapons)
79 GiveFrags_randomweapons = new(GiveFrags_randomweapons);
83 GiveFrags_randomweapons.weapons = WARMUP_START_WEAPONS;
85 GiveFrags_randomweapons.weapons = start_weapons;
87 // all others (including the culprit): remove
88 GiveFrags_randomweapons.weapons &= ~attacker.weapons;
89 GiveFrags_randomweapons.weapons &= ~(culprit.m_wepset);
91 // among the remaining ones, choose one by random
92 W_RandomWeapons(GiveFrags_randomweapons, 1);
94 if(GiveFrags_randomweapons.weapons)
96 attacker.weapons |= GiveFrags_randomweapons.weapons;
97 attacker.weapons &= ~(culprit.m_wepset);
101 // after a frag, choose another random weapon set
102 if (!(attacker.weapons & WepSet_FromWeapon(attacker.(weaponentity).m_weapon)))
103 W_SwitchWeapon_Force(attacker, w_getbestweapon(attacker, weaponentity), weaponentity);
106 // FIXME fix the mess this is (we have REAL points now!)
107 if(MUTATOR_CALLHOOK(GiveFragsForKill, attacker, targ, f))
108 f = M_ARGV(2, float);
110 attacker.totalfrags += f;
113 UpdateFrags(attacker, f);
118 string AppendItemcodes(string s, entity player)
120 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
122 .entity weaponentity = weaponentities[slot];
123 int w = player.(weaponentity).m_weapon.m_id;
125 w = player.(weaponentity).cnt; // previous weapon
126 if(w != 0 || slot == 0)
127 s = strcat(s, ftos(w));
129 if(time < player.strength_finished)
131 if(time < player.invincible_finished)
133 if(player.flagcarried != NULL)
135 if(PHYS_INPUT_BUTTON_CHAT(player))
142 void LogDeath(string mode, int deathtype, entity killer, entity killed)
145 if(!autocvar_sv_eventlog)
147 s = strcat(":kill:", mode);
148 s = strcat(s, ":", ftos(killer.playerid));
149 s = strcat(s, ":", ftos(killed.playerid));
150 s = strcat(s, ":type=", Deathtype_Name(deathtype));
151 s = strcat(s, ":items=");
152 s = AppendItemcodes(s, killer);
155 s = strcat(s, ":victimitems=");
156 s = AppendItemcodes(s, killed);
161 void Obituary_SpecialDeath(
165 string s1, string s2, string s3,
166 float f1, float f2, float f3)
168 if(!DEATH_ISSPECIAL(deathtype))
170 backtrace("Obituary_SpecialDeath called without a special deathtype?\n");
174 entity deathent = Deathtypes_from(deathtype - DT_FIRST);
177 backtrace("Obituary_SpecialDeath: Could not find deathtype entity!\n");
181 if(g_cts && deathtype == DEATH_KILL.m_id)
182 return; // TODO: somehow put this in CTS gamemode file!
184 Notification death_message = (murder) ? deathent.death_msgmurder : deathent.death_msgself;
187 Send_Notification_WOCOVA(
195 Send_Notification_WOCOVA(
199 death_message.nent_msginfo,
206 float Obituary_WeaponDeath(
210 string s1, string s2, string s3,
213 Weapon death_weapon = DEATH_WEAPONOF(deathtype);
214 if (death_weapon == WEP_Null)
217 w_deathtype = deathtype;
218 Notification death_message = ((murder) ? death_weapon.wr_killmessage(death_weapon) : death_weapon.wr_suicidemessage(death_weapon));
223 Send_Notification_WOCOVA(
231 // send the info part to everyone
232 Send_Notification_WOCOVA(
236 death_message.nent_msginfo,
244 "Obituary_WeaponDeath(): ^1Deathtype ^7(%d)^1 has no notification for weapon %d!\n",
253 bool frag_centermessage_override(entity attacker, entity targ, int deathtype, int kill_count_to_attacker, int kill_count_to_target)
255 if(deathtype == DEATH_FIRE.m_id)
257 Send_Notification(NOTIF_ONE, attacker, MSG_CHOICE, CHOICE_FRAG_FIRE, targ.netname, kill_count_to_attacker, (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping));
258 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));
262 return MUTATOR_CALLHOOK(FragCenterMessage, attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target);
265 entity buff_FirstFromFlags(int _buffs);
266 void Obituary(entity attacker, entity inflictor, entity targ, int deathtype, .entity weaponentity)
269 if (!IS_PLAYER(targ)) { backtrace("Obituary called on non-player?!\n"); return; }
272 float notif_firstblood = false;
273 float kill_count_to_attacker, kill_count_to_target;
275 // Set final information for the death
276 targ.death_origin = targ.origin;
277 string deathlocation = (autocvar_notification_server_allows_location ? NearestLocation(targ.death_origin) : "");
279 #ifdef NOTIFICATIONS_DEBUG
282 "Obituary(%s, %s, %s, %s = %d);\n",
286 Deathtype_Name(deathtype),
297 if(DEATH_ISSPECIAL(deathtype))
299 if(deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id)
301 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", targ.team, 0, 0);
305 switch(DEATH_ENT(deathtype))
307 case DEATH_MIRRORDAMAGE:
309 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0);
315 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0);
321 else if (!Obituary_WeaponDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0))
323 backtrace("SUICIDE: what the hell happened here?\n");
326 LogDeath("suicide", deathtype, targ, targ);
327 if(deathtype != DEATH_AUTOTEAMCHANGE.m_id) // special case: don't negate frags if auto switched
328 GiveFrags(attacker, targ, -1, deathtype, weaponentity);
334 else if(IS_PLAYER(attacker))
336 if(SAME_TEAM(attacker, targ))
338 LogDeath("tk", deathtype, attacker, targ);
339 GiveFrags(attacker, targ, -1, deathtype, weaponentity);
341 CS(attacker).killcount = 0;
343 Send_Notification(NOTIF_ONE, attacker, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAG, targ.netname);
344 Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAGGED, attacker.netname);
345 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(targ.team, INFO_DEATH_TEAMKILL), targ.netname, attacker.netname, deathlocation, CS(targ).killcount);
347 // In this case, the death message will ALWAYS be "foo was betrayed by bar"
348 // No need for specific death/weapon messages...
352 LogDeath("frag", deathtype, attacker, targ);
353 GiveFrags(attacker, targ, 1, deathtype, weaponentity);
355 CS(attacker).taunt_soundtime = time + 1;
356 CS(attacker).killcount = CS(attacker).killcount + 1;
358 attacker.killsound += 1;
360 // TODO: improve SPREE_ITEM and KILL_SPREE_LIST
361 // these 2 macros are spread over multiple files
362 #define SPREE_ITEM(counta,countb,center,normal,gentle) \
365 Send_Notification(NOTIF_ONE, attacker, MSG_ANNCE, ANNCE_KILLSTREAK_##countb); \
368 PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_##counta, 1); \
372 switch(CS(attacker).killcount)
379 if(!warmup_stage && !checkrules_firstblood)
381 checkrules_firstblood = true;
382 notif_firstblood = true; // modify the current messages so that they too show firstblood information
383 PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD, 1);
384 PlayerStats_GameReport_Event_Player(targ, PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM, 1);
386 // tell spree_inf and spree_cen that this is a first-blood and first-victim event
387 kill_count_to_attacker = -1;
388 kill_count_to_target = -2;
392 kill_count_to_attacker = CS(attacker).killcount;
393 kill_count_to_target = 0;
404 kill_count_to_attacker,
405 (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping)
413 kill_count_to_target,
414 GetResourceAmount(attacker, RESOURCE_HEALTH),
415 GetResourceAmount(attacker, RESOURCE_ARMOR),
416 (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping)
419 else if(!frag_centermessage_override(attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target))
427 kill_count_to_attacker,
428 (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping)
436 kill_count_to_target,
437 GetResourceAmount(attacker, RESOURCE_HEALTH),
438 GetResourceAmount(attacker, RESOURCE_ARMOR),
439 (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping)
444 if(deathtype == DEATH_BUFF.m_id)
445 f3 = buff_FirstFromFlags(STAT(BUFFS, attacker)).m_id;
447 if (!Obituary_WeaponDeath(targ, true, deathtype, targ.netname, attacker.netname, deathlocation, CS(targ).killcount, kill_count_to_attacker))
448 Obituary_SpecialDeath(targ, true, deathtype, targ.netname, attacker.netname, deathlocation, CS(targ).killcount, kill_count_to_attacker, f3);
457 switch(DEATH_ENT(deathtype))
459 // For now, we're just forcing HURTTRIGGER to behave as "DEATH_VOID" and giving it no special options...
460 // Later on you will only be able to make custom messages using DEATH_CUSTOM,
461 // and there will be a REAL DEATH_VOID implementation which mappers will use.
462 case DEATH_HURTTRIGGER:
464 Obituary_SpecialDeath(targ, false, deathtype,
476 Obituary_SpecialDeath(targ, false, deathtype,
478 ((strstrofs(deathmessage, "%", 0) < 0) ? strcat("%s ", deathmessage) : deathmessage),
488 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0);
493 LogDeath("accident", deathtype, targ, targ);
494 GiveFrags(targ, targ, -1, deathtype, weaponentity);
496 if(GameRules_scoring_add(targ, SCORE, 0) == -5)
498 Send_Notification(NOTIF_ONE, targ, MSG_ANNCE, ANNCE_ACHIEVEMENT_BOTLIKE);
501 PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_BOTLIKE, 1);
506 // reset target kill count
507 CS(targ).killcount = 0;
510 void Ice_Think(entity this)
512 if(!STAT(FROZEN, this.owner) || this.owner.iceblock != this)
517 setorigin(this, this.owner.origin - '0 0 16');
518 this.nextthink = time;
521 void Freeze (entity targ, float freeze_time, float frozen_type, float show_waypoint)
523 if(!IS_PLAYER(targ) && !IS_MONSTER(targ)) // only specified entities can be freezed
526 if(STAT(FROZEN, targ))
529 float targ_maxhealth = ((IS_MONSTER(targ)) ? targ.max_health : start_health);
531 STAT(FROZEN, targ) = frozen_type;
532 STAT(REVIVE_PROGRESS, targ) = ((frozen_type == 3) ? 1 : 0);
533 SetResourceAmount(targ, RESOURCE_HEALTH, ((frozen_type == 3) ? targ_maxhealth : 1));
534 targ.revive_speed = freeze_time;
536 IL_REMOVE(g_bot_targets, targ);
537 targ.bot_attack = false;
539 entity ice = new(ice);
541 ice.scale = targ.scale;
542 setthink(ice, Ice_Think);
543 ice.nextthink = time;
544 ice.frame = floor(random() * 21); // ice model has 20 different looking frames
545 setmodel(ice, MDL_ICE);
547 ice.colormod = Team_ColorRGB(targ.team);
548 ice.glowmod = ice.colormod;
550 targ.revival_time = 0;
554 RemoveGrapplingHooks(targ);
556 FOREACH_CLIENT(IS_PLAYER(it),
558 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
560 .entity weaponentity = weaponentities[slot];
561 if(it.(weaponentity).hook.aiment == targ)
562 RemoveHook(it.(weaponentity).hook);
568 WaypointSprite_Spawn(WP_Frozen, 0, 0, targ, '0 0 64', NULL, targ.team, targ, waypointsprite_attached, true, RADARICON_WAYPOINT);
571 void Unfreeze (entity targ)
573 if(!STAT(FROZEN, targ))
576 if(STAT(FROZEN, targ) && STAT(FROZEN, targ) != 3) // only reset health if target was frozen
578 SetResourceAmount(targ, RESOURCE_HEALTH, ((IS_PLAYER(targ)) ? start_health : targ.max_health));
579 targ.pauseregen_finished = time + autocvar_g_balance_pause_health_regen;
582 STAT(FROZEN, targ) = 0;
583 STAT(REVIVE_PROGRESS, targ) = 0;
584 targ.revival_time = time;
586 IL_PUSH(g_bot_targets, targ);
587 targ.bot_attack = true;
589 WaypointSprite_Kill(targ.waypointsprite_attached);
591 FOREACH_CLIENT(IS_PLAYER(it),
593 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
595 .entity weaponentity = weaponentities[slot];
596 if(it.(weaponentity).hook.aiment == targ)
597 RemoveHook(it.(weaponentity).hook);
601 // remove the ice block
603 delete(targ.iceblock);
604 targ.iceblock = NULL;
607 void Damage (entity targ, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
609 float complainteamdamage = 0;
610 float mirrordamage = 0;
611 float mirrorforce = 0;
613 if (game_stopped || (IS_CLIENT(targ) && CS(targ).killcount == FRAGS_SPECTATOR))
616 entity attacker_save = attacker;
618 // special rule: gravity bomb does not hit team mates (other than for disconnecting the hook)
619 if(DEATH_ISWEAPON(deathtype, WEP_HOOK) || DEATH_ISWEAPON(deathtype, WEP_TUBA))
621 if(IS_PLAYER(targ) && SAME_TEAM(targ, attacker))
627 if(deathtype == DEATH_KILL.m_id || deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id)
629 // exit the vehicle before killing (fixes a crash)
630 if(IS_PLAYER(targ) && targ.vehicle)
631 vehicles_exit(targ.vehicle, VHEF_RELEASE);
633 // These are ALWAYS lethal
634 // No damage modification here
635 // Instead, prepare the victim for his death...
636 SetResourceAmount(targ, RESOURCE_ARMOR, 0);
637 targ.spawnshieldtime = 0;
638 SetResourceAmount(targ, RESOURCE_HEALTH, 0.9); // this is < 1
639 targ.flags -= targ.flags & FL_GODMODE;
642 else if(deathtype == DEATH_MIRRORDAMAGE.m_id || deathtype == DEATH_NOAMMO.m_id)
648 // nullify damage if teamplay is on
649 if(deathtype != DEATH_TELEFRAG.m_id)
650 if(IS_PLAYER(attacker))
652 if(IS_PLAYER(targ) && targ != attacker && (IS_INDEPENDENT_PLAYER(attacker) || IS_INDEPENDENT_PLAYER(targ)))
657 else if(SAME_TEAM(attacker, targ))
659 if(autocvar_teamplay_mode == 1)
661 else if(attacker != targ)
663 if(autocvar_teamplay_mode == 3)
665 else if(autocvar_teamplay_mode == 4)
667 if(IS_PLAYER(targ) && !IS_DEAD(targ))
669 attacker.dmg_team = attacker.dmg_team + damage;
670 complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold;
671 if(complainteamdamage > 0)
672 mirrordamage = autocvar_g_mirrordamage * complainteamdamage;
673 mirrorforce = autocvar_g_mirrordamage * vlen(force);
674 damage = autocvar_g_friendlyfire * damage;
675 // mirrordamage will be used LATER
677 if(autocvar_g_mirrordamage_virtual)
679 vector v = healtharmor_applydamage(attacker.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, mirrordamage);
680 attacker.dmg_take += v.x;
681 attacker.dmg_save += v.y;
682 attacker.dmg_inflictor = inflictor;
687 if(autocvar_g_friendlyfire_virtual)
689 vector v = healtharmor_applydamage(targ.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, damage);
690 targ.dmg_take += v.x;
691 targ.dmg_save += v.y;
692 targ.dmg_inflictor = inflictor;
694 if(!autocvar_g_friendlyfire_virtual_force)
698 else if(!targ.canteamdamage)
705 if (!DEATH_ISSPECIAL(deathtype))
707 damage *= g_weapondamagefactor;
708 mirrordamage *= g_weapondamagefactor;
709 complainteamdamage *= g_weapondamagefactor;
710 force = force * g_weaponforcefactor;
711 mirrorforce *= g_weaponforcefactor;
714 // should this be changed at all? If so, in what way?
715 MUTATOR_CALLHOOK(Damage_Calculate, inflictor, attacker, targ, deathtype, damage, mirrordamage, force);
716 damage = M_ARGV(4, float);
717 mirrordamage = M_ARGV(5, float);
718 force = M_ARGV(6, vector);
720 if(IS_PLAYER(targ) && damage > 0 && attacker)
722 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
724 .entity went = weaponentities[slot];
725 if(targ.(went).hook && targ.(went).hook.aiment == attacker)
726 RemoveHook(targ.(went).hook);
730 if(STAT(FROZEN, targ))
731 if(deathtype != DEATH_HURTTRIGGER.m_id && deathtype != DEATH_TEAMCHANGE.m_id && deathtype != DEATH_AUTOTEAMCHANGE.m_id)
733 if(autocvar_g_frozen_revive_falldamage > 0)
734 if(deathtype == DEATH_FALL.m_id)
735 if(damage >= autocvar_g_frozen_revive_falldamage)
738 SetResourceAmount(targ, RESOURCE_HEALTH, autocvar_g_frozen_revive_falldamage_health);
739 Send_Effect(EFFECT_ICEORGLASS, targ.origin, '0 0 0', 3);
740 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, targ.netname);
741 Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF);
745 force *= autocvar_g_frozen_force;
748 if(STAT(FROZEN, targ) && deathtype == DEATH_HURTTRIGGER.m_id && !autocvar_g_frozen_damage_trigger)
750 Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1);
752 entity spot = SelectSpawnPoint (targ, false);
757 targ.deadflag = DEAD_NO;
759 targ.angles = spot.angles;
762 targ.effects |= EF_TELEPORT_BIT;
764 targ.angles_z = 0; // never spawn tilted even if the spot says to
765 targ.fixangle = true; // turn this way immediately
766 targ.velocity = '0 0 0';
767 targ.avelocity = '0 0 0';
768 targ.punchangle = '0 0 0';
769 targ.punchvector = '0 0 0';
770 targ.oldvelocity = targ.velocity;
772 targ.spawnorigin = spot.origin;
773 setorigin(targ, spot.origin + '0 0 1' * (1 - targ.mins.z - 24));
774 // don't reset back to last position, even if new position is stuck in solid
775 targ.oldorigin = targ.origin;
777 Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1);
783 // apply strength multiplier
784 if (attacker.items & ITEM_Strength.m_itemid)
788 damage = damage * autocvar_g_balance_powerup_strength_selfdamage;
789 force = force * autocvar_g_balance_powerup_strength_selfforce;
793 damage = damage * autocvar_g_balance_powerup_strength_damage;
794 force = force * autocvar_g_balance_powerup_strength_force;
798 // apply invincibility multiplier
799 if (targ.items & ITEM_Shield.m_itemid)
801 damage = damage * autocvar_g_balance_powerup_invincible_takedamage;
802 if (targ != attacker)
804 force = force * autocvar_g_balance_powerup_invincible_takeforce;
809 if (targ == attacker)
810 damage = damage * autocvar_g_balance_selfdamagepercent; // Partial damage if the attacker hits himself
815 if(deathtype != DEATH_BUFF.m_id)
816 if(targ.takedamage == DAMAGE_AIM)
820 if(IS_VEHICLE(targ) && targ.owner)
825 if(IS_PLAYER(victim) || (IS_TURRET(victim) && victim.active == ACTIVE_ACTIVE) || IS_MONSTER(victim) || MUTATOR_CALLHOOK(PlayHitsound, victim, attacker))
827 if(DIFF_TEAM(victim, attacker) && !STAT(FROZEN, victim))
831 if(deathtype != DEATH_FIRE.m_id)
833 if(PHYS_INPUT_BUTTON_CHAT(victim))
834 attacker.typehitsound += 1;
836 attacker.damage_dealt += damage;
839 damage_goodhits += 1;
840 damage_gooddamage += damage;
842 if (!DEATH_ISSPECIAL(deathtype))
844 if(IS_PLAYER(targ)) // don't do this for vehicles
850 else if(IS_PLAYER(attacker))
852 if(deathtype != DEATH_FIRE.m_id)
854 attacker.typehitsound += 1;
856 if(complainteamdamage > 0)
857 if(time > CS(attacker).teamkill_complain)
859 CS(attacker).teamkill_complain = time + 5;
860 CS(attacker).teamkill_soundtime = time + 0.4;
861 CS(attacker).teamkill_soundsource = targ;
869 if (targ.damageforcescale)
871 if (!IS_PLAYER(targ) || time >= targ.spawnshieldtime || targ == attacker)
873 vector farce = damage_explosion_calcpush(targ.damageforcescale * force, targ.velocity, autocvar_g_balance_damagepush_speedfactor);
874 if(targ.move_movetype == MOVETYPE_PHYSICS)
876 entity farcent = new(farce);
877 farcent.enemy = targ;
878 farcent.movedir = farce * 10;
880 farcent.movedir = farcent.movedir * targ.mass;
881 farcent.origin = hitloc;
882 farcent.forcetype = FORCETYPE_FORCEATPOS;
883 farcent.nextthink = time + 0.1;
884 setthink(farcent, SUB_Remove);
888 targ.velocity = targ.velocity + farce;
890 UNSET_ONGROUND(targ);
891 UpdateCSQCProjectile(targ);
894 if (damage != 0 || (targ.damageforcescale && force))
895 if (targ.event_damage)
896 targ.event_damage (targ, inflictor, attacker, damage, deathtype, weaponentity, hitloc, force);
898 // apply mirror damage if any
899 if(!autocvar_g_mirrordamage_onlyweapons || DEATH_WEAPONOF(deathtype) != WEP_Null)
900 if(mirrordamage > 0 || mirrorforce > 0)
902 attacker = attacker_save;
904 force = normalize(attacker.origin + attacker.view_ofs - hitloc) * mirrorforce;
905 Damage(attacker, inflictor, attacker, mirrordamage, DEATH_MIRRORDAMAGE.m_id, weaponentity, attacker.origin, force);
909 float RadiusDamageForSource (entity inflictor, vector inflictororigin, vector inflictorvelocity, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe,
910 float inflictorselfdamage, float forceintensity, int deathtype, .entity weaponentity, entity directhitentity)
911 // Returns total damage applies to creatures
915 float total_damage_to_creatures;
920 float stat_damagedone;
922 if(RadiusDamage_running)
924 backtrace("RadiusDamage called recursively! Expect stuff to go HORRIBLY wrong.");
928 RadiusDamage_running = 1;
930 tfloordmg = autocvar_g_throughfloor_damage;
931 tfloorforce = autocvar_g_throughfloor_force;
933 total_damage_to_creatures = 0;
935 if(deathtype != (WEP_HOOK.m_id | HITTYPE_SECONDARY | HITTYPE_BOUNCE)) // only send gravity bomb damage once
936 if(DEATH_WEAPONOF(deathtype) != WEP_TUBA) // do not send tuba damage (bandwidth hog)
938 force = inflictorvelocity;
942 force = normalize(force);
943 if(forceintensity >= 0)
944 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, rad, forceintensity * force, deathtype, 0, attacker);
946 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, -rad, (-forceintensity) * force, deathtype, 0, attacker);
951 targ = WarpZone_FindRadius (inflictororigin, rad + MAX_DAMAGEEXTRARADIUS, false);
955 if ((targ != inflictor) || inflictorselfdamage)
956 if (((cantbe != targ) && !mustbe) || (mustbe == targ))
963 // LordHavoc: measure distance to nearest point on target (not origin)
964 // (this guarentees 100% damage on a touch impact)
965 nearest = targ.WarpZone_findradius_nearest;
966 diff = targ.WarpZone_findradius_dist;
967 // round up a little on the damage to ensure full damage on impacts
968 // and turn the distance into a fraction of the radius
969 power = 1 - ((vlen (diff) - bound(MIN_DAMAGEEXTRARADIUS, targ.damageextraradius, MAX_DAMAGEEXTRARADIUS)) / rad);
971 //bprint(ftos(power));
972 //if (targ == attacker)
973 // print(ftos(power), "\n");
979 finaldmg = coredamage * power + edgedamage * (1 - power);
985 vector myblastorigin;
988 myblastorigin = WarpZone_TransformOrigin(targ, inflictororigin);
990 // if it's a player, use the view origin as reference
991 center = CENTER_OR_VIEWOFS(targ);
993 force = normalize(center - myblastorigin);
994 force = force * (finaldmg / coredamage) * forceintensity;
997 if(deathtype & WEP_BLASTER.m_id)
998 force *= WEP_CVAR_BOTH(blaster, !(deathtype & HITTYPE_SECONDARY), force_zscale);
1000 if(targ != directhitentity)
1005 float mininv_f, mininv_d;
1007 // test line of sight to multiple positions on box,
1008 // and do damage if any of them hit
1011 // we know: max stddev of hitratio = 1 / (2 * sqrt(n))
1012 // so for a given max stddev:
1013 // n = (1 / (2 * max stddev of hitratio))^2
1015 mininv_d = (finaldmg * (1-tfloordmg)) / autocvar_g_throughfloor_damage_max_stddev;
1016 mininv_f = (vlen(force) * (1-tfloorforce)) / autocvar_g_throughfloor_force_max_stddev;
1018 if(autocvar_g_throughfloor_debug)
1019 LOG_INFOF("THROUGHFLOOR: D=%f F=%f max(dD)=1/%f max(dF)=1/%f", finaldmg, vlen(force), mininv_d, mininv_f);
1022 total = 0.25 * (max(mininv_f, mininv_d) ** 2);
1024 if(autocvar_g_throughfloor_debug)
1025 LOG_INFOF(" steps=%f", total);
1028 if (IS_PLAYER(targ))
1029 total = ceil(bound(autocvar_g_throughfloor_min_steps_player, total, autocvar_g_throughfloor_max_steps_player));
1031 total = ceil(bound(autocvar_g_throughfloor_min_steps_other, total, autocvar_g_throughfloor_max_steps_other));
1033 if(autocvar_g_throughfloor_debug)
1034 LOG_INFOF(" steps=%f dD=%f dF=%f", total, finaldmg * (1-tfloordmg) / (2 * sqrt(total)), vlen(force) * (1-tfloorforce) / (2 * sqrt(total)));
1036 for(c = 0; c < total; ++c)
1038 //traceline(targ.WarpZone_findradius_findorigin, nearest, MOVE_NOMONSTERS, inflictor);
1039 WarpZone_TraceLine(inflictororigin, WarpZone_UnTransformOrigin(targ, nearest), MOVE_NOMONSTERS, inflictor);
1040 if (trace_fraction == 1 || trace_ent == targ)
1044 hitloc = hitloc + nearest;
1048 nearest.x = targ.origin.x + targ.mins.x + random() * targ.size.x;
1049 nearest.y = targ.origin.y + targ.mins.y + random() * targ.size.y;
1050 nearest.z = targ.origin.z + targ.mins.z + random() * targ.size.z;
1053 nearest = hitloc * (1 / max(1, hits));
1054 hitratio = (hits / total);
1055 a = bound(0, tfloordmg + (1-tfloordmg) * hitratio, 1);
1056 finaldmg = finaldmg * a;
1057 a = bound(0, tfloorforce + (1-tfloorforce) * hitratio, 1);
1060 if(autocvar_g_throughfloor_debug)
1061 LOG_INFOF(" D=%f F=%f", finaldmg, vlen(force));
1064 //if (targ == attacker)
1066 // print("hits ", ftos(hits), " / ", ftos(total));
1067 // print(" finaldmg ", ftos(finaldmg), " force ", vtos(force));
1068 // print(" (", ftos(a), ")\n");
1070 if(finaldmg || force)
1074 total_damage_to_creatures += finaldmg;
1076 if(accuracy_isgooddamage(attacker, targ))
1077 stat_damagedone += finaldmg;
1080 if(targ == directhitentity || DEATH_ISSPECIAL(deathtype))
1081 Damage (targ, inflictor, attacker, finaldmg, deathtype, weaponentity, nearest, force);
1083 Damage (targ, inflictor, attacker, finaldmg, deathtype | HITTYPE_SPLASH, weaponentity, nearest, force);
1091 RadiusDamage_running = 0;
1093 if(!DEATH_ISSPECIAL(deathtype))
1094 accuracy_add(attacker, DEATH_WEAPONOF(deathtype).m_id, 0, min(coredamage, stat_damagedone));
1096 return total_damage_to_creatures;
1099 float RadiusDamage (entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float forceintensity, int deathtype, .entity weaponentity, entity directhitentity)
1101 return RadiusDamageForSource (inflictor, (inflictor.origin + (inflictor.mins + inflictor.maxs) * 0.5), inflictor.velocity, attacker, coredamage, edgedamage, rad, cantbe, mustbe, false, forceintensity, deathtype, weaponentity, directhitentity);
1104 float Fire_IsBurning(entity e)
1106 return (time < e.fire_endtime);
1109 float Fire_AddDamage(entity e, entity o, float d, float t, float dt)
1112 float maxtime, mintime, maxdamage, mindamage, maxdps, mindps, totaldamage, totaltime;
1123 // print("adding a fire burner to ", e.classname, "\n");
1124 e.fire_burner = new(fireburner);
1125 setthink(e.fire_burner, fireburner_think);
1126 e.fire_burner.nextthink = time;
1127 e.fire_burner.owner = e;
1133 if(Fire_IsBurning(e))
1135 mintime = e.fire_endtime - time;
1136 maxtime = max(mintime, t);
1138 mindps = e.fire_damagepersec;
1139 maxdps = max(mindps, dps);
1141 if(maxtime > mintime || maxdps > mindps)
1145 // damage we have right now
1146 mindamage = mindps * mintime;
1148 // damage we want to get
1149 maxdamage = mindamage + d;
1151 // but we can't exceed maxtime * maxdps!
1152 totaldamage = min(maxdamage, maxtime * maxdps);
1156 // totaldamage = min(mindamage + d, maxtime * maxdps)
1158 // totaldamage <= maxtime * maxdps
1159 // ==> totaldamage / maxdps <= maxtime.
1161 // totaldamage / mindps = min(mindamage / mindps + d, maxtime * maxdps / mindps)
1162 // >= min(mintime, maxtime)
1163 // ==> totaldamage / maxdps >= mintime.
1166 // how long do we damage then?
1167 // at least as long as before
1168 // but, never exceed maxdps
1169 totaltime = max(mintime, totaldamage / maxdps); // always <= maxtime due to lemma
1173 // at most as long as maximum allowed
1174 // but, never below mindps
1175 totaltime = min(maxtime, totaldamage / mindps); // always >= mintime due to lemma
1177 // assuming t > mintime, dps > mindps:
1178 // we get d = t * dps = maxtime * maxdps
1179 // totaldamage = min(maxdamage, maxtime * maxdps) = min(... + d, maxtime * maxdps) = maxtime * maxdps
1180 // totaldamage / maxdps = maxtime
1181 // totaldamage / mindps > totaldamage / maxdps = maxtime
1183 // a) totaltime = max(mintime, maxtime) = maxtime
1184 // b) totaltime = min(maxtime, totaldamage / maxdps) = maxtime
1186 // assuming t <= mintime:
1187 // we get maxtime = mintime
1188 // a) totaltime = max(mintime, ...) >= mintime, also totaltime <= maxtime by the lemma, therefore totaltime = mintime = maxtime
1189 // b) totaltime = min(maxtime, ...) <= maxtime, also totaltime >= mintime by the lemma, therefore totaltime = mintime = maxtime
1191 // assuming dps <= mindps:
1192 // we get mindps = maxdps.
1193 // With this, the lemma says that mintime <= totaldamage / mindps = totaldamage / maxdps <= maxtime.
1194 // a) totaltime = max(mintime, totaldamage / maxdps) = totaldamage / maxdps
1195 // b) totaltime = min(maxtime, totaldamage / mindps) = totaldamage / maxdps
1197 e.fire_damagepersec = totaldamage / totaltime;
1198 e.fire_endtime = time + totaltime;
1199 if(totaldamage > 1.2 * mindamage)
1201 e.fire_deathtype = dt;
1202 if(e.fire_owner != o)
1205 e.fire_hitsound = false;
1208 if(accuracy_isgooddamage(o, e))
1209 accuracy_add(o, DEATH_WEAPONOF(dt).m_id, 0, max(0, totaldamage - mindamage));
1210 return max(0, totaldamage - mindamage); // can never be negative, but to make sure
1217 e.fire_damagepersec = dps;
1218 e.fire_endtime = time + t;
1219 e.fire_deathtype = dt;
1221 e.fire_hitsound = false;
1222 if(accuracy_isgooddamage(o, e))
1223 accuracy_add(o, DEATH_WEAPONOF(dt).m_id, 0, d);
1228 void Fire_ApplyDamage(entity e)
1233 if (!Fire_IsBurning(e))
1236 for(t = 0, o = e.owner; o.owner && t < 16; o = o.owner, ++t);
1237 if(IS_NOT_A_CLIENT(o))
1240 // water and slime stop fire
1242 if(e.watertype != CONTENT_LAVA)
1249 t = min(frametime, e.fire_endtime - time);
1250 d = e.fire_damagepersec * t;
1252 hi = e.fire_owner.damage_dealt;
1253 ty = e.fire_owner.typehitsound;
1254 Damage(e, e, e.fire_owner, d, e.fire_deathtype, DMG_NOWEP, e.origin, '0 0 0');
1255 if(e.fire_hitsound && e.fire_owner)
1257 e.fire_owner.damage_dealt = hi;
1258 e.fire_owner.typehitsound = ty;
1260 e.fire_hitsound = true;
1262 if(!IS_INDEPENDENT_PLAYER(e))
1263 if(!STAT(FROZEN, e))
1264 FOREACH_CLIENT(IS_PLAYER(it) && it != e, {
1266 if(!IS_INDEPENDENT_PLAYER(it))
1267 if(boxesoverlap(e.absmin, e.absmax, it.absmin, it.absmax))
1269 t = autocvar_g_balance_firetransfer_time * (e.fire_endtime - time);
1270 d = autocvar_g_balance_firetransfer_damage * e.fire_damagepersec * t;
1271 Fire_AddDamage(it, o, d, t, DEATH_FIRE.m_id);
1276 void Fire_ApplyEffect(entity e)
1278 if(Fire_IsBurning(e))
1279 e.effects |= EF_FLAME;
1281 e.effects &= ~EF_FLAME;
1284 void fireburner_think(entity this)
1286 // for players, this is done in the regular loop
1287 if(wasfreed(this.owner))
1292 Fire_ApplyEffect(this.owner);
1293 if(!Fire_IsBurning(this.owner))
1295 this.owner.fire_burner = NULL;
1299 Fire_ApplyDamage(this.owner);
1300 this.nextthink = time;