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 {
subscribe(name: string): void;
unsubscribe(name: string): void;
@ -21,7 +23,7 @@ export class Announce {
* @param name -
* @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 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 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 {

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 (name === 'gameDrawEnd') _status.gameDrawed = true;
if (name === 'gameStart') {
lib.announce.publish("Noname.Game.Event.GameStart", {});
lib.announce.publish('gameStart', {});
if (_status.brawl && _status.brawl.gameStart) _status.brawl.gameStart();
if (lib.config.show_cardpile) ui.cardPileButton.style.display = '';

View File

@ -1,8 +1,5 @@
import { lib } from "../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"])[]}
@ -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 {keyof HookType} Name
* @extends {Array<HookType[Name]>}
*/
export class NonameHook {
export class NonameHook extends Array {
/**
* @type {Name}
*/
#name
/**
* @type {HookType[Name][]}
*/
#methodList
/**
*
* @param {Name} name
*/
constructor(name) {
super()
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() {
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 { defaultAssemblys } from "../assembly/index.js"
export const defaultHooks = {
addGroup: new NonameHook("addGroup"),
addNature: new NonameHook("addNature"),
checkBegin: new NonameHook("checkBegin"),
checkCard: new NonameHook("checkCard"),
checkTarget: new NonameHook("checkTarget"),
checkButton: new NonameHook("checkButton"),
checkEnd: new NonameHook("checkEnd"),
uncheckBegin: new NonameHook("uncheckBegin"),
uncheckCard: new NonameHook("uncheckCard"),
uncheckTarget: new NonameHook("uncheckTarget"),
uncheckButton: new NonameHook("uncheckButton"),
uncheckEnd: new NonameHook("uncheckEnd")
...defaultAssemblys
}
export {
NonameHook
}

View File

@ -1,92 +1,94 @@
import { Button, Card, GameEvent, GameEventPromise, Player } from "../element"
export interface NonameHookType {
/**
*
* @param id - id
* @param short -
* @param name -
* @param config -
*/
addGroup(id: string, short: string, name: string, config: Record<string, unknown>)
/**
*
* @param id - id
* @param short -
* @param name -
* @param config -
*/
addGroup(id: string, short: string, name: string, config: Record<string, unknown>): any
/**
*
* @param nature - id
* @param translation -
* @param config -
*/
addNature(nature: string, translation: string, config: Record<string, unknown>)
/**
*
* @param nature - id
* @param translation -
* @param config -
*/
addNature(nature: string, translation: string, config: Record<string, unknown>): any
/**
*
* @param event -
*/
checkBegin(event: GameEvent & GameEventPromise)
// #region Assembly-Compatition
/**
*
* @param event -
*/
checkBegin(event: GameEvent & GameEventPromise): any
/**
*
* @param event -
* @param config -
*/
checkEnd(event: GameEvent & GameEventPromise, config: { ok: boolean, auto: boolean, autoConfirm: boolean })
/**
*
* @param event -
* @param config -
*/
checkEnd(event: GameEvent & GameEventPromise, config: { ok: boolean, auto: boolean, autoConfirm: boolean }): any
/**
*
* @param button - Button
* @param event -
*/
checkButton(button: Button, event: GameEvent & GameEventPromise)
/**
*
* @param button - Button
* @param event -
*/
checkButton(button: Button, event: GameEvent & GameEventPromise): any
/**
*
* @param card -
* @param event -
*/
checkCard(card: Card, event: GameEvent & GameEventPromise)
/**
*
* @param card -
* @param event -
*/
checkCard(card: Card, event: GameEvent & GameEventPromise): any
/**
*
* @param target -
* @param event -
*/
checkTarget(target: Player, event: GameEvent & GameEventPromise)
/**
*
* @param target -
* @param event -
*/
checkTarget(target: Player, event: GameEvent & GameEventPromise): any
/**
*
* @param event -
* @param args -
*/
uncheckBegin(event: GameEvent & GameEventPromise, args: ("button" | "card" | "target")[] = ["button", "card", "target"])
/**
*
* @param event -
* @param args -
*/
uncheckBegin(event: GameEvent & GameEventPromise, args: ("button" | "card" | "target")[] = ["button", "card", "target"]): any
/**
*
* @param event -
* @param args -
*/
uncheckEnd(event: GameEvent & GameEventPromise, args: ("button" | "card" | "target")[] = ["button", "card", "target"])
/**
*
* @param event -
* @param args -
*/
uncheckEnd(event: GameEvent & GameEventPromise, args: ("button" | "card" | "target")[] = ["button", "card", "target"]): any
/**
*
* @param button - Button
* @param event -
*/
uncheckButton(button: Button, event: GameEvent & GameEventPromise)
/**
*
* @param button - Button
* @param event -
*/
uncheckButton(button: Button, event: GameEvent & GameEventPromise): any
/**
*
* @param card -
* @param event -
*/
uncheckCard(card: Card, event: GameEvent & GameEventPromise)
/**
*
* @param card -
* @param event -
*/
uncheckCard(card: Card, event: GameEvent & GameEventPromise): any
/**
*
* @param target -
* @param event -
*/
uncheckTarget(target: Player, event: GameEvent & GameEventPromise)
/**
*
* @param target -
* @param event -
*/
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 { updateURLs } from "./update-urls.js";
import { defaultHooks } from "./hooks/index.js"
import { freezeButExtensible } from "../util/index.js"
export class Library extends Uninstantable {
@ -153,7 +154,7 @@ export class Library extends Uninstantable {
* 这样当某个地方调用game.callHook(钩子名,[...函数参数])就会按顺序将对应数组中的每个函数运行一遍传参为callHook的第二个参数
* 你可以将hook机制类比为event.trigger()但是这里只能放同步代码
*/
static hooks = { ...defaultHooks };
static hooks = freezeButExtensible({ ...defaultHooks });
/**
* **无名杀频道推送机制**

View File

@ -1,27 +1,47 @@
/** @type { string } */
// @ts-ignore
export const nonameInitialized = localStorage.getItem('noname_inited');
export const assetURL = location.protocol.startsWith('http') || typeof nonameInitialized != 'string' || nonameInitialized == 'nodejs' ? '' : nonameInitialized;
export const GeneratorFunction = (function* () {}).constructor;
export const AsyncFunction = (async function () {}).constructor;
export const userAgent = navigator.userAgent.toLowerCase();
export { Mutex } from './mutex.js';
export const characterDefaultPicturePath = "image/character/default_silhouette_";
/**
* 不能被new的类
*/
export class Uninstantable {
constructor() {
throw new TypeError(`${new.target.name} is not a constructor`);
}
}
/**
* 暂停x毫秒
* @param { number } ms
* @returns { Promise<void> }
*/
export function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/** @type { string } */
// @ts-ignore
export const nonameInitialized = localStorage.getItem('noname_inited');
export const assetURL = location.protocol.startsWith('http') || typeof nonameInitialized != 'string' || nonameInitialized == 'nodejs' ? '' : nonameInitialized;
export const GeneratorFunction = (function* () {}).constructor;
export const AsyncFunction = (async function () {}).constructor;
export const userAgent = navigator.userAgent.toLowerCase();
export { Mutex } from './mutex.js';
export const characterDefaultPicturePath = "image/character/default_silhouette_";
/**
* 不能被new的类
*/
export class Uninstantable {
constructor() {
throw new TypeError(`${new.target.name} is not a constructor`);
}
}
/**
* 暂停x毫秒
* @param { number } ms
* @returns { Promise<void> }
*/
export function delay(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
}