sendou.ink/app/modules/map-pool-serializer/map-pool.ts
Kalle e7bbb565be
SendouQ (#1455)
* Tables

* Clocks

* Maplist preference selector

* Fix SSR

* Nav icon

* RankedOrScrim

* Map pool

* Create group

* Redirect logic

* Persist map pool

* Advance from preparing page

* Rename query

* Fix merge

* Fix migration order

* Seed groups

* Find looking groups SQL

* Renders something

* More UI work

* Back to 30min

* Likes/dislikes

* Always return own group

* Fix like order

* 3 tc/rm/cb -> 2

* Show only 3 weapons

* Pass group size

* Handle both liked and liked by same group

* Fix SQL

* Group preference frontend work

* Morphing

* Styling

* Don't show group controls if not manager

* Give/remove manager

* Leave group

* Leave with confirm

* Delete likes when morphing groups

* Clocks consistency

* Remove bad invariant

* Persist settings to local storage

* Fix initial value flashing

* Fix never resolving loading indicator

* REFRESH_GROUP

* Flip animations

* Tweaks

* Auto refresh logic

* Groups of 4 seed

* Reduce throwing

* Load full groups initial

* Create match

* Match UI initial

* Score reporter initial

* Push footer down on match page

* Score reporter knows when set ended

* Score reporting untested

* Show score after report

* Align better

* Look again with same group functionality

* More migrations

* Team on match page

* Show confirmer before reporting score

* Report weapons

* Report weapos again by admin + skill changing

* Handle no tiebreaker given to MapPool

* Remove unranked

* Remove support for "team id skill"

* no-wrap -> nowrap

* Preparing page work

* Use common GroupCard component

* Add some metas

* MemberAdder in looking page

* Fix GroupCard actions

* Fix SZ only map list including other modes

* Add season info

* Prompt login

* Joining team

* Manage group on preparing page

* Manage group on preparing page

* Seed past matches

* Add to seed

* No map list preference when full group + fix expiry

* Fix skill matchesCount calculation

* Tiers initial work

* Some progress on tiers

* Tiering logic

* MMR in group cards

* Name to challenge

* Team MMR

* Big team rank icons

* Adjust todos

* Match score report with confirm

* Allow regular members to report score

* Handle reporting weapons edge cases

* Add tier images

* Improve GroupCard spacing

* Refactor looking page

* Looking mobile UI

* Calculate skill only for current season

* Divide groups visually when reporting weapons

* Fix match page weapons sorting

* Add cache to user skills+tier calculation

* Admin report match score

* Initial leaderboard

* Cached leaderboard

* Weapon category lb's

* Populate SkillTeamUser in SendouQ

* Team leaderboard filtered down

* Add TODOs

* Seasons initlal

* Season weapons initial

* Weapons stylized

* Show rest weapons as +

* Hide peak if same as current

* Load matches SQL initial

* Season matches UI initial

* Take user id in account

* Add weapons

* Paginated matches

* Fix pages count logic

* Scroll top on data change

* Day headers for matches

* Link from user page to user seasons page

* Summarize maps + ui initial

* Map stats

* Player info tabs

* MMR chart

* Chart adjustments

* Handle basing team MMR on player MMR

* Set initial MMR

* Add info about discord to match page

* Season support to tournaments

* Get tournament skills as well for the graph

* WIP

* New team rating logic + misc other

* tiered -> tiered.server

* Update season starting time

* TODOs

* Add rules page

* Hide elements correctly when off-season

* Fix crash when only one player with skill

* How-to video

* Fix StartRank showing when not logged in

* Make user leaderboard the default

* Make Skill season non-nullable

* Add suggested pass to match

* Add rule

* identifierToUserIds helper

* Fix tiers not showing
2023-08-12 22:42:54 +03:00

172 lines
3.8 KiB
TypeScript

import {
mapPoolToSerializedString,
serializedStringToMapPool,
} from "./serializer";
import type { ReadonlyMapPoolObject, MapPoolObject } from "./types";
import clone from "just-clone";
import type { MapPoolMap } from "~/db/types";
import { mapPoolListToMapPoolObject } from "~/modules/map-list-generator";
import {
type ModeShort,
type StageId,
stageIds,
} from "~/modules/in-game-lists";
type DbMapPoolList = Array<Pick<MapPoolMap, "stageId" | "mode">>;
export class MapPool {
private source: string | ReadonlyMapPoolObject;
private asSerialized?: string;
private asObject?: ReadonlyMapPoolObject;
constructor(init: ReadonlyMapPoolObject | string | DbMapPoolList) {
this.source = Array.isArray(init) ? mapPoolListToMapPoolObject(init) : init;
}
static serialize(init: ReadonlyMapPoolObject | string | DbMapPoolList) {
return new MapPool(init).serialized;
}
static parse(init: MapPoolObject | string | DbMapPoolList) {
return new MapPool(init).parsed;
}
static toDbList(init: MapPoolObject | string | DbMapPoolList) {
return new MapPool(init).dbList;
}
get serialized(): string {
if (this.asSerialized !== undefined) {
return this.asSerialized;
}
return (this.asSerialized =
typeof this.source === "string"
? this.source
: mapPoolToSerializedString(this.source));
}
get parsed(): ReadonlyMapPoolObject {
if (this.asObject !== undefined) {
return this.asObject;
}
return (this.asObject =
typeof this.source === "string"
? serializedStringToMapPool(this.source)
: this.source);
}
get dbList(): DbMapPoolList {
return Object.entries(this.parsed).flatMap(([mode, stages]) =>
stages.flatMap((stageId) => ({ mode: mode as ModeShort, stageId }))
);
}
get stages() {
return Object.values(this.parsed).flat();
}
get stageModePairs() {
return Object.entries(this.parsed).flatMap(([mode, stages]) =>
stages.map((stageId) => ({ mode: mode as ModeShort, stageId }))
);
}
has({ stageId, mode }: { stageId: StageId; mode: ModeShort }) {
return this.parsed[mode].includes(stageId);
}
hasMode(mode: ModeShort): boolean {
return this.parsed[mode].length > 0;
}
hasStage(stageId: StageId): boolean {
return Object.values(this.parsed).some((stages) =>
stages.includes(stageId)
);
}
overlaps(other: MapPool): boolean {
return this.stageModePairs.some((pair) => other.has(pair));
}
isEmpty(): boolean {
return Object.values(this.parsed).every((stages) => stages.length === 0);
}
countMapsByMode(mode: ModeShort): number {
return this.parsed[mode].length;
}
get length() {
return this.stageModePairs.length;
}
getClonedObject(): MapPoolObject {
return clone(this.parsed) as MapPoolObject;
}
toString() {
return this.serialized;
}
toJSON() {
return this.parsed;
}
[Symbol.iterator]() {
var index = -1;
var data = this.stageModePairs;
return {
next: () => ({ value: data[++index]!, done: !(index in data) }),
};
}
static EMPTY = new MapPool({
SZ: [],
TC: [],
CB: [],
RM: [],
TW: [],
});
static ALL = new MapPool({
SZ: [...stageIds],
TC: [...stageIds],
CB: [...stageIds],
RM: [...stageIds],
TW: [...stageIds],
});
static ANARCHY = new MapPool({
SZ: [...stageIds],
TC: [...stageIds],
CB: [...stageIds],
RM: [...stageIds],
TW: [],
});
static SZ = new MapPool({
...MapPool.EMPTY.parsed,
SZ: [...stageIds],
});
static TC = new MapPool({
...MapPool.EMPTY.parsed,
TC: [...stageIds],
});
static CB = new MapPool({
...MapPool.EMPTY.parsed,
CB: [...stageIds],
});
static RM = new MapPool({
...MapPool.EMPTY.parsed,
RM: [...stageIds],
});
static TW = new MapPool({
...MapPool.EMPTY.parsed,
TW: [...stageIds],
});
}