;
+}) {
+ const user = useUser();
+ const tournament = useTournament();
+
+ const isOnTeam = Boolean(tournament.teamMemberOfByUser(user));
+
+ return (
+
+ {tournament.canAddNewSubPost &&
+ !data.hasOwnSubPost &&
+ user &&
+ !isOnTeam ? (
+
+ ) : null}
+ {data.subs.map((sub) => (
+
+ ))}
+
+ );
+}
+
+function AddSubForm() {
+ const { t } = useTranslation(["tournament"]);
+
+ return (
+
+
+ {t("tournament:subs.addPost")}
+
+ }
+ >
+
+ {({ FormField }) => }
+
+
+
+ );
+}
+
+function SubCard({ sub }: { sub: SubEntry }) {
+ const { t } = useTranslation(["common", "tournament"]);
+ const user = useUser();
+ const tournament = useTournament();
+
+ const infos = [
+
+
+ {sub.vc === "YES"
+ ? t("tournament:subs.canVC")
+ : sub.vc === "LISTEN_ONLY"
+ ? t("tournament:subs.listenOnlyVC")
+ : t("tournament:subs.noVC")}
+
,
+ ];
+ if (sub.plusTier) {
+ infos.push(
+ {"\u00B7"},
+ +{sub.plusTier}
,
+ );
+ }
+ if (sub.languages.length > 0) {
+ infos.push(
+ {"\u00B7"},
+
+ {sub.languages.map((lang) => lang.toUpperCase()).join(" / ")}
+
,
+ );
+ }
+
+ return (
+
+
+
+
+ {sub.username}
+
+
+ {infos}
+ {sub.weapons ? (
+ <>
+
+ {t("tournament:subs.prefersToPlay")}
+
+
+ {sub.weapons.map((wpn) => (
+
+ ))}
+
+ >
+ ) : null}
+ {sub.message ? (
+ {sub.message}
+ ) : null}
+
+ {user?.id === sub.userId || tournament.isOrganizer(user) ? (
+
+
+ }
+ >
+ {t("common:actions.delete")}
+
+
+
+ ) : null}
+
+ );
+}
+
+function ColumnHeader({ children }: { children: React.ReactNode }) {
+ const { width } = useWindowSize();
+
+ const isMobile = width < 750;
+
+ if (isMobile) return null;
+
+ return {children}
;
+}
+
+function JoinQueueForm() {
+ const { t } = useTranslation(["q"]);
+
+ return (
+
+ {({ FormField }) => (
+ <>
+
+
+ >
+ )}
+
+ );
+}
+
+function TeamQueueSection() {
+ const { t } = useTranslation(["q"]);
+ const data = useLoaderData() as Extract<
+ LookingLoaderData,
+ { mode: "looking" }
+ >;
+ const tournament = useTournament();
+ const fetcher = useFetcher();
+
+ if (!data.ownTeam) return null;
+
+ const isAtMaxMembers =
+ data.ownTeam.members.length >= tournament.maxMembersPerTeam;
+
+ return (
+
+
+ {!isAtMaxMembers ? (
+
+
+ {t("q:looking.joinQPromptMembers")}
+
+
+ ) : null}
+
+ );
+}
+
+function LFGGroupLeaver({ type }: { type: "LEAVE_GROUP" | "LEAVE_Q" }) {
+ const { t } = useTranslation(["q"]);
+ const fetcher = useFetcher();
+
+ if (type === "LEAVE_GROUP") {
+ return (
+
+
+ {t("q:looking.groups.actions.stopLooking")}
+
+
+ );
+ }
+
+ return (
+
+
+ {t("q:looking.groups.actions.leaveQ")}
+
+
+ );
+}
diff --git a/app/features/tournament-lfg/tournament-lfg-schemas.ts b/app/features/tournament-lfg/tournament-lfg-schemas.ts
new file mode 100644
index 000000000..f2f1a25e5
--- /dev/null
+++ b/app/features/tournament-lfg/tournament-lfg-schemas.ts
@@ -0,0 +1,63 @@
+import { z } from "zod";
+import { stringConstant, textAreaOptional, toggle } from "~/form/fields";
+import { _action, id } from "~/utils/zod";
+
+const noteFieldSchema = textAreaOptional({
+ label: "labels.note",
+ maxLength: 160,
+});
+
+export const addSubFormSchema = z.object({
+ _action: stringConstant("ADD_SUB"),
+ message: noteFieldSchema,
+});
+
+const stayAsSubFieldSchema = toggle({
+ label: "labels.stayAsSub",
+ bottomText: "bottomTexts.stayAsSub",
+});
+
+export const joinQueueFormSchema = z.object({
+ _action: stringConstant("JOIN_QUEUE"),
+ note: noteFieldSchema,
+ stayAsSub: stayAsSubFieldSchema,
+});
+
+export const updateGroupFormSchema = z.object({
+ _action: stringConstant("UPDATE_GROUP"),
+ note: noteFieldSchema,
+ stayAsSub: stayAsSubFieldSchema,
+});
+
+export const lookingSchema = z.union([
+ joinQueueFormSchema,
+ z.object({
+ _action: _action("LIKE"),
+ targetTeamId: id,
+ }),
+ z.object({
+ _action: _action("UNLIKE"),
+ targetTeamId: id,
+ }),
+ z.object({
+ _action: _action("ACCEPT"),
+ targetTeamId: id,
+ }),
+ z.object({
+ _action: _action("GIVE_MANAGER"),
+ userId: id,
+ }),
+ z.object({
+ _action: _action("REMOVE_MANAGER"),
+ userId: id,
+ }),
+ updateGroupFormSchema,
+ z.object({
+ _action: _action("LEAVE_GROUP"),
+ }),
+ addSubFormSchema,
+ z.object({
+ _action: _action("DELETE_SUB"),
+ userId: id,
+ }),
+]);
diff --git a/app/features/tournament-lfg/tournament-lfg-utils.ts b/app/features/tournament-lfg/tournament-lfg-utils.ts
new file mode 100644
index 000000000..2e568629c
--- /dev/null
+++ b/app/features/tournament-lfg/tournament-lfg-utils.ts
@@ -0,0 +1,18 @@
+import type { LFGGroup } from "./components/LFGGroupCard";
+
+export function survivingTeamId({
+ ourGroup,
+ theirGroup,
+}: {
+ ourGroup: LFGGroup;
+ theirGroup: LFGGroup;
+}) {
+ if (!ourGroup.isPlaceholder && theirGroup.isPlaceholder) {
+ return ourGroup.id;
+ }
+ if (ourGroup.isPlaceholder && !theirGroup.isPlaceholder) {
+ return theirGroup.id;
+ }
+
+ return ourGroup.id;
+}
diff --git a/app/features/tournament-subs/TournamentSubRepository.server.ts b/app/features/tournament-subs/TournamentSubRepository.server.ts
deleted file mode 100644
index a5de09926..000000000
--- a/app/features/tournament-subs/TournamentSubRepository.server.ts
+++ /dev/null
@@ -1,164 +0,0 @@
-import { sql } from "kysely";
-import { db } from "~/db/sql";
-import type { TablesInsertable } from "~/db/tables";
-import type { MainWeaponId } from "~/modules/in-game-lists/types";
-
-export async function findSubsVisibleToUser({
- tournamentId,
- userId,
-}: {
- tournamentId: number;
- userId?: number;
-}) {
- const userPlusTier = await getUserPlusTier(userId);
-
- const rows = await baseQuery(tournamentId)
- .where((eb) =>
- eb.or([
- eb("TournamentSub.visibility", "=", "ALL"),
- eb.and([
- eb("TournamentSub.visibility", "=", "+1"),
- eb(sql`${userPlusTier}`, "=", 1),
- ]),
- eb.and([
- eb("TournamentSub.visibility", "=", "+2"),
- eb(sql`${userPlusTier}`, "<=", 2),
- ]),
- eb.and([
- eb("TournamentSub.visibility", "=", "+3"),
- eb(sql`${userPlusTier}`, "<=", 3),
- ]),
- ]),
- )
- .orderBy((eb) =>
- eb
- .case()
- .when("TournamentSub.userId", "=", userId ?? null)
- .then(0)
- .else(1)
- .end(),
- )
- .orderBy((eb) =>
- eb
- .case()
- .when(eb("PlusTier.tier", "is", null))
- .then(4)
- .else(eb.ref("PlusTier.tier"))
- .end(),
- )
- .orderBy("TournamentSub.createdAt", "desc")
- .execute();
-
- return mapRows(rows);
-}
-
-export async function findUserSubPost({
- tournamentId,
- userId,
-}: {
- tournamentId: number;
- userId: number;
-}) {
- const row = await baseQuery(tournamentId)
- .where("TournamentSub.userId", "=", userId)
- .executeTakeFirst();
-
- if (!row) return null;
-
- return mapRows([row])[0];
-}
-
-export function upsert(
- args: Omit & {
- bestWeapons: MainWeaponId[];
- okWeapons: MainWeaponId[];
- },
-) {
- return db.transaction().execute(async (trx) => {
- const bestWeaponsStr = args.bestWeapons.join(",");
- const okWeaponsStr =
- args.okWeapons.length > 0 ? args.okWeapons.join(",") : null;
-
- await trx
- .insertInto("TournamentSub")
- .values({
- userId: args.userId,
- tournamentId: args.tournamentId,
- canVc: args.canVc,
- bestWeapons: bestWeaponsStr,
- okWeapons: okWeaponsStr,
- message: args.message,
- visibility: args.visibility,
- })
- .onConflict((oc) =>
- oc.columns(["userId", "tournamentId"]).doUpdateSet({
- canVc: args.canVc,
- bestWeapons: bestWeaponsStr,
- okWeapons: okWeaponsStr,
- message: args.message,
- visibility: args.visibility,
- }),
- )
- .execute();
-
- await trx
- .updateTable("User")
- .set({ lastSubMessage: args.message })
- .where("id", "=", args.userId)
- .execute();
- });
-}
-
-async function getUserPlusTier(userId?: number) {
- if (!userId) return 4;
-
- const row = await db
- .selectFrom("PlusTier")
- .select("tier")
- .where("userId", "=", userId)
- .executeTakeFirst();
-
- return row?.tier ?? 4;
-}
-
-function baseQuery(tournamentId: number) {
- return db
- .selectFrom("TournamentSub")
- .innerJoin("User", "User.id", "TournamentSub.userId")
- .leftJoin("PlusTier", "PlusTier.userId", "User.id")
- .select([
- "TournamentSub.canVc",
- "TournamentSub.bestWeapons",
- "TournamentSub.okWeapons",
- "TournamentSub.message",
- "TournamentSub.visibility",
- "TournamentSub.createdAt",
- "TournamentSub.userId",
- "User.username",
- "User.discordAvatar",
- "User.country",
- "User.discordId",
- "User.customUrl",
- "PlusTier.tier as plusTier",
- ])
- .where("TournamentSub.tournamentId", "=", tournamentId);
-}
-
-function mapRows<
- T extends {
- bestWeapons: string;
- okWeapons: string | null;
- },
->(rows: T[]) {
- return rows.map((row) => ({
- ...row,
- bestWeapons: parseWeaponsArray(row.bestWeapons)!,
- okWeapons: parseWeaponsArray(row.okWeapons),
- }));
-}
-
-function parseWeaponsArray(value: string | null) {
- if (!value) return null;
-
- return value.split(",").map(Number) as MainWeaponId[];
-}
diff --git a/app/features/tournament-subs/actions/to.$id.subs.new.server.ts b/app/features/tournament-subs/actions/to.$id.subs.new.server.ts
deleted file mode 100644
index 8fa4bcdb1..000000000
--- a/app/features/tournament-subs/actions/to.$id.subs.new.server.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import { type ActionFunction, redirect } from "react-router";
-import { requireUser } from "~/features/auth/core/user.server";
-import { requireNotBannedByOrganization } from "~/features/tournament/tournament-utils.server";
-import {
- clearTournamentDataCache,
- tournamentFromDB,
-} from "~/features/tournament-bracket/core/Tournament.server";
-import {
- errorToastIfFalsy,
- parseParams,
- parseRequestPayload,
-} from "~/utils/remix.server";
-import { tournamentSubsPage } from "~/utils/urls";
-import { idObject } from "~/utils/zod";
-import * as TournamentSubRepository from "../TournamentSubRepository.server";
-import { subSchema } from "../tournament-subs-schemas.server";
-
-export const action: ActionFunction = async ({ params, request }) => {
- const user = requireUser();
- const data = await parseRequestPayload({
- request,
- schema: subSchema,
- });
- const { id: tournamentId } = parseParams({
- params,
- schema: idObject,
- });
- const tournament = await tournamentFromDB({ tournamentId, user });
-
- await requireNotBannedByOrganization({ tournament, user });
- errorToastIfFalsy(!tournament.everyBracketOver, "Tournament is over");
- errorToastIfFalsy(
- tournament.canAddNewSubPost,
- "Registration is closed or subs feature disabled",
- );
- errorToastIfFalsy(
- !tournament.teamMemberOfByUser(user),
- "Can't register as a sub and be in a team at the same time",
- );
-
- await TournamentSubRepository.upsert({
- bestWeapons: data.bestWeapons,
- okWeapons: data.okWeapons,
- canVc: data.canVc,
- visibility: data.visibility,
- message: data.message ?? null,
- tournamentId,
- userId: user.id,
- });
-
- clearTournamentDataCache(tournamentId);
-
- throw redirect(tournamentSubsPage(tournamentId));
-};
diff --git a/app/features/tournament-subs/actions/to.$id.subs.server.ts b/app/features/tournament-subs/actions/to.$id.subs.server.ts
deleted file mode 100644
index 1583976bc..000000000
--- a/app/features/tournament-subs/actions/to.$id.subs.server.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import type { ActionFunction } from "react-router";
-import { requireUser } from "~/features/auth/core/user.server";
-import {
- clearTournamentDataCache,
- tournamentFromDB,
-} from "~/features/tournament-bracket/core/Tournament.server";
-import {
- errorToastIfFalsy,
- parseParams,
- parseRequestPayload,
-} from "~/utils/remix.server";
-import { idObject } from "~/utils/zod";
-import { deleteSub } from "../queries/deleteSub.server";
-import { deleteSubSchema } from "../tournament-subs-schemas.server";
-
-export const action: ActionFunction = async ({ request, params }) => {
- const user = requireUser();
- const { id: tournamentId } = parseParams({
- params,
- schema: idObject,
- });
- const tournament = await tournamentFromDB({ tournamentId, user });
- const data = await parseRequestPayload({
- request,
- schema: deleteSubSchema,
- });
-
- errorToastIfFalsy(
- user.id === data.userId || tournament.isOrganizer(user),
- "You can only delete your own sub post",
- );
-
- deleteSub({
- tournamentId,
- userId: data.userId,
- });
-
- clearTournamentDataCache(tournamentId);
-
- return null;
-};
diff --git a/app/features/tournament-subs/loaders/to.$id.subs.new.server.ts b/app/features/tournament-subs/loaders/to.$id.subs.new.server.ts
deleted file mode 100644
index 7bbd1f447..000000000
--- a/app/features/tournament-subs/loaders/to.$id.subs.new.server.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { type LoaderFunctionArgs, redirect } from "react-router";
-import { requireUser } from "~/features/auth/core/user.server";
-import { tournamentFromDB } from "~/features/tournament-bracket/core/Tournament.server";
-import * as UserRepository from "~/features/user-page/UserRepository.server";
-import { parseParams } from "~/utils/remix.server";
-import { tournamentSubsPage } from "~/utils/urls";
-import { idObject } from "~/utils/zod";
-import * as TournamentSubRepository from "../TournamentSubRepository.server";
-
-export const loader = async ({ params }: LoaderFunctionArgs) => {
- const user = requireUser();
- const { id: tournamentId } = parseParams({
- params,
- schema: idObject,
- });
- const tournament = await tournamentFromDB({ tournamentId, user });
-
- if (!tournament.canAddNewSubPost) {
- throw redirect(tournamentSubsPage(tournamentId));
- }
-
- return {
- sub: await TournamentSubRepository.findUserSubPost({
- tournamentId,
- userId: user.id,
- }),
- userDefaults: await UserRepository.findSubDefaultsByUserId(user.id),
- };
-};
diff --git a/app/features/tournament-subs/loaders/to.$id.subs.server.ts b/app/features/tournament-subs/loaders/to.$id.subs.server.ts
index e95db669f..bff2886b5 100644
--- a/app/features/tournament-subs/loaders/to.$id.subs.server.ts
+++ b/app/features/tournament-subs/loaders/to.$id.subs.server.ts
@@ -1,30 +1,13 @@
import { type LoaderFunctionArgs, redirect } from "react-router";
-import { getUser } from "~/features/auth/core/user.server";
-import { tournamentFromDB } from "~/features/tournament-bracket/core/Tournament.server";
import { parseParams } from "~/utils/remix.server";
-import { tournamentRegisterPage } from "~/utils/urls";
+import { tournamentSubsPage } from "~/utils/urls";
import { idObject } from "~/utils/zod";
-import * as TournamentSubRepository from "../TournamentSubRepository.server";
export const loader = async ({ params }: LoaderFunctionArgs) => {
- const user = getUser();
const { id: tournamentId } = parseParams({
params,
schema: idObject,
});
- const tournament = await tournamentFromDB({ tournamentId, user });
- if (!tournament.subsFeatureEnabled) {
- throw redirect(tournamentRegisterPage(tournamentId));
- }
-
- const subs = await TournamentSubRepository.findSubsVisibleToUser({
- tournamentId,
- userId: user?.id,
- });
-
- return {
- subs,
- hasOwnSubPost: subs.some((sub) => sub.userId === user?.id),
- };
+ throw redirect(tournamentSubsPage(tournamentId));
};
diff --git a/app/features/tournament-subs/routes/to.$id.subs.new.module.css b/app/features/tournament-subs/routes/to.$id.subs.new.module.css
deleted file mode 100644
index 4c109cb67..000000000
--- a/app/features/tournament-subs/routes/to.$id.subs.new.module.css
+++ /dev/null
@@ -1,9 +0,0 @@
-.selectedWeapon {
- padding: var(--s-2);
- border-radius: 100%;
- background-color: var(--color-bg-high);
-}
-
-.weaponPool {
- width: 20rem;
-}
diff --git a/app/features/tournament-subs/routes/to.$id.subs.new.tsx b/app/features/tournament-subs/routes/to.$id.subs.new.tsx
deleted file mode 100644
index 4d950f56a..000000000
--- a/app/features/tournament-subs/routes/to.$id.subs.new.tsx
+++ /dev/null
@@ -1,297 +0,0 @@
-import { Trash } from "lucide-react";
-import React from "react";
-import { useTranslation } from "react-i18next";
-import { Form, Link, useLoaderData } from "react-router";
-import { SendouButton } from "~/components/elements/Button";
-import { FormMessage } from "~/components/FormMessage";
-import { WeaponImage } from "~/components/Image";
-import { Label } from "~/components/Label";
-import { RequiredHiddenInput } from "~/components/RequiredHiddenInput";
-import { SubmitButton } from "~/components/SubmitButton";
-import { WeaponSelect } from "~/components/WeaponSelect";
-import { useUser } from "~/features/auth/core/user";
-import type { MainWeaponId } from "~/modules/in-game-lists/types";
-import type { SendouRouteHandle } from "~/utils/remix.server";
-import { SENDOUQ_SETTINGS_PAGE } from "~/utils/urls";
-import { action } from "../actions/to.$id.subs.new.server";
-import { loader } from "../loaders/to.$id.subs.new.server";
-import { TOURNAMENT_SUB } from "../tournament-subs-constants";
-export { action, loader };
-
-import clsx from "clsx";
-import { mainStyles } from "~/components/Main";
-import styles from "./to.$id.subs.new.module.css";
-
-export const handle: SendouRouteHandle = {
- i18n: ["user"],
-};
-
-export default function NewTournamentSubPage() {
- const user = useUser();
- const { t } = useTranslation(["common", "tournament"]);
- const data = useLoaderData();
-
- const [bestWeapons, setBestWeapons] = React.useState(
- data.sub ? data.sub.bestWeapons : (data.userDefaults?.bestWeapons ?? []),
- );
- const [okWeapons, setOkWeapons] = React.useState(
- data.sub
- ? (data.sub.okWeapons ?? [])
- : (data.userDefaults?.okWeapons ?? []),
- );
-
- return (
-
- );
-}
-
-function VCRadios() {
- const { t } = useTranslation(["common", "tournament"]);
- const data = useLoaderData();
-
- const isDefaultChecked = (value: number) => {
- if (data.sub) {
- return data.sub.canVc === value;
- }
-
- const defaultCanVc = data.userDefaults?.canVc ?? 1;
-
- return defaultCanVc === value;
- };
-
- return (
-
-
-
-
- );
-}
-
-function Message() {
- const { t } = useTranslation(["tournament"]);
- const data = useLoaderData();
- const [value, setValue] = React.useState(
- data.sub?.message ?? data.userDefaults?.message ?? "",
- );
-
- return (
-
-
-
- );
-}
-
-function VisibilityRadios() {
- const { t } = useTranslation(["tournament"]);
- const data = useLoaderData();
- const user = useUser();
-
- const userPlusTier = user?.plusTier ?? 4;
-
- return (
-
-
-
-
- );
-}
-
-function WeaponPoolSelect({
- weapons,
- otherWeapons,
- setWeapons,
- label,
- id,
- infoText,
- required = false,
-}: {
- weapons: Array;
- otherWeapons: Array;
- setWeapons: (weapons: Array) => void;
- label: string;
- id: string;
- infoText: string;
- required?: boolean;
-}) {
- const { t } = useTranslation(["user"]);
-
- return (
-
-
0}
- name={id}
- value={JSON.stringify(weapons)}
- />
- {weapons.length < TOURNAMENT_SUB.WEAPON_POOL_MAX_SIZE ? (
- <>
- {
- setWeapons([...weapons, weaponId]);
- }}
- disabledWeaponIds={[...weapons, ...otherWeapons]}
- // empty on selection
- key={weapons[weapons.length - 1]}
- />
- {infoText}
- >
- ) : (
-
- {t("user:forms.errors.maxWeapons")}
-
- )}
- {weapons.length > 0 ? (
-
- {weapons.map((weapon) => {
- return (
-
-
-
-
-
}
- variant="minimal-destructive"
- aria-label="Delete weapon"
- onPress={() =>
- setWeapons(weapons.filter((w) => w !== weapon))
- }
- />
-
- );
- })}
-
- ) : null}
-
- );
-}
diff --git a/app/features/tournament-subs/routes/to.$id.subs.tsx b/app/features/tournament-subs/routes/to.$id.subs.tsx
index 519cc7c87..f6a8efc46 100644
--- a/app/features/tournament-subs/routes/to.$id.subs.tsx
+++ b/app/features/tournament-subs/routes/to.$id.subs.tsx
@@ -1,196 +1 @@
-import clsx from "clsx";
-import { Mic, Trash } from "lucide-react";
-import React from "react";
-import { useTranslation } from "react-i18next";
-import { Link, useLoaderData } from "react-router";
-import { Avatar } from "~/components/Avatar";
-import { LinkButton, SendouButton } from "~/components/elements/Button";
-import { SendouPopover } from "~/components/elements/Popover";
-import { Flag } from "~/components/Flag";
-import { FormWithConfirm } from "~/components/FormWithConfirm";
-import { WeaponImage } from "~/components/Image";
-import { Redirect } from "~/components/Redirect";
-import { useUser } from "~/features/auth/core/user";
-import { useTournament } from "~/features/tournament/routes/to.$id";
-import type { SerializeFrom } from "~/utils/remix";
-import { tournamentRegisterPage, userPage } from "~/utils/urls";
-import { action } from "../actions/to.$id.subs.server";
-import { loader } from "../loaders/to.$id.subs.server";
-export { action, loader };
-
-import styles from "./to.$id.subs.module.css";
-
-export default function TournamentSubsPage() {
- const user = useUser();
- const data = useLoaderData();
- const tournament = useTournament();
-
- if (tournament.everyBracketOver) {
- return ;
- }
-
- return (
-
- {!tournament.teamMemberOfByUser(user) && user ? (
-
- ) : null}
- {data.subs.map((sub) => {
- return
;
- })}
-
- );
-}
-
-function AddOrEditSubButton() {
- const { t } = useTranslation(["tournament"]);
- const data = useLoaderData();
- const tournament = useTournament();
-
- const buttonText = data.hasOwnSubPost
- ? t("tournament:subs.editPost")
- : t("tournament:subs.addPost");
-
- if (!tournament.canAddNewSubPost) {
- return (
- {buttonText}}
- >
- {data.hasOwnSubPost
- ? "Sub post can't be edited anymore since registration has closed"
- : "Sub post can't be added anymore since registration has closed"}
-
- );
- }
-
- return (
-
- {buttonText}
-
- );
-}
-
-function SubInfoSection({
- sub,
-}: {
- sub: SerializeFrom["subs"][number];
-}) {
- const { t } = useTranslation(["common", "tournament"]);
- const user = useUser();
- const tournament = useTournament();
-
- const infos = [
-
-
- {sub.canVc === 1
- ? t("tournament:subs.canVC")
- : sub.canVc === 2
- ? t("tournament:subs.listenOnlyVC")
- : t("tournament:subs.noVC")}
-
,
- ];
- if (sub.plusTier) {
- infos.push(/);
- infos.push(+{sub.plusTier}
);
- }
- if (sub.country) {
- infos.push(/);
- infos.push();
- }
-
- return (
-
-
-
-
- {sub.username}
-
-
- {infos}
-
- {t("tournament:subs.prefersToPlay")}
-
-
- {sub.bestWeapons.map((wpn) => (
-
- ))}
-
- {sub.okWeapons ? (
- <>
-
- {t("tournament:subs.canPlay")}
-
-
- {sub.okWeapons.map((wpn) => (
-
- ))}
-
- >
- ) : null}
- {sub.message ? (
- {sub.message}
- ) : null}
-
- {user?.id === sub.userId || tournament.isOrganizer(user) ? (
-
-
- }
- >
- {t("common:actions.delete")}
-
-
-
- ) : null}
-
- );
-}
+export { loader } from "../loaders/to.$id.subs.server";
diff --git a/app/features/tournament-subs/tournament-subs-constants.ts b/app/features/tournament-subs/tournament-subs-constants.ts
deleted file mode 100644
index 269019af1..000000000
--- a/app/features/tournament-subs/tournament-subs-constants.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export const TOURNAMENT_SUB = {
- WEAPON_POOL_MAX_SIZE: 5,
- MESSAGE_MAX_LENGTH: 280,
-};
diff --git a/app/features/tournament-subs/tournament-subs-schemas.server.ts b/app/features/tournament-subs/tournament-subs-schemas.server.ts
deleted file mode 100644
index d0c1145f0..000000000
--- a/app/features/tournament-subs/tournament-subs-schemas.server.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { z } from "zod";
-import {
- id,
- processMany,
- removeDuplicates,
- safeJSONParse,
- weaponSplId,
-} from "~/utils/zod";
-import { TOURNAMENT_SUB } from "./tournament-subs-constants";
-
-export const subSchema = z.object({
- canVc: z.coerce.number().int().min(0).max(2),
- bestWeapons: z.preprocess(
- processMany(safeJSONParse, removeDuplicates),
- z.array(weaponSplId).min(1).max(TOURNAMENT_SUB.WEAPON_POOL_MAX_SIZE),
- ),
- okWeapons: z.preprocess(
- processMany(safeJSONParse, removeDuplicates),
- z.array(weaponSplId).max(TOURNAMENT_SUB.WEAPON_POOL_MAX_SIZE),
- ),
- message: z.string().max(TOURNAMENT_SUB.MESSAGE_MAX_LENGTH).nullish(),
- visibility: z.enum(["+1", "+2", "+3", "ALL"]).default("ALL"),
-});
-
-export const deleteSubSchema = z.object({
- userId: id,
-});
diff --git a/app/features/tournament/TournamentRepository.server.ts b/app/features/tournament/TournamentRepository.server.ts
index 0b5971acc..48d695cac 100644
--- a/app/features/tournament/TournamentRepository.server.ts
+++ b/app/features/tournament/TournamentRepository.server.ts
@@ -123,13 +123,20 @@ export async function findById(id: number) {
).as("staff"),
jsonArrayFrom(
eb
- .selectFrom("TournamentSub")
+ .selectFrom("TournamentTeamMember")
+ .innerJoin(
+ "TournamentTeam",
+ "TournamentTeam.id",
+ "TournamentTeamMember.tournamentTeamId",
+ )
.select(({ fn }) => [
- "TournamentSub.visibility",
+ sql`'ALL'`.as("visibility"),
fn.countAll().as("count"),
])
- .where("TournamentSub.tournamentId", "=", id)
- .groupBy("TournamentSub.visibility"),
+ .where("TournamentTeam.tournamentId", "=", id)
+ // xxx: this is not correct
+ .where("TournamentTeam.isPlaceholder", "=", 1)
+ .where("TournamentTeamMember.isStayAsSub", "=", 1),
).as("subCounts"),
jsonArrayFrom(
eb
@@ -252,6 +259,7 @@ export async function findById(id: number) {
).as("team"),
])
.where("TournamentTeam.tournamentId", "=", id)
+ .where("TournamentTeam.isPlaceholder", "=", 0)
.orderBy("TournamentTeam.seed", "asc")
.orderBy("TournamentTeam.createdAt", "asc")
.orderBy("TournamentTeam.id", "asc"),
@@ -485,6 +493,7 @@ export function forShowcase() {
),
)
.whereRef("TournamentTeam.tournamentId", "=", "Tournament.id")
+ .where("TournamentTeam.isPlaceholder", "=", 0)
.where((eb) =>
eb.or([
eb("TournamentTeamCheckIn.checkedInAt", "is not", null),
diff --git a/app/features/tournament/TournamentTeamRepository.server.ts b/app/features/tournament/TournamentTeamRepository.server.ts
index d5b850e61..c2bb0f581 100644
--- a/app/features/tournament/TournamentTeamRepository.server.ts
+++ b/app/features/tournament/TournamentTeamRepository.server.ts
@@ -149,6 +149,7 @@ export function create({
tournamentTeamId: tournamentTeam.id,
userId,
isOwner: 1,
+ role: "OWNER",
inGameName: ownerInGameName,
})
.execute();
diff --git a/app/features/tournament/actions/to.$id.join.server.ts b/app/features/tournament/actions/to.$id.join.server.ts
index 32e3bbc95..82a340f0b 100644
--- a/app/features/tournament/actions/to.$id.join.server.ts
+++ b/app/features/tournament/actions/to.$id.join.server.ts
@@ -6,6 +6,7 @@ import {
clearTournamentDataCache,
tournamentFromDB,
} from "~/features/tournament-bracket/core/Tournament.server";
+import * as TournamentLFGRepository from "~/features/tournament-lfg/TournamentLFGRepository.server";
import * as UserRepository from "~/features/user-page/UserRepository.server";
import invariant from "~/utils/invariant";
import {
@@ -81,6 +82,7 @@ export const action: ActionFunction = async ({ request, params }) => {
? "DELETE"
: "LEAVE";
+ await TournamentLFGRepository.leaveLfg({ userId: user.id, tournamentId });
joinTeam({
userId: user.id,
newTeamId: teamToJoin.id,
diff --git a/app/features/tournament/actions/to.$id.register.server.ts b/app/features/tournament/actions/to.$id.register.server.ts
index e72b6f714..a13201a3f 100644
--- a/app/features/tournament/actions/to.$id.register.server.ts
+++ b/app/features/tournament/actions/to.$id.register.server.ts
@@ -11,6 +11,7 @@ import {
clearTournamentDataCache,
tournamentFromDB,
} from "~/features/tournament-bracket/core/Tournament.server";
+import * as TournamentLFGRepository from "~/features/tournament-lfg/TournamentLFGRepository.server";
import { deleteSub } from "~/features/tournament-subs/queries/deleteSub.server";
import * as UserRepository from "~/features/user-page/UserRepository.server";
import { logger } from "~/utils/logger";
@@ -120,6 +121,10 @@ export const action: ActionFunction = async ({ request, params }) => {
"Team name already taken for this tournament",
);
+ await TournamentLFGRepository.leaveLfg({
+ userId: user.id,
+ tournamentId,
+ });
await TournamentTeamRepository.create({
ownerInGameName: await inGameNameIfNeeded({
tournament,
@@ -277,6 +282,10 @@ export const action: ActionFunction = async ({ request, params }) => {
message: "The user is banned from events hosted by this organization",
});
+ await TournamentLFGRepository.leaveLfg({
+ userId: data.userId,
+ tournamentId,
+ });
joinTeam({
userId: data.userId,
newTeamId: ownTeam.id,
diff --git a/app/features/tournament/routes/to.$id.register.tsx b/app/features/tournament/routes/to.$id.register.tsx
index f223e7c2e..6d193faa3 100644
--- a/app/features/tournament/routes/to.$id.register.tsx
+++ b/app/features/tournament/routes/to.$id.register.tsx
@@ -74,6 +74,8 @@ import {
import { useTournament } from "./to.$id";
export { loader, action };
+// xxx: handle placeholder team made
+// xxx: allow promoting members to managers
export default function TournamentRegisterPage() {
const isMounted = useIsMounted();
const tournament = useTournament();
diff --git a/app/features/tournament/routes/to.$id.tsx b/app/features/tournament/routes/to.$id.tsx
index 5ba782dc6..2eb1868dc 100644
--- a/app/features/tournament/routes/to.$id.tsx
+++ b/app/features/tournament/routes/to.$id.tsx
@@ -181,11 +181,13 @@ export function TournamentLayout() {
>
{t("tournament:tabs.teams", { count: tournament.ctx.teams.length })}
- {!tournament.everyBracketOver && tournament.subsFeatureEnabled && (
-
- {t("tournament:tabs.subs", { count: subsCount() })}
+ {!tournament.isInvitational && !tournament.everyBracketOver ? (
+
+ {tournament.registrationOpen
+ ? t("tournament:tabs.looking")
+ : t("tournament:tabs.subs", { count: subsCount() })}
- )}
+ ) : null}
{tournament.hasStarted && !tournament.everyBracketOver ? (
{t("tournament:tabs.streams", {
diff --git a/app/features/user-page/UserRepository.server.ts b/app/features/user-page/UserRepository.server.ts
index dca6f5e36..ffd32d9f9 100644
--- a/app/features/user-page/UserRepository.server.ts
+++ b/app/features/user-page/UserRepository.server.ts
@@ -372,43 +372,6 @@ export function findByFriendCode(friendCode: string) {
.execute();
}
-export async function findSubDefaultsByUserId(userId: number) {
- const user = await db
- .selectFrom("User")
- .select(["User.vc", "User.qWeaponPool", "User.lastSubMessage"])
- .where("User.id", "=", userId)
- .executeTakeFirst();
-
- if (!user) return null;
-
- const vcToCanVc = (vc: "YES" | "NO" | "LISTEN_ONLY"): 0 | 1 | 2 => {
- if (vc === "YES") return 1;
- if (vc === "NO") return 0;
- return 2;
- };
-
- const qWeaponPool = user.qWeaponPool ?? [];
-
- let bestWeapons = qWeaponPool
- .filter((w) => w.isFavorite === 1)
- .map((w) => w.weaponSplId);
- let okWeapons = qWeaponPool
- .filter((w) => w.isFavorite === 0)
- .map((w) => w.weaponSplId);
-
- if (bestWeapons.length === 0) {
- bestWeapons = okWeapons;
- okWeapons = [];
- }
-
- return {
- canVc: vcToCanVc(user.vc),
- bestWeapons,
- okWeapons,
- message: user.lastSubMessage,
- };
-}
-
export async function findLeanById(id: number) {
const user = await db
.selectFrom("User")
diff --git a/app/routes.ts b/app/routes.ts
index 789f0a2f9..47dd700c8 100644
--- a/app/routes.ts
+++ b/app/routes.ts
@@ -120,8 +120,9 @@ export default [
route("results", "features/tournament/routes/to.$id.results.tsx"),
route("streams", "features/tournament/routes/to.$id.streams.tsx"),
+ route("looking", "features/tournament-lfg/routes/to.$id.looking.tsx"),
+
route("subs", "features/tournament-subs/routes/to.$id.subs.tsx"),
- route("subs/new", "features/tournament-subs/routes/to.$id.subs.new.tsx"),
route(
"divisions",
diff --git a/app/utils/kysely.server.ts b/app/utils/kysely.server.ts
index 0b8405f0a..5e21e389d 100644
--- a/app/utils/kysely.server.ts
+++ b/app/utils/kysely.server.ts
@@ -115,5 +115,6 @@ export function concatUserSubmittedImagePrefix(
]);
}
-export type JSONColumnTypeNullable =
- ColumnType;
+export type JSONColumnTypeNullable<
+ SelectType extends object | string | number | null,
+> = ColumnType;
diff --git a/app/utils/team-name-data.ts b/app/utils/team-name-data.ts
new file mode 100644
index 000000000..7c80abfa4
--- /dev/null
+++ b/app/utils/team-name-data.ts
@@ -0,0 +1,2055 @@
+// Auto-generated team name data from adj.json and sub.json
+// Regenerate with: npx tsx scripts/generate-team-names.ts
+
+export const ADJECTIVES = [
+ "Inkless",
+ "Clocked-Out",
+ "Attention-Seeking",
+ "Southpaw",
+ "Splatsville's",
+ "Big-Money",
+ "Normcore",
+ "Faithful",
+ "Defensive",
+ "Champion",
+ "Royal",
+ "Ammo Knights",
+ "Naut Couture",
+ "Man-o'-Wardrobe",
+ "Crush Station",
+ "Hotlantis",
+ "Tableturf Battling",
+ "Research-Conducting",
+ "Apprentice",
+ "Part-Time",
+ "Go-Gettin'",
+ "Overachieving",
+ "Profreshional",
+ "Legendary",
+ "Mid-Rush",
+ "High-Tide",
+ "Cohock-Chargin'",
+ "Foggy",
+ "Low-Tide",
+ "Fall-Lovin'",
+ "Spring-Lovin'",
+ "Winter-Lovin'",
+ "Summer-Lovin'",
+ "Blissed-Out",
+ "Trendy",
+ "Sharp-Edged",
+ "Stylin'",
+ "Cool",
+ "Basic",
+ "Traditional",
+ "Trendsetting",
+ "All-Natural",
+ "Sizzlin'",
+ "Day-Seizing",
+ "Fall-Themed",
+ "Spring-Fresh",
+ "Captivating",
+ "The",
+ "Low-Rolling",
+ "High-Rolling",
+ "Commanding",
+ "Wannabe",
+ "Deadeye",
+ "Reckless",
+ "Chilled-Out",
+ "Silent",
+ "Ambidextrous",
+ "Stand-Up",
+ "Livestreaming",
+ "Meditative",
+ "Right-Handed",
+ "Low-Key",
+ "Pro",
+ "Amateur",
+ "Inkopolis Square's",
+ "Inkopolis's Own",
+ "Inkopolitan",
+ "Feral",
+ "Downtown",
+ "Country-Born",
+ "Greedy",
+ "Trending",
+ "Shortcut-Taking",
+ "Fluid",
+ "Optimistic",
+ "Daydreamin'",
+ "Clever",
+ "Hungry, Hungry",
+ "Competitive",
+ "Slapdash",
+ "Half-Hearted",
+ "Early-Bird",
+ "Daring",
+ "Hungry",
+ "Athletic",
+ "Early-Morning",
+ "Inkslinging",
+ "Up-and-at-'Em",
+ "Deep-Sea",
+ "Rise-and-Shine",
+ "Beachy",
+ "Freewheelin'",
+ "Old-Fashioned",
+ "World's Best",
+ "National",
+ "Stormy",
+ "Lonely",
+ "Groundbreaking",
+ "Assertive",
+ "Super-Scary",
+ "Vacationing",
+ "Nostalgic",
+ "Homecoming",
+ "Hard-Workin'",
+ "Sweet-Toothed",
+ "Seafaring",
+ "Self-Taught",
+ "Summer-Breaking",
+ "Processed",
+ "Not-So-Fresh",
+ "Salty",
+ "Destined",
+ "Gullible",
+ "Wiped-Out",
+ "Rockin'",
+ "Retro",
+ "Careless",
+ "Avaricious",
+ "Mischievous",
+ "Soft",
+ "Unrivaled",
+ "Swole",
+ "Hip",
+ "Brand-New",
+ "Full-Throttle",
+ "Peppy",
+ "Law-Breaking",
+ "At-Work",
+ "The First",
+ "Promising",
+ "Ink-Sloshing",
+ "Wave-Riding",
+ "Swanky",
+ "Fresh-Catch",
+ "Scientific",
+ "Orderly",
+ "The Lone",
+ "Sappy",
+ "Dodge-Rolling",
+ "Notable",
+ "Explosive",
+ "Shy",
+ "Calamari",
+ "Fishy",
+ "Puffed-Up",
+ "Fuzzy",
+ "The Great",
+ "Birthday-Celebratin'",
+ "Whimsical",
+ "Dangerous",
+ "Legit",
+ "Dandy",
+ "Ultra",
+ "Floaty",
+ "Indoor",
+ "Ink-Covered",
+ "Ink-Dripping",
+ "Fresh",
+ "Wicked-Fresh",
+ "Stale",
+ "A Simple",
+ "8-Tentacled",
+ "10-Tentacled",
+ "Midwinter",
+ "Mind-Blowing",
+ "Lifelong",
+ "Top-Ranked",
+ "Actual",
+ "Growing",
+ "Studious",
+ "Overpowering",
+ "Spineless",
+ "Headstrong",
+ "Leaping",
+ "Running",
+ "Protective",
+ "Attacking",
+ "Watchful",
+ "Cooperative",
+ "Hidden",
+ "Submersible",
+ "Brute",
+ "Stealthy",
+ "Everyday",
+ "Gaming",
+ "Logical",
+ "Reliable",
+ "Gentle",
+ "Confident",
+ "Serious",
+ "Mild-Mannered",
+ "Cheerful",
+ "High-End",
+ "Chatty",
+ "Cash-Stacking",
+ "Absentminded",
+ "Beloved",
+ "Windy",
+ "Watery",
+ "Sneaky",
+ "Fiery",
+ "Incandescent",
+ "Young",
+ "Organized",
+ "Perfect",
+ "Mysterious",
+ "Central",
+ "Fierce",
+ "Ambiguous",
+ "Instantaneous",
+ "Exemplary",
+ "Tear-Stained",
+ "Traveling",
+ "Next Year's",
+ "Twilight",
+ "The One and Only",
+ "Hibernating",
+ "Sharp-Eyed",
+ "Lost",
+ "Procrastinating",
+ "Honorary",
+ "Tasteless",
+ "Merciless",
+ "Infinite",
+ "Sleepy",
+ "Time-Traveling",
+ "Suave",
+ "Howling",
+ "Phantasmagoric",
+ "Celestial",
+ "Aged",
+ "Lethal",
+ "Secret",
+ "Esoteric",
+ "Exhausted",
+ "Better-Than-You",
+ "All-Purpose",
+ "Gleaming",
+ "Midday",
+ "Unnoticed",
+ "Unprecedented",
+ "Tenacious",
+ "Scorching",
+ "Daytime",
+ "Meaty",
+ "Enigmatic",
+ "Unflappable",
+ "Savvy",
+ "Fleeing",
+ "Drained",
+ "Reincarnated",
+ "Sun-Dried",
+ "AKA",
+ "Superhuman",
+ "Subterranean",
+ "Massive",
+ "Terrestrial",
+ "Important",
+ "Jumbo",
+ "Oceanic",
+ "Big",
+ "Easygoing",
+ "Ancient",
+ "Multiplying",
+ "Fully Automatic",
+ "Glimmering",
+ "Seasick",
+ "Unblemished",
+ "Fightin'",
+ "Front-Line",
+ "Endangered",
+ "Persistent",
+ "Sacred",
+ "Local",
+ "Turn-of-the-Century",
+ "This Century's Best",
+ "Aquarium-Bound",
+ "Fan-Favorite",
+ "Rising",
+ "Spicy",
+ "Lightning-Fast",
+ "Awe-Inspiring",
+ "Midnight",
+ "Stark-White",
+ "Deep-Red",
+ "Bright-Blue",
+ "Pitch-Black",
+ "Woodsy",
+ "Next-Generation",
+ "Novice",
+ "Sensational",
+ "Dead-Tired",
+ "Dazzling",
+ "Tagalong",
+ "Little",
+ "Third-Eye-Opened",
+ "Early-Summer",
+ "Alabaster",
+ "Pure",
+ "Grown-Up",
+ "Partying",
+ "Dapper",
+ "Evil",
+ "Jet-Black",
+ "Vindictive",
+ "Self-Proclaimed",
+ "Selfie-Snapping",
+ "Eternity's",
+ "Self-Identified",
+ "Childish",
+ "Job-Lovin'",
+ "Late-Summer",
+ "Cutting-Edge",
+ "Mountain-Dwelling",
+ "Picture-Taking",
+ "Yesterday's",
+ "Graceful",
+ "First-Wave",
+ "All-New",
+ "The Very First",
+ "The Final",
+ "This Year's",
+ "Limited-Edition",
+ "Ever-Present",
+ "Booming",
+ "Crimson",
+ "Steady",
+ "Nosy",
+ "Enlightened",
+ "Aloof",
+ "Hearty",
+ "Artsy",
+ "Flying",
+ "Die-Hard",
+ "Fortunate",
+ "Last Year's",
+ "The True Ultimate",
+ "Impish",
+ "Shining",
+ "Forgetful",
+ "Heartfelt",
+ "Unarmed",
+ "Sweet",
+ "Sentimental",
+ "Sandy",
+ "Overheated",
+ "Catch-of-the-Day",
+ "Pool-Ready",
+ "Anachronistic",
+ "Supersonic",
+ "Lovely",
+ "Movie-Lovin'",
+ "Shadowy",
+ "Fated",
+ "Cosmic",
+ "Zipped-Lips",
+ "Gone-Fishin'",
+ "24-Hour",
+ "Mostly",
+ "Otherworldly",
+ "Junior-Grade",
+ "Wild",
+ "A Real",
+ "Lucky",
+ "Seasoned",
+ "Soft-and-Fluffy",
+ "100%",
+ "Veggie-Lovin'",
+ "Fluffy",
+ "Modern",
+ "Lost-and-Found",
+ "Won't-Lose",
+ "Auspicious",
+ "Innocent",
+ "Miraculous",
+ "Poser",
+ "Comic-Lovin'",
+ "Vulnerable",
+ "Light",
+ "Bomb-tastic",
+ "Fluttering",
+ "Buoyant",
+ "Wibbly-Wobbly",
+ "Fruit-Lovin'",
+ "Dark",
+ "Fragrant",
+ "Fashionable",
+ "Big-Time",
+ "Drenched",
+ "Bearded",
+ "Frosty",
+ "Incredible",
+ "Faraway",
+ "Flashy",
+ "Fame-Seeking",
+ "Bursting",
+ "Designer-Label",
+ "Swoon-Worthy",
+ "Strict",
+ "Slippery",
+ "Painted",
+ "Climactic",
+ "Nameless",
+ "Nice",
+ "Muddy",
+ "Neighborly",
+ "Tidy",
+ "Barbed",
+ "Pointy",
+ "Big Honkin'",
+ "Smooth",
+ "Iced",
+ "Occasional",
+ "Just Another",
+ "Essential",
+ "Diving",
+ "Dynamite",
+ "Overall",
+ "Bargain-Binned",
+ "Slim",
+ "Out-of-Options",
+ "Steampunky",
+ "Numb",
+ "Hiking",
+ "Wandering",
+ "Unyielding",
+ "Economical",
+ "Gothic",
+ "Covert",
+ "High-Performing",
+ "Praiseworthy",
+ "Arcade-Hopping",
+ "Pretty Good",
+ "The GOAT",
+ "Glistening",
+ "Sparkling",
+ "Gigantified",
+ "Camera-Facing",
+ "Clockwork",
+ "Solid",
+ "Fully",
+ "Unperceived",
+ "Carnivorous",
+ "Homebound",
+ "Fish-Lovin'",
+ "Sketching",
+ "Precocious",
+ "Easy-Peasy",
+ "Made-to-Order",
+ "Elegant",
+ "Water-Breathing",
+ "The Eternal",
+ "Instant",
+ "The Usual",
+ "Ready-to-Go",
+ "Lively",
+ "Unstoppable",
+ "Underground",
+ "The Real Ultimate",
+ "Overflowing",
+ "Toon-Lovin'",
+ "Splendid",
+ "Warm-and-Cozy",
+ "Amped-Up",
+ "Outdoorsy",
+ "Tech-Minded",
+ "Lovable",
+ "amiibo-Collecting",
+ "5-Year-Planning",
+ "Decade-Experienced",
+ "Crab-N-Go",
+ "Summer-Fun",
+ "Winter-Ready",
+ "Not-Streaming",
+ "Daylit",
+ "Dawning",
+ "Realistic",
+ "Lightly Snacking",
+ "Late-Night",
+ "Comfy-Cozy",
+ "Fearful",
+ "Winter-Breaking",
+ "So-Called",
+ "Intuitive",
+ "A Complex",
+ "The Original",
+ "Second-Generation",
+ "The Best",
+ "Roaming",
+ "DJ",
+ "Badlands",
+ "Snowy",
+ "Seaside",
+ "Big-Sky",
+ "Dry",
+ "Raw",
+ "Awesome",
+ "SUPERFRESH",
+ "Splatoon",
+ "Avant-Garde",
+ "Punk",
+ "Mod",
+ "Goth",
+ "Rockabilly",
+ "Casual",
+ "Sporty",
+ "Breakin'",
+ "Glam",
+ "Vintage",
+ "Bohemian",
+ "Grunge",
+ "Hip-Hop",
+ "Thunderous",
+ "Turbo",
+ "Wizened",
+ "Today's",
+ "Virtual",
+ "Half",
+ "Microscopic",
+ "Swift",
+ "Everyone's",
+ "Carefree",
+ "New",
+ "High-Earning",
+ "Pool-Frequenting",
+ "Revived",
+ "Preposterous",
+ "Mighty",
+ "Chill",
+ "Gossip-Loving",
+ "Social",
+ "Crafty",
+ "Hopeful",
+ "Sealed",
+ "Theoretically Possible",
+ "Rookie",
+ "Sleeved",
+ "World's First",
+ "Nature-Loving",
+ "Inflexible",
+ "Prized",
+ "Faux",
+ "Reasonable",
+ "Unfettered",
+ "Unthinkable",
+ "Phantom",
+ "Cherished",
+ "Gold",
+ "Silver",
+ "Undefeated",
+ "New Year's",
+ "This Year's Final",
+ "Newly Released",
+ "Tonight's",
+ "Reclusive",
+ "Stewed",
+ "Cyber-Brainiac",
+ "Magical",
+ "Of-the-Moment",
+ "Forevermore",
+ "Anticipated",
+ "Automated",
+ "Drab",
+ "Reflective",
+ "Hoary",
+ "Parallel World's",
+ "Power-Concealing",
+ "Nourishing",
+ "Highly Nutritious",
+ "Ascending",
+ "Sprightly",
+ "Always-in-Our-Hearts",
+ "Sanguine",
+ "Daybreaking",
+ "Repeating",
+ "Endlessly Ringing",
+ "Listless",
+ "In-Season",
+ "Rampaging",
+ "Scintillating",
+ "Abyssal",
+ "Originally from",
+ "Unknowing",
+ "Currently Obsessed with",
+ "Nonstop",
+ "Vivacious",
+ "Aggressive",
+ "Immediate",
+ "Natural",
+ "High-Calorie",
+ "Low-Calorie",
+ "Recently",
+ "Esteemed",
+ "Treasured",
+ "Existing",
+ "Nonexistent",
+ "On-the-Job",
+ "Choosy",
+ "Delicate",
+ "Haphazard",
+ "Golden Egg–Like",
+ "Ice-Cold",
+ "Low-Battery",
+ "Fully Charged",
+ "Noontime",
+ "Break-Time",
+ "Unavailable",
+ "Protecting",
+ "Long-Awaited",
+ "Now-Stronger",
+ "Breath-Holding",
+ "Genuine",
+ "Nimble",
+ "Movable",
+ "At-Home",
+ "Hometown",
+ "Shooter-Using",
+ "Roller-Using",
+ "Charger-Using",
+ "Slosher-Using",
+ "Splatling-Using",
+ "Dualie-Using",
+ "Brella-Using",
+ "Blaster-Using",
+ "Brush-Using",
+ "Stringer-Using",
+ "Splatana-Using",
+ "Replete with",
+ "Watered",
+ "Triumphant",
+ "Tasteful",
+ "Outstanding",
+ "Stylish",
+ "Three-Star",
+ "Scheduled",
+ "Trailblazing",
+ "Satiating",
+ "Newish",
+ "Same-as-Ever",
+ "Multifaceted",
+ "Orthodox",
+ "Regal",
+ "Classic",
+ "Old-School",
+ "Simplistic",
+ "Advanced",
+ "Prototypical",
+ "Sincere",
+ "Flexible",
+ "Creative",
+ "Classy",
+ "Slender",
+ "Genteel",
+ "Shrouded-in-Mystery",
+ "Novel",
+ "Brisk",
+ "Undying",
+ "Powerful",
+ "Unwavering",
+ "Heroic",
+ "Swaggering",
+ "Astute",
+ "Pushy",
+ "Resilient",
+ "Burly",
+ "Resourceful",
+ "Lax",
+ "Daredevilish",
+ "Magnetic",
+ "Dynamic",
+ "Unique",
+ "Keen-and-Nimble",
+ "Cordial",
+ "Dignified",
+ "Flowy",
+ "Cryptic",
+ "High-Spirited",
+ "Mind-Changing",
+ "Chic",
+ "Witty",
+ "Cheery",
+ "Vivid",
+ "Rare",
+ "Closed-Off",
+ "Surreal",
+ "Methodical",
+ "Gorgeous",
+ "Cute",
+ "Awkward",
+ "Deep",
+ "Unadorned",
+ "Cushy",
+ "Gallant",
+ "Adept",
+ "Invigorating",
+ "Veiled",
+ "Discreet",
+ "Frank",
+ "Rational",
+ "Charming",
+ "Quiet",
+ "Wise",
+ "Introverted",
+ "Abstract",
+ "Sharp",
+ "Gutsy",
+ "Authentic",
+ "Learned",
+ "Experienced",
+ "Gifted",
+ "Ingenious",
+ "Healthy",
+ "Honest",
+ "Just-Right",
+ "Blustery",
+ "Funky",
+ "Exciting",
+ "Exuberant",
+ "Well-Mannered",
+ "Dutiful",
+ "Modest",
+ "Prestigious",
+ "Affable",
+ "Sociable",
+ "Good-Humored",
+ "Urbane",
+ "Meek",
+ "Well-Rounded",
+ "Well-Behaved",
+ "Upstanding",
+ "Big-Hearted",
+ "Influential",
+ "Mold-Breaking",
+ "Ghastly",
+ "Academic",
+ "Plucky",
+ "Cheeky",
+ "Glorious",
+ "Maniacal",
+ "Magnificent",
+ "Energetic",
+ "Misplaced",
+ "De Facto",
+ "333x",
+ "Ageless",
+ "Apparent",
+ "Ostensible",
+ "Heart-Thumping",
+ "Starchy",
+ "Barely",
+ "Narrowly",
+ "Amusing",
+ "Excited",
+ "Swishy",
+ "The Genuine",
+ "Unnamed",
+ "Glory-Hogging",
+ "Prickly",
+ "Lackadaisical",
+ "Surviving",
+ "Eccentric",
+ "Idiosyncratic",
+ "Rule-Breaking",
+ "Snazzy",
+ "Zesty",
+ "Most Popular",
+ "On-the-Rise",
+ "Skyrocketing",
+ "World-Wise",
+ "Assisting",
+ "Covering",
+ "Bourgeois",
+ "Clean",
+ "Almighty",
+ "Robust",
+ "Enlivened",
+ "Proactive",
+ "Unruly",
+ "Fully Committed",
+ "Delightful",
+ "Flower-Shaped",
+ "Extraordinary",
+ "Original",
+ "Top-Notch",
+ "Clandestine",
+ "Painful",
+ "Topical",
+ "Lavish",
+ "Thrilling",
+ "Deluxe",
+ "Triple",
+ "Twin",
+ "Double",
+ "Individual",
+ "Comical",
+ "Imperfect",
+ "Uncommon",
+ "Peerless",
+ "Vertebrate",
+ "Expert",
+ "Giant",
+ "Stalwart",
+ "Exquisite",
+ "Acrobatic",
+ "Captivated",
+ "Fledgling",
+ "Relaxed",
+ "Playful",
+ "True",
+ "Candid",
+ "Straightforward",
+ "Unalloyed",
+ "Magnanimous",
+ "Stoic",
+ "Angelic",
+ "Single-Minded",
+ "Bland",
+ "Rich",
+ "Earnest",
+ "Forward-Plunging",
+ "Total",
+ "Passionate",
+ "Inexorable",
+ "Plain",
+ "Thoughtful",
+ "Frizzy",
+ "Somehow",
+ "Upright",
+ "Peaceful",
+ "Ephemeral",
+ "Bright",
+ "Laid-Back",
+ "Dried-Out",
+ "Metallic",
+ "Common",
+ "Nonchalant",
+ "Stubborn",
+ "Custom",
+ "Self-Centered",
+ "Determined",
+ "Indomitable",
+ "Unswerving",
+ "Steadfast",
+ "All-Out",
+ "Imperative",
+ "Undiscouraged",
+ "Enduring",
+ "Persisting",
+ "Completing",
+ "Piercing",
+ "Hyped",
+ "Climbing",
+ "Proper",
+ "Noteworthy",
+ "Relieved",
+ "Brazen",
+ "Unaffected",
+ "Exceptional",
+ "Challenging",
+ "Intellectual",
+ "Fair",
+ "Discerning",
+ "First-Rate",
+ "Strong-Willed",
+ "Formidable",
+ "Direct",
+ "Ambitious",
+ "Sturdy",
+ "Dogged",
+ "Dependable",
+ "Meticulous",
+ "Enterprising",
+ "Courageous",
+ "Resolved",
+ "Zealous",
+ "Insider",
+ "Skilled",
+ "Enthusiastic",
+ "Wonderful",
+ "Heavy-Duty",
+ "Struggling",
+ "Ultimate",
+ "Convenient",
+ "Restless",
+ "Composed",
+ "Comprehensible",
+ "Abandoned",
+ "Last",
+ "Supernatural",
+ "Clamorous",
+ "Dense",
+ "Airy",
+ "My Very Own",
+ "Improved",
+ "Blue",
+ "Ridiculous",
+ "Very",
+ "Really",
+ "Dauntless",
+ "Perpetual",
+ "Recently Graduated",
+ "Retirement-Age",
+ "Commencing",
+ "Genius",
+ "Praying",
+ "Sometime",
+ "Occasionally",
+ "Overdone",
+ "Lunchtime",
+ "Speciality",
+ "Phoenix-Like",
+ "Slacking",
+ "Grateful",
+ "Absolute",
+ "Blatant",
+ "Wistful",
+ "Airborne",
+ "Temporary",
+ "Conspicuous",
+ "Mesmerizing",
+ "Bronzed",
+ "Normal",
+ "Lightly Seasoned",
+ "Pampered",
+ "Simulated",
+ "Splatfest's",
+ "Splatfest Battle's",
+ "Rugged",
+ "Grand Festival",
+ "Monochrome",
+ "Long-Sleeved",
+ "Collab-Loving",
+ "Swaggy",
+ "Refreshing",
+ "Simmering",
+ "Positive",
+ "Dim",
+ "Stable",
+ "Merry",
+ "Ebullient",
+ "Tailwind-Riding",
+ "Peak-Condition",
+ "Thank You for",
+ "Super-Duper Lucky",
+ "Super-Rare",
+ "One-in-a-Million",
+ "Ace",
+ "Astonishing",
+ "Adored",
+ "Charmed",
+ "Shell-Out",
+ "Splatlandian",
+ "Indian Ocean",
+ "North Atlantic",
+ "North Pacific",
+ "Antarctic Ocean",
+ "Arctic Ocean",
+ "South Atlantic",
+ "South Pacific",
+ "The Shoal's Own",
+ "Official-Grizzco",
+ "The Crater's",
+ "The City's Own",
+ "Lobby-Bound",
+ "Third-Gen",
+ "Chaotic",
+ "Midsummer",
+ "Alternan",
+ "Reminiscent",
+ "Memversal",
+ "Side Orderly",
+ "Swarming",
+ "Marching",
+ "Battering",
+ "Jelletonal",
+ "Drizzling",
+ "Panicking",
+ "Whirling",
+ "Springing",
+ "Gushing",
+ "Spawning",
+ "Homing",
+ "Towering",
+ "Layered-Rotator",
+ "Elusive-Bounder",
+ "Intensifying-Harmony",
+ "Fun",
+ "The Strongest",
+ "Crusty",
+ "Saddle-Straddlin'",
+ "Streamlined",
+ "Wandercrust",
+ "Tender",
+ "Moving",
+] as const;
+
+export const SUBTITLES_PLURALIZED = [
+ "Sploosh-o-matic Users",
+ "Splattershot Jr. Users",
+ "Splash-o-matic Users",
+ "Aerospray Users",
+ "Splattershot Users",
+ ".52 Gal Users",
+ "N-ZAP Users",
+ "Splattershot Pro Users",
+ ".96 Gal Users",
+ "Jet Squelcher Users",
+ "Luna Blaster Users",
+ "Blaster Users",
+ "Range Blaster Users",
+ "Clash Blaster Users",
+ "Rapid Blaster Users",
+ "Rapid Blaster Pro Users",
+ "L-3 Nozzlenose Users",
+ "H-3 Nozzlenose Users",
+ "Squeezer Users",
+ "Carbon Roller Users",
+ "Splat Roller Users",
+ "Dynamo Roller Users",
+ "Flingza Roller Users",
+ "Inkbrush Users",
+ "Octobrush Users",
+ "Squiffer Users",
+ "Splat Charger Users",
+ "E-liter 4K Users",
+ "Bamboozler 14 Users",
+ "Goo Tuber Users",
+ "Slosher Users",
+ "Tri-Slosher Users",
+ "Sloshing Machine Users",
+ "Bloblobber Users",
+ "Explosher Users",
+ "Mini Splatling Users",
+ "Heavy Splatling Users",
+ "Hydra Splatling Users",
+ "Ballpoint Splatling Users",
+ "Nautilus Users",
+ "Dapple Dualies Users",
+ "Splat Dualies Users",
+ "Glooga Dualies Users",
+ "Dualie Squelchers Users",
+ "Tetra Dualies Users",
+ "Splat Brella Users",
+ "Tenta Brella Users",
+ "Undercover Brella Users",
+ "Tri-Stringer Users",
+ "Splatana Stamper Users",
+ "REEF-LUX 450 Users",
+ "Splatana Wiper Users",
+ "Splatterscope Users",
+ "E-liter 4K Scope Users",
+ "Splattershot Nova Users",
+ "Big Swig Roller Users",
+ "Snipewriter 5H Users",
+ "S-BLAST '92 Users",
+ "Painbrush Users",
+ "Dread Wringer Users",
+ "Heavy Edit Splatling Users",
+ "Douser Dualies FF Users",
+ "Recycled Brella 24 Mk I Users",
+ "Wellstring V Users",
+ "Decavitator Users",
+ "Splat Bomb Users",
+ "Suction Bomb Users",
+ "Burst Bomb Users",
+ "Curling Bomb Users",
+ "Autobomb Users",
+ "Ink Mine Users",
+ "Sprinkler Users",
+ "Point Sensor Users",
+ "Toxic Mist Users",
+ "Splash Wall Users",
+ "Squid Beakon Users",
+ "Fizzy Bomb Users",
+ "Torpedo Users",
+ "Angle Shooter Users",
+ "Ink Storm Users",
+ "Trizooka Users",
+ "Ultra Stamp Users",
+ "Big Bubbler Users",
+ "Inkjet Users",
+ "Wave Breaker Users",
+ "Zipcaster Users",
+ "Reefslider Users",
+ "Crab Tank Users",
+ "Booyah Bomb Users",
+ "Ink Vac Users",
+ "Tenta Missiles Users",
+ "Killer Wail 5.1 Users",
+ "Triple Inkstrike Users",
+ "Tacticooler Users",
+ "Super Chump Users",
+ "Kraken Royale Users",
+ "Triple Splashdown Users",
+ "Splattercolor Screen Users",
+ "SquidForce Models",
+ "Zink Models",
+ "Krak-On Models",
+ "Rockenberg Models",
+ "Zekko Models",
+ "Forge Models",
+ "Firefin Models",
+ "Skalop Models",
+ "Splash Mob Models",
+ "Inkline Models",
+ "Tentatek Models",
+ "Takoroka Models",
+ "Annaki Models",
+ "Enperry Models",
+ "Toni Kensa Models",
+ "Z+F Models",
+ "Barazushi Models",
+ "Emberz Models",
+ "Cuttlegear Models",
+ "Squid Squad Fans",
+ "Chirpy Chips Fans",
+ "Hightide Era Fans",
+ "Wet Floor Fans",
+ "Bottom Feeders Fans",
+ "Ink Theory Fans",
+ "SashiMori Fans",
+ "Diss-Pair Fans",
+ "ω-3 Fans",
+ "Turquoise October Fans",
+ "Dedf1sh Fans",
+ "C-Side Superfans",
+ "Front Roe Fans",
+ "Damp Socks Fans",
+ "Yoko & the Gold Bazookas Fans",
+ "H2Whoa Fans",
+ "Riot Act Fans",
+ "Now or Never Seven Fans",
+ "Hippies",
+ "Barbers",
+ "Double-Buns",
+ "Wave Riders",
+ "Pigtail Pros",
+ "Headbangers",
+ "Topknot Knights",
+ "Sharp Dressers",
+ "Buzz-Cuts",
+ "Ponytails",
+ "Spiked Hairs",
+ "Bowl Cuts",
+ "Braid Bearers",
+ "Classics",
+ "Wet Hairs",
+ "Cornrowses",
+ "Curly Hairs",
+ "Punks",
+ "Afros",
+ "Big Curlses",
+ "Fades",
+ "Wanderers",
+ "Greasers",
+ "Hipster s",
+ "Callie Fans",
+ "Naut Couture Regulars",
+ "Marina Fans",
+ "Frye Fans",
+ "Crush Station Regulars",
+ "Hotlantis Regulars",
+ "Squid Sisters Fans",
+ "Deep Cut Fans",
+ "Off the Hook Fans",
+ "Pearl Fans",
+ "Shiver Fans",
+ "Ammo Knights Regulars",
+ "Man-o'-Wardrobe Regulars",
+ "Marie Fans",
+ "Big Man Fans",
+ "Clam Blitz Enthusiasts",
+ "Splat Zones Enthusiasts",
+ "Rainmaker Enthusiasts",
+ "Tower Control Enthusiasts",
+ "Salmon Runners",
+ "Turf Warriors",
+ "Story Mode Heros",
+ "Clam Blitz Legends",
+ "Splat Zones Legends",
+ "Tower Control Legends",
+ "Turf War Legends",
+ "Rainmaker Legends",
+ "Salmon Run Legends",
+ "Story Mode Legends",
+ "Inklings",
+ "Octolings",
+ "Tee Models",
+ "Jacket Models",
+ "Shirt Models",
+ "Sneaker Models",
+ "Knit Models",
+ "Trendsetters",
+ "Hat Models",
+ "Glasses Models",
+ "Boot Models",
+ "Card Collectors",
+ "Item Collectors",
+ "Fashionistas",
+ "Locker Rock Stars",
+ "Squid Surgers",
+ "Squid Rollers",
+ "All-Arounders",
+ "Jumpers",
+ "Dodge Rollers",
+ "Slow Starters",
+ "Splat Bombers",
+ "Guardians",
+ "Raiders",
+ "Manta Rays",
+ "Octarians",
+ "Bears",
+ "Jellyfishes",
+ "Humans",
+ "Sea Creatures",
+ "Mammals",
+ "Artists",
+ "Pop Stars",
+ "Clock Punchers",
+ "Eel Tamers",
+ "Shark Tamers",
+ "Clerks",
+ "Office Drones",
+ "Ringleaders",
+ "Champs",
+ "Bass Droppers",
+ "Princes",
+ "Princesses",
+ "Brothers",
+ "Sisters",
+ "City Boys",
+ "City Girls",
+ "Grooms",
+ "Brides",
+ "Kings",
+ "Queens",
+ "Bros",
+ "Sises",
+ "Oden Bowls",
+ "Party Lovers",
+ "Surfers",
+ "Festivalgoers",
+ "Flowers",
+ "Heat Haters",
+ "Snowpersons",
+ "Firecrackers",
+ "Eyes of the Storm",
+ "Fish",
+ "Deity of Destructions",
+ "Dogs",
+ "Dragons",
+ "Cats",
+ "Dinosaurs",
+ "Cephalopods",
+ "Bivalves",
+ "Dolphins",
+ "Bookworms",
+ "CEOs",
+ "Assistants",
+ "Athletes",
+ "Agents",
+ "Risk-Takers",
+ "Coaches",
+ "Coordinators",
+ "Comedians",
+ "Scientists",
+ "Samurais",
+ "Spies",
+ "Dancers",
+ "Designers",
+ "Ninjas",
+ "Part-Timers",
+ "Pilots",
+ "Hunters",
+ "Planners",
+ "Programmers",
+ "Producers",
+ "Painters",
+ "Managers",
+ "Models",
+ "Writers",
+ "Executives",
+ "Astronauts",
+ "Singers",
+ "Presidents",
+ "Vikings",
+ "Students",
+ "Directors",
+ "Leads",
+ "Anglers",
+ "Assistant Managers",
+ "Security Guards",
+ "Researchers",
+ "Authors",
+ "Workers",
+ "Butlers",
+ "VPs",
+ "Board Members",
+ "Gofers",
+ "Artisans",
+ "Teachers",
+ "Village Leaders",
+ "Head Honchos",
+ "Detectives",
+ "Mountaineers",
+ "Professors",
+ "Secretaries",
+ "Beauticians",
+ "Middle Managers",
+ "Vice Presidents",
+ "Adventurers",
+ "Comic Artists",
+ "Heros",
+ "Chefs",
+ "Layabouts",
+ "Big Mouths",
+ "Chow Hounds",
+ "Dynamos",
+ "Hotheads",
+ "Octoling Soldiers",
+ "Power Eggs",
+ "Villains",
+ "Fams",
+ "Turtles",
+ "Forms",
+ "Knights",
+ "Eggs",
+ "Entertainers",
+ "Comrades",
+ "Superheros",
+ "Jokesters",
+ "Magicians",
+ "Brainiacs",
+ "Fish Balls",
+ "Materials",
+ "Sushis",
+ "Callers",
+ "Seafood",
+ "Mincemeats",
+ "Squid Jerkies",
+ "Booyahs",
+ "Vitamins",
+ "Weapons",
+ "Dried Fish",
+ "Muscles",
+ "Illusions",
+ "Shields",
+ "Leaders",
+ "Viewers",
+ "Systems",
+ "Tournament Competitors",
+ "Agent 1",
+ "Agent 2",
+ "Agent 4",
+ "Agent 8",
+ "AIs",
+ "Object of Affections",
+ "Such and Suches",
+ "Outlaws",
+ "Heels",
+ "Tabletop Gamers",
+ "Androids",
+ "Legends",
+ "Squid Inks",
+ "Squid Lives",
+ "Squid Rings",
+ "Squid, Inc.s",
+ "Inks",
+ "Influencers",
+ "Moray Eels",
+ "Elegances",
+ "Big Deals",
+ "Big Bosses",
+ "Otakus",
+ "Dance Masters",
+ "Freebies",
+ "Kids",
+ "Pack Leaders",
+ "Photographers",
+ "Fried Chickens",
+ "Extroverts",
+ "Carpaccios",
+ "Synth Players",
+ "Guitarists",
+ "Campers",
+ "Cupids",
+ "Crabs",
+ "Creators",
+ "Grooves",
+ "Gamers",
+ "Ultimate Weapons",
+ "Survivalists",
+ "Iconoclasts",
+ "Supporters",
+ "Sharks",
+ "Salmon Lives",
+ "Swimmers",
+ "Styles",
+ "Strikers",
+ "Streamers",
+ "Gelatins",
+ "Celebs",
+ "Taste Testers",
+ "Title Holders",
+ "Octopus Inks",
+ "Octo Lives",
+ "Tuna Tatakis",
+ "Challengers",
+ "Butterfingerses",
+ "Drivers",
+ "Drummers",
+ "Tornados",
+ "Trainers",
+ "Party Animals",
+ "Polyglots",
+ "Rebels",
+ "Workhorses",
+ "Introverts",
+ "Clubgoers",
+ "Stand-Up Comics",
+ "Fighters",
+ "Mirages",
+ "Planktons",
+ "Brand Loyalists",
+ "Freelancers",
+ "Snowstorms",
+ "Friends",
+ "Headliners",
+ "Seniors",
+ "Vocalists",
+ "Board Gamers",
+ "Bosses",
+ "Bodyguards",
+ "Mermaids",
+ "Masks",
+ "Superstars",
+ "Maniacs",
+ "Mysteries",
+ "Bifocal Wearers",
+ "Therapists",
+ "Members",
+ "Lifestyles",
+ "Rappers",
+ "Remote Workers",
+ "Rookies",
+ "Robots",
+ "Enthusiasts",
+ "Ordinary People",
+ "Lone Wolves",
+ "Paupers",
+ "Prodigies",
+ "Monarches",
+ "Hot-Spring Enjoyers",
+ "Fish Finders",
+ "Saltwater Fish",
+ "Umibozus",
+ "Cold Chills",
+ "Sightseers",
+ "Prime Cuts",
+ "Moods",
+ "Memories",
+ "Demons",
+ "Giants",
+ "Fish Prints",
+ "Mirrors",
+ "Avocado Rolls",
+ "Moons",
+ "Fists",
+ "Cavemans",
+ "Experts",
+ "Novices",
+ "Foremans",
+ "Ruffians",
+ "Comic Reliefs",
+ "Advisers",
+ "Sashimis",
+ "Legendary Warriors",
+ "Underlings",
+ "Senseis",
+ "Free Spirits",
+ "Sidekicks",
+ "Fledglings",
+ "Employees",
+ "Greases",
+ "Beginners",
+ "Imps",
+ "Higher-Ups",
+ "Trainees",
+ "Rookie of the Years",
+ "Freshmans",
+ "Truth Tellers",
+ "Kingpins",
+ "Lives of the Party",
+ "Wallflowers",
+ "Lives Itself",
+ "Humanities",
+ "Supervisors",
+ "Fillets",
+ "Inkantations",
+ "Penny-Pinchers",
+ "Mountaintop Gurus",
+ "Mentors",
+ "Warriors",
+ "Berserkers",
+ "Blade of Grasses",
+ "Entities",
+ "Suns",
+ "Representatives",
+ "Cabinet Members",
+ "Freshwater Fish",
+ "Group Members",
+ "Style Icons",
+ "Bugs",
+ "Contenders",
+ "Birds",
+ "Stans",
+ "Fisherfolks",
+ "Apprentices",
+ "Targets",
+ "Maestros",
+ "Angels",
+ "Island Dwellers",
+ "Tofus",
+ "Foreheads",
+ "Plumbers",
+ "Content Creators",
+ "Merpersons",
+ "Sidesteppers",
+ "Flotsams",
+ "Subordinates",
+ "Walls",
+ "Predators",
+ "Equations",
+ "Archdemons",
+ "Pros",
+ "Lives",
+ "Fuzzballs",
+ "Actors",
+ "Ghosts",
+ "Atlanteans",
+ "Teardrops",
+ "Ronins",
+ "Rice Bowls",
+ "Outfits",
+ "Palettes",
+ "Snackses",
+ "Followers",
+ "Marinades",
+ "Switch-Hitters",
+ "Stir-Fries",
+ "Catfish",
+ "Grand Masters",
+ "Cubs",
+ "DJs",
+ "Paradigms",
+ "Geeks",
+ "Heads",
+ "Fits",
+ "B-Boys",
+ "B-Girls",
+ "Skaters",
+ "Dukes",
+ "Duchesses",
+ "Celebrities",
+ "Mariners",
+ "Fashion Designers",
+ "Commanders",
+ "Store Managers",
+ "Patrons",
+ "Sparklers",
+ "Fantasy Lovers",
+ "Horrorboroses",
+ "Cohozunas",
+ "Floats",
+ "Nuances",
+ "Aliens",
+ "Newcomers",
+ "Hopes",
+ "MCs",
+ "Special Attacks",
+ "Special Spaces",
+ "Rankers",
+ "Aces in the Hole",
+ "Decks",
+ "Hole Cardses",
+ "Test Takers",
+ "Personnels",
+ "Philosophers",
+ "One-Hit Wonders",
+ "Organizations",
+ "Sleeve Collectors",
+ "Rock Climbers",
+ "Bungee Jumpers",
+ "Soliloquys",
+ "Sea-Cucumber Phones",
+ "Ripples",
+ "Salts",
+ "Peppers",
+ "Mayonnaises",
+ "Seasonings",
+ "Values",
+ "Time Killers",
+ "Clumps",
+ "Repeat Customers",
+ "Mushrooms",
+ "Stars",
+ "Talented People",
+ "Superpersons",
+ "Seaweeds",
+ "Kelps",
+ "Marine Snows",
+ "Bento Boxes",
+ "Fish Eggses",
+ "Stews",
+ "Simmered Foods",
+ "Prix Fixes",
+ "Curries",
+ "Aficionados",
+ "Informations",
+ "Heart Valves",
+ "Hearts",
+ "Rocks",
+ "Jazzes",
+ "Solos",
+ "Soup Stocks",
+ "Soups",
+ "Salads",
+ "Champions",
+ "Rice Balls",
+ "Paradoxes",
+ "Dressings",
+ "Sauces",
+ "Spices",
+ "Specialists",
+ "Specialty Shops",
+ "Bandleaders",
+ "Runners",
+ "Speedruns",
+ "Jukeboxes",
+ "Demigods",
+ "Laughs",
+ "Theme Parks",
+ "Hairstyles",
+ "Encores",
+ "Settlers",
+ "Storms",
+ "Breezes",
+ "Observers",
+ "Right Arms",
+ "Sea Urchins",
+ "Pianos",
+ "Guitars",
+ "Keyboards",
+ "Drums",
+ "Castanets",
+ "Recorders",
+ "Cymbalses",
+ "Musical Instruments",
+ "Prime Ministers",
+ "Project Managers",
+ "Physicians",
+ "Lawyers",
+ "Accountants",
+ "Bodybuilders",
+ "Lads",
+ "Lasses",
+ "Bases",
+ "Sambas",
+ "Machos",
+ "Cool Guys",
+ "Thespians",
+ "Hard Workers",
+ "Ship Captains",
+ "Chaperones",
+ "Time Travelers",
+ "Puddings",
+ "Counterfeits",
+ "Alter Egos",
+ "Grillers",
+ "Smallfries",
+ "Cohocks",
+ "Snatchers",
+ "Steelheads",
+ "Steel Eels",
+ "Scrappers",
+ "Stingers",
+ "Mawses",
+ "Drizzlers",
+ "Flyfishes",
+ "Fish Sticks",
+ "Flipper-Floppers",
+ "Slammin' Lids",
+ "Big Shots",
+ "Goldies",
+ "Motherships",
+ "Mudmouths",
+ "Glowflies",
+ "Ranks",
+ "Romanticists",
+ "Steel Bodies",
+ "Flavors",
+ "Elites",
+ "Determined Spirits",
+ "Person of Influences",
+ "Trailblazers",
+ "Casts",
+ "Aces",
+ "Number Ones",
+ "Foremost Figures",
+ "Mainstays",
+ "Emperors",
+ "Paragons",
+ "Intermediaries",
+ "Prototypes",
+ "Schemers",
+ "Brave Warriors",
+ "Person of Cultures",
+ "Honor Students",
+ "Brains",
+ "Trappers",
+ "Intellectuals",
+ "Impractical Theories",
+ "Dislikes",
+ "Meisters",
+ "Governors",
+ "Office Chiefs",
+ "Caretakers",
+ "Bank Presidents",
+ "Harlequins",
+ "Focal Points",
+ "Event Organizers",
+ "VIPs",
+ "Performers",
+ "Woodcutters",
+ "Sous-Chefs",
+ "Engineers",
+ "Contractors",
+ "Prosperous Merchants",
+ "Referees",
+ "Manila Clams",
+ "Concierges",
+ "Tentacles",
+ "Nobles",
+ "Temps",
+ "Pranksters",
+ "Great Regents",
+ "Main Characters",
+ "Leading Actors",
+ "Leading Actresses",
+ "Dark Horses",
+ "Key Figures",
+ "Masterminds",
+ "Tacticians",
+ "Vagabonds",
+ "Voyagers",
+ "Wandering Hermits",
+ "Nomads",
+ "Bounty Hunters",
+ "Timeline Jumpers",
+ "Generations",
+ "Elders",
+ "Captains",
+ "Guests",
+ "String Pullers",
+ "Galleries",
+ "Big Names",
+ "Regular Customers",
+ "Partners",
+ "Cyborgs",
+ "Successes",
+ "Shadow Commanders",
+ "Helpers",
+ "Backers",
+ "Millionaires",
+ "Custodians",
+ "Lieutenants",
+ "Origins",
+ "Czars",
+ "Cheer Teams",
+ "Staffs",
+ "Factotums",
+ "Energies",
+ "Engines",
+ "Consultants",
+ "Tutors",
+ "Puppet Masters",
+ "Buddies",
+ "Critics",
+ "Luck Pushers",
+ "Teams",
+ "Buffoons",
+ "Players",
+ "TV Personalities",
+ "Announcers",
+ "Geniuses",
+ "Golfers",
+ "Batters",
+ "Pitchers",
+ "Catchers",
+ "Benchwarmers",
+ "Virtuosos",
+ "Conductors",
+ "Musical Performers",
+ "Lyricists",
+ "Pianists",
+ "Ingenues",
+ "Narrators",
+ "Pro Wrestlers",
+ "Sprinters",
+ "Boxers",
+ "Extras",
+ "Cinematographers",
+ "Stunt Performers",
+ "Descendants",
+ "Offsprings",
+ "Sponsors",
+ "Suggestions",
+ "Facilitators",
+ "Showstoppers",
+ "Possessors",
+ "Housekeepers",
+ "Traders",
+ "Fortune Tellers",
+ "Globe-Trotters",
+ "Pinch Hitters",
+ "Leadoff Hitters",
+ "Musketeers",
+ "Crowds",
+ "Circles",
+ "Flocks",
+ "Cooperators",
+ "Units",
+ "Armies",
+ "Coteries",
+ "Quartets",
+ "Choruses",
+ "Podium Finishers",
+ "Guilds",
+ "Groups",
+ "Horses",
+ "Rabbits",
+ "Pandas",
+ "Koalas",
+ "Squirrels",
+ "Sea Otters",
+ "Elephants",
+ "Giraffes",
+ "Whales",
+ "Tigers",
+ "Lions",
+ "Hamsters",
+ "Deer",
+ "Bookkeepers",
+ "Idea People",
+ "Rock 'n' Rollers",
+ "Controllers",
+ "Storytellers",
+ "Illustrators",
+ "Finger Painters",
+ "Scriveners",
+ "Founders",
+ "Families",
+ "Craftsmans",
+ "Machines",
+ "Navigators",
+ "Inventors",
+ "Parties",
+ "Playmakers",
+ "Young Leaders",
+ "Mascots",
+ "Fairies",
+ "Joke Tellers",
+ "Voice Actors",
+ "Learners",
+ "Tenderfoots",
+ "Heads of the Class",
+ "University Students",
+ "Architects",
+ "Costumiers",
+ "Ferocious Fighters",
+ "Knowledgeable People",
+ "Lofty Dreams",
+ "Wizes",
+ "Prominent Figures",
+ "Grapplers",
+ "Natural Enemies",
+ "Horseshoe Crabs",
+ "Pond Snails",
+ "Nautiluses",
+ "Coconut Crabs",
+ "Sea Anemones",
+ "Goldfish",
+ "Horse Mackerels",
+ "Devil Rays",
+ "Sea Slugs",
+ "Shrimp",
+ "Spider Crabs",
+ "Rivals",
+ "Weaknesses",
+ "Types",
+ "Sweethearts",
+ "Imitations",
+ "Processes",
+ "Gatherings",
+ "Shoals",
+ "Crews",
+ "Beasts",
+ "Rulers",
+ "Fossils",
+ "Wardens",
+ "Collectors",
+ "Crushes",
+ "Souls",
+ "State of Minds",
+ "Marathons",
+ "Good People",
+ "Clients",
+ "Dolls",
+ "Noble Scions",
+ "Protagonists",
+ "Symbols",
+ "Authorities",
+ "Contributors",
+ "Cartoon Characters",
+ "Plushies",
+ "Spirits",
+ "Thoroughbreds",
+ "Person of Notes",
+ "Professionals",
+ "Gourmets",
+ "Sumo Wrestlers",
+ "Family Trees",
+ "Soldiers",
+ "Order of Knightses",
+ "Victors",
+ "Villagers",
+ "School Principals",
+ "Protectors",
+ "Explorers",
+ "Seekers",
+ "Pugilists",
+ "Paladins",
+ "Door Guards",
+ "Rangers",
+ "Monks",
+ "Sages",
+ "Doctors",
+ "Blacksmiths",
+ "Postdocs",
+ "Dignitaries",
+ "Artistes",
+ "Interns",
+ "Alchemists",
+ "Security Details",
+ "Ores",
+ "Guides",
+ "Chauffeurs",
+ "Bloggers",
+ "Operators",
+ "Librarians",
+ "Objet d'Arts",
+ "Successors",
+ "Owners",
+ "Proxies",
+ "Pupils",
+ "Disciples",
+ "Scholars",
+ "Graduates",
+ "Collegians",
+ "PhD Candidates",
+ "Ketchups",
+ "Bamboo Shoots",
+ "Pops",
+ "Psychics",
+ "Arbiters",
+ "Litterateurs",
+ "Megalodontias",
+ "Recollections",
+ "Stylists",
+ "Entrepreneurs",
+ "Musicians",
+ "Party Stars",
+ "Mercenaries",
+ "Alliances",
+ "Bards",
+ "Otherworldly Beings",
+ "Chaoses",
+ "Noobs",
+ "Vanguards",
+ "Luminaries",
+ "Playings",
+ "Lucky Ducks",
+ "Protected Specieses",
+ "Chosen Ones",
+ "Gadabouts",
+ "Point Guards",
+ "Optimists",
+ "Daredevils",
+ "Prizewinners",
+ "Youths",
+ "Squid",
+ "Octopuses",
+ "Salmon",
+ "Boys",
+ "Girls",
+ "Agent 3",
+ "Fathers",
+ "Mothers",
+ "Dads",
+ "Moms",
+ "Sons",
+ "Daughters",
+ "Big Brothers",
+ "Big Sisters",
+ "Little Brothers",
+ "Little Sisters",
+ "Grandpas",
+ "Grandmas",
+ "Grandchilds",
+ "Uncles",
+ "Aunts",
+ "Youngsters",
+ "Pets",
+ "World Champs",
+ "Mediators",
+ "Beta Testers",
+ "Revelers",
+ "Languendos",
+ "Andantes",
+ "Lentos",
+ "Portals",
+ "Capricciosos",
+ "Alla Mambos",
+ "Accelerandos",
+ "Spiccatos",
+ "Trionfales",
+ "Accordos",
+ "Arpeggios",
+ "Nobilmentes",
+ "Asynchronous Rondos",
+ "Pinging Marciales",
+ "Parallel Canons",
+ "Tableturf Battlers",
+ "Card Gamers",
+ "Travelers",
+ "Cyclists",
+ "Tempuras",
+ "Boosters",
+ "Resiliences",
+ "Finales",
+] as const;
diff --git a/app/utils/team-name.ts b/app/utils/team-name.ts
new file mode 100644
index 000000000..6428dfd61
--- /dev/null
+++ b/app/utils/team-name.ts
@@ -0,0 +1,12 @@
+import { ADJECTIVES, SUBTITLES_PLURALIZED } from "./team-name-data";
+
+/** Generates a random team name by combining a Splatoon 3 title adjective with a pluralized subtitle e.g. "Prestigious Heat Haters" */
+export function randomTeamName() {
+ const adjective = ADJECTIVES[Math.floor(Math.random() * ADJECTIVES.length)];
+ const subtitle =
+ SUBTITLES_PLURALIZED[
+ Math.floor(Math.random() * SUBTITLES_PLURALIZED.length)
+ ];
+
+ return `${adjective} ${subtitle}`;
+}
diff --git a/app/utils/urls.ts b/app/utils/urls.ts
index d88a84152..8d27184d2 100644
--- a/app/utils/urls.ts
+++ b/app/utils/urls.ts
@@ -358,7 +358,7 @@ export const tournamentJoinPage = ({
inviteCode: string;
}) => `/to/${tournamentId}/join?code=${inviteCode}`;
export const tournamentSubsPage = (tournamentId: number) => {
- return `/to/${tournamentId}/subs`;
+ return `/to/${tournamentId}/looking`;
};
export const tournamentStreamsPage = (tournamentId: number) => {
return `/to/${tournamentId}/streams`;
diff --git a/docs/dev/database-relations.md b/docs/dev/database-relations.md
index 1119f94bd..6608ce510 100644
--- a/docs/dev/database-relations.md
+++ b/docs/dev/database-relations.md
@@ -175,6 +175,31 @@ erDiagram
The database structure is mimicking the `brackets-manager.js` library. See this issue for a schema: [https://github.com/Drarig29/brackets-manager.js/issues/111#issuecomment-997417423](https://github.com/Drarig29/brackets-manager.js/issues/111#issuecomment-997417423)
+## Tournament Teams
+
+```mermaid
+erDiagram
+ Tournament ||--o{ TournamentTeam : has
+ TournamentTeam ||--|{ TournamentTeamMember : has
+ User ||--o{ TournamentTeamMember : member_of
+ TournamentTeam }o--o| AllTeam : team
+```
+
+### Notes
+
+- `isPlaceholder` - temporary team created automatically when an unregistered user joins the LFG queue
+- `isLooking` - team is visible in the LFG page
+- `teamId` - optional link to a sendou.ink team (for avatar/name)
+
+#### Placeholder team lifecycle
+
+- Unregistered user joins LFG -> placeholder team created (`isPlaceholder: 1`)
+- Registered user joins LFG -> existing team reused, only `isLooking` flag toggled
+- Placeholder user registers for tournament -> placeholder team deleted, new regular team created
+- Placeholder + placeholder merge -> surviving team set to `isPlaceholder: 0`
+- Placeholder + registered team merge -> registered team always survives
+- On merge, each member's `isStayAsSub` preference is preserved
+
## Tournament organizations
```mermaid
diff --git a/locales/da/forms.json b/locales/da/forms.json
index 07f42617a..5c83fdf19 100644
--- a/locales/da/forms.json
+++ b/locales/da/forms.json
@@ -220,5 +220,8 @@
"errors.alreadyFriends": "",
"errors.friendRequestExists": "",
"labels.type": "",
- "labels.visibility": ""
+ "labels.visibility": "",
+ "labels.note": "",
+ "labels.stayAsSub": "",
+ "bottomTexts.stayAsSub": ""
}
diff --git a/locales/da/q.json b/locales/da/q.json
index b186d30aa..691361fc9 100644
--- a/locales/da/q.json
+++ b/locales/da/q.json
@@ -94,6 +94,7 @@
"looking.groups.actions.removeManager": "",
"looking.groups.actions.kick": "",
"looking.groups.actions.leaveGroup": "",
+ "looking.groups.actions.stopLooking": "",
"looking.groups.actions.leaveQ": "",
"looking.groups.actions.goBack": "",
"looking.groups.adder.quickAdd": "",
@@ -103,6 +104,7 @@
"looking.groups.adder.select": "",
"looking.groups.editNote": "",
"looking.groups.addNote": "",
+ "looking.groups.stayAsSub": "",
"looking.replay": "",
"looking.teamSP": "",
"looking.teamSP.calculated": "",
@@ -112,6 +114,8 @@
"looking.rankCalculating": "",
"looking.allTiers": "",
"looking.joinQPrompt": "",
+ "looking.joinQPromptTeam": "",
+ "looking.joinQPromptMembers": "",
"looking.range.or": "",
"looking.range.or.explanation": "",
"match.header": "",
diff --git a/locales/da/tournament.json b/locales/da/tournament.json
index 10f23e585..614ac15e7 100644
--- a/locales/da/tournament.json
+++ b/locales/da/tournament.json
@@ -8,6 +8,7 @@
"tabs.results": "",
"tabs.streams": "Streams ({{count}})",
"tabs.subs": "Suppleanter ({{count}})",
+ "tabs.looking": "",
"pre.steps.header": "Fuldfør disse trin for at spille",
"pre.steps.name": "Holdnavn",
"pre.steps.roster": "Holdmedlemmer",
diff --git a/locales/de/forms.json b/locales/de/forms.json
index 4a29f12e1..935777bb3 100644
--- a/locales/de/forms.json
+++ b/locales/de/forms.json
@@ -220,5 +220,8 @@
"errors.alreadyFriends": "",
"errors.friendRequestExists": "",
"labels.type": "",
- "labels.visibility": ""
+ "labels.visibility": "",
+ "labels.note": "",
+ "labels.stayAsSub": "",
+ "bottomTexts.stayAsSub": ""
}
diff --git a/locales/de/q.json b/locales/de/q.json
index 3d6b052b2..bcd044095 100644
--- a/locales/de/q.json
+++ b/locales/de/q.json
@@ -94,6 +94,7 @@
"looking.groups.actions.removeManager": "",
"looking.groups.actions.kick": "",
"looking.groups.actions.leaveGroup": "",
+ "looking.groups.actions.stopLooking": "",
"looking.groups.actions.leaveQ": "",
"looking.groups.actions.goBack": "",
"looking.groups.adder.quickAdd": "",
@@ -103,6 +104,7 @@
"looking.groups.adder.select": "",
"looking.groups.editNote": "",
"looking.groups.addNote": "",
+ "looking.groups.stayAsSub": "",
"looking.replay": "",
"looking.teamSP": "",
"looking.teamSP.calculated": "",
@@ -112,6 +114,8 @@
"looking.rankCalculating": "",
"looking.allTiers": "",
"looking.joinQPrompt": "",
+ "looking.joinQPromptTeam": "",
+ "looking.joinQPromptMembers": "",
"looking.range.or": "",
"looking.range.or.explanation": "",
"match.header": "",
diff --git a/locales/de/tournament.json b/locales/de/tournament.json
index 5648774ff..7d03dc284 100644
--- a/locales/de/tournament.json
+++ b/locales/de/tournament.json
@@ -8,6 +8,7 @@
"tabs.results": "Ergebnisse",
"tabs.streams": "Streams ({{count}})",
"tabs.subs": "Ersatzspieler ({{count}})",
+ "tabs.looking": "",
"pre.steps.header": "Folge diesen Schritten, um zu spielen",
"pre.steps.name": "Teamname",
"pre.steps.roster": "Volles Roster",
diff --git a/locales/en/forms.json b/locales/en/forms.json
index e4ff1d414..051ee3d8d 100644
--- a/locales/en/forms.json
+++ b/locales/en/forms.json
@@ -220,5 +220,8 @@
"errors.alreadyFriends": "You are already friends with this user",
"errors.friendRequestExists": "A friend request already exists between you and this user",
"labels.type": "Type",
- "labels.visibility": "Visibility"
+ "labels.visibility": "Visibility",
+ "labels.note": "Note",
+ "labels.stayAsSub": "Stay as sub",
+ "bottomTexts.stayAsSub": "If you don't find a full team before registration ends, you will stay as a solo sub for the duration of the tournament"
}
diff --git a/locales/en/q.json b/locales/en/q.json
index 31d5d47cf..08b45dfcf 100644
--- a/locales/en/q.json
+++ b/locales/en/q.json
@@ -94,6 +94,7 @@
"looking.groups.actions.removeManager": "Remove manager",
"looking.groups.actions.kick": "Kick",
"looking.groups.actions.leaveGroup": "Leave group",
+ "looking.groups.actions.stopLooking": "Stop looking",
"looking.groups.actions.leaveQ": "Leave queue",
"looking.groups.actions.goBack": "Go back",
"looking.groups.adder.quickAdd": "Quick add",
@@ -103,6 +104,7 @@
"looking.groups.adder.select": "Select user...",
"looking.groups.editNote": "Edit note",
"looking.groups.addNote": "Add note",
+ "looking.groups.stayAsSub": "Sub",
"looking.replay": "Replay",
"looking.teamSP": "Team SP",
"looking.teamSP.calculated": "Team SP calculated",
@@ -112,6 +114,8 @@
"looking.rankCalculating": "Less than {{count}} sets played. Rank is still calculating...",
"looking.allTiers": "All tiers",
"looking.joinQPrompt": "Join the queue to find a group",
+ "looking.joinQPromptTeam": "Look for a team",
+ "looking.joinQPromptMembers": "Look for more members",
"looking.range.or": "or",
"looking.range.or.explanation": "You will be matched with a group whose average rank is one of these. The exact rank is not shown to avoid behavior where groups only play against tiers lower than themselves.",
"match.header": "Match #{{number}}",
diff --git a/locales/en/tournament.json b/locales/en/tournament.json
index d61f0e6d0..bc05bfbf2 100644
--- a/locales/en/tournament.json
+++ b/locales/en/tournament.json
@@ -8,6 +8,7 @@
"tabs.results": "Results",
"tabs.streams": "Streams ({{count}})",
"tabs.subs": "Subs ({{count}})",
+ "tabs.looking": "LFG",
"pre.steps.header": "Complete these steps to play",
"pre.steps.name": "Team name",
"pre.steps.roster": "Full roster",
diff --git a/locales/es-ES/forms.json b/locales/es-ES/forms.json
index 648c910c1..138ba449a 100644
--- a/locales/es-ES/forms.json
+++ b/locales/es-ES/forms.json
@@ -220,5 +220,8 @@
"errors.alreadyFriends": "",
"errors.friendRequestExists": "",
"labels.type": "",
- "labels.visibility": ""
+ "labels.visibility": "",
+ "labels.note": "",
+ "labels.stayAsSub": "",
+ "bottomTexts.stayAsSub": ""
}
diff --git a/locales/es-ES/q.json b/locales/es-ES/q.json
index 920207573..637be6a72 100644
--- a/locales/es-ES/q.json
+++ b/locales/es-ES/q.json
@@ -94,6 +94,7 @@
"looking.groups.actions.removeManager": "Quitar mánager",
"looking.groups.actions.kick": "Expulsar",
"looking.groups.actions.leaveGroup": "Dejar grupo",
+ "looking.groups.actions.stopLooking": "",
"looking.groups.actions.leaveQ": "Dejar fila",
"looking.groups.actions.goBack": "Regresar",
"looking.groups.adder.quickAdd": "Añadir rapido",
@@ -103,6 +104,7 @@
"looking.groups.adder.select": "Seleccionar usuario...",
"looking.groups.editNote": "Editar nota",
"looking.groups.addNote": "Añadir nota",
+ "looking.groups.stayAsSub": "",
"looking.replay": "Repetir",
"looking.teamSP": "Fuerza Sendou de Equipo",
"looking.teamSP.calculated": "Fuerza Sendou de Equipo calculada",
@@ -112,6 +114,8 @@
"looking.rankCalculating": "Menos de {{count}} sets jugados. Rango aún se está calculando...",
"looking.allTiers": "Todos los niveles",
"looking.joinQPrompt": "Únete a la fila para encontrar un grupo",
+ "looking.joinQPromptTeam": "",
+ "looking.joinQPromptMembers": "",
"looking.range.or": "o",
"looking.range.or.explanation": "Serás emparejado con un grupo cuyo rango promedio sea uno de estos. El rango exacto no se muestra para evitar que los grupos jueguen únicamente contra niveles inferiores al suyo.",
"match.header": "Partido #{{number}}",
diff --git a/locales/es-ES/tournament.json b/locales/es-ES/tournament.json
index af5735621..70e5dab56 100644
--- a/locales/es-ES/tournament.json
+++ b/locales/es-ES/tournament.json
@@ -8,6 +8,7 @@
"tabs.results": "Resultados",
"tabs.streams": "Streams ({{count}})",
"tabs.subs": "Subs ({{count}})",
+ "tabs.looking": "",
"pre.steps.header": "Completa estos pasos para jugar",
"pre.steps.name": "Nombre de equipo",
"pre.steps.roster": "Equipo lleno",
diff --git a/locales/es-US/forms.json b/locales/es-US/forms.json
index a77c5c6cc..2b9c2a5c5 100644
--- a/locales/es-US/forms.json
+++ b/locales/es-US/forms.json
@@ -220,5 +220,8 @@
"errors.alreadyFriends": "",
"errors.friendRequestExists": "",
"labels.type": "",
- "labels.visibility": ""
+ "labels.visibility": "",
+ "labels.note": "",
+ "labels.stayAsSub": "",
+ "bottomTexts.stayAsSub": ""
}
diff --git a/locales/es-US/q.json b/locales/es-US/q.json
index 40f0f41b4..0ea526a65 100644
--- a/locales/es-US/q.json
+++ b/locales/es-US/q.json
@@ -94,6 +94,7 @@
"looking.groups.actions.removeManager": "Quitar mánager",
"looking.groups.actions.kick": "Expulsar",
"looking.groups.actions.leaveGroup": "Dejar grupo",
+ "looking.groups.actions.stopLooking": "",
"looking.groups.actions.leaveQ": "Dejar fila",
"looking.groups.actions.goBack": "Regresar",
"looking.groups.adder.quickAdd": "Añadir rápido",
@@ -103,6 +104,7 @@
"looking.groups.adder.select": "",
"looking.groups.editNote": "Editar nota",
"looking.groups.addNote": "Añadir nota",
+ "looking.groups.stayAsSub": "",
"looking.replay": "Repetir",
"looking.teamSP": "Fuerza Sendou de Equipo",
"looking.teamSP.calculated": "Fuerza Sendou de Equipo calculada",
@@ -112,6 +114,8 @@
"looking.rankCalculating": "Menos de {{count}} sets jugados. Rango aún se está calculando...",
"looking.allTiers": "Todos los niveles",
"looking.joinQPrompt": "Únete a la fila para encontrar un grupo",
+ "looking.joinQPromptTeam": "",
+ "looking.joinQPromptMembers": "",
"looking.range.or": "o",
"looking.range.or.explanation": "",
"match.header": "Partido #{{number}}",
diff --git a/locales/es-US/tournament.json b/locales/es-US/tournament.json
index c969d0d03..da9a70b6b 100644
--- a/locales/es-US/tournament.json
+++ b/locales/es-US/tournament.json
@@ -8,6 +8,7 @@
"tabs.results": "",
"tabs.streams": "Streams ({{count}})",
"tabs.subs": "Subs ({{count}})",
+ "tabs.looking": "",
"pre.steps.header": "Completa estos pasos para jugar",
"pre.steps.name": "Nombre de equipo",
"pre.steps.roster": "Equipo lleno",
diff --git a/locales/fr-CA/forms.json b/locales/fr-CA/forms.json
index cbd53dd3f..0fab283ca 100644
--- a/locales/fr-CA/forms.json
+++ b/locales/fr-CA/forms.json
@@ -220,5 +220,8 @@
"errors.alreadyFriends": "",
"errors.friendRequestExists": "",
"labels.type": "",
- "labels.visibility": ""
+ "labels.visibility": "",
+ "labels.note": "",
+ "labels.stayAsSub": "",
+ "bottomTexts.stayAsSub": ""
}
diff --git a/locales/fr-CA/q.json b/locales/fr-CA/q.json
index 6740dfbe3..24c58bfe1 100644
--- a/locales/fr-CA/q.json
+++ b/locales/fr-CA/q.json
@@ -94,6 +94,7 @@
"looking.groups.actions.removeManager": "",
"looking.groups.actions.kick": "",
"looking.groups.actions.leaveGroup": "",
+ "looking.groups.actions.stopLooking": "",
"looking.groups.actions.leaveQ": "",
"looking.groups.actions.goBack": "",
"looking.groups.adder.quickAdd": "",
@@ -103,6 +104,7 @@
"looking.groups.adder.select": "",
"looking.groups.editNote": "",
"looking.groups.addNote": "",
+ "looking.groups.stayAsSub": "",
"looking.replay": "",
"looking.teamSP": "",
"looking.teamSP.calculated": "",
@@ -112,6 +114,8 @@
"looking.rankCalculating": "",
"looking.allTiers": "",
"looking.joinQPrompt": "",
+ "looking.joinQPromptTeam": "",
+ "looking.joinQPromptMembers": "",
"looking.range.or": "",
"looking.range.or.explanation": "",
"match.header": "",
diff --git a/locales/fr-CA/tournament.json b/locales/fr-CA/tournament.json
index f38a1f5a8..230634f42 100644
--- a/locales/fr-CA/tournament.json
+++ b/locales/fr-CA/tournament.json
@@ -8,6 +8,7 @@
"tabs.results": "",
"tabs.streams": "Diffusions ({{count}})",
"tabs.subs": "Remplaçants ({{count}})",
+ "tabs.looking": "",
"pre.steps.header": "Complétez ces étapes pour jouer",
"pre.steps.name": "Nom de l'équipe",
"pre.steps.roster": "Participants",
diff --git a/locales/fr-EU/forms.json b/locales/fr-EU/forms.json
index 0845273ef..5b9a37602 100644
--- a/locales/fr-EU/forms.json
+++ b/locales/fr-EU/forms.json
@@ -220,5 +220,8 @@
"errors.alreadyFriends": "",
"errors.friendRequestExists": "",
"labels.type": "",
- "labels.visibility": ""
+ "labels.visibility": "",
+ "labels.note": "",
+ "labels.stayAsSub": "",
+ "bottomTexts.stayAsSub": ""
}
diff --git a/locales/fr-EU/q.json b/locales/fr-EU/q.json
index 435d59f5a..f2197eb9d 100644
--- a/locales/fr-EU/q.json
+++ b/locales/fr-EU/q.json
@@ -94,6 +94,7 @@
"looking.groups.actions.removeManager": "Rétrograder",
"looking.groups.actions.kick": "Kick",
"looking.groups.actions.leaveGroup": "Quitter le groupe",
+ "looking.groups.actions.stopLooking": "",
"looking.groups.actions.leaveQ": "Quitter la queue",
"looking.groups.actions.goBack": "Retourner en arrière",
"looking.groups.adder.quickAdd": "Ajouter rapidement",
@@ -103,6 +104,7 @@
"looking.groups.adder.select": "Selectionner un utilisateur...",
"looking.groups.editNote": "Modifier le note",
"looking.groups.addNote": "Ajouter la note",
+ "looking.groups.stayAsSub": "",
"looking.replay": "Rejouer",
"looking.teamSP": "Team SP",
"looking.teamSP.calculated": "Team SP calculé",
@@ -112,6 +114,8 @@
"looking.rankCalculating": "Moins de {{count}} sets jouer. Le rank est toujours en calcul...",
"looking.allTiers": "Tout les ranks",
"looking.joinQPrompt": "Rejoindre la queue pour trouvez un groupe",
+ "looking.joinQPromptTeam": "",
+ "looking.joinQPromptMembers": "",
"looking.range.or": "ou",
"looking.range.or.explanation": "",
"match.header": "Match #{{number}}",
diff --git a/locales/fr-EU/tournament.json b/locales/fr-EU/tournament.json
index 6145d53fb..55f9dec34 100644
--- a/locales/fr-EU/tournament.json
+++ b/locales/fr-EU/tournament.json
@@ -8,6 +8,7 @@
"tabs.results": "Resultats",
"tabs.streams": "Diffusions ({{count}})",
"tabs.subs": "Remplaçants ({{count}})",
+ "tabs.looking": "",
"pre.steps.header": "Complétez ces étapes pour jouer",
"pre.steps.name": "Nom de l'équipe",
"pre.steps.roster": "Participants",
diff --git a/locales/he/forms.json b/locales/he/forms.json
index 66d87a7fe..827b42e3a 100644
--- a/locales/he/forms.json
+++ b/locales/he/forms.json
@@ -220,5 +220,8 @@
"errors.alreadyFriends": "",
"errors.friendRequestExists": "",
"labels.type": "",
- "labels.visibility": ""
+ "labels.visibility": "",
+ "labels.note": "",
+ "labels.stayAsSub": "",
+ "bottomTexts.stayAsSub": ""
}
diff --git a/locales/he/q.json b/locales/he/q.json
index 59519811e..15a26ac40 100644
--- a/locales/he/q.json
+++ b/locales/he/q.json
@@ -94,6 +94,7 @@
"looking.groups.actions.removeManager": "",
"looking.groups.actions.kick": "",
"looking.groups.actions.leaveGroup": "",
+ "looking.groups.actions.stopLooking": "",
"looking.groups.actions.leaveQ": "",
"looking.groups.actions.goBack": "",
"looking.groups.adder.quickAdd": "",
@@ -103,6 +104,7 @@
"looking.groups.adder.select": "",
"looking.groups.editNote": "",
"looking.groups.addNote": "",
+ "looking.groups.stayAsSub": "",
"looking.replay": "",
"looking.teamSP": "",
"looking.teamSP.calculated": "",
@@ -112,6 +114,8 @@
"looking.rankCalculating": "",
"looking.allTiers": "",
"looking.joinQPrompt": "",
+ "looking.joinQPromptTeam": "",
+ "looking.joinQPromptMembers": "",
"looking.range.or": "",
"looking.range.or.explanation": "",
"match.header": "",
diff --git a/locales/he/tournament.json b/locales/he/tournament.json
index 8938f005d..e648088e4 100644
--- a/locales/he/tournament.json
+++ b/locales/he/tournament.json
@@ -8,6 +8,7 @@
"tabs.results": "",
"tabs.streams": "שידורים חיים ({{count}})",
"tabs.subs": "מחליפים ({{count}})",
+ "tabs.looking": "",
"pre.steps.header": "השלימו את השלבים הבאים כדי לשחק",
"pre.steps.name": "שם קבוצה",
"pre.steps.roster": "צוות מלא",
diff --git a/locales/it/forms.json b/locales/it/forms.json
index eec0a7a6e..600dbd167 100644
--- a/locales/it/forms.json
+++ b/locales/it/forms.json
@@ -220,5 +220,8 @@
"errors.alreadyFriends": "",
"errors.friendRequestExists": "",
"labels.type": "",
- "labels.visibility": ""
+ "labels.visibility": "",
+ "labels.note": "",
+ "labels.stayAsSub": "",
+ "bottomTexts.stayAsSub": ""
}
diff --git a/locales/it/q.json b/locales/it/q.json
index bb0a8e78e..e50eb24a0 100644
--- a/locales/it/q.json
+++ b/locales/it/q.json
@@ -94,6 +94,7 @@
"looking.groups.actions.removeManager": "Rimuovi manager",
"looking.groups.actions.kick": "Caccia",
"looking.groups.actions.leaveGroup": "Lascia gruppo",
+ "looking.groups.actions.stopLooking": "",
"looking.groups.actions.leaveQ": "Lascia coda",
"looking.groups.actions.goBack": "Indietro",
"looking.groups.adder.quickAdd": "Aggiungi direttamente",
@@ -103,6 +104,7 @@
"looking.groups.adder.select": "Seleziona utente...",
"looking.groups.editNote": "Modifica nota",
"looking.groups.addNote": "Aggiungi nota",
+ "looking.groups.stayAsSub": "",
"looking.replay": "Replay",
"looking.teamSP": "SP del team",
"looking.teamSP.calculated": "SP del team calcolato",
@@ -112,6 +114,8 @@
"looking.rankCalculating": "Meno di {{count}} sets giocati. Il rango sta ancora venendo calcolato...",
"looking.allTiers": "Tutti i tier",
"looking.joinQPrompt": "Unisciti alla coda o trova un gruppo",
+ "looking.joinQPromptTeam": "",
+ "looking.joinQPromptMembers": "",
"looking.range.or": "o",
"looking.range.or.explanation": "",
"match.header": "Match #{{number}}",
diff --git a/locales/it/tournament.json b/locales/it/tournament.json
index 7fdc59c3f..1b7af13e9 100644
--- a/locales/it/tournament.json
+++ b/locales/it/tournament.json
@@ -8,6 +8,7 @@
"tabs.results": "Resultati",
"tabs.streams": "Stream ({{count}})",
"tabs.subs": "Sub ({{count}})",
+ "tabs.looking": "",
"pre.steps.header": "Completa questi passaggi per giocare",
"pre.steps.name": "Nome team",
"pre.steps.roster": "Roster completo",
diff --git a/locales/ja/forms.json b/locales/ja/forms.json
index 570ff720d..0dc092330 100644
--- a/locales/ja/forms.json
+++ b/locales/ja/forms.json
@@ -220,5 +220,8 @@
"errors.alreadyFriends": "",
"errors.friendRequestExists": "",
"labels.type": "",
- "labels.visibility": ""
+ "labels.visibility": "",
+ "labels.note": "",
+ "labels.stayAsSub": "",
+ "bottomTexts.stayAsSub": ""
}
diff --git a/locales/ja/q.json b/locales/ja/q.json
index 49e52789f..df8b2efbf 100644
--- a/locales/ja/q.json
+++ b/locales/ja/q.json
@@ -94,6 +94,7 @@
"looking.groups.actions.removeManager": "マネージャーを外す",
"looking.groups.actions.kick": "キックする",
"looking.groups.actions.leaveGroup": "グループを出る",
+ "looking.groups.actions.stopLooking": "",
"looking.groups.actions.leaveQ": "列から出る",
"looking.groups.actions.goBack": "戻る",
"looking.groups.adder.quickAdd": "クイック追加",
@@ -103,6 +104,7 @@
"looking.groups.adder.select": "",
"looking.groups.editNote": "メモを編集する",
"looking.groups.addNote": "メモを追加する",
+ "looking.groups.stayAsSub": "",
"looking.replay": "リプレイ",
"looking.teamSP": "チーム SP",
"looking.teamSP.calculated": "チーム SP を計算しました。",
@@ -112,6 +114,8 @@
"looking.rankCalculating": "遊んだセットが{{count}}個以下です。ランクはまだ計算中です...",
"looking.allTiers": "全てのティア",
"looking.joinQPrompt": "グループを見つけるには列に入ってください",
+ "looking.joinQPromptTeam": "",
+ "looking.joinQPromptMembers": "",
"looking.range.or": "か",
"looking.range.or.explanation": "",
"match.header": "マッチ #{{number}}番",
diff --git a/locales/ja/tournament.json b/locales/ja/tournament.json
index 759c61f3b..3bdceb91d 100644
--- a/locales/ja/tournament.json
+++ b/locales/ja/tournament.json
@@ -8,6 +8,7 @@
"tabs.results": "結果",
"tabs.streams": "配信 ({{count}})",
"tabs.subs": "サブ ({{count}})",
+ "tabs.looking": "",
"pre.steps.header": "トーナメント参加のために下記を入力してください",
"pre.steps.name": "チーム名",
"pre.steps.roster": "全プレイヤー",
diff --git a/locales/ko/forms.json b/locales/ko/forms.json
index b80c1c50b..9424bffad 100644
--- a/locales/ko/forms.json
+++ b/locales/ko/forms.json
@@ -220,5 +220,8 @@
"errors.alreadyFriends": "",
"errors.friendRequestExists": "",
"labels.type": "",
- "labels.visibility": ""
+ "labels.visibility": "",
+ "labels.note": "",
+ "labels.stayAsSub": "",
+ "bottomTexts.stayAsSub": ""
}
diff --git a/locales/ko/q.json b/locales/ko/q.json
index 3d6b052b2..bcd044095 100644
--- a/locales/ko/q.json
+++ b/locales/ko/q.json
@@ -94,6 +94,7 @@
"looking.groups.actions.removeManager": "",
"looking.groups.actions.kick": "",
"looking.groups.actions.leaveGroup": "",
+ "looking.groups.actions.stopLooking": "",
"looking.groups.actions.leaveQ": "",
"looking.groups.actions.goBack": "",
"looking.groups.adder.quickAdd": "",
@@ -103,6 +104,7 @@
"looking.groups.adder.select": "",
"looking.groups.editNote": "",
"looking.groups.addNote": "",
+ "looking.groups.stayAsSub": "",
"looking.replay": "",
"looking.teamSP": "",
"looking.teamSP.calculated": "",
@@ -112,6 +114,8 @@
"looking.rankCalculating": "",
"looking.allTiers": "",
"looking.joinQPrompt": "",
+ "looking.joinQPromptTeam": "",
+ "looking.joinQPromptMembers": "",
"looking.range.or": "",
"looking.range.or.explanation": "",
"match.header": "",
diff --git a/locales/ko/tournament.json b/locales/ko/tournament.json
index edac30615..9181f4f57 100644
--- a/locales/ko/tournament.json
+++ b/locales/ko/tournament.json
@@ -8,6 +8,7 @@
"tabs.results": "",
"tabs.streams": "",
"tabs.subs": "",
+ "tabs.looking": "",
"pre.steps.header": "",
"pre.steps.name": "",
"pre.steps.roster": "",
diff --git a/locales/nl/forms.json b/locales/nl/forms.json
index 51ce81172..7e4ef3d7d 100644
--- a/locales/nl/forms.json
+++ b/locales/nl/forms.json
@@ -220,5 +220,8 @@
"errors.alreadyFriends": "",
"errors.friendRequestExists": "",
"labels.type": "",
- "labels.visibility": ""
+ "labels.visibility": "",
+ "labels.note": "",
+ "labels.stayAsSub": "",
+ "bottomTexts.stayAsSub": ""
}
diff --git a/locales/nl/q.json b/locales/nl/q.json
index 3d6b052b2..bcd044095 100644
--- a/locales/nl/q.json
+++ b/locales/nl/q.json
@@ -94,6 +94,7 @@
"looking.groups.actions.removeManager": "",
"looking.groups.actions.kick": "",
"looking.groups.actions.leaveGroup": "",
+ "looking.groups.actions.stopLooking": "",
"looking.groups.actions.leaveQ": "",
"looking.groups.actions.goBack": "",
"looking.groups.adder.quickAdd": "",
@@ -103,6 +104,7 @@
"looking.groups.adder.select": "",
"looking.groups.editNote": "",
"looking.groups.addNote": "",
+ "looking.groups.stayAsSub": "",
"looking.replay": "",
"looking.teamSP": "",
"looking.teamSP.calculated": "",
@@ -112,6 +114,8 @@
"looking.rankCalculating": "",
"looking.allTiers": "",
"looking.joinQPrompt": "",
+ "looking.joinQPromptTeam": "",
+ "looking.joinQPromptMembers": "",
"looking.range.or": "",
"looking.range.or.explanation": "",
"match.header": "",
diff --git a/locales/nl/tournament.json b/locales/nl/tournament.json
index e231f00b8..c8292a3fe 100644
--- a/locales/nl/tournament.json
+++ b/locales/nl/tournament.json
@@ -8,6 +8,7 @@
"tabs.results": "",
"tabs.streams": "",
"tabs.subs": "",
+ "tabs.looking": "",
"pre.steps.header": "",
"pre.steps.name": "",
"pre.steps.roster": "",
diff --git a/locales/pl/forms.json b/locales/pl/forms.json
index 0623ae947..6387728fc 100644
--- a/locales/pl/forms.json
+++ b/locales/pl/forms.json
@@ -220,5 +220,8 @@
"errors.alreadyFriends": "",
"errors.friendRequestExists": "",
"labels.type": "",
- "labels.visibility": ""
+ "labels.visibility": "",
+ "labels.note": "",
+ "labels.stayAsSub": "",
+ "bottomTexts.stayAsSub": ""
}
diff --git a/locales/pl/q.json b/locales/pl/q.json
index 3d6b052b2..bcd044095 100644
--- a/locales/pl/q.json
+++ b/locales/pl/q.json
@@ -94,6 +94,7 @@
"looking.groups.actions.removeManager": "",
"looking.groups.actions.kick": "",
"looking.groups.actions.leaveGroup": "",
+ "looking.groups.actions.stopLooking": "",
"looking.groups.actions.leaveQ": "",
"looking.groups.actions.goBack": "",
"looking.groups.adder.quickAdd": "",
@@ -103,6 +104,7 @@
"looking.groups.adder.select": "",
"looking.groups.editNote": "",
"looking.groups.addNote": "",
+ "looking.groups.stayAsSub": "",
"looking.replay": "",
"looking.teamSP": "",
"looking.teamSP.calculated": "",
@@ -112,6 +114,8 @@
"looking.rankCalculating": "",
"looking.allTiers": "",
"looking.joinQPrompt": "",
+ "looking.joinQPromptTeam": "",
+ "looking.joinQPromptMembers": "",
"looking.range.or": "",
"looking.range.or.explanation": "",
"match.header": "",
diff --git a/locales/pl/tournament.json b/locales/pl/tournament.json
index 78d861709..f76a2e68a 100644
--- a/locales/pl/tournament.json
+++ b/locales/pl/tournament.json
@@ -8,6 +8,7 @@
"tabs.results": "",
"tabs.streams": "",
"tabs.subs": "",
+ "tabs.looking": "",
"pre.steps.header": "",
"pre.steps.name": "",
"pre.steps.roster": "",
diff --git a/locales/pt-BR/forms.json b/locales/pt-BR/forms.json
index 1638c978c..7ab7e262d 100644
--- a/locales/pt-BR/forms.json
+++ b/locales/pt-BR/forms.json
@@ -220,5 +220,8 @@
"errors.alreadyFriends": "",
"errors.friendRequestExists": "",
"labels.type": "",
- "labels.visibility": ""
+ "labels.visibility": "",
+ "labels.note": "",
+ "labels.stayAsSub": "",
+ "bottomTexts.stayAsSub": ""
}
diff --git a/locales/pt-BR/q.json b/locales/pt-BR/q.json
index 5807584ce..ba3509dd6 100644
--- a/locales/pt-BR/q.json
+++ b/locales/pt-BR/q.json
@@ -94,6 +94,7 @@
"looking.groups.actions.removeManager": "Remover gerência",
"looking.groups.actions.kick": "Chutar (kick)",
"looking.groups.actions.leaveGroup": "Sair do grupo",
+ "looking.groups.actions.stopLooking": "",
"looking.groups.actions.leaveQ": "Sair da fila",
"looking.groups.actions.goBack": "Voltar",
"looking.groups.adder.quickAdd": "Adicionar rapidamente (quick add)",
@@ -103,6 +104,7 @@
"looking.groups.adder.select": "",
"looking.groups.editNote": "Editar nota",
"looking.groups.addNote": "Adicionar nota",
+ "looking.groups.stayAsSub": "",
"looking.replay": "Replay",
"looking.teamSP": "SP do time",
"looking.teamSP.calculated": "SP do time calculado",
@@ -112,6 +114,8 @@
"looking.rankCalculating": "Menos que {{count}} sets jogados. O rank ainda está sendo calculado...",
"looking.allTiers": "Todas as tiers",
"looking.joinQPrompt": "Entre na fila para achar um grupo",
+ "looking.joinQPromptTeam": "",
+ "looking.joinQPromptMembers": "",
"looking.range.or": "",
"looking.range.or.explanation": "",
"match.header": "Partida #{{number}}",
diff --git a/locales/pt-BR/tournament.json b/locales/pt-BR/tournament.json
index 59fef0c19..24502032f 100644
--- a/locales/pt-BR/tournament.json
+++ b/locales/pt-BR/tournament.json
@@ -8,6 +8,7 @@
"tabs.results": "",
"tabs.streams": "Transmissões ({{count}})",
"tabs.subs": "Inscritos ({{count}})",
+ "tabs.looking": "",
"pre.steps.header": "Complete esses passos para jogar",
"pre.steps.name": "Nome do time",
"pre.steps.roster": "Lista de membros completa",
diff --git a/locales/ru/forms.json b/locales/ru/forms.json
index b641b6bf2..89ed93850 100644
--- a/locales/ru/forms.json
+++ b/locales/ru/forms.json
@@ -220,5 +220,8 @@
"errors.alreadyFriends": "",
"errors.friendRequestExists": "",
"labels.type": "",
- "labels.visibility": ""
+ "labels.visibility": "",
+ "labels.note": "",
+ "labels.stayAsSub": "",
+ "bottomTexts.stayAsSub": ""
}
diff --git a/locales/ru/q.json b/locales/ru/q.json
index c60e01c58..0c73abaf6 100644
--- a/locales/ru/q.json
+++ b/locales/ru/q.json
@@ -94,6 +94,7 @@
"looking.groups.actions.removeManager": "Удалить роль менеджера",
"looking.groups.actions.kick": "Выгнать",
"looking.groups.actions.leaveGroup": "Покинуть группу",
+ "looking.groups.actions.stopLooking": "",
"looking.groups.actions.leaveQ": "Покинуть очередь",
"looking.groups.actions.goBack": "Вернуться назад",
"looking.groups.adder.quickAdd": "Быстро добавить",
@@ -103,6 +104,7 @@
"looking.groups.adder.select": "Выбрать пользователя...",
"looking.groups.editNote": "Изменить заметку",
"looking.groups.addNote": "Добавить заметку",
+ "looking.groups.stayAsSub": "",
"looking.replay": "Повтор",
"looking.teamSP": "Командое SP",
"looking.teamSP.calculated": "Командое SP рассчитано",
@@ -112,6 +114,8 @@
"looking.rankCalculating": "Менее чем {{count}} матчей сыграно. Ранг ещё рассчитывается...",
"looking.allTiers": "Все ранги",
"looking.joinQPrompt": "Присоединитесь к очереди, чтобы найти группу",
+ "looking.joinQPromptTeam": "",
+ "looking.joinQPromptMembers": "",
"looking.range.or": "или",
"looking.range.or.explanation": "",
"match.header": "Матч #{{number}}",
diff --git a/locales/ru/tournament.json b/locales/ru/tournament.json
index c4afcaa02..05dfb61d7 100644
--- a/locales/ru/tournament.json
+++ b/locales/ru/tournament.json
@@ -8,6 +8,7 @@
"tabs.results": "Результаты",
"tabs.streams": "Трансляции: ({{count}})",
"tabs.subs": "Запасные: ({{count}})",
+ "tabs.looking": "",
"pre.steps.header": "Завершите следующие шаги, чтобы закончить регистрацию",
"pre.steps.name": "Имя команды",
"pre.steps.roster": "Полный состав",
diff --git a/locales/zh/forms.json b/locales/zh/forms.json
index 2734723a0..e1283f7d6 100644
--- a/locales/zh/forms.json
+++ b/locales/zh/forms.json
@@ -220,5 +220,8 @@
"errors.alreadyFriends": "",
"errors.friendRequestExists": "",
"labels.type": "",
- "labels.visibility": ""
+ "labels.visibility": "",
+ "labels.note": "",
+ "labels.stayAsSub": "",
+ "bottomTexts.stayAsSub": ""
}
diff --git a/locales/zh/q.json b/locales/zh/q.json
index 27018361c..2b3a96f92 100644
--- a/locales/zh/q.json
+++ b/locales/zh/q.json
@@ -94,6 +94,7 @@
"looking.groups.actions.removeManager": "移除管理者权限",
"looking.groups.actions.kick": "踢出",
"looking.groups.actions.leaveGroup": "离开小队",
+ "looking.groups.actions.stopLooking": "",
"looking.groups.actions.leaveQ": "停止匹配",
"looking.groups.actions.goBack": "返回",
"looking.groups.adder.quickAdd": "快速添加",
@@ -103,6 +104,7 @@
"looking.groups.adder.select": "",
"looking.groups.editNote": "编辑笔记",
"looking.groups.addNote": "添加笔记",
+ "looking.groups.stayAsSub": "",
"looking.replay": "重赛",
"looking.teamSP": "小队SP",
"looking.teamSP.calculated": "小队SP已定分",
@@ -112,6 +114,8 @@
"looking.rankCalculating": "对战数少于 {{count}} 场。段位定分中...",
"looking.allTiers": "所有段位",
"looking.joinQPrompt": "参加匹配以寻找小队",
+ "looking.joinQPromptTeam": "",
+ "looking.joinQPromptMembers": "",
"looking.range.or": "或",
"looking.range.or.explanation": "",
"match.header": "对战 #{{number}}",
diff --git a/locales/zh/tournament.json b/locales/zh/tournament.json
index 107210b2e..1d960ab4f 100644
--- a/locales/zh/tournament.json
+++ b/locales/zh/tournament.json
@@ -8,6 +8,7 @@
"tabs.results": "",
"tabs.streams": "直播 ({{count}})",
"tabs.subs": "替补 ({{count}})",
+ "tabs.looking": "",
"pre.steps.header": "参加比赛需完成以下步骤",
"pre.steps.name": "队伍名称",
"pre.steps.roster": "完整阵容",
diff --git a/migrations/119-tournament-lfg.js b/migrations/119-tournament-lfg.js
new file mode 100644
index 000000000..751b8ef34
--- /dev/null
+++ b/migrations/119-tournament-lfg.js
@@ -0,0 +1,77 @@
+export function up(db) {
+ db.transaction(() => {
+ db.prepare(
+ /*sql*/ `alter table "TournamentTeam" add column "isLooking" integer not null default 0`,
+ ).run();
+ db.prepare(
+ /*sql*/ `alter table "TournamentTeam" add column "isPlaceholder" integer not null default 0`,
+ ).run();
+ db.prepare(
+ /*sql*/ `alter table "TournamentTeam" add column "lfgVisibility" text`,
+ ).run();
+ db.prepare(
+ /*sql*/ `alter table "TournamentTeam" add column "lfgNote" text`,
+ ).run();
+
+ db.prepare(
+ /*sql*/ `alter table "TournamentTeamMember" add column "role" text not null default 'REGULAR'`,
+ ).run();
+ db.prepare(
+ /*sql*/ `alter table "TournamentTeamMember" add column "isStayAsSub" integer not null default 0`,
+ ).run();
+ db.prepare(
+ /*sql*/ `alter table "TournamentTeamMember" add column "isLooking" integer not null default 0`,
+ ).run();
+
+ db.prepare(
+ /*sql*/ `create index tournament_team_member_user_id_is_looking on "TournamentTeamMember"("userId", "isLooking")`,
+ ).run();
+
+ db.prepare(
+ /*sql*/ `
+ create trigger sync_is_looking_on_team_update
+ after update of isLooking on "TournamentTeam"
+ begin
+ update "TournamentTeamMember"
+ set "isLooking" = NEW."isLooking"
+ where "tournamentTeamId" = NEW."id";
+ end
+ `,
+ ).run();
+ db.prepare(
+ /*sql*/ `
+ create trigger sync_is_looking_on_member_insert
+ after insert on "TournamentTeamMember"
+ begin
+ update "TournamentTeamMember"
+ set "isLooking" = (select "isLooking" from "TournamentTeam" where "id" = NEW."tournamentTeamId")
+ where rowid = NEW.rowid;
+ end
+ `,
+ ).run();
+
+ db.prepare(
+ /*sql*/ `update "TournamentTeamMember" set "role" = 'OWNER' where "isOwner" = 1`,
+ ).run();
+
+ db.prepare(
+ /*sql*/ `
+ create table "TournamentLFGLike" (
+ "likerTeamId" integer not null,
+ "targetTeamId" integer not null,
+ "createdAt" integer default (strftime('%s', 'now')) not null,
+ foreign key ("likerTeamId") references "TournamentTeam"("id") on delete cascade,
+ foreign key ("targetTeamId") references "TournamentTeam"("id") on delete cascade,
+ unique("likerTeamId", "targetTeamId") on conflict rollback
+ ) strict
+ `,
+ ).run();
+
+ db.prepare(
+ /*sql*/ `create index tournament_lfg_like_liker_team_id on "TournamentLFGLike"("likerTeamId")`,
+ ).run();
+ db.prepare(
+ /*sql*/ `create index tournament_lfg_like_target_team_id on "TournamentLFGLike"("targetTeamId")`,
+ ).run();
+ })();
+}