Add score recording to SuperTuxKart
This commit is contained in:
parent
9dc5e6e3e6
commit
a4bc499cbc
|
@ -0,0 +1,287 @@
|
|||
diff --git a/src/network/protocols/server_lobby.cpp b/src/network/protocols/server_lobby.cpp
|
||||
index 3516ffe03..aa3fd698b 100644
|
||||
--- a/src/network/protocols/server_lobby.cpp
|
||||
+++ b/src/network/protocols/server_lobby.cpp
|
||||
@@ -249,6 +249,7 @@ ServerLobby::ServerLobby() : LobbyProtocol()
|
||||
m_game_mode.store(ServerConfig::m_server_mode);
|
||||
m_default_vote = new PeerVote();
|
||||
m_player_reports_table_exists = false;
|
||||
+ m_grand_prix_rowid = -1;
|
||||
initDatabase();
|
||||
} // ServerLobby
|
||||
|
||||
@@ -389,6 +390,51 @@ void ServerLobby::initServerStatsTable()
|
||||
") WITHOUT ROWID;", country_table_name.c_str());
|
||||
easySQLQuery(query);
|
||||
|
||||
+ // Extra default table _grandprixresults:
|
||||
+ std::string grandprixresults_table_name = std::string("v") +
|
||||
+ StringUtils::toString(ServerConfig::m_server_db_version) + "_" +
|
||||
+ ServerConfig::m_server_uid + "_grandprixresults";
|
||||
+ query = StringUtils::insertValues(
|
||||
+ "CREATE TABLE IF NOT EXISTS %s (\n"
|
||||
+ " num_races INTEGER NOT NULL, -- Number of races completed\n"
|
||||
+ " total_races INTEGER NOT NULL -- Total number of races\n"
|
||||
+ ");", grandprixresults_table_name.c_str());
|
||||
+ if(easySQLQuery(query)) {
|
||||
+ m_server_grandprixresults_table = grandprixresults_table_name;
|
||||
+ }
|
||||
+
|
||||
+ // Extra default table _raceresults:
|
||||
+ std::string raceresults_table_name = std::string("v") +
|
||||
+ StringUtils::toString(ServerConfig::m_server_db_version) + "_" +
|
||||
+ ServerConfig::m_server_uid + "_raceresults";
|
||||
+ query = StringUtils::insertValues(
|
||||
+ "CREATE TABLE IF NOT EXISTS %s (\n"
|
||||
+ " race_finished TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, -- Time when race was completed\n"
|
||||
+ " grandprix_rowid INTEGER, -- ROWID of the grand prix this race is part of (optional)\n"
|
||||
+ " track_name TEXT NOT NULL, -- Number of laps completed\n"
|
||||
+ " num_laps INTEGER NOT NULL, -- Number of laps completed\n"
|
||||
+ " total_laps INTEGER NOT NULL, -- Total number of laps\n"
|
||||
+ " fastest_lap_time FLOAT NOT NULL, -- Fastest lap so far\n"
|
||||
+ " fastest_lap_player TEXT NOT NULL -- Fastest lap so far\n"
|
||||
+ ");", raceresults_table_name.c_str());
|
||||
+ if(easySQLQuery(query)) {
|
||||
+ m_server_raceresults_table = raceresults_table_name;
|
||||
+ }
|
||||
+
|
||||
+ // Extra default table _raceresults:
|
||||
+ std::string playerresults_table_name = std::string("v") +
|
||||
+ StringUtils::toString(ServerConfig::m_server_db_version) + "_" +
|
||||
+ ServerConfig::m_server_uid + "_playerresults";
|
||||
+ query = StringUtils::insertValues(
|
||||
+ "CREATE TABLE IF NOT EXISTS %s (\n"
|
||||
+ " race_rowid INTEGER NOT NULL, -- ROWID of the race\n"
|
||||
+ " elapsed_time FLOAT NOT NULL, -- Total race time\n"
|
||||
+ " player_name TEXT NOT NULL -- Player name\n"
|
||||
+ ") WITHOUT ROWID;", playerresults_table_name.c_str());
|
||||
+ if(easySQLQuery(query)) {
|
||||
+ m_server_playerresults_table = playerresults_table_name;
|
||||
+ }
|
||||
+
|
||||
// Default views:
|
||||
// _full_stats
|
||||
// Full stats with ip in human readable format and time played of each
|
||||
@@ -2884,6 +2930,7 @@ void ServerLobby::checkRaceFinished()
|
||||
RaceEventManager::get()->getProtocol()->requestTerminate();
|
||||
GameProtocol::lock()->requestTerminate();
|
||||
|
||||
+
|
||||
// Save race result before delete the world
|
||||
m_result_ns->clear();
|
||||
m_result_ns->addUInt8(LE_RACE_FINISHED);
|
||||
@@ -2895,10 +2942,13 @@ void ServerLobby::checkRaceFinished()
|
||||
m_result_ns->addUInt32(fastest_lap);
|
||||
m_result_ns->encodeString(static_cast<LinearWorld*>(World::getWorld())
|
||||
->getFastestLapKartName());
|
||||
+ std::cout << static_cast<LinearWorld*>(World::getWorld())->getFastestLapKartName().c_str() << std::endl;
|
||||
+ Log::info("RaceResults", "Fastest lap by %s", StringUtils::wideToUtf8(static_cast<LinearWorld*>(World::getWorld())->getFastestLapKartName()).c_str());
|
||||
|
||||
// all gp tracks
|
||||
m_result_ns->addUInt8((uint8_t)m_game_setup->getTotalGrandPrixTracks())
|
||||
.addUInt8((uint8_t)m_game_setup->getAllTracks().size());
|
||||
+ Log::info("RaceResults", "Track number %d / %d", m_game_setup->getAllTracks().size(), m_game_setup->getTotalGrandPrixTracks());
|
||||
for (const std::string& gp_track : m_game_setup->getAllTracks())
|
||||
m_result_ns->encodeString(gp_track);
|
||||
|
||||
@@ -2917,9 +2967,13 @@ void ServerLobby::checkRaceFinished()
|
||||
overall_time = overall_time + player->getOverallTime();
|
||||
player->setScore(cur_score);
|
||||
player->setOverallTime(overall_time);
|
||||
+ Log::info("RaceResults", "Score for %s from %d to %d (%f)", StringUtils::wideToUtf8(player->getName()).c_str(), last_score, cur_score, overall_time);
|
||||
+ }
|
||||
+ else {
|
||||
+ Log::info("RaceResults", "Score for player %d from %d to %d (%f)", i, last_score, cur_score, overall_time);
|
||||
}
|
||||
m_result_ns->addUInt32(last_score).addUInt32(cur_score)
|
||||
- .addFloat(overall_time);
|
||||
+ .addFloat(overall_time);
|
||||
}
|
||||
}
|
||||
else if (RaceManager::get()->modeHasLaps())
|
||||
@@ -2936,6 +2990,8 @@ void ServerLobby::checkRaceFinished()
|
||||
ranking_changes_indication = 1;
|
||||
m_result_ns->addUInt8(ranking_changes_indication);
|
||||
|
||||
+ writeRaceResults();
|
||||
+
|
||||
if (ServerConfig::m_ranked)
|
||||
{
|
||||
computeNewRankings();
|
||||
@@ -2944,6 +3000,148 @@ void ServerLobby::checkRaceFinished()
|
||||
m_state.store(WAIT_FOR_RACE_STOPPED);
|
||||
} // checkRaceFinished
|
||||
|
||||
+int ServerLobby::getLastRowID(const std::string &table) {
|
||||
+ int rowid = -1;
|
||||
+ std::string query = StringUtils::insertValues("SELECT ROWID from %s ORDER BY ROWID DESC LIMIT 1;", table);
|
||||
+
|
||||
+ sqlite3_stmt* stmt = NULL;
|
||||
+ int ret = sqlite3_prepare_v2(m_db, query.c_str(), -1, &stmt, 0);
|
||||
+ if (ret == SQLITE_OK)
|
||||
+ {
|
||||
+ ret = sqlite3_step(stmt);
|
||||
+ if (ret == SQLITE_ROW)
|
||||
+ {
|
||||
+ rowid = sqlite3_column_int(stmt, 0);
|
||||
+ }
|
||||
+ ret = sqlite3_finalize(stmt);
|
||||
+ if (ret != SQLITE_OK)
|
||||
+ {
|
||||
+ Log::error("ServerLobby",
|
||||
+ "Error rowid database for query %s: %s",
|
||||
+ query.c_str(), sqlite3_errmsg(m_db));
|
||||
+ }
|
||||
+ }
|
||||
+ else
|
||||
+ {
|
||||
+ Log::error("ServerLobby", "Error preparing database for query %s: %s",
|
||||
+ query.c_str(), sqlite3_errmsg(m_db));
|
||||
+ return -1;
|
||||
+ }
|
||||
+ return rowid;
|
||||
+
|
||||
+}
|
||||
+
|
||||
+//-----------------------------------------------------------------------------
|
||||
+/** Write the results of the race
|
||||
+ */
|
||||
+void ServerLobby::writeRaceResults()
|
||||
+{
|
||||
+#ifdef ENABLE_SQLITE3
|
||||
+ std::string track_name = RaceManager::get()->getTrackName();
|
||||
+ bool track_reverse = RaceManager::get()->getReverseTrack();
|
||||
+ int player_count = RaceManager::get()->getNumPlayers();
|
||||
+ int laps_number = RaceManager::get()->getNumLaps();
|
||||
+
|
||||
+ if (RaceManager::get()->getMinorMode() == RaceManager::MINOR_MODE_NORMAL_RACE) {
|
||||
+ std::string query;
|
||||
+ if (m_game_setup->isGrandPrix()) {
|
||||
+ int race_number = m_game_setup->getAllTracks().size();
|
||||
+ int total_races = m_game_setup->getTotalGrandPrixTracks();
|
||||
+ if(race_number == 1) {
|
||||
+ query = StringUtils::insertValues(
|
||||
+ "INSERT INTO %s "
|
||||
+ "(num_races, total_races) "
|
||||
+ "VALUES (%d, %d);",
|
||||
+ m_server_grandprixresults_table,
|
||||
+ race_number,
|
||||
+ total_races
|
||||
+ );
|
||||
+ if(easySQLQuery(query)) {
|
||||
+ m_grand_prix_rowid = getLastRowID(m_server_grandprixresults_table);
|
||||
+ }
|
||||
+ }
|
||||
+ else {
|
||||
+ query = StringUtils::insertValues(
|
||||
+ "UPDATE %s "
|
||||
+ "SET num_races = %d "
|
||||
+ "WHERE ROWID = %d;",
|
||||
+ m_server_grandprixresults_table,
|
||||
+ race_number,
|
||||
+ m_grand_prix_rowid
|
||||
+ );
|
||||
+ easySQLQuery(query);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if(m_grand_prix_rowid >= 0) {
|
||||
+ query = StringUtils::insertValues(
|
||||
+ "INSERT INTO %s "
|
||||
+ "(grandprix_rowid, track_name, num_laps, total_laps, fastest_lap_time, fastest_lap_player) "
|
||||
+ "VALUES (%d, \"%s\", %d, %d, %f, \"%s\");",
|
||||
+ m_server_raceresults_table,
|
||||
+ m_grand_prix_rowid,
|
||||
+ track_name,
|
||||
+ laps_number,
|
||||
+ 0,
|
||||
+ 0.,
|
||||
+ ""
|
||||
+ );
|
||||
+ }
|
||||
+ else {
|
||||
+ query = StringUtils::insertValues(
|
||||
+ "INSERT INTO %s "
|
||||
+ "(track_name, num_laps, total_laps, fastest_lap_time, fastest_lap_player) "
|
||||
+ "VALUES (\"%s\", %d, %d, %f, \"%s\");",
|
||||
+ m_server_raceresults_table,
|
||||
+ track_name,
|
||||
+ laps_number,
|
||||
+ 0,
|
||||
+ 0.,
|
||||
+ ""
|
||||
+ );
|
||||
+ }
|
||||
+ if(easySQLQuery(query)) {
|
||||
+ int race_rowid = getLastRowID(m_server_raceresults_table);
|
||||
+ Log::info("RaceResults", "Row ID: %d", race_rowid);
|
||||
+ if(race_rowid >= 0) {
|
||||
+ for (int i = 0; i < player_count; i++) {
|
||||
+ double elapsed_time = RaceManager::get()->getKartRaceTime(i);
|
||||
+ std::string player_name = StringUtils::wideToUtf8(
|
||||
+ RaceManager::get()->getKartInfo(i).getPlayerName());
|
||||
+ //int grand_prix_rank = RaceManager::get()->getKartGPRank(i);
|
||||
+ query = StringUtils::insertValues(
|
||||
+ "INSERT INTO %s "
|
||||
+ "(race_rowid, elapsed_time, player_name) "
|
||||
+ "VALUES (%d, %f, ?);",
|
||||
+ m_server_playerresults_table,
|
||||
+ race_rowid,
|
||||
+ elapsed_time
|
||||
+ );
|
||||
+ easySQLQuery(query, [player_name](sqlite3_stmt* stmt) {
|
||||
+ if (sqlite3_bind_text(stmt, 1, player_name.c_str(),
|
||||
+ -1, SQLITE_TRANSIENT) != SQLITE_OK)
|
||||
+ {
|
||||
+ Log::error("easySQLQuery", "Failed to bind %s.",
|
||||
+ player_name.c_str());
|
||||
+ }
|
||||
+ });
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if(m_game_setup->isGrandPrix()) {
|
||||
+ int race_number = m_game_setup->getAllTracks().size();
|
||||
+ int total_races = m_game_setup->getTotalGrandPrixTracks();
|
||||
+
|
||||
+ if(race_number == total_races) {
|
||||
+ m_grand_prix_rowid = -1;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+#endif
|
||||
+}
|
||||
+
|
||||
+
|
||||
//-----------------------------------------------------------------------------
|
||||
/** Compute the new player's rankings used in ranked servers
|
||||
*/
|
||||
diff --git a/src/network/protocols/server_lobby.hpp b/src/network/protocols/server_lobby.hpp
|
||||
index 53d3aceda..780ef2d4e 100644
|
||||
--- a/src/network/protocols/server_lobby.hpp
|
||||
+++ b/src/network/protocols/server_lobby.hpp
|
||||
@@ -85,6 +85,14 @@ private:
|
||||
|
||||
std::string m_server_stats_table;
|
||||
|
||||
+ std::string m_server_grandprixresults_table;
|
||||
+
|
||||
+ std::string m_server_raceresults_table;
|
||||
+
|
||||
+ std::string m_server_playerresults_table;
|
||||
+
|
||||
+ int m_grand_prix_rowid;
|
||||
+
|
||||
bool m_ip_ban_table_exists;
|
||||
|
||||
bool m_ipv6_ban_table_exists;
|
||||
@@ -377,6 +385,8 @@ private:
|
||||
void testBannedForOnlineId(STKPeer* peer, uint32_t online_id) const;
|
||||
void writeDisconnectInfoTable(STKPeer* peer);
|
||||
void writePlayerReport(Event* event);
|
||||
+ int getLastRowID(const std::string &table);
|
||||
+ void writeRaceResults();
|
||||
bool supportsAI();
|
||||
void updateAddons();
|
||||
public:
|
|
@ -19,6 +19,11 @@ set -e
|
|||
|
||||
echo "Installing SuperTuxKart ${stk_version}"
|
||||
|
||||
apt-get install --assume-yes build-essential cmake libbluetooth-dev libsdl2-dev \
|
||||
libcurl4-openssl-dev libenet-dev libfreetype6-dev libharfbuzz-dev \
|
||||
libjpeg-dev libogg-dev libopenal-dev libpng-dev \
|
||||
libssl-dev libvorbis-dev libmbedtls-dev pkg-config zlib1g-dev subversion
|
||||
|
||||
if [ -e /etc/systemd/system/supertuxkart.service ]; then
|
||||
systemctl stop supertuxkart
|
||||
fi
|
||||
|
@ -31,10 +36,28 @@ fi
|
|||
stk_dir="/opt/SuperTuxKart-${stk_version}"
|
||||
mkdir -p ${stk_dir}
|
||||
curl --location "https://github.com/supertuxkart/stk-code/releases/download/${stk_version}/SuperTuxKart-${stk_version}-linux-x86_64.tar.xz" | tar --extract --xz --no-same-owner --strip-components=1 --directory=${stk_dir}
|
||||
|
||||
builddir=""${TMPDIR:-/tmp}/stk-build""
|
||||
if [ -d "$builddir" ];
|
||||
rm -rf "$builddir"
|
||||
fi
|
||||
mkdir -p "$builddir"
|
||||
curl --location "https://github.com/supertuxkart/stk-code/archive/refs/tags/${stk_version}.tar.gz" | tar --extract --gz --no-same-owner --directory=$builddir
|
||||
svn co https://svn.code.sf.net/p/supertuxkart/code/stk-assets ${builddir}/stk-assets
|
||||
patch -p1 < ~jarno/stkranking.patch
|
||||
mkdir -p "$builddir"/build
|
||||
cd "$builddir"/build
|
||||
cmake ../stk-code-${stk_version}/ -DCMAKE_INSTALL_PREFIX=/opt/SuperTuxKart-${stk_version}/ -DSERVER_ONLY=On
|
||||
make -j 2
|
||||
make install
|
||||
|
||||
ln -s ${stk_dir}/bin/supertuxkart /usr/games/supertuxkart
|
||||
|
||||
# Configuration
|
||||
cp $(dirname $0)/../configs/supertuxkart.xml /etc/supertuxkart.xml
|
||||
mkdir -p /etc/supertuxkart
|
||||
cp "$(dirname $0)"/../configs/supertuxkart.xml /etc/supertuxkart/supertuxkart.xml
|
||||
touch /etc/supertuxkart/stkservers.db
|
||||
chown -R ${systemuser}: /etc/supertuxkart
|
||||
|
||||
# Create SystemD unit
|
||||
cat > /etc/systemd/system/supertuxkart.service <<EOF
|
||||
|
@ -43,7 +66,30 @@ Description=SuperTuxKart server
|
|||
After=network.target
|
||||
|
||||
[Service]
|
||||
ExecStart=${stk_dir}/run_game.sh --server-config=/etc/supertuxkart.xml --lan-server=onFOSS
|
||||
ExecStart=${stk_dir}/bin/supertuxkart --server-config=/etc/supertuxkart/supertuxkart.xml --lan-server=onFOSS
|
||||
Restart=on-failure
|
||||
User=${systemuser}
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
# SuperTuxKart scoring
|
||||
cp "$(dirname $0)"/../services/supertuxkartscores.py ${stk_dir}
|
||||
|
||||
cat > /etc/nginx/gameserver.d/supertuxkart.conf <<EOF
|
||||
location /dynamic/supertuxkartscore.json {
|
||||
proxy_pass http://localhost:9985/;
|
||||
}
|
||||
EOF
|
||||
|
||||
cat > /etc/systemd/system/supertuxkartscores.service <<EOF
|
||||
[Unit]
|
||||
Description=SuperTuxKart score service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
ExecStart=${stk_dir}/supertuxkartscores.py
|
||||
Restart=on-failure
|
||||
User=${systemuser}
|
||||
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import sqlite3
|
||||
import json
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
|
||||
CONFIGNAME="supertuxkart"
|
||||
DATABASEFILE='/etc/supertuxkart/stkservers.db'
|
||||
|
||||
def results_as_json():
|
||||
cx = sqlite3.connect(DATABASEFILE)
|
||||
grandprixes = []
|
||||
grandprix_cursor = cx.cursor()
|
||||
for grandprix_row in grandprix_cursor.execute(f'SELECT ROWID, num_races, total_races FROM v1_{CONFIGNAME}_grandprixresults'):
|
||||
races = []
|
||||
race_cursor = cx.cursor()
|
||||
for race_row in race_cursor.execute(f'SELECT ROWID, track_name FROM v1_{CONFIGNAME}_raceresults WHERE grandprix_rowid = {grandprix_row[0]}'):
|
||||
race = {"track": race_row[1], "players": []}
|
||||
player_cursor = cx.cursor()
|
||||
for player_row in player_cursor.execute(f'SELECT elapsed_time, player_name FROM v1_your_config_playerresults WHERE race_rowid = {race_row[0]} ORDER BY elapsed_time ASC'):
|
||||
race['players'].append({"name": player_row[1], "time": player_row[0]})
|
||||
races.append(race)
|
||||
grandprixes.append({"num_races": grandprix_row[1], "total_races": grandprix_row[2], "races": races})
|
||||
cx.close()
|
||||
|
||||
return json.dumps(grandprixes, indent=None, separators=(',', ':'))
|
||||
|
||||
class STKStatServer(BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Access-Control-Allow-Origin", "*")
|
||||
self.end_headers()
|
||||
|
||||
self.wfile.write(results_as_json().encode('utf-8'))
|
||||
|
||||
if __name__ == "__main__":
|
||||
server = HTTPServer(("127.0.0.1", 9985), STKStatServer)
|
||||
server.serve_forever()
|
|
@ -1,4 +1,26 @@
|
|||
- date: 2024-05-06T12:00:00UTC
|
||||
- date: 2023-09-23T12:00:00UTC
|
||||
intro: "onFOSS-LAN hosted by DeathByDenim"
|
||||
games:
|
||||
- time: "12:00"
|
||||
title: "Get and chill together"
|
||||
- time: "13:00"
|
||||
title: "FTEQW"
|
||||
- time: "14:30"
|
||||
title: "Hypersomnia"
|
||||
- time: "15:30"
|
||||
title: "Break"
|
||||
- time: "16:00"
|
||||
title: "SuperTuxKart"
|
||||
tournament: true
|
||||
- time: "17:00"
|
||||
title: "Break"
|
||||
- time: "17:30"
|
||||
title: "Bzflag"
|
||||
- time: "18:30"
|
||||
title: "Lix"
|
||||
- time: "19:30"
|
||||
title: "Free Play!"
|
||||
- date: 2023-05-06T12:00:00UTC
|
||||
intro: "onFOSS-LAN hosted by hribhrib"
|
||||
games:
|
||||
- time: "12:00"
|
||||
|
@ -92,8 +114,170 @@
|
|||
<li>Anonymous: €40</li>
|
||||
</ul>
|
||||
- date: 2022-02-12T15:00UTC
|
||||
intro: "Session to test if it all works as intended"
|
||||
intro: "Session to test if it all works as intended! hosted by DeathByDenim, first time hosted by someone else"
|
||||
games:
|
||||
- title: "OpenSpades"
|
||||
- title: "OpenHV"
|
||||
- title: "Xonotic"
|
||||
- date: 2022-01-10T12:00:00UTC
|
||||
intro: "onFOSS-LAN: powered by LibreGaming! hosted by LibreGaming, first group-effort event"
|
||||
games:
|
||||
- time: "12:00"
|
||||
title: "Meetup on Mumble and chill gaming"
|
||||
- time: "13:00"
|
||||
title: "Break"
|
||||
- time: "14:00"
|
||||
title: "New to onFOSS-LAN (UFO:AI, Soldat)"
|
||||
- time: "15:30"
|
||||
title: "Break"
|
||||
- time: "16:00"
|
||||
title: "SuperTuxKart Tournament"
|
||||
- time: "18:00"
|
||||
title: "Break"
|
||||
- time: "18:30"
|
||||
title: "Good old classics (Lix, Hedgewars)"
|
||||
- time: "20:00"
|
||||
title: "Late night gaming"
|
||||
extras: ["Soldat", "UFO:AI","Lix","Hedgewars","SuperTuxKart"]
|
||||
- date: 2021-12-19T11:00:00UTC
|
||||
intro: "onFOSS-LAN: Double the fun! hosted by hribhrib, first event over two days, livestream by opensource_gaming"
|
||||
games:
|
||||
- time: "13:00"
|
||||
title: "Meetup on Mumble and chill gaming"
|
||||
- time: "15:30"
|
||||
title: "Break"
|
||||
- time: "16:00"
|
||||
title: "Teeworlds Tournament - Finale LIVESTREAM"
|
||||
- date: 2021-12-18T11:00:00UTC
|
||||
intro: "onFOSS-LAN: Double the fun! hosted by hribhrib, first event over two days, livestream by opensource_gaming"
|
||||
games:
|
||||
- time: "11:00"
|
||||
title: "Meetup on Mumble and chill gaming"
|
||||
- time: "14:00"
|
||||
title: "Break"
|
||||
- time: "15:00"
|
||||
title: "Teeworlds Tournament - Playoffs LIVESTREAM"
|
||||
- time: "18:00"
|
||||
title: "Break"
|
||||
- time: "19:00"
|
||||
title: "Late night gaming"
|
||||
extras: ["Teeworlds", "OpenHV","Hedgewars","SuperTuxKart","Mindustry","OpenSpades","Unvanquished"]
|
||||
- date: 2021-11-13T10:00:00UTC
|
||||
intro: "onFOSS-LAN: Casual Saturday! hosted by hribhrib"
|
||||
games:
|
||||
- time: "10:00"
|
||||
title: "Meetup on Mumble and chill gaming"
|
||||
- time: "13:00"
|
||||
title: "Break"
|
||||
- time: "14:00"
|
||||
title: "Casual gaming"
|
||||
- time: "17:00"
|
||||
title: "Break"
|
||||
- time: "18:00"
|
||||
title: "Late night gaming"
|
||||
extras: ["BZFlag", "Hedgewars","Mindustry","SuperTuxKart"]
|
||||
- date: 2021-09-11T10:00:00UTC
|
||||
intro: "onFOSS-LAN: spoRTSmanship! hosted by hribhrib, livestream by opensource_gaming"
|
||||
games:
|
||||
- time: "10:00"
|
||||
title: "Meetup on Mumble and chill gaming"
|
||||
- time: "13:00"
|
||||
title: "Break"
|
||||
- time: "14:00"
|
||||
title: "OpenHV tournament (LIVESTREAM)"
|
||||
- time: "17:00"
|
||||
title: "Break"
|
||||
- time: "17:30"
|
||||
title: "OpenHV finals (LIVESTREAM)"
|
||||
- time: "19:00"
|
||||
title: "Break"
|
||||
- time: "20:00"
|
||||
title: "Late night gaming"
|
||||
extras: ["0ad", "OpenHV","Widelands"]
|
||||
- date: 2021-07-10T10:00:00UTC
|
||||
intro: "onFOSS-LAN: Race for the loot! hosted by hribhrib"
|
||||
games:
|
||||
- time: "10:00"
|
||||
title: "Meetup on Mumble and chill gaming"
|
||||
- time: "13:00"
|
||||
title: "Break"
|
||||
- time: "14:00"
|
||||
title: "Veloren Loot and Level"
|
||||
- time: "17:00"
|
||||
title: "Break"
|
||||
- time: "18:00"
|
||||
title: "Veloren final Dungeon (LIVESTREAM)"
|
||||
- time: "20:00"
|
||||
title: "Late night gaming"
|
||||
extras: ["SuperTuxKart", "armagetron","Minetest"]
|
||||
- date: 2021-05-22T10:00:00UTC
|
||||
intro: "onFOSS-LAN: Live and Reloaded! hosted by hribhrib, first livestream by Murks"
|
||||
games:
|
||||
- time: "10:00"
|
||||
title: "Meetup on Mumble and chill gaming"
|
||||
- time: "13:00"
|
||||
title: "Break"
|
||||
- time: "14:00"
|
||||
title: "Teeworlds tournament (LIVESTREAM)"
|
||||
- time: "17:00"
|
||||
title: "Break"
|
||||
- time: "17:30"
|
||||
title: "Teeworlds finals (LIVESTREAM)"
|
||||
- time: "19:00"
|
||||
title: "Break"
|
||||
- time: "20:00"
|
||||
title: "Late night gaming"
|
||||
extras: ["OpenSpades", "Xonotic","Minetest"]
|
||||
- date: 2021-04-17T10:30:00UTC
|
||||
intro: "onFOSS-LAN hosted by hribhrib, first public onFOSS-LAN!"
|
||||
games:
|
||||
- time: "10:30"
|
||||
title: "Meetup on Mumble and chill gaming"
|
||||
- time: "13:00"
|
||||
title: "OpenRA tournament"
|
||||
- time: "16:00"
|
||||
title: "Break"
|
||||
- time: "17:00"
|
||||
title: "Hedgewars tournament"
|
||||
- time: "20:00"
|
||||
title: "Break"
|
||||
- time: "21:00"
|
||||
title: "Late night gaming"
|
||||
extras: ["Minetest", "SuperTuxKart (Race and Soccer modes)","Teeworlds"]
|
||||
- date: 2021-03-06T10:30:00UTC
|
||||
intro: "onFOSS-LAN hosted by hribhrib, private"
|
||||
games:
|
||||
- time: "10:30"
|
||||
title: "Meetup on Mumble and chill gaming"
|
||||
- time: "13:00"
|
||||
title: "test warzone2100"
|
||||
- time: "15:00"
|
||||
title: "OpenRA - Free for all (12player max)"
|
||||
- time: "17:00"
|
||||
title: "OpenRA - 1v1/2v2/3v3 tournaments"
|
||||
- time: "21:00"
|
||||
title: "Late night gaming"
|
||||
- date: 2021-01-23T10:30:00UTC
|
||||
intro: "onFOSS-LAN hosted by hribhrib, private"
|
||||
games:
|
||||
- time: "10:30"
|
||||
title: "Meetup on Mumble and chill gaming"
|
||||
- time: "13:00"
|
||||
title: "supertuxkart turnament (8player max)"
|
||||
- time: "15:00"
|
||||
title: "hedgewars turnament (8player max)"
|
||||
- time: "17:00"
|
||||
title: "teeworlds together"
|
||||
- time: "19:00"
|
||||
title: "0ad US vs BOTS (6player max or 8 without bots)"
|
||||
- time: "21:00"
|
||||
title: "Late night gaming"
|
||||
- date: 2021-01-02T10:30:00UTC
|
||||
intro: "onFOSS-LAN hosted by hribhrib, private"
|
||||
games:
|
||||
- time: "10:30"
|
||||
title: "Doors open, Welcoming and troubleshooting games if not running, talk, play"
|
||||
- time: "12:00"
|
||||
title: "Lunch together (or alone)"
|
||||
- time: "13:00"
|
||||
title: "GAMES"
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
let grandprix_blocks;
|
||||
let races;
|
||||
|
||||
function positionToScore(i) {
|
||||
switch(i) {
|
||||
case 0:
|
||||
return 4;
|
||||
case 1:
|
||||
return 2;
|
||||
case 2:
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function supertuxkartScoreUpdate() {
|
||||
d3.json("/dynamic/supertuxkartscore.json").then((data) => {
|
||||
grandprix_blocks = d3.select('#supertuxkart-results')
|
||||
.selectAll('div.grandprix-div')
|
||||
.data(data)
|
||||
.join(
|
||||
(enter) => {
|
||||
const e = enter.append('div')
|
||||
.classed('grandprix-div', true);
|
||||
e.append('h3')
|
||||
.text((d, i) => 'Grand prix ' + (i+1));
|
||||
e.append('div').classed('races', true);
|
||||
return e;
|
||||
},
|
||||
(update) => {
|
||||
update.select('h3')
|
||||
.text((d, i) => 'Grand prix ' + (i+1));
|
||||
return update;
|
||||
},
|
||||
(exit) => {
|
||||
exit.remove();
|
||||
}
|
||||
);
|
||||
|
||||
races = grandprix_blocks.select('div.races')
|
||||
.selectAll('table')
|
||||
.data((d) => {
|
||||
return d.races.map((x) => {
|
||||
x["total"] = d.total_races;
|
||||
return x;
|
||||
});
|
||||
})
|
||||
.join(
|
||||
(enter) => {
|
||||
const table = enter.append('table')
|
||||
.classed('table', true);
|
||||
let thead = table.append('thead');
|
||||
thead.append('tr')
|
||||
.append('th')
|
||||
.attr('colspan', 4)
|
||||
.text((d, i) => {
|
||||
return "Race " + (i+1) + " out of " + d.total + " on " + d.track;
|
||||
});
|
||||
let headerrows = thead.append('tr');
|
||||
["Rank", "Name", "Time", "Points"].forEach((col) => {
|
||||
headerrows.append('th').text(col);
|
||||
});
|
||||
table.append('tbody')
|
||||
.classed('grandprix-tbody', true);
|
||||
return table;
|
||||
},
|
||||
(update) => {
|
||||
const u = update;
|
||||
update.select('th[colspan="4"]')
|
||||
.text((d, i) => {
|
||||
return "Race " + (i+1) + " out of " + d.total + " on " + d.track;
|
||||
});
|
||||
return u;
|
||||
},
|
||||
(exit) => {
|
||||
exit.remove();
|
||||
}
|
||||
);
|
||||
|
||||
let tbodies = races.select('tbody.grandprix-tbody')
|
||||
.selectAll('tr')
|
||||
.data((d) => d.players)
|
||||
.join(
|
||||
(enter) => {
|
||||
let e = enter.append('tr');
|
||||
e.append('td')
|
||||
.text((d,i) => (i+1) + '.');
|
||||
e.append('td')
|
||||
.text((d,i) => d.name);
|
||||
e.append('td')
|
||||
.text((d,i) => d.time);
|
||||
e.append('td')
|
||||
.text((d,i) => positionToScore(i));
|
||||
return e;
|
||||
},
|
||||
(update) => {
|
||||
update.select('td:nth-child(1)')
|
||||
.text((d,i) => {console.log(d); return (i+1) + '.'});
|
||||
update.select('td:nth-child(2)')
|
||||
.text((d,i) => d.name);
|
||||
update.select('td:nth-child(3)')
|
||||
.text((d,i) => d.time);
|
||||
update.select('td:nth-child(4)')
|
||||
.text((d,i) => positionToScore(i));
|
||||
return update;
|
||||
},
|
||||
(exit) => exit.remove()
|
||||
)
|
||||
});
|
||||
}
|
|
@ -3,6 +3,7 @@ layout: default
|
|||
nav_pill: tournament
|
||||
---
|
||||
<script src="js/xonscore.js"></script>
|
||||
<script src="js/supertuxkartscore.js"></script>
|
||||
<h1>Tournament</h1>
|
||||
{% assign sitetime = site.time | date: "%FT%T" %}
|
||||
{% assign nextevent = site.data.events | where_exp: "item", "item.date >= sitetime" | last %}
|
||||
|
@ -25,19 +26,20 @@ nav_pill: tournament
|
|||
{% endfor %}
|
||||
</ul>
|
||||
<h2>Ranking</h2>
|
||||
<div id="supertuxkart-results"></div>
|
||||
<script>
|
||||
if(typeof d3 === 'undefined') {
|
||||
document.write('<p><a href="supertuxkartscore.json">Results</a> (allow access to d3js.org for dynamic updates)</p>');
|
||||
}
|
||||
else {
|
||||
supertuxkartScoreUpdate();
|
||||
//setInterval(supertuxkartScoreUpdate, 10000);
|
||||
}
|
||||
</script>
|
||||
<noscript><p><a href="supertuxkartscore.json">Results</a> (Enable JavaScript for dynamic updates)</p></noscript>
|
||||
<ol id="xonotic-ranking"></ol>
|
||||
<h2>Rounds</h2>
|
||||
<div id="xonotic-results"></div>
|
||||
<script>
|
||||
if(typeof d3 === 'undefined') {
|
||||
document.write('<p><a href="xonscore.txt">Results</a> (allow access to d3js.org for dynamic updates)</p>');
|
||||
}
|
||||
else {
|
||||
xonoticScoreUpdate();
|
||||
setInterval(xonoticScoreUpdate, 10000);
|
||||
}
|
||||
</script>
|
||||
<noscript><p><a href="xonscore.txt">Results</a> (Enable JavaScript for dynamic updates)</p></noscript>
|
||||
{% else %}
|
||||
<p>No tournaments have been planned for the next event.</p>
|
||||
{% endif %}
|
||||
|
|
Loading…
Reference in New Issue