Update to ingest/display new stats format; QoL updates soon...

This commit is contained in:
Anthony Mineo 2020-08-15 12:02:05 -04:00
parent bb179b82f8
commit 0b5aceca7a
9 changed files with 497 additions and 440 deletions

2
.gitignore vendored
View file

@ -4,6 +4,8 @@ node_modules
notes.md notes.md
app/t2-stat-parser/serverStats/stats app/t2-stat-parser/serverStats/stats
app/t2-stat-parser/serverStats/mlData
app/t2-stat-parser/serverStats/lData
docker-compose.deploy.yml docker-compose.deploy.yml
traefik.yml traefik.yml

View file

@ -14,9 +14,9 @@ import (
) )
func main() { func main() {
fmt.Println("Starting FTP stat file download") // fmt.Println("Starting FTP stat file download")
initFTP() initFTP()
fmt.Println("Stat files downloaded!") // fmt.Println("Stat files downloaded!")
fmt.Println("Starting stat parser") fmt.Println("Starting stat parser")
initParser() initParser()

View file

@ -59,12 +59,12 @@ type Game struct {
dbStatOverWrite int `db.players:"stat_overwrite"` dbStatOverWrite int `db.players:"stat_overwrite"`
statOverWrite int statOverWrite int
gameMap string `db.games:"map"` gameMap string `db.game_detail:"map"`
gameID int `db.games:"game_id"` gameID int `db.game_detail:"game_id"`
gameType string `db.games:"gametype"` gameType string `db.game_detail:"gametype"`
dateStamp string `db.games:"datestamp"` dateStamp string `db.game_detail:"datestamp"`
stats string `db.games:"stats"` stats string `db.game_detail:"stats"`
uuid string `db.games:"uuid"` uuid string `db.game_detail:"uuid"`
} }
func initParser() { func initParser() {
@ -265,7 +265,10 @@ func parseStatOverWriteLine(g Game, mStatLine map[string][]string, arrPosition i
fmt.Println(g) fmt.Println(g)
} }
if checkGameEntry(g) == false && g.gameID != 0 { // Check if we need to create a new game record
checkGameRecord(g)
if checkGameEntryForPlayer(g) == false && g.gameID != 0 {
fmt.Println("does not exist, add") fmt.Println("does not exist, add")
// insert game stat // insert game stat
addPlayerGameStat(g, strings.ToLower(gt)) addPlayerGameStat(g, strings.ToLower(gt))
@ -283,6 +286,28 @@ func rowExists(query string, args ...interface{}) bool {
return exists return exists
} }
func checkGameRecord(g Game) {
check := rowExists("select game_id from games where game_id = $1 and map = $2", g.gameID, g.gameMap)
if !check {
createGame(g)
} else {
fmt.Println("Game Record ", g.gameID, g.gameMap, " already exists")
}
}
func createGame(g Game) {
fmt.Println("Creating new Game ", g.gameMap, g.gameID, g.dateStamp, g.gameType)
if g.gameID != 0 {
sqlInsert := `insert into games(map, game_id, datestamp, gametype) values($1,$2,$3,$4)`
_, err := db.Exec(sqlInsert, g.gameMap, g.gameID, g.dateStamp, g.gameType)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to create new game record (Possible Dupe ID): %v\n", err)
// Don't exit - just skip this insert
// os.Exit(1)
}
}
}
func checkPlayer(g Game) { func checkPlayer(g Game) {
check := rowExists("select player_guid from players where player_guid = $1", g.playerGUID) check := rowExists("select player_guid from players where player_guid = $1", g.playerGUID)
if !check { if !check {
@ -292,8 +317,8 @@ func checkPlayer(g Game) {
} }
} }
func checkGameEntry(g Game) bool { func checkGameEntryForPlayer(g Game) bool {
check := rowExists("select player_guid from games where player_guid = $1 and game_id = $2 and map = $3", g.playerGUID, g.gameID, g.gameMap) check := rowExists("select player_guid from game_detail where player_guid = $1 and game_id = $2 and map = $3", g.playerGUID, g.gameID, g.gameMap)
if !check { if !check {
return false return false
} else { } else {
@ -341,7 +366,7 @@ func addPlayerGameStat(g Game, gt string) {
if g.dateStamp != "0" { if g.dateStamp != "0" {
// Insert new stat line // Insert new stat line
fmt.Println("New stat line!", g.playerName, g.dateStamp) fmt.Println("New stat line!", g.playerName, g.dateStamp)
sqlInsert := `insert into games(player_guid, player_name, stat_overwrite, map, game_id, stats, datestamp, uuid, gametype) values($1,$2,$3,$4,$5,$6,$7,$8,$9)` sqlInsert := `insert into game_detail(player_guid, player_name, stat_overwrite, map, game_id, stats, datestamp, uuid, gametype) values($1,$2,$3,$4,$5,$6,$7,$8,$9)`
_, err := db.Exec(sqlInsert, g.playerGUID, g.playerName, g.statOverWrite, g.gameMap, g.gameID, g.stats, g.dateStamp, g.uuid, g.gameType) _, err := db.Exec(sqlInsert, g.playerGUID, g.playerName, g.statOverWrite, g.gameMap, g.gameID, g.stats, g.dateStamp, g.uuid, g.gameType)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Unable to add player's game stat: %v\n", err) fmt.Fprintf(os.Stderr, "Unable to add player's game stat: %v\n", err)

View file

@ -0,0 +1,7 @@
./lData
./mlData
./stats/CTFGame
./stats/DMGame
./stats/LakRabbitGame
./stats/SCTF/Game

View file

@ -1,12 +1,10 @@
'use strict' 'use strict';
const Database = use('Database') const Database = use('Database');
class GameController { class GameController {
// /games // /games
async index({ inertia }) { async index({ inertia }) {
const pageTitle = `Latest Games`; const pageTitle = `Latest Games`;
// const gamesQry = await Database.raw(` // const gamesQry = await Database.raw(`
@ -17,55 +15,50 @@ class GameController {
// LIMIT 2000; // LIMIT 2000;
// `); // `);
// const gamesQry = await Database.raw(`
// WITH gamelist AS (
// SELECT DISTINCT p.game_id, p.map, p.gametype, p.datestamp, count(*) OVER (PARTITION BY game_id) as count from games p
// )
// SELECT * from gamelist
// WHERE (count > 1) AND (game_id <> 0)
// ORDER BY game_id desc
// LIMIT 2000
// `);
const gamesQry = await Database.raw(` // // filter out duplicate game_ids (https://dev.to/marinamosti/removing-duplicates-in-an-array-of-objects-in-js-with-sets-3fep)
WITH gamelist AS ( // const games = gamesQry.rows.reduce((game, current) => {
SELECT DISTINCT p.game_id, p.map, p.gametype, p.datestamp, count(*) OVER (PARTITION BY game_id) as count from games p
)
SELECT * from gamelist
WHERE (count > 1) AND (game_id <> 0)
ORDER BY game_id desc
LIMIT 2000
`);
// filter out duplicate game_ids (https://dev.to/marinamosti/removing-duplicates-in-an-array-of-objects-in-js-with-sets-3fep) // const x = game.find(item => item.game_id === current.game_id);
const games = gamesQry.rows.reduce((game, current) => { // if (!x) {
// return game.concat([current]);
// } else {
// return game;
// }
// }, []);
const x = game.find(item => item.game_id === current.game_id); const games = await Database.from('games').orderBy('game_id', 'desc').limit(200);
if (!x) { // const CTFgames = await Database.from('games').where({gametype: 'CTFGame'}).orderBy('game_id', 'desc').limit(120);
return game.concat([current]); // const LAKgames = await Database.from('games').where({gametype: 'LakRabbitGame'}).orderBy('game_id', 'desc').limit(120);
} else {
return game;
}
}, []);
// move the 0 score display logic here // move the 0 score display logic here
return inertia.render('Games/Main', { pageTitle, games }, { edgeVar: 'server-variable' }) return inertia.render('Games/Main', { pageTitle, games }, { edgeVar: 'server-variable' });
} }
// game/:game_id // game/:game_id
async game({ inertia, request }) { async game({ inertia, request }) {
const gameInfo = await Database.from('games') const gameInfo = await Database.from('game_detail')
.distinct('game_id', .select('game_id', 'map', 'player_name', 'player_guid', 'gametype', 'stats', 'datestamp')
'map',
'player_name',
'player_guid',
'gametype',
'stats',
'datestamp')
.where({ game_id: request.params.game_id }) .where({ game_id: request.params.game_id })
.orderByRaw("stats->>'scoreTG' desc");
const pageTitle = { const pageTitle = {
name: gameInfo[0]['map'], name: gameInfo[0]['map'],
gametype: gameInfo[0]['gametype'] gametype: gameInfo[0]['gametype']
};
return inertia.render('Games/Game', { pageTitle, gameInfo }, { edgeVar: 'server-variable' });
}
} }
return inertia.render('Games/Game', { pageTitle, gameInfo }, { edgeVar: 'server-variable' }) module.exports = GameController;
}
}
module.exports = GameController

View file

@ -1,62 +1,61 @@
'use strict' 'use strict';
const Database = use('Database') const Database = use('Database');
class PlayerController { class PlayerController {
// Main Players List // Main Players List
async index({ inertia }) { async index({ inertia }) {
const pageTitle = "All Players" const pageTitle = 'All Players';
const players = await Database.table('players') const players = await Database.table('players')
.distinct('player_guid', .distinct(
'player_guid',
'player_name', 'player_name',
'total_games_ctfgame', 'total_games_ctfgame',
'total_games_dmgame', 'total_games_dmgame',
'total_games_lakrabbitgame', 'total_games_lakrabbitgame',
'total_games_sctfgame', 'total_games_sctfgame',
'updated_at') 'updated_at'
)
.groupBy('player_guid') .groupBy('player_guid')
.orderBy('player_name', 'asc') .orderBy('player_name', 'asc')
.limit(1000) .limit(1000);
return inertia.render('Players/Main', { pageTitle, players }, { edgeVar: 'server-variable' }) return inertia.render('Players/Main', { pageTitle, players }, { edgeVar: 'server-variable' });
} }
// Player Detail // Player Detail
async player({ inertia, request }) { async player({ inertia, request }) {
const playerInfo = await Database.from('players') const playerInfo = await Database.from('players')
.distinct('player_guid', .distinct(
'player_guid',
'player_name', 'player_name',
'total_games_ctfgame', 'total_games_ctfgame',
'total_games_dmgame', 'total_games_dmgame',
'total_games_lakrabbitgame', 'total_games_lakrabbitgame',
'total_games_sctfgame') 'total_games_sctfgame'
)
.where({ player_guid: request.params.player_guid }) .where({ player_guid: request.params.player_guid })
.limit(50) .limit(50);
const playerStatData = await Database.from('game_detail')
const playerStatData = await Database.from('games') .select('game_id', 'gametype', 'stats')
.select('game_id',
'gametype',
'stats')
.where({ player_guid: request.params.player_guid }) .where({ player_guid: request.params.player_guid })
.orderBy('game_id', 'desc') .orderBy('game_id', 'desc')
.limit(50) .limit(50);
// Dynamically generate and sum the stats object // Dynamically generate and sum the stats object
let playerStatTotals = {}, let playerStatTotals = {},
statKeys = Object.keys(playerStatData[0].stats) statKeys = Object.keys(playerStatData[0].stats);
for (let i = 0; i < statKeys.length; i++) { for (let i = 0; i < statKeys.length; i++) {
if(statKeys[i] === "map" || if (statKeys[i] === 'map' || statKeys[i] === 'dateStamp' || statKeys[i] === 'timeDayMonth') {
statKeys[i] === "dateStamp" || continue;
statKeys[i] === "timeDayMonth" ){continue;} }
playerStatTotals[statKeys[i]] = 0; playerStatTotals[statKeys[i]] = 0;
} }
// Loop through the playerStatsData query from the DB // Loop through the playerStatsData query from the DB
playerStatData.map(statLine => { playerStatData.map((statLine) => {
// look through each object in playerStatsData array // look through each object in playerStatsData array
for (let [ key, value ] of Object.entries(statLine.stats)) { for (let [ key, value ] of Object.entries(statLine.stats)) {
// console.log(`${key}: ${value}`); // console.log(`${key}: ${value}`);
@ -67,18 +66,17 @@ class PlayerController {
playerStatTotals[key] = Number(value); playerStatTotals[key] = Number(value);
} }
} }
}) });
let playerData = { let playerData = {
player: playerInfo[0], player: playerInfo[0],
stats: playerStatData, stats: playerStatData,
totals: playerStatTotals totals: playerStatTotals
} };
const pageTitle = playerData.player.player_name const pageTitle = playerData.player.player_name;
return inertia.render('Players/Player', { pageTitle, playerData }, { edgeVar: 'server-variable' }) return inertia.render('Players/Player', { pageTitle, playerData }, { edgeVar: 'server-variable' });
} }
} }
module.exports = PlayerController module.exports = PlayerController;

View file

@ -1,13 +1,12 @@
import React, {PureComponent} from 'react' import React, { PureComponent } from 'react';
import { InertiaLink } from '@inertiajs/inertia-react' import { InertiaLink } from '@inertiajs/inertia-react';
import Layout from '@/Shared/Layout' import Layout from '@/Shared/Layout';
import {PieChart, Pie, Sector} from 'recharts' import { PieChart, Pie, Sector } from 'recharts';
const renderActiveShape = (props) => { const renderActiveShape = (props) => {
const RADIAN = Math.PI / 180; const RADIAN = Math.PI / 180;
const { cx, cy, midAngle, innerRadius, outerRadius, startAngle, endAngle, const { cx, cy, midAngle, innerRadius, outerRadius, startAngle, endAngle, fill, payload, percent, value } = props;
fill, payload, percent, value } = props;
const sin = Math.sin(-RADIAN * midAngle); const sin = Math.sin(-RADIAN * midAngle);
const cos = Math.cos(-RADIAN * midAngle); const cos = Math.cos(-RADIAN * midAngle);
const sx = cx + (outerRadius + 10) * cos; const sx = cx + (outerRadius + 10) * cos;
@ -20,7 +19,9 @@ const renderActiveShape = (props) => {
return ( return (
<g> <g>
<text x={cx} y={cy} dy={8} textAnchor="middle" fill="#5850ec">{payload.name}</text> <text x={cx} y={cy} dy={8} textAnchor="middle" fill="#5850ec">
{payload.name}
</text>
<Sector <Sector
cx={cx} cx={cx}
cy={cy} cy={cy}
@ -51,14 +52,13 @@ const renderActiveShape = (props) => {
export class TwoLevelPieChart extends PureComponent { export class TwoLevelPieChart extends PureComponent {
state = { state = {
activeIndex: ((this.props.data.oScore >= this.props.data.dScore) ? 0 : 1 ), activeIndex: this.props.data.oScore >= this.props.data.dScore ? 0 : 1,
data: [{name: 'Offense', value: this.props.data.oScore}, data: [ { name: 'Offense', value: this.props.data.oScore }, { name: 'Defense', value: this.props.data.dScore } ]
{name: 'Defense', value: this.props.data.dScore}]
}; };
onPieEnter = (data, index) => { onPieEnter = (data, index) => {
this.setState({ this.setState({
activeIndex: index, activeIndex: index
}); });
}; };
@ -83,81 +83,82 @@ export class TwoLevelPieChart extends PureComponent {
} }
} }
const PlayerRow = (player, index) => { const PlayerRow = (player, index) => {
// dont show scoreless players // dont show scoreless players
//if (Number(player.stats.score) <= 0){return} // if (Number(player.stats.scoreTG) <= 0) {
// return;
// }
return <div className="flex flex-col rounded-lg shadow-lg overflow-hidden" key={index}> return (
<div className="flex flex-col rounded-lg shadow-lg overflow-hidden" key={index}>
<div className="bg-white shadow overflow-hidden sm:rounded-lg"> <div className="bg-white shadow overflow-hidden sm:rounded-lg">
<div className="px-4 py-5 border-b border-gray-200 sm:px-6"> <div className="px-4 py-5 border-b border-gray-200 sm:px-6">
<h3 className="text-lg leading-6 font-medium"> <h3 className="text-lg leading-6 font-medium">
<InertiaLink href={`/player/${player.player_guid}`} className="text-indigo-600 hover:text-indigo-500 transition duration-150 ease-in-out">{player.player_name}</InertiaLink> <InertiaLink
href={`/player/${player.player_guid}`}
className="text-indigo-600 hover:text-indigo-500 transition duration-150 ease-in-out"
>
{player.player_name}
</InertiaLink>
</h3> </h3>
</div> </div>
<div> <div>
<dl> <dl>
<div className="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"> <div className="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt className="text-sm leading-5 font-medium text-gray-500"> <dt className="text-sm leading-5 font-medium text-gray-500">Total Score</dt>
Total Score
</dt>
<dd className="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2"> <dd className="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{player.stats.score} {player.stats.scoreTG}
</dd> </dd>
</div> </div>
<div className="bg-gray-50 flex items-center justify-center"> <div className="bg-gray-50 flex items-center justify-center">
{ {player.gametype == 'CTFGame' || player.gametype == 'SCtFGame' ? (
(player.gametype == "CTFGame" || player.gametype == "SCtFGame") ? <TwoLevelPieChart data={{ <TwoLevelPieChart
oScore: Number(player.stats.offenseScore[0]), data={{
dScore: Number(player.stats.defenseScore[0])} oScore: Number(player.stats.offenseScoreTG[0]),
}/> dScore: Number(player.stats.defenseScoreTG[0])
: '' }}
} />
) : (
''
)}
</div> </div>
<div className="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"> <div className="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt className="text-sm leading-5 font-medium text-gray-500"> <dt className="text-sm leading-5 font-medium text-gray-500">Kills / Assists</dt>
Kills / Assists
</dt>
<dd className="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2"> <dd className="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{player.stats.kills} / {player.stats.assist} {player.stats.killsTG} / {player.stats.assistTG}
</dd>
</div>
<div className="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt className="text-sm leading-5 font-medium text-gray-500">MAs</dt>
<dd className="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{player.stats.totalMATG}
</dd>
</div>
<div className="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt className="text-sm leading-5 font-medium text-gray-500">Flag Grabs / Caps</dt>
<dd className="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{player.stats.flagGrabsTG} / {player.stats.flagCapsTG}
</dd> </dd>
</div> </div>
<div className="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"> <div className="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt className="text-sm leading-5 font-medium text-gray-500"> <dt className="text-sm leading-5 font-medium text-gray-500">
MAs Flag Defends / Capper Kills / Returns
</dt> </dt>
<dd className="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2"> <dd className="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{player.stats.totalMA} {player.stats.flagDefendsTG} / {player.stats.carrierKillsTG} /{' '}
</dd> {player.stats.flagReturnsTG}
</div>
<div className="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt className="text-sm leading-5 font-medium text-gray-500">
Flag Grabs / Caps
</dt>
<dd className="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{player.stats.flagGrabs} / {player.stats.flagCaps}
</dd>
</div>
<div className="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt className="text-sm leading-5 font-medium text-gray-500">
Flag Defends / Carrier Kills / Returns
</dt>
<dd className="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{player.stats.flagDefends} / {player.stats.carrierKills} / {player.stats.flagReturns}
</dd> </dd>
</div> </div>
</dl> </dl>
</div> </div>
</div> </div>
</div>; </div>
} );
};
export default function Game(props) { export default function Game(props) {
return ( return (
<Layout title={props.pageTitle.name} gametype={props.pageTitle.gametype}> <Layout title={props.pageTitle.name} gametype={props.pageTitle.gametype}>
<div className="mt-2 grid gap-5 max-w-lg mx-auto lg:grid-cols-3 lg:max-w-none"> <div className="mt-2 grid gap-5 max-w-lg mx-auto lg:grid-cols-3 lg:max-w-none">
{props.gameInfo.map((game, index) => PlayerRow(game, index))} {props.gameInfo.map((game, index) => PlayerRow(game, index))}
</div> </div>
@ -166,5 +167,5 @@ export default function Game(props) {
<div className="py-10 px-10"><code> {JSON.stringify(props.gameInfo)}</code></div> <div className="py-10 px-10"><code> {JSON.stringify(props.gameInfo)}</code></div>
</div> */} </div> */}
</Layout> </Layout>
) );
} }

View file

@ -1,25 +1,24 @@
import React from 'react' import React from 'react';
import { InertiaLink } from '@inertiajs/inertia-react' import { InertiaLink } from '@inertiajs/inertia-react';
import Layout from '@/Shared/Layout' import Layout from '@/Shared/Layout';
import GameTypesPlayedCols from '@/Components/GameTypesPlayedCols' import GameTypesPlayedCols from '@/Components/GameTypesPlayedCols';
import {Radar, RadarChart, PolarGrid, PolarAngleAxis, PolarRadiusAxis} from 'recharts'
import { Radar, RadarChart, PolarGrid, PolarAngleAxis, PolarRadiusAxis } from 'recharts';
const returnWeaponTotals = (statTotals) => { const returnWeaponTotals = (statTotals) => {
let totals = [ let totals = [
{ weapon: 'Chaingun', val: statTotals["chainKills"]}, { weapon: 'Chaingun', val: statTotals['cgKillsTG'] },
{ weapon: 'Disc', val: statTotals["discKills"] }, { weapon: 'Disc', val: statTotals['discKillsTG'] },
{ weapon: 'Grenade Launcher', val: statTotals["grenadeKills"]}, { weapon: 'Grenade Launcher', val: statTotals['grenadeKillsTG'] },
{ weapon: 'Shocklance', val: statTotals["shockLanceKills"]}, { weapon: 'Shocklance', val: statTotals['shockKillsTG'] },
{ weapon: 'Laser Rifle', val: statTotals["laserKills"]}, { weapon: 'Laser Rifle', val: statTotals['laserKillsTG'] },
{ weapon: 'Blaster', val: statTotals["blasterKills"]}, { weapon: 'Blaster', val: statTotals['blasterKillsTG'] },
{ weapon: 'Plasma Rifle', val: statTotals["plasmaKills"]}, { weapon: 'Plasma Rifle', val: statTotals['plasmaKillsTG'] },
{ weapon: 'Mortar Launcher', val: statTotals["mortarKills"]}, { weapon: 'Mortar Launcher', val: statTotals['mortarKillsTG'] },
{ weapon: 'Missile Launcher', val: statTotals["missileKills"]}, { weapon: 'Missile Launcher', val: statTotals['missileKillsTG'] },
{ weapon: 'Hand Grenade', val: statTotals["hGrenadeKills"]}, { weapon: 'Hand Grenade', val: statTotals['hGrenadeKillsTG'] },
{ weapon: 'Mine', val: statTotals["mineKills"]}, { weapon: 'Mine', val: statTotals['mineKillsTG'] },
{ weapon: 'Satchel', val: statTotals["satchelChargeKills"]}, { weapon: 'Satchel', val: statTotals['satchelKillsTG'] }
]; ];
// dont return if the val is 0 // dont return if the val is 0
@ -28,22 +27,28 @@ const returnWeaponTotals = (statTotals) => {
}); });
}; };
const GameCard = (player, index) => { const GameCard = (player, index) => {
// only display card if player has score // only display card if player has score
// if (Number(player.stats.score) <= 0){return} // if (Number(player.stats.score) <= 0){return}
return <div key={index} className="bg-white shadow overflow-hidden sm:rounded-lg mb-5"> return (
<div key={index} className="bg-white shadow overflow-hidden sm:rounded-lg mb-5">
<div className="px-4 py-5 border-b border-gray-200 sm:px-6"> <div className="px-4 py-5 border-b border-gray-200 sm:px-6">
<div className="-ml-4 -mt-4 flex justify-between items-center flex-wrap sm:flex-no-wrap"> <div className="-ml-4 -mt-4 flex justify-between items-center flex-wrap sm:flex-no-wrap">
<div className="ml-4 mt-4"> <div className="ml-4 mt-4">
<h3 className="text-lg leading-6 font-medium text-gray-900"> <h3 className="text-lg leading-6 font-medium text-gray-900">
<InertiaLink href={`/game/${player.game_id}`} className="text-indigo-600 hover:text-indigo-500 transition duration-150 ease-in-out">{player.stats.map}</InertiaLink> <InertiaLink
href={`/game/${player.game_id}`}
className="text-indigo-600 hover:text-indigo-500 transition duration-150 ease-in-out"
>
{player.stats.map}
</InertiaLink>
</h3> </h3>
<p className="mt-1 max-w-2xl text-sm leading-5 text-gray-500"> <p className="mt-1 max-w-2xl text-sm leading-5 text-gray-500">
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium leading-4 bg-gray-100 text-gray-800">{player.stats.dateStamp}</span> <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium leading-4 bg-gray-100 text-gray-800">
{player.stats.dateStamp}
</span>
</p> </p>
</div> </div>
<div className="ml-4 mt-4 flex-shrink-0"> <div className="ml-4 mt-4 flex-shrink-0">
@ -54,88 +59,87 @@ const GameCard = (player, index) => {
</div> </div>
</div> </div>
<div className="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"> <div className="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt className="text-sm leading-5 font-medium text-gray-500"> <dt className="text-sm leading-5 font-medium text-gray-500">Total Score</dt>
Total Score <dd className="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">{player.stats.scoreTG}</dd>
</dt>
<dd className="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{player.stats.score}
</dd>
</div> </div>
<div className="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"> <div className="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt className="text-sm leading-5 font-medium text-gray-500"> <dt className="text-sm leading-5 font-medium text-gray-500">Kills / Assists</dt>
Kills / Assists
</dt>
<dd className="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2"> <dd className="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{player.stats.kills} / {player.stats.assist} {player.stats.killsTG} / {player.stats.assistTG}
</dd> </dd>
</div> </div>
<div className="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"> <div className="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt className="text-sm leading-5 font-medium text-gray-500"> <dt className="text-sm leading-5 font-medium text-gray-500">MAs</dt>
MAs <dd className="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">{player.stats.totalMATG}</dd>
</dt>
<dd className="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{player.stats.totalMA}
</dd>
</div> </div>
<div className="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"> <div className="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt className="text-sm leading-5 font-medium text-gray-500"> <dt className="text-sm leading-5 font-medium text-gray-500">Flag Grabs / Caps</dt>
Flag Grabs / Caps
</dt>
<dd className="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2"> <dd className="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{player.stats.flagGrabs} / {player.stats.flagCaps} {player.stats.flagGrabsTG} / {player.stats.flagCapsTG}
</dd> </dd>
</div> </div>
<div className="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"> <div className="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt className="text-sm leading-5 font-medium text-gray-500"> <dt className="text-sm leading-5 font-medium text-gray-500">Flag Defends / Carrier Kills / Returns</dt>
Flag Defends / Carrier Kills / Returns
</dt>
<dd className="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2"> <dd className="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{player.stats.flagDefends} / {player.stats.carrierKills} / {player.stats.flagReturns} {player.stats.flagDefendsTG} / {player.stats.carrierKillsTG} / {player.stats.flagReturnsTG}
</dd> </dd>
</div> </div>
</div>; </div>
} );
};
export default function Player(props) { export default function Player(props) {
return ( return (
<Layout title={props.pageTitle}> <Layout title={props.pageTitle}>
<div className="md:grid md:grid-cols-4 md:gap-6"> <div className="md:grid md:grid-cols-4 md:gap-6">
<div className="md:col-span-1"> <div className="md:col-span-1">
<div className="px-4 sm:px-0"> <div className="px-4 sm:px-0">
<h3 className="text-lg font-medium leading-6 text-gray-900">Aggregate</h3> <h3 className="text-lg font-medium leading-6 text-gray-900">Aggregate</h3>
<p className="mt-1 text-sm leading-5 text-gray-500"> <p className="mt-1 text-sm leading-5 text-gray-500">Stat Totals</p>
Stat Totals
</p>
</div> </div>
</div> </div>
<div className="mt-5 md:mt-0 md:col-span-3"> <div className="mt-5 md:mt-0 md:col-span-3">
<div className="bg-white shadow overflow-hidden sm:rounded-lg"> <div className="bg-white shadow overflow-hidden sm:rounded-lg">
<div className="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"> <div className="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<div className="text-sm leading-5 font-medium text-gray-500"> <div className="text-sm leading-5 font-medium text-gray-500">Games Played</div>
Games Played
</div>
<div className="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2"> <div className="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
<GameTypesPlayedCols ctf={props.playerData.player.total_games_ctfgame} <GameTypesPlayedCols
ctf={props.playerData.player.total_games_ctfgame}
dm={props.playerData.player.total_games_dmgame} dm={props.playerData.player.total_games_dmgame}
lak={props.playerData.player.total_games_lakrabbitgame} lak={props.playerData.player.total_games_lakrabbitgame}
spawnctf={props.playerData.player.total_games_sctfgame}/> spawnctf={props.playerData.player.total_games_sctfgame}
/>
</div> </div>
</div> </div>
<div className="bg-gray-50 px-4 py-5"> <div className="bg-gray-50 px-4 py-5">
<dt className="text-sm leading-5 font-medium text-gray-500"> <dt className="text-sm leading-5 font-medium text-gray-500">Weapon Usage</dt>
Weapon Usage
</dt>
<dd className="mt-1 text-sm leading-5 text-gray-900 flex items-center justify-center"> <dd className="mt-1 text-sm leading-5 text-gray-900 flex items-center justify-center">
<RadarChart cx={300} cy={250} outerRadius={150} width={600} height={500} data={ returnWeaponTotals(props.playerData.totals).length ? returnWeaponTotals(props.playerData.totals) : [{ weapon: 'No Data', val:1}]} className="text-xs"> <RadarChart
cx={300}
cy={250}
outerRadius={150}
width={600}
height={500}
data={
returnWeaponTotals(props.playerData.totals).length ? (
returnWeaponTotals(props.playerData.totals)
) : (
[ { weapon: 'No Data', val: 1 } ]
)
}
className="text-xs"
>
<PolarGrid /> <PolarGrid />
<PolarAngleAxis dataKey="weapon" /> <PolarAngleAxis dataKey="weapon" />
<PolarRadiusAxis /> <PolarRadiusAxis />
<Radar name={props.playerData.player.player_name} dataKey="val" stroke="#8884d8" fill="#8884d8" fillOpacity={0.6}/> <Radar
name={props.playerData.player.player_name}
dataKey="val"
stroke="#8884d8"
fill="#8884d8"
fillOpacity={0.6}
/>
</RadarChart> </RadarChart>
</dd> </dd>
</div> </div>
@ -162,5 +166,5 @@ export default function Player(props) {
<div className="py-10 px-10"><code> {JSON.stringify(props.playerData.stats)}</code></div> <div className="py-10 px-10"><code> {JSON.stringify(props.playerData.stats)}</code></div>
</div> */} </div> */}
</Layout> </Layout>
) );
} }

View file

@ -31,27 +31,54 @@ CREATE TABLE "public"."players" (
); );
DROP TABLE IF EXISTS "public"."games"; DROP TABLE IF EXISTS "public"."games";
CREATE SEQUENCE IF NOT EXISTS games_id_seq;
-- Table Definition -- Table Definition
CREATE TABLE "public"."games" ( CREATE TABLE "public"."games" (
"game_id" numeric NOT NULL UNIQUE,
"map" text NOT NULL,
"datestamp" timestamp NOT NULL,
"gametype" text NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT game_pk PRIMARY KEY (game_id)
);
DROP TABLE IF EXISTS "public"."game_detail";
CREATE SEQUENCE IF NOT EXISTS games_id_seq;
-- Table Definition
CREATE TABLE "public"."game_detail" (
"id" int4 NOT NULL DEFAULT nextval('games_id_seq'::regclass), "id" int4 NOT NULL DEFAULT nextval('games_id_seq'::regclass),
"player_guid" numeric NOT NULL, "player_guid" numeric NOT NULL,
"player_name" text NOT NULL, "player_name" text NOT NULL,
"stat_overwrite" numeric NOT NULL, "stat_overwrite" numeric NOT NULL,
"map" text NOT NULL, "map" text NOT NULL,
"game_id" numeric NOT NULL DEFAULT 0, "game_id" numeric NOT NULL,
"stats" jsonb NOT NULL, "stats" jsonb NOT NULL,
"datestamp" timestamp NOT NULL, "datestamp" timestamp NOT NULL,
"uuid" text NOT NULL UNIQUE, "uuid" text NOT NULL UNIQUE,
"gametype" text NOT NULL, "gametype" text NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT games_pk PRIMARY KEY (id), CONSTRAINT games_pk PRIMARY KEY (id),
FOREIGN KEY (game_id) REFERENCES games (game_id),
FOREIGN KEY (player_guid) REFERENCES players (player_guid) FOREIGN KEY (player_guid) REFERENCES players (player_guid)
); );
CREATE OR REPLACE FUNCTION trigger_set_timestamp() CREATE OR REPLACE FUNCTION trigger_set_timestamp()
RETURNS TRIGGER AS $$ RETURNS TRIGGER AS $$
BEGIN BEGIN