3 #include <common/effects/all.qh>
6 #include <server/gamelog.qh>
7 #include <server/mutators/_mod.qh>
10 #include "spawnpoints.qh"
11 #include "../common/state.qh"
12 #include "../common/physics/player.qh"
13 #include "../common/t_items.qh"
14 #include "resources.qh"
15 #include "../common/vehicles/all.qh"
16 #include "../common/items/_mod.qh"
17 #include "../common/mutators/mutator/waypoints/waypointsprites.qh"
18 #include "../common/mutators/mutator/instagib/sv_instagib.qh"
19 #include "../common/mutators/mutator/buffs/buffs.qh"
20 #include "weapons/accuracy.qh"
21 #include "weapons/csqcprojectile.qh"
22 #include "weapons/selection.qh"
23 #include "../common/constants.qh"
24 #include "../common/deathtypes/all.qh"
25 #include "../common/notifications/all.qh"
26 #include "../common/physics/movetypes/movetypes.qh"
27 #include "../common/playerstats.qh"
28 #include "../common/teams.qh"
29 #include "../common/util.qh"
30 #include <common/gamemodes/rules.qh>
31 #include <common/weapons/_all.qh>
32 #include "../lib/csqcmodel/sv_model.qh"
33 #include "../lib/warpzone/common.qh"
35 void UpdateFrags(entity player, int f)
37 GameRules_scoring_add_team(player, SCORE, f);
40 void GiveFrags(entity attacker, entity targ, float f, int deathtype, .entity weaponentity)
42 // TODO route through PlayerScores instead
43 if(game_stopped) return;
50 GameRules_scoring_add(attacker, SUICIDES, 1);
55 GameRules_scoring_add(attacker, TEAMKILLS, 1);
61 GameRules_scoring_add(attacker, KILLS, 1);
62 if(!warmup_stage && targ.playerid)
63 PlayerStats_GameReport_Event_Player(attacker, sprintf("kills-%d", targ.playerid), 1);
66 GameRules_scoring_add(targ, DEATHS, 1);
68 // FIXME fix the mess this is (we have REAL points now!)
69 if(MUTATOR_CALLHOOK(GiveFragsForKill, attacker, targ, f, deathtype, attacker.(weaponentity)))
72 attacker.totalfrags += f;
75 UpdateFrags(attacker, f);
78 string AppendItemcodes(string s, entity player)
80 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
82 .entity weaponentity = weaponentities[slot];
83 int w = player.(weaponentity).m_weapon.m_id;
85 w = player.(weaponentity).cnt; // previous weapon
86 if(w != 0 || slot == 0)
87 s = strcat(s, ftos(w));
89 if(time < STAT(STRENGTH_FINISHED, player))
91 if(time < STAT(INVINCIBLE_FINISHED, player))
93 if(PHYS_INPUT_BUTTON_CHAT(player))
95 // TODO: include these codes as a flag on the item itself
96 MUTATOR_CALLHOOK(LogDeath_AppendItemCodes, player, s);
97 s = M_ARGV(1, string);
101 void LogDeath(string mode, int deathtype, entity killer, entity killed)
104 if(!autocvar_sv_eventlog)
106 s = strcat(":kill:", mode);
107 s = strcat(s, ":", ftos(killer.playerid));
108 s = strcat(s, ":", ftos(killed.playerid));
109 s = strcat(s, ":type=", Deathtype_Name(deathtype));
110 s = strcat(s, ":items=");
111 s = AppendItemcodes(s, killer);
114 s = strcat(s, ":victimitems=");
115 s = AppendItemcodes(s, killed);
120 void Obituary_SpecialDeath(
124 string s1, string s2, string s3,
125 float f1, float f2, float f3)
127 if(!DEATH_ISSPECIAL(deathtype))
129 backtrace("Obituary_SpecialDeath called without a special deathtype?\n");
133 entity deathent = REGISTRY_GET(Deathtypes, deathtype - DT_FIRST);
136 backtrace("Obituary_SpecialDeath: Could not find deathtype entity!\n");
140 if(g_cts && deathtype == DEATH_KILL.m_id)
141 return; // TODO: somehow put this in CTS gamemode file!
143 Notification death_message = (murder) ? deathent.death_msgmurder : deathent.death_msgself;
146 Send_Notification_WOCOVA(
154 Send_Notification_WOCOVA(
158 death_message.nent_msginfo,
165 float Obituary_WeaponDeath(
169 string s1, string s2, string s3,
172 Weapon death_weapon = DEATH_WEAPONOF(deathtype);
173 if (death_weapon == WEP_Null)
176 w_deathtype = deathtype;
177 Notification death_message = ((murder) ? death_weapon.wr_killmessage(death_weapon) : death_weapon.wr_suicidemessage(death_weapon));
182 Send_Notification_WOCOVA(
190 // send the info part to everyone
191 Send_Notification_WOCOVA(
195 death_message.nent_msginfo,
203 "Obituary_WeaponDeath(): ^1Deathtype ^7(%d)^1 has no notification for weapon %s!\n",
212 bool frag_centermessage_override(entity attacker, entity targ, int deathtype, int kill_count_to_attacker, int kill_count_to_target)
214 if(deathtype == DEATH_FIRE.m_id)
216 Send_Notification(NOTIF_ONE, attacker, MSG_CHOICE, CHOICE_FRAG_FIRE, targ.netname, kill_count_to_attacker, (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping));
217 Send_Notification(NOTIF_ONE, targ, MSG_CHOICE, CHOICE_FRAGGED_FIRE, attacker.netname, kill_count_to_target, GetResource(attacker, RES_HEALTH), GetResource(attacker, RES_ARMOR), (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping));
221 return MUTATOR_CALLHOOK(FragCenterMessage, attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target);
224 void Obituary(entity attacker, entity inflictor, entity targ, int deathtype, .entity weaponentity)
227 if (!IS_PLAYER(targ)) { backtrace("Obituary called on non-player?!\n"); return; }
230 float notif_firstblood = false;
231 float kill_count_to_attacker, kill_count_to_target;
233 // Set final information for the death
234 targ.death_origin = targ.origin;
235 string deathlocation = (autocvar_notification_server_allows_location ? NearestLocation(targ.death_origin) : "");
237 #ifdef NOTIFICATIONS_DEBUG
240 "Obituary(%s, %s, %s, %s = %d);\n",
244 Deathtype_Name(deathtype),
255 if(DEATH_ISSPECIAL(deathtype))
257 if(deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id)
259 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", targ.team, 0, 0);
263 switch(DEATH_ENT(deathtype))
265 case DEATH_MIRRORDAMAGE:
267 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0);
273 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0);
279 else if (!Obituary_WeaponDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0))
281 backtrace("SUICIDE: what the hell happened here?\n");
284 LogDeath("suicide", deathtype, targ, targ);
285 if(deathtype != DEATH_AUTOTEAMCHANGE.m_id) // special case: don't negate frags if auto switched
286 GiveFrags(attacker, targ, -1, deathtype, weaponentity);
292 else if(IS_PLAYER(attacker))
294 if(SAME_TEAM(attacker, targ))
296 LogDeath("tk", deathtype, attacker, targ);
297 GiveFrags(attacker, targ, -1, deathtype, weaponentity);
299 CS(attacker).killcount = 0;
301 Send_Notification(NOTIF_ONE, attacker, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAG, targ.netname);
302 Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAGGED, attacker.netname);
303 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(targ.team, INFO_DEATH_TEAMKILL), targ.netname, attacker.netname, deathlocation, CS(targ).killcount);
305 // In this case, the death message will ALWAYS be "foo was betrayed by bar"
306 // No need for specific death/weapon messages...
310 LogDeath("frag", deathtype, attacker, targ);
311 GiveFrags(attacker, targ, 1, deathtype, weaponentity);
313 CS(attacker).taunt_soundtime = time + 1;
314 CS(attacker).killcount = CS(attacker).killcount + 1;
316 attacker.killsound += 1;
318 // TODO: improve SPREE_ITEM and KILL_SPREE_LIST
319 // these 2 macros are spread over multiple files
320 #define SPREE_ITEM(counta,countb,center,normal,gentle) \
322 Send_Notification(NOTIF_ONE, attacker, MSG_ANNCE, ANNCE_KILLSTREAK_##countb); \
324 PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_##counta, 1); \
327 switch(CS(attacker).killcount)
334 if(!warmup_stage && !checkrules_firstblood)
336 checkrules_firstblood = true;
337 notif_firstblood = true; // modify the current messages so that they too show firstblood information
338 PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD, 1);
339 PlayerStats_GameReport_Event_Player(targ, PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM, 1);
341 // tell spree_inf and spree_cen that this is a first-blood and first-victim event
342 kill_count_to_attacker = -1;
343 kill_count_to_target = -2;
347 kill_count_to_attacker = CS(attacker).killcount;
348 kill_count_to_target = 0;
359 kill_count_to_attacker,
360 (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping)
368 kill_count_to_target,
369 GetResource(attacker, RES_HEALTH),
370 GetResource(attacker, RES_ARMOR),
371 (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping)
374 else if(!frag_centermessage_override(attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target))
382 kill_count_to_attacker,
383 (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping)
391 kill_count_to_target,
392 GetResource(attacker, RES_HEALTH),
393 GetResource(attacker, RES_ARMOR),
394 (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping)
399 if(deathtype == DEATH_BUFF.m_id)
400 f3 = buff_FirstFromFlags(STAT(BUFFS, attacker)).m_id;
402 if (!Obituary_WeaponDeath(targ, true, deathtype, targ.netname, attacker.netname, deathlocation, CS(targ).killcount, kill_count_to_attacker))
403 Obituary_SpecialDeath(targ, true, deathtype, targ.netname, attacker.netname, deathlocation, CS(targ).killcount, kill_count_to_attacker, f3);
412 switch(DEATH_ENT(deathtype))
414 // For now, we're just forcing HURTTRIGGER to behave as "DEATH_VOID" and giving it no special options...
415 // Later on you will only be able to make custom messages using DEATH_CUSTOM,
416 // and there will be a REAL DEATH_VOID implementation which mappers will use.
417 case DEATH_HURTTRIGGER:
419 Obituary_SpecialDeath(targ, false, deathtype,
431 Obituary_SpecialDeath(targ, false, deathtype,
433 ((strstrofs(deathmessage, "%", 0) < 0) ? strcat("%s ", deathmessage) : deathmessage),
443 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0);
448 LogDeath("accident", deathtype, targ, targ);
449 GiveFrags(targ, targ, -1, deathtype, weaponentity);
451 if(GameRules_scoring_add(targ, SCORE, 0) == -5)
453 Send_Notification(NOTIF_ONE, targ, MSG_ANNCE, ANNCE_ACHIEVEMENT_BOTLIKE);
456 PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_BOTLIKE, 1);
461 // reset target kill count
462 CS(targ).killcount = 0;
465 void Ice_Think(entity this)
467 if(!STAT(FROZEN, this.owner) || this.owner.iceblock != this)
472 vector ice_org = this.owner.origin - '0 0 16';
473 if (this.origin != ice_org)
474 setorigin(this, ice_org);
475 this.nextthink = time;
478 void Freeze(entity targ, float revivespeed, int frozen_type, bool show_waypoint)
480 if(!IS_PLAYER(targ) && !IS_MONSTER(targ)) // TODO: only specified entities can be freezed
483 if(STAT(FROZEN, targ))
486 float targ_maxhealth = ((IS_MONSTER(targ)) ? targ.max_health : start_health);
488 STAT(FROZEN, targ) = frozen_type;
489 STAT(REVIVE_PROGRESS, targ) = ((frozen_type == FROZEN_TEMP_DYING) ? 1 : 0);
490 SetResource(targ, RES_HEALTH, ((frozen_type == FROZEN_TEMP_DYING) ? targ_maxhealth : 1));
491 targ.revive_speed = revivespeed;
493 IL_REMOVE(g_bot_targets, targ);
494 targ.bot_attack = false;
495 targ.freeze_time = time;
497 entity ice = new(ice);
499 ice.scale = targ.scale;
500 // set_movetype(ice, MOVETYPE_FOLLOW) would rotate the ice model with the player
501 setthink(ice, Ice_Think);
502 ice.nextthink = time;
503 ice.frame = floor(random() * 21); // ice model has 20 different looking frames
504 setmodel(ice, MDL_ICE);
506 ice.colormod = Team_ColorRGB(targ.team);
507 ice.glowmod = ice.colormod;
509 targ.revival_time = 0;
513 RemoveGrapplingHooks(targ);
515 FOREACH_CLIENT(IS_PLAYER(it),
517 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
519 .entity weaponentity = weaponentities[slot];
520 if(it.(weaponentity).hook.aiment == targ)
521 RemoveHook(it.(weaponentity).hook);
526 if(MUTATOR_CALLHOOK(Freeze, targ, revivespeed, frozen_type) || show_waypoint)
527 WaypointSprite_Spawn(WP_Frozen, 0, 0, targ, '0 0 64', NULL, targ.team, targ, waypointsprite_attached, true, RADARICON_WAYPOINT);
530 void Unfreeze(entity targ, bool reset_health)
532 if(!STAT(FROZEN, targ))
535 if (reset_health && STAT(FROZEN, targ) != FROZEN_TEMP_DYING)
536 SetResource(targ, RES_HEALTH, ((IS_PLAYER(targ)) ? start_health : targ.max_health));
538 targ.pauseregen_finished = time + autocvar_g_balance_pause_health_regen;
540 STAT(FROZEN, targ) = 0;
541 STAT(REVIVE_PROGRESS, targ) = 0;
542 targ.revival_time = time;
544 IL_PUSH(g_bot_targets, targ);
545 targ.bot_attack = true;
547 WaypointSprite_Kill(targ.waypointsprite_attached);
549 FOREACH_CLIENT(IS_PLAYER(it),
551 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
553 .entity weaponentity = weaponentities[slot];
554 if(it.(weaponentity).hook.aiment == targ)
555 RemoveHook(it.(weaponentity).hook);
559 // remove the ice block
561 delete(targ.iceblock);
562 targ.iceblock = NULL;
564 MUTATOR_CALLHOOK(Unfreeze, targ);
567 void Damage(entity targ, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
569 float complainteamdamage = 0;
570 float mirrordamage = 0;
571 float mirrorforce = 0;
573 if (game_stopped || (IS_CLIENT(targ) && CS(targ).killcount == FRAGS_SPECTATOR))
576 entity attacker_save = attacker;
578 // special rule: gravity bomb does not hit team mates (other than for disconnecting the hook)
579 if(DEATH_ISWEAPON(deathtype, WEP_HOOK) || DEATH_ISWEAPON(deathtype, WEP_TUBA))
581 if(IS_PLAYER(targ) && SAME_TEAM(targ, attacker))
587 if(deathtype == DEATH_KILL.m_id || deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id)
589 // exit the vehicle before killing (fixes a crash)
590 if(IS_PLAYER(targ) && targ.vehicle)
591 vehicles_exit(targ.vehicle, VHEF_RELEASE);
593 // These are ALWAYS lethal
594 // No damage modification here
595 // Instead, prepare the victim for his death...
596 SetResourceExplicit(targ, RES_ARMOR, 0);
597 targ.spawnshieldtime = 0;
598 SetResourceExplicit(targ, RES_HEALTH, 0.9); // this is < 1
599 targ.flags -= targ.flags & FL_GODMODE;
602 else if(deathtype == DEATH_MIRRORDAMAGE.m_id || deathtype == DEATH_NOAMMO.m_id)
608 // nullify damage if teamplay is on
609 if(deathtype != DEATH_TELEFRAG.m_id)
610 if(IS_PLAYER(attacker))
612 if(IS_PLAYER(targ) && targ != attacker && (IS_INDEPENDENT_PLAYER(attacker) || IS_INDEPENDENT_PLAYER(targ)))
617 else if(SAME_TEAM(attacker, targ))
619 if(autocvar_teamplay_mode == 1)
621 else if(attacker != targ)
623 if(autocvar_teamplay_mode == 2)
625 if(IS_PLAYER(targ) && !IS_DEAD(targ))
627 attacker.dmg_team = attacker.dmg_team + damage;
628 complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold;
631 else if(autocvar_teamplay_mode == 3)
633 else if(autocvar_teamplay_mode == 4)
635 if(IS_PLAYER(targ) && !IS_DEAD(targ))
637 attacker.dmg_team = attacker.dmg_team + damage;
638 complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold;
639 if(complainteamdamage > 0)
640 mirrordamage = autocvar_g_mirrordamage * complainteamdamage;
641 mirrorforce = autocvar_g_mirrordamage * vlen(force);
642 damage = autocvar_g_friendlyfire * damage;
643 // mirrordamage will be used LATER
645 if(autocvar_g_mirrordamage_virtual)
647 vector v = healtharmor_applydamage(GetResource(attacker, RES_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, mirrordamage);
648 attacker.dmg_take += v.x;
649 attacker.dmg_save += v.y;
650 attacker.dmg_inflictor = inflictor;
655 if(autocvar_g_friendlyfire_virtual)
657 vector v = healtharmor_applydamage(GetResource(targ, RES_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, damage);
658 targ.dmg_take += v.x;
659 targ.dmg_save += v.y;
660 targ.dmg_inflictor = inflictor;
662 if(!autocvar_g_friendlyfire_virtual_force)
666 else if(!targ.canteamdamage)
673 if (!DEATH_ISSPECIAL(deathtype))
675 damage *= g_weapondamagefactor;
676 mirrordamage *= g_weapondamagefactor;
677 complainteamdamage *= g_weapondamagefactor;
678 force = force * g_weaponforcefactor;
679 mirrorforce *= g_weaponforcefactor;
682 // should this be changed at all? If so, in what way?
683 MUTATOR_CALLHOOK(Damage_Calculate, inflictor, attacker, targ, deathtype, damage, mirrordamage, force, attacker.(weaponentity));
684 damage = M_ARGV(4, float);
685 mirrordamage = M_ARGV(5, float);
686 force = M_ARGV(6, vector);
688 if(IS_PLAYER(targ) && damage > 0 && attacker)
690 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
692 .entity went = weaponentities[slot];
693 if(targ.(went).hook && targ.(went).hook.aiment == attacker)
694 RemoveHook(targ.(went).hook);
698 if(STAT(FROZEN, targ) && !ITEM_DAMAGE_NEEDKILL(deathtype)
699 && deathtype != DEATH_TEAMCHANGE.m_id && deathtype != DEATH_AUTOTEAMCHANGE.m_id)
701 if(autocvar_g_frozen_revive_falldamage > 0 && deathtype == DEATH_FALL.m_id && damage >= autocvar_g_frozen_revive_falldamage)
703 Unfreeze(targ, false);
704 SetResource(targ, RES_HEALTH, autocvar_g_frozen_revive_falldamage_health);
705 Send_Effect(EFFECT_ICEORGLASS, targ.origin, '0 0 0', 3);
706 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, targ.netname);
707 Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF);
711 force *= autocvar_g_frozen_force;
714 if(IS_PLAYER(targ) && STAT(FROZEN, targ)
715 && ITEM_DAMAGE_NEEDKILL(deathtype) && !autocvar_g_frozen_damage_trigger)
717 Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1);
719 entity spot = SelectSpawnPoint(targ, false);
723 targ.deadflag = DEAD_NO;
725 targ.angles = spot.angles;
728 targ.effects |= EF_TELEPORT_BIT;
730 targ.angles_z = 0; // never spawn tilted even if the spot says to
731 targ.fixangle = true; // turn this way immediately
732 targ.velocity = '0 0 0';
733 targ.avelocity = '0 0 0';
734 targ.punchangle = '0 0 0';
735 targ.punchvector = '0 0 0';
736 targ.oldvelocity = targ.velocity;
738 targ.spawnorigin = spot.origin;
739 setorigin(targ, spot.origin + '0 0 1' * (1 - targ.mins.z - 24));
740 // don't reset back to last position, even if new position is stuck in solid
741 targ.oldorigin = targ.origin;
743 Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1);
747 if(!MUTATOR_IS_ENABLED(mutator_instagib))
749 // apply strength multiplier
750 if (attacker.items & ITEM_Strength.m_itemid)
754 damage = damage * autocvar_g_balance_powerup_strength_selfdamage;
755 force = force * autocvar_g_balance_powerup_strength_selfforce;
759 damage = damage * autocvar_g_balance_powerup_strength_damage;
760 force = force * autocvar_g_balance_powerup_strength_force;
764 // apply invincibility multiplier
765 if (targ.items & ITEM_Shield.m_itemid)
767 damage = damage * autocvar_g_balance_powerup_invincible_takedamage;
768 if (targ != attacker)
770 force = force * autocvar_g_balance_powerup_invincible_takeforce;
775 if (targ == attacker)
776 damage = damage * autocvar_g_balance_selfdamagepercent; // Partial damage if the attacker hits himself
781 if(deathtype != DEATH_BUFF.m_id)
782 if(targ.takedamage == DAMAGE_AIM)
786 if(IS_VEHICLE(targ) && targ.owner)
791 if(IS_PLAYER(victim) || (IS_TURRET(victim) && victim.active == ACTIVE_ACTIVE) || IS_MONSTER(victim) || MUTATOR_CALLHOOK(PlayHitsound, victim, attacker))
793 if(DIFF_TEAM(victim, attacker) && !STAT(FROZEN, victim))
797 if(deathtype != DEATH_FIRE.m_id)
799 if(PHYS_INPUT_BUTTON_CHAT(victim))
800 attacker.typehitsound += 1;
802 attacker.damage_dealt += damage;
805 damage_goodhits += 1;
806 damage_gooddamage += damage;
808 if (!DEATH_ISSPECIAL(deathtype))
810 if(IS_PLAYER(targ)) // don't do this for vehicles
816 else if(IS_PLAYER(attacker))
818 // if enemy gets frozen in this frame and receives other damage don't
819 // play the typehitsound e.g. when hit by multiple bullets of the shotgun
820 if (deathtype != DEATH_FIRE.m_id && (!STAT(FROZEN, victim) || time > victim.freeze_time))
822 attacker.typehitsound += 1;
824 if(complainteamdamage > 0)
825 if(time > CS(attacker).teamkill_complain)
827 CS(attacker).teamkill_complain = time + 5;
828 CS(attacker).teamkill_soundtime = time + 0.4;
829 CS(attacker).teamkill_soundsource = targ;
837 if (targ.damageforcescale)
839 if (!IS_PLAYER(targ) || time >= targ.spawnshieldtime || targ == attacker)
841 vector farce = damage_explosion_calcpush(targ.damageforcescale * force, targ.velocity, autocvar_g_balance_damagepush_speedfactor);
842 if(targ.move_movetype == MOVETYPE_PHYSICS)
844 entity farcent = new(farce);
845 farcent.enemy = targ;
846 farcent.movedir = farce * 10;
848 farcent.movedir = farcent.movedir * targ.mass;
849 farcent.origin = hitloc;
850 farcent.forcetype = FORCETYPE_FORCEATPOS;
851 farcent.nextthink = time + 0.1;
852 setthink(farcent, SUB_Remove);
856 targ.velocity = targ.velocity + farce;
858 UNSET_ONGROUND(targ);
859 UpdateCSQCProjectile(targ);
862 if (damage != 0 || (targ.damageforcescale && force))
863 if (targ.event_damage)
864 targ.event_damage (targ, inflictor, attacker, damage, deathtype, weaponentity, hitloc, force);
866 // apply mirror damage if any
867 if(!autocvar_g_mirrordamage_onlyweapons || DEATH_WEAPONOF(deathtype) != WEP_Null)
868 if(mirrordamage > 0 || mirrorforce > 0)
870 attacker = attacker_save;
872 force = normalize(attacker.origin + attacker.view_ofs - hitloc) * mirrorforce;
873 Damage(attacker, inflictor, attacker, mirrordamage, DEATH_MIRRORDAMAGE.m_id, weaponentity, attacker.origin, force);
877 float RadiusDamageForSource (entity inflictor, vector inflictororigin, vector inflictorvelocity, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe,
878 float inflictorselfdamage, float forceintensity, float forcezscale, int deathtype, .entity weaponentity, entity directhitentity)
879 // Returns total damage applies to creatures
883 float total_damage_to_creatures;
888 float stat_damagedone;
890 if(RadiusDamage_running)
892 backtrace("RadiusDamage called recursively! Expect stuff to go HORRIBLY wrong.");
896 RadiusDamage_running = 1;
898 tfloordmg = autocvar_g_throughfloor_damage;
899 tfloorforce = autocvar_g_throughfloor_force;
901 total_damage_to_creatures = 0;
903 if(deathtype != (WEP_HOOK.m_id | HITTYPE_SECONDARY | HITTYPE_BOUNCE)) // only send gravity bomb damage once
904 if(DEATH_WEAPONOF(deathtype) != WEP_TUBA) // do not send tuba damage (bandwidth hog)
906 force = inflictorvelocity;
910 force = normalize(force);
911 if(forceintensity >= 0)
912 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, rad, forceintensity * force, deathtype, 0, attacker);
914 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, -rad, (-forceintensity) * force, deathtype, 0, attacker);
919 targ = WarpZone_FindRadius (inflictororigin, rad + MAX_DAMAGEEXTRARADIUS, false);
923 if ((targ != inflictor) || inflictorselfdamage)
924 if (((cantbe != targ) && !mustbe) || (mustbe == targ))
931 // LordHavoc: measure distance to nearest point on target (not origin)
932 // (this guarentees 100% damage on a touch impact)
933 nearest = targ.WarpZone_findradius_nearest;
934 diff = targ.WarpZone_findradius_dist;
935 // round up a little on the damage to ensure full damage on impacts
936 // and turn the distance into a fraction of the radius
937 power = 1 - ((vlen (diff) - bound(MIN_DAMAGEEXTRARADIUS, targ.damageextraradius, MAX_DAMAGEEXTRARADIUS)) / rad);
939 //bprint(ftos(power));
940 //if (targ == attacker)
941 // print(ftos(power), "\n");
947 finaldmg = coredamage * power + edgedamage * (1 - power);
953 vector myblastorigin;
956 myblastorigin = WarpZone_TransformOrigin(targ, inflictororigin);
958 // if it's a player, use the view origin as reference
959 center = CENTER_OR_VIEWOFS(targ);
961 force = normalize(center - myblastorigin);
962 force = force * (finaldmg / coredamage) * forceintensity;
965 // apply special scaling along the z axis if set
966 // NOTE: 0 value is not allowed for compatibility, in the case of weapon cvars not being set
968 force.z *= forcezscale;
970 if(targ != directhitentity)
975 float mininv_f, mininv_d;
977 // test line of sight to multiple positions on box,
978 // and do damage if any of them hit
981 // we know: max stddev of hitratio = 1 / (2 * sqrt(n))
982 // so for a given max stddev:
983 // n = (1 / (2 * max stddev of hitratio))^2
985 mininv_d = (finaldmg * (1-tfloordmg)) / autocvar_g_throughfloor_damage_max_stddev;
986 mininv_f = (vlen(force) * (1-tfloorforce)) / autocvar_g_throughfloor_force_max_stddev;
988 if(autocvar_g_throughfloor_debug)
989 LOG_INFOF("THROUGHFLOOR: D=%f F=%f max(dD)=1/%f max(dF)=1/%f", finaldmg, vlen(force), mininv_d, mininv_f);
992 total = 0.25 * (max(mininv_f, mininv_d) ** 2);
994 if(autocvar_g_throughfloor_debug)
995 LOG_INFOF(" steps=%f", total);
999 total = ceil(bound(autocvar_g_throughfloor_min_steps_player, total, autocvar_g_throughfloor_max_steps_player));
1001 total = ceil(bound(autocvar_g_throughfloor_min_steps_other, total, autocvar_g_throughfloor_max_steps_other));
1003 if(autocvar_g_throughfloor_debug)
1004 LOG_INFOF(" steps=%f dD=%f dF=%f", total, finaldmg * (1-tfloordmg) / (2 * sqrt(total)), vlen(force) * (1-tfloorforce) / (2 * sqrt(total)));
1006 for(c = 0; c < total; ++c)
1008 //traceline(targ.WarpZone_findradius_findorigin, nearest, MOVE_NOMONSTERS, inflictor);
1009 WarpZone_TraceLine(inflictororigin, WarpZone_UnTransformOrigin(targ, nearest), MOVE_NOMONSTERS, inflictor);
1010 if (trace_fraction == 1 || trace_ent == targ)
1014 hitloc = hitloc + nearest;
1018 nearest.x = targ.origin.x + targ.mins.x + random() * targ.size.x;
1019 nearest.y = targ.origin.y + targ.mins.y + random() * targ.size.y;
1020 nearest.z = targ.origin.z + targ.mins.z + random() * targ.size.z;
1023 nearest = hitloc * (1 / max(1, hits));
1024 hitratio = (hits / total);
1025 a = bound(0, tfloordmg + (1-tfloordmg) * hitratio, 1);
1026 finaldmg = finaldmg * a;
1027 a = bound(0, tfloorforce + (1-tfloorforce) * hitratio, 1);
1030 if(autocvar_g_throughfloor_debug)
1031 LOG_INFOF(" D=%f F=%f", finaldmg, vlen(force));
1034 //if (targ == attacker)
1036 // print("hits ", ftos(hits), " / ", ftos(total));
1037 // print(" finaldmg ", ftos(finaldmg), " force ", vtos(force));
1038 // print(" (", ftos(a), ")\n");
1040 if(finaldmg || force)
1044 total_damage_to_creatures += finaldmg;
1046 if(accuracy_isgooddamage(attacker, targ))
1047 stat_damagedone += finaldmg;
1050 if(targ == directhitentity || DEATH_ISSPECIAL(deathtype))
1051 Damage(targ, inflictor, attacker, finaldmg, deathtype, weaponentity, nearest, force);
1053 Damage(targ, inflictor, attacker, finaldmg, deathtype | HITTYPE_SPLASH, weaponentity, nearest, force);
1061 RadiusDamage_running = 0;
1063 if(!DEATH_ISSPECIAL(deathtype))
1064 accuracy_add(attacker, DEATH_WEAPONOF(deathtype), 0, min(coredamage, stat_damagedone));
1066 return total_damage_to_creatures;
1069 float RadiusDamage(entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float forceintensity, int deathtype, .entity weaponentity, entity directhitentity)
1071 return RadiusDamageForSource(inflictor, (inflictor.origin + (inflictor.mins + inflictor.maxs) * 0.5), inflictor.velocity, attacker, coredamage, edgedamage, rad,
1072 cantbe, mustbe, false, forceintensity, 1, deathtype, weaponentity, directhitentity);
1075 bool Heal(entity targ, entity inflictor, float amount, float limit)
1077 if(game_stopped || (IS_CLIENT(targ) && CS(targ).killcount == FRAGS_SPECTATOR) || STAT(FROZEN, targ) || IS_DEAD(targ))
1080 bool healed = false;
1082 healed = targ.event_heal(targ, inflictor, amount, limit);
1083 // TODO: additional handling? what if the healing kills them? should this abort if healing would do so etc
1084 // TODO: healing fx!
1085 // TODO: armor healing?
1089 float Fire_IsBurning(entity e)
1091 return (time < e.fire_endtime);
1094 float Fire_AddDamage(entity e, entity o, float d, float t, float dt)
1097 float maxtime, mintime, maxdamage, mindamage, maxdps, mindps, totaldamage, totaltime;
1108 // print("adding a fire burner to ", e.classname, "\n");
1109 e.fire_burner = new(fireburner);
1110 setthink(e.fire_burner, fireburner_think);
1111 e.fire_burner.nextthink = time;
1112 e.fire_burner.owner = e;
1118 if(Fire_IsBurning(e))
1120 mintime = e.fire_endtime - time;
1121 maxtime = max(mintime, t);
1123 mindps = e.fire_damagepersec;
1124 maxdps = max(mindps, dps);
1126 if(maxtime > mintime || maxdps > mindps)
1130 // damage we have right now
1131 mindamage = mindps * mintime;
1133 // damage we want to get
1134 maxdamage = mindamage + d;
1136 // but we can't exceed maxtime * maxdps!
1137 totaldamage = min(maxdamage, maxtime * maxdps);
1141 // totaldamage = min(mindamage + d, maxtime * maxdps)
1143 // totaldamage <= maxtime * maxdps
1144 // ==> totaldamage / maxdps <= maxtime.
1146 // totaldamage / mindps = min(mindamage / mindps + d, maxtime * maxdps / mindps)
1147 // >= min(mintime, maxtime)
1148 // ==> totaldamage / maxdps >= mintime.
1151 // how long do we damage then?
1152 // at least as long as before
1153 // but, never exceed maxdps
1154 totaltime = max(mintime, totaldamage / maxdps); // always <= maxtime due to lemma
1158 // at most as long as maximum allowed
1159 // but, never below mindps
1160 totaltime = min(maxtime, totaldamage / mindps); // always >= mintime due to lemma
1162 // assuming t > mintime, dps > mindps:
1163 // we get d = t * dps = maxtime * maxdps
1164 // totaldamage = min(maxdamage, maxtime * maxdps) = min(... + d, maxtime * maxdps) = maxtime * maxdps
1165 // totaldamage / maxdps = maxtime
1166 // totaldamage / mindps > totaldamage / maxdps = maxtime
1168 // a) totaltime = max(mintime, maxtime) = maxtime
1169 // b) totaltime = min(maxtime, totaldamage / maxdps) = maxtime
1171 // assuming t <= mintime:
1172 // we get maxtime = mintime
1173 // a) totaltime = max(mintime, ...) >= mintime, also totaltime <= maxtime by the lemma, therefore totaltime = mintime = maxtime
1174 // b) totaltime = min(maxtime, ...) <= maxtime, also totaltime >= mintime by the lemma, therefore totaltime = mintime = maxtime
1176 // assuming dps <= mindps:
1177 // we get mindps = maxdps.
1178 // With this, the lemma says that mintime <= totaldamage / mindps = totaldamage / maxdps <= maxtime.
1179 // a) totaltime = max(mintime, totaldamage / maxdps) = totaldamage / maxdps
1180 // b) totaltime = min(maxtime, totaldamage / mindps) = totaldamage / maxdps
1182 e.fire_damagepersec = totaldamage / totaltime;
1183 e.fire_endtime = time + totaltime;
1184 if(totaldamage > 1.2 * mindamage)
1186 e.fire_deathtype = dt;
1187 if(e.fire_owner != o)
1190 e.fire_hitsound = false;
1193 if(accuracy_isgooddamage(o, e))
1194 accuracy_add(o, DEATH_WEAPONOF(dt), 0, max(0, totaldamage - mindamage));
1195 return max(0, totaldamage - mindamage); // can never be negative, but to make sure
1202 e.fire_damagepersec = dps;
1203 e.fire_endtime = time + t;
1204 e.fire_deathtype = dt;
1206 e.fire_hitsound = false;
1207 if(accuracy_isgooddamage(o, e))
1208 accuracy_add(o, DEATH_WEAPONOF(dt), 0, d);
1213 void Fire_ApplyDamage(entity e)
1218 if (!Fire_IsBurning(e))
1221 for(t = 0, o = e.owner; o.owner && t < 16; o = o.owner, ++t);
1222 if(IS_NOT_A_CLIENT(o))
1225 // water and slime stop fire
1227 if(e.watertype != CONTENT_LAVA)
1234 t = min(frametime, e.fire_endtime - time);
1235 d = e.fire_damagepersec * t;
1237 hi = e.fire_owner.damage_dealt;
1238 ty = e.fire_owner.typehitsound;
1239 Damage(e, e, e.fire_owner, d, e.fire_deathtype, DMG_NOWEP, e.origin, '0 0 0');
1240 if(e.fire_hitsound && e.fire_owner)
1242 e.fire_owner.damage_dealt = hi;
1243 e.fire_owner.typehitsound = ty;
1245 e.fire_hitsound = true;
1247 if(!IS_INDEPENDENT_PLAYER(e) && !STAT(FROZEN, e))
1249 IL_EACH(g_damagedbycontents, it.damagedbycontents && it != e,
1251 if(!IS_DEAD(it) && it.takedamage && !IS_INDEPENDENT_PLAYER(it))
1252 if(boxesoverlap(e.absmin, e.absmax, it.absmin, it.absmax))
1254 t = autocvar_g_balance_firetransfer_time * (e.fire_endtime - time);
1255 d = autocvar_g_balance_firetransfer_damage * e.fire_damagepersec * t;
1256 Fire_AddDamage(it, o, d, t, DEATH_FIRE.m_id);
1262 void Fire_ApplyEffect(entity e)
1264 if(Fire_IsBurning(e))
1265 e.effects |= EF_FLAME;
1267 e.effects &= ~EF_FLAME;
1270 void fireburner_think(entity this)
1272 // for players, this is done in the regular loop
1273 if(wasfreed(this.owner))
1278 Fire_ApplyEffect(this.owner);
1279 if(!Fire_IsBurning(this.owner))
1281 this.owner.fire_burner = NULL;
1285 Fire_ApplyDamage(this.owner);
1286 this.nextthink = time;