+.entity accuracy;
+.float accuracy_frags[WEP_MAXCOUNT];
+
float weaponstats_buffer;
void WeaponStats_Init()
{
- if(autocvar_sv_weaponstats_killfile != "" || autocvar_sv_weaponstats_damagefile != "")
+ if(autocvar_sv_weaponstats_file != "")
weaponstats_buffer = buf_create();
else
weaponstats_buffer = -1;
}
-#define WEAPONSTATS_GETINDEX(awep,vwep) ((vwep) + (awep) * (WEP_LAST - WEP_FIRST + 1) - (WEP_FIRST + WEP_FIRST * (WEP_LAST - WEP_FIRST + 1)))
+#define WEAPONSTATS_GETINDEX(awep,abot,vwep,vbot) (((vwep) + (awep) * (WEP_LAST - WEP_FIRST + 1) - (WEP_FIRST + WEP_FIRST * (WEP_LAST - WEP_FIRST + 1))) * 4 + (abot) * 2 + (vbot))
void WeaponStats_Shutdown()
{
- float i, j, idx, f;
+ float i, j, ibot, jbot, idx;
float fh;
+ vector v;
string prefix;
if(weaponstats_buffer < 0)
return;
prefix = strcat(autocvar_hostname, "\t", GetGametype(), "_", GetMapname(), "\t");
- if(autocvar_sv_weaponstats_killfile != "")
- {
- fh = fopen(autocvar_sv_weaponstats_killfile, FILE_APPEND);
- if(fh >= 0)
- {
- fputs(fh, "#begin killfile\n");
- fputs(fh, strcat("#date ", strftime(TRUE, "%a %b %e %H:%M:%S %Z %Y"), "\n"));
- fputs(fh, strcat("#config ", ftos(crc16(FALSE, cvar_changes)), "\n"));
- for(i = WEP_FIRST; i <= WEP_LAST; ++i)
- for(j = WEP_FIRST; j <= WEP_LAST; ++j)
- {
- idx = WEAPONSTATS_GETINDEX(i, j);
- f = stov(bufstr_get(weaponstats_buffer, idx)) * '0 1 0';
- if(f != 0)
- fputs(fh, strcat(prefix, ftos(i), "\t", ftos(j), "\t", ftos(f), "\n"));
- }
- fputs(fh, "#end\n\n");
- fclose(fh);
- print("Weapon kill stats written\n");
- }
- }
- if(autocvar_sv_weaponstats_damagefile != "")
+ if(autocvar_sv_weaponstats_file != "")
{
- fh = fopen(autocvar_sv_weaponstats_damagefile, FILE_APPEND);
+ fh = fopen(autocvar_sv_weaponstats_file, FILE_APPEND);
if(fh >= 0)
{
- fputs(fh, "#begin damagefile\n");
+ fputs(fh, "#begin statsfile\n");
fputs(fh, strcat("#date ", strftime(TRUE, "%a %b %e %H:%M:%S %Z %Y"), "\n"));
fputs(fh, strcat("#config ", ftos(crc16(FALSE, cvar_changes)), "\n"));
- for(i = WEP_FIRST; i <= WEP_LAST; ++i)
- for(j = WEP_FIRST; j <= WEP_LAST; ++j)
+ for(i = WEP_FIRST; i <= WEP_LAST; ++i) for(ibot = 0; ibot <= 1; ++ibot)
+ for(j = WEP_FIRST; j <= WEP_LAST; ++j) for(jbot = 0; jbot <= 1; ++jbot)
{
- idx = WEAPONSTATS_GETINDEX(i, j);
- f = stov(bufstr_get(weaponstats_buffer, idx)) * '1 0 0';
- if(f != 0)
- fputs(fh, strcat(prefix, ftos(i), "\t", ftos(j), "\t", ftos(f), "\n"));
+ idx = WEAPONSTATS_GETINDEX(i, ibot, j, jbot);
+ v = stov(bufstr_get(weaponstats_buffer, idx));
+ if(v != '0 0 0')
+ {
+ //vector is: kills hits damage
+ fputs(fh, sprintf("%s%d %d\t%d %d\t", prefix, i, ibot, j, jbot));
+ fputs(fh, sprintf("%d %d %g\n", v_x, v_y, v_z));
+ }
}
fputs(fh, "#end\n\n");
fclose(fh);
- print("Weapon damage stats written\n");
+ print("Weapon stats written\n");
}
}
buf_del(weaponstats_buffer);
weaponstats_buffer = -1;
}
-void WeaponStats_LogItem(float awep, float vwep, vector item)
+void WeaponStats_LogItem(float awep, float abot, float vwep, float vbot, vector item)
{
float idx;
if(weaponstats_buffer < 0)
return;
if(awep > WEP_LAST || vwep > WEP_LAST)
return;
- idx = WEAPONSTATS_GETINDEX(awep,vwep);
+ idx = WEAPONSTATS_GETINDEX(awep,abot,vwep,vbot);
bufstr_set(weaponstats_buffer, idx, vtos(stov(bufstr_get(weaponstats_buffer, idx)) + item));
}
-void WeaponStats_LogDamage(float awep, float vwep, float damage)
+void WeaponStats_LogDamage(float awep, float abot, float vwep, float vbot, float damage)
{
if(damage < 0)
error("negative damage?");
- WeaponStats_LogItem(awep, vwep, '1 0 0' * damage);
+ WeaponStats_LogItem(awep, abot, vwep, vbot, '0 0 1' * damage + '0 1 0');
}
-void WeaponStats_LogKill(float awep, float vwep)
+void WeaponStats_LogKill(float awep, float abot, float vwep, float vbot)
{
- WeaponStats_LogItem(awep, vwep, '0 1 0');
+ WeaponStats_LogItem(awep, abot, vwep, vbot, '1 0 0');
}
// changes by LordHavoc on 03/29/04 and 03/30/04 at Vermeulen's request
self.anim_forwardleft = '20 1 1';
self.anim_backright = '21 1 1';
self.anim_backleft = '22 1 1';
+ self.anim_melee = '23 1 1';
animparseerror = FALSE;
animfilename = strcat(self.model, ".animinfo");
animfile = fopen(animfilename, FILE_READ);
self.anim_forwardleft = animparseline(animfile);
self.anim_backright = animparseline(animfile);
self.anim_backleft = animparseline(animfile);
+ self.anim_melee = animparseline(animfile);
fclose(animfile);
// derived anims
{
if(self.weapons & W_WeaponBit(j))
if(W_IsWeaponThrowable(j))
- W_ThrowNewWeapon(self, j, FALSE, self.origin, randomvec() * 175 + '0 0 325');
+ W_ThrowNewWeapon(self, j, FALSE, org, randomvec() * 175 + '0 0 325');
}
}
else
- W_ThrowWeapon(randomvec() * 125 + '0 0 200', org - self.origin, FALSE);
+ {
+ if(W_IsWeaponThrowable(self.weapon))
+ W_ThrowNewWeapon(self, self.weapon, FALSE, org, randomvec() * 125 + '0 0 200');
+ }
}
void PlayerCorpseDamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
if(sound_allowed(MSG_BROADCAST, attacker))
{
if (save > 10)
- sound (self, CHAN_PROJECTILE, "misc/armorimpact.wav", VOL_BASE, ATTN_NORM);
+ sound (self, CH_SHOTS, "misc/armorimpact.wav", VOL_BASE, ATTN_NORM);
else if (take > 30)
- sound (self, CHAN_PROJECTILE, "misc/bodyimpact2.wav", VOL_BASE, ATTN_NORM);
+ sound (self, CH_SHOTS, "misc/bodyimpact2.wav", VOL_BASE, ATTN_NORM);
else if (take > 10)
- sound (self, CHAN_PROJECTILE, "misc/bodyimpact1.wav", VOL_BASE, ATTN_NORM);
+ sound (self, CH_SHOTS, "misc/bodyimpact1.wav", VOL_BASE, ATTN_NORM);
}
if (take > 50)
self.dmg_take = self.dmg_take + take;//max(take - 10, 0);
self.dmg_inflictor = inflictor;
- if (self.health <= -100 && self.modelindex != 0)
+ if (self.health <= -autocvar_sv_gibhealth && self.modelindex != 0)
{
// don't use any animations as a gib
self.frame = 0;
void ClientKill_Now_TeamChange();
void freezetag_CheckWinner();
-void freezetag_Unfreeze();
void PlayerDamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
{
local float take, save, waves, sdelay, dh, da, j;
vector v;
float valid_damage_for_weaponstats;
+ float excess;
+
+ if((g_arena && numspawned < 2) || (g_ca && !ca_teams_ok) && !inWarmupStage)
+ return;
dh = max(self.health, 0);
da = max(self.armorvalue, 0);
else
Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, self, attacker);
- if((g_arena && numspawned < 2) || (g_ca && ca_players < required_ca_players) && !inWarmupStage)
- return;
-
if (!g_minstagib)
{
v = healtharmor_applydamage(self.armorvalue, autocvar_g_balance_armor_blockpercent, damage);
MUTATOR_CALLHOOK(PlayerDamage_SplitHealthArmor);
take = bound(0, damage_take, self.health);
save = bound(0, damage_save, self.armorvalue);
+ excess = max(0, damage - take - save);
if(sound_allowed(MSG_BROADCAST, attacker))
{
if (save > 10)
- sound (self, CHAN_PROJECTILE, "misc/armorimpact.wav", VOL_BASE, ATTN_NORM);
+ sound (self, CH_SHOTS, "misc/armorimpact.wav", VOL_BASE, ATTN_NORM);
else if (take > 30)
- sound (self, CHAN_PROJECTILE, "misc/bodyimpact2.wav", VOL_BASE, ATTN_NORM);
+ sound (self, CH_SHOTS, "misc/bodyimpact2.wav", VOL_BASE, ATTN_NORM);
else if (take > 10)
- sound (self, CHAN_PROJECTILE, "misc/bodyimpact1.wav", VOL_BASE, ATTN_NORM); // FIXME possibly remove them?
+ sound (self, CH_SHOTS, "misc/bodyimpact1.wav", VOL_BASE, ATTN_NORM); // FIXME possibly remove them?
}
if (take > 50)
// exclude pain sounds for laserjumps as long as you aren't REALLY low on health and would die of the next two
{
if(deathtype == DEATH_FALL)
- PlayerSound(playersound_fall, CHAN_PAIN, VOICETYPE_PLAYERSOUND);
+ PlayerSound(playersound_fall, CH_PAIN, VOICETYPE_PLAYERSOUND);
else if(self.health > 75) // TODO make a "gentle" version?
- PlayerSound(playersound_pain100, CHAN_PAIN, VOICETYPE_PLAYERSOUND);
+ PlayerSound(playersound_pain100, CH_PAIN, VOICETYPE_PLAYERSOUND);
else if(self.health > 50)
- PlayerSound(playersound_pain75, CHAN_PAIN, VOICETYPE_PLAYERSOUND);
+ PlayerSound(playersound_pain75, CH_PAIN, VOICETYPE_PLAYERSOUND);
else if(self.health > 25)
- PlayerSound(playersound_pain50, CHAN_PAIN, VOICETYPE_PLAYERSOUND);
+ PlayerSound(playersound_pain50, CH_PAIN, VOICETYPE_PLAYERSOUND);
else
- PlayerSound(playersound_pain25, CHAN_PAIN, VOICETYPE_PLAYERSOUND);
+ PlayerSound(playersound_pain25, CH_PAIN, VOICETYPE_PLAYERSOUND);
}
}
// escape a lava pit or similar
//self.pushltime = 0;
}
- else if(attacker.classname == "player" || attacker.classname == "gib")
+ else if(attacker.classname == "player")
{
self.pusher = attacker;
self.pushltime = time + autocvar_g_maxpushtime;
else
self.pushltime = 0;
+ float abot, vbot, awep;
+ abot = (clienttype(attacker) == CLIENTTYPE_BOT);
+ vbot = (clienttype(self) == CLIENTTYPE_BOT);
+
valid_damage_for_weaponstats = 0;
- if(clienttype(self) == CLIENTTYPE_REAL)
- if(clienttype(attacker) == CLIENTTYPE_REAL)
- if(self != attacker)
- if(!DEATH_ISSPECIAL(deathtype))
+ if(vbot || clienttype(self) == CLIENTTYPE_REAL)
+ if(abot || clienttype(attacker) == CLIENTTYPE_REAL)
+ if(attacker && self != attacker)
if(IsDifferentTeam(self, attacker))
+ {
+ if(DEATH_ISSPECIAL(deathtype))
+ awep = attacker.weapon;
+ else
+ awep = DEATH_WEAPONOF(deathtype);
valid_damage_for_weaponstats = 1;
-
+ }
+
if(valid_damage_for_weaponstats)
{
dh = dh - max(self.health, 0);
da = da - max(self.armorvalue, 0);
- WeaponStats_LogDamage(DEATH_WEAPONOF(deathtype), self.weapon, dh + da);
+ WeaponStats_LogDamage(awep, abot, self.weapon, vbot, dh + da);
}
if (self.health < 1)
}
if(valid_damage_for_weaponstats)
- WeaponStats_LogKill(DEATH_WEAPONOF(deathtype), self.weapon);
+ WeaponStats_LogKill(awep, abot, self.weapon, vbot);
if(sv_gentle < 1) // TODO make a "gentle" version?
if(sound_allowed(MSG_BROADCAST, attacker))
{
if(deathtype == DEATH_DROWN)
- PlayerSound(playersound_drown, CHAN_PAIN, VOICETYPE_PLAYERSOUND);
+ PlayerSound(playersound_drown, CH_PAIN, VOICETYPE_PLAYERSOUND);
else
- PlayerSound(playersound_death, CHAN_PAIN, VOICETYPE_PLAYERSOUND);
+ PlayerSound(playersound_death, CH_PAIN, VOICETYPE_PLAYERSOUND);
}
// get rid of kill indicator
{
// become fully visible
self.alpha = 1;
- // clear selected player display
- ClearSelectedPlayer();
// throw a weapon
SpawnThrownWeapon (self.origin + (self.mins + self.maxs) * 0.5, self.switchweapon);
}
race_PreDie();
DropAllRunes(self);
+ // increment frag counter for used weapon type
+ float w;
+ w = DEATH_WEAPONOF(deathtype);
+ if(WEP_VALID(w))
+ if(self.classname == "player")
+ if(self != attacker)
+ attacker.accuracy.(accuracy_frags[w-1]) += 1;
+
if(deathtype == DEATH_HURTTRIGGER && g_freezetag)
{
PutClientInServer();
frag_inflictor = inflictor;
frag_target = self;
MUTATOR_CALLHOOK(PlayerDies);
+ weapon_action(self.weapon, WR_PLAYERDEATH);
if(self.flagcarried)
{
- if(attacker.classname != "player" && attacker.classname != "gib")
+ if(attacker.classname != "player")
DropFlag(self.flagcarried, self, attacker); // penalty for flag loss by suicide
else if(attacker.team == self.team)
DropFlag(self.flagcarried, attacker, attacker); // penalty for flag loss by suicide/teamkill
if(self.ballcarried && g_nexball)
DropBall(self.ballcarried, self.origin, self.velocity);
Portal_ClearAllLater(self);
- // clear waypoints
- WaypointSprite_PlayerDead();
if(clienttype(self) == CLIENTTYPE_REAL)
{
if(g_freezetag)
return;
+ // when we get here, player actually dies
+ // clear waypoints (do this AFTER FreezeTag)
+ WaypointSprite_PlayerDead();
+
// make the corpse upright (not tilted)
self.angles_x = 0;
self.angles_z = 0;
// set damage function to corpse damage
self.event_damage = PlayerCorpseDamage;
// call the corpse damage function just in case it wants to gib
- self.event_damage(inflictor, attacker, 0, deathtype, hitloc, force);
+ self.event_damage(inflictor, attacker, excess, deathtype, hitloc, force);
// set up to fade out later
SUB_SetFade (self, time + 6 + random (), 1);
if(sv_gentle > 0 || autocvar_ekg) {
// remove corpse
- PlayerCorpseDamage (inflictor, attacker, 100.0, deathtype, hitloc, force);
+ PlayerCorpseDamage (inflictor, attacker, autocvar_sv_gibhealth+1.0, deathtype, hitloc, force);
}
// reset fields the weapons may use just in case
}
}
-float UpdateSelectedPlayer_countvalue(float v)
-{
- return max(0, (v - 1.0) / 0.5);
-}
-
-// returns: -2 if no hit, otherwise cos of the angle
-// uses the global v_angle
-float UpdateSelectedPlayer_canSee(entity p, float mincosangle, float maxdist)
-{
- vector so, d;
- float c;
-
- if(p == self)
- return -2;
-
- if(p.deadflag)
- return -2;
-
- so = self.origin + self.view_ofs;
- d = p.origin - so;
-
- // misaimed?
- if(dist_point_line(d, '0 0 0', v_forward) > maxdist)
- return -2;
-
- // now find the cos of the angle...
- c = normalize(d) * v_forward;
-
- if(c <= mincosangle)
- return -2;
-
- // not visible in any way? forget it
- if(!checkpvs(so, p))
- return -2;
-
- traceline(so, p.origin, MOVE_NOMONSTERS, self);
- if(trace_fraction < 1)
- return -2;
-
- return c;
-}
-
-void ClearSelectedPlayer()
-{
- if(self.selected_player)
- {
- centerprint_expire(self, CENTERPRIO_POINT);
- self.selected_player = world;
- self.selected_player_display_needs_update = FALSE;
- }
-}
-
-void UpdateSelectedPlayer()
-{
- entity selected;
- float selected_score;
- selected = world;
- selected_score = 0.95; // 18 degrees
-
- if(!autocvar_sv_allow_shownames)
- return;
-
- if(clienttype(self) != CLIENTTYPE_REAL)
- return;
-
- if(self.cvar_cl_shownames == 0)
- return;
-
- if(self.cvar_cl_shownames == 1 && !teams_matter)
- return;
-
- makevectors(self.v_angle); // sets v_forward
-
- // 1. cursor trace is always right
- WarpZone_crosshair_trace(self);
- if(trace_ent && trace_ent.classname == "player" && !trace_ent.deadflag)
- {
- selected = trace_ent;
- }
- else
- {
- // 2. if we don't have a cursor trace, find the player which is least
- // mis-aimed at
- entity p;
- FOR_EACH_PLAYER(p)
- {
- float c;
- c = UpdateSelectedPlayer_canSee(p, selected_score, 100); // 100 = 2.5 meters
- if(c >= -1)
- {
- selected = p;
- selected_score = c;
- }
- }
- }
-
- if(selected)
- {
- self.selected_player_display_timeout = time + self.cvar_scr_centertime;
- }
- else
- {
- if(time < self.selected_player_display_timeout)
- if(UpdateSelectedPlayer_canSee(self.selected_player, 0.7, 200) >= -1) // 5 meters, 45 degrees
- selected = self.selected_player;
- }
-
- if(selected)
- {
- if(selected == self.selected_player)
- {
- float save;
- save = UpdateSelectedPlayer_countvalue(self.selected_player_count);
- self.selected_player_count = self.selected_player_count + frametime;
- if(save != UpdateSelectedPlayer_countvalue(self.selected_player_count))
- {
- string namestr, healthstr;
- namestr = playername(selected);
- if(teams_matter)
- {
- healthstr = ftos(floor(selected.health));
- if(self.team == selected.team)
- {
- namestr = strcat(namestr, " (", healthstr, "%)");
- self.selected_player_display_needs_update = TRUE;
- }
- }
- centerprint_atprio(self, CENTERPRIO_POINT, namestr);
- }
- }
- else
- {
- ClearSelectedPlayer();
- self.selected_player = selected;
- self.selected_player_time = time;
- self.selected_player_count = 0;
- self.selected_player_display_needs_update = FALSE;
- }
- }
- else
- {
- ClearSelectedPlayer();
- }
-
- if(self.selected_player)
- self.last_selected_player = self.selected_player;
-}
-
.float muted; // to be used by prvm_edictset server playernumber muted 1
float Say(entity source, float teamsay, entity privatesay, string msgin, float floodcontrol)
// message "": do not say, just test flood control
if(source.classname != "player")
colorstr = "^0"; // black for spectators
- else if(teams_matter)
+ else if(teamplay)
colorstr = Team_ColorCode(source.team);
else
teamsay = FALSE;
self.skinindex_for_playersound = self.skinindex;
ClearPlayerSounds();
LoadPlayerSounds("sound/player/default.sounds", 1);
- if(!LoadPlayerSounds(get_model_datafilename(self.model, self.skinindex, "sounds"), 0))
- LoadPlayerSounds(get_model_datafilename(self.model, 0, "sounds"), 0);
+ if(!autocvar_g_debug_defaultsounds)
+ if(!LoadPlayerSounds(get_model_datafilename(self.model, self.skinindex, "sounds"), 0))
+ LoadPlayerSounds(get_model_datafilename(self.model, 0, "sounds"), 0);
}
void FakeGlobalSound(string sample, float chan, float voicetype)
break;
case VOICETYPE_TEAMRADIO:
FOR_EACH_REALCLIENT(msg_entity)
- if(!teams_matter || msg_entity.team == self.team)
+ if(!teamplay || msg_entity.team == self.team)
{
if(msg_entity.cvar_cl_voice_directional == 1)
soundto(MSG_ONE, self, chan, sample, VOL_BASEVOICE, ATTN_MIN);
flood = Say(self, ownteam, world, msg, 1);
if (flood > 0)
- GlobalSound(self.sample, CHAN_VOICE, voicetype);
+ GlobalSound(self.sample, CH_VOICE, voicetype);
else if (flood < 0)
- FakeGlobalSound(self.sample, CHAN_VOICE, voicetype);
+ FakeGlobalSound(self.sample, CH_VOICE, voicetype);
}
void MoveToTeam(entity client, float team_colour, float type, float show_message)