.float teamkill_soundtime;
.entity teamkill_soundsource;
.entity pusher;
+.float istypefrag;
.float taunt_soundtime;
// NOTE: f=0 means still count as a (positive) kill, but count no frags for it
void W_SwitchWeapon_Force(entity e, float w);
+entity GiveFrags_randomweapons;
void GiveFrags (entity attacker, entity targ, float f, float deathtype)
{
- float w;
-
// TODO route through PlayerScores instead
if(gameover) return;
// after a frag, exchange the current weapon (or the culprit, if detectable) by a new random weapon
float culprit;
culprit = DEATH_WEAPONOF(deathtype);
- if(!culprit || !(attacker.weapons & W_WeaponBit(culprit)))
+ if(!culprit)
+ culprit = attacker.weapon;
+ else if(!WEPSET_CONTAINS_EW(attacker, culprit))
culprit = attacker.weapon;
- if(g_weaponarena_random_with_laser && culprit == WEPBIT_LASER)
+ if(g_weaponarena_random_with_laser && culprit == WEP_LASER)
{
// no exchange
}
else
{
+ if(!GiveFrags_randomweapons)
+ {
+ GiveFrags_randomweapons = spawn();
+ GiveFrags_randomweapons.classname = "GiveFrags_randomweapons";
+ }
+
if(inWarmupStage)
- w = warmup_start_weapons;
+ WEPSET_COPY_EA(GiveFrags_randomweapons, warmup_start_weapons);
else
- w = start_weapons;
+ WEPSET_COPY_EA(GiveFrags_randomweapons, start_weapons);
// all others (including the culprit): remove
- w &~= attacker.weapons;
+ WEPSET_ANDNOT_EE(GiveFrags_randomweapons, attacker);
+ WEPSET_ANDNOT_EW(GiveFrags_randomweapons, culprit);
// among the remaining ones, choose one by random
- w = randombits(w, 1, FALSE);
- if(w)
+ W_RandomWeapons(GiveFrags_randomweapons, 1);
+
+ if(!WEPSET_EMPTY_E(GiveFrags_randomweapons))
{
- attacker.weapons |= w;
- attacker.weapons &~= W_WeaponBit(culprit);
+ WEPSET_OR_EE(attacker, GiveFrags_randomweapons);
+ WEPSET_ANDNOT_EW(attacker, culprit);
}
}
// after a frag, choose another random weapon set
- if not(attacker.weapons & W_WeaponBit(attacker.weapon))
+ if not(WEPSET_CONTAINS_EW(attacker, attacker.weapon))
W_SwitchWeapon_Force(attacker, w_getbestweapon(attacker));
}
}
f = 0;
}
- else if(g_ctf)
- {
- if(g_ctf_ignore_frags)
- f = 0;
- }
}
attacker.totalfrags += f;
string Obituary_ExtraFragInfo(entity player) // Extra fragmessage information
{
- string health_output;
- string ping_output;
- string handicap_output;
- string output;
+ string health_output = string_null;
+ string ping_output = string_null;
+ string handicap_output = string_null;
+ string output = string_null;
if(autocvar_sv_fraginfo && ((autocvar_sv_fraginfo == 2) || inWarmupStage))
{
s = strcat(":kill:", mode);
s = strcat(s, ":", ftos(killer.playerid));
s = strcat(s, ":", ftos(killed.playerid));
- s = strcat(s, ":type=", ftos(deathtype));
+ s = strcat(s, ":type=", Deathtype_Name(deathtype));
s = strcat(s, ":items=");
s = AppendItemcodes(s, killer);
if(killed != killer)
GameLogEcho(s);
}
-void Send_KillNotification (string s1, string s2, string s3, float msg, float type)
+void Obituary_SpecialDeath(entity notif_target, float murder, float deathtype, string s1, string s2, float f1, float f2, float f3)
{
- WriteByte(MSG_ALL, SVC_TEMPENTITY);
- WriteByte(MSG_ALL, TE_CSQC_KILLNOTIFY);
- WriteString(MSG_ALL, s1);
- WriteString(MSG_ALL, s2);
- WriteString(MSG_ALL, s3);
- WriteShort(MSG_ALL, msg);
- WriteByte(MSG_ALL, type);
+ float handled, hits;
+ if(DEATH_ISSPECIAL(deathtype))
+ {
+ #define DEATHTYPE(name,msg_death_by,msg_death,position) \
+ { if(deathtype == max(0, name)) \
+ { \
+ #if murder \
+ if(max(0, msg_death_by)) { Send_Notification(notif_target, MSG_ONE, MSG_DEATH, msg_death_by, s1, s2, f1, f2, f3); ++handled; } \
+ #else \
+ if(max(0, msg_death)) { Send_Notification(notif_target, MSG_ONE, MSG_DEATH, msg_death, s1, s2, f1, f2, f3); ++handled; } \
+ #endif \
+ ++hits; \
+ } }
+
+ DEATHTYPES
+ if not(hits)
+ {
+ backtrace("Unhandled deathtype. Please notify Samual!\n");
+ //return;
+ }
+ if not(handled)
+ {
+ print(sprintf("Obituary_SpecialDeath(): ^1Deathtype ^7(%s-%d)^1 has no notification!\n", Deathtype_Name(deathtype), deathtype));
+ return;
+ }
+ }
}
-// Function is used to send a generic centerprint whose content CSQC gets to decide (gentle version or not in the below cases)
-void Send_CSQC_KillCenterprint(entity e, string s1, string s2, float msg, float type)
+void Obituary_WeaponDeath(entity notif_target, float deathtype, string s1, string s2, float f1, float f2, float f3)
{
- if (clienttype(e) == CLIENTTYPE_REAL)
+ float handled, hits;
+ if(DEATH_ISSPECIAL(deathtype))
{
- msg_entity = e;
- WRITESPECTATABLE_MSG_ONE({
- WriteByte(MSG_ONE, SVC_TEMPENTITY);
- WriteByte(MSG_ONE, TE_CSQC_KILLCENTERPRINT);
- WriteString(MSG_ONE, s1);
- WriteString(MSG_ONE, s2);
- WriteShort(MSG_ONE, msg);
- WriteByte(MSG_ONE, type);
- });
+ #define DEATHTYPE(name,msg_death_by,msg_death,position) \
+ { if(deathtype == max(0, name)) \
+ { \
+ ++hits; \
+ } }
+
+ DEATHTYPES
+ if not(hits)
+ {
+ backtrace("Unhandled deathtype. Please notify Samual!\n");
+ //return;
+ }
+ if not(handled)
+ {
+ print(sprintf("Obituary_SpecialDeath(): ^1Deathtype ^7(%s-%d)^1 has no notification!\n", Deathtype_Name(deathtype), deathtype));
+ return;
+ }
}
}
-void Obituary (entity attacker, entity inflictor, entity targ, float deathtype)
+.float FRAG_VERBOSE;
+
+void Obituary(entity attacker, entity inflictor, entity targ, float deathtype)
{
+ // Sanity check
+ if not(targ.classname == "player") { backtrace("Obituary called on non-player?!\n"); return; }
+
+ // Declarations
string s, a, msg;
float w, type;
- if (targ.classname == "player")
- {
- s = targ.netname;
- a = attacker.netname;
+ string s1, s2 = NO_STR_ARG;
+ float f1, f2, f3 = NO_FL_ARG;
+ float notif_firstblood;
- if (targ == attacker) // suicides
- {
- if (deathtype == DEATH_TEAMCHANGE || deathtype == DEATH_AUTOTEAMCHANGE)
- msg = ColoredTeamName(targ.team); // TODO: check if needed?
- if(!g_cts) // no "killed your own dumb self" message in CTS
- Send_CSQC_KillCenterprint(targ, msg, "", deathtype, MSG_SUICIDE);
- if(deathtype != DEATH_TEAMCHANGE && deathtype != DEATH_QUIET)
- {
- LogDeath("suicide", deathtype, targ, targ);
- GiveFrags(attacker, targ, -1, deathtype);
- }
+ print(sprintf("Obituary(): Deathtype = %s (%d), Attacker = %s, Inflictor = %s, Target = %s...\n", Deathtype_Name(deathtype), deathtype, attacker.netname, inflictor.netname, targ.netname));
- if (targ.killcount > 2)
- msg = ftos(targ.killcount);
- if(teamplay && deathtype == DEATH_MIRRORDAMAGE)
- {
- if(attacker.team == COLOR_TEAM1)
- deathtype = KILL_TEAM_RED;
- else
- deathtype = KILL_TEAM_BLUE;
- }
- Send_KillNotification(s, msg, ftos(w), deathtype, MSG_SUICIDE);
- }
- else if (attacker.classname == "player")
+ // =======
+ // SUICIDE
+ // =======
+ if(targ == attacker)
+ {
+ if(DEATH_ISSPECIAL(deathtype))
{
- if(!IsDifferentTeam(attacker, targ))
+ if(deathtype == DEATH_TEAMCHANGE || deathtype == DEATH_AUTOTEAMCHANGE)
{
- if(attacker.team == COLOR_TEAM1)
- type = KILL_TEAM_RED;
- else
- type = KILL_TEAM_BLUE;
-
- GiveFrags(attacker, targ, -1, deathtype);
-
- Send_CSQC_KillCenterprint(attacker, s, "", type, MSG_KILL);
-
- if (targ.killcount > 2) {
- msg = ftos(targ.killcount);
- }
-
- if (attacker.killcount > 2) {
- msg = ftos(attacker.killcount);
- type = KILL_TEAM_SPREE;
- }
- Send_KillNotification(a, s, msg, type, MSG_KILL);
-
- attacker.killcount = 0;
-
- LogDeath("tk", deathtype, attacker, targ);
+ s1 = targ.netname;
+ f1 = targ.team;
}
else
{
- if (!checkrules_firstblood)
+ switch(deathtype)
{
- checkrules_firstblood = TRUE;
- Send_KillNotification(a, "", "", KILL_FIRST_BLOOD, MSG_KILL);
- // TODO: make these print a newline if they dont
- Send_CSQC_KillCenterprint(attacker, "", "", KILL_FIRST_BLOOD, MSG_KILL);
- Send_CSQC_KillCenterprint(targ, "", "", KILL_FIRST_VICTIM, MSG_KILL);
- PlayerStats_Event(attacker, PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD, 1);
- PlayerStats_Event(targ, PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM, 1);
- }
-
- if(targ.BUTTON_CHAT) {
- Send_CSQC_KillCenterprint(attacker, s, Obituary_ExtraFragInfo(targ), KILL_TYPEFRAG, MSG_KILL);
- Send_CSQC_KillCenterprint(targ, a, Obituary_ExtraFragInfo(attacker), KILL_TYPEFRAGGED, MSG_KILL);
- } else {
- Send_CSQC_KillCenterprint(attacker, s, Obituary_ExtraFragInfo(targ), KILL_FRAG, MSG_KILL);
- Send_CSQC_KillCenterprint(targ, a, Obituary_ExtraFragInfo(attacker), KILL_FRAGGED, MSG_KILL);
+ case DEATH_MIRRORDAMAGE:
+ {
+ s1 = targ.netname;
+ f1 = targ.team;
+ //f2 = targ.killcount;
+ break;
+ }
+
+ default:
+ {
+ s1 = s2 = NO_STR_ARG;
+ f1 = f2 = f3 = NO_FL_ARG;
+ break;
+ }
}
+ LogDeath("suicide", deathtype, targ, targ);
+ GiveFrags(attacker, targ, -1, deathtype);
+ }
+ Obituary_SpecialDeath(targ, FALSE, deathtype, s1, s2, f1, f2, NO_FL_ARG);
+ }
+ else if(DEATH_WEAPONOF(deathtype))
+ {
+ print("death was a weapon!\n");
+ }
+ else
+ {
+ backtrace("what the hell happened here?\n");
+ }
+ }
- attacker.taunt_soundtime = time + 1;
-
- // TODO: fix this?
- if (deathtype == DEATH_CUSTOM)
- msg = deathmessage;
- else
- msg = inflictor.message2;
- if(strstrofs(msg, "%", 0) < 0)
- msg = strcat("%s ", msg, " by %s");
+ // ======
+ // MURDER
+ // ======
+ else if(attacker.classname == "player")
+ {
+ if(!IsDifferentTeam(attacker, targ))
+ {
+ if(DEATH_ISSPECIAL(deathtype))
+ {
+ backtrace("hmm death was special?\n");
+ }
+ else if(DEATH_WEAPONOF(deathtype))
+ {
+ print("death was a weapon!\n");
+ }
+ else
+ {
+ backtrace("what the hell happened here?\n");
+ }
+
+ LogDeath("tk", deathtype, attacker, targ);
+ GiveFrags(attacker, targ, -1, deathtype);
+ }
+ else
+ {
+ s1 = attacker.netname;
+ s2 = targ.netname;
- Send_KillNotification(a, s, msg, deathtype, MSG_KILL);
+ attacker.FRAG_VERBOSE = TRUE;
+ targ.FRAG_VERBOSE = TRUE;
- if(g_ctf && targ.flagcarried)
- {
- UpdateFrags(attacker, ctf_score_value("score_kill"));
- PlayerScore_Add(attacker, SP_CTF_FCKILLS, 1);
- GiveFrags(attacker, targ, 0, deathtype); // for logging
- }
- else
- GiveFrags(attacker, targ, 1, deathtype);
+ LogDeath("frag", deathtype, attacker, targ);
+ GiveFrags(attacker, targ, 1, deathtype);
- if (targ.killcount > 2) {
- Send_KillNotification(s, ftos(targ.killcount), a, KILL_END_SPREE, MSG_SPREE);
+ attacker.taunt_soundtime = time + 1;
+ attacker.killcount = attacker.killcount + 1;
+ if(targ.killcount > 2) { Send_KillNotification(s, ftos(targ.killcount), a, KILL_END_SPREE, MSG_SPREE); }
+
+ #define ADD_ACHIEVEMENT_CASE(numa,numb) \
+ case numa: \
+ { \
+ AnnounceTo(attacker, strcat(#numb, "kills")); \
+ PlayerStats_Event(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_##numa, 1); \
+ break; \
}
+ switch(attacker.killcount)
+ {
+ ADD_ACHIEVEMENT_CASE(3, 03)
+ ADD_ACHIEVEMENT_CASE(5, 05)
+ ADD_ACHIEVEMENT_CASE(10, 10)
+ ADD_ACHIEVEMENT_CASE(15, 15)
+ ADD_ACHIEVEMENT_CASE(20, 20)
+ ADD_ACHIEVEMENT_CASE(25, 25)
+ ADD_ACHIEVEMENT_CASE(30, 30)
+ default: break;
+ }
+ #undef ADD_ACHIEVEMENT_CASE
- attacker.killcount = attacker.killcount + 1;
+ if(!checkrules_firstblood)
+ {
+ checkrules_firstblood = TRUE;
+ notif_firstblood = TRUE; // modify the current messages so that they too show firstblood information
+ PlayerStats_Event(attacker, PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD, 1);
+ PlayerStats_Event(targ, PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM, 1);
+ }
- if (attacker.killcount > 2) {
- Send_KillNotification(a, ftos(attacker.killcount), "", KILL_SPREE, MSG_SPREE);
- }
- else if (attacker.killcount == 3)
- {
- Send_KillNotification(a, "", "", KILL_SPREE_3, MSG_SPREE);
- AnnounceTo(attacker, "03kills");
- PlayerStats_Event(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_3, 1);
- }
- else if (attacker.killcount == 5)
- {
- Send_KillNotification(a, "", "", KILL_SPREE_5, MSG_SPREE);
- AnnounceTo(attacker, "05kills");
- PlayerStats_Event(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_5, 1);
- }
- else if (attacker.killcount == 10)
- {
- Send_KillNotification(a, "", "", KILL_SPREE_10, MSG_SPREE);
- AnnounceTo(attacker, "10kills");
- PlayerStats_Event(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_10, 1);
- }
- else if (attacker.killcount == 15)
+ if(notif_firstblood) // first blood, no kill sprees yet
+ {
+ if(targ.istypefrag)
{
- Send_KillNotification(a, "", "", KILL_SPREE_15, MSG_SPREE);
- AnnounceTo(attacker, "15kills");
- PlayerStats_Event(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_15, 1);
+ Send_Notification(attacker, MSG_ONE, MSG_DEATH, (attacker.FRAG_VERBOSE ? DEATH_MURDER_TYPEFRAG_FIRST_VERBOSE : DEATH_MURDER_TYPEFRAG_FIRST),
+ s2, NO_STR_ARG, (attacker.FRAG_VERBOSE ? ((clienttype(targ) == CLIENTTYPE_BOT) ? BOT_PING : targ.ping) : NO_FL_ARG), NO_FL_ARG, NO_FL_ARG);
+
+ Send_Notification(targ, MSG_ONE, MSG_DEATH, (targ.FRAG_VERBOSE ? DEATH_MURDER_TYPEFRAGGED_FIRST_VERBOSE : DEATH_MURDER_TYPEFRAGGED_FIRST),
+ s1, NO_STR_ARG, (targ.FRAG_VERBOSE ? attacker.health : NO_FL_ARG), (targ.FRAG_VERBOSE ? attacker.armorvalue : NO_FL_ARG), (targ.FRAG_VERBOSE ? ((clienttype(attacker) == CLIENTTYPE_BOT) ? BOT_PING : attacker.ping) : NO_FL_ARG));
}
- else if (attacker.killcount == 20)
+ else
{
- Send_KillNotification(a, "", "", KILL_SPREE_20, MSG_SPREE);
- AnnounceTo(attacker, "20kills");
- PlayerStats_Event(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_20, 1);
+ Send_Notification(attacker, MSG_ONE, MSG_DEATH, (attacker.FRAG_VERBOSE ? DEATH_MURDER_FRAG_FIRST_VERBOSE : DEATH_MURDER_FRAG_FIRST),
+ s2, NO_STR_ARG, (attacker.FRAG_VERBOSE ? ((clienttype(targ) == CLIENTTYPE_BOT) ? BOT_PING : targ.ping) : NO_FL_ARG), NO_FL_ARG, NO_FL_ARG);
+
+ Send_Notification(targ, MSG_ONE, MSG_DEATH, (targ.FRAG_VERBOSE ? DEATH_MURDER_FRAGGED_FIRST_VERBOSE : DEATH_MURDER_FRAGGED_FIRST),
+ s1, NO_STR_ARG, (targ.FRAG_VERBOSE ? attacker.health : NO_FL_ARG), (targ.FRAG_VERBOSE ? attacker.armorvalue : NO_FL_ARG), (targ.FRAG_VERBOSE ? ((clienttype(attacker) == CLIENTTYPE_BOT) ? BOT_PING : attacker.ping) : NO_FL_ARG));
}
- else if (attacker.killcount == 25)
+ //Send_Notification(world, MSG_INFO, INFO_DEATH_FRAG_FIRSTBLOOD, s1, s2, attacker.team, NO_FL_ARG, NO_FL_ARG);
+ }
+ else // normal frags, kill sprees listed
+ {
+ if(targ.istypefrag)
{
- Send_KillNotification(a, "", "", KILL_SPREE_25, MSG_SPREE);
- AnnounceTo(attacker, "25kills");
- PlayerStats_Event(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_25, 1);
+ Send_Notification(attacker, MSG_ONE, MSG_DEATH, (attacker.FRAG_VERBOSE ? DEATH_MURDER_TYPEFRAG_VERBOSE : DEATH_MURDER_TYPEFRAG),
+ s2, NO_STR_ARG, attacker.killcount, (attacker.FRAG_VERBOSE ? ((clienttype(targ) == CLIENTTYPE_BOT) ? BOT_PING : targ.ping) : NO_FL_ARG), NO_FL_ARG);
+
+ Send_Notification(targ, MSG_ONE, MSG_DEATH, (targ.FRAG_VERBOSE ? DEATH_MURDER_TYPEFRAGGED_VERBOSE : DEATH_MURDER_TYPEFRAGGED),
+ s1, NO_STR_ARG, (targ.FRAG_VERBOSE ? attacker.health : NO_FL_ARG), (targ.FRAG_VERBOSE ? attacker.armorvalue : NO_FL_ARG), (targ.FRAG_VERBOSE ? ((clienttype(attacker) == CLIENTTYPE_BOT) ? BOT_PING : attacker.ping) : NO_FL_ARG));
}
- else if (attacker.killcount == 30)
+ else
{
- Send_KillNotification(a, "", "", KILL_SPREE_30, MSG_SPREE);
- AnnounceTo(attacker, "30kills");
- PlayerStats_Event(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_30, 1);
+ Send_Notification(attacker, MSG_ONE, MSG_DEATH, (attacker.FRAG_VERBOSE ? DEATH_MURDER_FRAG_VERBOSE : DEATH_MURDER_FRAG),
+ s2, NO_STR_ARG, attacker.killcount, (attacker.FRAG_VERBOSE ? ((clienttype(targ) == CLIENTTYPE_BOT) ? BOT_PING : targ.ping) : NO_FL_ARG), NO_FL_ARG);
+
+ Send_Notification(targ, MSG_ONE, MSG_DEATH, (targ.FRAG_VERBOSE ? DEATH_MURDER_FRAGGED_VERBOSE : DEATH_MURDER_FRAGGED),
+ s1, NO_STR_ARG, (targ.FRAG_VERBOSE ? attacker.health : NO_FL_ARG), (targ.FRAG_VERBOSE ? attacker.armorvalue : NO_FL_ARG), (targ.FRAG_VERBOSE ? ((clienttype(attacker) == CLIENTTYPE_BOT) ? BOT_PING : attacker.ping) : NO_FL_ARG));
}
- LogDeath("frag", deathtype, attacker, targ);
+ //if(DEATH_WEAPONOF(deathtype)) { Send_Notification(world, MSG_WEAPON, 50, s1, s2, attacker.killcount, targ.killcount, Obituary_Score_Position); }
+ //else { Obituary_SpecialDeath(world, deathtype, s1, s2, attacker.killcount, targ.killcount, Obituary_Score_Position); }
}
}
- else
- {
- Send_CSQC_KillCenterprint(targ, "", "", deathtype, MSG_KILL_ACTION);
- if (deathtype == DEATH_HURTTRIGGER && inflictor.message != "")
- msg = inflictor.message;
- else if (deathtype == DEATH_CUSTOM)
- msg = deathmessage;
- if(strstrofs(msg, "%", 0) < 0)
- msg = strcat("%s ", msg);
-
- GiveFrags(targ, targ, -1, deathtype);
- if(PlayerScore_Add(targ, SP_SCORE, 0) == -5) {
- AnnounceTo(targ, "botlike");
- PlayerStats_Event(attacker, PLAYERSTATS_ACHIEVEMENT_BOTLIKE, 1);
- }
- Send_KillNotification(s, msg, "", deathtype, MSG_KILL_ACTION);
+ }
- if (targ.killcount > 2)
- Send_KillNotification(s, ftos(targ.killcount), "", 0, MSG_KILL_ACTION_SPREE);
+ // =============
+ // ACCIDENT/TRAP
+ // =============
+ else
+ {
+ if (deathtype == DEATH_HURTTRIGGER && inflictor.message != "")
+ msg = inflictor.message;
+ else if (deathtype == DEATH_CUSTOM)
+ msg = deathmessage;
+ else
+ msg = "";
+
+ if(strstrofs(msg, "%", 0) < 0) { msg = strcat("%s ", msg); }
- LogDeath("accident", deathtype, targ, targ);
+ LogDeath("accident", deathtype, targ, targ);
+ GiveFrags(targ, targ, -1, deathtype);
+ if(PlayerScore_Add(targ, SP_SCORE, 0) == -5) {
+ AnnounceTo(targ, "botlike");
+ PlayerStats_Event(attacker, PLAYERSTATS_ACHIEVEMENT_BOTLIKE, 1);
}
+ //Send_KillNotification(s, msg, "", deathtype, MSG_KILL_ACTION);
- targ.death_origin = targ.origin;
- if(targ != attacker)
- targ.killer_origin = attacker.origin;
+ //if (targ.killcount > 2)
+ // Send_KillNotification(s, ftos(targ.killcount), "", 0, MSG_KILL_ACTION_SPREE);
- // FIXME: this should go in PutClientInServer
- if (targ.killcount)
- targ.killcount = 0;
+ Obituary_SpecialDeath(targ, FALSE, deathtype, s1, s2, f1, f2, NO_FL_ARG);
}
+
+ targ.death_origin = targ.origin;
+ if(targ != attacker)
+ targ.killer_origin = attacker.origin;
+
+ // FIXME: this should go in PutClientInServer
+ if (targ.killcount)
+ targ.killcount = 0;
}
// these are updated by each Damage call for use in button triggering and such
}
}
- if(deathtype == DEATH_KILL || deathtype == DEATH_TEAMCHANGE || deathtype == DEATH_AUTOTEAMCHANGE || deathtype == DEATH_QUIET)
+ if(deathtype == DEATH_KILL || deathtype == DEATH_TEAMCHANGE || deathtype == DEATH_AUTOTEAMCHANGE)
{
// These are ALWAYS lethal
// No damage modification here
if(autocvar_g_mirrordamage_virtual)
{
- vector v;
- v = healtharmor_applydamage(attacker.armorvalue, autocvar_g_balance_armor_blockpercent, mirrordamage);
- v_z = 0; // fteqcc sucks
+ vector v = healtharmor_applydamage(attacker.armorvalue, autocvar_g_balance_armor_blockpercent, mirrordamage);
attacker.dmg_take += v_x;
attacker.dmg_save += v_y;
attacker.dmg_inflictor = inflictor;
- mirrordamage = 0;
+ mirrordamage = v_z; // = 0, to make fteqcc stfu
mirrorforce = 0;
}
if(autocvar_g_friendlyfire_virtual)
{
- vector v;
- v = healtharmor_applydamage(targ.armorvalue, autocvar_g_balance_armor_blockpercent, damage);
- v_z = 0; // fteqcc sucks
+ vector v = healtharmor_applydamage(targ.armorvalue, autocvar_g_balance_armor_blockpercent, damage);
targ.dmg_take += v_x;
targ.dmg_save += v_y;
targ.dmg_inflictor = inflictor;
damage = damage * autocvar_g_balance_selfdamagepercent; // Partial damage if the attacker hits himself
}
- // CTF: reduce damage/force
- if(g_ctf)
- if(targ == attacker)
- if(targ.flagcarried)
- {
- damage = damage * autocvar_g_ctf_flagcarrier_selfdamage;
- force = force * autocvar_g_ctf_flagcarrier_selfforce;
- }
-
if(g_runematch)
{
// apply strength rune
if(targ.takedamage == DAMAGE_AIM)
if(targ != attacker)
{
- if(damage_headshotbonus > 0)
+ if(damage_headshotbonus)
{
if(targ.classname == "player")
{
deathtype |= HITTYPE_HEADSHOT;
}
if(deathtype & HITTYPE_HEADSHOT)
- damage *= 1 + damage_headshotbonus;
+ if(damage_headshotbonus > 0)
+ damage *= 1 + damage_headshotbonus;
}
entity victim;
float RadiusDamage_running;
float RadiusDamage (entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity ignore, float forceintensity, float deathtype, entity directhitentity)
-// Returns total damage applies to creatures
+ // Returns total damage applies to creatures
{
entity targ;
- float finaldmg;
- float power;
vector blastorigin;
vector force;
- vector diff;
- vector center;
- vector nearest;
float total_damage_to_creatures;
entity next;
float tfloordmg;
total_damage_to_creatures = 0;
if(deathtype != (WEP_HOOK | HITTYPE_SECONDARY | HITTYPE_BOUNCE)) // only send gravity bomb damage once
- if(DEATH_WEAPONOF(deathtype) != WEP_TUBA) // do not send tuba damage (bandwidth hog)
- {
- force = inflictor.velocity;
- if(vlen(force) == 0)
- force = '0 0 -1';
- else
- force = normalize(force);
- if(forceintensity >= 0)
- Damage_DamageInfo(blastorigin, coredamage, edgedamage, rad, forceintensity * force, deathtype, 0, attacker);
- else
- Damage_DamageInfo(blastorigin, coredamage, edgedamage, -rad, (-forceintensity) * force, deathtype, 0, attacker);
- }
+ if(DEATH_WEAPONOF(deathtype) != WEP_TUBA) // do not send tuba damage (bandwidth hog)
+ {
+ force = inflictor.velocity;
+ if(vlen(force) == 0)
+ force = '0 0 -1';
+ else
+ force = normalize(force);
+ if(forceintensity >= 0)
+ Damage_DamageInfo(blastorigin, coredamage, edgedamage, rad, forceintensity * force, deathtype, 0, attacker);
+ else
+ Damage_DamageInfo(blastorigin, coredamage, edgedamage, -rad, (-forceintensity) * force, deathtype, 0, attacker);
+ }
stat_damagedone = 0;
if (targ != inflictor)
if (ignore != targ) if(targ.takedamage)
{
+ vector nearest;
+ vector diff;
+ float power;
+
// LordHavoc: measure distance to nearest point on target (not origin)
// (this guarentees 100% damage on a touch impact)
nearest = targ.WarpZone_findradius_nearest;
// print(ftos(power), "\n");
if (power > 0)
{
+ float finaldmg;
if (power > 1)
power = 1;
finaldmg = coredamage * power + edgedamage * (1 - power);
{
float a;
float c;
- float hits;
- float total;
- float hitratio;
vector hitloc;
vector myblastorigin;
+ vector center;
+
myblastorigin = WarpZone_TransformOrigin(targ, blastorigin);
- center = targ.origin + (targ.mins + targ.maxs) * 0.5;
+
// if it's a player, use the view origin as reference
- if (targ.classname == "player")
- center = targ.origin + targ.view_ofs;
+ center = CENTER_OR_VIEWOFS(targ);
+
force = normalize(center - myblastorigin);
force = force * (finaldmg / coredamage) * forceintensity;
- // test line of sight to multiple positions on box,
- // and do damage if any of them hit
- hits = 0;
- if (targ.classname == "player")
- total = ceil(bound(1, finaldmg, 50));
- else
- total = ceil(bound(1, finaldmg/10, 5));
hitloc = nearest;
- c = 0;
- while (c < total)
+
+ if(targ != directhitentity)
{
- //traceline(targ.WarpZone_findradius_findorigin, nearest, MOVE_NOMONSTERS, inflictor);
- WarpZone_TraceLine(blastorigin, WarpZone_UnTransformOrigin(targ, nearest), MOVE_NOMONSTERS, inflictor);
- if (trace_fraction == 1 || trace_ent == targ)
+ float hits;
+ float total;
+ float hitratio;
+ float mininv_f, mininv_d;
+
+ // test line of sight to multiple positions on box,
+ // and do damage if any of them hit
+ hits = 0;
+
+ // we know: max stddev of hitratio = 1 / (2 * sqrt(n))
+ // so for a given max stddev:
+ // n = (1 / (2 * max stddev of hitratio))^2
+
+ mininv_d = (finaldmg * (1-tfloordmg)) / autocvar_g_throughfloor_damage_max_stddev;
+ mininv_f = (vlen(force) * (1-tfloorforce)) / autocvar_g_throughfloor_force_max_stddev;
+
+ if(autocvar_g_throughfloor_debug)
+ print(sprintf("THROUGHFLOOR: D=%f F=%f max(dD)=1/%f max(dF)=1/%f", finaldmg, vlen(force), mininv_d, mininv_f));
+
+ total = 0.25 * pow(max(mininv_f, mininv_d), 2);
+
+ if(autocvar_g_throughfloor_debug)
+ print(sprintf(" steps=%f", total));
+
+ if (targ.classname == "player")
+ total = ceil(bound(autocvar_g_throughfloor_min_steps_player, total, autocvar_g_throughfloor_max_steps_player));
+ else
+ total = ceil(bound(autocvar_g_throughfloor_min_steps_other, total, autocvar_g_throughfloor_max_steps_other));
+
+ if(autocvar_g_throughfloor_debug)
+ print(sprintf(" steps=%f dD=%f dF=%f", total, finaldmg * (1-tfloordmg) / (2 * sqrt(total)), vlen(force) * (1-tfloorforce) / (2 * sqrt(total))));
+
+ for(c = 0; c < total; ++c)
{
- hits = hits + 1;
- if (hits > 1)
- hitloc = hitloc + nearest;
- else
- hitloc = nearest;
+ //traceline(targ.WarpZone_findradius_findorigin, nearest, MOVE_NOMONSTERS, inflictor);
+ WarpZone_TraceLine(blastorigin, WarpZone_UnTransformOrigin(targ, nearest), MOVE_NOMONSTERS, inflictor);
+ if (trace_fraction == 1 || trace_ent == targ)
+ {
+ ++hits;
+ if (hits > 1)
+ hitloc = hitloc + nearest;
+ else
+ hitloc = nearest;
+ }
+ nearest_x = targ.origin_x + targ.mins_x + random() * targ.size_x;
+ nearest_y = targ.origin_y + targ.mins_y + random() * targ.size_y;
+ nearest_z = targ.origin_z + targ.mins_z + random() * targ.size_z;
}
- nearest_x = targ.origin_x + targ.mins_x + random() * targ.size_x;
- nearest_y = targ.origin_y + targ.mins_y + random() * targ.size_y;
- nearest_z = targ.origin_z + targ.mins_z + random() * targ.size_z;
- c = c + 1;
+
+ nearest = hitloc * (1 / max(1, hits));
+ hitratio = (hits / total);
+ a = bound(0, tfloordmg + (1-tfloordmg) * hitratio, 1);
+ finaldmg = finaldmg * a;
+ a = bound(0, tfloorforce + (1-tfloorforce) * hitratio, 1);
+ force = force * a;
+
+ if(autocvar_g_throughfloor_debug)
+ print(sprintf(" D=%f F=%f\n", finaldmg, vlen(force)));
}
- nearest = hitloc * (1 / max(1, hits));
- hitratio = (hits / total);
- a = bound(0, tfloordmg + (1-tfloordmg) * hitratio, 1);
- finaldmg = finaldmg * a;
- a = bound(0, tfloorforce + (1-tfloorforce) * hitratio, 1);
- force = force * a;
// laser force adjustments :P
if(DEATH_WEAPONOF(deathtype) == WEP_LASER)
{
- if (targ == attacker)
- {
- vector vel;
-
- float force_zscale;
- float force_velocitybiasramp;
- float force_velocitybias;
-
- force_velocitybiasramp = autocvar_sv_maxspeed;
- if(deathtype & HITTYPE_SECONDARY)
- {
- force_zscale = autocvar_g_balance_laser_secondary_force_zscale;
- force_velocitybias = autocvar_g_balance_laser_secondary_force_velocitybias;
- }
- else
- {
- force_zscale = autocvar_g_balance_laser_primary_force_zscale;
- force_velocitybias = autocvar_g_balance_laser_primary_force_velocitybias;
- }
-
- vel = targ.velocity;
- vel_z = 0;
- vel = normalize(vel) * bound(0, vlen(vel) / force_velocitybiasramp, 1) * force_velocitybias;
- force =
- vlen(force)
- *
- normalize(normalize(force) + vel);
-
- force_z *= force_zscale;
- }
- else
- {
- if(deathtype & HITTYPE_SECONDARY)
- {
- force *= autocvar_g_balance_laser_secondary_force_other_scale;
- }
- else
- {
- force *= autocvar_g_balance_laser_primary_force_other_scale;
- }
- }
+ if (targ == attacker)
+ {
+ vector vel;
+
+ float force_zscale;
+ float force_velocitybiasramp;
+ float force_velocitybias;
+
+ force_velocitybiasramp = autocvar_sv_maxspeed;
+ if(deathtype & HITTYPE_SECONDARY)
+ {
+ force_zscale = autocvar_g_balance_laser_secondary_force_zscale;
+ force_velocitybias = autocvar_g_balance_laser_secondary_force_velocitybias;
+ }
+ else
+ {
+ force_zscale = autocvar_g_balance_laser_primary_force_zscale;
+ force_velocitybias = autocvar_g_balance_laser_primary_force_velocitybias;
+ }
+
+ vel = targ.velocity;
+ vel_z = 0;
+ vel = normalize(vel) * bound(0, vlen(vel) / force_velocitybiasramp, 1) * force_velocitybias;
+ force =
+ vlen(force)
+ *
+ normalize(normalize(force) + vel);
+
+ force_z *= force_zscale;
+ }
+ else
+ {
+ if(deathtype & HITTYPE_SECONDARY)
+ {
+ force *= autocvar_g_balance_laser_secondary_force_other_scale;
+ }
+ else
+ {
+ force *= autocvar_g_balance_laser_primary_force_other_scale;
+ }
+ }
}
//if (targ == attacker)
// print(" finaldmg ", ftos(finaldmg), " force ", vtos(force));
// print(" (", ftos(a), ")\n");
//}
- if(hits || tfloordmg || tfloorforce)
+ if(finaldmg || vlen(force))
{
if(targ.iscreature)
{
if(maxtime > mintime || maxdps > mindps)
{
+ // Constraints:
+
+ // damage we have right now
mindamage = mindps * mintime;
- maxdamage = mindamage + d;
-
- // interval [mintime, maxtime] * [mindps, maxdps]
- // intersected with
- // [mindamage, maxdamage]
- // maximum of this!
- if(maxdamage >= maxtime * maxdps)
- {
- totaltime = maxtime;
- totaldamage = maxtime * maxdps;
-
- // this branch increases totaldamage if either t > mintime, or dps > mindps
- }
- else
- {
- // maxdamage is inside the interval!
- // first, try to use mindps; only if this fails, increase dps as needed
- totaltime = min(maxdamage / mindps, maxtime); // maxdamage / mindps >= mindamage / mindps = mintime
- totaldamage = maxdamage;
- // can totaldamage / totaltime be >= maxdps?
- // max(mindps, maxdamage / maxtime) >= maxdps?
- // we know maxdamage < maxtime * maxdps
- // so it cannot be
-
- // this branch ALWAYS increases totaldamage, but requires maxdamage < maxtime * maxdps
- }
+ // damage we want to get
+ maxdamage = mindamage + d;
- // total conditions for increasing:
- // maxtime > mintime OR maxdps > mindps OR maxtime * maxdps > maxdamage
- // however:
- // if maxtime = mintime, maxdps = mindps
- // then:
- // maxdamage = mindamage + d
- // mindamage = mindps * mintime = maxdps * maxtime < maxdamage!
- // so the last condition is not needed
+ // but we can't exceed maxtime * maxdps!
+ totaldamage = min(maxdamage, maxtime * maxdps);
+
+ // LEMMA:
+ // Look at:
+ // totaldamage = min(mindamage + d, maxtime * maxdps)
+ // We see:
+ // totaldamage <= maxtime * maxdps
+ // ==> totaldamage / maxdps <= maxtime.
+ // We also see:
+ // totaldamage / mindps = min(mindamage / mindps + d, maxtime * maxdps / mindps)
+ // >= min(mintime, maxtime)
+ // ==> totaldamage / maxdps >= mintime.
+
+ /*
+ // how long do we damage then?
+ // at least as long as before
+ // but, never exceed maxdps
+ totaltime = max(mintime, totaldamage / maxdps); // always <= maxtime due to lemma
+ */
+
+ // alternate:
+ // at most as long as maximum allowed
+ // but, never below mindps
+ totaltime = min(maxtime, totaldamage / mindps); // always >= mintime due to lemma
+
+ // assuming t > mintime, dps > mindps:
+ // we get d = t * dps = maxtime * maxdps
+ // totaldamage = min(maxdamage, maxtime * maxdps) = min(... + d, maxtime * maxdps) = maxtime * maxdps
+ // totaldamage / maxdps = maxtime
+ // totaldamage / mindps > totaldamage / maxdps = maxtime
+ // FROM THIS:
+ // a) totaltime = max(mintime, maxtime) = maxtime
+ // b) totaltime = min(maxtime, totaldamage / maxdps) = maxtime
+
+ // assuming t <= mintime:
+ // we get maxtime = mintime
+ // a) totaltime = max(mintime, ...) >= mintime, also totaltime <= maxtime by the lemma, therefore totaltime = mintime = maxtime
+ // b) totaltime = min(maxtime, ...) <= maxtime, also totaltime >= mintime by the lemma, therefore totaltime = mintime = maxtime
+
+ // assuming dps <= mindps:
+ // we get mindps = maxdps.
+ // With this, the lemma says that mintime <= totaldamage / mindps = totaldamage / maxdps <= maxtime.
+ // a) totaltime = max(mintime, totaldamage / maxdps) = totaldamage / maxdps
+ // b) totaltime = min(maxtime, totaldamage / mindps) = totaldamage / maxdps
e.fire_damagepersec = totaldamage / totaltime;
e.fire_endtime = time + totaltime;