mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-14 07:06:14 -05:00
Report weapons initial
This commit is contained in:
@@ -19,6 +19,7 @@ import {
|
||||
type MatchTimelineProps,
|
||||
type TimelineMap,
|
||||
} from "./MatchTimeline";
|
||||
import { WeaponReporter, type WeaponReporterProps } from "./WeaponReporter";
|
||||
|
||||
interface ActionTabTeam {
|
||||
id: number;
|
||||
@@ -41,6 +42,7 @@ interface MatchActionTabProps {
|
||||
isSubmitting?: boolean;
|
||||
setEnding?: SetEndingData;
|
||||
actionButtons?: React.ReactNode;
|
||||
weaponReport?: WeaponReporterProps;
|
||||
}
|
||||
|
||||
export function MatchActionTab({
|
||||
@@ -53,6 +55,7 @@ export function MatchActionTab({
|
||||
isSubmitting,
|
||||
setEnding,
|
||||
actionButtons,
|
||||
weaponReport,
|
||||
}: MatchActionTabProps) {
|
||||
const { t } = useTranslation(["q", "game-misc", "common"]);
|
||||
const [winnerId, setWinnerId] = useState<number | null>(null);
|
||||
@@ -170,6 +173,7 @@ export function MatchActionTab({
|
||||
</SendouButton>
|
||||
</div>
|
||||
)}
|
||||
{weaponReport ? <WeaponReporter {...weaponReport} /> : null}
|
||||
</SendouTabPanel>
|
||||
);
|
||||
}
|
||||
|
||||
57
app/components/match-page/WeaponReporter.module.css
Normal file
57
app/components/match-page/WeaponReporter.module.css
Normal file
@@ -0,0 +1,57 @@
|
||||
.root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--s-4);
|
||||
background-color: var(--color-bg-higher);
|
||||
border-radius: 0 0 var(--radius-box) var(--radius-box);
|
||||
padding: var(--s-4);
|
||||
margin: var(--s-4) calc(-1 * var(--s-4)) calc(-1 * var(--s-4));
|
||||
}
|
||||
|
||||
.pastRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--s-2);
|
||||
}
|
||||
|
||||
.mapRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--s-3);
|
||||
}
|
||||
|
||||
.mapInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--s-1);
|
||||
}
|
||||
|
||||
.mapLabel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--s-1);
|
||||
font-size: var(--font-3xs);
|
||||
font-weight: var(--weight-semi);
|
||||
color: var(--color-text-high);
|
||||
}
|
||||
|
||||
.stageImage {
|
||||
border-radius: var(--radius-box);
|
||||
}
|
||||
|
||||
.inputRow {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: var(--s-3);
|
||||
}
|
||||
|
||||
.weaponSelectContainer {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.unreportedRow {
|
||||
display: flex;
|
||||
gap: var(--s-1);
|
||||
}
|
||||
131
app/components/match-page/WeaponReporter.tsx
Normal file
131
app/components/match-page/WeaponReporter.tsx
Normal file
@@ -0,0 +1,131 @@
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { shortStageName } from "~/modules/in-game-lists/stage-ids";
|
||||
import type {
|
||||
MainWeaponId,
|
||||
ModeShort,
|
||||
StageId,
|
||||
} from "~/modules/in-game-lists/types";
|
||||
import { abilityImageUrl } from "~/utils/urls";
|
||||
import { SendouButton } from "../elements/Button";
|
||||
import { Image, ModeImage, StageImage, WeaponImage } from "../Image";
|
||||
import { WeaponSelect } from "../WeaponSelect";
|
||||
import styles from "./WeaponReporter.module.css";
|
||||
|
||||
interface WeaponReporterMap {
|
||||
stageId: StageId;
|
||||
mode: ModeShort;
|
||||
}
|
||||
|
||||
export interface WeaponReporterProps {
|
||||
maps: WeaponReporterMap[];
|
||||
pastReported: MainWeaponId[];
|
||||
quickSelectWeaponIds?: MainWeaponId[];
|
||||
onSubmit: (weaponSplId: MainWeaponId) => void;
|
||||
onUndo: () => void;
|
||||
isSubmitting?: boolean;
|
||||
}
|
||||
|
||||
// xxx: default collapsed, small minimal button to uncollapse
|
||||
// xxx: on sendouq all weapons report different / component tab..? or not? check usage
|
||||
export function WeaponReporter({
|
||||
maps,
|
||||
pastReported,
|
||||
quickSelectWeaponIds,
|
||||
onSubmit,
|
||||
onUndo,
|
||||
isSubmitting,
|
||||
}: WeaponReporterProps) {
|
||||
const { t } = useTranslation(["q", "game-misc", "common"]);
|
||||
const [selectedWeapon, setSelectedWeapon] = useState<MainWeaponId | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
const inputTargetIndex = pastReported.length;
|
||||
|
||||
const inputTargetMap = maps[inputTargetIndex];
|
||||
const unreportedCount =
|
||||
maps.length - inputTargetIndex - (inputTargetMap ? 1 : 0);
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
{pastReported.length > 0 ? (
|
||||
<div className={styles.pastRow}>
|
||||
{pastReported.map((weaponId, i) => (
|
||||
<WeaponImage
|
||||
key={i}
|
||||
weaponSplId={weaponId}
|
||||
variant="badge"
|
||||
size={24}
|
||||
/>
|
||||
))}
|
||||
<SendouButton
|
||||
variant="minimal"
|
||||
size="small"
|
||||
isDisabled={isSubmitting}
|
||||
onPress={onUndo}
|
||||
>
|
||||
{t("q:match.weapon.undoWeapon")}
|
||||
</SendouButton>
|
||||
</div>
|
||||
) : null}
|
||||
{inputTargetMap ? (
|
||||
<div className={styles.mapRow}>
|
||||
<MapInfo map={inputTargetMap} />
|
||||
<div className={styles.inputRow}>
|
||||
<div className={styles.weaponSelectContainer}>
|
||||
<WeaponSelect
|
||||
label={t("q:match.weapon.yourWeapon")}
|
||||
value={selectedWeapon}
|
||||
onChange={setSelectedWeapon}
|
||||
quickSelectWeaponsIds={quickSelectWeaponIds}
|
||||
/>
|
||||
</div>
|
||||
<SendouButton
|
||||
variant="primary"
|
||||
size="small"
|
||||
isDisabled={selectedWeapon === null || isSubmitting}
|
||||
onPress={() => {
|
||||
if (selectedWeapon === null) return;
|
||||
onSubmit(selectedWeapon);
|
||||
setSelectedWeapon(null);
|
||||
}}
|
||||
>
|
||||
{t("common:actions.submit")}
|
||||
</SendouButton>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{unreportedCount > 0 ? (
|
||||
<div className={styles.unreportedRow}>
|
||||
{Array.from({ length: unreportedCount }, (_, i) => (
|
||||
<Image
|
||||
key={i}
|
||||
path={abilityImageUrl("UNKNOWN")}
|
||||
alt="?"
|
||||
size={24}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MapInfo({ map }: { map: WeaponReporterMap }) {
|
||||
const { t } = useTranslation(["game-misc"]);
|
||||
|
||||
return (
|
||||
<div className={styles.mapInfo}>
|
||||
<StageImage
|
||||
stageId={map.stageId}
|
||||
width={60}
|
||||
className={styles.stageImage}
|
||||
/>
|
||||
<div className={styles.mapLabel}>
|
||||
<ModeImage mode={map.mode} size={14} />
|
||||
<span>{shortStageName(t(`game-misc:STAGE_${map.stageId}`))}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -40,6 +40,31 @@ export async function replaceByMatchId(
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteByUserMapIndex({
|
||||
matchId,
|
||||
userId,
|
||||
mapIndex,
|
||||
}: {
|
||||
matchId: number;
|
||||
userId: number;
|
||||
mapIndex: number;
|
||||
}) {
|
||||
const groupMatchMap = await db
|
||||
.selectFrom("GroupMatchMap")
|
||||
.select("id")
|
||||
.where("matchId", "=", matchId)
|
||||
.where("index", "=", mapIndex)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!groupMatchMap) return;
|
||||
|
||||
await db
|
||||
.deleteFrom("ReportedWeapon")
|
||||
.where("groupMatchMapId", "=", groupMatchMap.id)
|
||||
.where("userId", "=", userId)
|
||||
.execute();
|
||||
}
|
||||
|
||||
export async function findByMatchId(matchId: number) {
|
||||
const rows = await db
|
||||
.selectFrom("ReportedWeapon")
|
||||
|
||||
@@ -135,9 +135,18 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
|
||||
|
||||
throw redirect(SENDOUQ_PREPARING_PAGE);
|
||||
}
|
||||
// xxx: why not REPORT_WEAPON
|
||||
case "REPORT_WEAPONS": {
|
||||
const match = notFoundIfFalsy(await SQMatchRepository.findById(matchId));
|
||||
errorToastIfFalsy(match.reportedAt, "Match has not been reported yet");
|
||||
|
||||
const members = [
|
||||
...match.groupAlpha.members,
|
||||
...match.groupBravo.members,
|
||||
];
|
||||
invariant(
|
||||
members.some((m) => m.id === user.id),
|
||||
"User is not a member of any group",
|
||||
);
|
||||
|
||||
const oldReportedWeapons =
|
||||
(await ReportedWeaponRepository.findByMatchId(matchId)) ?? [];
|
||||
@@ -161,6 +170,17 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
|
||||
|
||||
break;
|
||||
}
|
||||
case "UNDO_WEAPON_REPORT": {
|
||||
notFoundIfFalsy(await SQMatchRepository.findById(matchId));
|
||||
|
||||
await ReportedWeaponRepository.deleteByUserMapIndex({
|
||||
matchId,
|
||||
userId: user.id,
|
||||
mapIndex: data.mapIndex,
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
case "ADD_PRIVATE_USER_NOTE": {
|
||||
await PrivateUserNoteRepository.upsert({
|
||||
authorId: user.id,
|
||||
|
||||
@@ -7,8 +7,13 @@ import { FormWithConfirm } from "~/components/FormWithConfirm";
|
||||
import { MatchActionTab } from "~/components/match-page/MatchActionTab";
|
||||
import { TAB_KEYS } from "~/components/match-page/MatchTabs";
|
||||
import { SENDOUQ_BEST_OF } from "~/features/sendouq/q-constants";
|
||||
import { useRecentlyReportedWeapons } from "~/features/sendouq/q-hooks";
|
||||
import { isSetOverByScore } from "~/features/tournament-bracket/tournament-bracket-utils";
|
||||
import type { ModeShort, StageId } from "~/modules/in-game-lists/types";
|
||||
import type {
|
||||
MainWeaponId,
|
||||
ModeShort,
|
||||
StageId,
|
||||
} from "~/modules/in-game-lists/types";
|
||||
import type { SendouQMatchLoaderData } from "../loaders/q.match.$id.server";
|
||||
import styles from "./SendouQMatchActionTab.module.css";
|
||||
|
||||
@@ -16,17 +21,22 @@ export function SendouQMatchActionTab({
|
||||
data,
|
||||
currentMap,
|
||||
ownTeamId,
|
||||
ownUserId,
|
||||
reportedCount,
|
||||
}: {
|
||||
data: SendouQMatchLoaderData;
|
||||
currentMap: { stageId: StageId; mode: ModeShort };
|
||||
ownTeamId: number;
|
||||
ownUserId: number;
|
||||
reportedCount: number;
|
||||
}) {
|
||||
const { t } = useTranslation(["q", "common"]);
|
||||
const fetcher = useFetcher();
|
||||
const undoFetcher = useFetcher();
|
||||
const cancelFetcher = useFetcher();
|
||||
const weaponFetcher = useFetcher();
|
||||
const { recentlyReportedWeapons, addRecentlyReportedWeapon } =
|
||||
useRecentlyReportedWeapons();
|
||||
|
||||
const alphaScore = data.match.mapList.filter(
|
||||
(m) => m.winnerGroupId === data.match.groupAlpha.id,
|
||||
@@ -131,6 +141,16 @@ export function SendouQMatchActionTab({
|
||||
|
||||
const scoreIsNotZero = alphaScore > 0 || bravoScore > 0;
|
||||
|
||||
const weaponReportMaps = data.match.mapList
|
||||
.slice(0, reportedCount + 1)
|
||||
.map((m) => ({ stageId: m.stageId, mode: m.mode }));
|
||||
|
||||
const weaponPastReported: MainWeaponId[] = data.reportedWeapons
|
||||
? data.reportedWeapons
|
||||
.filter((w) => w.userId === ownUserId)
|
||||
.map((w) => w.weaponSplId)
|
||||
: [];
|
||||
|
||||
return (
|
||||
<MatchActionTab
|
||||
key={reportedCount}
|
||||
@@ -154,6 +174,42 @@ export function SendouQMatchActionTab({
|
||||
{ method: "post" },
|
||||
);
|
||||
}}
|
||||
weaponReport={{
|
||||
maps: weaponReportMaps,
|
||||
pastReported: weaponPastReported,
|
||||
quickSelectWeaponIds: recentlyReportedWeapons,
|
||||
isSubmitting: weaponFetcher.state !== "idle",
|
||||
onSubmit: (weaponSplId) => {
|
||||
addRecentlyReportedWeapon(weaponSplId);
|
||||
const mapIndex = weaponPastReported.length;
|
||||
const map = data.match.mapList[mapIndex];
|
||||
weaponFetcher.submit(
|
||||
{
|
||||
_action: "REPORT_WEAPONS",
|
||||
weapons: JSON.stringify([
|
||||
{
|
||||
weaponSplId,
|
||||
userId: ownUserId,
|
||||
mapIndex,
|
||||
groupMatchMapId: map.id,
|
||||
},
|
||||
]),
|
||||
},
|
||||
{ method: "post" },
|
||||
);
|
||||
},
|
||||
onUndo: () => {
|
||||
const mapIndex = weaponPastReported.length - 1;
|
||||
if (mapIndex < 0) return;
|
||||
weaponFetcher.submit(
|
||||
{
|
||||
_action: "UNDO_WEAPON_REPORT",
|
||||
mapIndex: String(mapIndex),
|
||||
},
|
||||
{ method: "post" },
|
||||
);
|
||||
},
|
||||
}}
|
||||
actionButtons={
|
||||
<>
|
||||
<FormWithConfirm
|
||||
|
||||
@@ -111,7 +111,7 @@ export function SendouQMatchTabs({ data }: { data: SendouQMatchLoaderData }) {
|
||||
alpha: alphaWins,
|
||||
bravo: bravoWins,
|
||||
}}
|
||||
maps={resolveTimelineMaps(data.match)}
|
||||
maps={resolveTimelineMaps(data.match, data.reportedWeapons)}
|
||||
spChanges={resolveTimelineSpChanges(data.match)}
|
||||
>
|
||||
{data.match.cancelRequestedByUserId ? (
|
||||
@@ -175,6 +175,8 @@ export function SendouQMatchTabs({ data }: { data: SendouQMatchLoaderData }) {
|
||||
data={data}
|
||||
currentMap={currentMap}
|
||||
ownTeamId={ownTeamId}
|
||||
// xxx: why not just useUser in SendouQMatchActionTab?
|
||||
ownUserId={user!.id}
|
||||
reportedCount={reportedCount}
|
||||
/>
|
||||
) : null}
|
||||
@@ -208,7 +210,7 @@ function ConfirmerTab({
|
||||
(m) => m.winnerGroupId === data.match.groupBravo.id,
|
||||
).length,
|
||||
}}
|
||||
maps={resolveTimelineMaps(data.match)}
|
||||
maps={resolveTimelineMaps(data.match, data.reportedWeapons)}
|
||||
/>
|
||||
<div className="stack md items-center mt-4">
|
||||
<SendouButton
|
||||
@@ -252,7 +254,7 @@ function ReporterWaitingTab({ data }: { data: SendouQMatchLoaderData }) {
|
||||
(m) => m.winnerGroupId === data.match.groupBravo.id,
|
||||
).length,
|
||||
}}
|
||||
maps={resolveTimelineMaps(data.match)}
|
||||
maps={resolveTimelineMaps(data.match, data.reportedWeapons)}
|
||||
/>
|
||||
<div className="stack md items-center mt-4">
|
||||
<p className="text-lighter text-sm">
|
||||
@@ -292,22 +294,47 @@ function resolveTimelineTeams(match: MatchData) {
|
||||
};
|
||||
}
|
||||
|
||||
function resolveTimelineMaps(match: MatchData): TimelineMap[] {
|
||||
function resolveTimelineMaps(
|
||||
match: MatchData,
|
||||
reportedWeapons: SendouQMatchLoaderData["reportedWeapons"],
|
||||
): TimelineMap[] {
|
||||
return match.mapList
|
||||
.filter((m) => m.winnerGroupId !== null)
|
||||
.map((map) => ({
|
||||
stageId: map.stageId,
|
||||
mode: map.mode,
|
||||
timestamp: match.createdAt,
|
||||
winner:
|
||||
map.winnerGroupId === match.groupAlpha.id
|
||||
? ("ALPHA" as const)
|
||||
: ("BRAVO" as const),
|
||||
rosters: {
|
||||
alpha: match.groupAlpha.members,
|
||||
bravo: match.groupBravo.members,
|
||||
},
|
||||
}));
|
||||
.map((map) => {
|
||||
const alphaWeapons = match.groupAlpha.members.map((member) => {
|
||||
const w = reportedWeapons?.find(
|
||||
(rw) => rw.groupMatchMapId === map.id && rw.userId === member.id,
|
||||
);
|
||||
return w ? w.weaponSplId : null;
|
||||
});
|
||||
const bravoWeapons = match.groupBravo.members.map((member) => {
|
||||
const w = reportedWeapons?.find(
|
||||
(rw) => rw.groupMatchMapId === map.id && rw.userId === member.id,
|
||||
);
|
||||
return w ? w.weaponSplId : null;
|
||||
});
|
||||
|
||||
const hasAnyWeapon =
|
||||
alphaWeapons.some((w) => w !== null) ||
|
||||
bravoWeapons.some((w) => w !== null);
|
||||
|
||||
return {
|
||||
stageId: map.stageId,
|
||||
mode: map.mode,
|
||||
timestamp: match.createdAt,
|
||||
winner:
|
||||
map.winnerGroupId === match.groupAlpha.id
|
||||
? ("ALPHA" as const)
|
||||
: ("BRAVO" as const),
|
||||
rosters: {
|
||||
alpha: match.groupAlpha.members,
|
||||
bravo: match.groupBravo.members,
|
||||
},
|
||||
weapons: hasAnyWeapon
|
||||
? { alpha: alphaWeapons, bravo: bravoWeapons }
|
||||
: undefined,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function resolveTimelineSpChanges(
|
||||
|
||||
@@ -4,6 +4,7 @@ import { chatAccessible } from "~/features/chat/chat-utils";
|
||||
import * as RoomLinkRepository from "~/features/chat/RoomLinkRepository.server";
|
||||
import { SendouQ } from "~/features/sendouq/core/SendouQ.server";
|
||||
import * as PrivateUserNoteRepository from "~/features/sendouq/PrivateUserNoteRepository.server";
|
||||
import * as ReportedWeaponRepository from "~/features/sendouq-match/ReportedWeaponRepository.server";
|
||||
import * as SQMatchRepository from "~/features/sendouq-match/SQMatchRepository.server";
|
||||
import * as UserRepository from "~/features/user-page/UserRepository.server";
|
||||
import { databaseTimestampToDate } from "~/utils/dates";
|
||||
@@ -27,15 +28,15 @@ export const loader = async ({ params }: LoaderFunctionArgs) => {
|
||||
...matchUnmapped.groupBravo.members,
|
||||
].map((m) => m.id);
|
||||
|
||||
const [privateNotes, roomLinks, anyUserPrefersNoSplatnet] = await Promise.all(
|
||||
[
|
||||
const [privateNotes, roomLinks, anyUserPrefersNoSplatnet, reportedWeapons] =
|
||||
await Promise.all([
|
||||
user
|
||||
? PrivateUserNoteRepository.byAuthorUserId(user.id, matchUsers)
|
||||
: undefined,
|
||||
RoomLinkRepository.findByUserIds(matchUsers, 3),
|
||||
UserRepository.anyUserPrefersNoSplatnet(matchUsers),
|
||||
],
|
||||
);
|
||||
ReportedWeaponRepository.findByMatchId(matchId),
|
||||
]);
|
||||
|
||||
const match = SendouQ.mapMatch(matchUnmapped, user, privateNotes);
|
||||
|
||||
@@ -43,6 +44,7 @@ export const loader = async ({ params }: LoaderFunctionArgs) => {
|
||||
match,
|
||||
roomLinks,
|
||||
anyUserPrefersNoSplatnet,
|
||||
reportedWeapons,
|
||||
chatCode: (() => {
|
||||
const isStaff = user?.roles.includes("STAFF") ?? false;
|
||||
const isParticipant = user && matchUsers.includes(user.id);
|
||||
|
||||
@@ -54,6 +54,10 @@ export const matchSchema = z.union([
|
||||
z.object({
|
||||
_action: _action("UNDO_MAP_REPORT"),
|
||||
}),
|
||||
z.object({
|
||||
_action: _action("UNDO_WEAPON_REPORT"),
|
||||
mapIndex: z.coerce.number().int().nonnegative(),
|
||||
}),
|
||||
z.object({
|
||||
_action: _action("REQUEST_CANCEL"),
|
||||
}),
|
||||
|
||||
@@ -193,6 +193,8 @@
|
||||
"match.timeline.teamSp": "",
|
||||
"match.waitingForConfirmation": "",
|
||||
"match.undoReport": "",
|
||||
"match.weapon.yourWeapon": "",
|
||||
"match.weapon.undoWeapon": "",
|
||||
"match.confirmScore": "",
|
||||
"match.confirmScore.wrongHint": "",
|
||||
"preparing.joinQ": "",
|
||||
|
||||
@@ -193,6 +193,8 @@
|
||||
"match.timeline.teamSp": "",
|
||||
"match.waitingForConfirmation": "",
|
||||
"match.undoReport": "",
|
||||
"match.weapon.yourWeapon": "",
|
||||
"match.weapon.undoWeapon": "",
|
||||
"match.confirmScore": "",
|
||||
"match.confirmScore.wrongHint": "",
|
||||
"preparing.joinQ": "",
|
||||
|
||||
@@ -193,6 +193,8 @@
|
||||
"match.timeline.teamSp": "Team SP",
|
||||
"match.waitingForConfirmation": "Waiting for the other team to confirm the result",
|
||||
"match.undoReport": "Undo report",
|
||||
"match.weapon.yourWeapon": "Your weapon",
|
||||
"match.weapon.undoWeapon": "Undo weapon",
|
||||
"match.confirmScore": "Confirm score",
|
||||
"match.confirmScore.wrongHint": "Wrong score? Ask the other team to undo their report and adjust it.",
|
||||
"preparing.joinQ": "Join the queue",
|
||||
|
||||
@@ -193,6 +193,8 @@
|
||||
"match.timeline.teamSp": "",
|
||||
"match.waitingForConfirmation": "",
|
||||
"match.undoReport": "",
|
||||
"match.weapon.yourWeapon": "",
|
||||
"match.weapon.undoWeapon": "",
|
||||
"match.confirmScore": "",
|
||||
"match.confirmScore.wrongHint": "",
|
||||
"preparing.joinQ": "Unirte a la fila",
|
||||
|
||||
@@ -193,6 +193,8 @@
|
||||
"match.timeline.teamSp": "",
|
||||
"match.waitingForConfirmation": "",
|
||||
"match.undoReport": "",
|
||||
"match.weapon.yourWeapon": "",
|
||||
"match.weapon.undoWeapon": "",
|
||||
"match.confirmScore": "",
|
||||
"match.confirmScore.wrongHint": "",
|
||||
"preparing.joinQ": "Unirte a la fila",
|
||||
|
||||
@@ -193,6 +193,8 @@
|
||||
"match.timeline.teamSp": "",
|
||||
"match.waitingForConfirmation": "",
|
||||
"match.undoReport": "",
|
||||
"match.weapon.yourWeapon": "",
|
||||
"match.weapon.undoWeapon": "",
|
||||
"match.confirmScore": "",
|
||||
"match.confirmScore.wrongHint": "",
|
||||
"preparing.joinQ": "",
|
||||
|
||||
@@ -193,6 +193,8 @@
|
||||
"match.timeline.teamSp": "",
|
||||
"match.waitingForConfirmation": "",
|
||||
"match.undoReport": "",
|
||||
"match.weapon.yourWeapon": "",
|
||||
"match.weapon.undoWeapon": "",
|
||||
"match.confirmScore": "",
|
||||
"match.confirmScore.wrongHint": "",
|
||||
"preparing.joinQ": "Rejoindre la queue",
|
||||
|
||||
@@ -193,6 +193,8 @@
|
||||
"match.timeline.teamSp": "",
|
||||
"match.waitingForConfirmation": "",
|
||||
"match.undoReport": "",
|
||||
"match.weapon.yourWeapon": "",
|
||||
"match.weapon.undoWeapon": "",
|
||||
"match.confirmScore": "",
|
||||
"match.confirmScore.wrongHint": "",
|
||||
"preparing.joinQ": "",
|
||||
|
||||
@@ -193,6 +193,8 @@
|
||||
"match.timeline.teamSp": "",
|
||||
"match.waitingForConfirmation": "",
|
||||
"match.undoReport": "",
|
||||
"match.weapon.yourWeapon": "",
|
||||
"match.weapon.undoWeapon": "",
|
||||
"match.confirmScore": "",
|
||||
"match.confirmScore.wrongHint": "",
|
||||
"preparing.joinQ": "Unisciti alla coda",
|
||||
|
||||
@@ -193,6 +193,8 @@
|
||||
"match.timeline.teamSp": "",
|
||||
"match.waitingForConfirmation": "",
|
||||
"match.undoReport": "",
|
||||
"match.weapon.yourWeapon": "",
|
||||
"match.weapon.undoWeapon": "",
|
||||
"match.confirmScore": "",
|
||||
"match.confirmScore.wrongHint": "",
|
||||
"preparing.joinQ": "列に入る",
|
||||
|
||||
@@ -193,6 +193,8 @@
|
||||
"match.timeline.teamSp": "",
|
||||
"match.waitingForConfirmation": "",
|
||||
"match.undoReport": "",
|
||||
"match.weapon.yourWeapon": "",
|
||||
"match.weapon.undoWeapon": "",
|
||||
"match.confirmScore": "",
|
||||
"match.confirmScore.wrongHint": "",
|
||||
"preparing.joinQ": "",
|
||||
|
||||
@@ -193,6 +193,8 @@
|
||||
"match.timeline.teamSp": "",
|
||||
"match.waitingForConfirmation": "",
|
||||
"match.undoReport": "",
|
||||
"match.weapon.yourWeapon": "",
|
||||
"match.weapon.undoWeapon": "",
|
||||
"match.confirmScore": "",
|
||||
"match.confirmScore.wrongHint": "",
|
||||
"preparing.joinQ": "",
|
||||
|
||||
@@ -193,6 +193,8 @@
|
||||
"match.timeline.teamSp": "",
|
||||
"match.waitingForConfirmation": "",
|
||||
"match.undoReport": "",
|
||||
"match.weapon.yourWeapon": "",
|
||||
"match.weapon.undoWeapon": "",
|
||||
"match.confirmScore": "",
|
||||
"match.confirmScore.wrongHint": "",
|
||||
"preparing.joinQ": "",
|
||||
|
||||
@@ -193,6 +193,8 @@
|
||||
"match.timeline.teamSp": "",
|
||||
"match.waitingForConfirmation": "",
|
||||
"match.undoReport": "",
|
||||
"match.weapon.yourWeapon": "",
|
||||
"match.weapon.undoWeapon": "",
|
||||
"match.confirmScore": "",
|
||||
"match.confirmScore.wrongHint": "",
|
||||
"preparing.joinQ": "Entrar na fila",
|
||||
|
||||
@@ -193,6 +193,8 @@
|
||||
"match.timeline.teamSp": "",
|
||||
"match.waitingForConfirmation": "",
|
||||
"match.undoReport": "",
|
||||
"match.weapon.yourWeapon": "",
|
||||
"match.weapon.undoWeapon": "",
|
||||
"match.confirmScore": "",
|
||||
"match.confirmScore.wrongHint": "",
|
||||
"preparing.joinQ": "Присоединиться к очереди",
|
||||
|
||||
@@ -193,6 +193,8 @@
|
||||
"match.timeline.teamSp": "",
|
||||
"match.waitingForConfirmation": "",
|
||||
"match.undoReport": "",
|
||||
"match.weapon.yourWeapon": "",
|
||||
"match.weapon.undoWeapon": "",
|
||||
"match.confirmScore": "",
|
||||
"match.confirmScore.wrongHint": "",
|
||||
"preparing.joinQ": "开始匹配",
|
||||
|
||||
Reference in New Issue
Block a user