mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-04-24 23:19:39 -05:00
SendouQ: add temporary screen special banning solution Closes #1588
This commit is contained in:
parent
d267a4c7d1
commit
82013bb800
|
|
@ -547,6 +547,7 @@ export interface User {
|
|||
>;
|
||||
qWeaponPool: ColumnType<MainWeaponId[] | null, string | null, string | null>;
|
||||
plusSkippedForSeasonNth: number | null;
|
||||
noScreen: number;
|
||||
}
|
||||
|
||||
export interface UserResultHighlight {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
}),
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
5
migrations/044-no-screen.js
Normal file
5
migrations/044-no-screen.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
module.exports.up = function (db) {
|
||||
db.prepare(
|
||||
/* sql */ `alter table "User" add "noScreen" integer default 0`,
|
||||
).run();
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user