+.entity accuracy;
+.float accuracy_frags[WEP_MAXCOUNT];
+
+float weaponstats_buffer;
+
+void WeaponStats_Init()
+{
+ if(autocvar_sv_weaponstats_file != "")
+ weaponstats_buffer = buf_create();
+ else
+ weaponstats_buffer = -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_ready(entity fh, entity pass, float status)
+{
+ float i, j, n, ibot, jbot, idx;
+ vector v;
+ string prefix, s;
+ switch(status)
+ {
+ case URL_READY_CANWRITE:
+ // we can write
+ prefix = strcat(autocvar_hostname, "\t", GetGametype(), "_", GetMapname(), "\t");
+ url_fputs(fh, "#begin statsfile\n");
+ url_fputs(fh, strcat("#date ", strftime(TRUE, "%a %b %e %H:%M:%S %Z %Y"), "\n"));
+#ifdef WATERMARK
+ url_fputs(fh, strcat("#version ", WATERMARK, "\n"));
+#endif
+ url_fputs(fh, strcat("#config ", ftos(crc16(FALSE, cvar_purechanges)), "\n"));
+ url_fputs(fh, strcat("#cvar_purechanges ", ftos(cvar_purechanges_count), "\n"));
+ n = tokenizebyseparator(cvar_purechanges, "\n");
+ for(i = 0; i < n; ++i)
+ url_fputs(fh, strcat("#cvar_purechange ", argv(i), "\n"));
+ 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, ibot, j, jbot);
+ v = stov(bufstr_get(weaponstats_buffer, idx));
+ if(v != '0 0 0')
+ {
+ //vector is: kills hits damage
+ url_fputs(fh, sprintf("%s%d %d\t%d %d\t", prefix, i, ibot, j, jbot));
+ url_fputs(fh, sprintf("%d %d %g\n", v_x, v_y, v_z));
+ }
+ }
+ url_fputs(fh, "#end\n\n");
+ url_fclose(fh);
+ break;
+ case URL_READY_CANREAD:
+ // url_fclose is processing, we got a response for writing the data
+ // this must come from HTTP
+ print("Got response from weapon stats server:\n");
+ while((s = url_fgets(fh)))
+ print(" ", s, "\n");
+ print("End of response.\n");
+ url_fclose(fh);
+ break;
+ case URL_READY_CLOSED:
+ // url_fclose has finished
+ print("Weapon stats written\n");
+ buf_del(weaponstats_buffer);
+ weaponstats_buffer = -1;
+ break;
+ case URL_READY_ERROR:
+ default:
+ print("Weapon stats writing failed: ", ftos(status), "\n");
+ buf_del(weaponstats_buffer);
+ weaponstats_buffer = -1;
+ break;
+ }
+}
+
+void WeaponStats_Shutdown()
+{
+ if(weaponstats_buffer < 0)
+ return;
+ if(autocvar_sv_weaponstats_file != "")
+ {
+ url_multi_fopen(autocvar_sv_weaponstats_file, FILE_APPEND, WeaponStats_ready, world);
+ }
+ else
+ {
+ buf_del(weaponstats_buffer);
+ weaponstats_buffer = -1;
+ }
+}
+
+void WeaponStats_LogItem(float awep, float abot, float vwep, float vbot, vector item)
+{
+ float idx;
+ if(weaponstats_buffer < 0)
+ return;
+ if(awep < WEP_FIRST || vwep < WEP_FIRST)
+ return;
+ if(awep > WEP_LAST || vwep > WEP_LAST)
+ return;
+ 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 abot, float vwep, float vbot, float damage)
+{
+ if(damage < 0)
+ error("negative damage?");
+ WeaponStats_LogItem(awep, abot, vwep, vbot, '0 0 1' * damage + '0 1 0');
+}
+void WeaponStats_LogKill(float awep, float abot, float vwep, float vbot)
+{
+ WeaponStats_LogItem(awep, abot, vwep, vbot, '1 0 0');
+}
+
+// changes by LordHavoc on 03/29/04 and 03/30/04 at Vermeulen's request
+// merged player_run and player_stand to player_anim
+// added death animations to player_anim
+// can now spawn thrown weapons from anywhere, not just from players
+// thrown weapons now fade out after 20 seconds
+// created PlayerGib function
+// PlayerDie no longer uses hitloc or damage
+// PlayerDie now supports dying animations as well as gibbing
+// cleaned up PlayerDie a lot
+// added CopyBody
+
.entity pusher;
.float pushltime;
.float istypefrag;
else
deadbits = ANIMSTATE_DEAD2;
float animbits = deadbits;
- if(self.freezetag_frozen)
+ if(self.frozen)
animbits |= ANIMSTATE_FROZEN;
if(self.crouch)
animbits |= ANIMSTATE_DUCK;
}
}
+// g_<gametype>_str:
+// If 0, default is used.
+// If <0, 0 is used.
+// Otherwise, g_str (default value) is used.
+// For consistency, negative values there are mapped to zero too.
+#define GAMETYPE_DEFAULTED_SETTING(str) \
+ ((gametype_setting_tmp = cvar(strcat("g_", GetGametype(), "_" #str))), \
+ (gametype_setting_tmp < 0) ? 0 : \
+ (gametype_setting_tmp == 0) ? max(0, autocvar_g_##str) : \
+ gametype_setting_tmp)
+
+
+void calculate_player_respawn_time()
+{
+ float gametype_setting_tmp;
+ float sdelay_max = GAMETYPE_DEFAULTED_SETTING(respawn_delay_max);
+ float sdelay_small = GAMETYPE_DEFAULTED_SETTING(respawn_delay_small);
+ float sdelay_large = GAMETYPE_DEFAULTED_SETTING(respawn_delay_large);
+ float sdelay_small_count = GAMETYPE_DEFAULTED_SETTING(respawn_delay_small_count);
+ float sdelay_large_count = GAMETYPE_DEFAULTED_SETTING(respawn_delay_large_count);
+ float waves = GAMETYPE_DEFAULTED_SETTING(respawn_waves);
+
+ float pcount = 1; // Include myself whether or not team is already set right and I'm a "player".
+ entity pl;
+ if (teamplay)
+ {
+ FOR_EACH_PLAYER(pl)
+ if (pl != self)
+ if (pl.team == self.team)
+ ++pcount;
+ if (sdelay_small_count == 0)
+ sdelay_small_count = 1;
+ if (sdelay_large_count == 0)
+ sdelay_large_count = 1;
+ }
+ else
+ {
+ FOR_EACH_PLAYER(pl)
+ if (pl != self)
+ ++pcount;
+ if (sdelay_small_count == 0)
+ {
+ if (g_cts)
+ {
+ // Players play independently. No point in requiring enemies.
+ sdelay_small_count = 1;
+ }
+ else
+ {
+ // Players play AGAINST each other. Enemies required.
+ sdelay_small_count = 2;
+ }
+ }
+ if (sdelay_large_count == 0)
+ {
+ if (g_cts)
+ {
+ // Players play independently. No point in requiring enemies.
+ sdelay_large_count = 1;
+ }
+ else
+ {
+ // Players play AGAINST each other. Enemies required.
+ sdelay_large_count = 2;
+ }
+ }
+ }
+
+ float sdelay;
+
+ if (pcount <= sdelay_small_count)
+ sdelay = sdelay_small;
+ else if (pcount >= sdelay_large_count)
+ sdelay = sdelay_large;
+ else // NOTE: this case implies sdelay_large_count > sdelay_small_count.
+ sdelay = sdelay_small + (sdelay_large - sdelay_small) * (pcount - sdelay_small_count) / (sdelay_large_count - sdelay_small_count);
+
+ if(waves)
+ self.respawn_time = ceil((time + sdelay) / waves) * waves;
+ else
+ self.respawn_time = time + sdelay;
+
+ if(sdelay < sdelay_max)
+ self.respawn_time_max = time + sdelay_max;
+ else
+ self.respawn_time_max = self.respawn_time;
+
+ if((sdelay + waves >= 5.0) && (self.respawn_time - time > 1.75))
+ self.respawn_countdown = 10; // first number to count down from is 10
+ else
+ self.respawn_countdown = -1; // do not count down
+
+ if(autocvar_g_forced_respawn)
+ self.respawn_flags = self.respawn_flags | RESPAWN_FORCE;
+}
+
void ClientKill_Now_TeamChange();
void PlayerDamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
{
- float take, save, waves, sdelay, dh, da, j;
+ float take, save, dh, da, j;
vector v;
float valid_damage_for_weaponstats;
float excess;
}
if(sound_allowed(MSG_BROADCAST, attacker))
- if(!DEATH_ISWEAPON(deathtype, WEP_BLASTER) || attacker != self || self.health < 2 * WEP_CVAR_PRI(blaster, damage) * autocvar_g_balance_selfdamagepercent + 1) // WEAPONTODO: create separate limit for pain notification with laser
+ if(!DEATH_ISWEAPON(deathtype, WEP_LASER) || attacker != self || self.health < 2 * autocvar_g_balance_laser_primary_damage * autocvar_g_balance_selfdamagepercent + 1)
if(self.health > 1)
// exclude pain sounds for laserjumps as long as you aren't REALLY low on health and would die of the next two
{
// print an obituary message
Obituary (attacker, inflictor, self, deathtype);
- race_PreDie();
// increment frag counter for used weapon type
float w;
frag_deathtype = deathtype;
MUTATOR_CALLHOOK(PlayerDies);
- WEP_ACTION(self.weapon, WR_PLAYERDEATH);
+ weapon_action(self.weapon, WR_PLAYERDEATH);
RemoveGrapplingHook(self);
Portal_ClearAllLater(self);
- if(IS_REAL_CLIENT(self))
- {
- self.fixangle = TRUE;
- //msg_entity = self;
- //WriteByte (MSG_ONE, SVC_SETANGLE);
- //WriteAngle (MSG_ONE, self.v_angle_x);
- //WriteAngle (MSG_ONE, self.v_angle_y);
- //WriteAngle (MSG_ONE, 80);
- }
+ self.fixangle = TRUE;
if(defer_ClientKill_Now_TeamChange)
ClientKill_Now_TeamChange(); // can turn player into spectator
// when we get here, player actually dies
+ Unfreeze(self); // remove any icy remains
+ self.health = 0; // Unfreeze resets health, so we need to set it back
+
// clear waypoints
WaypointSprite_PlayerDead();
// throw a weapon
// dying animation
self.deadflag = DEAD_DYING;
// when to allow respawn
- sdelay = 0;
- waves = 0;
- sdelay = cvar(strcat("g_", GetGametype(), "_respawn_delay"));
- if(!sdelay)
- {
- if(g_cts)
- sdelay = 0; // no respawn delay in CTS
- else
- sdelay = autocvar_g_respawn_delay;
- }
- waves = cvar(strcat("g_", GetGametype(), "_respawn_waves"));
- if(!waves)
- waves = autocvar_g_respawn_waves;
- if(waves)
- self.respawn_time = ceil((time + sdelay) / waves) * waves;
- else
- self.respawn_time = time + sdelay;
- if(autocvar_g_respawn_delay_max > sdelay)
- self.respawn_time_max = time + autocvar_g_respawn_delay_max;
- else
- self.respawn_time_max = self.respawn_time;
- if((sdelay + waves >= 5.0) && (self.respawn_time - time > 1.75))
- self.respawn_countdown = 10; // first number to count down from is 10
- else
- self.respawn_countdown = -1; // do not count down
-
- if(g_cts || autocvar_g_forced_respawn)
- self.respawn_flags = self.respawn_flags | RESPAWN_FORCE;
+ calculate_player_respawn_time();
self.death_time = time;
if (random() < 0.5)
// reset fields the weapons may use just in case
for (j = WEP_FIRST; j <= WEP_LAST; ++j)
{
- WEP_ACTION(j, WR_RESETPLAYER);
+ weapon_action(j, WR_RESETPLAYER);
ATTACK_FINISHED_FOR(self, j) = 0;
}
}
// 0 = reject
// -1 = fake accept
{
- string msgstr, colorstr, cmsgstr, namestr, fullmsgstr, sourcemsgstr, fullcmsgstr, sourcecmsgstr;
+ string msgstr, colorstr, cmsgstr, namestr, fullmsgstr, sourcemsgstr, fullcmsgstr, sourcecmsgstr, colorprefix;
float flood;
var .float flood_field;
entity head;
else
namestr = source.netname;
+ if(strdecolorize(namestr) == namestr)
+ colorprefix = "^3";
+ else
+ colorprefix = "^7";
+
if(msgin != "")
{
if(privatesay)
{
- msgstr = strcat("\{1}\{13}* ^3", namestr, "^3 tells you: ^7");
+ msgstr = strcat("\{1}\{13}* ", colorprefix, namestr, "^3 tells you: ^7");
privatemsgprefixlen = strlen(msgstr);
msgstr = strcat(msgstr, msgin);
- cmsgstr = strcat(colorstr, "^3", namestr, "^3 tells you:\n^7", msgin);
+ cmsgstr = strcat(colorstr, colorprefix, namestr, "^3 tells you:\n^7", msgin);
if(autocvar_g_chat_teamcolors)
privatemsgprefix = strcat("\{1}\{13}* ^3You tell ", playername(privatesay), ": ^7");
else
}
else if(teamsay)
{
- msgstr = strcat("\{1}\{13}", colorstr, "(^3", namestr, colorstr, ") ^7", msgin);
- cmsgstr = strcat(colorstr, "(^3", namestr, colorstr, ")\n^7", msgin);
+ msgstr = strcat("\{1}\{13}", colorstr, "(", colorprefix, namestr, colorstr, ") ^7", msgin);
+ cmsgstr = strcat(colorstr, "(", colorprefix, namestr, colorstr, ")\n^7", msgin);
}
else
{
- msgstr = strcat("\{1}", namestr, "^7: ", msgin);
+ msgstr = strcat("\{1}", colorprefix, namestr, "^7: ", msgin);
cmsgstr = "";
}
msgstr = strcat(strreplace("\n", " ", msgstr), "\n"); // newlines only are good for centerprint