mirror of
https://github.com/amineo/t2-stat-parser.git
synced 2026-01-20 01:34:47 +00:00
Update to ingest/display new stats format; QoL updates soon...
This commit is contained in:
parent
bb179b82f8
commit
0b5aceca7a
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
7
app/t2-stat-parser/serverStats/dir.txt
Normal file
7
app/t2-stat-parser/serverStats/dir.txt
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
./lData
|
||||
./mlData
|
||||
|
||||
./stats/CTFGame
|
||||
./stats/DMGame
|
||||
./stats/LakRabbitGame
|
||||
./stats/SCTF/Game
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue