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 "../common/mutators/mutator/instagib/sv_instagib.qh"
17 #include "weapons/accuracy.qh"
18 #include "weapons/csqcprojectile.qh"
19 #include "weapons/selection.qh"
20 #include "../common/constants.qh"
21 #include "../common/deathtypes/all.qh"
22 #include "../common/notifications/all.qh"
23 #include "../common/physics/movetypes/movetypes.qh"
24 #include "../common/playerstats.qh"
25 #include "../common/teams.qh"
26 #include "../common/util.qh"
27 #include <common/weapons/_all.qh>
28 #include "../lib/csqcmodel/sv_model.qh"
29 #include "../lib/warpzone/common.qh"
31 void UpdateFrags(entity player, int f)
33 GameRules_scoring_add_team(player, SCORE, f);
36 void GiveFrags (entity attacker, entity targ, float f, int deathtype, .entity weaponentity)
38 // TODO route through PlayerScores instead
39 if(game_stopped) return;
46 GameRules_scoring_add(attacker, SUICIDES, 1);
51 GameRules_scoring_add(attacker, TEAMKILLS, 1);
57 GameRules_scoring_add(attacker, KILLS, 1);
58 if(!warmup_stage && targ.playerid)
59 PlayerStats_GameReport_Event_Player(attacker, sprintf("kills-%d", targ.playerid), 1);
62 GameRules_scoring_add(targ, DEATHS, 1);
64 if(targ != attacker) // not for suicides
65 if(g_weaponarena_random)
67 // after a frag, exchange the current weapon (or the culprit, if detectable) by a new random weapon
68 Weapon culprit = DEATH_WEAPONOF(deathtype);
69 if(!culprit) culprit = attacker.(weaponentity).m_weapon;
70 else if(!(STAT(WEAPONS, attacker) & (culprit.m_wepset))) culprit = attacker.(weaponentity).m_weapon;
72 if(g_weaponarena_random_with_blaster && culprit == WEP_BLASTER) // WEAPONTODO: Shouldn't this be in a mutator?
78 if(!GiveFrags_randomweapons)
80 GiveFrags_randomweapons = new(GiveFrags_randomweapons);
84 STAT(WEAPONS, GiveFrags_randomweapons) = WARMUP_START_WEAPONS;
86 STAT(WEAPONS, GiveFrags_randomweapons) = start_weapons;
88 // all others (including the culprit): remove
89 STAT(WEAPONS, GiveFrags_randomweapons) &= ~STAT(WEAPONS, attacker);
90 STAT(WEAPONS, GiveFrags_randomweapons) &= ~(culprit.m_wepset);
92 // among the remaining ones, choose one by random
93 STAT(WEAPONS, GiveFrags_randomweapons) = W_RandomWeapons(GiveFrags_randomweapons, STAT(WEAPONS, GiveFrags_randomweapons), 1);
95 if(STAT(WEAPONS, GiveFrags_randomweapons))
97 STAT(WEAPONS, attacker) |= STAT(WEAPONS, GiveFrags_randomweapons);
98 STAT(WEAPONS, attacker) &= ~(culprit.m_wepset);
102 // after a frag, choose another random weapon set
103 if (!(STAT(WEAPONS, attacker) & WepSet_FromWeapon(attacker.(weaponentity).m_weapon)))
104 W_SwitchWeapon_Force(attacker, w_getbestweapon(attacker, weaponentity), weaponentity);
107 // FIXME fix the mess this is (we have REAL points now!)
108 if(MUTATOR_CALLHOOK(GiveFragsForKill, attacker, targ, f))
109 f = M_ARGV(2, float);
111 attacker.totalfrags += f;
114 UpdateFrags(attacker, f);
119 string AppendItemcodes(string s, entity player)
121 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
123 .entity weaponentity = weaponentities[slot];
124 int w = player.(weaponentity).m_weapon.m_id;
126 w = player.(weaponentity).cnt; // previous weapon
127 if(w != 0 || slot == 0)
128 s = strcat(s, ftos(w));
130 if(time < player.strength_finished)
132 if(time < player.invincible_finished)
134 if(player.flagcarried != NULL)
136 if(PHYS_INPUT_BUTTON_CHAT(player))
143 void LogDeath(string mode, int deathtype, entity killer, entity killed)
146 if(!autocvar_sv_eventlog)
148 s = strcat(":kill:", mode);
149 s = strcat(s, ":", ftos(killer.playerid));
150 s = strcat(s, ":", ftos(killed.playerid));
151 s = strcat(s, ":type=", Deathtype_Name(deathtype));
152 s = strcat(s, ":items=");
153 s = AppendItemcodes(s, killer);
156 s = strcat(s, ":victimitems=");
157 s = AppendItemcodes(s, killed);
162 void Obituary_SpecialDeath(
166 string s1, string s2, string s3,
167 float f1, float f2, float f3)
169 if(!DEATH_ISSPECIAL(deathtype))
171 backtrace("Obituary_SpecialDeath called without a special deathtype?\n");
175 entity deathent = Deathtypes_from(deathtype - DT_FIRST);
178 backtrace("Obituary_SpecialDeath: Could not find deathtype entity!\n");
182 if(g_cts && deathtype == DEATH_KILL.m_id)
183 return; // TODO: somehow put this in CTS gamemode file!
185 Notification death_message = (murder) ? deathent.death_msgmurder : deathent.death_msgself;
188 Send_Notification_WOCOVA(
196 Send_Notification_WOCOVA(
200 death_message.nent_msginfo,
207 float Obituary_WeaponDeath(
211 string s1, string s2, string s3,
214 Weapon death_weapon = DEATH_WEAPONOF(deathtype);
215 if (death_weapon == WEP_Null)
218 w_deathtype = deathtype;
219 Notification death_message = ((murder) ? death_weapon.wr_killmessage(death_weapon) : death_weapon.wr_suicidemessage(death_weapon));
224 Send_Notification_WOCOVA(
232 // send the info part to everyone
233 Send_Notification_WOCOVA(
237 death_message.nent_msginfo,
245 "Obituary_WeaponDeath(): ^1Deathtype ^7(%d)^1 has no notification for weapon %d!\n",
254 bool frag_centermessage_override(entity attacker, entity targ, int deathtype, int kill_count_to_attacker, int kill_count_to_target)
256 if(deathtype == DEATH_FIRE.m_id)
258 Send_Notification(NOTIF_ONE, attacker, MSG_CHOICE, CHOICE_FRAG_FIRE, targ.netname, kill_count_to_attacker, (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping));
259 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));
263 return MUTATOR_CALLHOOK(FragCenterMessage, attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target);
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(STAT(BUFFS, attacker)).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 revivespeed, 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 STAT(REVIVE_PROGRESS, targ) = ((frozen_type == 3) ? 1 : 0);
534 SetResourceAmount(targ, RESOURCE_HEALTH, ((frozen_type == 3) ? targ_maxhealth : 1));
535 targ.revive_speed = revivespeed;
537 IL_REMOVE(g_bot_targets, targ);
538 targ.bot_attack = false;
539 targ.freeze_time = time;
541 entity ice = new(ice);
543 ice.scale = targ.scale;
544 setthink(ice, Ice_Think);
545 ice.nextthink = time;
546 ice.frame = floor(random() * 21); // ice model has 20 different looking frames
547 setmodel(ice, MDL_ICE);
549 ice.colormod = Team_ColorRGB(targ.team);
550 ice.glowmod = ice.colormod;
552 targ.revival_time = 0;
556 RemoveGrapplingHooks(targ);
558 FOREACH_CLIENT(IS_PLAYER(it),
560 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
562 .entity weaponentity = weaponentities[slot];
563 if(it.(weaponentity).hook.aiment == targ)
564 RemoveHook(it.(weaponentity).hook);
570 WaypointSprite_Spawn(WP_Frozen, 0, 0, targ, '0 0 64', NULL, targ.team, targ, waypointsprite_attached, true, RADARICON_WAYPOINT);
573 void Unfreeze (entity targ)
575 if(!STAT(FROZEN, targ))
578 if(STAT(FROZEN, targ) && STAT(FROZEN, targ) != 3) // only reset health if target was frozen
580 SetResourceAmount(targ, RESOURCE_HEALTH, ((IS_PLAYER(targ)) ? start_health : targ.max_health));
581 targ.pauseregen_finished = time + autocvar_g_balance_pause_health_regen;
584 STAT(FROZEN, targ) = 0;
585 STAT(REVIVE_PROGRESS, targ) = 0;
586 targ.revival_time = time;
588 IL_PUSH(g_bot_targets, targ);
589 targ.bot_attack = true;
591 WaypointSprite_Kill(targ.waypointsprite_attached);
593 FOREACH_CLIENT(IS_PLAYER(it),
595 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
597 .entity weaponentity = weaponentities[slot];
598 if(it.(weaponentity).hook.aiment == targ)
599 RemoveHook(it.(weaponentity).hook);
603 // remove the ice block
605 delete(targ.iceblock);
606 targ.iceblock = NULL;
609 void Damage (entity targ, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
611 float complainteamdamage = 0;
612 float mirrordamage = 0;
613 float mirrorforce = 0;
615 if (game_stopped || (IS_CLIENT(targ) && CS(targ).killcount == FRAGS_SPECTATOR))
618 entity attacker_save = attacker;
620 // special rule: gravity bomb does not hit team mates (other than for disconnecting the hook)
621 if(DEATH_ISWEAPON(deathtype, WEP_HOOK) || DEATH_ISWEAPON(deathtype, WEP_TUBA))
623 if(IS_PLAYER(targ) && SAME_TEAM(targ, attacker))
629 if(deathtype == DEATH_KILL.m_id || deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id)
631 // exit the vehicle before killing (fixes a crash)
632 if(IS_PLAYER(targ) && targ.vehicle)
633 vehicles_exit(targ.vehicle, VHEF_RELEASE);
635 // These are ALWAYS lethal
636 // No damage modification here
637 // Instead, prepare the victim for his death...
638 SetResourceAmount(targ, RESOURCE_ARMOR, 0);
639 targ.spawnshieldtime = 0;
640 SetResourceAmount(targ, RESOURCE_HEALTH, 0.9); // this is < 1
641 targ.flags -= targ.flags & FL_GODMODE;
644 else if(deathtype == DEATH_MIRRORDAMAGE.m_id || deathtype == DEATH_NOAMMO.m_id)
650 // nullify damage if teamplay is on
651 if(deathtype != DEATH_TELEFRAG.m_id)
652 if(IS_PLAYER(attacker))
654 if(IS_PLAYER(targ) && targ != attacker && (IS_INDEPENDENT_PLAYER(attacker) || IS_INDEPENDENT_PLAYER(targ)))
659 else if(SAME_TEAM(attacker, targ))
661 if(autocvar_teamplay_mode == 1)
663 else if(attacker != targ)
665 if(autocvar_teamplay_mode == 3)
667 else if(autocvar_teamplay_mode == 4)
669 if(IS_PLAYER(targ) && !IS_DEAD(targ))
671 attacker.dmg_team = attacker.dmg_team + damage;
672 complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold;
673 if(complainteamdamage > 0)
674 mirrordamage = autocvar_g_mirrordamage * complainteamdamage;
675 mirrorforce = autocvar_g_mirrordamage * vlen(force);
676 damage = autocvar_g_friendlyfire * damage;
677 // mirrordamage will be used LATER
679 if(autocvar_g_mirrordamage_virtual)
681 vector v = healtharmor_applydamage(attacker.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, mirrordamage);
682 attacker.dmg_take += v.x;
683 attacker.dmg_save += v.y;
684 attacker.dmg_inflictor = inflictor;
689 if(autocvar_g_friendlyfire_virtual)
691 vector v = healtharmor_applydamage(targ.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, damage);
692 targ.dmg_take += v.x;
693 targ.dmg_save += v.y;
694 targ.dmg_inflictor = inflictor;
696 if(!autocvar_g_friendlyfire_virtual_force)
700 else if(!targ.canteamdamage)
707 if (!DEATH_ISSPECIAL(deathtype))
709 damage *= g_weapondamagefactor;
710 mirrordamage *= g_weapondamagefactor;
711 complainteamdamage *= g_weapondamagefactor;
712 force = force * g_weaponforcefactor;
713 mirrorforce *= g_weaponforcefactor;
716 // should this be changed at all? If so, in what way?
717 MUTATOR_CALLHOOK(Damage_Calculate, inflictor, attacker, targ, deathtype, damage, mirrordamage, force, attacker.(weaponentity));
718 damage = M_ARGV(4, float);
719 mirrordamage = M_ARGV(5, float);
720 force = M_ARGV(6, vector);
722 if(IS_PLAYER(targ) && damage > 0 && attacker)
724 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
726 .entity went = weaponentities[slot];
727 if(targ.(went).hook && targ.(went).hook.aiment == attacker)
728 RemoveHook(targ.(went).hook);
732 if(STAT(FROZEN, targ))
733 if(deathtype != DEATH_HURTTRIGGER.m_id && deathtype != DEATH_TEAMCHANGE.m_id && deathtype != DEATH_AUTOTEAMCHANGE.m_id)
735 if(autocvar_g_frozen_revive_falldamage > 0)
736 if(deathtype == DEATH_FALL.m_id)
737 if(damage >= autocvar_g_frozen_revive_falldamage)
740 SetResourceAmount(targ, RESOURCE_HEALTH, autocvar_g_frozen_revive_falldamage_health);
741 Send_Effect(EFFECT_ICEORGLASS, targ.origin, '0 0 0', 3);
742 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, targ.netname);
743 Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF);
747 force *= autocvar_g_frozen_force;
750 if(STAT(FROZEN, targ) && deathtype == DEATH_HURTTRIGGER.m_id && !autocvar_g_frozen_damage_trigger)
752 Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1);
754 entity spot = SelectSpawnPoint (targ, false);
759 targ.deadflag = DEAD_NO;
761 targ.angles = spot.angles;
764 targ.effects |= EF_TELEPORT_BIT;
766 targ.angles_z = 0; // never spawn tilted even if the spot says to
767 targ.fixangle = true; // turn this way immediately
768 targ.velocity = '0 0 0';
769 targ.avelocity = '0 0 0';
770 targ.punchangle = '0 0 0';
771 targ.punchvector = '0 0 0';
772 targ.oldvelocity = targ.velocity;
774 targ.spawnorigin = spot.origin;
775 setorigin(targ, spot.origin + '0 0 1' * (1 - targ.mins.z - 24));
776 // don't reset back to last position, even if new position is stuck in solid
777 targ.oldorigin = targ.origin;
779 Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1);
783 if(!MUTATOR_IS_ENABLED(mutator_instagib))
785 // apply strength multiplier
786 if (attacker.items & ITEM_Strength.m_itemid)
790 damage = damage * autocvar_g_balance_powerup_strength_selfdamage;
791 force = force * autocvar_g_balance_powerup_strength_selfforce;
795 damage = damage * autocvar_g_balance_powerup_strength_damage;
796 force = force * autocvar_g_balance_powerup_strength_force;
800 // apply invincibility multiplier
801 if (targ.items & ITEM_Shield.m_itemid)
803 damage = damage * autocvar_g_balance_powerup_invincible_takedamage;
804 if (targ != attacker)
806 force = force * autocvar_g_balance_powerup_invincible_takeforce;
811 if (targ == attacker)
812 damage = damage * autocvar_g_balance_selfdamagepercent; // Partial damage if the attacker hits himself
817 if(deathtype != DEATH_BUFF.m_id)
818 if(targ.takedamage == DAMAGE_AIM)
822 if(IS_VEHICLE(targ) && targ.owner)
827 if(IS_PLAYER(victim) || (IS_TURRET(victim) && victim.active == ACTIVE_ACTIVE) || IS_MONSTER(victim) || MUTATOR_CALLHOOK(PlayHitsound, victim, attacker))
829 if(DIFF_TEAM(victim, attacker) && !STAT(FROZEN, victim))
833 if(deathtype != DEATH_FIRE.m_id)
835 if(PHYS_INPUT_BUTTON_CHAT(victim))
836 attacker.typehitsound += 1;
838 attacker.damage_dealt += damage;
841 damage_goodhits += 1;
842 damage_gooddamage += damage;
844 if (!DEATH_ISSPECIAL(deathtype))
846 if(IS_PLAYER(targ)) // don't do this for vehicles
852 else if(IS_PLAYER(attacker))
854 if(deathtype != DEATH_FIRE.m_id)
856 attacker.typehitsound += 1;
858 if(complainteamdamage > 0)
859 if(time > CS(attacker).teamkill_complain)
861 CS(attacker).teamkill_complain = time + 5;
862 CS(attacker).teamkill_soundtime = time + 0.4;
863 CS(attacker).teamkill_soundsource = targ;
871 if (targ.damageforcescale)
873 if (!IS_PLAYER(targ) || time >= targ.spawnshieldtime || targ == attacker)
875 vector farce = damage_explosion_calcpush(targ.damageforcescale * force, targ.velocity, autocvar_g_balance_damagepush_speedfactor);
876 if(targ.move_movetype == MOVETYPE_PHYSICS)
878 entity farcent = new(farce);
879 farcent.enemy = targ;
880 farcent.movedir = farce * 10;
882 farcent.movedir = farcent.movedir * targ.mass;
883 farcent.origin = hitloc;
884 farcent.forcetype = FORCETYPE_FORCEATPOS;
885 farcent.nextthink = time + 0.1;
886 setthink(farcent, SUB_Remove);
890 targ.velocity = targ.velocity + farce;
892 UNSET_ONGROUND(targ);
893 UpdateCSQCProjectile(targ);
896 if (damage != 0 || (targ.damageforcescale && force))
897 if (targ.event_damage)
898 targ.event_damage (targ, inflictor, attacker, damage, deathtype, weaponentity, hitloc, force);
900 // apply mirror damage if any
901 if(!autocvar_g_mirrordamage_onlyweapons || DEATH_WEAPONOF(deathtype) != WEP_Null)
902 if(mirrordamage > 0 || mirrorforce > 0)
904 attacker = attacker_save;
906 force = normalize(attacker.origin + attacker.view_ofs - hitloc) * mirrorforce;
907 Damage(attacker, inflictor, attacker, mirrordamage, DEATH_MIRRORDAMAGE.m_id, weaponentity, attacker.origin, force);
911 float RadiusDamageForSource (entity inflictor, vector inflictororigin, vector inflictorvelocity, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe,
912 float inflictorselfdamage, float forceintensity, int deathtype, .entity weaponentity, entity directhitentity)
913 // Returns total damage applies to creatures
917 float total_damage_to_creatures;
922 float stat_damagedone;
924 if(RadiusDamage_running)
926 backtrace("RadiusDamage called recursively! Expect stuff to go HORRIBLY wrong.");
930 RadiusDamage_running = 1;
932 tfloordmg = autocvar_g_throughfloor_damage;
933 tfloorforce = autocvar_g_throughfloor_force;
935 total_damage_to_creatures = 0;
937 if(deathtype != (WEP_HOOK.m_id | HITTYPE_SECONDARY | HITTYPE_BOUNCE)) // only send gravity bomb damage once
938 if(DEATH_WEAPONOF(deathtype) != WEP_TUBA) // do not send tuba damage (bandwidth hog)
940 force = inflictorvelocity;
944 force = normalize(force);
945 if(forceintensity >= 0)
946 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, rad, forceintensity * force, deathtype, 0, attacker);
948 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, -rad, (-forceintensity) * force, deathtype, 0, attacker);
953 targ = WarpZone_FindRadius (inflictororigin, rad + MAX_DAMAGEEXTRARADIUS, false);
957 if ((targ != inflictor) || inflictorselfdamage)
958 if (((cantbe != targ) && !mustbe) || (mustbe == targ))
965 // LordHavoc: measure distance to nearest point on target (not origin)
966 // (this guarentees 100% damage on a touch impact)
967 nearest = targ.WarpZone_findradius_nearest;
968 diff = targ.WarpZone_findradius_dist;
969 // round up a little on the damage to ensure full damage on impacts
970 // and turn the distance into a fraction of the radius
971 power = 1 - ((vlen (diff) - bound(MIN_DAMAGEEXTRARADIUS, targ.damageextraradius, MAX_DAMAGEEXTRARADIUS)) / rad);
973 //bprint(ftos(power));
974 //if (targ == attacker)
975 // print(ftos(power), "\n");
981 finaldmg = coredamage * power + edgedamage * (1 - power);
987 vector myblastorigin;
990 myblastorigin = WarpZone_TransformOrigin(targ, inflictororigin);
992 // if it's a player, use the view origin as reference
993 center = CENTER_OR_VIEWOFS(targ);
995 force = normalize(center - myblastorigin);
996 force = force * (finaldmg / coredamage) * forceintensity;
999 if(deathtype & WEP_BLASTER.m_id)
1000 force *= WEP_CVAR_BOTH(blaster, !(deathtype & HITTYPE_SECONDARY), force_zscale);
1002 if(targ != directhitentity)
1007 float mininv_f, mininv_d;
1009 // test line of sight to multiple positions on box,
1010 // and do damage if any of them hit
1013 // we know: max stddev of hitratio = 1 / (2 * sqrt(n))
1014 // so for a given max stddev:
1015 // n = (1 / (2 * max stddev of hitratio))^2
1017 mininv_d = (finaldmg * (1-tfloordmg)) / autocvar_g_throughfloor_damage_max_stddev;
1018 mininv_f = (vlen(force) * (1-tfloorforce)) / autocvar_g_throughfloor_force_max_stddev;
1020 if(autocvar_g_throughfloor_debug)
1021 LOG_INFOF("THROUGHFLOOR: D=%f F=%f max(dD)=1/%f max(dF)=1/%f", finaldmg, vlen(force), mininv_d, mininv_f);
1024 total = 0.25 * (max(mininv_f, mininv_d) ** 2);
1026 if(autocvar_g_throughfloor_debug)
1027 LOG_INFOF(" steps=%f", total);
1030 if (IS_PLAYER(targ))
1031 total = ceil(bound(autocvar_g_throughfloor_min_steps_player, total, autocvar_g_throughfloor_max_steps_player));
1033 total = ceil(bound(autocvar_g_throughfloor_min_steps_other, total, autocvar_g_throughfloor_max_steps_other));
1035 if(autocvar_g_throughfloor_debug)
1036 LOG_INFOF(" steps=%f dD=%f dF=%f", total, finaldmg * (1-tfloordmg) / (2 * sqrt(total)), vlen(force) * (1-tfloorforce) / (2 * sqrt(total)));
1038 for(c = 0; c < total; ++c)
1040 //traceline(targ.WarpZone_findradius_findorigin, nearest, MOVE_NOMONSTERS, inflictor);
1041 WarpZone_TraceLine(inflictororigin, WarpZone_UnTransformOrigin(targ, nearest), MOVE_NOMONSTERS, inflictor);
1042 if (trace_fraction == 1 || trace_ent == targ)
1046 hitloc = hitloc + nearest;
1050 nearest.x = targ.origin.x + targ.mins.x + random() * targ.size.x;
1051 nearest.y = targ.origin.y + targ.mins.y + random() * targ.size.y;
1052 nearest.z = targ.origin.z + targ.mins.z + random() * targ.size.z;
1055 nearest = hitloc * (1 / max(1, hits));
1056 hitratio = (hits / total);
1057 a = bound(0, tfloordmg + (1-tfloordmg) * hitratio, 1);
1058 finaldmg = finaldmg * a;
1059 a = bound(0, tfloorforce + (1-tfloorforce) * hitratio, 1);
1062 if(autocvar_g_throughfloor_debug)
1063 LOG_INFOF(" D=%f F=%f", finaldmg, vlen(force));
1066 //if (targ == attacker)
1068 // print("hits ", ftos(hits), " / ", ftos(total));
1069 // print(" finaldmg ", ftos(finaldmg), " force ", vtos(force));
1070 // print(" (", ftos(a), ")\n");
1072 if(finaldmg || force)
1076 total_damage_to_creatures += finaldmg;
1078 if(accuracy_isgooddamage(attacker, targ))
1079 stat_damagedone += finaldmg;
1082 if(targ == directhitentity || DEATH_ISSPECIAL(deathtype))
1083 Damage (targ, inflictor, attacker, finaldmg, deathtype, weaponentity, nearest, force);
1085 Damage (targ, inflictor, attacker, finaldmg, deathtype | HITTYPE_SPLASH, weaponentity, nearest, force);
1093 RadiusDamage_running = 0;
1095 if(!DEATH_ISSPECIAL(deathtype))
1096 accuracy_add(attacker, DEATH_WEAPONOF(deathtype).m_id, 0, min(coredamage, stat_damagedone));
1098 return total_damage_to_creatures;
1101 float RadiusDamage (entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float forceintensity, int deathtype, .entity weaponentity, entity directhitentity)
1103 return RadiusDamageForSource (inflictor, (inflictor.origin + (inflictor.mins + inflictor.maxs) * 0.5), inflictor.velocity, attacker, coredamage, edgedamage, rad, cantbe, mustbe, false, forceintensity, deathtype, weaponentity, directhitentity);
1106 float Fire_IsBurning(entity e)
1108 return (time < e.fire_endtime);
1111 float Fire_AddDamage(entity e, entity o, float d, float t, float dt)
1114 float maxtime, mintime, maxdamage, mindamage, maxdps, mindps, totaldamage, totaltime;
1125 // print("adding a fire burner to ", e.classname, "\n");
1126 e.fire_burner = new(fireburner);
1127 setthink(e.fire_burner, fireburner_think);
1128 e.fire_burner.nextthink = time;
1129 e.fire_burner.owner = e;
1135 if(Fire_IsBurning(e))
1137 mintime = e.fire_endtime - time;
1138 maxtime = max(mintime, t);
1140 mindps = e.fire_damagepersec;
1141 maxdps = max(mindps, dps);
1143 if(maxtime > mintime || maxdps > mindps)
1147 // damage we have right now
1148 mindamage = mindps * mintime;
1150 // damage we want to get
1151 maxdamage = mindamage + d;
1153 // but we can't exceed maxtime * maxdps!
1154 totaldamage = min(maxdamage, maxtime * maxdps);
1158 // totaldamage = min(mindamage + d, maxtime * maxdps)
1160 // totaldamage <= maxtime * maxdps
1161 // ==> totaldamage / maxdps <= maxtime.
1163 // totaldamage / mindps = min(mindamage / mindps + d, maxtime * maxdps / mindps)
1164 // >= min(mintime, maxtime)
1165 // ==> totaldamage / maxdps >= mintime.
1168 // how long do we damage then?
1169 // at least as long as before
1170 // but, never exceed maxdps
1171 totaltime = max(mintime, totaldamage / maxdps); // always <= maxtime due to lemma
1175 // at most as long as maximum allowed
1176 // but, never below mindps
1177 totaltime = min(maxtime, totaldamage / mindps); // always >= mintime due to lemma
1179 // assuming t > mintime, dps > mindps:
1180 // we get d = t * dps = maxtime * maxdps
1181 // totaldamage = min(maxdamage, maxtime * maxdps) = min(... + d, maxtime * maxdps) = maxtime * maxdps
1182 // totaldamage / maxdps = maxtime
1183 // totaldamage / mindps > totaldamage / maxdps = maxtime
1185 // a) totaltime = max(mintime, maxtime) = maxtime
1186 // b) totaltime = min(maxtime, totaldamage / maxdps) = maxtime
1188 // assuming t <= mintime:
1189 // we get maxtime = mintime
1190 // a) totaltime = max(mintime, ...) >= mintime, also totaltime <= maxtime by the lemma, therefore totaltime = mintime = maxtime
1191 // b) totaltime = min(maxtime, ...) <= maxtime, also totaltime >= mintime by the lemma, therefore totaltime = mintime = maxtime
1193 // assuming dps <= mindps:
1194 // we get mindps = maxdps.
1195 // With this, the lemma says that mintime <= totaldamage / mindps = totaldamage / maxdps <= maxtime.
1196 // a) totaltime = max(mintime, totaldamage / maxdps) = totaldamage / maxdps
1197 // b) totaltime = min(maxtime, totaldamage / mindps) = totaldamage / maxdps
1199 e.fire_damagepersec = totaldamage / totaltime;
1200 e.fire_endtime = time + totaltime;
1201 if(totaldamage > 1.2 * mindamage)
1203 e.fire_deathtype = dt;
1204 if(e.fire_owner != o)
1207 e.fire_hitsound = false;
1210 if(accuracy_isgooddamage(o, e))
1211 accuracy_add(o, DEATH_WEAPONOF(dt).m_id, 0, max(0, totaldamage - mindamage));
1212 return max(0, totaldamage - mindamage); // can never be negative, but to make sure
1219 e.fire_damagepersec = dps;
1220 e.fire_endtime = time + t;
1221 e.fire_deathtype = dt;
1223 e.fire_hitsound = false;
1224 if(accuracy_isgooddamage(o, e))
1225 accuracy_add(o, DEATH_WEAPONOF(dt).m_id, 0, d);
1230 void Fire_ApplyDamage(entity e)
1235 if (!Fire_IsBurning(e))
1238 for(t = 0, o = e.owner; o.owner && t < 16; o = o.owner, ++t);
1239 if(IS_NOT_A_CLIENT(o))
1242 // water and slime stop fire
1244 if(e.watertype != CONTENT_LAVA)
1251 t = min(frametime, e.fire_endtime - time);
1252 d = e.fire_damagepersec * t;
1254 hi = e.fire_owner.damage_dealt;
1255 ty = e.fire_owner.typehitsound;
1256 Damage(e, e, e.fire_owner, d, e.fire_deathtype, DMG_NOWEP, e.origin, '0 0 0');
1257 if(e.fire_hitsound && e.fire_owner)
1259 e.fire_owner.damage_dealt = hi;
1260 e.fire_owner.typehitsound = ty;
1262 e.fire_hitsound = true;
1264 if(!IS_INDEPENDENT_PLAYER(e))
1265 if(!STAT(FROZEN, e))
1266 FOREACH_CLIENT(IS_PLAYER(it) && it != e, {
1268 if(!IS_INDEPENDENT_PLAYER(it))
1269 if(boxesoverlap(e.absmin, e.absmax, it.absmin, it.absmax))
1271 t = autocvar_g_balance_firetransfer_time * (e.fire_endtime - time);
1272 d = autocvar_g_balance_firetransfer_damage * e.fire_damagepersec * t;
1273 Fire_AddDamage(it, o, d, t, DEATH_FIRE.m_id);
1278 void Fire_ApplyEffect(entity e)
1280 if(Fire_IsBurning(e))
1281 e.effects |= EF_FLAME;
1283 e.effects &= ~EF_FLAME;
1286 void fireburner_think(entity this)
1288 // for players, this is done in the regular loop
1289 if(wasfreed(this.owner))
1294 Fire_ApplyEffect(this.owner);
1295 if(!Fire_IsBurning(this.owner))
1297 this.owner.fire_burner = NULL;
1301 Fire_ApplyDamage(this.owner);
1302 this.nextthink = time;