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 .int buffs = _STAT(BUFFS); // TODO: remove
266 entity buff_FirstFromFlags(int _buffs);
267 void Obituary(entity attacker, entity inflictor, entity targ, int deathtype, .entity weaponentity)
270 if (!IS_PLAYER(targ)) { backtrace("Obituary called on non-player?!\n"); return; }
273 float notif_firstblood = false;
274 float kill_count_to_attacker, kill_count_to_target;
276 // Set final information for the death
277 targ.death_origin = targ.origin;
278 string deathlocation = (autocvar_notification_server_allows_location ? NearestLocation(targ.death_origin) : "");
280 #ifdef NOTIFICATIONS_DEBUG
283 "Obituary(%s, %s, %s, %s = %d);\n",
287 Deathtype_Name(deathtype),
298 if(DEATH_ISSPECIAL(deathtype))
300 if(deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id)
302 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", targ.team, 0, 0);
306 switch(DEATH_ENT(deathtype))
308 case DEATH_MIRRORDAMAGE:
310 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0);
316 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0);
322 else if (!Obituary_WeaponDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0))
324 backtrace("SUICIDE: what the hell happened here?\n");
327 LogDeath("suicide", deathtype, targ, targ);
328 if(deathtype != DEATH_AUTOTEAMCHANGE.m_id) // special case: don't negate frags if auto switched
329 GiveFrags(attacker, targ, -1, deathtype, weaponentity);
335 else if(IS_PLAYER(attacker))
337 if(SAME_TEAM(attacker, targ))
339 LogDeath("tk", deathtype, attacker, targ);
340 GiveFrags(attacker, targ, -1, deathtype, weaponentity);
342 CS(attacker).killcount = 0;
344 Send_Notification(NOTIF_ONE, attacker, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAG, targ.netname);
345 Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAGGED, attacker.netname);
346 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(targ.team, INFO_DEATH_TEAMKILL), targ.netname, attacker.netname, deathlocation, CS(targ).killcount);
348 // In this case, the death message will ALWAYS be "foo was betrayed by bar"
349 // No need for specific death/weapon messages...
353 LogDeath("frag", deathtype, attacker, targ);
354 GiveFrags(attacker, targ, 1, deathtype, weaponentity);
356 CS(attacker).taunt_soundtime = time + 1;
357 CS(attacker).killcount = CS(attacker).killcount + 1;
359 attacker.killsound += 1;
361 // TODO: improve SPREE_ITEM and KILL_SPREE_LIST
362 // these 2 macros are spread over multiple files
363 #define SPREE_ITEM(counta,countb,center,normal,gentle) \
366 Send_Notification(NOTIF_ONE, attacker, MSG_ANNCE, ANNCE_KILLSTREAK_##countb); \
369 PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_##counta, 1); \
373 switch(CS(attacker).killcount)
380 if(!warmup_stage && !checkrules_firstblood)
382 checkrules_firstblood = true;
383 notif_firstblood = true; // modify the current messages so that they too show firstblood information
384 PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD, 1);
385 PlayerStats_GameReport_Event_Player(targ, PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM, 1);
387 // tell spree_inf and spree_cen that this is a first-blood and first-victim event
388 kill_count_to_attacker = -1;
389 kill_count_to_target = -2;
393 kill_count_to_attacker = CS(attacker).killcount;
394 kill_count_to_target = 0;
405 kill_count_to_attacker,
406 (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping)
414 kill_count_to_target,
415 GetResourceAmount(attacker, RESOURCE_HEALTH),
416 GetResourceAmount(attacker, RESOURCE_ARMOR),
417 (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping)
420 else if(!frag_centermessage_override(attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target))
428 kill_count_to_attacker,
429 (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping)
437 kill_count_to_target,
438 GetResourceAmount(attacker, RESOURCE_HEALTH),
439 GetResourceAmount(attacker, RESOURCE_ARMOR),
440 (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping)
445 if(deathtype == DEATH_BUFF.m_id)
446 f3 = buff_FirstFromFlags(attacker.buffs).m_id;
448 if (!Obituary_WeaponDeath(targ, true, deathtype, targ.netname, attacker.netname, deathlocation, CS(targ).killcount, kill_count_to_attacker))
449 Obituary_SpecialDeath(targ, true, deathtype, targ.netname, attacker.netname, deathlocation, CS(targ).killcount, kill_count_to_attacker, f3);
458 switch(DEATH_ENT(deathtype))
460 // For now, we're just forcing HURTTRIGGER to behave as "DEATH_VOID" and giving it no special options...
461 // Later on you will only be able to make custom messages using DEATH_CUSTOM,
462 // and there will be a REAL DEATH_VOID implementation which mappers will use.
463 case DEATH_HURTTRIGGER:
465 Obituary_SpecialDeath(targ, false, deathtype,
477 Obituary_SpecialDeath(targ, false, deathtype,
479 ((strstrofs(deathmessage, "%", 0) < 0) ? strcat("%s ", deathmessage) : deathmessage),
489 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0);
494 LogDeath("accident", deathtype, targ, targ);
495 GiveFrags(targ, targ, -1, deathtype, weaponentity);
497 if(GameRules_scoring_add(targ, SCORE, 0) == -5)
499 Send_Notification(NOTIF_ONE, targ, MSG_ANNCE, ANNCE_ACHIEVEMENT_BOTLIKE);
502 PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_BOTLIKE, 1);
507 // reset target kill count
508 CS(targ).killcount = 0;
511 void Ice_Think(entity this)
513 if(!STAT(FROZEN, this.owner) || this.owner.iceblock != this)
518 setorigin(this, this.owner.origin - '0 0 16');
519 this.nextthink = time;
522 void Freeze (entity targ, float freeze_time, float frozen_type, float show_waypoint)
524 if(!IS_PLAYER(targ) && !IS_MONSTER(targ)) // only specified entities can be freezed
527 if(STAT(FROZEN, targ))
530 float targ_maxhealth = ((IS_MONSTER(targ)) ? targ.max_health : start_health);
532 STAT(FROZEN, targ) = frozen_type;
533 targ.revive_progress = ((frozen_type == 3) ? 1 : 0);
534 SetResourceAmount(targ, RESOURCE_HEALTH, ((frozen_type == 3) ? targ_maxhealth : 1));
535 targ.revive_speed = freeze_time;
537 IL_REMOVE(g_bot_targets, targ);
538 targ.bot_attack = false;
540 entity ice = new(ice);
542 ice.scale = targ.scale;
543 setthink(ice, Ice_Think);
544 ice.nextthink = time;
545 ice.frame = floor(random() * 21); // ice model has 20 different looking frames
546 setmodel(ice, MDL_ICE);
548 ice.colormod = Team_ColorRGB(targ.team);
549 ice.glowmod = ice.colormod;
551 targ.revival_time = 0;
555 RemoveGrapplingHooks(targ);
557 FOREACH_CLIENT(IS_PLAYER(it),
559 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
561 .entity weaponentity = weaponentities[slot];
562 if(it.(weaponentity).hook.aiment == targ)
563 RemoveHook(it.(weaponentity).hook);
569 WaypointSprite_Spawn(WP_Frozen, 0, 0, targ, '0 0 64', NULL, targ.team, targ, waypointsprite_attached, true, RADARICON_WAYPOINT);
572 void Unfreeze (entity targ)
574 if(!STAT(FROZEN, targ))
577 if(STAT(FROZEN, targ) && STAT(FROZEN, targ) != 3) // only reset health if target was frozen
579 SetResourceAmount(targ, RESOURCE_HEALTH, ((IS_PLAYER(targ)) ? start_health : targ.max_health));
580 targ.pauseregen_finished = time + autocvar_g_balance_pause_health_regen;
583 STAT(FROZEN, targ) = 0;
584 targ.revive_progress = 0;
585 targ.revival_time = time;
587 IL_PUSH(g_bot_targets, targ);
588 targ.bot_attack = true;
590 WaypointSprite_Kill(targ.waypointsprite_attached);
592 FOREACH_CLIENT(IS_PLAYER(it),
594 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
596 .entity weaponentity = weaponentities[slot];
597 if(it.(weaponentity).hook.aiment == targ)
598 RemoveHook(it.(weaponentity).hook);
602 // remove the ice block
604 delete(targ.iceblock);
605 targ.iceblock = NULL;
608 void Damage (entity targ, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
610 float complainteamdamage = 0;
611 float mirrordamage = 0;
612 float mirrorforce = 0;
614 if (game_stopped || (IS_CLIENT(targ) && CS(targ).killcount == FRAGS_SPECTATOR))
617 entity attacker_save = attacker;
619 // special rule: gravity bomb does not hit team mates (other than for disconnecting the hook)
620 if(DEATH_ISWEAPON(deathtype, WEP_HOOK) || DEATH_ISWEAPON(deathtype, WEP_TUBA))
622 if(IS_PLAYER(targ) && SAME_TEAM(targ, attacker))
628 if(deathtype == DEATH_KILL.m_id || deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id)
630 // exit the vehicle before killing (fixes a crash)
631 if(IS_PLAYER(targ) && targ.vehicle)
632 vehicles_exit(targ.vehicle, VHEF_RELEASE);
634 // These are ALWAYS lethal
635 // No damage modification here
636 // Instead, prepare the victim for his death...
637 SetResourceAmount(targ, RESOURCE_ARMOR, 0);
638 targ.spawnshieldtime = 0;
639 SetResourceAmount(targ, RESOURCE_HEALTH, 0.9); // this is < 1
640 targ.flags -= targ.flags & FL_GODMODE;
643 else if(deathtype == DEATH_MIRRORDAMAGE.m_id || deathtype == DEATH_NOAMMO.m_id)
649 // nullify damage if teamplay is on
650 if(deathtype != DEATH_TELEFRAG.m_id)
651 if(IS_PLAYER(attacker))
653 if(IS_PLAYER(targ) && targ != attacker && (IS_INDEPENDENT_PLAYER(attacker) || IS_INDEPENDENT_PLAYER(targ)))
658 else if(SAME_TEAM(attacker, targ))
660 if(autocvar_teamplay_mode == 1)
662 else if(attacker != targ)
664 if(autocvar_teamplay_mode == 3)
666 else if(autocvar_teamplay_mode == 4)
668 if(IS_PLAYER(targ) && !IS_DEAD(targ))
670 attacker.dmg_team = attacker.dmg_team + damage;
671 complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold;
672 if(complainteamdamage > 0)
673 mirrordamage = autocvar_g_mirrordamage * complainteamdamage;
674 mirrorforce = autocvar_g_mirrordamage * vlen(force);
675 damage = autocvar_g_friendlyfire * damage;
676 // mirrordamage will be used LATER
678 if(autocvar_g_mirrordamage_virtual)
680 vector v = healtharmor_applydamage(attacker.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, mirrordamage);
681 attacker.dmg_take += v.x;
682 attacker.dmg_save += v.y;
683 attacker.dmg_inflictor = inflictor;
688 if(autocvar_g_friendlyfire_virtual)
690 vector v = healtharmor_applydamage(targ.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, damage);
691 targ.dmg_take += v.x;
692 targ.dmg_save += v.y;
693 targ.dmg_inflictor = inflictor;
695 if(!autocvar_g_friendlyfire_virtual_force)
699 else if(!targ.canteamdamage)
706 if (!DEATH_ISSPECIAL(deathtype))
708 damage *= g_weapondamagefactor;
709 mirrordamage *= g_weapondamagefactor;
710 complainteamdamage *= g_weapondamagefactor;
711 force = force * g_weaponforcefactor;
712 mirrorforce *= g_weaponforcefactor;
715 // should this be changed at all? If so, in what way?
716 MUTATOR_CALLHOOK(Damage_Calculate, inflictor, attacker, targ, deathtype, damage, mirrordamage, force);
717 damage = M_ARGV(4, float);
718 mirrordamage = M_ARGV(5, float);
719 force = M_ARGV(6, vector);
721 if(IS_PLAYER(targ) && damage > 0 && attacker)
723 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
725 .entity went = weaponentities[slot];
726 if(targ.(went).hook && targ.(went).hook.aiment == attacker)
727 RemoveHook(targ.(went).hook);
731 if(STAT(FROZEN, targ))
732 if(deathtype != DEATH_HURTTRIGGER.m_id && deathtype != DEATH_TEAMCHANGE.m_id && deathtype != DEATH_AUTOTEAMCHANGE.m_id)
734 if(autocvar_g_frozen_revive_falldamage > 0)
735 if(deathtype == DEATH_FALL.m_id)
736 if(damage >= autocvar_g_frozen_revive_falldamage)
739 SetResourceAmount(targ, RESOURCE_HEALTH, autocvar_g_frozen_revive_falldamage_health);
740 Send_Effect(EFFECT_ICEORGLASS, targ.origin, '0 0 0', 3);
741 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, targ.netname);
742 Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF);
746 force *= autocvar_g_frozen_force;
749 if(STAT(FROZEN, targ) && deathtype == DEATH_HURTTRIGGER.m_id && !autocvar_g_frozen_damage_trigger)
751 Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1);
753 entity spot = SelectSpawnPoint (targ, false);
758 targ.deadflag = DEAD_NO;
760 targ.angles = spot.angles;
763 targ.effects |= EF_TELEPORT_BIT;
765 targ.angles_z = 0; // never spawn tilted even if the spot says to
766 targ.fixangle = true; // turn this way immediately
767 targ.velocity = '0 0 0';
768 targ.avelocity = '0 0 0';
769 targ.punchangle = '0 0 0';
770 targ.punchvector = '0 0 0';
771 targ.oldvelocity = targ.velocity;
773 targ.spawnorigin = spot.origin;
774 setorigin(targ, spot.origin + '0 0 1' * (1 - targ.mins.z - 24));
775 // don't reset back to last position, even if new position is stuck in solid
776 targ.oldorigin = targ.origin;
778 Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1);
784 // apply strength multiplier
785 if (attacker.items & ITEM_Strength.m_itemid)
789 damage = damage * autocvar_g_balance_powerup_strength_selfdamage;
790 force = force * autocvar_g_balance_powerup_strength_selfforce;
794 damage = damage * autocvar_g_balance_powerup_strength_damage;
795 force = force * autocvar_g_balance_powerup_strength_force;
799 // apply invincibility multiplier
800 if (targ.items & ITEM_Shield.m_itemid)
802 damage = damage * autocvar_g_balance_powerup_invincible_takedamage;
803 if (targ != attacker)
805 force = force * autocvar_g_balance_powerup_invincible_takeforce;
810 if (targ == attacker)
811 damage = damage * autocvar_g_balance_selfdamagepercent; // Partial damage if the attacker hits himself
816 if(deathtype != DEATH_BUFF.m_id)
817 if(targ.takedamage == DAMAGE_AIM)
821 if(IS_VEHICLE(targ) && targ.owner)
826 if(IS_PLAYER(victim) || (IS_TURRET(victim) && victim.active == ACTIVE_ACTIVE) || IS_MONSTER(victim) || MUTATOR_CALLHOOK(PlayHitsound, victim, attacker))
828 if(DIFF_TEAM(victim, attacker) && !STAT(FROZEN, victim))
832 if(deathtype != DEATH_FIRE.m_id)
834 if(PHYS_INPUT_BUTTON_CHAT(victim))
835 attacker.typehitsound += 1;
837 attacker.damage_dealt += damage;
840 damage_goodhits += 1;
841 damage_gooddamage += damage;
843 if (!DEATH_ISSPECIAL(deathtype))
845 if(IS_PLAYER(targ)) // don't do this for vehicles
851 else if(IS_PLAYER(attacker))
853 if(deathtype != DEATH_FIRE.m_id)
855 attacker.typehitsound += 1;
857 if(complainteamdamage > 0)
858 if(time > CS(attacker).teamkill_complain)
860 CS(attacker).teamkill_complain = time + 5;
861 CS(attacker).teamkill_soundtime = time + 0.4;
862 CS(attacker).teamkill_soundsource = targ;
870 if (targ.damageforcescale)
872 if (!IS_PLAYER(targ) || time >= targ.spawnshieldtime || targ == attacker)
874 vector farce = damage_explosion_calcpush(targ.damageforcescale * force, targ.velocity, autocvar_g_balance_damagepush_speedfactor);
875 if(targ.move_movetype == MOVETYPE_PHYSICS)
877 entity farcent = new(farce);
878 farcent.enemy = targ;
879 farcent.movedir = farce * 10;
881 farcent.movedir = farcent.movedir * targ.mass;
882 farcent.origin = hitloc;
883 farcent.forcetype = FORCETYPE_FORCEATPOS;
884 farcent.nextthink = time + 0.1;
885 setthink(farcent, SUB_Remove);
889 targ.velocity = targ.velocity + farce;
891 UNSET_ONGROUND(targ);
892 UpdateCSQCProjectile(targ);
895 if (damage != 0 || (targ.damageforcescale && force))
896 if (targ.event_damage)
897 targ.event_damage (targ, inflictor, attacker, damage, deathtype, weaponentity, hitloc, force);
899 // apply mirror damage if any
900 if(!autocvar_g_mirrordamage_onlyweapons || DEATH_WEAPONOF(deathtype) != WEP_Null)
901 if(mirrordamage > 0 || mirrorforce > 0)
903 attacker = attacker_save;
905 force = normalize(attacker.origin + attacker.view_ofs - hitloc) * mirrorforce;
906 Damage(attacker, inflictor, attacker, mirrordamage, DEATH_MIRRORDAMAGE.m_id, weaponentity, attacker.origin, force);
910 float RadiusDamageForSource (entity inflictor, vector inflictororigin, vector inflictorvelocity, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe,
911 float inflictorselfdamage, float forceintensity, int deathtype, .entity weaponentity, entity directhitentity)
912 // Returns total damage applies to creatures
916 float total_damage_to_creatures;
921 float stat_damagedone;
923 if(RadiusDamage_running)
925 backtrace("RadiusDamage called recursively! Expect stuff to go HORRIBLY wrong.");
929 RadiusDamage_running = 1;
931 tfloordmg = autocvar_g_throughfloor_damage;
932 tfloorforce = autocvar_g_throughfloor_force;
934 total_damage_to_creatures = 0;
936 if(deathtype != (WEP_HOOK.m_id | HITTYPE_SECONDARY | HITTYPE_BOUNCE)) // only send gravity bomb damage once
937 if(DEATH_WEAPONOF(deathtype) != WEP_TUBA) // do not send tuba damage (bandwidth hog)
939 force = inflictorvelocity;
943 force = normalize(force);
944 if(forceintensity >= 0)
945 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, rad, forceintensity * force, deathtype, 0, attacker);
947 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, -rad, (-forceintensity) * force, deathtype, 0, attacker);
952 targ = WarpZone_FindRadius (inflictororigin, rad + MAX_DAMAGEEXTRARADIUS, false);
956 if ((targ != inflictor) || inflictorselfdamage)
957 if (((cantbe != targ) && !mustbe) || (mustbe == targ))
964 // LordHavoc: measure distance to nearest point on target (not origin)
965 // (this guarentees 100% damage on a touch impact)
966 nearest = targ.WarpZone_findradius_nearest;
967 diff = targ.WarpZone_findradius_dist;
968 // round up a little on the damage to ensure full damage on impacts
969 // and turn the distance into a fraction of the radius
970 power = 1 - ((vlen (diff) - bound(MIN_DAMAGEEXTRARADIUS, targ.damageextraradius, MAX_DAMAGEEXTRARADIUS)) / rad);
972 //bprint(ftos(power));
973 //if (targ == attacker)
974 // print(ftos(power), "\n");
980 finaldmg = coredamage * power + edgedamage * (1 - power);
986 vector myblastorigin;
989 myblastorigin = WarpZone_TransformOrigin(targ, inflictororigin);
991 // if it's a player, use the view origin as reference
992 center = CENTER_OR_VIEWOFS(targ);
994 force = normalize(center - myblastorigin);
995 force = force * (finaldmg / coredamage) * forceintensity;
998 if(deathtype & WEP_BLASTER.m_id)
999 force *= WEP_CVAR_BOTH(blaster, !(deathtype & HITTYPE_SECONDARY), force_zscale);
1001 if(targ != directhitentity)
1006 float mininv_f, mininv_d;
1008 // test line of sight to multiple positions on box,
1009 // and do damage if any of them hit
1012 // we know: max stddev of hitratio = 1 / (2 * sqrt(n))
1013 // so for a given max stddev:
1014 // n = (1 / (2 * max stddev of hitratio))^2
1016 mininv_d = (finaldmg * (1-tfloordmg)) / autocvar_g_throughfloor_damage_max_stddev;
1017 mininv_f = (vlen(force) * (1-tfloorforce)) / autocvar_g_throughfloor_force_max_stddev;
1019 if(autocvar_g_throughfloor_debug)
1020 LOG_INFOF("THROUGHFLOOR: D=%f F=%f max(dD)=1/%f max(dF)=1/%f", finaldmg, vlen(force), mininv_d, mininv_f);
1023 total = 0.25 * (max(mininv_f, mininv_d) ** 2);
1025 if(autocvar_g_throughfloor_debug)
1026 LOG_INFOF(" steps=%f", total);
1029 if (IS_PLAYER(targ))
1030 total = ceil(bound(autocvar_g_throughfloor_min_steps_player, total, autocvar_g_throughfloor_max_steps_player));
1032 total = ceil(bound(autocvar_g_throughfloor_min_steps_other, total, autocvar_g_throughfloor_max_steps_other));
1034 if(autocvar_g_throughfloor_debug)
1035 LOG_INFOF(" steps=%f dD=%f dF=%f", total, finaldmg * (1-tfloordmg) / (2 * sqrt(total)), vlen(force) * (1-tfloorforce) / (2 * sqrt(total)));
1037 for(c = 0; c < total; ++c)
1039 //traceline(targ.WarpZone_findradius_findorigin, nearest, MOVE_NOMONSTERS, inflictor);
1040 WarpZone_TraceLine(inflictororigin, WarpZone_UnTransformOrigin(targ, nearest), MOVE_NOMONSTERS, inflictor);
1041 if (trace_fraction == 1 || trace_ent == targ)
1045 hitloc = hitloc + nearest;
1049 nearest.x = targ.origin.x + targ.mins.x + random() * targ.size.x;
1050 nearest.y = targ.origin.y + targ.mins.y + random() * targ.size.y;
1051 nearest.z = targ.origin.z + targ.mins.z + random() * targ.size.z;
1054 nearest = hitloc * (1 / max(1, hits));
1055 hitratio = (hits / total);
1056 a = bound(0, tfloordmg + (1-tfloordmg) * hitratio, 1);
1057 finaldmg = finaldmg * a;
1058 a = bound(0, tfloorforce + (1-tfloorforce) * hitratio, 1);
1061 if(autocvar_g_throughfloor_debug)
1062 LOG_INFOF(" D=%f F=%f", finaldmg, vlen(force));
1065 //if (targ == attacker)
1067 // print("hits ", ftos(hits), " / ", ftos(total));
1068 // print(" finaldmg ", ftos(finaldmg), " force ", vtos(force));
1069 // print(" (", ftos(a), ")\n");
1071 if(finaldmg || force)
1075 total_damage_to_creatures += finaldmg;
1077 if(accuracy_isgooddamage(attacker, targ))
1078 stat_damagedone += finaldmg;
1081 if(targ == directhitentity || DEATH_ISSPECIAL(deathtype))
1082 Damage (targ, inflictor, attacker, finaldmg, deathtype, weaponentity, nearest, force);
1084 Damage (targ, inflictor, attacker, finaldmg, deathtype | HITTYPE_SPLASH, weaponentity, nearest, force);
1092 RadiusDamage_running = 0;
1094 if(!DEATH_ISSPECIAL(deathtype))
1095 accuracy_add(attacker, DEATH_WEAPONOF(deathtype).m_id, 0, min(coredamage, stat_damagedone));
1097 return total_damage_to_creatures;
1100 float RadiusDamage (entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float forceintensity, int deathtype, .entity weaponentity, entity directhitentity)
1102 return RadiusDamageForSource (inflictor, (inflictor.origin + (inflictor.mins + inflictor.maxs) * 0.5), inflictor.velocity, attacker, coredamage, edgedamage, rad, cantbe, mustbe, false, forceintensity, deathtype, weaponentity, directhitentity);
1105 float Fire_IsBurning(entity e)
1107 return (time < e.fire_endtime);
1110 float Fire_AddDamage(entity e, entity o, float d, float t, float dt)
1113 float maxtime, mintime, maxdamage, mindamage, maxdps, mindps, totaldamage, totaltime;
1124 // print("adding a fire burner to ", e.classname, "\n");
1125 e.fire_burner = new(fireburner);
1126 setthink(e.fire_burner, fireburner_think);
1127 e.fire_burner.nextthink = time;
1128 e.fire_burner.owner = e;
1134 if(Fire_IsBurning(e))
1136 mintime = e.fire_endtime - time;
1137 maxtime = max(mintime, t);
1139 mindps = e.fire_damagepersec;
1140 maxdps = max(mindps, dps);
1142 if(maxtime > mintime || maxdps > mindps)
1146 // damage we have right now
1147 mindamage = mindps * mintime;
1149 // damage we want to get
1150 maxdamage = mindamage + d;
1152 // but we can't exceed maxtime * maxdps!
1153 totaldamage = min(maxdamage, maxtime * maxdps);
1157 // totaldamage = min(mindamage + d, maxtime * maxdps)
1159 // totaldamage <= maxtime * maxdps
1160 // ==> totaldamage / maxdps <= maxtime.
1162 // totaldamage / mindps = min(mindamage / mindps + d, maxtime * maxdps / mindps)
1163 // >= min(mintime, maxtime)
1164 // ==> totaldamage / maxdps >= mintime.
1167 // how long do we damage then?
1168 // at least as long as before
1169 // but, never exceed maxdps
1170 totaltime = max(mintime, totaldamage / maxdps); // always <= maxtime due to lemma
1174 // at most as long as maximum allowed
1175 // but, never below mindps
1176 totaltime = min(maxtime, totaldamage / mindps); // always >= mintime due to lemma
1178 // assuming t > mintime, dps > mindps:
1179 // we get d = t * dps = maxtime * maxdps
1180 // totaldamage = min(maxdamage, maxtime * maxdps) = min(... + d, maxtime * maxdps) = maxtime * maxdps
1181 // totaldamage / maxdps = maxtime
1182 // totaldamage / mindps > totaldamage / maxdps = maxtime
1184 // a) totaltime = max(mintime, maxtime) = maxtime
1185 // b) totaltime = min(maxtime, totaldamage / maxdps) = maxtime
1187 // assuming t <= mintime:
1188 // we get maxtime = mintime
1189 // a) totaltime = max(mintime, ...) >= mintime, also totaltime <= maxtime by the lemma, therefore totaltime = mintime = maxtime
1190 // b) totaltime = min(maxtime, ...) <= maxtime, also totaltime >= mintime by the lemma, therefore totaltime = mintime = maxtime
1192 // assuming dps <= mindps:
1193 // we get mindps = maxdps.
1194 // With this, the lemma says that mintime <= totaldamage / mindps = totaldamage / maxdps <= maxtime.
1195 // a) totaltime = max(mintime, totaldamage / maxdps) = totaldamage / maxdps
1196 // b) totaltime = min(maxtime, totaldamage / mindps) = totaldamage / maxdps
1198 e.fire_damagepersec = totaldamage / totaltime;
1199 e.fire_endtime = time + totaltime;
1200 if(totaldamage > 1.2 * mindamage)
1202 e.fire_deathtype = dt;
1203 if(e.fire_owner != o)
1206 e.fire_hitsound = false;
1209 if(accuracy_isgooddamage(o, e))
1210 accuracy_add(o, DEATH_WEAPONOF(dt).m_id, 0, max(0, totaldamage - mindamage));
1211 return max(0, totaldamage - mindamage); // can never be negative, but to make sure
1218 e.fire_damagepersec = dps;
1219 e.fire_endtime = time + t;
1220 e.fire_deathtype = dt;
1222 e.fire_hitsound = false;
1223 if(accuracy_isgooddamage(o, e))
1224 accuracy_add(o, DEATH_WEAPONOF(dt).m_id, 0, d);
1229 void Fire_ApplyDamage(entity e)
1234 if (!Fire_IsBurning(e))
1237 for(t = 0, o = e.owner; o.owner && t < 16; o = o.owner, ++t);
1238 if(IS_NOT_A_CLIENT(o))
1241 // water and slime stop fire
1243 if(e.watertype != CONTENT_LAVA)
1250 t = min(frametime, e.fire_endtime - time);
1251 d = e.fire_damagepersec * t;
1253 hi = e.fire_owner.damage_dealt;
1254 ty = e.fire_owner.typehitsound;
1255 Damage(e, e, e.fire_owner, d, e.fire_deathtype, DMG_NOWEP, e.origin, '0 0 0');
1256 if(e.fire_hitsound && e.fire_owner)
1258 e.fire_owner.damage_dealt = hi;
1259 e.fire_owner.typehitsound = ty;
1261 e.fire_hitsound = true;
1263 if(!IS_INDEPENDENT_PLAYER(e))
1264 if(!STAT(FROZEN, e))
1265 FOREACH_CLIENT(IS_PLAYER(it) && it != e, {
1267 if(!IS_INDEPENDENT_PLAYER(it))
1268 if(boxesoverlap(e.absmin, e.absmax, it.absmin, it.absmax))
1270 t = autocvar_g_balance_firetransfer_time * (e.fire_endtime - time);
1271 d = autocvar_g_balance_firetransfer_damage * e.fire_damagepersec * t;
1272 Fire_AddDamage(it, o, d, t, DEATH_FIRE.m_id);
1277 void Fire_ApplyEffect(entity e)
1279 if(Fire_IsBurning(e))
1280 e.effects |= EF_FLAME;
1282 e.effects &= ~EF_FLAME;
1285 void fireburner_think(entity this)
1287 // for players, this is done in the regular loop
1288 if(wasfreed(this.owner))
1293 Fire_ApplyEffect(this.owner);
1294 if(!Fire_IsBurning(this.owner))
1296 this.owner.fire_burner = NULL;
1300 Fire_ApplyDamage(this.owner);
1301 this.nextthink = time;