#include "race.qh"
-#include <server/defs.qh>
-#include <server/miscfunctions.qh>
-#include "client.qh"
-#include "portals.qh"
-#include "scores.qh"
-#include "spawnpoints.qh"
-#include "bot/api.qh"
-#include "command/getreplies.qh"
-#include "../common/deathtypes/all.qh"
-#include "../common/notifications/all.qh"
-#include "../common/mapinfo.qh"
+#include <common/deathtypes/all.qh>
+#include <common/gamemodes/_mod.qh>
+#include <common/gamemodes/rules.qh>
+#include <common/mapobjects/subs.qh>
+#include <common/mapobjects/triggers.qh>
+#include <common/mutators/mutator/waypoints/waypointsprites.qh>
#include <common/net_linked.qh>
+#include <common/notifications/all.qh>
#include <common/state.qh>
-#include "../common/triggers/subs.qh"
-#include "../lib/warpzone/util_server.qh"
-#include "../lib/warpzone/common.qh"
-#include "../common/mutators/mutator/waypoints/waypointsprites.qh"
+#include <common/stats.qh>
+#include <common/vehicles/sv_vehicles.qh>
+#include <common/weapons/_all.qh>
+#include <common/weapons/weapon/porto.qh>
+#include <lib/warpzone/common.qh>
+#include <lib/warpzone/util_server.qh>
+#include <server/bot/api.qh>
+#include <server/cheats.qh>
+#include <server/client.qh>
+#include <server/command/getreplies.qh>
+#include <server/damage.qh>
+#include <server/gamelog.qh>
+#include <server/intermission.qh>
+#include <server/main.qh>
+#include <server/mutators/_mod.qh>
+#include <server/portals.qh>
+#include <server/scores.qh>
+#include <server/spawnpoints.qh>
+#include <server/weapons/common.qh>
+#include <server/world.qh>
+
+.string stored_netname; // TODO: store this information independently of race-based gamemodes
+
+string uid2name(string myuid)
+{
+ string s = db_get(ServerProgsDB, strcat("/uid2name/", myuid));
+
+ // FIXME remove this later after 0.6 release
+ // convert old style broken records to correct style
+ if(s == "")
+ {
+ s = db_get(ServerProgsDB, strcat("uid2name", myuid));
+ if(s != "")
+ {
+ db_put(ServerProgsDB, strcat("/uid2name/", myuid), s);
+ db_remove(ServerProgsDB, strcat("uid2name", myuid));
+ }
+ }
+
+ if(s == "")
+ s = "^1Unregistered Player";
+ return s;
+}
+
+void write_recordmarker(entity pl, float tstart, float dt)
+{
+ GameLogEcho(strcat(":recordset:", ftos(pl.playerid), ":", ftos(dt)));
+
+ // also write a marker into demo files for demotc-race-record-extractor to find
+ stuffcmd(pl,
+ strcat(
+ strcat("//", strconv(2, 0, 0, GetGametype()), " RECORD SET ", TIME_ENCODED_TOSTRING(TIME_ENCODE(dt))),
+ " ", ftos(tstart), " ", ftos(dt), "\n"));
+}
IntrusiveList g_race_targets;
-STATIC_INIT(g_race_targets) { g_race_targets = IL_NEW(); }
+IntrusiveList g_racecheckpoints;
void race_InitSpectator()
{
race_SendNextCheckpoint(msg_entity.enemy, 1);
}
-void W_Porto_Fail(entity this, float failhard);
-
float race_readTime(string map, float pos)
{
- string rr = ((g_cts) ? CTS_RECORD : ((g_ctf) ? CTF_RECORD : RACE_RECORD));
-
- return stof(db_get(ServerProgsDB, strcat(map, rr, "time", ftos(pos))));
+ return stof(db_get(ServerProgsDB, strcat(map, record_type, "time", ftos(pos))));
}
string race_readUID(string map, float pos)
{
- string rr = ((g_cts) ? CTS_RECORD : ((g_ctf) ? CTF_RECORD : RACE_RECORD));
-
- return db_get(ServerProgsDB, strcat(map, rr, "crypto_idfp", ftos(pos)));
+ return db_get(ServerProgsDB, strcat(map, record_type, "crypto_idfp", ftos(pos)));
}
float race_readPos(string map, float t)
void race_writeTime(string map, float t, string myuid)
{
- string rr = ((g_cts) ? CTS_RECORD : ((g_ctf) ? CTF_RECORD : RACE_RECORD));
-
float newpos;
newpos = race_readPos(map, t);
// player improved his existing record, only have to iterate on ranks between new and old recs
for (i = prevpos; i > newpos; --i)
{
- db_put(ServerProgsDB, strcat(map, rr, "time", ftos(i)), ftos(race_readTime(map, i - 1)));
- db_put(ServerProgsDB, strcat(map, rr, "crypto_idfp", ftos(i)), race_readUID(map, i - 1));
+ db_put(ServerProgsDB, strcat(map, record_type, "time", ftos(i)), ftos(race_readTime(map, i - 1)));
+ db_put(ServerProgsDB, strcat(map, record_type, "crypto_idfp", ftos(i)), race_readUID(map, i - 1));
}
}
else
// player has no ranked record yet
for (i = RANKINGS_CNT; i > newpos; --i)
{
- db_put(ServerProgsDB, strcat(map, rr, "time", ftos(i)), ftos(race_readTime(map, i - 1)));
- db_put(ServerProgsDB, strcat(map, rr, "crypto_idfp", ftos(i)), race_readUID(map, i - 1));
+ float other_time = race_readTime(map, i - 1);
+ if (other_time) {
+ db_put(ServerProgsDB, strcat(map, record_type, "time", ftos(i)), ftos(other_time));
+ db_put(ServerProgsDB, strcat(map, record_type, "crypto_idfp", ftos(i)), race_readUID(map, i - 1));
+ }
}
}
// store new time itself
- db_put(ServerProgsDB, strcat(map, rr, "time", ftos(newpos)), ftos(t));
- db_put(ServerProgsDB, strcat(map, rr, "crypto_idfp", ftos(newpos)), myuid);
+ db_put(ServerProgsDB, strcat(map, record_type, "time", ftos(newpos)), ftos(t));
+ db_put(ServerProgsDB, strcat(map, record_type, "crypto_idfp", ftos(newpos)), myuid);
}
string race_readName(string map, float pos)
{
- string rr = ((g_cts) ? CTS_RECORD : ((g_ctf) ? CTF_RECORD : RACE_RECORD));
+ return uid2name(db_get(ServerProgsDB, strcat(map, record_type, "crypto_idfp", ftos(pos))));
+}
- return uid2name(db_get(ServerProgsDB, strcat(map, rr, "crypto_idfp", ftos(pos))));
+void race_checkAndWriteName(entity player)
+{
+ if(CS_CVAR(player).cvar_cl_allow_uidtracking == 1 && CS_CVAR(player).cvar_cl_allow_uid2name == 1)
+ {
+ if (!player.stored_netname)
+ player.stored_netname = strzone(uid2name(player.crypto_idfp));
+ if(player.stored_netname != player.netname)
+ {
+ db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
+ strcpy(player.stored_netname, player.netname);
+ }
+ }
}
const float MAX_CHECKPOINTS = 255;
-spawnfunc(target_checkpoint);
-
.float race_penalty;
.float race_penalty_accumulator;
.string race_penalty_reason;
WriteInt24_t(msg, race_readTime(GetMapname(), 1));
}
-
void race_send_speedaward(float msg)
{
// send the best speed of the round
WriteString(msg, speedaward_alltimebest_holder);
}
-void race_SendRankings(float pos, float prevpos, float del, float msg)
+void race_send_rankings_cnt(float msg)
+{
+ WriteHeader(msg, TE_CSQC_RACE);
+ WriteByte(msg, RACE_NET_RANKINGS_CNT);
+ int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt);
+ WriteByte(msg, m);
+}
+
+void race_SendRanking(float pos, float prevpos, float del, float msg)
{
WriteHeader(msg, TE_CSQC_RACE);
WriteByte(msg, RACE_NET_SERVER_RANKINGS);
WriteInt24_t(msg, race_readTime(GetMapname(), pos));
}
+void race_SpeedAwardFrame(entity player)
+{
+ if (IS_OBSERVER(player))
+ return;
+
+ if(vdist(player.velocity - player.velocity_z * '0 0 1', >, speedaward_speed))
+ {
+ speedaward_speed = vlen(player.velocity - player.velocity_z * '0 0 1');
+ speedaward_holder = player.netname;
+ speedaward_uid = player.crypto_idfp;
+ speedaward_lastupdate = time;
+ }
+ if (speedaward_speed > speedaward_lastsent && (time - speedaward_lastupdate > 1 || intermission_running))
+ {
+ race_send_speedaward(MSG_ALL);
+ speedaward_lastsent = speedaward_speed;
+ if (speedaward_speed > speedaward_alltimebest && speedaward_uid != "")
+ {
+ speedaward_alltimebest = speedaward_speed;
+ speedaward_alltimebest_holder = speedaward_holder;
+ speedaward_alltimebest_uid = speedaward_uid;
+ db_put(ServerProgsDB, strcat(GetMapname(), record_type, "speed/speed"), ftos(speedaward_alltimebest));
+ db_put(ServerProgsDB, strcat(GetMapname(), record_type, "speed/crypto_idfp"), speedaward_alltimebest_uid);
+ race_send_speedaward_alltimebest(MSG_ALL);
+ }
+ }
+}
+
+void race_SendAll(entity player, bool only_rankings)
+{
+ if(!IS_REAL_CLIENT(player))
+ return;
+
+ msg_entity = player;
+ if (!only_rankings)
+ {
+ race_send_recordtime(MSG_ONE);
+ race_send_speedaward(MSG_ONE);
+
+ speedaward_alltimebest = stof(db_get(ServerProgsDB, strcat(GetMapname(), record_type, "speed/speed")));
+ speedaward_alltimebest_holder = uid2name(db_get(ServerProgsDB, strcat(GetMapname(), record_type, "speed/crypto_idfp")));
+ race_send_speedaward_alltimebest(MSG_ONE);
+ }
+
+ int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt);
+ race_send_rankings_cnt(MSG_ONE);
+ for (int i = 1; i <= m; ++i)
+ race_SendRanking(i, 0, 0, MSG_ONE);
+}
+
void race_SendStatus(float id, entity e)
{
if(!IS_REAL_CLIENT(e))
race_send_recordtime(MSG_ALL);
}
- race_SendRankings(newpos, player_prevpos, 0, MSG_ALL);
- if(rankings_reply)
- strunzone(rankings_reply);
- rankings_reply = strzone(getrankings());
+ race_SendRanking(newpos, player_prevpos, 0, MSG_ALL);
+ strcpy(rankings_reply, getrankings());
if(newpos == player_prevpos)
{
void race_deleteTime(string map, float pos)
{
- string rr = ((g_cts) ? CTS_RECORD : ((g_ctf) ? CTF_RECORD : RACE_RECORD));
-
for(int i = pos; i <= RANKINGS_CNT; ++i)
{
string therank = ftos(i);
if (i == RANKINGS_CNT)
{
- db_remove(ServerProgsDB, strcat(map, rr, "time", therank));
- db_remove(ServerProgsDB, strcat(map, rr, "crypto_idfp", therank));
+ db_remove(ServerProgsDB, strcat(map, record_type, "time", therank));
+ db_remove(ServerProgsDB, strcat(map, record_type, "crypto_idfp", therank));
}
else
{
- db_put(ServerProgsDB, strcat(map, rr, "time", therank), ftos(race_readTime(GetMapname(), i+1)));
- db_put(ServerProgsDB, strcat(map, rr, "crypto_idfp", therank), race_readUID(GetMapname(), i+1));
+ db_put(ServerProgsDB, strcat(map, record_type, "time", therank), ftos(race_readTime(GetMapname(), i+1)));
+ db_put(ServerProgsDB, strcat(map, record_type, "crypto_idfp", therank), race_readUID(GetMapname(), i+1));
}
}
- race_SendRankings(pos, 0, 1, MSG_ALL);
+ race_SendRanking(pos, 0, 1, MSG_ALL);
if(pos == 1)
race_send_recordtime(MSG_ALL);
- if(rankings_reply)
- strunzone(rankings_reply);
- rankings_reply = strzone(getrankings());
+ strcpy(rankings_reply, getrankings());
}
void race_SendTime(entity e, float cp, float t, float tvalid)
float s;
if(g_race_qualifying)
{
- s = PlayerScore_Add(e, SP_RACE_FASTEST, 0);
+ s = GameRules_scoring_add(e, RACE_FASTEST, 0);
if(!s || t < s)
- PlayerScore_Add(e, SP_RACE_FASTEST, t - s);
+ GameRules_scoring_add(e, RACE_FASTEST, t - s);
}
else
{
- s = PlayerScore_Add(e, SP_RACE_FASTEST, 0);
+ s = GameRules_scoring_add(e, RACE_FASTEST, 0);
if(!s || t < s)
- PlayerScore_Add(e, SP_RACE_FASTEST, t - s);
+ GameRules_scoring_add(e, RACE_FASTEST, t - s);
- s = PlayerScore_Add(e, SP_RACE_TIME, 0);
+ s = GameRules_scoring_add(e, RACE_TIME, 0);
snew = TIME_ENCODE(time - game_starttime);
- PlayerScore_Add(e, SP_RACE_TIME, snew - s);
- l = PlayerTeamScore_Add(e, SP_RACE_LAPS, ST_RACE_LAPS, 1);
+ GameRules_scoring_add(e, RACE_TIME, snew - s);
+ l = GameRules_scoring_add_team(e, RACE_LAPS, 1);
if(autocvar_fraglimit)
if(l >= autocvar_fraglimit)
if(t < recordtime || recordtime == 0)
{
race_checkpoint_records[cp] = t;
- if(race_checkpoint_recordholders[cp])
- strunzone(race_checkpoint_recordholders[cp]);
- race_checkpoint_recordholders[cp] = strzone(e.netname);
+ strcpy(race_checkpoint_recordholders[cp], e.netname);
if(g_race_qualifying)
FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it) && it.race_checkpoint == cp, { race_SendNextCheckpoint(it, 0); });
}
entity oth = race_checkpoint_lastplayers[cp];
if(oth)
{
- mylaps = PlayerScore_Add(e, SP_RACE_LAPS, 0);
+ mylaps = GameRules_scoring_add(e, RACE_LAPS, 0);
lother = race_checkpoint_lastlaps[cp];
othtime = race_checkpoint_lasttimes[cp];
}
void checkpoint_passed(entity this, entity player)
{
+ if(IS_VEHICLE(player) && player.owner)
+ player = player.owner;
+
if(player.personal && autocvar_g_allow_checkpoints)
return; // practice mode!
else
{
if(this.spawnflags & 4)
- Damage (player, this, this, 10000, DEATH_HURTTRIGGER.m_id, player.origin, '0 0 0');
+ Damage (player, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, player.origin, '0 0 0');
}
}
// race only (middle of the race)
g_race_qualifying = false;
pl_race_place = 0;
- if (!Spawn_FilterOutBadSpots(this, findchain(classname, "info_player_deathmatch"), 0, false)) {
+ if (!Spawn_FilterOutBadSpots(this, findchain(classname, "info_player_deathmatch"), 0, false, true)) {
error(strcat("Checkpoint ", ftos(i), " misses a spawnpoint with race_place==", ftos(pl_race_place), " (used for respawning in race) - bailing out"));
}
// qualifying only
g_race_qualifying = 1;
pl_race_place = race_lowest_place_spawn;
- if (!Spawn_FilterOutBadSpots(this, findchain(classname, "info_player_deathmatch"), 0, false)) {
+ if (!Spawn_FilterOutBadSpots(this, findchain(classname, "info_player_deathmatch"), 0, false, true)) {
error(strcat("Checkpoint ", ftos(i), " misses a spawnpoint with race_place==", ftos(pl_race_place), " (used for qualifying) - bailing out"));
}
g_race_qualifying = 0;
for (int p = 1; p <= race_highest_place_spawn; ++p) {
pl_race_place = p;
- if (!Spawn_FilterOutBadSpots(this, findchain(classname, "info_player_deathmatch"), 0, false)) {
+ if (!Spawn_FilterOutBadSpots(this, findchain(classname, "info_player_deathmatch"), 0, false, true)) {
error(strcat("Checkpoint ", ftos(i), " misses a spawnpoint with race_place==", ftos(pl_race_place), " (used for initially spawning in race) - bailing out"));
}
}
pl_race_checkpoint = race_NextCheckpoint(0);
g_race_qualifying = 1;
pl_race_place = race_lowest_place_spawn;
- if (!Spawn_FilterOutBadSpots(this, findchain(classname, "info_player_deathmatch"), 0, false)) {
+ if (!Spawn_FilterOutBadSpots(this, findchain(classname, "info_player_deathmatch"), 0, false, true)) {
error(strcat("Checkpoint 0 misses a spawnpoint with race_place==", ftos(pl_race_place), " (used for qualifying) - bailing out"));
}
} else {
this.sprite.waypointsprite_visible_for_player = race_waypointsprite_visible_for_player;
this.spawn_evalfunc = trigger_race_checkpoint_spawn_evalfunc;
+ if (!g_racecheckpoints)
+ g_racecheckpoints = IL_NEW();
IL_PUSH(g_racecheckpoints, this);
+ // trigger_race_checkpoint_verify checks this list too
+ if (!g_race_targets)
+ g_race_targets = IL_NEW();
+
InitializeEntity(this, trigger_race_checkpoint_verify, INITPRIO_FINDTARGET);
}
-spawnfunc(target_checkpoint) // defrag entity
+void target_checkpoint_setup(entity this)
{
if(!g_race && !g_cts) { delete(this); return; }
defrag_ents = 1;
// if this is targeted, then it probably isn't a trigger
- bool is_trigger = !boolean(!this.nottargeted && this.targetname != "");
+ bool is_trigger = this.targetname == "";
if(is_trigger)
EXACTTRIGGER_INIT;
race_timed_checkpoint = 1;
+ if (!g_race_targets)
+ g_race_targets = IL_NEW();
IL_PUSH(g_race_targets, this);
+ // trigger_race_checkpoint_verify checks this list too
+ if (!g_racecheckpoints)
+ g_racecheckpoints = IL_NEW();
+
InitializeEntity(this, trigger_race_checkpoint_verify, INITPRIO_FINDTARGET);
}
-spawnfunc(target_startTimer) { spawnfunc_target_checkpoint(this); }
-spawnfunc(target_stopTimer) { spawnfunc_target_checkpoint(this); }
+spawnfunc(target_checkpoint)
+{
+ // xonotic defrag entity
+ target_checkpoint_setup(this);
+}
+
+// compatibility entity names
+spawnfunc(target_startTimer) { target_checkpoint_setup(this); }
+spawnfunc(target_stopTimer) { target_checkpoint_setup(this); }
void race_AbandonRaceCheck(entity p)
{
for(int j = 0; j < MAX_CHECKPOINTS; ++j)
{
race_checkpoint_records[j] = 0;
- if(race_checkpoint_recordholders[j])
- strunzone(race_checkpoint_recordholders[j]);
- race_checkpoint_recordholders[j] = string_null;
+ strfree(race_checkpoint_recordholders[j]);
}
FOREACH_CLIENT(true, {
// links on CP entities)
float l;
- l = PlayerScore_Add(e, SP_RACE_LAPS, 0);
+ l = GameRules_scoring_add(e, RACE_LAPS, 0);
if(CS(e).race_completed)
return l; // not fractional