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
app/t2-stat-parser/serverStats/stats
app/t2-stat-parser/serverStats/mlData
app/t2-stat-parser/serverStats/lData
docker-compose.deploy.yml
traefik.yml

View file

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

View file

@ -59,12 +59,12 @@ type Game struct {
dbStatOverWrite int `db.players:"stat_overwrite"`
statOverWrite int
gameMap string `db.games:"map"`
gameID int `db.games:"game_id"`
gameType string `db.games:"gametype"`
dateStamp string `db.games:"datestamp"`
stats string `db.games:"stats"`
uuid string `db.games:"uuid"`
gameMap string `db.game_detail:"map"`
gameID int `db.game_detail:"game_id"`
gameType string `db.game_detail:"gametype"`
dateStamp string `db.game_detail:"datestamp"`
stats string `db.game_detail:"stats"`
uuid string `db.game_detail:"uuid"`
}
func initParser() {
@ -265,7 +265,10 @@ func parseStatOverWriteLine(g Game, mStatLine map[string][]string, arrPosition i
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")
// insert game stat
addPlayerGameStat(g, strings.ToLower(gt))
@ -283,6 +286,28 @@ func rowExists(query string, args ...interface{}) bool {
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) {
check := rowExists("select player_guid from players where player_guid = $1", g.playerGUID)
if !check {
@ -292,8 +317,8 @@ func checkPlayer(g Game) {
}
}
func checkGameEntry(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)
func checkGameEntryForPlayer(g Game) bool {
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 {
return false
} else {
@ -341,7 +366,7 @@ func addPlayerGameStat(g Game, gt string) {
if g.dateStamp != "0" {
// Insert new stat line
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)
if err != nil {
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 {
// /games
async index({ inertia }) {
const pageTitle = `Latest Games`;
// const gamesQry = await Database.raw(`
@ -17,55 +15,50 @@ class GameController {
// 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(`
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
`);
// // filter out duplicate game_ids (https://dev.to/marinamosti/removing-duplicates-in-an-array-of-objects-in-js-with-sets-3fep)
// const games = gamesQry.rows.reduce((game, current) => {
// filter out duplicate game_ids (https://dev.to/marinamosti/removing-duplicates-in-an-array-of-objects-in-js-with-sets-3fep)
const games = gamesQry.rows.reduce((game, current) => {
// const x = game.find(item => item.game_id === current.game_id);
// if (!x) {
// return game.concat([current]);
// } else {
// return game;
// }
// }, []);
const x = game.find(item => item.game_id === current.game_id);
if (!x) {
return game.concat([current]);
} else {
return game;
}
}, []);
const games = await Database.from('games').orderBy('game_id', 'desc').limit(200);
// const CTFgames = await Database.from('games').where({gametype: 'CTFGame'}).orderBy('game_id', 'desc').limit(120);
// const LAKgames = await Database.from('games').where({gametype: 'LakRabbitGame'}).orderBy('game_id', 'desc').limit(120);
// 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
async game({ inertia, request }) {
const gameInfo = await Database.from('games')
.distinct('game_id',
'map',
'player_name',
'player_guid',
'gametype',
'stats',
'datestamp')
const gameInfo = await Database.from('game_detail')
.select('game_id', 'map', 'player_name', 'player_guid', 'gametype', 'stats', 'datestamp')
.where({ game_id: request.params.game_id })
.orderByRaw("stats->>'scoreTG' desc");
const pageTitle = {
name: gameInfo[0]['map'],
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,84 +1,82 @@
'use strict'
'use strict';
const Database = use('Database')
const Database = use('Database');
class PlayerController {
// Main Players List
async index({ inertia }) {
const pageTitle = "All Players"
const pageTitle = 'All Players';
const players = await Database.table('players')
.distinct('player_guid',
.distinct(
'player_guid',
'player_name',
'total_games_ctfgame',
'total_games_dmgame',
'total_games_lakrabbitgame',
'total_games_sctfgame',
'updated_at')
'updated_at'
)
.groupBy('player_guid')
.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
async player({ inertia, request }) {
const playerInfo = await Database.from('players')
.distinct('player_guid',
.distinct(
'player_guid',
'player_name',
'total_games_ctfgame',
'total_games_dmgame',
'total_games_lakrabbitgame',
'total_games_sctfgame')
'total_games_sctfgame'
)
.where({ player_guid: request.params.player_guid })
.limit(50)
.limit(50);
const playerStatData = await Database.from('games')
.select('game_id',
'gametype',
'stats')
const playerStatData = await Database.from('game_detail')
.select('game_id', 'gametype', 'stats')
.where({ player_guid: request.params.player_guid })
.orderBy('game_id', 'desc')
.limit(50)
.limit(50);
// Dynamically generate and sum the stats object
let playerStatTotals = {},
statKeys = Object.keys(playerStatData[0].stats)
statKeys = Object.keys(playerStatData[0].stats);
for(let i = 0 ; i < statKeys.length; i++) {
if(statKeys[i] === "map" ||
statKeys[i] === "dateStamp" ||
statKeys[i] === "timeDayMonth" ){continue;}
for (let i = 0; i < statKeys.length; i++) {
if (statKeys[i] === 'map' || statKeys[i] === 'dateStamp' || statKeys[i] === 'timeDayMonth') {
continue;
}
playerStatTotals[statKeys[i]] = 0;
}
// Loop through the playerStatsData query from the DB
playerStatData.map(statLine => {
playerStatData.map((statLine) => {
// 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}`);
// If the stat item exists, add it -- if not create a new key in playerStatTotals
if(playerStatTotals.hasOwnProperty(key) === true){
if (playerStatTotals.hasOwnProperty(key) === true) {
playerStatTotals[key] = playerStatTotals[key] + Number(value);
}else{
} else {
playerStatTotals[key] = Number(value);
}
}
})
});
let playerData = {
player: playerInfo[0],
stats: playerStatData,
totals: playerStatTotals
}
};
const pageTitle = playerData.player.player_name
return inertia.render('Players/Player', { pageTitle, playerData }, { edgeVar: 'server-variable' })
const pageTitle = playerData.player.player_name;
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 { InertiaLink } from '@inertiajs/inertia-react'
import Layout from '@/Shared/Layout'
import React, { PureComponent } from 'react';
import { InertiaLink } from '@inertiajs/inertia-react';
import Layout from '@/Shared/Layout';
import {PieChart, Pie, Sector} from 'recharts'
import { PieChart, Pie, Sector } from 'recharts';
const renderActiveShape = (props) => {
const RADIAN = Math.PI / 180;
const { cx, cy, midAngle, innerRadius, outerRadius, startAngle, endAngle,
fill, payload, percent, value } = props;
const { cx, cy, midAngle, innerRadius, outerRadius, startAngle, endAngle, fill, payload, percent, value } = props;
const sin = Math.sin(-RADIAN * midAngle);
const cos = Math.cos(-RADIAN * midAngle);
const sx = cx + (outerRadius + 10) * cos;
@ -20,7 +19,9 @@ const renderActiveShape = (props) => {
return (
<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
cx={cx}
cy={cy}
@ -39,8 +40,8 @@ const renderActiveShape = (props) => {
outerRadius={outerRadius + 10}
fill="#6761d6"
/>
<path d={`M${sx},${sy}L${mx},${my}L${ex},${ey}`} stroke={fill} fill="none"/>
<circle cx={ex} cy={ey} r={2} fill={fill} stroke="none"/>
<path d={`M${sx},${sy}L${mx},${my}L${ex},${ey}`} stroke={fill} fill="none" />
<circle cx={ex} cy={ey} r={2} fill={fill} stroke="none" />
<text x={ex + (cos >= 0 ? 1 : -1) * 12} y={ey} textAnchor={textAnchor} fill="#333">{`${value}`}</text>
<text x={ex + (cos >= 0 ? 1 : -1) * 12} y={ey} dy={18} textAnchor={textAnchor} fill="#999">
{`(${(percent * 100).toFixed(2)}%)`}
@ -51,14 +52,13 @@ const renderActiveShape = (props) => {
export class TwoLevelPieChart extends PureComponent {
state = {
activeIndex: ((this.props.data.oScore >= this.props.data.dScore) ? 0 : 1 ),
data: [{name: 'Offense', value: this.props.data.oScore},
{name: 'Defense', value: this.props.data.dScore}]
activeIndex: this.props.data.oScore >= this.props.data.dScore ? 0 : 1,
data: [ { name: 'Offense', value: this.props.data.oScore }, { name: 'Defense', value: this.props.data.dScore } ]
};
onPieEnter = (data, index) => {
this.setState({
activeIndex: index,
activeIndex: index
});
};
@ -83,88 +83,89 @@ export class TwoLevelPieChart extends PureComponent {
}
}
const PlayerRow = (player, index) => {
// 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="px-4 py-5 border-b border-gray-200 sm:px-6">
<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>
</div>
<div>
<dl>
<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">
Total Score
</dt>
<dt className="text-sm leading-5 font-medium text-gray-500">Total Score</dt>
<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>
</div>
<div className="bg-gray-50 flex items-center justify-center">
{
(player.gametype == "CTFGame" || player.gametype == "SCtFGame") ? <TwoLevelPieChart data={{
oScore: Number(player.stats.offenseScore[0]),
dScore: Number(player.stats.defenseScore[0])}
}/>
: ''
}
{player.gametype == 'CTFGame' || player.gametype == 'SCtFGame' ? (
<TwoLevelPieChart
data={{
oScore: Number(player.stats.offenseScoreTG[0]),
dScore: Number(player.stats.defenseScoreTG[0])
}}
/>
) : (
''
)}
</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">
Kills / Assists
</dt>
<dt className="text-sm leading-5 font-medium text-gray-500">Kills / Assists</dt>
<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>
</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
Flag Defends / Capper Kills / Returns
</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 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}
{player.stats.flagDefendsTG} / {player.stats.carrierKillsTG} /{' '}
{player.stats.flagReturnsTG}
</dd>
</div>
</dl>
</div>
</div>
</div>;
}
</div>
);
};
export default function Game(props) {
return (
<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">
{ props.gameInfo.map((game, index) => PlayerRow(game, index)) }
{props.gameInfo.map((game, index) => PlayerRow(game, index))}
</div>
{/*
{/*
<div className="bg-white shadow overflow-hidden sm:rounded-md">
<div className="py-10 px-10"><code> {JSON.stringify(props.gameInfo)}</code></div>
</div> */}
</Layout>
)
);
}

View file

@ -1,141 +1,145 @@
import React from 'react'
import { InertiaLink } from '@inertiajs/inertia-react'
import Layout from '@/Shared/Layout'
import GameTypesPlayedCols from '@/Components/GameTypesPlayedCols'
import {Radar, RadarChart, PolarGrid, PolarAngleAxis, PolarRadiusAxis} from 'recharts'
import React from 'react';
import { InertiaLink } from '@inertiajs/inertia-react';
import Layout from '@/Shared/Layout';
import GameTypesPlayedCols from '@/Components/GameTypesPlayedCols';
import { Radar, RadarChart, PolarGrid, PolarAngleAxis, PolarRadiusAxis } from 'recharts';
const returnWeaponTotals = (statTotals) => {
let totals = [
{ weapon: 'Chaingun', val: statTotals["chainKills"]},
{ weapon: 'Disc', val: statTotals["discKills"] },
{ weapon: 'Grenade Launcher', val: statTotals["grenadeKills"]},
{ weapon: 'Shocklance', val: statTotals["shockLanceKills"]},
{ weapon: 'Laser Rifle', val: statTotals["laserKills"]},
{ weapon: 'Blaster', val: statTotals["blasterKills"]},
{ weapon: 'Plasma Rifle', val: statTotals["plasmaKills"]},
{ weapon: 'Mortar Launcher', val: statTotals["mortarKills"]},
{ weapon: 'Missile Launcher', val: statTotals["missileKills"]},
{ weapon: 'Hand Grenade', val: statTotals["hGrenadeKills"]},
{ weapon: 'Mine', val: statTotals["mineKills"]},
{ weapon: 'Satchel', val: statTotals["satchelChargeKills"]},
{ weapon: 'Chaingun', val: statTotals['cgKillsTG'] },
{ weapon: 'Disc', val: statTotals['discKillsTG'] },
{ weapon: 'Grenade Launcher', val: statTotals['grenadeKillsTG'] },
{ weapon: 'Shocklance', val: statTotals['shockKillsTG'] },
{ weapon: 'Laser Rifle', val: statTotals['laserKillsTG'] },
{ weapon: 'Blaster', val: statTotals['blasterKillsTG'] },
{ weapon: 'Plasma Rifle', val: statTotals['plasmaKillsTG'] },
{ weapon: 'Mortar Launcher', val: statTotals['mortarKillsTG'] },
{ weapon: 'Missile Launcher', val: statTotals['missileKillsTG'] },
{ weapon: 'Hand Grenade', val: statTotals['hGrenadeKillsTG'] },
{ weapon: 'Mine', val: statTotals['mineKillsTG'] },
{ weapon: 'Satchel', val: statTotals['satchelKillsTG'] }
];
// dont return if the val is 0
return totals.filter(function (el) {
return totals.filter(function(el) {
return el.val > 0;
});
};
const GameCard = (player, index) => {
// only display card if player has score
// 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="-ml-4 -mt-4 flex justify-between items-center flex-wrap sm:flex-no-wrap">
<div className="ml-4 mt-4">
<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>
<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>
</div>
<div className="ml-4 mt-4 flex-shrink-0">
<span className="inline-flex items-center px-3 py-0.5 rounded-full text-xs font-medium leading-5 bg-indigo-100 text-indigo-800">
{ player.gametype }
{player.gametype}
</span>
</div>
</div>
</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">
Total Score
</dt>
<dd className="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{player.stats.score}
</dd>
<dt className="text-sm leading-5 font-medium text-gray-500">Total Score</dt>
<dd className="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">{player.stats.scoreTG}</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">
Kills / Assists
</dt>
<dt className="text-sm leading-5 font-medium text-gray-500">Kills / Assists</dt>
<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.totalMA}
</dd>
<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>
<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}
{player.stats.flagGrabsTG} / {player.stats.flagCapsTG}
</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>
<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}
{player.stats.flagDefendsTG} / {player.stats.carrierKillsTG} / {player.stats.flagReturnsTG}
</dd>
</div>
</div>;
}
</div>
);
};
export default function Player(props) {
return (
<Layout title={props.pageTitle}>
<div className="md:grid md:grid-cols-4 md:gap-6">
<div className="md:col-span-1">
<div className="px-4 sm:px-0">
<h3 className="text-lg font-medium leading-6 text-gray-900">Aggregate</h3>
<p className="mt-1 text-sm leading-5 text-gray-500">
Stat Totals
</p>
<p className="mt-1 text-sm leading-5 text-gray-500">Stat Totals</p>
</div>
</div>
<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 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">
Games Played
</div>
<div className="text-sm leading-5 font-medium text-gray-500">Games Played</div>
<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}
lak={props.playerData.player.total_games_lakrabbitgame}
spawnctf={props.playerData.player.total_games_sctfgame}/>
spawnctf={props.playerData.player.total_games_sctfgame}
/>
</div>
</div>
<div className="bg-gray-50 px-4 py-5">
<dt className="text-sm leading-5 font-medium text-gray-500">
Weapon Usage
</dt>
<dt className="text-sm leading-5 font-medium text-gray-500">Weapon Usage</dt>
<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 />
<PolarAngleAxis dataKey="weapon" />
<PolarRadiusAxis/>
<Radar name={props.playerData.player.player_name} dataKey="val" stroke="#8884d8" fill="#8884d8" fillOpacity={0.6}/>
<PolarRadiusAxis />
<Radar
name={props.playerData.player.player_name}
dataKey="val"
stroke="#8884d8"
fill="#8884d8"
fillOpacity={0.6}
/>
</RadarChart>
</dd>
</div>
@ -153,14 +157,14 @@ export default function Player(props) {
</div>
</div>
<div className="mt-5 md:mt-0 md:col-span-3">
{ props.playerData.stats.map((player, index) => GameCard(player, index)) }
{props.playerData.stats.map((player, index) => GameCard(player, index))}
</div>
</div>
{/*
{/*
<div className="bg-white shadow overflow-hidden sm:rounded-md">
<div className="py-10 px-10"><code> {JSON.stringify(props.playerData.stats)}</code></div>
</div> */}
</Layout>
)
);
}

View file

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