Merge pull request #1102 from nofficalfs/Dev-Feat-HookTension

对`lib.hooks`的调整,以及准备增加`lib.assembly`
This commit is contained in:
Spmario233 2024-03-17 22:25:18 +08:00 committed by GitHub
commit 32920c5f09
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 507 additions and 256 deletions

View File

@ -1,3 +1,5 @@
import { NonameAnnounceType } from "./interface.d.ts"
export interface IAnnounceSubscriber { export interface IAnnounceSubscriber {
subscribe(name: string): void; subscribe(name: string): void;
unsubscribe(name: string): void; unsubscribe(name: string): void;
@ -21,7 +23,7 @@ export class Announce {
* @param name - * @param name -
* @param values - * @param values -
*/ */
publish<T>(name: string, values: T): T publish<Type extends NonameAnnounceType, Name extends keyof Type>(name: Name, values: Parameters<Type[Name]>[0]): Parameters<Type[Name]>[0]
/** /**
* *
@ -33,7 +35,7 @@ export class Announce {
* @param name - * @param name -
* @param method - * @param method -
*/ */
subscribe<T>(name: string, method: (values: T) => void): (values: T) => void subscribe<Type extends NonameAnnounceType, Name extends keyof Type>(name: Name, method: Type[Name]): Type[Name]
/** /**
* *
@ -43,7 +45,7 @@ export class Announce {
* @param name - * @param name -
* @param method - * @param method -
*/ */
unsubscribe<T>(name: string, method: (values: T) => void): (values: T) => void unsubscribe<Type extends NonameAnnounceType, Name extends keyof Type>(name: Name, method: Type[Name]): Type[Name]
} }
export class AnnounceSubscriber<T> implements IAnnounceSubscriber { export class AnnounceSubscriber<T> implements IAnnounceSubscriber {

54
noname/library/announce/interface.d.ts vendored Normal file
View File

@ -0,0 +1,54 @@
export interface NonameAnnounceType {
// Apperaence 外观区域
// 用于关于无名杀外观方面的通知
// Apperaence.Theme 无名杀主题区域
/**
*
*
* @param values -
*/
"Noname.Apperaence.Theme.onChanging": AnnounceFunction<string>
/**
*
*
* @param values -
*/
"Noname.Apperaence.Theme.onChanged": AnnounceFunction<string>
/**
*
*
* @param values -
*/
"Noname.Apperaence.Theme.onChangeFinished": AnnounceFunction<string>
// Game 游戏区域
// 包含游戏对局下的通知
// Game.Event 事件区域
/**
*
*
* @param values -
*/
"Noname.Game.Event.GameStart": AnnounceFunction<{}>
// Init 初始化区域
// 用于关于初始化方面的通知
// Init.Extension 扩展初始化区域
/**
*
*
* @param values -
*/
"Noname.Init.Extension.onLoad": AnnounceFunction<string>
}
export type AnnounceFunction<T> = (values: T) => void

View File

@ -0,0 +1,110 @@
import { lib } from "../index.js"
import { ui } from "../../ui/index.js"
import { get } from "../../get/index.js"
import { _status } from "../../status/index.js"
/**
* @type {(import("./interface.js").NonameAssemblyType["checkBegin"])}
*/
export const checkBegin = {}
/**
* @type {(import("./interface.js").NonameAssemblyType["checkCard"])}
*/
export const checkCard = {
updateTempname(card, event) {
if (lib.config.cardtempname === 'off') return
if (get.name(card) === card.name && get.is.sameNature(get.nature(card), card.nature, true)) return
const node = ui.create.cardTempName(card)
if (lib.config.cardtempname !== 'default') node.classList.remove('vertical')
}
}
/**
* @type {(import("./interface.js").NonameAssemblyType["checkTarget"])}
*/
export const checkTarget = {
updateInstance(target, event) {
// @ts-ignore
if (!target.instance) return;
['selected', 'selectable'].forEach(className => {
if (target.classList.contains(className)) {
// @ts-ignore
target.instance.classList.add(className)
} else {
// @ts-ignore
target.instance.classList.remove(className)
}
})
}
}
/**
* @type {(import("./interface.js").NonameAssemblyType["checkButton"])}
*/
export const checkButton = {}
/**
* @type {(import("./interface.js").NonameAssemblyType["checkEnd"])}
*/
export const checkEnd = {
autoConfirm(event, { ok, auto, autoConfirm }) {
if (!event.isMine()) return
const skillinfo = get.info(event.skill) || {}
if (ok && auto && (autoConfirm || skillinfo.direct) && !_status.touchnocheck
&& !_status.mousedown && (!_status.mousedragging || !_status.mouseleft)) {
if (ui.confirm) ui.confirm.close()
// @ts-ignore
if (event.skillDialog === true) event.skillDialog = false
ui.click.ok()
_status.mousedragging = null
if (skillinfo.preservecancel) ui.create.confirm('c')
}
}
}
/**
* @type {(import("./interface.js").NonameAssemblyType["uncheckBegin"])}
*/
export const uncheckBegin = {}
/**
* @type {(import("./interface.js").NonameAssemblyType["uncheckCard"])}
*/
export const uncheckCard = {
removeTempname(card, event) {
// @ts-ignore
if (!card._tempName) return
// @ts-ignore
card._tempName.delete()
// @ts-ignore
delete card._tempName
}
}
/**
* @type {(import("./interface.js").NonameAssemblyType["uncheckTarget"])}
*/
export const uncheckTarget = {
removeInstance(target, event) {
// @ts-ignore
if (!target.instance) return
// @ts-ignore
target.instance.classList.remove('selected')
// @ts-ignore
target.instance.classList.remove('selectable')
}
}
/**
* @type {(import("./interface.js").NonameAssemblyType["uncheckButton"])}
*/
export const uncheckButton = {}
/**
* @type {(import("./interface.js").NonameAssemblyType["uncheckEnd"])}
*/
export const uncheckEnd = {}

View File

@ -0,0 +1,134 @@
import * as buildin from "./buildin.js"
/**
* > 这玩意因为狂神还得是数组
*
* @template {import("./interface.d.ts").NonameAssemblyType} AssemblyType
* @template {keyof AssemblyType} Name
* @extends {Array<AssemblyType[Name][keyof AssemblyType[Name]]>}
*/
export class NonameAssembly extends Array {
/**
* @type {Name}
*/
#name
/**
* @type {Map<keyof AssemblyType[Name], number>}
*/
#record
/**
*
* @param {Name} name
*/
constructor(name) {
super()
this.#name = name
this.#record = new Map()
if (name in buildin) {
// @ts-ignore
for (const [key, item] of Object.entries(buildin[name])) {
// @ts-ignore
this.add(key, item)
}
}
}
static get [Symbol.species]() {
return Array
}
get name() {
return this.#name
}
/**
*
* @param {keyof AssemblyType[Name]} name
* @param {AssemblyType[Name][keyof AssemblyType[Name]]} content
* @override
*/
// @ts-ignore
add(name, content) {
if (!content) {
// @ts-expect-error A
content = name
// @ts-expect-error A
name = content.name
}
if (typeof content !== "function") throw new Error("you can't add a non-function to assembly.")
if (typeof name !== "string" || name.length === 0) throw new Error("you can't add a anonymous function to assembly.")
if (!this.has(name)) {
this.#record.set(name, this.length)
Array.prototype.push.call(this, content)
}
return this
}
/**
*
* @param {keyof AssemblyType[Name]} name
* @param {AssemblyType[Name][keyof AssemblyType[Name]]} content
* @override
*/
// @ts-ignore
push(name, content) {
return this.add(name, content).length
}
/**
*
* @param {keyof AssemblyType[Name]} name
*/
has(name) {
return this.#record.has(name)
}
/**
*
* @param {keyof AssemblyType[Name]} name
* @returns {AssemblyType[Name][keyof AssemblyType[Name]] | undefined}
*/
get(name) {
if (!this.has(name)) return void 0
// @ts-ignore
return this[this.#record.get(name)]
}
/**
*
* @param {keyof AssemblyType[Name]} name
* @param {AssemblyType[Name][keyof AssemblyType[Name]]} content
*/
update(name, content) {
if (!this.has(name)) return false
try {
// @ts-ignore
this[this.#record.get(name)] = content
} catch (e) {
console.error(e)
return false
}
return true
}
}
export const defaultAssemblys = {
checkBegin: new NonameAssembly("checkBegin"),
checkCard: new NonameAssembly("checkCard"),
checkTarget: new NonameAssembly("checkTarget"),
checkButton: new NonameAssembly("checkButton"),
checkEnd: new NonameAssembly("checkEnd"),
uncheckBegin: new NonameAssembly("uncheckBegin"),
uncheckCard: new NonameAssembly("uncheckCard"),
uncheckTarget: new NonameAssembly("uncheckTarget"),
uncheckButton: new NonameAssembly("uncheckButton"),
uncheckEnd: new NonameAssembly("uncheckEnd")
}

58
noname/library/assembly/interface.d.ts vendored Normal file
View File

@ -0,0 +1,58 @@
import { Button, Card, GameEvent, GameEventPromise, Player } from "../element"
export interface NonameAssemblyType {
checkBegin: {}
checkCard: {
/**
*
* @param card -
* @param event -
*/
updateTempname(card: Card, event: GameEvent & GameEventPromise): void
}
checkTarget: {
/**
*
* @param target -
* @param event -
*/
updateInstance(target: Player, event: GameEvent & GameEventPromise): void
}
checkButton: {}
checkEnd: {
/**
*
* @param event -
* @param config -
*/
autoConfirm(event: GameEvent & GameEventPromise, config: { ok: boolean, auto: boolean, autoConfirm: boolean }): void
}
uncheckBegin: {}
uncheckCard: {
/**
*
* @param card -
* @param event -
*/
removeTempname(card: Card, event: GameEvent & GameEventPromise): void
}
uncheckTarget: {
/**
*
* @param target -
* @param event -
*/
removeInstance(target: Player, event: GameEvent & GameEventPromise): void
}
uncheckButton: {}
uncheckEnd: {}
}

View File

@ -750,6 +750,7 @@ export class GameEvent {
if ((this.name === 'gain' || this.name === 'lose') && !_status.gameDrawed) return; if ((this.name === 'gain' || this.name === 'lose') && !_status.gameDrawed) return;
if (name === 'gameDrawEnd') _status.gameDrawed = true; if (name === 'gameDrawEnd') _status.gameDrawed = true;
if (name === 'gameStart') { if (name === 'gameStart') {
lib.announce.publish("Noname.Game.Event.GameStart", {});
lib.announce.publish('gameStart', {}); lib.announce.publish('gameStart', {});
if (_status.brawl && _status.brawl.gameStart) _status.brawl.gameStart(); if (_status.brawl && _status.brawl.gameStart) _status.brawl.gameStart();
if (lib.config.show_cardpile) ui.cardPileButton.style.display = ''; if (lib.config.show_cardpile) ui.cardPileButton.style.display = '';

View File

@ -1,8 +1,5 @@
import { lib } from "../index.js" import { lib } from "../index.js"
import { game } from "../../game/index.js" import { game } from "../../game/index.js"
import { ui } from "../../ui/index.js"
import { get } from "../../get/index.js"
import { _status } from "../../status/index.js"
/** /**
* @type {(import("./interface.js").NonameHookType["addGroup"])[]} * @type {(import("./interface.js").NonameHookType["addGroup"])[]}
@ -213,112 +210,3 @@ export const addNature = [
} }
} }
] ]
/**
* @type {(import("./interface.js").NonameHookType["checkBegin"])[]}
*/
export const checkBegin = []
/**
* @type {(import("./interface.js").NonameHookType["checkCard"])[]}
*/
export const checkCard = [
function updateTempname(card, event) {
if (lib.config.cardtempname === 'off') return
if (get.name(card) === card.name && get.is.sameNature(get.nature(card), card.nature, true)) return
const node = ui.create.cardTempName(card)
if (lib.config.cardtempname !== 'default') node.classList.remove('vertical')
}
]
/**
* @type {(import("./interface.js").NonameHookType["checkTarget"])[]}
*/
export const checkTarget = [
function updateInstance(target, event) {
// @ts-ignore
if (!target.instance) return;
['selected', 'selectable'].forEach(className => {
if (target.classList.contains(className)) {
// @ts-ignore
target.instance.classList.add(className)
} else {
// @ts-ignore
target.instance.classList.remove(className)
}
})
},
]
/**
* @type {(import("./interface.js").NonameHookType["checkButton"])[]}
*/
export const checkButton = []
/**
* @type {(import("./interface.js").NonameHookType["checkEnd"])[]}
*/
export const checkEnd = [
function autoConfirm(event, { ok, auto, autoConfirm }) {
if (!event.isMine()) return
const skillinfo = get.info(event.skill) || {}
if (ok && auto && (autoConfirm || skillinfo.direct) && !_status.touchnocheck
&& !_status.mousedown && (!_status.mousedragging || !_status.mouseleft)) {
if (ui.confirm) ui.confirm.close()
// @ts-ignore
if (event.skillDialog === true) event.skillDialog = false
ui.click.ok()
_status.mousedragging = null
if (skillinfo.preservecancel) ui.create.confirm('c')
}
}
]
/**
* @type {(import("./interface.js").NonameHookType["uncheckBegin"])[]}
*/
export const uncheckBegin = []
/**
* @type {(import("./interface.js").NonameHookType["uncheckCard"])[]}
*/
export const uncheckCard = [
function removeTempname(card, event) {
// @ts-ignore
if (!card._tempName) return
// @ts-ignore
card._tempName.delete()
// @ts-ignore
delete card._tempName
},
]
/**
* @type {(import("./interface.js").NonameHookType["uncheckTarget"])[]}
*/
export const uncheckTarget = [
function removeInstance(target, event) {
// @ts-ignore
if (!target.instance) return
// @ts-ignore
target.instance.classList.remove('selected')
// @ts-ignore
target.instance.classList.remove('selectable')
},
]
/**
* @type {(import("./interface.js").NonameHookType["uncheckButton"])[]}
*/
export const uncheckButton = []
/**
* @type {(import("./interface.js").NonameHookType["uncheckEnd"])[]}
*/
export const uncheckEnd = []

View File

@ -3,49 +3,35 @@ import * as buildin from "./buildin.js"
/** /**
* @template {import("./interface.js").NonameHookType} HookType * @template {import("./interface.js").NonameHookType} HookType
* @template {keyof HookType} Name * @template {keyof HookType} Name
* @extends {Array<HookType[Name]>}
*/ */
export class NonameHook { export class NonameHook extends Array {
/** /**
* @type {Name} * @type {Name}
*/ */
#name #name
/**
* @type {HookType[Name][]}
*/
#methodList
/** /**
* *
* @param {Name} name * @param {Name} name
*/ */
constructor(name) { constructor(name) {
super()
this.#name = name this.#name = name
this.#methodList = (name in buildin) ? [...buildin[name]] : []
if (name in buildin) {
// @ts-ignore
for (const item of buildin[name]) {
this.push(item)
}
}
}
static get [Symbol.species]() {
return Array
} }
get name() { get name() {
return this.#name return this.#name
} }
/**
*
* @param {HookType[Name]} method
*/
add(method) {
return this.#methodList.add(method)
}
/**
*
* @param {HookType[Name]} method
*/
push(method) {
return this.#methodList.push(method)
}
*[Symbol.iterator]() {
yield* this.#methodList
}
} }

View File

@ -1,18 +1,13 @@
import { NonameHook } from "./hook.js" import { NonameHook } from "./hook.js"
import { defaultAssemblys } from "../assembly/index.js"
export const defaultHooks = { export const defaultHooks = {
addGroup: new NonameHook("addGroup"), addGroup: new NonameHook("addGroup"),
addNature: new NonameHook("addNature"), addNature: new NonameHook("addNature"),
checkBegin: new NonameHook("checkBegin"), ...defaultAssemblys
checkCard: new NonameHook("checkCard"), }
checkTarget: new NonameHook("checkTarget"),
checkButton: new NonameHook("checkButton"), export {
checkEnd: new NonameHook("checkEnd"), NonameHook
uncheckBegin: new NonameHook("uncheckBegin"),
uncheckCard: new NonameHook("uncheckCard"),
uncheckTarget: new NonameHook("uncheckTarget"),
uncheckButton: new NonameHook("uncheckButton"),
uncheckEnd: new NonameHook("uncheckEnd")
} }

View File

@ -1,92 +1,94 @@
import { Button, Card, GameEvent, GameEventPromise, Player } from "../element" import { Button, Card, GameEvent, GameEventPromise, Player } from "../element"
export interface NonameHookType { export interface NonameHookType {
/** /**
* *
* @param id - id * @param id - id
* @param short - * @param short -
* @param name - * @param name -
* @param config - * @param config -
*/ */
addGroup(id: string, short: string, name: string, config: Record<string, unknown>) addGroup(id: string, short: string, name: string, config: Record<string, unknown>): any
/** /**
* *
* @param nature - id * @param nature - id
* @param translation - * @param translation -
* @param config - * @param config -
*/ */
addNature(nature: string, translation: string, config: Record<string, unknown>) addNature(nature: string, translation: string, config: Record<string, unknown>): any
/** // #region Assembly-Compatition
* /**
* @param event - *
*/ * @param event -
checkBegin(event: GameEvent & GameEventPromise) */
checkBegin(event: GameEvent & GameEventPromise): any
/** /**
* *
* @param event - * @param event -
* @param config - * @param config -
*/ */
checkEnd(event: GameEvent & GameEventPromise, config: { ok: boolean, auto: boolean, autoConfirm: boolean }) checkEnd(event: GameEvent & GameEventPromise, config: { ok: boolean, auto: boolean, autoConfirm: boolean }): any
/** /**
* *
* @param button - Button * @param button - Button
* @param event - * @param event -
*/ */
checkButton(button: Button, event: GameEvent & GameEventPromise) checkButton(button: Button, event: GameEvent & GameEventPromise): any
/** /**
* *
* @param card - * @param card -
* @param event - * @param event -
*/ */
checkCard(card: Card, event: GameEvent & GameEventPromise) checkCard(card: Card, event: GameEvent & GameEventPromise): any
/** /**
* *
* @param target - * @param target -
* @param event - * @param event -
*/ */
checkTarget(target: Player, event: GameEvent & GameEventPromise) checkTarget(target: Player, event: GameEvent & GameEventPromise): any
/** /**
* *
* @param event - * @param event -
* @param args - * @param args -
*/ */
uncheckBegin(event: GameEvent & GameEventPromise, args: ("button" | "card" | "target")[] = ["button", "card", "target"]) uncheckBegin(event: GameEvent & GameEventPromise, args: ("button" | "card" | "target")[] = ["button", "card", "target"]): any
/** /**
* *
* @param event - * @param event -
* @param args - * @param args -
*/ */
uncheckEnd(event: GameEvent & GameEventPromise, args: ("button" | "card" | "target")[] = ["button", "card", "target"]) uncheckEnd(event: GameEvent & GameEventPromise, args: ("button" | "card" | "target")[] = ["button", "card", "target"]): any
/** /**
* *
* @param button - Button * @param button - Button
* @param event - * @param event -
*/ */
uncheckButton(button: Button, event: GameEvent & GameEventPromise) uncheckButton(button: Button, event: GameEvent & GameEventPromise): any
/** /**
* *
* @param card - * @param card -
* @param event - * @param event -
*/ */
uncheckCard(card: Card, event: GameEvent & GameEventPromise) uncheckCard(card: Card, event: GameEvent & GameEventPromise): any
/** /**
* *
* @param target - * @param target -
* @param event - * @param event -
*/ */
uncheckTarget(target: Player, event: GameEvent & GameEventPromise) uncheckTarget(target: Player, event: GameEvent & GameEventPromise): any
// #endregion
} }

View File

@ -24,6 +24,7 @@ import { Experimental } from "./experimental/index.js";
import * as Element from "./element/index.js"; import * as Element from "./element/index.js";
import { updateURLs } from "./update-urls.js"; import { updateURLs } from "./update-urls.js";
import { defaultHooks } from "./hooks/index.js" import { defaultHooks } from "./hooks/index.js"
import { freezeButExtensible } from "../util/index.js"
export class Library extends Uninstantable { export class Library extends Uninstantable {
@ -153,7 +154,7 @@ export class Library extends Uninstantable {
* 这样当某个地方调用game.callHook(钩子名,[...函数参数])就会按顺序将对应数组中的每个函数运行一遍传参为callHook的第二个参数 * 这样当某个地方调用game.callHook(钩子名,[...函数参数])就会按顺序将对应数组中的每个函数运行一遍传参为callHook的第二个参数
* 你可以将hook机制类比为event.trigger()但是这里只能放同步代码 * 你可以将hook机制类比为event.trigger()但是这里只能放同步代码
*/ */
static hooks = { ...defaultHooks }; static hooks = freezeButExtensible({ ...defaultHooks });
/** /**
* **无名杀频道推送机制** * **无名杀频道推送机制**

View File

@ -25,3 +25,23 @@ export class Uninstantable {
export function delay(ms) { export function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms)); return new Promise((resolve) => setTimeout(resolve, ms));
} }
/**
* 将当前Record已有的普通项封装起来但不阻止其继续扩展
*
* @template {object} T
* @param {T} record - 要封装的Record
* @returns {Readonly<T>}
*/
export function freezeButExtensible(record) {
const descriptors = Object.getOwnPropertyDescriptors(record)
if (descriptors) {
for (const [key, descriptor] of Object.entries(descriptors)) {
if ("value" in descriptor) descriptor.writable = false
descriptor.configurable = false
Reflect.defineProperty(record, key, descriptor)
}
}
return record
}