Add event management features to API, including event cancellation, deletion, registration closure, and workflow balance generation. Introduce new roster management endpoints and enhance participant handling with avoided player preferences. Update database schema and service logic to support these changes.
functionLoadingState({label='Loading match data…'}:{label?: string}){return<divclassName="state-card"role="status"><RefreshCwclassName="spin"/><h2>{label}</h2><p>Syncingwiththeserver.</p></div>}
<PageHeadereyebrow="Scrim schedule"title="Your events"description="Join a mix, check the roster, and get match-ready."actions={<divclassName="segmented"><buttonclassName={filter==='upcoming'?'active':''}onClick={()=>setFilter('upcoming')}>Upcoming</button><buttonclassName={filter==='all'?'active':''}onClick={()=>setFilter('all')}>All</button></div>}/>
<sectionclassName="card event-hero"><divclassName="event-time"><CalendarDays/><div><span>Starts</span><strong>{newDate(event.startsAt).toLocaleString(undefined,{weekday:'long',month:'long',day:'numeric',hour:'2-digit',minute:'2-digit'})}</strong></div></div><divclassName="event-time"><Clock3/><div><span>Registrationcloses</span><strong>{newDate(event.registrationDeadline).toLocaleString(undefined,{weekday:'short',hour:'2-digit',minute:'2-digit'})}</strong></div></div><divclassName="divider"/><h2>Areyouplaying?</h2><p>{event.workflowState==='RegistrationOpen'?'Yourlatestresponse is sent to the organizer.':'Registration has been closed by the organizer.'}</p><divclassName="rsvp-control"role="group"aria-label="RSVP status">{(Object.keys(labels)asRsvpStatus[]).map((status)=><buttonkey={status}disabled={rsvpMutation.isPending||event.workflowState!=='RegistrationOpen'}className={rsvp===status?`active ${status}`:''}aria-pressed={rsvp===status}onClick={()=>rsvpMutation.mutate(status)}>{status==='going'?<Check/>:status==='maybe'?<CircleHelp/>:<X/>}{labels[status]}</button>)}</div>{rsvpMutation.isSuccess&&<divclassName="save-note"><Check/>Responsesaved·Serverconfirmedjustnow</div>}{rsvpMutation.isError&&<pclassName="error-note">{rsvpMutation.error.message}</p>}</section>
return<divclassName="page faq-page"><PageHeadereyebrow="Rules and help"title="Frequently asked questions"description="Everything players and captains need before the scrim starts."/><divclassName="faq-grid">
constroles: Array<{key: PlayerRole;label: string;hint: string}>=[{key:'tank',label:'Tank',hint:'Space, pressure, and frontline'},{key:'damage',label:'Damage',hint:'Eliminations and map control'},{key:'support',label:'Support',hint:'Sustain, utility, and tempo'}]
return<divclassName="page narrow"><PageHeadereyebrow="Player profile"title="Set your competitive ranks"description="Your role ranks help the server build fair, role-locked teams."/><sectionclassName="profile-banner card"><divclassName="large-avatar">{profile.data.displayName.slice(0,2).toUpperCase()}</div><div><h2>{profile.data.displayName}</h2><p>{profile.data.battleTag}</p><Badgetone="blue"><Gamepad2/>ConnectedviaDiscord</Badge></div></section><formclassName="card ratings-form"onSubmit={submit}><divclassName="section-title"><div><h2>Competitiveranks</h2><p>SelectyourcurrentOverwatchrankforeachrole.</p></div><span>Updated{newDate(profile.data.ratings.updatedAt).toLocaleDateString()}</span></div>{roles.map((role)=><labelclassName={`rating-row role-${role.key}`}key={role.key}><div><spanclassName="role-icon">{role.key==='tank'?<Shield/>:role.key==='damage'?<Zap/>:<Sparkles/>}</span><span><strong>{role.label}</strong><small>{role.hint}</small></span></div><selectid={`rating-${role.key}`}className="rank-select"value={ratings[role.key]}onChange={(e)=>setRatings({...ratings,[role.key]:toRankOrdinal(Number(e.target.value))})}>{options.map((option)=><optionvalue={option.value}key={option.value}>{option.label}</option>)}</select></label>)}<sectionclassName="preference-section"><div><h2>Preferredroles</h2><p>Chooseeveryroleyouenjoyplaying.</p></div><divclassName="preference-chips">{roles.map((role)=><buttontype="button"aria-pressed={preferredRoles.includes(role.key)}className={preferredRoles.includes(role.key)?'active':''}key={role.key}onClick={()=>setPreferredRoles((current)=>current.includes(role.key)?current.filter((item)=>item!==role.key):[...current,role.key])}>{role.label}</button>)}</div></section><sectionclassName="preference-section"><div><h2>Preferredteammates</h2><p>Chooseupto3players.Thebalancertreatstheseaspreferences,notguarantees.</p></div><divclassName="teammate-grid">{players.data.filter((player)=>player.id!==profile.data.id).map((player)=>{constselected=preferredPlayerIds.includes(player.id);constdisabled=!selected&&preferredPlayerIds.length>=3;return<labelclassName={selected?'selected':''}key={player.id}><inputtype="checkbox"checked={selected}disabled={disabled}onChange={()=>setPreferredPlayerIds((current)=>selected?current.filter((id)=>id!==player.id):[...current,player.id])}/><spanclassName="mini-avatar">{player.displayName.slice(0,2).toUpperCase()}</span><strong>{player.displayName}</strong></label>})}</div></section><divclassName="form-footer"><p><Shield/>Onlyyoucanedityourranksandpreferences.{update.isError&&`${update.error.message}`}</p><buttonclassName="button primary"type="submit"disabled={update.isPending}>{update.isPending?'Saving…':update.isSuccess?<><Check/>Saved</>:'Save profile'}</button></div></form></div>
return<divclassName="page narrow"><PageHeadereyebrow="Player profile"title="Set your competitive ranks"description="Your role ranks help the server build fair, role-locked teams."/><sectionclassName="profile-banner card"><divclassName="large-avatar">{profile.data.displayName.slice(0,2).toUpperCase()}</div><div><h2>{profile.data.displayName}</h2><p>{profile.data.battleTag}</p><Badgetone="blue"><Gamepad2/>ConnectedviaDiscord</Badge></div></section><formclassName="card ratings-form"onSubmit={submit}><divclassName="section-title"><div><h2>Competitiveranks</h2><p>SelectyourcurrentOverwatchrankforeachrole.</p></div><span>Updated{newDate(profile.data.ratings.updatedAt).toLocaleDateString()}</span></div>{roles.map((role)=><labelclassName={`rating-row role-${role.key}`}key={role.key}><div><spanclassName="role-icon">{role.key==='tank'?<Shield/>:role.key==='damage'?<Zap/>:<Sparkles/>}</span><span><strong>{role.label}</strong><small>{role.hint}</small></span></div><selectid={`rating-${role.key}`}className="rank-select"value={ratings[role.key]}onChange={(e)=>setRatings({...ratings,[role.key]:toRankOrdinal(Number(e.target.value))})}>{options.map((option)=><optionvalue={option.value}key={option.value}>{option.label}</option>)}</select></label>)}<sectionclassName="preference-section"><div><h2>Preferredroles</h2><p>Chooseeveryroleyouenjoyplaying.</p></div><divclassName="preference-chips">{roles.map((role)=><buttontype="button"aria-pressed={preferredRoles.includes(role.key)}className={preferredRoles.includes(role.key)?'active':''}key={role.key}onClick={()=>setPreferredRoles((current)=>current.includes(role.key)?current.filter((item)=>item!==role.key):[...current,role.key])}>{role.label}</button>)}</div></section><sectionclassName="preference-section"><div><h2>Preferredteammates</h2><p>Chooseupto3players.Thebalancertreatstheseaspreferences,notguarantees.</p></div><divclassName="teammate-grid">{eligiblePlayers.filter((player)=>!avoidedPlayerIds.includes(player.id)).map((player)=>{constselected=preferredPlayerIds.includes(player.id);constdisabled=!selected&&preferredPlayerIds.length>=3;return<labelclassName={selected?'selected':''}key={player.id}><inputtype="checkbox"checked={selected}disabled={disabled}onChange={()=>setPreferredPlayerIds((current)=>selected?current.filter((id)=>id!==player.id):[...current,player.id])}/><spanclassName="mini-avatar">{player.displayName.slice(0,2).toUpperCase()}</span><strong>{player.displayName}</strong></label>})}</div></section><sectionclassName="preference-section avoid-section"><div><h2>Avoidedteammates</h2><p>Chooseupto3players.Thisisasoftpreference,sobalancecanstillplaceyoutogetherwhennecessary.</p></div><divclassName="teammate-grid">{eligiblePlayers.filter((player)=>!preferredPlayerIds.includes(player.id)).map((player)=>{constselected=avoidedPlayerIds.includes(player.id);constdisabled=!selected&&avoidedPlayerIds.length>=3;return<labelclassName={selected?'selected avoided':''}key={player.id}><inputtype="checkbox"checked={selected}disabled={disabled}onChange={()=>setAvoidedPlayerIds((current)=>selected?current.filter((id)=>id!==player.id):[...current,player.id])}/><spanclassName="mini-avatar">{player.displayName.slice(0,2).toUpperCase()}</span><strong>{player.displayName}</strong></label>})}</div></section><divclassName="form-footer"><p><Shield/>Onlyyoucanedityourranksandpreferences.{update.isError&&`${update.error.message}`}</p><buttonclassName="button primary"type="submit"disabled={update.isPending}>{update.isPending?'Saving…':update.isSuccess?<><Check/>Saved</>:'Save profile'}</button></div></form></div>
return<divclassName="page"><PageHeadereyebrow="Admin control room"title={createMode?'Create event':selected?.title??'No events yet'}description={selected?`Registration closes ${newDate(selected.registrationDeadline).toLocaleString()}.`:'Create the first community event.'}actions={<>{events.data&&events.data.length>0&&<selectaria-label="Selected event"value={selected?.id??''}onChange={(e)=>{setSelectedId(e.target.value);setCreateMode(false)}}>{events.data.map((event)=><optionvalue={event.id}key={event.id}>{event.title}</option>)}</select>}<buttonclassName="button secondary"onClick={()=>{setCreateMode(true);setTab('events')}}><Sparkles/>Newevent</button></>}/><divclassName="tabs"role="tablist">{(['events','participants','balance']asAdminTab[]).map((item)=><buttonkey={item}role="tab"aria-selected={tab===item}className={tab===item?'active':''}disabled={!selected&&item!=='events'}onClick={()=>setTab(item)}>{item==='events'?'Event setup':item==='participants'?'Participants':'Balance teams'}</button>)}</div>{tab==='events'&&<EventSetupevent={createMode?undefined:selected}onCreated={(event)=>{setCreateMode(false);setSelectedId(event.id)}}/>}{tab==='participants'&&selected&&<ParticipantseventId={selected.id}/>}{tab==='balance'&&selected&&<BalanceVieweventId={selected.id}/>}</div>
return<divclassName="page"><PageHeadereyebrow="Admin control room"title={createMode?'Create event':selected?.title??'No events yet'}description={selected?`Registration closes ${newDate(selected.registrationDeadline).toLocaleString()}.`:'Create the first community event.'}actions={<>{events.data&&events.data.length>0&&<selectaria-label="Selected event"value={selected?.id??''}onChange={(e)=>{setSelectedId(e.target.value);setCreateMode(false)}}>{events.data.map((event)=><optionvalue={event.id}key={event.id}>{event.title}</option>)}</select>}<buttonclassName="button secondary"onClick={()=>{setCreateMode(true);setTab('events')}}><Sparkles/>Newevent</button></>}/><divclassName="tabs"role="tablist">{(['events','participants','balance']asAdminTab[]).map((item)=><buttonkey={item}role="tab"aria-selected={tab===item}className={tab===item?'active':''}disabled={!selected&&item!=='events'}onClick={()=>setTab(item)}>{item==='events'?'Event setup':item==='participants'?'Participants':'Balance teams'}</button>)}</div>{tab==='events'&&<EventSetupevent={createMode?undefined:selected}onCreated={(event)=>{setCreateMode(false);setSelectedId(event.id)}}onDeleted={()=>{setSelectedId(undefined);setCreateMode(false)}}/>}{tab==='participants'&&selected&&<ParticipantseventId={selected.id}/>}{tab==='balance'&&selected&&<BalanceVieweventId={selected.id}/>}</div>
}
functiontoLocalInput(value: string){
@@ -214,8 +264,9 @@ function eventInput(event?: MixEvent): EventInput {
return<divclassName="admin-grid"><formclassName="card settings-card"onSubmit={(e)=>{e.preventDefault();save.mutate()}}><divclassName="section-title"><div><h2>{event?'Event settings':'New event'}</h2><p>Schedulingandregistration</p></div>{event&&<Badgetone="success">Published</Badge>}</div><divclassName="field-grid"><label>Eventname<inputrequiredvalue={form.name}onChange={(e)=>set('name',e.target.value)}/></label><label>Starttime<inputrequiredtype="datetime-local"value={form.startsAt}onChange={(e)=>set('startsAt',e.target.value)}/></label><label>Endtime<inputrequiredtype="datetime-local"value={form.endsAt}onChange={(e)=>set('endsAt',e.target.value)}/></label><label>Registrationdeadline<inputrequiredtype="datetime-local"value={form.registrationDeadline}onChange={(e)=>set('registrationDeadline',e.target.value)}/></label></div><label>Description<textareavalue={form.description}onChange={(e)=>set('description',e.target.value)}/></label><divclassName="form-footer"><p>{save.isError?save.error.message:'Times are sent to the server in UTC.'}</p><buttonclassName="button primary"disabled={save.isPending}>{save.isPending?'Saving…':event?'Save changes':'Create event'}</button></div></form><asideclassName="card admin-summary"><h2>Serverauthority</h2><divclassName="readiness-score"><Shield/><span>Validationrunsonthebackend</span></div><ul><li><Check/>UTCscheduling</li><li><Check/>Auditedadminchanges</li><li><Check/>Registrationdeadlinerules</li></ul></aside></div>
<divclassName="form-footer"><p>{save.isError?save.error.message : cancel.isError?cancel.error.message : remove.isError?remove.error.message:'Times are sent to the server in UTC.'}</p><buttonclassName="button primary"disabled={save.isPending}>{save.isPending?'Saving…':event?'Save changes':'Create event'}</button></div>
{state==='RegistrationOpen'&&<WorkflowActiontitle="Close registration"description="Freeze the Going participant list before balancing."button="Close registration"pending={close.isPending}onClick={()=>close.mutate()}/>}
{state==='Balancing'&&candidates.length===0&&<WorkflowActiontitle="Generate balance candidates"description="Generate again after a refresh to choose one of the server candidates."button="Generate candidates"pending={generate.isPending}onClick={()=>generate.mutate()}/>}
{state==='RostersDraft'&&roster.data&&<WorkflowActiontitle="Confirm rosters"description="Every team must have a valid 1/2/2 lineup and a captain."button="Confirm rosters"pending={confirm.isPending}onClick={()=>confirm.mutate()}/>}
{state==='RostersConfirmed'&&<WorkflowActiontitle="Start scrim"description="This atomically creates the Bo3 series or tournament bracket."button="Start scrim"pending={start.isPending}onClick={()=>start.mutate()}/>}
return<divclassName="page live-page"><PageHeadereyebrow={`${data.roundLabel} · ${data.status}`}title="Series control"description="Actions are validated and recorded by the server."actions={<LinkclassName="button secondary"to="/watch/$seriesId"params={{seriesId: data.id}}><Radio/>Spectatorview</Link>}/><Scoreboardseries={data}/><divclassName="live-layout"><sectionclassName={`turn-card team-${data.currentStep.activeTeam??'alpha'}`}><divclassName="turn-header"><div><spanclassName="eyebrow">Currentstep</span><h2>{data.currentStep.title}</h2><p>{data.currentStep.instruction}</p></div>{data.currentStep.activeTeam&&<divclassName="turn-team"><span>Actingteam</span><strong>{data.currentStep.activeTeam==='alpha'?data.teamAlpha.name : data.teamBeta.name}</strong></div>}</div>{data.currentStep.kind==='result'?<formclassName="result-form"onSubmit={(e)=>{e.preventDefault();result.mutate()}}><label>Mapname<inputrequiredvalue={mapName}onChange={(e)=>setMapName(e.target.value)}placeholder="e.g. Lijiang Tower"/></label><label>Outcome<selectvalue={outcome}onChange={(e)=>setOutcome(e.target.valueasMapOutcome)}><optionvalue="TeamAWin">{data.teamAlpha.name}wins</option><optionvalue="TeamBWin">{data.teamBeta.name}wins</option><optionvalue="Draw">Draw</option></select></label><buttonclassName="button team-action"disabled={result.isPending}>{result.isPending?'Recording…':'Record result'}</button>{result.isError&&<pclassName="error-note">{result.error.message}</p>}</form>:<><divclassName="draft-options"role="radiogroup"aria-label="Available draft choices">{data.options.map((option)=><buttonrole="radio"aria-checked={selected===option.id}key={option.id}disabled={option.disabled}className={selected===option.id?'selected':''}onClick={()=>setSelected(option.id)}><spanclassName={`role-chip ${option.role}`}>{option.role?.slice(0,1).toUpperCase()}</span><strong>{option.name}</strong>{option.disabled?<small>{option.disabledReason}</small>:<Check/>}</button>)}</div><divclassName="turn-footer"><p><Shield/>Draftcommandsusededicatedbackendendpointsandserver-sidevalidation.</p><buttonclassName="button team-action"disabled={!selected}>Confirmaction<ChevronRight/></button></div></>}</section><asideclassName="match-sidebar"><MapTimelineseries={data}/><AuditLogseries={data}/></aside></div></div>
return<divclassName="page live-page"><PageHeadereyebrow={`${data.roundLabel} · ${data.status}`}title="Series control"description="Actions are validated and recorded by the server."actions={<LinkclassName="button secondary"to="/watch/$seriesId"params={{seriesId: data.id}}><Radio/>Spectatorview</Link>}/><Scoreboardseries={data}/><divclassName="live-layout"><sectionclassName={`turn-card team-${data.currentStep.activeTeam??'alpha'}`}><divclassName="turn-header"><div><spanclassName="eyebrow">Currentstep</span><h2>{data.currentStep.title}</h2><p>{data.currentStep.instruction}</p></div>{data.currentStep.activeTeam&&<divclassName="turn-team"><span>Actingteam</span><strong>{data.currentStep.activeTeam==='alpha'?data.teamAlpha.name : data.teamBeta.name}</strong></div>}</div>{data.currentStep.kind==='result'?<formclassName="result-form"onSubmit={(e)=>{e.preventDefault();result.mutate()}}><label>Outcome<selectvalue={outcome}onChange={(e)=>setOutcome(e.target.valueasMapOutcome)}><optionvalue="TeamAWin">{data.teamAlpha.name}wins</option><optionvalue="TeamBWin">{data.teamBeta.name}wins</option><optionvalue="Draw">Draw</option></select></label><buttonclassName="button team-action"disabled={result.isPending}>{result.isPending?'Recording…':'Record result'}</button>{result.isError&&<pclassName="error-note">{result.error.message}</p>}</form>:data.currentStep.kind==='complete'?<divclassName="turn-footer"><p><Check/>Seriescompletedandbracketupdated.</p></div>:<><divclassName="draft-options"role="radiogroup"aria-label="Available draft choices">{data.options.map((option)=><buttonrole="radio"aria-checked={selected===option.id}key={option.id}disabled={option.disabled}className={selected===option.id?'selected':''}onClick={()=>setSelected(option.id)}><spanclassName={`role-chip ${option.role}`}>{option.role?.slice(0,1).toUpperCase()}</span><strong>{option.name}</strong>{option.disabled?<small>{option.disabledReason}</small>:<Check/>}</button>)}</div><divclassName="turn-footer"><p><Shield/>Draftcommandsusededicatedbackendendpointsandserver-sidevalidation.</p><buttonclassName="button team-action"disabled={(data.currentStep.kind!=='coin_toss'&&!selected)||action.isPending}onClick={()=>action.mutate()}>{action.isPending?'Confirming…':data.currentStep.kind==='coin_toss'?'Toss coin':'Confirm action'}<ChevronRight/></button></div>{action.isError&&<pclassName="error-note">{action.error.message}</p>}</>}</section><asideclassName="match-sidebar"><MapTimelineseries={data}/><AuditLogseries={data}/></aside></div></div>
}
functionScoreboard({series}:{series: Series}){
@@ -344,13 +558,11 @@ function AuditLog({ series }: { series: Series }) { return <section className="c
'Choose every role you enjoy playing.':'Выберите все роли, на которых вам нравится играть.',
'Preferred teammates':'Предпочитаемые напарники',
'Choose up to 3 players. The balancer treats these as preferences, not guarantees.':'Выберите до 3 игроков. Балансировщик учитывает это как пожелание, а не гарантию.',
'Avoided teammates':'Избегаемые напарники',
'Choose up to 3 players. This is a soft preference, so balance can still place you together when necessary.':'Выберите до 3 игроков. Это мягкое предпочтение, поэтому при необходимости балансировщик всё ещё может поставить вас вместе.',
'Loading admin control room…':'Загрузка панели администратора…',
'Freeze the Going participant list before balancing.':'Зафиксировать список участников перед балансировкой.',
'Generate fair teams':'Сбалансировать команды',
'The optimizer considers ranks, preferred roles, and preferred teammates.':'Оптимизатор учитывает ранги, предпочитаемые роли и напарников.',
'Generate balance':'Сгенерировать баланс',
'Generate candidates':'Сгенерировать варианты',
'Roster editor':'Редактор составов',
'Captain':'Капитан',
'Choose captain':'Выбрать капитана',
'Swap…':'Обменять…',
'Reserve…':'Из резерва…',
'Confirm rosters':'Подтвердить составы',
'Every team must have a valid 1/2/2 lineup and a captain.':'В каждой команде должен быть состав 1/2/2 и капитан.',
'Start scrim':'Начать скрим',
'This atomically creates the Bo3 series or tournament bracket.':'Серия Bo3 или турнирная сетка будут созданы атомарно.',
'Scrim is live':'Скрим начался',
'Coin toss, bans, and map results are now available.':'Теперь доступны жеребьёвка, баны и результаты карт.',
'Open live series':'Открыть серию',
'Open bracket':'Открыть сетку',
'Scrim completed':'Скрим завершён',
'The final result and full audit trail are preserved.':'Итоговый результат и полный журнал действий сохранены.',
'Registration has been closed by the organizer.':'Организатор закрыл регистрацию.',
'Toss coin':'Подбросить монету',
'Confirming…':'Подтверждение…',
'Working…':'Выполняется…',
'Series completed and bracket updated.':'Серия завершена, сетка обновлена.',
'Loser picks the next map':'Проигравшая команда выбирает следующую карту',
'Rules and help':'Правила и помощь',
'Frequently asked questions':'Частые вопросы',
'Everything players and captains need before the scrim starts.':'Всё, что нужно знать игрокам и капитанам до начала скрима.',
'How are teams balanced?':'Как балансируются команды?',
'The server builds role-locked 1 Tank / 2 Damage / 2 Support teams. Rank balance is the main goal. Preferred roles, preferred teammates, and up to three avoided teammates are soft preferences and never override a fair valid roster.':'Сервер собирает команды 1 танк / 2 урона / 2 поддержки. Главная цель — баланс рангов. Предпочитаемые роли, напарники и до трёх avoid-игроков являются мягкими пожеланиями и не важнее честного корректного состава.',
'How does registration work?':'Как работает регистрация?',
'Choose Going, Maybe, or Can’t go before registration closes. Only Going players enter balancing. After an admin closes registration, normal RSVP changes are locked.':'До закрытия регистрации выберите «Иду», «Возможно» или «Не иду». В балансировку попадают только игроки со статусом «Иду». После закрытия регистрации обычные изменения блокируются.',
'What happens before the scrim?':'Что происходит перед скримом?',
'An admin generates balance candidates, chooses one, may swap players of the same role or use the reserve, assigns captains, confirms the rosters, and starts the scrim.':'Администратор создаёт варианты баланса, выбирает один, при необходимости меняет местами игроков одной роли или использует резерв, назначает капитанов, подтверждает составы и запускает скрим.',
'How is the first map selected?':'Как выбирается первая карта?',
'The initial coin toss decides which team bans first. Teams alternate map bans until one map remains. That map becomes the first map of the series.':'Начальная жеребьёвка определяет, какая команда банит первой. Команды по очереди банят карты, пока не останется одна — она становится первой картой серии.',
'Who picks later maps?':'Кто выбирает следующие карты?',
'After a captain or admin records a map result, the team that lost that map immediately chooses the next map from the next ruleset pool. Already played maps are unavailable. After a draw, the initial coin-toss loser receives the pick.':'После фиксации результата капитаном или администратором проигравшая команда сразу выбирает следующую карту из следующего пула регламента. Сыгранные карты недоступны. После ничьей выбор получает команда, проигравшая начальный жребий.',
'How do hero bans work?':'Как работают баны героев?',
'Before every map, teams make two bans each in alternating order. The initial coin-toss loser bans first. A team’s two bans must use different roles, and a team cannot repeat its own hero ban during the same series. It may ban a hero previously banned by the opponent.':'Перед каждой картой команды делают по два бана по очереди. Первой банит команда, проигравшая начальный жребий. Два бана команды должны относиться к разным ролям; повторять собственный бан в серии нельзя, но можно забанить героя, которого ранее банил соперник.',
'How does Bo3 scoring work?':'Как считается Bo3?',
'The first team to win two maps wins the series. A captain selects the result after each map; admins can correct mistakes. Tournament winners advance automatically.':'Первая команда с двумя победами выигрывает серию. После каждой карты капитан указывает результат, администратор может исправить ошибку. Победители турнира проходят дальше автоматически.',
'What can captains do?':'Что могут делать капитаны?',
'Captains can perform actions for their team: map bans, later-map picks, and hero bans. Admins can perform or correct any authoritative action. Every command is versioned and recorded in the match history.':'Капитаны могут действовать за свою команду: банить карты, выбирать следующие карты и банить героев. Администраторы могут выполнить или исправить любое действие. Все команды версионируются и записываются в историю матча.',
}
functiontranslate(value: string){
@@ -213,8 +269,11 @@ function translate(value: string) {
[/^(.+) wins$/,(m)=>`${m[1]} побеждает`],
[/^Team (.+)$/,(m)=>`Команда ${m[1]}`],
[/^RSVP for (.+)$/,(m)=>`Статус участия для ${m[1]}`],
[/^Remove (.+) from event$/,(m)=>`Удалить ${m[1]} из микса`],
[/^Open (.+)$/,(m)=>`Открыть ${m[1]}`],
[/^(\d+) preferred role assignments$/,(m)=>`${m[1]} назначений на предпочитаемые роли`],
[/^(\d+) preferred teammate pairs? kept together$/,(m)=>`${m[1]} предпочитаемых пар сохранено`],
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.