This commit is contained in:
ScrawnyRonnie 2026-01-10 20:01:03 -05:00
parent a64195bbed
commit c53469fbd6
6 changed files with 301 additions and 8 deletions

View file

@ -380,9 +380,16 @@ export async function get_weaponstats_by_avatar(id) {
}
export async function get_avatar(id) {
try {
const avatar = await pool.query('SELECT id, name, faction_id, bep, cep, gender_id, head_id FROM avatar WHERE id=$1', [id])
return avatar.rows[0];
try {
const avatar = await pool.query(
'SELECT a.id, a.name, a.faction_id, a.bep, a.cep, a.gender_id, a.head_id,' +
' o.id AS outfit_id, o.name AS outfit_name' +
' FROM avatar a' +
' LEFT JOIN outfitmember om ON om.avatar_id = a.id' +
' LEFT JOIN outfit o ON o.id = om.outfit_id' +
' WHERE a.id = $1',
[id])
return avatar.rows[0];
} catch (e) {
if (e.code)
e.code = pg_error_inv[e.code]
@ -438,6 +445,92 @@ export async function get_top_kills_byDate() {
}
}
export async function get_top_outfits() {
try {
const outfits = await pool.query(
'WITH OutfitData AS (' +
' SELECT o.id AS outfit_id,' +
' o.faction,' +
' o.name AS outfit_name,' +
' a.id AS leader_id, a.name AS leader_name,' +
' COUNT(om.avatar_id)::int AS members,' +
' (op.points / 100.0)::int AS points' +
' FROM outfit o' +
' JOIN avatar a ON a.id = o.owner_id' +
' LEFT JOIN outfitmember om ON om.outfit_id = o.id' +
' LEFT JOIN outfitpoint_mv op ON op.outfit_id = o.id' +
' GROUP BY o.id, o.faction, o.name, a.name, a.id, op.points' +
') ' +
'SELECT outfit_id, faction, outfit_name, leader_name, leader_id, members, points ' +
'FROM OutfitData ' +
'ORDER BY points DESC')
return outfits.rows;
} catch (e) {
if (e.code)
e.code = pg_error_inv[e.code]
throw e;
}
}
export async function get_outfit(id) {
try {
const outfit = await pool.query(
'WITH OutfitData AS (' +
' SELECT o.id AS outfit_id, o.faction, o.name AS outfit_name, o.created,' +
' a.id AS leader_id,' +
' a.name AS leader_name,' +
' COUNT(om.avatar_id)::int AS members,' +
' (op.points / 100.0)::int AS points' +
' FROM outfit o ' +
' JOIN avatar a ON a.id = o.owner_id ' +
' LEFT JOIN outfitmember om ON om.outfit_id = o.id ' +
' LEFT JOIN outfitpoint_mv op ON op.outfit_id = o.id ' +
' WHERE o.id = $1 ' +
' GROUP BY o.created, o.id, o.faction, o.name, a.id, a.name, op.points) ' +
'SELECT outfit_id, faction, outfit_name, leader_id, leader_name, members, points, created ' +
'FROM OutfitData',
[id])
return outfit.rows[0];
} catch (e) {
if (e.code)
e.code = pg_error_inv[e.code];
throw e;
}
}
export async function get_outfit_members(id) {
try {
const members = await pool.query(
'SELECT av.id AS avatar_id, av.bep, av.cep,' +
' av.name AS avatar_name,' +
' om.rank AS rank_num,' +
' CASE om.rank ' +
' WHEN 0 THEN COALESCE(o.rank0, \'Fodder\') ' +
' WHEN 1 THEN COALESCE(o.rank1, \'Soldier\') ' +
' WHEN 2 THEN COALESCE(o.rank2, \'Commando\') ' +
' WHEN 3 THEN COALESCE(o.rank3, \'Master at Arms\') ' +
' WHEN 4 THEN COALESCE(o.rank4, \'Tactical Officer\') ' +
' WHEN 5 THEN COALESCE(o.rank5, \'Strategic Officer\') ' +
' WHEN 6 THEN COALESCE(o.rank6, \'Chief Officer\') ' +
' WHEN 7 THEN COALESCE(o.rank7, \'Outfit Leader\') ' +
' END AS rank_title,' +
' COALESCE((op.points / 100.0)::int, 0) AS points,' +
' om.created AS joined' +
' FROM outfitmember om ' +
' JOIN avatar av ON av.id = om.avatar_id ' +
' JOIN outfit o ON o.id = om.outfit_id ' +
' LEFT JOIN outfitpoint op ON op.avatar_id = om.avatar_id ' +
' WHERE om.outfit_id = $1 ' +
' ORDER BY points DESC, avatar_name ASC',
[id])
return members.rows;
} catch (e) {
if (e.code)
e.code = pg_error_inv[e.code];
throw e;
}
}
export async function get_characters_by_account(account_id) {
try {
const characters = await pool.query('SELECT a.*, b.* FROM avatar a LEFT JOIN avatarmodepermission b ON a.id = b.avatar_id WHERE a.account_id = $1 AND a.deleted = false', [account_id])

View file

@ -78,6 +78,38 @@ api.get('/top_kills_byDate', async (req, res, next) => {
}
});
api.get('/top_outfits', async (req, res, next) => {
try {
const outfits = await db.get_top_outfits();
res.status(200).json({ outfits: outfits });
} catch (e) {
console.log(e);
res.status(500).json({ message: 'error' });
}
});
api.get('/outfit/:outfit', async (req, res, next) => {
const fit = req.params.outfit
try {
const outfit = await db.get_outfit(fit);
res.status(200).json(outfit);
} catch (e) {
console.log(e);
res.status(500).json({ message: 'error' });
}
});
api.get('/outfit/:outfit/members', async (req, res, next) => {
const fit = req.params.outfit
try {
const members = await db.get_outfit_members(fit);
res.status(200).json({ members: members });
} catch (e) {
console.log(e);
res.status(500).json({ message: 'error' });
}
});
api.get('/weaponstats/:avatar', async (req, res, next) => {
const avatar = req.params.avatar;
@ -102,7 +134,9 @@ api.get('/avatar/:avatar', async (req, res, next) => {
cep: avatarData.cep,
faction: avatarData.faction_id,
gender: avatarData.gender_id,
head: avatarData.head_id
head: avatarData.head_id,
outfit: avatarData.outfit_name,
outfit_id: avatarData.outfit_id
});
} catch (e) {
console.log(e);

View file

@ -28,6 +28,7 @@ import AdminPanel from './views/AdminPanel.svelte';
import CharacterList from './views/CharacterList.svelte';
import Leaderboard from './views/Leaderboard.svelte';
import Avatar from './views/Avatar.svelte'
import Outfit from './views/Outfit.svelte'
// Defined by webpack
let APP_VERSION = __VERSION__;
@ -106,6 +107,7 @@ page("/avatar/:id", setRoute(Avatar));
page("/admin", setRoute(AdminPanel));
page("/profile", setRoute(Profile, true));
page("/user/:id", setRoute(Profile, true));
page("/outfit/:id", setRoute(Outfit));
if (process.env.NODE_ENV !== 'production') {
console.log("Development mode active");
const cc = await import('./views/components.svelte');

View file

@ -138,7 +138,11 @@
<tr>
<td>
<span style="color:lightgrey;">Character Name:</span> {avatar.name}<br>
<span style="color:lightgrey;">Empire:</span> {getFactionName(avatar.faction)}
<span style="color:lightgrey;">Empire:</span> {getFactionName(avatar.faction)}<br>
<span style="color:lightgrey;">Outfit:</span>
{#if avatar.outfit_id}
<a href="/outfit/{avatar.outfit_id}">{avatar.outfit}</a>
{/if}<br>
</td>
<td><img height="60" src={getFactionIcon(avatar.faction)} alt={avatar.faction}/></td>
</tr>

View file

@ -8,12 +8,14 @@
get_BEPleaderboard();
get_CEPleaderboard();
get_kills();
get_outfits();
get_topDateKills();
});
let bepPlayers = [];
let cepPlayers = [];
let kills = [];
let outfits = [];
let dateKills = [];
let alert;
@ -21,7 +23,7 @@
try {
const resp = await axios.get("/api/char_stats_bep/0");
const stats = resp.data;
bepPlayers = stats.players;
bepPlayers = stats.players.filter(p => p.bep > 14999);
// Reset alert message if needed
alert.message("");
} catch (e) {
@ -34,7 +36,7 @@
try {
const resp = await axios.get("/api/char_stats_cep/0");
const stats = resp.data;
cepPlayers = stats.players;
cepPlayers = stats.players.filter(p => p.cep > 0);
// Reset alert message if needed
alert.message("");
} catch (e) {
@ -56,6 +58,19 @@
}
}
async function get_outfits() {
try {
const resp = await axios.get("/api/top_outfits");
const stats = resp.data;
outfits = stats.outfits;
// Reset alert message if needed
alert.message("");
} catch (e) {
console.log(e);
alert.message("Failed to fetch stats from server");
}
}
async function get_topDateKills() {
try {
const resp = await axios.get("/api/top_kills_byDate");
@ -86,6 +101,9 @@
<li class="nav-item">
<a class="nav-link" id="cr-tab" data-toggle="tab" href="#cr" role="tab" aria-controls="cr" aria-selected="false">Command Rank</a>
</li>
<li class="nav-item">
<a class="nav-link" id="outfits-tab" data-toggle="tab" href="#outfits" role="tab" aria-controls="outfits" aria-selected="false">Outfits</a>
</li>
<li class="nav-item">
<a class="nav-link" id="top-tab" data-toggle="tab" href="#top" role="tab" aria-controls="top" aria-selected="false">Top X</a>
</li>
@ -166,7 +184,32 @@
</table>
</div>
<div class="tab-pane" id="top" role="tabpanel" aria-labelledby="top-tab">
<div class="tab-pane" id="outfits" role="tabpanel" aria-labelledby="outfits-tab">
<table class="table table-sm table-dark table-responsive-md table-striped table-hover">
<thead class="thead-light">
<th>#</th>
<th>Name</th>
<th>Leader</th>
<th>Members</th>
<th>Points</th>
</thead>
<tbody>
{#each outfits as outfit, $index}
<tr>
<td>{$index + 1}</td>
<td>
<img height="24" src={getFactionIcon(outfit.faction)} alt={outfit.faction} />
<a href="/outfit/{outfit.outfit_id}">{outfit.outfit_name}</a></td>
<td><a href="/avatar/{outfit.leader_id}">{outfit.leader_name}</a></td>
<td>{outfit.members}</td>
<td>{outfit.points}</td>
</tr>
{/each}
</tbody>
</table>
</div>
<div class="tab-pane" id="top" role="tabpanel" aria-labelledby="top-tab">
<span style="color:lightgrey; text-align:center;">Top 50 Characters Most Daily Kills</span>
<table class="table table-sm table-dark table-responsive-md table-striped table-hover">
<thead class="thead-light">

117
app/views/Outfit.svelte Normal file
View file

@ -0,0 +1,117 @@
<script>
import { onMount } from 'svelte';
import axios from 'axios'
import Alert from '../components/Alert'
import { bepRanges, cepRanges, calculateBr, calculateCr, getFactionIcon, getFactionName } from '../statFunctions';
onMount(() => {
get_outfit();
get_outfit_members();
});
export let params;
let alert;
let outfit = {};
let outfitMembers = [];
let url = params.id || outfit.id
const outfitUrl = "/api/outfit/"+url
const outfitMembersUrl = "/api/outfit/"+url+"/members"
async function get_outfit() {
try {
const resp = await axios.get(outfitUrl);
outfit = resp.data
// Reset alert message if needed
alert.message("");
} catch (e) {
console.log(e);
alert.message("Failed to fetch stats from server");
}
}
async function get_outfit_members() {
try {
const resp = await axios.get(outfitMembersUrl);
const stats = resp.data;
outfitMembers = stats.members
// Reset alert message if needed
alert.message("");
} catch (e) {
console.log(e);
alert.message("Failed to fetch stats from server");
}
}
function stripColorCode(title) {
if (!title) return "";
if (title.startsWith("\\#")) {
return title.slice(8);
}
return title;
}
</script>
<svelte:head>
<title>Outfit Stats</title>
</svelte:head>
<table width="70%">
<tbody>
<tr>
<td width="%50" valign="top">
<table>
<tbody>
<tr>
<td>
<img height="60" src={getFactionIcon(outfit.faction)} alt={outfit.faction}/><br>
<span style="color:lightgrey;">Outfit Name:</span> {outfit.outfit_name}<br>
<span style="color:lightgrey;">Created:</span>
{new Date(outfit.created).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
})}<br>
<span style="color:lightgrey;">Leader:</span> <a href="/avatar/{outfit.leader_id}">{outfit.leader_name}</a><br>
<span style="color:lightgrey;">Points:</span> {outfit.points}<br>
<span style="color:lightgrey;">Members:</span> {outfit.members}<br>
</td>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<br>
<br>
<table class="table table-sm table-dark table-responsive-md table-striped table-hover">
<thead class="thead-light">
<th></th>
<th>Name</th>
<th>Title</th>
<th>Points</th>
<th>BR</th>
<th>CR</th>
<th>Joined</th>
</thead>
<tbody>
{#each outfitMembers as member, $index}
<tr>
<td>{$index + 1}</td>
<td><a href="/avatar/{member.avatar_id}">{member.avatar_name}</a></td>
<td>{stripColorCode(member.rank_title)}</td>
<td>{member.points}</td>
<td>{calculateBr(member.bep)}</td>
<td>{calculateCr(member.cep)}</td>
<td>
{new Date(member.joined).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
})}
</td>
</tr>
{/each}
</tbody>
</table>