SendouQ: add temporary screen special banning solution Closes #1588

This commit is contained in:
Kalle 2023-12-05 21:23:21 +02:00
parent d267a4c7d1
commit 82013bb800
9 changed files with 174 additions and 0 deletions

View File

@ -547,6 +547,7 @@ export interface User {
>;
qWeaponPool: ColumnType<MainWeaponId[] | null, string | null, string | null>;
plusSkippedForSeasonNth: number | null;
noScreen: number;
}
export interface UserResultHighlight {

View File

@ -159,3 +159,15 @@ export async function findGroupById({
})),
} as GroupForMatch;
}
export function groupMembersNoScreenSettings(groups: GroupForMatch[]) {
return db
.selectFrom("User")
.select("User.noScreen")
.where(
"User.id",
"in",
groups.flatMap((group) => group.members.map((member) => member.id)),
)
.execute();
}

View File

@ -10,6 +10,7 @@ export async function settingsByUserId(userId: number) {
"User.vc",
"User.languages",
"User.qWeaponPool",
"User.noScreen",
])
.where("id", "=", userId)
.executeTakeFirstOrThrow();
@ -62,3 +63,19 @@ export function updateSendouQWeaponPool(args: {
.where("User.id", "=", args.userId)
.execute();
}
export function updateNoScreen({
noScreen,
userId,
}: {
noScreen: number;
userId: number;
}) {
return db
.updateTable("User")
.set({
noScreen,
})
.where("User.id", "=", userId)
.execute();
}

View File

@ -2,6 +2,7 @@ import { z } from "zod";
import { languagesUnified } from "~/modules/i18n/config";
import {
_action,
checkboxValueToBoolean,
modeShort,
noDuplicates,
safeJSONParse,
@ -42,4 +43,8 @@ export const settingsActionSchema = z.union([
z.array(weaponSplId).max(SENDOUQ_WEAPON_POOL_MAX_SIZE),
),
}),
z.object({
_action: _action("UPDATE_NO_SCREEN"),
noScreen: z.preprocess(checkboxValueToBoolean, z.boolean()),
}),
]);

View File

@ -37,6 +37,8 @@ import { settingsActionSchema } from "../q-settings-schemas.server";
import styles from "../q-settings.css";
import { BANNED_MAPS } from "../banned-maps";
import { Divider } from "~/components/Divider";
import { Toggle } from "~/components/Toggle";
import { FormMessage } from "~/components/FormMessage";
export const links: LinksFunction = () => {
return [{ rel: "stylesheet", href: styles }];
@ -88,6 +90,13 @@ export const action = async ({ request }: ActionArgs) => {
});
break;
}
case "UPDATE_NO_SCREEN": {
await QSettingsRepository.updateNoScreen({
userId: user.id,
noScreen: Number(data.noScreen),
});
break;
}
default: {
assertUnreachable(data);
}
@ -112,6 +121,7 @@ export default function SendouQSettingsPage() {
<WeaponPool />
<VoiceChat />
<Sounds />
<Misc />
</div>
</Main>
);
@ -652,3 +662,53 @@ function SoundCheckboxes() {
</div>
);
}
function Misc() {
const data = useLoaderData<typeof loader>();
const [checked, setChecked] = React.useState(Boolean(data.settings.noScreen));
const { t } = useTranslation(["common", "q"]);
const fetcher = useFetcher();
return (
<details>
<summary className="q-settings__summary">
<div>Misc</div>
</summary>
<fetcher.Form method="post" className="mb-4 ml-2-5 stack sm">
<div className="stack horizontal xs items-center">
<Toggle
checked={checked}
setChecked={setChecked}
id="noScreen"
name="noScreen"
/>
<label className="mb-0" htmlFor="noScreen">
Avoid Splattercolor Screen
</label>
</div>
<FormMessage type="info">
Accessibility concerns related to the new special have been raised.{" "}
<a
href="https://twitter.com/ProChara/status/1730986554078945562"
target="_blank"
rel="noopener noreferrer"
>
Read more here
</a>
. Enabling this setting will make the special banned in SendouQ
lobbies and tournament matches you play via sendou.ink
</FormMessage>
<div className="mt-6">
<SubmitButton
size="big"
className="mx-auto"
_action="UPDATE_NO_SCREEN"
state={fetcher.state}
>
{t("common:actions.save")}
</SubmitButton>
</div>
</fetcher.Form>
</details>
);
}

View File

@ -434,6 +434,23 @@
letter-spacing: 1px;
}
.q-match__screen-legality svg {
width: 24px;
}
.q-match__screen-legality .alert {
padding-block: var(--s-1);
padding-inline: var(--s-2-5);
}
.q-match__screen-legality__button:focus-visible {
outline: none !important;
}
.q-match__screen-legality__button:focus-visible .alert {
background-color: var(--bg-lighter);
}
@media screen and (min-width: 640px) {
.q-match__teams-container {
grid-template-columns: 1fr 1fr;

View File

@ -100,6 +100,8 @@ import { DiscordIcon } from "~/components/icons/Discord";
import { useWindowSize } from "~/hooks/useWindowSize";
import { joinListToNaturalString } from "~/utils/arrays";
import { NewTabs } from "~/components/NewTabs";
import { Alert } from "~/components/Alert";
import cachified from "cachified";
export const meta: V2_MetaFunction = (args) => {
const data = args.data as SerializeFrom<typeof loader> | null;
@ -419,6 +421,22 @@ export const loader = async ({ params, request }: LoaderArgs) => {
? reportedWeaponsByMatchId(matchId)
: null;
const banScreen = !match.isLocked
? await cachified({
key: `matches-screen-ban-${match.id}`,
cache,
async getFreshValue() {
const noScreenSettings =
await QMatchRepository.groupMembersNoScreenSettings([
groupAlpha,
groupBravo,
]);
return noScreenSettings.some((user) => user.noScreen);
},
})
: null;
return {
match: censoredMatch,
matchChatCode: canAccessMatchChat ? match.chatCode : null,
@ -426,6 +444,7 @@ export const loader = async ({ params, request }: LoaderArgs) => {
groupChatCode: groupChatCode(),
groupAlpha: censoredGroupAlpha,
groupBravo: censoredGroupBravo,
banScreen,
groupMemberOf: isTeamAlphaMember
? ("ALPHA" as const)
: isTeamBravoMember
@ -1116,6 +1135,11 @@ function BottomSection({
</FormWithConfirm>
) : null;
const screenLegalityInfoElement =
data.banScreen !== null ? (
<ScreenLegalityInfo ban={data.banScreen} />
) : null;
const chatHidden = chatRooms.length === 0;
if (!showMid && chatHidden) {
@ -1128,6 +1152,7 @@ function BottomSection({
<div className="stack horizontal lg items-center justify-center">
{roomJoiningInfoElement}
<div className="stack md">
{screenLegalityInfoElement}
{rulesButtonElement}
{helpdeskButtonElement}
{cancelMatchElement}
@ -1176,6 +1201,7 @@ function BottomSection({
>
<div className="stack md">
{roomJoiningInfoElement}
{screenLegalityInfoElement}
{rulesButtonElement}
{helpdeskButtonElement}
{cancelMatchElement}
@ -1199,6 +1225,28 @@ function BottomSection({
);
}
function ScreenLegalityInfo({ ban }: { ban: boolean }) {
return (
<div className="q-match__screen-legality">
<Popover
triggerClassName="minimal tiny q-match__screen-legality__button"
buttonChildren={
<Alert variation={ban ? "ERROR" : "SUCCESS"}>
<div className="stack xs horizontal items-center">
<WeaponImage weaponSplId={401} width={30} variant="build" />
<WeaponImage weaponSplId={6021} width={30} variant="build" />
</div>
</Alert>
}
>
{ban
? "Weapons with Splattercolor Screen are not allowed in this match"
: "Weapons with Splattercolor Screen are allowed in this match"}
</Popover>
</div>
);
}
function InfoWithHeader({ header, value }: { header: string; value: string }) {
return (
<div>

View File

@ -37,6 +37,15 @@ export default function SendouqRules() {
should be played out.
</div>
<h2 className="text-lg mt-4">Unallowed weapons</h2>
<div>
If someone picks an unallowed weapon game can be canceled within 1
minute by any player. For the replay everyone has to use the same
weapons and gear except the player with unallowed weapon who should
switch to the allowed variant of the weapon. For example had a player
picked Foil Squeezer they need to play regular Squeezer in the replay.
</div>
<h2 className="text-lg mt-4">Subs</h2>
<div>
There are no subs. If a player is unavailable to play from either team

View File

@ -0,0 +1,5 @@
module.exports.up = function (db) {
db.prepare(
/* sql */ `alter table "User" add "noScreen" integer default 0`,
).run();
};