mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-08-27 05:36:28 -05:00
Show XP when looking
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import type { LookingLoaderDataGroup } from "~/routes/play/looking";
|
||||
import { layoutIcon } from "~/utils";
|
||||
import { Avatar } from "../Avatar";
|
||||
|
||||
export function GroupMembers({
|
||||
@@ -41,6 +42,29 @@ function Contents({ members }: { members: LookingLoaderDataGroup["members"] }) {
|
||||
{member.MMR && (
|
||||
<div className="play-looking__member-mmr">MMR: {member.MMR}</div>
|
||||
)}
|
||||
{member.peakXP && (
|
||||
<div className="play-looking__member-power">
|
||||
<img
|
||||
src={layoutIcon("top500")}
|
||||
width="18"
|
||||
height="18"
|
||||
title="Peak X Power"
|
||||
/>{" "}
|
||||
{member.peakXP}
|
||||
</div>
|
||||
)}
|
||||
{member.peakLP && (
|
||||
<div className="play-looking__member-power league">
|
||||
<img
|
||||
// TODO: actual league icon
|
||||
src={layoutIcon("top500")}
|
||||
width="18"
|
||||
height="18"
|
||||
title="Peak League Power"
|
||||
/>{" "}
|
||||
{member.peakLP}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
181
app/constants.ts
181
app/constants.ts
@@ -1,22 +1,3 @@
|
||||
export const navItems = [
|
||||
{
|
||||
title: "builds",
|
||||
items: ["browse", "gear", "analyzer"],
|
||||
},
|
||||
{
|
||||
title: "play",
|
||||
items: ["calendar", "play", "Rankings"],
|
||||
},
|
||||
{
|
||||
title: "tools",
|
||||
items: ["planner", "rotations", "top 500"],
|
||||
},
|
||||
{
|
||||
title: "misc",
|
||||
items: ["badges", "links"],
|
||||
},
|
||||
] as const;
|
||||
|
||||
export const DISCORD_URL = "https://discord.gg/sendou";
|
||||
|
||||
export const ADMIN_TEST_UUID = "846e12eb-d373-4002-a0c3-e23077e1c88c";
|
||||
@@ -37,3 +18,165 @@ export const BEST_OF_OPTIONS = [3, 5, 7, 9] as const;
|
||||
export const checkInClosesDate = (startTime: string): Date => {
|
||||
return new Date(new Date(startTime).getTime() - 1000 * 10);
|
||||
};
|
||||
|
||||
export const navItems = [
|
||||
{
|
||||
title: "builds",
|
||||
items: ["browse", "gear", "analyzer"],
|
||||
},
|
||||
{
|
||||
title: "play",
|
||||
items: ["calendar", "play", "Rankings"],
|
||||
},
|
||||
{
|
||||
title: "tools",
|
||||
items: ["planner", "rotations", "top 500"],
|
||||
},
|
||||
{
|
||||
title: "misc",
|
||||
items: ["badges", "links"],
|
||||
},
|
||||
] as const;
|
||||
|
||||
export const weapons = [
|
||||
"Sploosh-o-matic",
|
||||
"Neo Sploosh-o-matic",
|
||||
"Sploosh-o-matic 7",
|
||||
"Splattershot Jr.",
|
||||
"Custom Splattershot Jr.",
|
||||
"Kensa Splattershot Jr.",
|
||||
"Splash-o-matic",
|
||||
"Neo Splash-o-matic",
|
||||
"Aerospray MG",
|
||||
"Aerospray RG",
|
||||
"Aerospray PG",
|
||||
"Splattershot",
|
||||
"Tentatek Splattershot",
|
||||
"Kensa Splattershot",
|
||||
".52 Gal",
|
||||
".52 Gal Deco",
|
||||
"Kensa .52 Gal",
|
||||
"N-ZAP '85",
|
||||
"N-ZAP '89",
|
||||
"N-ZAP '83",
|
||||
"Splattershot Pro",
|
||||
"Forge Splattershot Pro",
|
||||
"Kensa Splattershot Pro",
|
||||
".96 Gal",
|
||||
".96 Gal Deco",
|
||||
"Jet Squelcher",
|
||||
"Custom Jet Squelcher",
|
||||
"L-3 Nozzlenose",
|
||||
"L-3 Nozzlenose D",
|
||||
"Kensa L-3 Nozzlenose",
|
||||
"H-3 Nozzlenose",
|
||||
"H-3 Nozzlenose D",
|
||||
"Cherry H-3 Nozzlenose",
|
||||
"Squeezer",
|
||||
"Foil Squeezer",
|
||||
"Luna Blaster",
|
||||
"Luna Blaster Neo",
|
||||
"Kensa Luna Blaster",
|
||||
"Blaster",
|
||||
"Custom Blaster",
|
||||
"Range Blaster",
|
||||
"Custom Range Blaster",
|
||||
"Grim Range Blaster",
|
||||
"Rapid Blaster",
|
||||
"Rapid Blaster Deco",
|
||||
"Kensa Rapid Blaster",
|
||||
"Rapid Blaster Pro",
|
||||
"Rapid Blaster Pro Deco",
|
||||
"Clash Blaster",
|
||||
"Clash Blaster Neo",
|
||||
"Carbon Roller",
|
||||
"Carbon Roller Deco",
|
||||
"Splat Roller",
|
||||
"Krak-On Splat Roller",
|
||||
"Kensa Splat Roller",
|
||||
"Dynamo Roller",
|
||||
"Gold Dynamo Roller",
|
||||
"Kensa Dynamo Roller",
|
||||
"Flingza Roller",
|
||||
"Foil Flingza Roller",
|
||||
"Inkbrush",
|
||||
"Inkbrush Nouveau",
|
||||
"Permanent Inkbrush",
|
||||
"Octobrush",
|
||||
"Octobrush Nouveau",
|
||||
"Kensa Octobrush",
|
||||
"Classic Squiffer",
|
||||
"New Squiffer",
|
||||
"Fresh Squiffer",
|
||||
"Splat Charger",
|
||||
"Firefin Splat Charger",
|
||||
"Kensa Charger",
|
||||
"Splatterscope",
|
||||
"Firefin Splatterscope",
|
||||
"Kensa Splatterscope",
|
||||
"E-liter 4K",
|
||||
"Custom E-liter 4K",
|
||||
"E-liter 4K Scope",
|
||||
"Custom E-liter 4K Scope",
|
||||
"Bamboozler 14 Mk I",
|
||||
"Bamboozler 14 Mk II",
|
||||
"Bamboozler 14 Mk III",
|
||||
"Goo Tuber",
|
||||
"Custom Goo Tuber",
|
||||
"Slosher",
|
||||
"Slosher Deco",
|
||||
"Soda Slosher",
|
||||
"Tri-Slosher",
|
||||
"Tri-Slosher Nouveau",
|
||||
"Sloshing Machine",
|
||||
"Sloshing Machine Neo",
|
||||
"Kensa Sloshing Machine",
|
||||
"Bloblobber",
|
||||
"Bloblobber Deco",
|
||||
"Explosher",
|
||||
"Custom Explosher",
|
||||
"Mini Splatling",
|
||||
"Zink Mini Splatling",
|
||||
"Kensa Mini Splatling",
|
||||
"Heavy Splatling",
|
||||
"Heavy Splatling Deco",
|
||||
"Heavy Splatling Remix",
|
||||
"Hydra Splatling",
|
||||
"Custom Hydra Splatling",
|
||||
"Ballpoint Splatling",
|
||||
"Ballpoint Splatling Nouveau",
|
||||
"Nautilus 47",
|
||||
"Nautilus 79",
|
||||
"Dapple Dualies",
|
||||
"Dapple Dualies Nouveau",
|
||||
"Clear Dapple Dualies",
|
||||
"Splat Dualies",
|
||||
"Enperry Splat Dualies",
|
||||
"Kensa Splat Dualies",
|
||||
"Glooga Dualies",
|
||||
"Glooga Dualies Deco",
|
||||
"Kensa Glooga Dualies",
|
||||
"Dualie Squelchers",
|
||||
"Custom Dualie Squelchers",
|
||||
"Dark Tetra Dualies",
|
||||
"Light Tetra Dualies",
|
||||
"Splat Brella",
|
||||
"Sorella Brella",
|
||||
"Tenta Brella",
|
||||
"Tenta Sorella Brella",
|
||||
"Tenta Camo Brella",
|
||||
"Undercover Brella",
|
||||
"Undercover Sorella Brella",
|
||||
"Kensa Undercover Brella",
|
||||
// reskins
|
||||
"Hero Shot Replica",
|
||||
"Hero Blaster Replica",
|
||||
"Hero Roller Replica",
|
||||
"Herobrush Replica",
|
||||
"Hero Charger Replica",
|
||||
"Hero Slosher Replica",
|
||||
"Hero Splatling Replica",
|
||||
"Hero Dualie Replicas",
|
||||
"Hero Brella Replica",
|
||||
"Octo Shot Replica",
|
||||
] as const;
|
||||
|
||||
34847
app/core/play/playerInfos/data.json
Normal file
34847
app/core/play/playerInfos/data.json
Normal file
File diff suppressed because it is too large
Load Diff
63
app/core/play/playerInfos/playerInfos.server.ts
Normal file
63
app/core/play/playerInfos/playerInfos.server.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import z from "zod";
|
||||
import { weapons } from "~/constants";
|
||||
import type {
|
||||
LookingLoaderData,
|
||||
LookingLoaderDataGroup,
|
||||
} from "~/routes/play/looking";
|
||||
|
||||
const infos = z
|
||||
.record(
|
||||
z.object({
|
||||
weapons: z.array(z.enum(weapons)).optional(),
|
||||
peakXP: z.number().optional(),
|
||||
peakLP: z.number().optional(),
|
||||
})
|
||||
)
|
||||
.parse(
|
||||
JSON.parse(
|
||||
fs
|
||||
.readFileSync(
|
||||
path.join(
|
||||
__dirname,
|
||||
"..",
|
||||
"..",
|
||||
"app",
|
||||
"core",
|
||||
"play",
|
||||
"playerInfos",
|
||||
"data.json"
|
||||
)
|
||||
)
|
||||
.toString()
|
||||
)
|
||||
);
|
||||
|
||||
export function addInfoFromOldSendouInk(
|
||||
type: "LEAGUE" | "SOLO",
|
||||
data: LookingLoaderData
|
||||
): LookingLoaderData {
|
||||
return {
|
||||
...data,
|
||||
ownGroup: mapGroup(data.ownGroup),
|
||||
likedGroups: data.likedGroups.map(mapGroup),
|
||||
neutralGroups: data.neutralGroups.map(mapGroup),
|
||||
likerGroups: data.likerGroups.map(mapGroup),
|
||||
};
|
||||
|
||||
function mapGroup(group: LookingLoaderDataGroup): LookingLoaderDataGroup {
|
||||
return {
|
||||
...group,
|
||||
members: group.members?.map((member) => {
|
||||
const playerInfos = infos[member.discordId];
|
||||
return {
|
||||
...member,
|
||||
weapons: playerInfos?.weapons,
|
||||
peakXP: type === "SOLO" ? playerInfos?.peakXP : undefined,
|
||||
peakLP: type === "LEAGUE" ? playerInfos?.peakLP : undefined,
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
UserLean,
|
||||
validate,
|
||||
} from "~/utils";
|
||||
import { addInfoFromOldSendouInk } from "~/core/play/playerInfos/playerInfos.server";
|
||||
|
||||
export const links: LinksFunction = () => {
|
||||
return [{ rel: "stylesheet", href: styles }];
|
||||
@@ -174,6 +175,9 @@ export type LookingLoaderDataGroup = {
|
||||
id: string;
|
||||
members?: (UserLean & {
|
||||
MMR?: number;
|
||||
weapons?: string[];
|
||||
peakXP?: number;
|
||||
peakLP?: number;
|
||||
})[];
|
||||
teamMMR?: {
|
||||
exact: boolean;
|
||||
@@ -244,77 +248,79 @@ export const loader: LoaderFunction = async ({ context }) => {
|
||||
|
||||
const { EXPIRED: expiredDate } = groupExpiredDates();
|
||||
|
||||
return json<LookingLoaderData>({
|
||||
ownGroup: ownGroupForResponse,
|
||||
type: ownGroup.type,
|
||||
isCaptain: isGroupAdmin({ group: ownGroup, user }),
|
||||
lastActionAtTimestamp: ownGroup.lastActionAt.getTime(),
|
||||
...groupsOfType
|
||||
.filter(
|
||||
(group) =>
|
||||
(lookingForMatch && group.members.length === LFG_GROUP_FULL_SIZE) ||
|
||||
canUniteWithGroup({
|
||||
ownGroupType: ownGroup.type,
|
||||
ownGroupSize: ownGroup.members.length,
|
||||
otherGroupSize: group.members.length,
|
||||
})
|
||||
)
|
||||
.filter((group) => group.id !== ownGroup.id)
|
||||
.filter((group) => group.lastActionAt.getTime() > expiredDate.getTime())
|
||||
.map((group) => {
|
||||
const ranked = () => {
|
||||
if (lookingForMatch && !ownGroup.ranked) return false;
|
||||
return json<LookingLoaderData>(
|
||||
addInfoFromOldSendouInk(ownGroup.type === "VERSUS" ? "SOLO" : "LEAGUE", {
|
||||
ownGroup: ownGroupForResponse,
|
||||
type: ownGroup.type,
|
||||
isCaptain: isGroupAdmin({ group: ownGroup, user }),
|
||||
lastActionAtTimestamp: ownGroup.lastActionAt.getTime(),
|
||||
...groupsOfType
|
||||
.filter(
|
||||
(group) =>
|
||||
(lookingForMatch && group.members.length === LFG_GROUP_FULL_SIZE) ||
|
||||
canUniteWithGroup({
|
||||
ownGroupType: ownGroup.type,
|
||||
ownGroupSize: ownGroup.members.length,
|
||||
otherGroupSize: group.members.length,
|
||||
})
|
||||
)
|
||||
.filter((group) => group.id !== ownGroup.id)
|
||||
.filter((group) => group.lastActionAt.getTime() > expiredDate.getTime())
|
||||
.map((group) => {
|
||||
const ranked = () => {
|
||||
if (lookingForMatch && !ownGroup.ranked) return false;
|
||||
|
||||
return group.ranked ?? undefined;
|
||||
};
|
||||
return {
|
||||
id: group.id,
|
||||
// When looking for a match ranked groups are censored
|
||||
// and instead we only reveal their approximate skill level
|
||||
members:
|
||||
ownGroup.ranked && group.ranked && lookingForMatch
|
||||
? undefined
|
||||
: group.members.map((m) => {
|
||||
const { skill, ...rest } = m.user;
|
||||
return group.ranked ?? undefined;
|
||||
};
|
||||
return {
|
||||
id: group.id,
|
||||
// When looking for a match ranked groups are censored
|
||||
// and instead we only reveal their approximate skill level
|
||||
members:
|
||||
ownGroup.ranked && group.ranked && lookingForMatch
|
||||
? undefined
|
||||
: group.members.map((m) => {
|
||||
const { skill, ...rest } = m.user;
|
||||
|
||||
return {
|
||||
...rest,
|
||||
MMR: skillToMMR(skill),
|
||||
};
|
||||
}),
|
||||
ranked: ranked(),
|
||||
teamMMR:
|
||||
lookingForMatch && isRanked
|
||||
? {
|
||||
exact: false,
|
||||
value: teamSkillToApproximateMMR(group.members),
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
})
|
||||
.reduce(
|
||||
(
|
||||
acc: Omit<
|
||||
LookingLoaderData,
|
||||
"ownGroup" | "type" | "isCaptain" | "lastActionAtTimestamp"
|
||||
>,
|
||||
group
|
||||
) => {
|
||||
// likesReceived first so that if both received like and
|
||||
// given like then handle this edge case by just displaying the
|
||||
// group as waiting like back
|
||||
if (likesReceived.has(group.id)) {
|
||||
acc.likerGroups.push(group);
|
||||
} else if (likesGiven.has(group.id)) {
|
||||
acc.likedGroups.push(group);
|
||||
} else {
|
||||
acc.neutralGroups.push(group);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{ likedGroups: [], neutralGroups: [], likerGroups: [] }
|
||||
),
|
||||
});
|
||||
return {
|
||||
...rest,
|
||||
MMR: skillToMMR(skill),
|
||||
};
|
||||
}),
|
||||
ranked: ranked(),
|
||||
teamMMR:
|
||||
lookingForMatch && isRanked
|
||||
? {
|
||||
exact: false,
|
||||
value: teamSkillToApproximateMMR(group.members),
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
})
|
||||
.reduce(
|
||||
(
|
||||
acc: Omit<
|
||||
LookingLoaderData,
|
||||
"ownGroup" | "type" | "isCaptain" | "lastActionAtTimestamp"
|
||||
>,
|
||||
group
|
||||
) => {
|
||||
// likesReceived first so that if both received like and
|
||||
// given like then handle this edge case by just displaying the
|
||||
// group as waiting like back
|
||||
if (likesReceived.has(group.id)) {
|
||||
acc.likerGroups.push(group);
|
||||
} else if (likesGiven.has(group.id)) {
|
||||
acc.likedGroups.push(group);
|
||||
} else {
|
||||
acc.neutralGroups.push(group);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{ likedGroups: [], neutralGroups: [], likerGroups: [] }
|
||||
),
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
export default function LookingPage() {
|
||||
|
||||
@@ -142,6 +142,17 @@
|
||||
font-size: var(--fonts-xxs);
|
||||
}
|
||||
|
||||
.play-looking__member-power {
|
||||
display: flex;
|
||||
margin-top: -4px;
|
||||
font-size: var(--fonts-xxs);
|
||||
gap: var(--s-1);
|
||||
}
|
||||
|
||||
.play-looking__member-power.league {
|
||||
filter: hue-rotate(180deg);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.play-looking__divider {
|
||||
display: block;
|
||||
|
||||
Reference in New Issue
Block a user