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/resources/sv_resources.qh>
21 #include <common/state.qh>
22 #include <common/teams.qh>
23 #include <common/util.qh>
24 #include <common/vehicles/all.qh>
25 #include <common/weapons/_all.qh>
26 #include <lib/csqcmodel/sv_model.qh>
27 #include <lib/warpzone/common.qh>
28 #include <server/bot/api.qh>
29 #include <server/client.qh>
30 #include <server/gamelog.qh>
31 #include <server/hook.qh>
32 #include <server/items/items.qh>
33 #include <server/main.qh>
34 #include <server/mutators/_mod.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(PHYS_INPUT_BUTTON_CHAT(player))
100 // TODO: include these codes as a flag on the item itself
101 MUTATOR_CALLHOOK(LogDeath_AppendItemCodes, player, s);
102 s = M_ARGV(1, string);
106 void LogDeath(string mode, int deathtype, entity killer, entity killed)
109 if(!autocvar_sv_eventlog)
111 s = strcat(":kill:", mode);
112 s = strcat(s, ":", ftos(killer.playerid));
113 s = strcat(s, ":", ftos(killed.playerid));
114 s = strcat(s, ":type=", Deathtype_Name(deathtype));
115 s = strcat(s, ":items=");
116 s = AppendItemcodes(s, killer);
119 s = strcat(s, ":victimitems=");
120 s = AppendItemcodes(s, killed);
125 void Obituary_SpecialDeath(
130 string s1, string s2, string s3,
131 float f1, float f2, float f3)
133 if(!DEATH_ISSPECIAL(deathtype))
135 backtrace("Obituary_SpecialDeath called without a special deathtype?\n");
139 entity deathent = REGISTRY_GET(Deathtypes, deathtype - DT_FIRST);
142 backtrace("Obituary_SpecialDeath: Could not find deathtype entity!\n");
146 if(g_cts && deathtype == DEATH_KILL.m_id)
147 return; // TODO: somehow put this in CTS gamemode file!
149 Notification death_message = (murder) ? deathent.death_msgmurder : deathent.death_msgself;
152 Send_Notification_WOCOVA(
160 Send_Notification_WOCOVA(
164 death_message.nent_msginfo,
170 if(deathtype == DEATH_TELEFRAG.m_id) {
171 Give_Medal(attacker, TELEFRAG);
175 float Obituary_WeaponDeath(
180 string s1, string s2, string s3,
183 Weapon death_weapon = DEATH_WEAPONOF(deathtype);
184 if (death_weapon == WEP_Null)
187 w_deathtype = deathtype;
188 Notification death_message = ((murder) ? death_weapon.wr_killmessage(death_weapon) : death_weapon.wr_suicidemessage(death_weapon));
193 Send_Notification_WOCOVA(
201 // send the info part to everyone
202 Send_Notification_WOCOVA(
206 death_message.nent_msginfo,
211 // z411 special medals
213 switch(death_message) {
214 case WEAPON_SHOTGUN_MURDER_SLAP:
215 if(!cvar("g_melee_only")) { // don't spam humiliation if we're in melee_only mode
216 Give_Medal(attacker, HUMILIATION);
219 case WEAPON_ELECTRO_MURDER_COMBO:
220 Give_Medal(attacker, ELECTROBITCH);
228 "Obituary_WeaponDeath(): ^1Deathtype ^7(%d)^1 has no notification for weapon %s!\n",
237 bool frag_centermessage_override(entity attacker, entity targ, int deathtype, int kill_count_to_attacker, int kill_count_to_target, string attacker_name)
239 if(deathtype == DEATH_FIRE.m_id)
241 Send_Notification(NOTIF_ONE, attacker, MSG_CHOICE, CHOICE_FRAG_FIRE, targ.netname, kill_count_to_attacker, (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping));
242 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));
246 return MUTATOR_CALLHOOK(FragCenterMessage, attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target);
249 void Obituary(entity attacker, entity inflictor, entity targ, int deathtype, .entity weaponentity)
252 if (!IS_PLAYER(targ)) { backtrace("Obituary called on non-player?!\n"); return; }
255 float notif_firstblood = false;
256 float kill_count_to_attacker, kill_count_to_target;
257 bool notif_anonymous = false;
258 string attacker_name = attacker.netname;
260 // Set final information for the death
261 targ.death_origin = targ.origin;
262 string deathlocation = (autocvar_notification_server_allows_location ? NearestLocation(targ.death_origin) : "");
264 // Abort now if a mutator requests it
265 if (MUTATOR_CALLHOOK(ClientObituary, inflictor, attacker, targ, deathtype, attacker.(weaponentity))) { CS(targ).killcount = 0; return; }
266 notif_anonymous = M_ARGV(5, bool);
269 attacker_name = "???";
271 #ifdef NOTIFICATIONS_DEBUG
274 "Obituary(%s, %s, %s, %s = %d);\n",
278 Deathtype_Name(deathtype),
289 if(DEATH_ISSPECIAL(deathtype))
291 if(deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id)
293 Obituary_SpecialDeath(targ, NULL, false, deathtype, targ.netname, deathlocation, "", targ.team, 0, 0);
297 switch(DEATH_ENT(deathtype))
299 case DEATH_MIRRORDAMAGE:
301 Obituary_SpecialDeath(targ, NULL, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0);
304 case DEATH_HURTTRIGGER:
305 Obituary_SpecialDeath(targ, NULL, false, deathtype, targ.netname, inflictor.message, deathlocation, CS(targ).killcount, 0, 0);
309 Obituary_SpecialDeath(targ, NULL, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0);
315 else if (!Obituary_WeaponDeath(targ, NULL, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0))
317 backtrace("SUICIDE: what the hell happened here?\n");
320 LogDeath("suicide", deathtype, targ, targ);
321 Send_Notification(NOTIF_ONE, targ, MSG_ANNCE, ANNCE_SUICIDE);
322 if(deathtype != DEATH_AUTOTEAMCHANGE.m_id) // special case: don't negate frags if auto switched
323 GiveFrags(attacker, targ, -1, deathtype, weaponentity);
329 else if(IS_PLAYER(attacker))
331 if(SAME_TEAM(attacker, targ))
333 LogDeath("tk", deathtype, attacker, targ);
334 GiveFrags(attacker, targ, -1, deathtype, weaponentity);
336 CS(attacker).killcount = 0;
338 Send_Notification(NOTIF_ONE, attacker, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAG, targ.netname);
339 Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAGGED, attacker_name);
340 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(targ.team, INFO_DEATH_TEAMKILL),
341 playername(targ.netname, targ.team, true), playername(attacker_name, attacker.team, true),
342 deathlocation, CS(targ).killcount);
344 // In this case, the death message will ALWAYS be "foo was betrayed by bar"
345 // No need for specific death/weapon messages...
349 LogDeath("frag", deathtype, attacker, targ);
350 GiveFrags(attacker, targ, 1, deathtype, weaponentity);
352 CS(attacker).taunt_soundtime = time + 1;
353 CS(attacker).killcount = CS(attacker).killcount + 1;
355 attacker.killsound += 1;
357 // TODO: improve SPREE_ITEM and KILL_SPREE_LIST
358 // these 2 macros are spread over multiple files
359 #define SPREE_ITEM(counta,countb,center,normal,gentle) \
361 Give_Medal(attacker, KILLSTREAK_##countb); \
363 PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_##counta, 1); \
366 switch(CS(attacker).killcount)
373 if(!warmup_stage && !checkrules_firstblood)
375 checkrules_firstblood = true;
376 notif_firstblood = true; // modify the current messages so that they too show firstblood information
377 Give_Medal(attacker, FIRSTBLOOD);
378 PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD, 1);
379 PlayerStats_GameReport_Event_Player(targ, PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM, 1);
381 // tell spree_inf and spree_cen that this is a first-blood and first-victim event
382 kill_count_to_attacker = -1;
383 kill_count_to_target = -2;
387 kill_count_to_attacker = CS(attacker).killcount;
388 kill_count_to_target = 0;
392 if(attacker.lastkill && attacker.lastkill > time - autocvar_g_medals_excellent_time) {
393 Give_Medal(attacker, EXCELLENT);
395 attacker.lastkill = time;
405 kill_count_to_attacker,
406 (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping)
414 kill_count_to_target,
415 GetResource(attacker, RES_HEALTH),
416 GetResource(attacker, RES_ARMOR),
417 (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping)
420 else if(!frag_centermessage_override(attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target, attacker_name))
428 kill_count_to_attacker,
429 (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping)
437 kill_count_to_target,
438 GetResource(attacker, RES_HEALTH),
439 GetResource(attacker, RES_ARMOR),
440 (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping)
445 if(deathtype == DEATH_BUFF.m_id)
446 f3 = buff_FirstFromFlags(attacker).m_id;
448 if (!Obituary_WeaponDeath(targ, attacker, true, deathtype, playername(targ.netname, targ.team, true), playername(attacker_name, attacker.team, true), deathlocation, CS(targ).killcount, kill_count_to_attacker))
449 Obituary_SpecialDeath(targ, attacker, true, deathtype, playername(targ.netname, targ.team, true), playername(attacker_name, attacker.team, true), deathlocation, CS(targ).killcount, kill_count_to_attacker, f3);
458 switch(DEATH_ENT(deathtype))
460 // For now, we're just forcing HURTTRIGGER to behave as "DEATH_VOID" and giving it no special options...
461 // Later on you will only be able to make custom messages using DEATH_CUSTOM,
462 // and there will be a REAL DEATH_VOID implementation which mappers will use.
463 case DEATH_HURTTRIGGER:
465 Obituary_SpecialDeath(targ, NULL, false, deathtype,
466 playername(targ.netname, targ.team, true),
477 Obituary_SpecialDeath(targ, NULL, false, deathtype,
478 playername(targ.netname, targ.team, true),
479 ((strstrofs(deathmessage, "%", 0) < 0) ? strcat("%s ", deathmessage) : deathmessage),
489 Obituary_SpecialDeath(targ, NULL, false, deathtype, playername(targ.netname, targ.team, true), deathlocation, "", CS(targ).killcount, 0, 0);
494 LogDeath("accident", deathtype, targ, targ);
495 Send_Notification(NOTIF_ONE, targ, MSG_ANNCE, ANNCE_ACCIDENT);
496 GiveFrags(targ, targ, -1, deathtype, weaponentity);
498 if(GameRules_scoring_add(targ, SCORE, 0) == -5)
500 Send_Notification(NOTIF_ONE, targ, MSG_ANNCE, ANNCE_ACHIEVEMENT_BOTLIKE);
503 PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_BOTLIKE, 1);
508 // reset target kill count
509 CS(targ).killcount = 0;
512 void Ice_Think(entity this)
514 if(!STAT(FROZEN, this.owner) || this.owner.iceblock != this)
519 vector ice_org = this.owner.origin - '0 0 16';
520 if (this.origin != ice_org)
521 setorigin(this, ice_org);
522 this.nextthink = time;
525 void Freeze(entity targ, float revivespeed, int frozen_type, bool show_waypoint)
527 if(!IS_PLAYER(targ) && !IS_MONSTER(targ)) // TODO: only specified entities can be freezed
530 if(STAT(FROZEN, targ))
533 float targ_maxhealth = ((IS_MONSTER(targ)) ? targ.max_health : start_health);
535 STAT(FROZEN, targ) = frozen_type;
536 STAT(REVIVE_PROGRESS, targ) = ((frozen_type == FROZEN_TEMP_DYING) ? 1 : 0);
537 SetResource(targ, RES_HEALTH, ((frozen_type == FROZEN_TEMP_DYING) ? targ_maxhealth : 1));
538 targ.revive_speed = revivespeed;
540 IL_REMOVE(g_bot_targets, targ);
541 targ.bot_attack = false;
542 targ.freeze_time = time;
544 entity ice = new(ice);
546 ice.scale = targ.scale;
547 // set_movetype(ice, MOVETYPE_FOLLOW) would rotate the ice model with the player
548 setthink(ice, Ice_Think);
549 ice.nextthink = time;
550 ice.frame = floor(random() * 21); // ice model has 20 different looking frames
551 setmodel(ice, MDL_ICE);
553 ice.colormod = Team_ColorRGB(targ.team);
554 ice.glowmod = ice.colormod;
556 targ.revival_time = 0;
560 RemoveGrapplingHooks(targ);
562 FOREACH_CLIENT(IS_PLAYER(it),
564 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
566 .entity weaponentity = weaponentities[slot];
567 if(it.(weaponentity).hook.aiment == targ)
568 RemoveHook(it.(weaponentity).hook);
573 if(MUTATOR_CALLHOOK(Freeze, targ, revivespeed, frozen_type) || show_waypoint)
574 WaypointSprite_Spawn(WP_Frozen, 0, 0, targ, '0 0 64', NULL, targ.team, targ, waypointsprite_attached, true, RADARICON_WAYPOINT);
577 void Unfreeze(entity targ, bool reset_health)
579 if(!STAT(FROZEN, targ))
582 if (reset_health && STAT(FROZEN, targ) != FROZEN_TEMP_DYING)
583 SetResource(targ, RES_HEALTH, ((IS_PLAYER(targ)) ? start_health : targ.max_health));
585 targ.pauseregen_finished = time + autocvar_g_balance_pause_health_regen;
587 STAT(FROZEN, targ) = 0;
588 STAT(REVIVE_PROGRESS, targ) = 0;
589 targ.revival_time = time;
591 IL_PUSH(g_bot_targets, targ);
592 targ.bot_attack = true;
594 WaypointSprite_Kill(targ.waypointsprite_attached);
596 FOREACH_CLIENT(IS_PLAYER(it),
598 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
600 .entity weaponentity = weaponentities[slot];
601 if(it.(weaponentity).hook.aiment == targ)
602 RemoveHook(it.(weaponentity).hook);
606 // remove the ice block
608 delete(targ.iceblock);
609 targ.iceblock = NULL;
611 MUTATOR_CALLHOOK(Unfreeze, targ);
614 void Damage(entity targ, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
616 float complainteamdamage = 0;
617 float mirrordamage = 0;
618 float mirrorforce = 0;
620 if (game_stopped || (IS_CLIENT(targ) && CS(targ).killcount == FRAGS_SPECTATOR))
623 entity attacker_save = attacker;
625 // special rule: gravity bombs and sound-based attacks do not affect team mates (other than for disconnecting the hook)
626 if(DEATH_ISWEAPON(deathtype, WEP_HOOK) || (deathtype & HITTYPE_SOUND))
628 if(IS_PLAYER(targ) && SAME_TEAM(targ, attacker))
634 if(deathtype == DEATH_KILL.m_id || deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id)
636 // exit the vehicle before killing (fixes a crash)
637 if(IS_PLAYER(targ) && targ.vehicle)
638 vehicles_exit(targ.vehicle, VHEF_RELEASE);
640 // These are ALWAYS lethal
641 // No damage modification here
642 // Instead, prepare the victim for his death...
643 if(deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id)
645 SetResourceExplicit(targ, RES_ARMOR, 0);
646 SetResourceExplicit(targ, RES_HEALTH, 0.9); // this is < 1
648 StatusEffects_remove(STATUSEFFECT_SpawnShield, targ, STATUSEFFECT_REMOVE_CLEAR);
649 targ.flags -= targ.flags & FL_GODMODE;
652 else if(deathtype == DEATH_MIRRORDAMAGE.m_id || deathtype == DEATH_NOAMMO.m_id)
658 // nullify damage if teamplay is on
659 if(deathtype != DEATH_TELEFRAG.m_id)
660 if(IS_PLAYER(attacker))
662 if(IS_PLAYER(targ) && targ != attacker && (IS_INDEPENDENT_PLAYER(attacker) || IS_INDEPENDENT_PLAYER(targ)))
667 else if(!STAT(FROZEN, targ) && SAME_TEAM(attacker, targ))
669 if(autocvar_teamplay_mode == 1)
671 else if(attacker != targ)
673 if(autocvar_teamplay_mode == 2)
675 if(IS_PLAYER(targ) && !IS_DEAD(targ))
677 attacker.dmg_team = attacker.dmg_team + damage;
678 complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold;
681 else if(autocvar_teamplay_mode == 3)
683 else if(autocvar_teamplay_mode == 4)
685 if(IS_PLAYER(targ) && !IS_DEAD(targ))
687 attacker.dmg_team = attacker.dmg_team + damage;
688 complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold;
689 if(complainteamdamage > 0)
690 mirrordamage = autocvar_g_mirrordamage * complainteamdamage;
691 mirrorforce = autocvar_g_mirrordamage * vlen(force);
692 damage = autocvar_g_friendlyfire * damage;
693 // mirrordamage will be used LATER
695 if(autocvar_g_mirrordamage_virtual)
697 vector v = healtharmor_applydamage(GetResource(attacker, RES_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, mirrordamage);
698 attacker.dmg_take += v.x;
699 attacker.dmg_save += v.y;
700 attacker.dmg_inflictor = inflictor;
705 if(autocvar_g_friendlyfire_virtual)
707 vector v = healtharmor_applydamage(GetResource(targ, RES_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, damage);
708 targ.dmg_take += v.x;
709 targ.dmg_save += v.y;
710 targ.dmg_inflictor = inflictor;
712 if(!autocvar_g_friendlyfire_virtual_force)
716 else if(!targ.canteamdamage)
723 if (!DEATH_ISSPECIAL(deathtype))
725 damage *= autocvar_g_weapondamagefactor;
726 mirrordamage *= autocvar_g_weapondamagefactor;
727 complainteamdamage *= autocvar_g_weapondamagefactor;
728 force = force * autocvar_g_weaponforcefactor;
729 mirrorforce *= autocvar_g_weaponforcefactor;
732 // should this be changed at all? If so, in what way?
733 MUTATOR_CALLHOOK(Damage_Calculate, inflictor, attacker, targ, deathtype, damage, mirrordamage, force, attacker.(weaponentity));
734 damage = M_ARGV(4, float);
735 mirrordamage = M_ARGV(5, float);
736 force = M_ARGV(6, vector);
738 if(IS_PLAYER(targ) && damage > 0 && attacker)
740 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
742 .entity went = weaponentities[slot];
743 if(targ.(went).hook && targ.(went).hook.aiment == attacker)
744 RemoveHook(targ.(went).hook);
748 if(STAT(FROZEN, targ) && !ITEM_DAMAGE_NEEDKILL(deathtype)
749 && deathtype != DEATH_TEAMCHANGE.m_id && deathtype != DEATH_AUTOTEAMCHANGE.m_id)
751 if(autocvar_g_frozen_revive_falldamage > 0 && deathtype == DEATH_FALL.m_id && damage >= autocvar_g_frozen_revive_falldamage)
753 Unfreeze(targ, false);
754 SetResource(targ, RES_HEALTH, autocvar_g_frozen_revive_falldamage_health);
755 Send_Effect(EFFECT_ICEORGLASS, targ.origin, '0 0 0', 3);
756 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, targ.netname);
757 Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF);
761 force *= autocvar_g_frozen_force;
764 if(IS_PLAYER(targ) && STAT(FROZEN, targ)
765 && ITEM_DAMAGE_NEEDKILL(deathtype) && !autocvar_g_frozen_damage_trigger)
767 Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1);
769 entity spot = SelectSpawnPoint(targ, false);
773 targ.deadflag = DEAD_NO;
775 targ.angles = spot.angles;
778 targ.effects |= EF_TELEPORT_BIT;
780 targ.angles_z = 0; // never spawn tilted even if the spot says to
781 targ.fixangle = true; // turn this way immediately
782 targ.velocity = '0 0 0';
783 targ.avelocity = '0 0 0';
784 targ.punchangle = '0 0 0';
785 targ.punchvector = '0 0 0';
786 targ.oldvelocity = targ.velocity;
788 targ.spawnorigin = spot.origin;
789 setorigin(targ, spot.origin + '0 0 1' * (1 - targ.mins.z - 24));
790 // don't reset back to last position, even if new position is stuck in solid
791 targ.oldorigin = targ.origin;
793 Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1);
797 if (targ == attacker)
798 damage = damage * autocvar_g_balance_selfdamagepercent; // Partial damage if the attacker hits himself
803 if(deathtype != DEATH_BUFF.m_id)
804 if(targ.takedamage == DAMAGE_AIM)
808 if(IS_VEHICLE(targ) && targ.owner)
813 if(IS_PLAYER(victim) || (IS_TURRET(victim) && victim.active == ACTIVE_ACTIVE) || IS_MONSTER(victim) || MUTATOR_CALLHOOK(PlayHitsound, victim, attacker))
815 if (DIFF_TEAM(victim, attacker))
819 if(deathtype != DEATH_FIRE.m_id)
821 if(PHYS_INPUT_BUTTON_CHAT(victim))
822 attacker.typehitsound += 1;
824 attacker.hitsound_damage_dealt += damage;
827 impressive_hits += 1;
829 if (!DEATH_ISSPECIAL(deathtype))
831 if(IS_PLAYER(targ)) // don't do this for vehicles
837 else if (IS_PLAYER(attacker) && !STAT(FROZEN, victim)) // same team
839 if (deathtype != DEATH_FIRE.m_id)
841 attacker.typehitsound += 1;
843 if(complainteamdamage > 0)
844 if(time > CS(attacker).teamkill_complain)
846 CS(attacker).teamkill_complain = time + 5;
847 CS(attacker).teamkill_soundtime = time + 0.4;
848 CS(attacker).teamkill_soundsource = targ;
856 if (targ.damageforcescale)
858 if (!IS_PLAYER(targ) || !StatusEffects_active(STATUSEFFECT_SpawnShield, targ) || targ == attacker)
860 vector farce = damage_explosion_calcpush(targ.damageforcescale * force, targ.velocity, autocvar_g_balance_damagepush_speedfactor);
861 if(targ.move_movetype == MOVETYPE_PHYSICS)
863 entity farcent = new(farce);
864 farcent.enemy = targ;
865 farcent.movedir = farce * 10;
867 farcent.movedir = farcent.movedir * targ.mass;
868 farcent.origin = hitloc;
869 farcent.forcetype = FORCETYPE_FORCEATPOS;
870 farcent.nextthink = time + 0.1;
871 setthink(farcent, SUB_Remove);
873 else if(targ.move_movetype != MOVETYPE_NOCLIP)
875 targ.velocity = targ.velocity + farce;
877 UNSET_ONGROUND(targ);
878 UpdateCSQCProjectile(targ);
881 if (damage != 0 || (targ.damageforcescale && force))
882 if (targ.event_damage)
883 targ.event_damage (targ, inflictor, attacker, damage, deathtype, weaponentity, hitloc, force);
885 // apply mirror damage if any
886 if(!autocvar_g_mirrordamage_onlyweapons || DEATH_WEAPONOF(deathtype) != WEP_Null)
887 if(mirrordamage > 0 || mirrorforce > 0)
889 attacker = attacker_save;
891 force = normalize(attacker.origin + attacker.view_ofs - hitloc) * mirrorforce;
892 Damage(attacker, inflictor, attacker, mirrordamage, DEATH_MIRRORDAMAGE.m_id, weaponentity, attacker.origin, force);
896 // Returns total damage applies to creatures
897 float RadiusDamageForSource (entity inflictor, vector inflictororigin, vector inflictorvelocity, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe,
898 float inflictorselfdamage, float forceintensity, float forcezscale, int deathtype, .entity weaponentity, entity directhitentity)
902 float total_damage_to_creatures;
907 float stat_damagedone;
909 if(RadiusDamage_running)
911 backtrace("RadiusDamage called recursively! Expect stuff to go HORRIBLY wrong.");
915 if (rad < 0) rad = 0;
917 RadiusDamage_running = 1;
919 tfloordmg = autocvar_g_throughfloor_damage;
920 tfloorforce = autocvar_g_throughfloor_force;
922 total_damage_to_creatures = 0;
924 if(deathtype != (WEP_HOOK.m_id | HITTYPE_SECONDARY | HITTYPE_BOUNCE)) // only send gravity bomb damage once
925 if(!(deathtype & HITTYPE_SOUND)) // do not send radial sound damage (bandwidth hog)
927 force = inflictorvelocity;
931 force = normalize(force);
932 if(forceintensity >= 0)
933 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, rad, forceintensity * force, deathtype, 0, attacker);
935 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, -rad, (-forceintensity) * force, deathtype, 0, attacker);
940 targ = WarpZone_FindRadius (inflictororigin, rad + MAX_DAMAGEEXTRARADIUS, false);
944 if ((targ != inflictor) || inflictorselfdamage)
945 if (((cantbe != targ) && !mustbe) || (mustbe == targ))
948 // measure distance from nearest point on target (not origin)
949 // to nearest point on inflictor (not origin)
950 vector nearest = targ.WarpZone_findradius_nearest;
951 vector inflictornearest = NearestPointOnBoundingBox(
952 inflictororigin - (inflictor.maxs - inflictor.mins) * 0.5,
953 inflictororigin + (inflictor.maxs - inflictor.mins) * 0.5,
955 vector diff = inflictornearest - nearest;
957 // round up a little on the damage to ensure full damage on impacts
958 // and turn the distance into a fraction of the radius
959 float dist = max(0, vlen(diff) - bound(MIN_DAMAGEEXTRARADIUS, targ.damageextraradius, MAX_DAMAGEEXTRARADIUS));
964 power -= (dist / rad);
965 // at this point power can't be < 0 or > 1
966 float finaldmg = coredamage * power + edgedamage * (1 - power);
972 vector myblastorigin;
975 myblastorigin = WarpZone_TransformOrigin(targ, inflictororigin);
977 // if it's a player, use the view origin as reference
978 center = CENTER_OR_VIEWOFS(targ);
980 force = normalize(center - myblastorigin);
981 force = force * (finaldmg / coredamage) * forceintensity;
984 // apply special scaling along the z axis if set
985 // NOTE: 0 value is not allowed for compatibility, in the case of weapon cvars not being set
987 force.z *= forcezscale;
989 if(targ != directhitentity)
994 float mininv_f, mininv_d;
996 // test line of sight to multiple positions on box,
997 // and do damage if any of them hit
1000 // we know: max stddev of hitratio = 1 / (2 * sqrt(n))
1001 // so for a given max stddev:
1002 // n = (1 / (2 * max stddev of hitratio))^2
1004 mininv_d = (finaldmg * (1-tfloordmg)) / autocvar_g_throughfloor_damage_max_stddev;
1005 mininv_f = (vlen(force) * (1-tfloorforce)) / autocvar_g_throughfloor_force_max_stddev;
1007 if(autocvar_g_throughfloor_debug)
1008 LOG_INFOF("THROUGHFLOOR: D=%f F=%f max(dD)=1/%f max(dF)=1/%f", finaldmg, vlen(force), mininv_d, mininv_f);
1011 total = 0.25 * (max(mininv_f, mininv_d) ** 2);
1013 if(autocvar_g_throughfloor_debug)
1014 LOG_INFOF(" steps=%f", total);
1017 if (IS_PLAYER(targ))
1018 total = ceil(bound(autocvar_g_throughfloor_min_steps_player, total, autocvar_g_throughfloor_max_steps_player));
1020 total = ceil(bound(autocvar_g_throughfloor_min_steps_other, total, autocvar_g_throughfloor_max_steps_other));
1022 if(autocvar_g_throughfloor_debug)
1023 LOG_INFOF(" steps=%f dD=%f dF=%f", total, finaldmg * (1-tfloordmg) / (2 * sqrt(total)), vlen(force) * (1-tfloorforce) / (2 * sqrt(total)));
1025 for(c = 0; c < total; ++c)
1027 //traceline(targ.WarpZone_findradius_findorigin, nearest, MOVE_NOMONSTERS, inflictor);
1028 WarpZone_TraceLine(inflictororigin, WarpZone_UnTransformOrigin(targ, nearest), MOVE_NOMONSTERS, inflictor);
1029 if (trace_fraction == 1 || trace_ent == targ)
1033 hitloc = hitloc + nearest;
1037 nearest.x = targ.origin.x + targ.mins.x + random() * targ.size.x;
1038 nearest.y = targ.origin.y + targ.mins.y + random() * targ.size.y;
1039 nearest.z = targ.origin.z + targ.mins.z + random() * targ.size.z;
1042 nearest = hitloc * (1 / max(1, hits));
1043 hitratio = (hits / total);
1044 a = bound(0, tfloordmg + (1-tfloordmg) * hitratio, 1);
1045 finaldmg = finaldmg * a;
1046 a = bound(0, tfloorforce + (1-tfloorforce) * hitratio, 1);
1049 if(autocvar_g_throughfloor_debug)
1050 LOG_INFOF(" D=%f F=%f", finaldmg, vlen(force));
1053 //if (targ == attacker)
1055 // print("hits ", ftos(hits), " / ", ftos(total));
1056 // print(" finaldmg ", ftos(finaldmg), " force ", vtos(force));
1057 // print(" (", ftos(a), ")\n");
1059 if(finaldmg || force)
1063 total_damage_to_creatures += finaldmg;
1065 if(accuracy_isgooddamage(attacker, targ))
1066 stat_damagedone += finaldmg;
1069 if(targ == directhitentity || DEATH_ISSPECIAL(deathtype))
1070 Damage(targ, inflictor, attacker, finaldmg, deathtype, weaponentity, nearest, force);
1072 Damage(targ, inflictor, attacker, finaldmg, deathtype | HITTYPE_SPLASH, weaponentity, nearest, force);
1080 RadiusDamage_running = 0;
1082 if(!DEATH_ISSPECIAL(deathtype))
1083 accuracy_add(attacker, DEATH_WEAPONOF(deathtype), 0, min(coredamage, stat_damagedone));
1085 return total_damage_to_creatures;
1088 float RadiusDamage(entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float forceintensity, int deathtype, .entity weaponentity, entity directhitentity)
1090 return RadiusDamageForSource(inflictor, (inflictor.origin + (inflictor.mins + inflictor.maxs) * 0.5), inflictor.velocity, attacker, coredamage, edgedamage, rad,
1091 cantbe, mustbe, false, forceintensity, 1, deathtype, weaponentity, directhitentity);
1094 bool Heal(entity targ, entity inflictor, float amount, float limit)
1096 if(game_stopped || (IS_CLIENT(targ) && CS(targ).killcount == FRAGS_SPECTATOR) || STAT(FROZEN, targ) || IS_DEAD(targ))
1099 bool healed = false;
1101 healed = targ.event_heal(targ, inflictor, amount, limit);
1102 // TODO: additional handling? what if the healing kills them? should this abort if healing would do so etc
1103 // TODO: healing fx!
1104 // TODO: armor healing?
1108 float Fire_AddDamage(entity e, entity o, float d, float t, float dt)
1111 float maxtime, mintime, maxdamage, mindamage, maxdps, mindps, totaldamage, totaltime;
1121 if(StatusEffects_active(STATUSEFFECT_Burning, e))
1123 float fireendtime = StatusEffects_gettime(STATUSEFFECT_Burning, e);
1125 mintime = fireendtime - time;
1126 maxtime = max(mintime, t);
1128 mindps = e.fire_damagepersec;
1129 maxdps = max(mindps, dps);
1131 if(maxtime > mintime || maxdps > mindps)
1135 // damage we have right now
1136 mindamage = mindps * mintime;
1138 // damage we want to get
1139 maxdamage = mindamage + d;
1141 // but we can't exceed maxtime * maxdps!
1142 totaldamage = min(maxdamage, maxtime * maxdps);
1146 // totaldamage = min(mindamage + d, maxtime * maxdps)
1148 // totaldamage <= maxtime * maxdps
1149 // ==> totaldamage / maxdps <= maxtime.
1151 // totaldamage / mindps = min(mindamage / mindps + d, maxtime * maxdps / mindps)
1152 // >= min(mintime, maxtime)
1153 // ==> totaldamage / maxdps >= mintime.
1156 // how long do we damage then?
1157 // at least as long as before
1158 // but, never exceed maxdps
1159 totaltime = max(mintime, totaldamage / maxdps); // always <= maxtime due to lemma
1163 // at most as long as maximum allowed
1164 // but, never below mindps
1165 totaltime = min(maxtime, totaldamage / mindps); // always >= mintime due to lemma
1167 // assuming t > mintime, dps > mindps:
1168 // we get d = t * dps = maxtime * maxdps
1169 // totaldamage = min(maxdamage, maxtime * maxdps) = min(... + d, maxtime * maxdps) = maxtime * maxdps
1170 // totaldamage / maxdps = maxtime
1171 // totaldamage / mindps > totaldamage / maxdps = maxtime
1173 // a) totaltime = max(mintime, maxtime) = maxtime
1174 // b) totaltime = min(maxtime, totaldamage / maxdps) = maxtime
1176 // assuming t <= mintime:
1177 // we get maxtime = mintime
1178 // a) totaltime = max(mintime, ...) >= mintime, also totaltime <= maxtime by the lemma, therefore totaltime = mintime = maxtime
1179 // b) totaltime = min(maxtime, ...) <= maxtime, also totaltime >= mintime by the lemma, therefore totaltime = mintime = maxtime
1181 // assuming dps <= mindps:
1182 // we get mindps = maxdps.
1183 // With this, the lemma says that mintime <= totaldamage / mindps = totaldamage / maxdps <= maxtime.
1184 // a) totaltime = max(mintime, totaldamage / maxdps) = totaldamage / maxdps
1185 // b) totaltime = min(maxtime, totaldamage / mindps) = totaldamage / maxdps
1187 e.fire_damagepersec = totaldamage / totaltime;
1188 StatusEffects_apply(STATUSEFFECT_Burning, e, time + totaltime, 0);
1189 if(totaldamage > 1.2 * mindamage)
1191 e.fire_deathtype = dt;
1192 if(e.fire_owner != o)
1195 e.fire_hitsound = false;
1198 if(accuracy_isgooddamage(o, e))
1199 accuracy_add(o, DEATH_WEAPONOF(dt), 0, max(0, totaldamage - mindamage));
1200 return max(0, totaldamage - mindamage); // can never be negative, but to make sure
1207 e.fire_damagepersec = dps;
1208 StatusEffects_apply(STATUSEFFECT_Burning, e, time + t, 0);
1209 e.fire_deathtype = dt;
1211 e.fire_hitsound = false;
1212 if(accuracy_isgooddamage(o, e))
1213 accuracy_add(o, DEATH_WEAPONOF(dt), 0, d);
1218 void Fire_ApplyDamage(entity e)
1223 for(t = 0, o = e.owner; o.owner && t < 16; o = o.owner, ++t);
1224 if(IS_NOT_A_CLIENT(o))
1227 float fireendtime = StatusEffects_gettime(STATUSEFFECT_Burning, e);
1228 t = min(frametime, fireendtime - time);
1229 d = e.fire_damagepersec * t;
1231 hi = e.fire_owner.hitsound_damage_dealt;
1232 ty = e.fire_owner.typehitsound;
1233 Damage(e, e, e.fire_owner, d, e.fire_deathtype, DMG_NOWEP, e.origin, '0 0 0');
1234 if(e.fire_hitsound && e.fire_owner)
1236 e.fire_owner.hitsound_damage_dealt = hi;
1237 e.fire_owner.typehitsound = ty;
1239 e.fire_hitsound = true;
1241 if(!IS_INDEPENDENT_PLAYER(e) && !STAT(FROZEN, e))
1243 IL_EACH(g_damagedbycontents, it.damagedbycontents && it != e,
1245 if(!IS_DEAD(it) && it.takedamage && !IS_INDEPENDENT_PLAYER(it))
1246 if(boxesoverlap(e.absmin, e.absmax, it.absmin, it.absmax))
1248 t = autocvar_g_balance_firetransfer_time * (fireendtime - time);
1249 d = autocvar_g_balance_firetransfer_damage * e.fire_damagepersec * t;
1250 Fire_AddDamage(it, o, d, t, DEATH_FIRE.m_id);