3 #include <common/constants.qh>
4 #include <common/deathtypes/all.qh>
5 #include <common/effects/all.qh>
6 #include <common/gamemodes/_mod.qh>
7 #include <common/gamemodes/rules.qh>
8 #include <common/items/_mod.qh>
9 #include <common/mapobjects/defs.qh>
10 #include <common/mapobjects/triggers.qh>
11 #include <common/mutators/mutator/buffs/buffs.qh>
12 #include <common/mutators/mutator/buffs/sv_buffs.qh>
13 #include <common/mutators/mutator/instagib/sv_instagib.qh>
14 #include <common/mutators/mutator/status_effects/_mod.qh>
15 #include <common/mutators/mutator/waypoints/waypointsprites.qh>
16 #include <common/notifications/all.qh>
17 #include <common/physics/movetypes/movetypes.qh>
18 #include <common/physics/player.qh>
19 #include <common/playerstats.qh>
20 #include <common/state.qh>
21 #include <common/teams.qh>
22 #include <common/util.qh>
23 #include <common/vehicles/all.qh>
24 #include <common/weapons/_all.qh>
25 #include <lib/csqcmodel/sv_model.qh>
26 #include <lib/warpzone/common.qh>
27 #include <server/bot/api.qh>
28 #include <server/client.qh>
29 #include <server/gamelog.qh>
30 #include <server/hook.qh>
31 #include <server/items/items.qh>
32 #include <server/main.qh>
33 #include <server/mutators/_mod.qh>
34 #include <server/resources.qh>
35 #include <server/scores.qh>
36 #include <server/spawnpoints.qh>
37 #include <server/teamplay.qh>
38 #include <server/weapons/accuracy.qh>
39 #include <server/weapons/csqcprojectile.qh>
40 #include <server/weapons/selection.qh>
41 #include <server/weapons/weaponsystem.qh>
42 #include <server/world.qh>
44 void UpdateFrags(entity player, int f)
46 GameRules_scoring_add_team(player, SCORE, f);
49 void GiveFrags(entity attacker, entity targ, float f, int deathtype, .entity weaponentity)
51 // TODO route through PlayerScores instead
52 if(game_stopped) return;
59 GameRules_scoring_add(attacker, SUICIDES, 1);
64 GameRules_scoring_add(attacker, TEAMKILLS, 1);
70 GameRules_scoring_add(attacker, KILLS, 1);
71 if(!warmup_stage && targ.playerid)
72 PlayerStats_GameReport_Event_Player(attacker, sprintf("kills-%d", targ.playerid), 1);
75 GameRules_scoring_add(targ, DEATHS, 1);
77 // FIXME fix the mess this is (we have REAL points now!)
78 if(MUTATOR_CALLHOOK(GiveFragsForKill, attacker, targ, f, deathtype, attacker.(weaponentity)))
81 attacker.totalfrags += f;
84 UpdateFrags(attacker, f);
87 string AppendItemcodes(string s, entity player)
89 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
91 .entity weaponentity = weaponentities[slot];
92 int w = player.(weaponentity).m_weapon.m_id;
94 w = player.(weaponentity).cnt; // previous weapon
95 if(w != 0 || slot == 0)
96 s = strcat(s, ftos(w));
98 if(StatusEffects_active(STATUSEFFECT_Strength, player))
100 if(StatusEffects_active(STATUSEFFECT_Shield, player))
102 if(PHYS_INPUT_BUTTON_CHAT(player))
104 // TODO: include these codes as a flag on the item itself
105 MUTATOR_CALLHOOK(LogDeath_AppendItemCodes, player, s);
106 s = M_ARGV(1, string);
110 void LogDeath(string mode, int deathtype, entity killer, entity killed)
113 if(!autocvar_sv_eventlog)
115 s = strcat(":kill:", mode);
116 s = strcat(s, ":", ftos(killer.playerid));
117 s = strcat(s, ":", ftos(killed.playerid));
118 s = strcat(s, ":type=", Deathtype_Name(deathtype));
119 s = strcat(s, ":items=");
120 s = AppendItemcodes(s, killer);
123 s = strcat(s, ":victimitems=");
124 s = AppendItemcodes(s, killed);
129 void Obituary_SpecialDeath(
133 string s1, string s2, string s3,
134 float f1, float f2, float f3)
136 if(!DEATH_ISSPECIAL(deathtype))
138 backtrace("Obituary_SpecialDeath called without a special deathtype?\n");
142 entity deathent = REGISTRY_GET(Deathtypes, deathtype - DT_FIRST);
145 backtrace("Obituary_SpecialDeath: Could not find deathtype entity!\n");
149 if(g_cts && deathtype == DEATH_KILL.m_id)
150 return; // TODO: somehow put this in CTS gamemode file!
152 Notification death_message = (murder) ? deathent.death_msgmurder : deathent.death_msgself;
155 Send_Notification_WOCOVA(
163 Send_Notification_WOCOVA(
167 death_message.nent_msginfo,
174 float Obituary_WeaponDeath(
178 string s1, string s2, string s3,
181 Weapon death_weapon = DEATH_WEAPONOF(deathtype);
182 if (death_weapon == WEP_Null)
185 w_deathtype = deathtype;
186 Notification death_message = ((murder) ? death_weapon.wr_killmessage(death_weapon) : death_weapon.wr_suicidemessage(death_weapon));
191 Send_Notification_WOCOVA(
199 // send the info part to everyone
200 Send_Notification_WOCOVA(
204 death_message.nent_msginfo,
212 "Obituary_WeaponDeath(): ^1Deathtype ^7(%d)^1 has no notification for weapon %s!\n",
221 bool frag_centermessage_override(entity attacker, entity targ, int deathtype, int kill_count_to_attacker, int kill_count_to_target, string attacker_name)
223 if(deathtype == DEATH_FIRE.m_id)
225 Send_Notification(NOTIF_ONE, attacker, MSG_CHOICE, CHOICE_FRAG_FIRE, targ.netname, kill_count_to_attacker, (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping));
226 Send_Notification(NOTIF_ONE, targ, MSG_CHOICE, CHOICE_FRAGGED_FIRE, attacker_name, kill_count_to_target, GetResource(attacker, RES_HEALTH), GetResource(attacker, RES_ARMOR), (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping));
230 return MUTATOR_CALLHOOK(FragCenterMessage, attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target);
233 void Obituary(entity attacker, entity inflictor, entity targ, int deathtype, .entity weaponentity)
236 if (!IS_PLAYER(targ)) { backtrace("Obituary called on non-player?!\n"); return; }
239 float notif_firstblood = false;
240 float kill_count_to_attacker, kill_count_to_target;
241 bool notif_anonymous = false;
242 string attacker_name = attacker.netname;
244 // Set final information for the death
245 targ.death_origin = targ.origin;
246 string deathlocation = (autocvar_notification_server_allows_location ? NearestLocation(targ.death_origin) : "");
248 // Abort now if a mutator requests it
249 if (MUTATOR_CALLHOOK(ClientObituary, inflictor, attacker, targ, deathtype, attacker.(weaponentity))) { CS(targ).killcount = 0; return; }
250 notif_anonymous = M_ARGV(5, bool);
253 attacker_name = "Anonymous player";
255 #ifdef NOTIFICATIONS_DEBUG
258 "Obituary(%s, %s, %s, %s = %d);\n",
262 Deathtype_Name(deathtype),
273 if(DEATH_ISSPECIAL(deathtype))
275 if(deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id)
277 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", targ.team, 0, 0);
281 switch(DEATH_ENT(deathtype))
283 case DEATH_MIRRORDAMAGE:
285 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0);
288 case DEATH_HURTTRIGGER:
289 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, inflictor.message, deathlocation, CS(targ).killcount, 0, 0);
293 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0);
299 else if (!Obituary_WeaponDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0))
301 backtrace("SUICIDE: what the hell happened here?\n");
304 LogDeath("suicide", deathtype, targ, targ);
305 if(deathtype != DEATH_AUTOTEAMCHANGE.m_id) // special case: don't negate frags if auto switched
306 GiveFrags(attacker, targ, -1, deathtype, weaponentity);
312 else if(IS_PLAYER(attacker))
314 if(SAME_TEAM(attacker, targ))
316 LogDeath("tk", deathtype, attacker, targ);
317 GiveFrags(attacker, targ, -1, deathtype, weaponentity);
319 CS(attacker).killcount = 0;
321 Send_Notification(NOTIF_ONE, attacker, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAG, targ.netname);
322 Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAGGED, attacker_name);
323 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(targ.team, INFO_DEATH_TEAMKILL), targ.netname, attacker_name, deathlocation, CS(targ).killcount);
325 // In this case, the death message will ALWAYS be "foo was betrayed by bar"
326 // No need for specific death/weapon messages...
330 LogDeath("frag", deathtype, attacker, targ);
331 GiveFrags(attacker, targ, 1, deathtype, weaponentity);
333 CS(attacker).taunt_soundtime = time + 1;
334 CS(attacker).killcount = CS(attacker).killcount + 1;
336 attacker.killsound += 1;
338 // TODO: improve SPREE_ITEM and KILL_SPREE_LIST
339 // these 2 macros are spread over multiple files
340 #define SPREE_ITEM(counta,countb,center,normal,gentle) \
342 Send_Notification(NOTIF_ONE, attacker, MSG_ANNCE, ANNCE_KILLSTREAK_##countb); \
344 PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_##counta, 1); \
347 switch(CS(attacker).killcount)
354 if(!warmup_stage && !checkrules_firstblood)
356 checkrules_firstblood = true;
357 notif_firstblood = true; // modify the current messages so that they too show firstblood information
358 PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD, 1);
359 PlayerStats_GameReport_Event_Player(targ, PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM, 1);
361 // tell spree_inf and spree_cen that this is a first-blood and first-victim event
362 kill_count_to_attacker = -1;
363 kill_count_to_target = -2;
367 kill_count_to_attacker = CS(attacker).killcount;
368 kill_count_to_target = 0;
379 kill_count_to_attacker,
380 (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping)
388 kill_count_to_target,
389 GetResource(attacker, RES_HEALTH),
390 GetResource(attacker, RES_ARMOR),
391 (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping)
394 else if(!frag_centermessage_override(attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target, attacker_name))
402 kill_count_to_attacker,
403 (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping)
411 kill_count_to_target,
412 GetResource(attacker, RES_HEALTH),
413 GetResource(attacker, RES_ARMOR),
414 (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping)
419 if(deathtype == DEATH_BUFF.m_id)
420 f3 = buff_FirstFromFlags(attacker).m_id;
422 if (!Obituary_WeaponDeath(targ, true, deathtype, targ.netname, attacker_name, deathlocation, CS(targ).killcount, kill_count_to_attacker))
423 Obituary_SpecialDeath(targ, true, deathtype, targ.netname, attacker_name, deathlocation, CS(targ).killcount, kill_count_to_attacker, f3);
432 switch(DEATH_ENT(deathtype))
434 // For now, we're just forcing HURTTRIGGER to behave as "DEATH_VOID" and giving it no special options...
435 // Later on you will only be able to make custom messages using DEATH_CUSTOM,
436 // and there will be a REAL DEATH_VOID implementation which mappers will use.
437 case DEATH_HURTTRIGGER:
439 Obituary_SpecialDeath(targ, false, deathtype,
451 Obituary_SpecialDeath(targ, false, deathtype,
453 ((strstrofs(deathmessage, "%", 0) < 0) ? strcat("%s ", deathmessage) : deathmessage),
463 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0);
468 LogDeath("accident", deathtype, targ, targ);
469 GiveFrags(targ, targ, -1, deathtype, weaponentity);
471 if(GameRules_scoring_add(targ, SCORE, 0) == -5)
473 Send_Notification(NOTIF_ONE, targ, MSG_ANNCE, ANNCE_ACHIEVEMENT_BOTLIKE);
476 PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_BOTLIKE, 1);
481 // reset target kill count
482 CS(targ).killcount = 0;
485 void Ice_Think(entity this)
487 if(!STAT(FROZEN, this.owner) || this.owner.iceblock != this)
492 vector ice_org = this.owner.origin - '0 0 16';
493 if (this.origin != ice_org)
494 setorigin(this, ice_org);
495 this.nextthink = time;
498 void Freeze(entity targ, float revivespeed, int frozen_type, bool show_waypoint)
500 if(!IS_PLAYER(targ) && !IS_MONSTER(targ)) // TODO: only specified entities can be freezed
503 if(STAT(FROZEN, targ))
506 float targ_maxhealth = ((IS_MONSTER(targ)) ? targ.max_health : start_health);
508 STAT(FROZEN, targ) = frozen_type;
509 STAT(REVIVE_PROGRESS, targ) = ((frozen_type == FROZEN_TEMP_DYING) ? 1 : 0);
510 SetResource(targ, RES_HEALTH, ((frozen_type == FROZEN_TEMP_DYING) ? targ_maxhealth : 1));
511 targ.revive_speed = revivespeed;
513 IL_REMOVE(g_bot_targets, targ);
514 targ.bot_attack = false;
515 targ.freeze_time = time;
517 entity ice = new(ice);
519 ice.scale = targ.scale;
520 // set_movetype(ice, MOVETYPE_FOLLOW) would rotate the ice model with the player
521 setthink(ice, Ice_Think);
522 ice.nextthink = time;
523 ice.frame = floor(random() * 21); // ice model has 20 different looking frames
524 setmodel(ice, MDL_ICE);
526 ice.colormod = Team_ColorRGB(targ.team);
527 ice.glowmod = ice.colormod;
529 targ.revival_time = 0;
533 RemoveGrapplingHooks(targ);
535 FOREACH_CLIENT(IS_PLAYER(it),
537 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
539 .entity weaponentity = weaponentities[slot];
540 if(it.(weaponentity).hook.aiment == targ)
541 RemoveHook(it.(weaponentity).hook);
546 if(MUTATOR_CALLHOOK(Freeze, targ, revivespeed, frozen_type) || show_waypoint)
547 WaypointSprite_Spawn(WP_Frozen, 0, 0, targ, '0 0 64', NULL, targ.team, targ, waypointsprite_attached, true, RADARICON_WAYPOINT);
550 void Unfreeze(entity targ, bool reset_health)
552 if(!STAT(FROZEN, targ))
555 if (reset_health && STAT(FROZEN, targ) != FROZEN_TEMP_DYING)
556 SetResource(targ, RES_HEALTH, ((IS_PLAYER(targ)) ? start_health : targ.max_health));
558 targ.pauseregen_finished = time + autocvar_g_balance_pause_health_regen;
560 STAT(FROZEN, targ) = 0;
561 STAT(REVIVE_PROGRESS, targ) = 0;
562 targ.revival_time = time;
564 IL_PUSH(g_bot_targets, targ);
565 targ.bot_attack = true;
567 WaypointSprite_Kill(targ.waypointsprite_attached);
569 FOREACH_CLIENT(IS_PLAYER(it),
571 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
573 .entity weaponentity = weaponentities[slot];
574 if(it.(weaponentity).hook.aiment == targ)
575 RemoveHook(it.(weaponentity).hook);
579 // remove the ice block
581 delete(targ.iceblock);
582 targ.iceblock = NULL;
584 MUTATOR_CALLHOOK(Unfreeze, targ);
587 void Damage(entity targ, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
589 float complainteamdamage = 0;
590 float mirrordamage = 0;
591 float mirrorforce = 0;
593 if (game_stopped || (IS_CLIENT(targ) && CS(targ).killcount == FRAGS_SPECTATOR))
596 entity attacker_save = attacker;
598 // special rule: gravity bombs and sound-based attacks do not affect team mates (other than for disconnecting the hook)
599 if(DEATH_ISWEAPON(deathtype, WEP_HOOK) || (deathtype & HITTYPE_SOUND))
601 if(IS_PLAYER(targ) && SAME_TEAM(targ, attacker))
607 if(deathtype == DEATH_KILL.m_id || deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id)
609 // exit the vehicle before killing (fixes a crash)
610 if(IS_PLAYER(targ) && targ.vehicle)
611 vehicles_exit(targ.vehicle, VHEF_RELEASE);
613 // These are ALWAYS lethal
614 // No damage modification here
615 // Instead, prepare the victim for his death...
616 if(deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id)
618 SetResourceExplicit(targ, RES_ARMOR, 0);
619 SetResourceExplicit(targ, RES_HEALTH, 0.9); // this is < 1
621 targ.spawnshieldtime = 0;
622 targ.flags -= targ.flags & FL_GODMODE;
625 else if(deathtype == DEATH_MIRRORDAMAGE.m_id || deathtype == DEATH_NOAMMO.m_id)
631 // nullify damage if teamplay is on
632 if(deathtype != DEATH_TELEFRAG.m_id)
633 if(IS_PLAYER(attacker))
635 if(IS_PLAYER(targ) && targ != attacker && (IS_INDEPENDENT_PLAYER(attacker) || IS_INDEPENDENT_PLAYER(targ)))
640 else if(!STAT(FROZEN, targ) && SAME_TEAM(attacker, targ))
642 if(autocvar_teamplay_mode == 1)
644 else if(attacker != targ)
646 if(autocvar_teamplay_mode == 2)
648 if(IS_PLAYER(targ) && !IS_DEAD(targ))
650 attacker.dmg_team = attacker.dmg_team + damage;
651 complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold;
654 else if(autocvar_teamplay_mode == 3)
656 else if(autocvar_teamplay_mode == 4)
658 if(IS_PLAYER(targ) && !IS_DEAD(targ))
660 attacker.dmg_team = attacker.dmg_team + damage;
661 complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold;
662 if(complainteamdamage > 0)
663 mirrordamage = autocvar_g_mirrordamage * complainteamdamage;
664 mirrorforce = autocvar_g_mirrordamage * vlen(force);
665 damage = autocvar_g_friendlyfire * damage;
666 // mirrordamage will be used LATER
668 if(autocvar_g_mirrordamage_virtual)
670 vector v = healtharmor_applydamage(GetResource(attacker, RES_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, mirrordamage);
671 attacker.dmg_take += v.x;
672 attacker.dmg_save += v.y;
673 attacker.dmg_inflictor = inflictor;
678 if(autocvar_g_friendlyfire_virtual)
680 vector v = healtharmor_applydamage(GetResource(targ, RES_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, damage);
681 targ.dmg_take += v.x;
682 targ.dmg_save += v.y;
683 targ.dmg_inflictor = inflictor;
685 if(!autocvar_g_friendlyfire_virtual_force)
689 else if(!targ.canteamdamage)
696 if (!DEATH_ISSPECIAL(deathtype))
698 damage *= autocvar_g_weapondamagefactor;
699 mirrordamage *= autocvar_g_weapondamagefactor;
700 complainteamdamage *= autocvar_g_weapondamagefactor;
701 force = force * autocvar_g_weaponforcefactor;
702 mirrorforce *= autocvar_g_weaponforcefactor;
705 // should this be changed at all? If so, in what way?
706 MUTATOR_CALLHOOK(Damage_Calculate, inflictor, attacker, targ, deathtype, damage, mirrordamage, force, attacker.(weaponentity));
707 damage = M_ARGV(4, float);
708 mirrordamage = M_ARGV(5, float);
709 force = M_ARGV(6, vector);
711 if(IS_PLAYER(targ) && damage > 0 && attacker)
713 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
715 .entity went = weaponentities[slot];
716 if(targ.(went).hook && targ.(went).hook.aiment == attacker)
717 RemoveHook(targ.(went).hook);
721 if(STAT(FROZEN, targ) && !ITEM_DAMAGE_NEEDKILL(deathtype)
722 && deathtype != DEATH_TEAMCHANGE.m_id && deathtype != DEATH_AUTOTEAMCHANGE.m_id)
724 if(autocvar_g_frozen_revive_falldamage > 0 && deathtype == DEATH_FALL.m_id && damage >= autocvar_g_frozen_revive_falldamage)
726 Unfreeze(targ, false);
727 SetResource(targ, RES_HEALTH, autocvar_g_frozen_revive_falldamage_health);
728 Send_Effect(EFFECT_ICEORGLASS, targ.origin, '0 0 0', 3);
729 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, targ.netname);
730 Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF);
734 force *= autocvar_g_frozen_force;
737 if(IS_PLAYER(targ) && STAT(FROZEN, targ)
738 && ITEM_DAMAGE_NEEDKILL(deathtype) && !autocvar_g_frozen_damage_trigger)
740 Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1);
742 entity spot = SelectSpawnPoint(targ, false);
746 targ.deadflag = DEAD_NO;
748 targ.angles = spot.angles;
751 targ.effects |= EF_TELEPORT_BIT;
753 targ.angles_z = 0; // never spawn tilted even if the spot says to
754 targ.fixangle = true; // turn this way immediately
755 targ.velocity = '0 0 0';
756 targ.avelocity = '0 0 0';
757 targ.punchangle = '0 0 0';
758 targ.punchvector = '0 0 0';
759 targ.oldvelocity = targ.velocity;
761 targ.spawnorigin = spot.origin;
762 setorigin(targ, spot.origin + '0 0 1' * (1 - targ.mins.z - 24));
763 // don't reset back to last position, even if new position is stuck in solid
764 targ.oldorigin = targ.origin;
766 Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1);
770 if(!MUTATOR_IS_ENABLED(mutator_instagib))
772 // apply strength multiplier
773 if (attacker.items & ITEM_Strength.m_itemid)
777 damage = damage * autocvar_g_balance_powerup_strength_selfdamage;
778 force = force * autocvar_g_balance_powerup_strength_selfforce;
782 damage = damage * autocvar_g_balance_powerup_strength_damage;
783 force = force * autocvar_g_balance_powerup_strength_force;
787 // apply invincibility multiplier
788 if (targ.items & ITEM_Shield.m_itemid)
790 damage = damage * autocvar_g_balance_powerup_invincible_takedamage;
791 if (targ != attacker)
793 force = force * autocvar_g_balance_powerup_invincible_takeforce;
798 if (targ == attacker)
799 damage = damage * autocvar_g_balance_selfdamagepercent; // Partial damage if the attacker hits himself
804 if(deathtype != DEATH_BUFF.m_id)
805 if(targ.takedamage == DAMAGE_AIM)
809 if(IS_VEHICLE(targ) && targ.owner)
814 if(IS_PLAYER(victim) || (IS_TURRET(victim) && victim.active == ACTIVE_ACTIVE) || IS_MONSTER(victim) || MUTATOR_CALLHOOK(PlayHitsound, victim, attacker))
816 if (DIFF_TEAM(victim, attacker))
820 if(deathtype != DEATH_FIRE.m_id)
822 if(PHYS_INPUT_BUTTON_CHAT(victim))
823 attacker.typehitsound += 1;
825 attacker.damage_dealt += damage;
828 impressive_hits += 1;
830 if (!DEATH_ISSPECIAL(deathtype))
832 if(IS_PLAYER(targ)) // don't do this for vehicles
838 else if (IS_PLAYER(attacker) && !STAT(FROZEN, victim)) // same team
840 if (deathtype != DEATH_FIRE.m_id)
842 attacker.typehitsound += 1;
844 if(complainteamdamage > 0)
845 if(time > CS(attacker).teamkill_complain)
847 CS(attacker).teamkill_complain = time + 5;
848 CS(attacker).teamkill_soundtime = time + 0.4;
849 CS(attacker).teamkill_soundsource = targ;
857 if (targ.damageforcescale)
859 if (!IS_PLAYER(targ) || time >= targ.spawnshieldtime || targ == attacker)
861 vector farce = damage_explosion_calcpush(targ.damageforcescale * force, targ.velocity, autocvar_g_balance_damagepush_speedfactor);
862 if(targ.move_movetype == MOVETYPE_PHYSICS)
864 entity farcent = new(farce);
865 farcent.enemy = targ;
866 farcent.movedir = farce * 10;
868 farcent.movedir = farcent.movedir * targ.mass;
869 farcent.origin = hitloc;
870 farcent.forcetype = FORCETYPE_FORCEATPOS;
871 farcent.nextthink = time + 0.1;
872 setthink(farcent, SUB_Remove);
874 else if(targ.move_movetype != MOVETYPE_NOCLIP)
876 targ.velocity = targ.velocity + farce;
878 UNSET_ONGROUND(targ);
879 UpdateCSQCProjectile(targ);
882 if (damage != 0 || (targ.damageforcescale && force))
883 if (targ.event_damage)
884 targ.event_damage (targ, inflictor, attacker, damage, deathtype, weaponentity, hitloc, force);
886 // apply mirror damage if any
887 if(!autocvar_g_mirrordamage_onlyweapons || DEATH_WEAPONOF(deathtype) != WEP_Null)
888 if(mirrordamage > 0 || mirrorforce > 0)
890 attacker = attacker_save;
892 force = normalize(attacker.origin + attacker.view_ofs - hitloc) * mirrorforce;
893 Damage(attacker, inflictor, attacker, mirrordamage, DEATH_MIRRORDAMAGE.m_id, weaponentity, attacker.origin, force);
897 // Returns total damage applies to creatures
898 float RadiusDamageForSource (entity inflictor, vector inflictororigin, vector inflictorvelocity, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe,
899 float inflictorselfdamage, float forceintensity, float forcezscale, int deathtype, .entity weaponentity, entity directhitentity)
903 float total_damage_to_creatures;
908 float stat_damagedone;
910 if(RadiusDamage_running)
912 backtrace("RadiusDamage called recursively! Expect stuff to go HORRIBLY wrong.");
916 RadiusDamage_running = 1;
918 tfloordmg = autocvar_g_throughfloor_damage;
919 tfloorforce = autocvar_g_throughfloor_force;
921 total_damage_to_creatures = 0;
923 if(deathtype != (WEP_HOOK.m_id | HITTYPE_SECONDARY | HITTYPE_BOUNCE)) // only send gravity bomb damage once
924 if(!(deathtype & HITTYPE_SOUND)) // do not send radial sound damage (bandwidth hog)
926 force = inflictorvelocity;
930 force = normalize(force);
931 if(forceintensity >= 0)
932 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, rad, forceintensity * force, deathtype, 0, attacker);
934 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, -rad, (-forceintensity) * force, deathtype, 0, attacker);
939 targ = WarpZone_FindRadius (inflictororigin, rad + MAX_DAMAGEEXTRARADIUS, false);
943 if ((targ != inflictor) || inflictorselfdamage)
944 if (((cantbe != targ) && !mustbe) || (mustbe == targ))
951 // LordHavoc: measure distance to nearest point on target (not origin)
952 // (this guarentees 100% damage on a touch impact)
953 nearest = targ.WarpZone_findradius_nearest;
954 diff = targ.WarpZone_findradius_dist;
955 // round up a little on the damage to ensure full damage on impacts
956 // and turn the distance into a fraction of the radius
957 power = 1 - ((vlen (diff) - bound(MIN_DAMAGEEXTRARADIUS, targ.damageextraradius, MAX_DAMAGEEXTRARADIUS)) / rad);
959 //bprint(ftos(power));
960 //if (targ == attacker)
961 // print(ftos(power), "\n");
967 finaldmg = coredamage * power + edgedamage * (1 - power);
973 vector myblastorigin;
976 myblastorigin = WarpZone_TransformOrigin(targ, inflictororigin);
978 // if it's a player, use the view origin as reference
979 center = CENTER_OR_VIEWOFS(targ);
981 force = normalize(center - myblastorigin);
982 force = force * (finaldmg / coredamage) * forceintensity;
985 // apply special scaling along the z axis if set
986 // NOTE: 0 value is not allowed for compatibility, in the case of weapon cvars not being set
988 force.z *= forcezscale;
990 if(targ != directhitentity)
995 float mininv_f, mininv_d;
997 // test line of sight to multiple positions on box,
998 // and do damage if any of them hit
1001 // we know: max stddev of hitratio = 1 / (2 * sqrt(n))
1002 // so for a given max stddev:
1003 // n = (1 / (2 * max stddev of hitratio))^2
1005 mininv_d = (finaldmg * (1-tfloordmg)) / autocvar_g_throughfloor_damage_max_stddev;
1006 mininv_f = (vlen(force) * (1-tfloorforce)) / autocvar_g_throughfloor_force_max_stddev;
1008 if(autocvar_g_throughfloor_debug)
1009 LOG_INFOF("THROUGHFLOOR: D=%f F=%f max(dD)=1/%f max(dF)=1/%f", finaldmg, vlen(force), mininv_d, mininv_f);
1012 total = 0.25 * (max(mininv_f, mininv_d) ** 2);
1014 if(autocvar_g_throughfloor_debug)
1015 LOG_INFOF(" steps=%f", total);
1018 if (IS_PLAYER(targ))
1019 total = ceil(bound(autocvar_g_throughfloor_min_steps_player, total, autocvar_g_throughfloor_max_steps_player));
1021 total = ceil(bound(autocvar_g_throughfloor_min_steps_other, total, autocvar_g_throughfloor_max_steps_other));
1023 if(autocvar_g_throughfloor_debug)
1024 LOG_INFOF(" steps=%f dD=%f dF=%f", total, finaldmg * (1-tfloordmg) / (2 * sqrt(total)), vlen(force) * (1-tfloorforce) / (2 * sqrt(total)));
1026 for(c = 0; c < total; ++c)
1028 //traceline(targ.WarpZone_findradius_findorigin, nearest, MOVE_NOMONSTERS, inflictor);
1029 WarpZone_TraceLine(inflictororigin, WarpZone_UnTransformOrigin(targ, nearest), MOVE_NOMONSTERS, inflictor);
1030 if (trace_fraction == 1 || trace_ent == targ)
1034 hitloc = hitloc + nearest;
1038 nearest.x = targ.origin.x + targ.mins.x + random() * targ.size.x;
1039 nearest.y = targ.origin.y + targ.mins.y + random() * targ.size.y;
1040 nearest.z = targ.origin.z + targ.mins.z + random() * targ.size.z;
1043 nearest = hitloc * (1 / max(1, hits));
1044 hitratio = (hits / total);
1045 a = bound(0, tfloordmg + (1-tfloordmg) * hitratio, 1);
1046 finaldmg = finaldmg * a;
1047 a = bound(0, tfloorforce + (1-tfloorforce) * hitratio, 1);
1050 if(autocvar_g_throughfloor_debug)
1051 LOG_INFOF(" D=%f F=%f", finaldmg, vlen(force));
1054 //if (targ == attacker)
1056 // print("hits ", ftos(hits), " / ", ftos(total));
1057 // print(" finaldmg ", ftos(finaldmg), " force ", vtos(force));
1058 // print(" (", ftos(a), ")\n");
1060 if(finaldmg || force)
1064 total_damage_to_creatures += finaldmg;
1066 if(accuracy_isgooddamage(attacker, targ))
1067 stat_damagedone += finaldmg;
1070 if(targ == directhitentity || DEATH_ISSPECIAL(deathtype))
1071 Damage(targ, inflictor, attacker, finaldmg, deathtype, weaponentity, nearest, force);
1073 Damage(targ, inflictor, attacker, finaldmg, deathtype | HITTYPE_SPLASH, weaponentity, nearest, force);
1081 RadiusDamage_running = 0;
1083 if(!DEATH_ISSPECIAL(deathtype))
1084 accuracy_add(attacker, DEATH_WEAPONOF(deathtype), 0, min(coredamage, stat_damagedone));
1086 return total_damage_to_creatures;
1089 float RadiusDamage(entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float forceintensity, int deathtype, .entity weaponentity, entity directhitentity)
1091 return RadiusDamageForSource(inflictor, (inflictor.origin + (inflictor.mins + inflictor.maxs) * 0.5), inflictor.velocity, attacker, coredamage, edgedamage, rad,
1092 cantbe, mustbe, false, forceintensity, 1, deathtype, weaponentity, directhitentity);
1095 bool Heal(entity targ, entity inflictor, float amount, float limit)
1097 if(game_stopped || (IS_CLIENT(targ) && CS(targ).killcount == FRAGS_SPECTATOR) || STAT(FROZEN, targ) || IS_DEAD(targ))
1100 bool healed = false;
1102 healed = targ.event_heal(targ, inflictor, amount, limit);
1103 // TODO: additional handling? what if the healing kills them? should this abort if healing would do so etc
1104 // TODO: healing fx!
1105 // TODO: armor healing?
1109 float Fire_AddDamage(entity e, entity o, float d, float t, float dt)
1112 float maxtime, mintime, maxdamage, mindamage, maxdps, mindps, totaldamage, totaltime;
1122 if(StatusEffects_active(STATUSEFFECT_Burning, e))
1124 float fireendtime = StatusEffects_gettime(STATUSEFFECT_Burning, e);
1126 mintime = fireendtime - time;
1127 maxtime = max(mintime, t);
1129 mindps = e.fire_damagepersec;
1130 maxdps = max(mindps, dps);
1132 if(maxtime > mintime || maxdps > mindps)
1136 // damage we have right now
1137 mindamage = mindps * mintime;
1139 // damage we want to get
1140 maxdamage = mindamage + d;
1142 // but we can't exceed maxtime * maxdps!
1143 totaldamage = min(maxdamage, maxtime * maxdps);
1147 // totaldamage = min(mindamage + d, maxtime * maxdps)
1149 // totaldamage <= maxtime * maxdps
1150 // ==> totaldamage / maxdps <= maxtime.
1152 // totaldamage / mindps = min(mindamage / mindps + d, maxtime * maxdps / mindps)
1153 // >= min(mintime, maxtime)
1154 // ==> totaldamage / maxdps >= mintime.
1157 // how long do we damage then?
1158 // at least as long as before
1159 // but, never exceed maxdps
1160 totaltime = max(mintime, totaldamage / maxdps); // always <= maxtime due to lemma
1164 // at most as long as maximum allowed
1165 // but, never below mindps
1166 totaltime = min(maxtime, totaldamage / mindps); // always >= mintime due to lemma
1168 // assuming t > mintime, dps > mindps:
1169 // we get d = t * dps = maxtime * maxdps
1170 // totaldamage = min(maxdamage, maxtime * maxdps) = min(... + d, maxtime * maxdps) = maxtime * maxdps
1171 // totaldamage / maxdps = maxtime
1172 // totaldamage / mindps > totaldamage / maxdps = maxtime
1174 // a) totaltime = max(mintime, maxtime) = maxtime
1175 // b) totaltime = min(maxtime, totaldamage / maxdps) = maxtime
1177 // assuming t <= mintime:
1178 // we get maxtime = mintime
1179 // a) totaltime = max(mintime, ...) >= mintime, also totaltime <= maxtime by the lemma, therefore totaltime = mintime = maxtime
1180 // b) totaltime = min(maxtime, ...) <= maxtime, also totaltime >= mintime by the lemma, therefore totaltime = mintime = maxtime
1182 // assuming dps <= mindps:
1183 // we get mindps = maxdps.
1184 // With this, the lemma says that mintime <= totaldamage / mindps = totaldamage / maxdps <= maxtime.
1185 // a) totaltime = max(mintime, totaldamage / maxdps) = totaldamage / maxdps
1186 // b) totaltime = min(maxtime, totaldamage / mindps) = totaldamage / maxdps
1188 e.fire_damagepersec = totaldamage / totaltime;
1189 StatusEffects_apply(STATUSEFFECT_Burning, e, time + totaltime, 0);
1190 if(totaldamage > 1.2 * mindamage)
1192 e.fire_deathtype = dt;
1193 if(e.fire_owner != o)
1196 e.fire_hitsound = false;
1199 if(accuracy_isgooddamage(o, e))
1200 accuracy_add(o, DEATH_WEAPONOF(dt), 0, max(0, totaldamage - mindamage));
1201 return max(0, totaldamage - mindamage); // can never be negative, but to make sure
1208 e.fire_damagepersec = dps;
1209 StatusEffects_apply(STATUSEFFECT_Burning, e, time + t, 0);
1210 e.fire_deathtype = dt;
1212 e.fire_hitsound = false;
1213 if(accuracy_isgooddamage(o, e))
1214 accuracy_add(o, DEATH_WEAPONOF(dt), 0, d);
1219 void Fire_ApplyDamage(entity e)
1224 for(t = 0, o = e.owner; o.owner && t < 16; o = o.owner, ++t);
1225 if(IS_NOT_A_CLIENT(o))
1228 float fireendtime = StatusEffects_gettime(STATUSEFFECT_Burning, e);
1229 t = min(frametime, fireendtime - time);
1230 d = e.fire_damagepersec * t;
1232 hi = e.fire_owner.damage_dealt;
1233 ty = e.fire_owner.typehitsound;
1234 Damage(e, e, e.fire_owner, d, e.fire_deathtype, DMG_NOWEP, e.origin, '0 0 0');
1235 if(e.fire_hitsound && e.fire_owner)
1237 e.fire_owner.damage_dealt = hi;
1238 e.fire_owner.typehitsound = ty;
1240 e.fire_hitsound = true;
1242 if(!IS_INDEPENDENT_PLAYER(e) && !STAT(FROZEN, e))
1244 IL_EACH(g_damagedbycontents, it.damagedbycontents && it != e,
1246 if(!IS_DEAD(it) && it.takedamage && !IS_INDEPENDENT_PLAYER(it))
1247 if(boxesoverlap(e.absmin, e.absmax, it.absmin, it.absmax))
1249 t = autocvar_g_balance_firetransfer_time * (fireendtime - time);
1250 d = autocvar_g_balance_firetransfer_damage * e.fire_damagepersec * t;
1251 Fire_AddDamage(it, o, d, t, DEATH_FIRE.m_id);