feedback7 G-SMALL: co-op rooms can make AI teammates draft last (PR #368 by @anAtheist987)
Applied the host-only room option 「AI 队友最后选择」, default off. Off, the draft order and the random streams stay as they are. On, humans draft before AI seats after the existing shuffle, and a skip stays ahead of the AI seats. Dropped docs/history/0.2.2.md and every §27 citation. The player-facing sentence is in docs/PLAYING.md. The room-option rule is in docs/design/match.md, docs/design/client.md and docs/design/network.md. The #338 per-bot faction and ban presets are not included. Tests: node --test test/lobby-ai-last.test.js test/match/ai-picks-last.test.js test/ui/ai-picks-last.test.js — 18 pass. node --test test/lobby.test.js test/client-static.test.js — 378 pass. node tools/i18n.mjs check --all --strict — 1032/1032 in en, ja, ko and zh-TW, 0 errors.
This commit is contained in:
1 parent
ae22335cfe
commit
fd791a66cb
19 files changed
+527
-21
No files matched your search
@@ -1,6 +1,6 @@
|
||||
// Room screen (同盟等待室): 4 seat cards (avatar frame, name, ready state, AI badge, host crown),
|
||||
// host controls (difficulty picker, add/remove AI in co-op, start), invite code with copy code /
|
||||
// copy link, ready toggle and leave.
|
||||
// host controls (difficulty picker, add/remove AI and the 「AI 队友最后选择」 switch in co-op, start), invite code with
|
||||
// copy code / copy link, ready toggle and leave.
|
||||
//
|
||||
// Start rule (server/lobby.js): room.start needs every *other* human connected and ready; the
|
||||
// host's start counts as the host's ready. So 开始模拟 is enabled exactly then and sends room.start
|
||||
@@ -69,6 +69,19 @@ export function roomFacts(room, myId) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The co-op room option 「AI 队友最后选择」 (GitHub #338; room.setAiPicksLast, room.state.aiPicksLast): in the strategy and
|
||||
* 机变 drafts every human picks before the AI teammates. null in a solo room (no AI teammates); otherwise its state (off
|
||||
* when the server sends none) and whether this player may switch it — the host only, the others see it read-only.
|
||||
* @param {any} room room.state payload
|
||||
* @param {any} myId
|
||||
* @returns {{ on: boolean, editable: boolean } | null}
|
||||
*/
|
||||
export function aiLastOption(room, myId) {
|
||||
if (!room || room.mode === 'solo') return null;
|
||||
return { on: room.aiPicksLast === true, editable: room.hostId != null && room.hostId === myId };
|
||||
}
|
||||
|
||||
/** Invite link for a room code (current page URL with ?room=CODE). */
|
||||
export function inviteLink(code) {
|
||||
const loc = globalThis.location;
|
||||
@@ -189,6 +202,21 @@ function DifficultyPicker({ room, isHost, busy, onPick }) {
|
||||
</div>`;
|
||||
}
|
||||
|
||||
/** The 「AI 队友最后选择」 switch (co-op): the host toggles it, everybody else sees its state. */
|
||||
function AiLastToggle({ option, busy, onToggle }) {
|
||||
if (!option) return null;
|
||||
return html`<div class="ailast">
|
||||
<${Tooltip} text=${t('策略与机变轮选时,所有博士先于 AI 队友选择')}>
|
||||
<button type="button" role="switch" aria-checked=${option.on ? 'true' : 'false'}
|
||||
class=${`dpick__opt ailast__opt${option.on ? ' is-active' : ''}`} disabled=${!option.editable || !!busy}
|
||||
onClick=${() => option.editable && onToggle(!option.on)}>
|
||||
<${Icon} name="check" class=${option.on ? 'is-on' : ''} />${t('AI 队友最后选择')}
|
||||
</button>
|
||||
<//>
|
||||
${option.editable ? null : html`<span class="t-dim">${t('由创建者设置')}</span>`}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
/** Room screen component. */
|
||||
export function RoomScreen() {
|
||||
const room = useStore((s) => s.room);
|
||||
@@ -229,6 +257,7 @@ export function RoomScreen() {
|
||||
if (ok) run(`kick${seat}`, () => net.request('room.kick', { seat, playerId }));
|
||||
};
|
||||
const setDifficulty = (difficulty) => run('diff', () => net.request('room.setDifficulty', { difficulty }));
|
||||
const setAiLast = (on) => run('ailast', () => net.request('room.setAiPicksLast', { on }));
|
||||
// spectator seats: the host frees one; a spectator takes a free player seat with room.join of this room
|
||||
const removeSpectator = (playerId) => run(`rs${playerId}`, () => net.request('room.removeSpectator', { playerId }));
|
||||
const sit = () => run('sit', () => net.request('room.join', { code: room.code }));
|
||||
@@ -307,7 +336,10 @@ export function RoomScreen() {
|
||||
<footer class="room-bar">
|
||||
<div class="room-bar__left">
|
||||
<span class="room-bar__label">${t('模拟难度')}<${MicroLabel}>DIFFICULTY<//></span>
|
||||
<${DifficultyPicker} room=${room} isHost=${facts.isHost} busy=${busy} onPick=${setDifficulty} />
|
||||
<div class="room-bar__opts">
|
||||
<${DifficultyPicker} room=${room} isHost=${facts.isHost} busy=${busy} onPick=${setDifficulty} />
|
||||
<${AiLastToggle} option=${aiLastOption(room, me.playerId)} busy=${busy} onToggle=${setAiLast} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="room-bar__center">
|
||||
<div class="ready-count" hidden=${!coop}>
|
||||
|
||||
Reference in new issue
Block a user