@yyyu-star/arcade-kit 0.19.1 → 0.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/README.md +48 -2
- package/dist/client.d.ts +44 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +103 -0
- package/dist/client.js.map +1 -1
- package/dist/fun-logic.d.ts +8 -0
- package/dist/fun-logic.d.ts.map +1 -1
- package/dist/fun-logic.js +12 -1
- package/dist/fun-logic.js.map +1 -1
- package/dist/game.d.ts +7 -0
- package/dist/game.d.ts.map +1 -1
- package/dist/game.js +38 -0
- package/dist/game.js.map +1 -1
- package/dist/internal.d.ts +4 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +6 -0
- package/dist/internal.js.map +1 -1
- package/dist/match.d.ts +85 -0
- package/dist/match.d.ts.map +1 -0
- package/dist/match.js +361 -0
- package/dist/match.js.map +1 -0
- package/dist/meter.d.ts +38 -0
- package/dist/meter.d.ts.map +1 -1
- package/dist/meter.js +56 -14
- package/dist/meter.js.map +1 -1
- package/dist/protocol.d.ts +35 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js.map +1 -1
- package/dist/room.d.ts +48 -1
- package/dist/room.d.ts.map +1 -1
- package/dist/room.js +157 -17
- package/dist/room.js.map +1 -1
- package/dist/table-chat.d.ts +5 -1
- package/dist/table-chat.d.ts.map +1 -1
- package/dist/table-chat.js +25 -2
- package/dist/table-chat.js.map +1 -1
- package/dist/table-fun.d.ts +31 -7
- package/dist/table-fun.d.ts.map +1 -1
- package/dist/table-fun.js +31 -14
- package/dist/table-fun.js.map +1 -1
- package/dist/worker.d.ts +12 -0
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +44 -1
- package/dist/worker.js.map +1 -1
- package/package.json +5 -1
- package/src/client.ts +118 -1
- package/src/fun-logic.ts +14 -1
- package/src/game.ts +40 -0
- package/src/internal.ts +8 -0
- package/src/match.ts +371 -0
- package/src/meter.ts +68 -12
- package/src/protocol.ts +41 -0
- package/src/room.ts +171 -18
- package/src/table-chat.ts +26 -3
- package/src/table-fun.ts +53 -14
- package/src/worker.ts +44 -2
package/src/table-fun.ts
CHANGED
|
@@ -55,6 +55,8 @@ export interface FunSeat {
|
|
|
55
55
|
temper?: FunTemper;
|
|
56
56
|
/** 名牌、筹码数这些挂在头像旁的东西(屏幕矩形,0.18.2):气泡避开它们,不盖住名字和钱数 */
|
|
57
57
|
plate?: RectLike;
|
|
58
|
+
/** 头像旁别的小标签(缺门签、庄家、托管……,屏幕矩形,0.20.1):和名牌一样,气泡和首次提示不盖 */
|
|
59
|
+
marks?: readonly RectLike[];
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
/**
|
|
@@ -84,9 +86,10 @@ export interface TableFunOptions {
|
|
|
84
86
|
button?(): RectLike | null;
|
|
85
87
|
/**
|
|
86
88
|
* "互动"按钮的样子(0.18.2,替掉游戏自己对 .atf-btn 的 CSS 覆盖):scale 把字(13px)和图标(22px)一起放大(舞台放大了的大屏用,最小 1);
|
|
87
|
-
* row 为 true
|
|
89
|
+
* row 为 true 时图标和字横排;fontSize、icon(0.20.1,像素)直接给字号和图标边长,给了就不按 scale 算(德州、麻将原来直接改 .atf-btn)。
|
|
90
|
+
* 颜色、圆角走主题变量(--ak-soft、--ak-border、--ak-radius-btn)。每次 refresh 现问
|
|
88
91
|
*/
|
|
89
|
-
buttonStyle?():
|
|
92
|
+
buttonStyle?(): ButtonStyle;
|
|
90
93
|
/** 发出去:好友房走 kit emote,单机直接回给 receive */
|
|
91
94
|
send(id: number, to?: number): void;
|
|
92
95
|
/** 现在能不能发(好友房:坐下了才能),默认能 */
|
|
@@ -112,6 +115,11 @@ export interface TableFunOptions {
|
|
|
112
115
|
lite?(): boolean;
|
|
113
116
|
/** 电脑版聊天框放哪(0.19.0,用 left、top、width):不给就在牌桌四个角里挑一个不压手牌、按钮、名牌的 */
|
|
114
117
|
chatDock?(): RectLike | null;
|
|
118
|
+
/**
|
|
119
|
+
* 电脑版聊天框默认收不收(0.20.1):玩家自己点过收起、展开就以玩家为准(存在 prefsKey 里);没点过时听这个,给了 chatDock 也生效。
|
|
120
|
+
* 不给:没给 chatDock 时四个角都会压到东西才收(dockFits,0.20.0),给了 chatDock 默认展开。德州原来自己往 texas.chat 写 folded
|
|
121
|
+
*/
|
|
122
|
+
chatFolded?(): boolean;
|
|
115
123
|
/**
|
|
116
124
|
* 弹幕在哪个区域飘(0.19.0):不给就是屏幕 8%–58%,上沿压到最上面一排座位的头像、名牌下面;轨道自动避开手牌、按钮、名牌、头像、center
|
|
117
125
|
*/
|
|
@@ -218,15 +226,50 @@ type Tab = 'phrase' | 'sticker' | 'prop' | 'chat';
|
|
|
218
226
|
/**
|
|
219
227
|
* "互动" / "聊天"按钮的尺寸参数(0.18.2):游戏没给 buttonStyle 就不写行内样式(不然会盖掉还没迁过来的游戏自己对 .atf-btn 的 CSS)
|
|
220
228
|
*/
|
|
221
|
-
export function styleButton(b: HTMLElement, st:
|
|
229
|
+
export function styleButton(b: HTMLElement, st: ButtonStyle | undefined): void {
|
|
222
230
|
const k = Math.max(1, st?.scale ?? 1);
|
|
223
|
-
|
|
231
|
+
// 字号、图标给了就用(有个下限:字不小于 12、图标不小于 16,按钮本身保底 44 像素),没给按 scale
|
|
232
|
+
const font = st?.fontSize !== undefined && Number.isFinite(st.fontSize) ? Math.max(12, st.fontSize) : 13 * k;
|
|
233
|
+
const icon = st?.icon !== undefined && Number.isFinite(st.icon) ? Math.max(16, st.icon) : 22 * k;
|
|
234
|
+
b.style.fontSize = st ? `${font}px` : '';
|
|
224
235
|
b.style.flexDirection = st?.row ? 'row' : '';
|
|
225
|
-
b.style.gap = st?.row ? `${
|
|
226
|
-
if (st) b.style.setProperty('--atf-icon', `${
|
|
236
|
+
b.style.gap = st?.row ? `${Math.round(font * 0.45)}px` : '';
|
|
237
|
+
if (st) b.style.setProperty('--atf-icon', `${icon}px`);
|
|
227
238
|
else b.style.removeProperty('--atf-icon');
|
|
228
239
|
}
|
|
229
240
|
|
|
241
|
+
/** "互动" / "聊天"按钮的尺寸参数(TableFunOptions.buttonStyle) */
|
|
242
|
+
export interface ButtonStyle {
|
|
243
|
+
scale?: number;
|
|
244
|
+
row?: boolean;
|
|
245
|
+
/** 字号(像素,0.20.1) */
|
|
246
|
+
fontSize?: number;
|
|
247
|
+
/** 图标边长(像素,0.20.1) */
|
|
248
|
+
icon?: number;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* 头像旁的气泡(含首次提示"点头像扔道具")要避开哪些地方(0.20.1):
|
|
253
|
+
* - 软的(压一点还行):游戏的禁区、桌上出的牌、正在冒的别的气泡、别人的头像;
|
|
254
|
+
* - 硬的(代价 ×3):叠在气泡上面的按钮、聊天框(0.19.1,竖屏自己的气泡被"互动"按钮压住),以及各家的名牌和头像旁的标签(marks,麻将的缺门签)——
|
|
255
|
+
* 名字和筹码数是小字,气泡底色不透明,压一点就看不清是谁、剩多少(0.18.2 当软的,提示还是会盖住对家的名牌,所有者 10-18 报)
|
|
256
|
+
*/
|
|
257
|
+
export function bubbleZones(
|
|
258
|
+
seats: readonly (FunSeat | null)[],
|
|
259
|
+
seat: number,
|
|
260
|
+
z: { avoid: readonly RectLike[]; over: readonly RectLike[] },
|
|
261
|
+
): { avoid: RectLike[]; hard: RectLike[] } {
|
|
262
|
+
const avoid = [...z.avoid];
|
|
263
|
+
const hard = [...z.over];
|
|
264
|
+
seats.forEach((o, i) => {
|
|
265
|
+
if (!o) return;
|
|
266
|
+
if (o.plate) hard.push(o.plate);
|
|
267
|
+
if (o.marks) hard.push(...o.marks);
|
|
268
|
+
if (i !== seat) avoid.push(o.rect);
|
|
269
|
+
});
|
|
270
|
+
return { avoid, hard };
|
|
271
|
+
}
|
|
272
|
+
|
|
230
273
|
/** 挂子元素(Workers 的类型里也有一个 append,签名对不上,所以统一用 appendChild) */
|
|
231
274
|
function add(parent: Node, ...kids: Node[]): void {
|
|
232
275
|
for (const k of kids) parent.appendChild(k);
|
|
@@ -308,6 +351,7 @@ export class TableFun {
|
|
|
308
351
|
button: () => (this.btn ? this.btn.getBoundingClientRect() : null),
|
|
309
352
|
styleButton: (b) => styleButton(b, this.o.buttonStyle?.()),
|
|
310
353
|
dock: () => this.o.chatDock?.() ?? null,
|
|
354
|
+
...(opts.chatFolded ? { folded: () => opts.chatFolded!() } : {}),
|
|
311
355
|
chatButton: () => this.o.chatButton?.() ?? null,
|
|
312
356
|
area: () => this.o.danmakuArea?.() ?? null,
|
|
313
357
|
lite: () => this.o.lite?.() ?? ((navigator as { deviceMemory?: number }).deviceMemory ?? 8) <= 2,
|
|
@@ -893,14 +937,9 @@ export class TableFun {
|
|
|
893
937
|
this.layer.el.appendChild(b);
|
|
894
938
|
const w = Math.min(maxW, b.offsetWidth || text.length * fs + 24);
|
|
895
939
|
const h = b.offsetHeight || fs * 1.6;
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
const hard: RectLike[] = [...this.cui.rects(), ...(this.btn ? [this.btn.getBoundingClientRect()] : [])];
|
|
900
|
-
this.o.seats().forEach((o, i) => {
|
|
901
|
-
if (!o) return;
|
|
902
|
-
if (o.plate) avoid.push(o.plate);
|
|
903
|
-
if (i !== seat) avoid.push(o.rect);
|
|
940
|
+
const { avoid, hard } = bubbleZones(this.o.seats(), seat, {
|
|
941
|
+
avoid: [...(this.o.avoid?.() ?? []), ...(this.o.center?.() ?? []), ...[...this.live].map((e) => e.getBoundingClientRect())],
|
|
942
|
+
over: [...this.cui.rects(), ...(this.btn ? [this.btn.getBoundingClientRect()] : [])],
|
|
904
943
|
});
|
|
905
944
|
const at = placeBubble(s.rect, w, h, { width: window.innerWidth, height: window.innerHeight }, avoid, hard);
|
|
906
945
|
b.style.transform = `translate(${at.left}px,${at.top}px)`;
|
package/src/worker.ts
CHANGED
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { identify, sameOrigin } from './auth.js';
|
|
14
14
|
import { type GuestOptions, isStandalone, type ModeEnv } from './guest.js';
|
|
15
|
-
import { CLOSE, type CreateError, ROOM_CODE_RE } from './protocol.js';
|
|
16
|
-
import { identityHeader, type Identity, KIT_HOST, LIMITED_HEADER, meterStub, MISS_HEADER, missStub, newRoomCode, roomStub } from './internal.js';
|
|
15
|
+
import { CLOSE, type CreateError, type MatchDownMsg, ROOM_CODE_RE } from './protocol.js';
|
|
16
|
+
import { identityHeader, type Identity, KIT_HOST, LIMITED_HEADER, matchStub, meterStub, MISS_HEADER, missStub, newRoomCode, roomStub, TIER_RE } from './internal.js';
|
|
17
17
|
// 只引类型:meter.ts 依赖 cloudflare:workers,游戏会在普通 Node 的测试里加载本文件
|
|
18
18
|
import type { MeterEnv, RoomMeter } from './meter.js';
|
|
19
19
|
import type { CreateOutcome } from './room.js';
|
|
@@ -144,6 +144,48 @@ export async function acceptJoin(request: Request, env: KitEnv, code: string, op
|
|
|
144
144
|
return new Response(null, { status: 101, webSocket: res.webSocket });
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
export interface MatchOptions extends GateOptions {
|
|
148
|
+
/** 开放快速匹配的档位(只开有单机能接住的档位);不在里面的回 4004 */
|
|
149
|
+
tiers: readonly string[];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* 快速匹配(0.20.0,docs/match.md):WebSocket 升级,连上就是在 tier 这个档位排队(游戏 Worker 绑 MATCH,类继承 match.ts 的 MatchQueue)。
|
|
154
|
+
* 认人、查 Origin 和 acceptJoin 一样;入队前先问计量一次(只读):已经有开着的房间回 { t: 'has_room', code },
|
|
155
|
+
* 今天的开房次数用完了、全站忙、计量连不上回 { t: 'solo', why },都不进队。没绑计量(单独部署)不问
|
|
156
|
+
*/
|
|
157
|
+
export async function acceptMatch(request: Request, env: KitEnv & { MATCH: DurableObjectNamespace }, tier: string, opts: MatchOptions): Promise<Response> {
|
|
158
|
+
if (request.headers.get('upgrade')?.toLowerCase() !== 'websocket') return new Response('websocket only', { status: 426 });
|
|
159
|
+
if (!TIER_RE.test(tier) || !opts.tiers.includes(tier)) return refuse(CLOSE.noRoom, 'no such tier');
|
|
160
|
+
if (!sameOrigin(request, opts.siteOrigin)) return refuse(CLOSE.forbidden, 'bad origin');
|
|
161
|
+
const w = await who(request, env, opts, false);
|
|
162
|
+
if ('error' in w) return w.error === 'anonymous' ? refuse(CLOSE.unauthorized, 'login first') : refuse(CLOSE.tryLater, 'try later');
|
|
163
|
+
const me = w.user;
|
|
164
|
+
if (env.METER) {
|
|
165
|
+
try {
|
|
166
|
+
const p = await (meterStub(env.METER) as DurableObjectStub<RoomMeter>).kitPeek(me.userId);
|
|
167
|
+
if (p.room !== null) return answer({ t: 'has_room', code: p.room });
|
|
168
|
+
if (p.daily) return answer({ t: 'solo', why: 'daily_limit' });
|
|
169
|
+
if (p.level !== 'ok') return answer({ t: 'solo', why: 'busy' });
|
|
170
|
+
} catch (e) {
|
|
171
|
+
console.error('meter unreachable, match goes solo', e instanceof Error ? e.message : e);
|
|
172
|
+
return answer({ t: 'solo', why: 'unavailable' });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return matchStub(env.MATCH, tier).fetch(`https://${KIT_HOST}/match?tier=${tier}`, {
|
|
176
|
+
headers: { upgrade: 'websocket', 'x-kit-user': identityHeader(me) },
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** 接受升级、发一条排队的结果、正常关掉(页面按消息走,不看关闭码) */
|
|
181
|
+
function answer(msg: MatchDownMsg): Response {
|
|
182
|
+
const pair = new WebSocketPair();
|
|
183
|
+
pair[1].accept();
|
|
184
|
+
pair[1].send(JSON.stringify(msg));
|
|
185
|
+
pair[1].close(1000, msg.t);
|
|
186
|
+
return new Response(null, { status: 101, webSocket: pair[0] });
|
|
187
|
+
}
|
|
188
|
+
|
|
147
189
|
/**
|
|
148
190
|
* 按人、按 IP 数"码不存在"(0.14.0,第三轮审计:6 位房间码能一个个试)。没绑 METER(单独部署、测试里没配)就不管,和开房额度一样。
|
|
149
191
|
* 没有 IP 头(本地开发)只按人算
|