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 SetResourceExplicit(targ, RES_ARMOR, 0);
617 targ.spawnshieldtime = 0;
618 SetResourceExplicit(targ, RES_HEALTH, 0.9); // this is < 1
619 targ.flags -= targ.flags & FL_GODMODE;
622 else if(deathtype == DEATH_MIRRORDAMAGE.m_id || deathtype == DEATH_NOAMMO.m_id)
628 // nullify damage if teamplay is on
629 if(deathtype != DEATH_TELEFRAG.m_id)
630 if(IS_PLAYER(attacker))
632 if(IS_PLAYER(targ) && targ != attacker && (IS_INDEPENDENT_PLAYER(attacker) || IS_INDEPENDENT_PLAYER(targ)))
637 else if(!STAT(FROZEN, targ) && SAME_TEAM(attacker, targ))
639 if(autocvar_teamplay_mode == 1)
641 else if(attacker != targ)
643 if(autocvar_teamplay_mode == 2)
645 if(IS_PLAYER(targ) && !IS_DEAD(targ))
647 attacker.dmg_team = attacker.dmg_team + damage;
648 complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold;
651 else if(autocvar_teamplay_mode == 3)
653 else if(autocvar_teamplay_mode == 4)
655 if(IS_PLAYER(targ) && !IS_DEAD(targ))
657 attacker.dmg_team = attacker.dmg_team + damage;
658 complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold;
659 if(complainteamdamage > 0)
660 mirrordamage = autocvar_g_mirrordamage * complainteamdamage;
661 mirrorforce = autocvar_g_mirrordamage * vlen(force);
662 damage = autocvar_g_friendlyfire * damage;
663 // mirrordamage will be used LATER
665 if(autocvar_g_mirrordamage_virtual)
667 vector v = healtharmor_applydamage(GetResource(attacker, RES_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, mirrordamage);
668 attacker.dmg_take += v.x;
669 attacker.dmg_save += v.y;
670 attacker.dmg_inflictor = inflictor;
675 if(autocvar_g_friendlyfire_virtual)
677 vector v = healtharmor_applydamage(GetResource(targ, RES_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, damage);
678 targ.dmg_take += v.x;
679 targ.dmg_save += v.y;
680 targ.dmg_inflictor = inflictor;
682 if(!autocvar_g_friendlyfire_virtual_force)
686 else if(!targ.canteamdamage)
693 if (!DEATH_ISSPECIAL(deathtype))
695 damage *= autocvar_g_weapondamagefactor;
696 mirrordamage *= autocvar_g_weapondamagefactor;
697 complainteamdamage *= autocvar_g_weapondamagefactor;
698 force = force * autocvar_g_weaponforcefactor;
699 mirrorforce *= autocvar_g_weaponforcefactor;
702 // should this be changed at all? If so, in what way?
703 MUTATOR_CALLHOOK(Damage_Calculate, inflictor, attacker, targ, deathtype, damage, mirrordamage, force, attacker.(weaponentity));
704 damage = M_ARGV(4, float);
705 mirrordamage = M_ARGV(5, float);
706 force = M_ARGV(6, vector);
708 if(IS_PLAYER(targ) && damage > 0 && attacker)
710 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
712 .entity went = weaponentities[slot];
713 if(targ.(went).hook && targ.(went).hook.aiment == attacker)
714 RemoveHook(targ.(went).hook);
718 if(STAT(FROZEN, targ) && !ITEM_DAMAGE_NEEDKILL(deathtype)
719 && deathtype != DEATH_TEAMCHANGE.m_id && deathtype != DEATH_AUTOTEAMCHANGE.m_id)
721 if(autocvar_g_frozen_revive_falldamage > 0 && deathtype == DEATH_FALL.m_id && damage >= autocvar_g_frozen_revive_falldamage)
723 Unfreeze(targ, false);
724 SetResource(targ, RES_HEALTH, autocvar_g_frozen_revive_falldamage_health);
725 Send_Effect(EFFECT_ICEORGLASS, targ.origin, '0 0 0', 3);
726 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, targ.netname);
727 Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF);
731 force *= autocvar_g_frozen_force;
734 if(IS_PLAYER(targ) && STAT(FROZEN, targ)
735 && ITEM_DAMAGE_NEEDKILL(deathtype) && !autocvar_g_frozen_damage_trigger)
737 Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1);
739 entity spot = SelectSpawnPoint(targ, false);
743 targ.deadflag = DEAD_NO;
745 targ.angles = spot.angles;
748 targ.effects |= EF_TELEPORT_BIT;
750 targ.angles_z = 0; // never spawn tilted even if the spot says to
751 targ.fixangle = true; // turn this way immediately
752 targ.velocity = '0 0 0';
753 targ.avelocity = '0 0 0';
754 targ.punchangle = '0 0 0';
755 targ.punchvector = '0 0 0';
756 targ.oldvelocity = targ.velocity;
758 targ.spawnorigin = spot.origin;
759 setorigin(targ, spot.origin + '0 0 1' * (1 - targ.mins.z - 24));
760 // don't reset back to last position, even if new position is stuck in solid
761 targ.oldorigin = targ.origin;
763 Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1);
767 if(!MUTATOR_IS_ENABLED(mutator_instagib))
769 // apply strength multiplier
770 if (attacker.items & ITEM_Strength.m_itemid)
774 damage = damage * autocvar_g_balance_powerup_strength_selfdamage;
775 force = force * autocvar_g_balance_powerup_strength_selfforce;
779 damage = damage * autocvar_g_balance_powerup_strength_damage;
780 force = force * autocvar_g_balance_powerup_strength_force;
784 // apply invincibility multiplier
785 if (targ.items & ITEM_Shield.m_itemid)
787 damage = damage * autocvar_g_balance_powerup_invincible_takedamage;
788 if (targ != attacker)
790 force = force * autocvar_g_balance_powerup_invincible_takeforce;
795 if (targ == attacker)
796 damage = damage * autocvar_g_balance_selfdamagepercent; // Partial damage if the attacker hits himself
801 if(deathtype != DEATH_BUFF.m_id)
802 if(targ.takedamage == DAMAGE_AIM)
806 if(IS_VEHICLE(targ) && targ.owner)
811 if(IS_PLAYER(victim) || (IS_TURRET(victim) && victim.active == ACTIVE_ACTIVE) || IS_MONSTER(victim) || MUTATOR_CALLHOOK(PlayHitsound, victim, attacker))
813 if (DIFF_TEAM(victim, attacker))
817 if(deathtype != DEATH_FIRE.m_id)
819 if(PHYS_INPUT_BUTTON_CHAT(victim))
820 attacker.typehitsound += 1;
822 attacker.damage_dealt += damage;
825 impressive_hits += 1;
827 if (!DEATH_ISSPECIAL(deathtype))
829 if(IS_PLAYER(targ)) // don't do this for vehicles
835 else if (IS_PLAYER(attacker) && !STAT(FROZEN, victim)) // same team
837 if (deathtype != DEATH_FIRE.m_id)
839 attacker.typehitsound += 1;
841 if(complainteamdamage > 0)
842 if(time > CS(attacker).teamkill_complain)
844 CS(attacker).teamkill_complain = time + 5;
845 CS(attacker).teamkill_soundtime = time + 0.4;
846 CS(attacker).teamkill_soundsource = targ;
854 if (targ.damageforcescale)
856 if (!IS_PLAYER(targ) || time >= targ.spawnshieldtime || targ == attacker)
858 vector farce = damage_explosion_calcpush(targ.damageforcescale * force, targ.velocity, autocvar_g_balance_damagepush_speedfactor);
859 if(targ.move_movetype == MOVETYPE_PHYSICS)
861 entity farcent = new(farce);
862 farcent.enemy = targ;
863 farcent.movedir = farce * 10;
865 farcent.movedir = farcent.movedir * targ.mass;
866 farcent.origin = hitloc;
867 farcent.forcetype = FORCETYPE_FORCEATPOS;
868 farcent.nextthink = time + 0.1;
869 setthink(farcent, SUB_Remove);
871 else if(targ.move_movetype != MOVETYPE_NOCLIP)
873 targ.velocity = targ.velocity + farce;
875 UNSET_ONGROUND(targ);
876 UpdateCSQCProjectile(targ);
879 if (damage != 0 || (targ.damageforcescale && force))
880 if (targ.event_damage)
881 targ.event_damage (targ, inflictor, attacker, damage, deathtype, weaponentity, hitloc, force);
883 // apply mirror damage if any
884 if(!autocvar_g_mirrordamage_onlyweapons || DEATH_WEAPONOF(deathtype) != WEP_Null)
885 if(mirrordamage > 0 || mirrorforce > 0)
887 attacker = attacker_save;
889 force = normalize(attacker.origin + attacker.view_ofs - hitloc) * mirrorforce;
890 Damage(attacker, inflictor, attacker, mirrordamage, DEATH_MIRRORDAMAGE.m_id, weaponentity, attacker.origin, force);
894 // Returns total damage applies to creatures
895 float RadiusDamageForSource (entity inflictor, vector inflictororigin, vector inflictorvelocity, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe,
896 float inflictorselfdamage, float forceintensity, float forcezscale, int deathtype, .entity weaponentity, entity directhitentity)
900 float total_damage_to_creatures;
905 float stat_damagedone;
907 if(RadiusDamage_running)
909 backtrace("RadiusDamage called recursively! Expect stuff to go HORRIBLY wrong.");
913 RadiusDamage_running = 1;
915 tfloordmg = autocvar_g_throughfloor_damage;
916 tfloorforce = autocvar_g_throughfloor_force;
918 total_damage_to_creatures = 0;
920 if(deathtype != (WEP_HOOK.m_id | HITTYPE_SECONDARY | HITTYPE_BOUNCE)) // only send gravity bomb damage once
921 if(!(deathtype & HITTYPE_SOUND)) // do not send radial sound damage (bandwidth hog)
923 force = inflictorvelocity;
927 force = normalize(force);
928 if(forceintensity >= 0)
929 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, rad, forceintensity * force, deathtype, 0, attacker);
931 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, -rad, (-forceintensity) * force, deathtype, 0, attacker);
936 targ = WarpZone_FindRadius (inflictororigin, rad + MAX_DAMAGEEXTRARADIUS, false);
940 if ((targ != inflictor) || inflictorselfdamage)
941 if (((cantbe != targ) && !mustbe) || (mustbe == targ))
948 // LordHavoc: measure distance to nearest point on target (not origin)
949 // (this guarentees 100% damage on a touch impact)
950 nearest = targ.WarpZone_findradius_nearest;
951 diff = targ.WarpZone_findradius_dist;
952 // round up a little on the damage to ensure full damage on impacts
953 // and turn the distance into a fraction of the radius
954 power = 1 - ((vlen (diff) - bound(MIN_DAMAGEEXTRARADIUS, targ.damageextraradius, MAX_DAMAGEEXTRARADIUS)) / rad);
956 //bprint(ftos(power));
957 //if (targ == attacker)
958 // print(ftos(power), "\n");
964 finaldmg = coredamage * power + edgedamage * (1 - power);
970 vector myblastorigin;
973 myblastorigin = WarpZone_TransformOrigin(targ, inflictororigin);
975 // if it's a player, use the view origin as reference
976 center = CENTER_OR_VIEWOFS(targ);
978 force = normalize(center - myblastorigin);
979 force = force * (finaldmg / coredamage) * forceintensity;
982 // apply special scaling along the z axis if set
983 // NOTE: 0 value is not allowed for compatibility, in the case of weapon cvars not being set
985 force.z *= forcezscale;
987 if(targ != directhitentity)
992 float mininv_f, mininv_d;
994 // test line of sight to multiple positions on box,
995 // and do damage if any of them hit
998 // we know: max stddev of hitratio = 1 / (2 * sqrt(n))
999 // so for a given max stddev:
1000 // n = (1 / (2 * max stddev of hitratio))^2
1002 mininv_d = (finaldmg * (1-tfloordmg)) / autocvar_g_throughfloor_damage_max_stddev;
1003 mininv_f = (vlen(force) * (1-tfloorforce)) / autocvar_g_throughfloor_force_max_stddev;
1005 if(autocvar_g_throughfloor_debug)
1006 LOG_INFOF("THROUGHFLOOR: D=%f F=%f max(dD)=1/%f max(dF)=1/%f", finaldmg, vlen(force), mininv_d, mininv_f);
1009 total = 0.25 * (max(mininv_f, mininv_d) ** 2);
1011 if(autocvar_g_throughfloor_debug)
1012 LOG_INFOF(" steps=%f", total);
1015 if (IS_PLAYER(targ))
1016 total = ceil(bound(autocvar_g_throughfloor_min_steps_player, total, autocvar_g_throughfloor_max_steps_player));
1018 total = ceil(bound(autocvar_g_throughfloor_min_steps_other, total, autocvar_g_throughfloor_max_steps_other));
1020 if(autocvar_g_throughfloor_debug)
1021 LOG_INFOF(" steps=%f dD=%f dF=%f", total, finaldmg * (1-tfloordmg) / (2 * sqrt(total)), vlen(force) * (1-tfloorforce) / (2 * sqrt(total)));
1023 for(c = 0; c < total; ++c)
1025 //traceline(targ.WarpZone_findradius_findorigin, nearest, MOVE_NOMONSTERS, inflictor);
1026 WarpZone_TraceLine(inflictororigin, WarpZone_UnTransformOrigin(targ, nearest), MOVE_NOMONSTERS, inflictor);
1027 if (trace_fraction == 1 || trace_ent == targ)
1031 hitloc = hitloc + nearest;
1035 nearest.x = targ.origin.x + targ.mins.x + random() * targ.size.x;
1036 nearest.y = targ.origin.y + targ.mins.y + random() * targ.size.y;
1037 nearest.z = targ.origin.z + targ.mins.z + random() * targ.size.z;
1040 nearest = hitloc * (1 / max(1, hits));
1041 hitratio = (hits / total);
1042 a = bound(0, tfloordmg + (1-tfloordmg) * hitratio, 1);
1043 finaldmg = finaldmg * a;
1044 a = bound(0, tfloorforce + (1-tfloorforce) * hitratio, 1);
1047 if(autocvar_g_throughfloor_debug)
1048 LOG_INFOF(" D=%f F=%f", finaldmg, vlen(force));
1051 //if (targ == attacker)
1053 // print("hits ", ftos(hits), " / ", ftos(total));
1054 // print(" finaldmg ", ftos(finaldmg), " force ", vtos(force));
1055 // print(" (", ftos(a), ")\n");
1057 if(finaldmg || force)
1061 total_damage_to_creatures += finaldmg;
1063 if(accuracy_isgooddamage(attacker, targ))
1064 stat_damagedone += finaldmg;
1067 if(targ == directhitentity || DEATH_ISSPECIAL(deathtype))
1068 Damage(targ, inflictor, attacker, finaldmg, deathtype, weaponentity, nearest, force);
1070 Damage(targ, inflictor, attacker, finaldmg, deathtype | HITTYPE_SPLASH, weaponentity, nearest, force);
1078 RadiusDamage_running = 0;
1080 if(!DEATH_ISSPECIAL(deathtype))
1081 accuracy_add(attacker, DEATH_WEAPONOF(deathtype), 0, min(coredamage, stat_damagedone));
1083 return total_damage_to_creatures;
1086 float RadiusDamage(entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float forceintensity, int deathtype, .entity weaponentity, entity directhitentity)
1088 return RadiusDamageForSource(inflictor, (inflictor.origin + (inflictor.mins + inflictor.maxs) * 0.5), inflictor.velocity, attacker, coredamage, edgedamage, rad,
1089 cantbe, mustbe, false, forceintensity, 1, deathtype, weaponentity, directhitentity);
1092 bool Heal(entity targ, entity inflictor, float amount, float limit)
1094 if(game_stopped || (IS_CLIENT(targ) && CS(targ).killcount == FRAGS_SPECTATOR) || STAT(FROZEN, targ) || IS_DEAD(targ))
1097 bool healed = false;
1099 healed = targ.event_heal(targ, inflictor, amount, limit);
1100 // TODO: additional handling? what if the healing kills them? should this abort if healing would do so etc
1101 // TODO: healing fx!
1102 // TODO: armor healing?
1106 float Fire_AddDamage(entity e, entity o, float d, float t, float dt)
1109 float maxtime, mintime, maxdamage, mindamage, maxdps, mindps, totaldamage, totaltime;
1119 if(StatusEffects_active(STATUSEFFECT_Burning, e))
1121 float fireendtime = StatusEffects_gettime(STATUSEFFECT_Burning, e);
1123 mintime = fireendtime - time;
1124 maxtime = max(mintime, t);
1126 mindps = e.fire_damagepersec;
1127 maxdps = max(mindps, dps);
1129 if(maxtime > mintime || maxdps > mindps)
1133 // damage we have right now
1134 mindamage = mindps * mintime;
1136 // damage we want to get
1137 maxdamage = mindamage + d;
1139 // but we can't exceed maxtime * maxdps!
1140 totaldamage = min(maxdamage, maxtime * maxdps);
1144 // totaldamage = min(mindamage + d, maxtime * maxdps)
1146 // totaldamage <= maxtime * maxdps
1147 // ==> totaldamage / maxdps <= maxtime.
1149 // totaldamage / mindps = min(mindamage / mindps + d, maxtime * maxdps / mindps)
1150 // >= min(mintime, maxtime)
1151 // ==> totaldamage / maxdps >= mintime.
1154 // how long do we damage then?
1155 // at least as long as before
1156 // but, never exceed maxdps
1157 totaltime = max(mintime, totaldamage / maxdps); // always <= maxtime due to lemma
1161 // at most as long as maximum allowed
1162 // but, never below mindps
1163 totaltime = min(maxtime, totaldamage / mindps); // always >= mintime due to lemma
1165 // assuming t > mintime, dps > mindps:
1166 // we get d = t * dps = maxtime * maxdps
1167 // totaldamage = min(maxdamage, maxtime * maxdps) = min(... + d, maxtime * maxdps) = maxtime * maxdps
1168 // totaldamage / maxdps = maxtime
1169 // totaldamage / mindps > totaldamage / maxdps = maxtime
1171 // a) totaltime = max(mintime, maxtime) = maxtime
1172 // b) totaltime = min(maxtime, totaldamage / maxdps) = maxtime
1174 // assuming t <= mintime:
1175 // we get maxtime = mintime
1176 // a) totaltime = max(mintime, ...) >= mintime, also totaltime <= maxtime by the lemma, therefore totaltime = mintime = maxtime
1177 // b) totaltime = min(maxtime, ...) <= maxtime, also totaltime >= mintime by the lemma, therefore totaltime = mintime = maxtime
1179 // assuming dps <= mindps:
1180 // we get mindps = maxdps.
1181 // With this, the lemma says that mintime <= totaldamage / mindps = totaldamage / maxdps <= maxtime.
1182 // a) totaltime = max(mintime, totaldamage / maxdps) = totaldamage / maxdps
1183 // b) totaltime = min(maxtime, totaldamage / mindps) = totaldamage / maxdps
1185 e.fire_damagepersec = totaldamage / totaltime;
1186 StatusEffects_apply(STATUSEFFECT_Burning, e, time + totaltime, 0);
1187 if(totaldamage > 1.2 * mindamage)
1189 e.fire_deathtype = dt;
1190 if(e.fire_owner != o)
1193 e.fire_hitsound = false;
1196 if(accuracy_isgooddamage(o, e))
1197 accuracy_add(o, DEATH_WEAPONOF(dt), 0, max(0, totaldamage - mindamage));
1198 return max(0, totaldamage - mindamage); // can never be negative, but to make sure
1205 e.fire_damagepersec = dps;
1206 StatusEffects_apply(STATUSEFFECT_Burning, e, time + t, 0);
1207 e.fire_deathtype = dt;
1209 e.fire_hitsound = false;
1210 if(accuracy_isgooddamage(o, e))
1211 accuracy_add(o, DEATH_WEAPONOF(dt), 0, d);
1216 void Fire_ApplyDamage(entity e)
1221 for(t = 0, o = e.owner; o.owner && t < 16; o = o.owner, ++t);
1222 if(IS_NOT_A_CLIENT(o))
1225 float fireendtime = StatusEffects_gettime(STATUSEFFECT_Burning, e);
1226 t = min(frametime, fireendtime - time);
1227 d = e.fire_damagepersec * t;
1229 hi = e.fire_owner.damage_dealt;
1230 ty = e.fire_owner.typehitsound;
1231 Damage(e, e, e.fire_owner, d, e.fire_deathtype, DMG_NOWEP, e.origin, '0 0 0');
1232 if(e.fire_hitsound && e.fire_owner)
1234 e.fire_owner.damage_dealt = hi;
1235 e.fire_owner.typehitsound = ty;
1237 e.fire_hitsound = true;
1239 if(!IS_INDEPENDENT_PLAYER(e) && !STAT(FROZEN, e))
1241 IL_EACH(g_damagedbycontents, it.damagedbycontents && it != e,
1243 if(!IS_DEAD(it) && it.takedamage && !IS_INDEPENDENT_PLAYER(it))
1244 if(boxesoverlap(e.absmin, e.absmax, it.absmin, it.absmax))
1246 t = autocvar_g_balance_firetransfer_time * (fireendtime - time);
1247 d = autocvar_g_balance_firetransfer_damage * e.fire_damagepersec * t;
1248 Fire_AddDamage(it, o, d, t, DEATH_FIRE.m_id);