From ba438ef817f5d517b8d5804a1af9916222a064fe Mon Sep 17 00:00:00 2001 From: shijian <2954700422@qq.com> Date: Thu, 28 Dec 2023 00:43:55 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=96=B0=E6=8B=86=E5=88=86lib.element?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- noname/get/index.js | 11 +- noname/library/element/button.js | 42 + noname/library/element/card.js | 787 +++++++ noname/library/element/client.js | 75 + noname/library/element/content.js | 2 +- noname/library/element/contents.js | 2 +- noname/library/element/control.js | 132 ++ noname/library/element/dialog.js | 178 ++ noname/library/element/gameEvent.js | 855 ++++++++ noname/library/element/gameEventPromise.js | 161 ++ noname/library/element/index.js | 12 + noname/library/element/nodeWS.js | 25 + noname/library/element/player.js | 6 +- noname/library/element/vcard.js | 114 + noname/library/index.js | 2300 +------------------- noname/util/index.js | 4 +- 16 files changed, 2429 insertions(+), 2277 deletions(-) create mode 100644 noname/library/element/button.js create mode 100644 noname/library/element/card.js create mode 100644 noname/library/element/client.js create mode 100644 noname/library/element/control.js create mode 100644 noname/library/element/dialog.js create mode 100644 noname/library/element/gameEvent.js create mode 100644 noname/library/element/gameEventPromise.js create mode 100644 noname/library/element/index.js create mode 100644 noname/library/element/nodeWS.js create mode 100644 noname/library/element/vcard.js diff --git a/noname/get/index.js b/noname/get/index.js index 97a2943ea..6940135d4 100644 --- a/noname/get/index.js +++ b/noname/get/index.js @@ -2229,10 +2229,13 @@ export class Get extends Uninstantable { } /** * @template T - * @type {{ - * (key: T) => GameEvent[T]; - * () => GameEvent; - * }} + * @overload + * @param {T} key + * @returns {import("../library/index.js").GameEvent[T]} + */ + /** + * @overload + * @returns {import("../library/index.js").GameEvent} */ static event(key) { return key ? _status.event[key] : _status.event } static player() { return _status.event.player } diff --git a/noname/library/element/button.js b/noname/library/element/button.js new file mode 100644 index 000000000..b497eda7f --- /dev/null +++ b/noname/library/element/button.js @@ -0,0 +1,42 @@ +import { AI as ai } from '../../ai/index.js'; +import { Get as get } from '../../get/index.js'; +import { Game as game } from '../../game/index.js'; +import { Library as lib } from "../index.js"; +import { status as _status } from '../../status/index.js'; +import { UI as ui } from '../../ui/index.js'; + +export class Button extends HTMLDivElement { + /** + * @param {{}} item + * @param {keyof typeof ui.create.buttonPresets | ((item: {}, type: Function, position?: HTMLDivElement | DocumentFragment, noClick?: true, button?: typeof Button) => typeof Button)} type + * @param {HTMLDivElement|DocumentFragment} [position] + * @param {true} [noClick] + * @param { typeof Button } [button] + */ + // @ts-ignore + constructor(item, type, position, noClick, button) { + if (ui.create.buttonPresets[type]) button = ui.create.buttonPresets[type](item, type, position, noClick, button); + else if (typeof type == 'function') button = type(item, type, position, noClick, button); + Object.setPrototypeOf(button, Button.prototype); + // @ts-ignore + if (!noClick) button.addEventListener(lib.config.touchscreen ? 'touchend' : 'click', ui.click.button); + else { + // @ts-ignore + button.classList.add('noclick'); + // @ts-ignore + const intro = button.querySelector('.intro'); + if (intro) intro.remove(); + } + // @ts-ignore + return button; + } + exclude() { + if (_status.event.excludeButton == undefined) { + _status.event.excludeButton = []; + } + _status.event.excludeButton.add(this); + } + get updateTransform() { + return lib.element.Card.prototype.updateTransform; + } +} \ No newline at end of file diff --git a/noname/library/element/card.js b/noname/library/element/card.js new file mode 100644 index 000000000..f75be62db --- /dev/null +++ b/noname/library/element/card.js @@ -0,0 +1,787 @@ +import { AI as ai } from '../../ai/index.js'; +import { Get as get } from '../../get/index.js'; +import { Game as game } from '../../game/index.js'; +import { Library as lib } from "../index.js"; +import { status as _status } from '../../status/index.js'; +import { UI as ui } from '../../ui/index.js'; + +export class Card extends HTMLDivElement { + /** + * @param {HTMLDivElement|DocumentFragment} [position] + * @param {'noclick'} [info] + * @param {true} [noclick] + */ + // @ts-ignore + constructor(position, info, noclick) { + /** + * @type {this} + */ + // @ts-ignore + const card = ui.create.div('.card', position); + Object.setPrototypeOf(card, Card.prototype); + card.build(info, noclick); + return card; + } + build(info, noclick) { + let card = this; + card.buildNode(); + card.buildIntro(noclick); + card.buildProperty(); + card.buildEventListener(info); + } + buildEventListener(info) { + let card = this; + if (info != 'noclick') { + card.addEventListener(lib.config.touchscreen ? 'touchend' : 'click', ui.click.card); + if (lib.config.touchscreen) { + card.addEventListener('touchstart', ui.click.cardtouchstart); + card.addEventListener('touchmove', ui.click.cardtouchmove); + } + if (lib.cardSelectObserver) lib.cardSelectObserver.observe(card, { + attributes: true + }); + } + } + buildProperty() { + let card = this; + card.storage = {}; + card.vanishtag = []; + card.gaintag = []; + card._uncheck = []; + } + buildNode() { + this.node = { + image: ui.create.div('.image', this), + info: ui.create.div('.info', this), + name: ui.create.div('.name', this), + name2: ui.create.div('.name2', this), + background: ui.create.div('.background', this), + intro: ui.create.div('.intro', this), + range: ui.create.div('.range', this), + gaintag: ui.create.div('.gaintag', this), + }; + this.node.intro.innerHTML = lib.config.intro; + } + buildIntro(noclick) { + if (!noclick) lib.setIntro(this); + } + //执行销毁一张牌的钩子函数 + selfDestroy(event) { + if (this._selfDestroyed) return; + this._selfDestroyed = true; + this.fix(); + this.delete(); + const info = get.info(this, false); + if (!info) return; + if (info.destroyLog !== false) game.log(this, '被销毁了'); + if (info.onDestroy) info.onDestroy(this, event); + } + //判断一张牌进入某个区域后是否会被销毁 + willBeDestroyed(targetPosition, player, event) { + const destroyed = this.destroyed; + if (typeof destroyed == 'function') { + return destroyed(this, targetPosition, player, event); + } + else if (lib.skill[destroyed]) { + if (player) { + if (player.hasSkill(destroyed)) { + delete this.destroyed; + return false; + } + } + return true; + } + else if (typeof destroyed == 'string') { + return (destroyed == targetPosition); + } + return destroyed; + } + hasNature(nature, player) { + return game.hasNature(this, nature, player); + } + //只针对【杀】起效果 + addNature(nature) { + let natures = []; + if (!this.nature) this.nature = ''; + else { + natures.addArray(get.natureList(this.nature)); + } + natures.addArray(get.natureList(nature)); + this.nature = get.nature(natures); + this.classList.add(nature); + let str = get.translation(this.nature) + '杀'; + this.node.name.innerText = str; + let name = get.name(this, false); + do { + if (name == 'sha') { + let _bg; + for (const n of natures) if (lib.natureBg.has(n)) _bg = n; + if (_bg) { + this.node.image.setBackgroundImage(lib.natureBg.get(_bg)); + break; + } + } + this.node.image.setBackgroundImage('image/card/' + name + '.png'); + } + while (0); + return this.nature; + } + removeNature(nature) { + if (!this.nature) return; + let natures = get.natureList(this.nature); + natures.remove(nature); + if (!natures.length) delete this.nature; + else this.nature = get.nature(natures); + this.classList.remove(nature); + let str = get.translation(this.nature) + '杀'; + this.node.name.innerText = str; + let name = get.name(this, false); + do { + if (name == 'sha') { + let _bg; + for (const n of natures) if (lib.natureBg.has(n)) _bg = n; + if (_bg) { + this.node.image.setBackgroundImage(lib.natureBg.get(_bg)); + break; + } + } + this.node.image.setBackgroundImage('image/card/' + name + '.png'); + } + while (0); + return this.nature; + } + addGaintag(gaintag) { + if (Array.isArray(gaintag)) this.gaintag = gaintag.slice(0); + else this.gaintag.add(gaintag); + var str = ''; + for (var gi = 0; gi < this.gaintag.length; gi++) { + var translate = get.translation(this.gaintag[gi]); + if (translate != 'invisible') { + str += translate; + if (gi < this.gaintag.length - 1) str += ' '; + } + } + this.node.gaintag.innerHTML = str; + } + removeGaintag(tag) { + if (tag === true) { + if (this.gaintag && this.gaintag.length || this.node.gaintag.innerHTML.length) this.addGaintag([]); + } + else if (this.hasGaintag(tag)) { + this.gaintag.remove(tag); + this.addGaintag(this.gaintag); + } + } + hasGaintag(tag) { + return this.gaintag && this.gaintag.contains(tag); + } + /** + * @param {[string, number, string, string] | { + * suit: string; + * number: number; + * name: string; + * nature: string; + * }} card + */ + init(card) { + if (Array.isArray(card)) { + if (card[2] == 'huosha') { + card[2] = 'sha'; + card[3] = 'fire'; + } + else if (card[2] == 'leisha') { + card[2] = 'sha'; + card[3] = 'thunder'; + } + else if (card[2] == 'cisha') { + card[2] = 'sha'; + card[3] = 'stab'; + } + else if (card[2].length > 3) { + let prefix = card[2].slice(0, card[2].lastIndexOf('sha')); + if (lib.nature.has(prefix)) { + if (prefix.length + 3 == card[2].length) { + card[2] = 'sha'; + card[3] = prefix; + } + } + if (card[2].startsWith('sha_')) { + let suffix = card[2].slice(4); + let natureList = suffix.split('_'); + card[2] = 'sha'; + card[3] = get.nature(natureList); + } + } + } + else if (typeof card == 'object') { + card = [card.suit, card.number, card.name, card.nature]; + } + var cardnum = card[1] || ''; + if (parseInt(cardnum) == cardnum) cardnum = parseInt(cardnum); + + if (!lib.card[card[2]]) { + lib.card[card[2]] = {}; + } + var info = lib.card[card[2]]; + if (info.global && !this.classList.contains('button')) { + if (Array.isArray(info.global)) { + while (info.global.length) { + game.addGlobalSkill(info.global.shift()); + } + } + else if (typeof info.global == 'string') { + game.addGlobalSkill(info.global); + } + delete info.global; + } + this.suit = card[0]; + this.number = parseInt(card[1]) || 0; + this.name = card[2]; + + if (info.destroy && (typeof info.destroy != 'boolean' && !lib.skill[info.destroy])) { + this.destroyed = info.destroy; + } + + if (_status.connectMode && !game.online && lib.cardOL && !this.cardid) { + this.cardid = get.id(); + lib.cardOL[this.cardid] = this; + } + if (!_status.connectMode && !_status.video) { + this.cardid = get.id(); + } + + this.$init(card); + + if (this.inits) { + for (var i = 0; i < this.inits.length; i++) { + this.inits[i](this); + } + } + if (typeof info.init == 'function') info.init(); + + return this; + } + /** + * @param {[string, number, string, string]} card + */ + $init(card) { + var info = lib.card[card[2]]; + var cardnum = card[1] || ''; + if (parseInt(cardnum) == cardnum) cardnum = parseInt(cardnum); + if (cardnum > 0 && cardnum < 14) { + cardnum = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'][cardnum - 1]; + } + if (this.name) { + this.classList.remove('epic'); + this.classList.remove('legend'); + this.classList.remove('gold'); + this.classList.remove('unique'); + this.style.background = ''; + var subtype = get.subtype(this, false); + if (subtype) { + this.classList.remove(subtype); + } + } + if (info.epic) { + this.classList.add('epic'); + } + else if (info.legend) { + this.classList.add('legend'); + } + else if (info.gold) { + this.classList.add('gold'); + } + else if (info.unique) { + this.classList.add('unique'); + } + var bg = card[2]; + if (info.cardimage) { + bg = info.cardimage; + } + var img = lib.card[bg].image; + if (img) { + if (img.startsWith('db:')) { + img = img.slice(3); + } + else if (!img.startsWith('ext:')) { + img = null; + } + } + this.classList.remove('fullskin'); + this.classList.remove('fullimage'); + this.classList.remove('fullborder'); + this.dataset.cardName = card[2]; + this.dataset.cardType = info.type || ''; + this.dataset.cardSubype = info.subtype || ''; + this.dataset.cardMultitarget = info.multitarget ? '1' : '0'; + this.node.name.dataset.nature = ''; + this.node.info.classList.remove('red'); + if (!lib.config.hide_card_image && lib.card[bg].fullskin) { + this.classList.add('fullskin'); + if (img) { + if (img.startsWith('ext:')) { + this.node.image.setBackgroundImage(img.replace(/^ext:/, 'extension/')); + } + else { + this.node.image.setBackgroundDB(img); + } + } + else { + if (lib.card[bg].modeimage) { + this.node.image.setBackgroundImage('image/mode/' + lib.card[bg].modeimage + '/card/' + bg + '.png'); + } + else { + do { + let nature = card[3]; + if (bg == 'sha' && typeof nature == 'string') { + let natures = get.natureList(nature), _bg; + for (const n of natures) if (lib.natureBg.has(n)) _bg = n; + if (_bg) { + this.node.image.setBackgroundImage(lib.natureBg.get(_bg)); + break; + } + } + this.node.image.setBackgroundImage('image/card/' + bg + '.png'); + } + while (0); + } + } + } + else if (lib.card[bg].image == 'background') { + if (card[3]) this.node.background.setBackground(bg + '_' + get.natureList(card[3])[0], 'card'); + else this.node.background.setBackground(bg, 'card'); + } + else if (lib.card[bg].fullimage) { + this.classList.add('fullimage'); + if (img) { + if (img.startsWith('ext:')) { + this.setBackgroundImage(img.replace(/^ext:/, 'extension/')); + this.style.backgroundSize = 'cover'; + } + else { + this.setBackgroundDB(img); + } + } + else if (lib.card[bg].image) { + if (lib.card[bg].image.startsWith('character:')) { + this.setBackground(lib.card[bg].image.slice(10), 'character'); + } + else { + this.setBackground(lib.card[bg].image); + } + } + else { + var cardPack = lib.cardPack['mode_' + get.mode()]; + if (Array.isArray(cardPack) && cardPack.contains(bg)) { + this.setBackground('mode/' + get.mode() + '/card/' + bg); + } + else { + this.setBackground('card/' + bg); + } + } + } + else if (lib.card[bg].fullborder) { + this.classList.add('fullborder'); + if (lib.card[bg].fullborder == 'gold') { + this.node.name.dataset.nature = 'metalmm'; + } + else if (lib.card[bg].fullborder == 'silver') { + this.node.name.dataset.nature = 'watermm'; + } + if (!this.node.avatar) { + this.node.avatar = ui.create.div('.cardavatar'); + this.insertBefore(this.node.avatar, this.firstChild); + } + if (!this.node.framebg) { + this.node.framebg = ui.create.div('.cardframebg'); + this.node.framebg.dataset.auto = lib.card[bg].fullborder; + this.insertBefore(this.node.framebg, this.firstChild); + } + if (img) { + if (img.startsWith('ext:')) { + this.node.avatar.setBackgroundImage(img.replace(/^ext:/, 'extension/')); + this.node.avatar.style.backgroundSize = 'cover'; + } + else { + this.node.avatar.setBackgroundDB(img); + } + } + else if (lib.card[bg].image) { + if (lib.card[bg].image.startsWith('character:')) { + this.node.avatar.setBackground(lib.card[bg].image.slice(10), 'character'); + } + else { + this.node.avatar.setBackground(lib.card[bg].image); + } + } + else { + var cardPack = lib.cardPack['mode_' + get.mode()]; + if (Array.isArray(cardPack) && cardPack.contains(bg)) { + this.node.avatar.setBackground('mode/' + get.mode() + '/card/' + bg); + } + else { + this.node.avatar.setBackground('card/' + bg); + } + } + } + else if (lib.card[bg].image == 'card') { + if (card[3]) this.setBackground(bg + '_' + get.natureList(card[3])[0], 'card'); + else this.setBackground(bg, 'card'); + } + else if (typeof lib.card[bg].image == 'string' && !lib.card[bg].fullskin) { + if (img) { + if (img.startsWith('ext:')) { + this.setBackgroundImage(img.replace(/^ext:/, 'extension/')); + this.style.backgroundSize = 'cover'; + } + else { + this.setBackgroundDB(img); + } + } + else { + this.setBackground(lib.card[bg].image); + } + } + else { + this.node.background.innerHTML = lib.translate[bg + '_cbg'] || lib.translate[bg + '_bg'] || get.translation(bg)[0]; + // this.node.background.style.fontFamily=lib.config.card_font; + if (this.node.background.innerHTML.length > 1) this.node.background.classList.add('tight'); + else this.node.background.classList.remove('tight'); + } + if (!lib.card[bg].fullborder && this.node.avatar && this.node.framebg) { + this.node.avatar.remove(); + this.node.framebg.remove(); + delete this.node.avatar; + delete this.node.framebg; + } + if (info.noname && !this.classList.contains('button')) { + this.node.name.style.display = 'none'; + } + if (info.color) { + this.style.color = info.color; + } + if (info.textShadow) { + this.style.textShadow = info.textShadow; + } + if (info.opacity) { + this.node.info.style.opacity = info.opacity; + this.node.name.style.opacity = info.opacity; + } + if (info.modinfo) { + this.node.info.innerHTML = info.modinfo; + } + else { + this.node.info.innerHTML = get.translation(card[0]) + ' ' + cardnum + ''; + } + if (info.addinfo) { + if (!this.node.addinfo) { + this.node.addinfo = ui.create.div('.range', this); + } + this.node.addinfo.innerHTML = info.addinfo; + } + else if (this.node.addinfo) { + this.node.addinfo.remove(); + delete this.node.addinfo; + } + if (card[0] == 'heart' || card[0] == 'diamond') { + this.node.info.classList.add('red'); + } + this.node.image.className = 'image'; + var name = get.translation(card[2]); + if (card[2] == 'sha') { + name = ''; + let nature = card[3]; + if (nature) { + let natures = get.natureList(nature); + natures.sort(lib.sort.nature); + for (let nature of natures) { + name += lib.translate['nature_' + nature] || lib.translate[nature] || ''; + if (nature != 'stab') this.node.image.classList.add(nature); + } + } + name += '杀'; + } + this.node.name.innerHTML = name; + if (name.length >= 5) { + this.node.name.classList.add('long'); + if (name.length >= 7) { + this.node.name.classList.add('longlong'); + } + } + this.node.name2.innerHTML = get.translation(card[0]) + cardnum + ' ' + name; + this.classList.add('card'); + if (card[3]) { + let natures = get.natureList(card[3]); + natures.forEach(n => { if (n) this.classList.add(n); }); + this.nature = natures.filter(n => lib.nature.has(n)).sort(lib.sort.nature).join(lib.natureSeparator); + } + else if (this.nature) { + this.classList.remove(this.nature); + delete this.nature; + } + if (info.subtype) this.classList.add(info.subtype); + this.node.range.innerHTML = ''; + switch (get.subtype(this, false)) { + case 'equip1': + var added = false; + if (lib.card[this.name] && lib.card[this.name].distance) { + var dist = lib.card[this.name].distance; + if (dist.attackFrom) { + added = true; + this.node.range.innerHTML = '范围: ' + (-dist.attackFrom + 1); + } + } + if (!added) { + this.node.range.innerHTML = '范围: 1'; + } + break; + case 'equip3': + if (info.distance && info.distance.globalTo) { + this.node.range.innerHTML = '防御: ' + info.distance.globalTo; + this.node.name2.innerHTML += '+'; + } + break; + case 'equip4': + if (info.distance && info.distance.globalFrom) { + this.node.range.innerHTML = '进攻: ' + (-info.distance.globalFrom); + this.node.name2.innerHTML += '-'; + } + break; + } + var tags = []; + if (Array.isArray(card[4])) { + tags.addArray(card[4]); + } + if (this.cardid) { + if (!_status.cardtag) { + _status.cardtag = {}; + } + for (var i in _status.cardtag) { + if (_status.cardtag[i].contains(this.cardid)) { + tags.add(i); + } + } + if (tags.length) { + var tagstr = ' '; + for (var i = 0; i < tags.length; i++) { + var tag = tags[i]; + if (!_status.cardtag[tag]) { + _status.cardtag[tag] = []; + } + _status.cardtag[tag].add(this.cardid); + tagstr += lib.translate[tag + '_tag']; + //if(i this._knowers.add(p.playerid)); + } + } + } + removeKnower(player) { + if (!this._knowers) { + return; + } + if (typeof player == 'string') { + this._knowers.remove(player); + } else { + let type = get.itemtype(player); + if (type == 'player') { + this._knowers.remove(player.playerid); + } else if (type == 'players') { + player.forEach(p => this._knowers.remove(p.playerid)); + } + } + } + //清除此牌的知情者。 + clearKnowers() { + if (this._knowers) delete this._knowers; + } + //判断玩家对此牌是否知情。 + isKnownBy(player) { + if (['e', 'j'].includes(get.position(this))) return true;//装备区或者判定区的牌,必知情。 + let owner = get.owner(this); + if (owner) { + if (owner == player) return true;//是牌主,必知情。 + if (player.hasSkillTag('viewHandcard', null, owner, true)) return true;//有viewHandcard标签,必知情。 + if (owner.isUnderControl(true, player)) return true;//被操控,必知情。 + } + if (get.is.shownCard(this)) return true;//此牌是明置牌,必知情。 + if (this._knowers) { + return this._knowers.includes('everyone') || this._knowers.includes(player.playerid); + } + return false; + } + getSource(name) { + if (this.name == name) return true; + var info = lib.card[this.name]; + if (info && Array.isArray(info.source)) { + return info.source.contains(name); + } + return false; + } + moveDelete(player) { + this.fixed = true; + if (!this._listeningEnd || this._transitionEnded) { + this.moveTo(player); + var that = this; + setTimeout(function () { + that.delete(); + }, 200); + } + else { + this._onEndMoveDelete = player; + } + } + moveTo(player) { + this.fixed = true; + var dx, dy; + if (this.classList.contains('center')) { + var nx = [50, -52]; + var ny = [50, -52]; + nx = nx[0] * ui.arena.offsetWidth / 100 + nx[1]; + ny = ny[0] * ui.arena.offsetHeight / 100 + ny[1]; + dx = player.getLeft() + player.offsetWidth / 2 - 52 - nx; + dy = player.getTop() + player.offsetHeight / 2 - 52 - ny; + } + else { + this.style.left = this.offsetLeft + 'px'; + this.style.top = this.offsetTop + 'px'; + + dx = player.getLeft() + player.offsetWidth / 2 - 52 - this.offsetLeft; + dy = player.getTop() + player.offsetHeight / 2 - 52 - this.offsetTop; + } + if (get.is.mobileMe(player)) { + dx += get.cardOffset(); + if (ui.arena.classList.contains('oblongcard')) { + dy -= 16; + } + } + + + if (this.style.transform && this.style.transform != 'none' && this.style.transform.indexOf('translate') == -1) { + this.style.transform += ' translate(' + dx + 'px,' + dy + 'px)'; + } + else { + this.style.transform = 'translate(' + dx + 'px,' + dy + 'px)'; + } + return this; + } + copy() { + /** + * @type {Card} + */ + var node = this.cloneNode(true); + node.style.transform = ''; + node.name = this.name; + node.suit = this.suit; + node.number = this.number; + node.nature = this.nature; + node.classList.remove('hidden'); + node.classList.remove('start'); + node.classList.remove('thrown'); + node.classList.remove('selectable'); + node.classList.remove('selected'); + node.classList.remove('removing'); + node.classList.remove('drawinghidden'); + node.classList.remove('glows'); + node.node = { + name: node.querySelector('.name'), + info: node.querySelector('.info'), + intro: node.querySelector('.intro'), + background: node.querySelector('.background'), + image: node.querySelector('.image'), + gaintag: node.querySelector('.gaintag'), + }; + node.node.gaintag.innerHTML = ''; + var clone = true; + var position; + for (var i = 0; i < arguments.length; i++) { + if (typeof arguments[i] == 'string') node.classList.add(arguments[i]); + else if (['div', 'fragment'].includes(get.objtype(arguments[i]))) position = arguments[i]; + else if (typeof arguments[i] == 'boolean') clone = arguments[i]; + } + node.moveTo = lib.element.Card.prototype.moveTo; + node.moveDelete = lib.element.Card.prototype.moveDelete; + if (clone) this.clone = node; + if (position) position.appendChild(node); + return node; + } + uncheck(skill) { + if (skill) this._uncheck.add(skill); + this.classList.add('uncheck'); + } + recheck(skill) { + if (skill) this._uncheck.remove(skill); + else this._uncheck.length = 0; + if (this._uncheck.length == 0) this.classList.remove('uncheck'); + } + discard(bool) { + if (!this._selfDestroyed) { + this.fix(); + ui.discardPile.appendChild(this); + } + this.classList.remove('glow'); + if (bool === false) { + ui.cardPile.insertBefore(this, ui.cardPile.childNodes[Math.floor(Math.random() * ui.cardPile.childNodes.length)]); + } + else { + if (_status.discarded) { + _status.discarded.add(this); + } + } + } + hasTag(tag) { + if (this.cardid && _status.cardtag && _status.cardtag[tag] && _status.cardtag[tag].contains(this.cardid)) { + return true; + } + return false; + } + hasPosition() { + return ['h', 'e', 'j', 's', 'x'].contains(get.position(this)); + } + isInPile() { + return ['c', 'd'].contains(get.position(this)); + } +} \ No newline at end of file diff --git a/noname/library/element/client.js b/noname/library/element/client.js new file mode 100644 index 000000000..6aa0649d4 --- /dev/null +++ b/noname/library/element/client.js @@ -0,0 +1,75 @@ +import { AI as ai } from '../../ai/index.js'; +import { Get as get } from '../../get/index.js'; +import { Game as game } from '../../game/index.js'; +import { Library as lib } from "../index.js"; +import { status as _status } from '../../status/index.js'; +import { UI as ui } from '../../ui/index.js'; +import { GNC as gnc } from '../../gnc/index.js'; + +export class Client { + /** + * @param {import('../index.js').NodeWS | InstanceType} ws + */ + constructor(ws) { + this.ws = ws; + /** + * @type { string } + */ + // @ts-ignore + this.id = ws.wsid || get.id(); + this.closed = false; + } + send() { + if (this.closed) return this; + var args = Array.from(arguments); + if (typeof args[0] == 'function') { + args.unshift('exec'); + } + for (var i = 1; i < args.length; i++) { + args[i] = get.stringifiedResult(args[i]); + } + try { + this.ws.send(JSON.stringify(args)); + } + catch (e) { + this.ws.close(); + } + return this; + } + close() { + lib.node.clients.remove(this); + lib.node.observing.remove(this); + if (ui.removeObserve && !lib.node.observing.length) { + ui.removeObserve.remove(); + delete ui.removeObserve; + } + this.closed = true; + if (_status.waitingForPlayer) { + for (var i = 0; i < game.connectPlayers.length; i++) { + if (game.connectPlayers[i].playerid == this.id) { + game.connectPlayers[i].uninitOL(); + delete game.connectPlayers[i].playerid; + } + } + if (game.onlinezhu == this.id) { + game.onlinezhu = null; + } + game.updateWaiting(); + } + else if (lib.playerOL[this.id]) { + var player = lib.playerOL[this.id]; + player.setNickname(player.nickname + ' - 离线'); + // @ts-ignore + game.broadcast(function (player) { + player.setNickname(player.nickname + ' - 离线'); + }, player); + player.unwait('ai'); + } + + if (window.isNonameServer) { + // @ts-ignore + document.querySelector('#server_count').innerHTML = lib.node.clients.length; + } + return this; + } +} \ No newline at end of file diff --git a/noname/library/element/content.js b/noname/library/element/content.js index d0b258254..5de904b7e 100644 --- a/noname/library/element/content.js +++ b/noname/library/element/content.js @@ -7,7 +7,7 @@ import { UI as ui } from '../../ui/index.js'; import { GNC as gnc } from '../../gnc/index.js'; // 未来再改 -export default { +export const Content = { emptyEvent: async (event) => { event.trigger(event.name); }, diff --git a/noname/library/element/contents.js b/noname/library/element/contents.js index 2a90d1f04..ec9d5f332 100644 --- a/noname/library/element/contents.js +++ b/noname/library/element/contents.js @@ -6,7 +6,7 @@ import { status as _status } from '../../status/index.js'; import { UI as ui } from '../../ui/index.js'; import { GNC as gnc } from '../../gnc/index.js'; -export default { +export const Contents = { phase: [ async (event, _trigger, player) => { // 初始化阶段列表 diff --git a/noname/library/element/control.js b/noname/library/element/control.js new file mode 100644 index 000000000..b3d4252dd --- /dev/null +++ b/noname/library/element/control.js @@ -0,0 +1,132 @@ +import { AI as ai } from '../../ai/index.js'; +import { Get as get } from '../../get/index.js'; +import { Game as game } from '../../game/index.js'; +import { Library as lib } from "../index.js"; +import { status as _status } from '../../status/index.js'; +import { UI as ui } from '../../ui/index.js'; +import { GNC as gnc } from '../../gnc/index.js'; + +export class Control extends HTMLDivElement { + // @ts-ignore + constructor() { + const nc = !ui.control.querySelector('div:not(.removing):not(.stayleft)'); + const controls = Array.isArray(arguments[0]) ? arguments[0] : Array.from(arguments); + /** + * @type {this} + */ + // @ts-ignore + const control = ui.create.div('.control'); + Object.setPrototypeOf(control, Control.prototype); + ui.control.insertBefore(control, _status.createControl || ui.confirm); + controls.forEach(argument => { + if (argument == 'nozoom') return; + if (typeof argument == 'function') control.custom = argument; + else if (argument == 'stayleft') { + control.stayleft = true; + control.classList.add('stayleft'); + } + else control.add(argument); + }); + ui.controls.unshift(control); + if (nc) ui.control.animate('nozoom', 100); + if (control.childNodes.length) { + control.style.transition = 'opacity 0.5s'; + control.animate('controlpressdownx', 500); + ui.refresh(control); + if (!control.stayleft) control.style.transform = `translateX(-${control.offsetWidth / 2}px)`; + control.style.opacity = 1; + ui.refresh(control); + control.style.transition = ''; + } + + control.addEventListener(lib.config.touchscreen ? 'touchend' : 'click', ui.click.control2); + + if (lib.config.button_press) { + control.addEventListener(lib.config.touchscreen ? 'touchstart' : 'mousedown', function () { + if (this.classList.contains('disabled')) return; + this.classList.add('controlpressdown'); + if (typeof this._offset == 'number') this.style.transform = `translateX(${this._offset}px) scale(0.97)`; + }); + control.addEventListener(lib.config.touchscreen ? 'touchend' : 'mouseup', function () { + this.classList.remove('controlpressdown'); + if (typeof this._offset == 'number') this.style.transform = `translateX(${this._offset}px)`; + }); + } + + ui.updatec(); + return control; + } + open() { + ui.control.insertBefore(this, _status.createControl || ui.confirm); + ui.controls.unshift(this); + if (this.childNodes.length) { + this.style.transition = 'opacity 0.5s'; + ui.refresh(this); + this.style.transform = 'translateX(-' + (this.offsetWidth / 2) + 'px)'; + this.style.opacity = 1; + ui.refresh(this); + this.style.transition = ''; + } + else { + this.animate('controlpressdownx', 500); + } + ui.updatec(); + return this; + } + add(item) { + var node = document.createElement('div'); + this.appendChild(node); + node.link = item; + node.innerHTML = get.translation(item); + node.addEventListener(lib.config.touchscreen ? 'touchend' : 'click', ui.click.control); + } + close() { + this.animate('controlpressdownx', 500); + + ui.controls.remove(this); + this.delete(); + + setTimeout(ui.updatec, 100); + + + if (ui.confirm == this) delete ui.confirm; + if (ui.skills == this) delete ui.skills; + if (ui.skills2 == this) delete ui.skills2; + if (ui.skills3 == this) delete ui.skills3; + } + replace() { + // this.animate('controlpressdownx',500); + if (this.replaceTransition === false) { + this.style.transitionProperty = 'none'; + ui.refresh(this); + } + + while (this.childNodes.length) this.firstChild.remove(); + var i, controls; + if (Array.isArray(arguments[0])) controls = arguments[0]; + else controls = arguments; + delete this.custom; + for (i = 0; i < controls.length; i++) { + if (typeof controls[i] == 'function') { + this.custom = controls[i]; + } + else { + this.add(controls[i]); + } + } + if (this.childNodes.length) { + var width = 0; + for (i = 0; i < this.childNodes.length; i++) width += this.childNodes[i].offsetWidth; + ui.refresh(this); + this.style.width = width + 'px'; + } + ui.updatec(); + if (this.replaceTransition === false) { + var that = this; + setTimeout(function () { + that.style.transitionProperty = ''; + }, 200); + } + return this; + } +} \ No newline at end of file diff --git a/noname/library/element/dialog.js b/noname/library/element/dialog.js new file mode 100644 index 000000000..647ec8fb9 --- /dev/null +++ b/noname/library/element/dialog.js @@ -0,0 +1,178 @@ +import { AI as ai } from '../../ai/index.js'; +import { Get as get } from '../../get/index.js'; +import { Game as game } from '../../game/index.js'; +import { Library as lib } from "../index.js"; +import { status as _status } from '../../status/index.js'; +import { UI as ui } from '../../ui/index.js'; + +export class Dialog extends HTMLDivElement { + // @ts-ignore + constructor() { + let hidden = false; + let noTouchScroll = false; + let forceButton = false; + let noForceButton = false; + /** @type {this} */ + // @ts-ignore + const dialog = ui.create.div('.dialog'); + Object.setPrototypeOf(dialog, Dialog.prototype); + dialog.contentContainer = ui.create.div('.content-container', dialog); + dialog.content = ui.create.div('.content', dialog.contentContainer); + dialog.bar1 = ui.create.div('.bar.top', dialog); + dialog.bar2 = ui.create.div('.bar.bottom', dialog); + dialog.buttons = []; + Array.from(arguments).forEach(argument => { + if (typeof argument == 'boolean') dialog.static = argument; + else if (argument == 'hidden') hidden = true; + else if (argument == 'notouchscroll') noTouchScroll = true; + else if (argument == 'forcebutton') forceButton = true; + else if (argument == 'noforcebutton') noForceButton = true; + else dialog.add(argument); + }); + if (!hidden) dialog.open(); + if (!lib.config.touchscreen) dialog.contentContainer.onscroll = ui.update; + if (!noTouchScroll) { + dialog.contentContainer.ontouchstart = ui.click.dialogtouchStart; + dialog.contentContainer.ontouchmove = ui.click.touchScroll; + dialog.contentContainer.style.webkitOverflowScrolling = 'touch'; + dialog.ontouchstart = ui.click.dragtouchdialog; + } + if (noForceButton) dialog.noforcebutton = true; + else if (forceButton) { + dialog.forcebutton = true; + dialog.classList.add('forcebutton'); + } + return dialog; + } + add(item, noclick, zoom) { + if (typeof item == 'string') { + if (item.startsWith('###')) { + var items = item.slice(3).split('###'); + this.add(items[0], noclick, zoom); + this.addText(items[1], items[1].length <= 20, zoom); + } + else if (noclick) { + var strstr = item; + item = ui.create.div('', this.content); + item.innerHTML = strstr; + } + else { + item = ui.create.caption(item, this.content); + } + } + else if (['div', 'fragment'].includes(get.objtype(item))) { + this.content.appendChild(item); + } + else if (get.itemtype(item) == 'cards') { + var buttons = ui.create.div('.buttons', this.content); + if (zoom) buttons.classList.add('smallzoom'); + this.buttons = this.buttons.concat(ui.create.buttons(item, 'card', buttons, noclick)); + } + else if (get.itemtype(item) == 'players') { + var buttons = ui.create.div('.buttons', this.content); + if (zoom) buttons.classList.add('smallzoom'); + this.buttons = this.buttons.concat(ui.create.buttons(item, 'player', buttons, noclick)); + } + else if (item[1] == 'textbutton') { + ui.create.textbuttons(item[0], this, noclick); + } + else { + var buttons = ui.create.div('.buttons', this.content); + if (zoom) buttons.classList.add('smallzoom'); + this.buttons = this.buttons.concat(ui.create.buttons(item[0], item[1], buttons, noclick)); + } + if (this.buttons.length) { + if (this.forcebutton !== false) this.forcebutton = true; + if (this.buttons.length > 3 || (zoom && this.buttons.length > 5)) { + this.classList.remove('forcebutton-auto'); + } + else if (!this.noforcebutton) { + this.classList.add('forcebutton-auto'); + } + } + ui.update(); + return item; + } + addText(str, center) { + if (str && str.startsWith('' + str + ''); + } + else { + this.add('
' + str + '
'); + } + return this; + } + addSmall(item, noclick) { + return this.add(item, noclick, true); + } + addAuto(content) { + if (content && content.length > 4 && !this._hovercustomed) { + this.addSmall(content); + } + else { + this.add(content); + } + } + open() { + if (this.noopen) return; + for (var i = 0; i < ui.dialogs.length; i++) { + if (ui.dialogs[i] == this) { + this.show(); + this.refocus(); + ui.dialogs.remove(this); + ui.dialogs.unshift(this); + ui.update(); + return this; + } + if (ui.dialogs[i].static) ui.dialogs[i].unfocus(); + else ui.dialogs[i].hide(); + } + ui.dialog = this; + var translate; + if (lib.config.remember_dialog && lib.config.dialog_transform && !this.classList.contains('fixed')) { + translate = lib.config.dialog_transform; + this._dragtransform = translate; + this.style.transform = 'translate(' + translate[0] + 'px,' + translate[1] + 'px) scale(0.8)'; + } + else { + this.style.transform = 'scale(0.8)'; + } + this.style.transitionProperty = 'opacity,transform'; + this.style.opacity = 0; + ui.arena.appendChild(this); + ui.dialogs.unshift(this); + ui.update(); + ui.refresh(this); + if (lib.config.remember_dialog && lib.config.dialog_transform && !this.classList.contains('fixed')) { + this.style.transform = 'translate(' + translate[0] + 'px,' + translate[1] + 'px) scale(1)'; + } + else { + this.style.transform = 'scale(1)'; + } + this.style.opacity = 1; + var that = this; + setTimeout(function () { + that.style.transitionProperty = ''; + }, 500); + return this; + } + close() { + ui.dialogs.remove(this); + this.delete(); + if (ui.dialogs.length > 0) { + ui.dialog = ui.dialogs[0]; + ui.dialog.show(); + ui.dialog.refocus(); + ui.update(); + } + // if(ui.arenalog){ + // ui.arenalog.classList.remove('withdialog'); + // } + return this; + } + setCaption(str) { + this.querySelector('.caption').innerHTML = str; + return this; + } +} \ No newline at end of file diff --git a/noname/library/element/gameEvent.js b/noname/library/element/gameEvent.js new file mode 100644 index 000000000..4715597c9 --- /dev/null +++ b/noname/library/element/gameEvent.js @@ -0,0 +1,855 @@ +import { AI as ai } from '../../ai/index.js'; +import { Get as get } from '../../get/index.js'; +import { Game as game } from '../../game/index.js'; +import { Library as lib } from "../index.js"; +import { status as _status } from '../../status/index.js'; +import { UI as ui } from '../../ui/index.js'; +import { AsyncFunction } from '../../util/index.js'; + +export class GameEvent { + /** @type { import('./gameEventPromise.js').default } */ + #promise; + /** + * @param {string} [name] + * @param {false} [trigger] + */ + constructor(name, trigger) { + if (typeof name == 'string') { + this.name = name; + const gameEvent = get.event(); + if (gameEvent) { + const type = `onNext${name[0].toUpperCase()}${name.slice(1)}`; + if (gameEvent.hasHandler(type)) this.pushHandler(...gameEvent.getHandler(type)); + } + game.globalEventHandlers.addHandlerToEvent(this); + } + this.step = 0; + this.finished = false; + /** + * @type {(import('./GameEventPromise.js').default)[]} + */ + this.next = []; + /** + * @type {(import('./GameEventPromise.js').default)[]} + */ + this.after = []; + this.custom = { + add: {}, + replace: {} + }; + this._aiexclude = []; + this._notrigger = []; + this._result = {}; + this._set = []; + /** + * @type {boolean} 这个事件是否使用异步函数处理 + **/ + this.async = false; + /** + * @type {null|(event: GameEvent)=>any} 这个异步事件对应Promise的resolve函数 + **/ + this.resolve = null; + if (trigger !== false && !game.online) this._triggered = 0; + } + static initialGameEvent() { + return new GameEvent().finish().toPromise(); + } + /** + * @param {keyof this} key + * @param {number} [value] + * @param {number} [baseValue] + */ + addNumber(key, value, baseValue) { + if (typeof value != 'number') value = 0; + if (typeof this[key] == 'number') this[key] += value; + else { + if (typeof baseValue != 'number') baseValue = 0; + this[key] = baseValue + value; + } + return this; + } + /** + * @param {keyof this} key + * @param {number} [baseValue] + */ + decrease(key, baseValue) { + if (typeof this[key] == 'number') this[key]--; + else this.subtractNumber(key, 1, baseValue); + return this; + } + /** + * @param {keyof this} key + * @param {number} [baseValue] + */ + increase(key, baseValue) { + if (typeof this[key] == 'number') this[key]++; + else this.addNumber(key, 1, baseValue); + return this; + } + /** + * @param {keyof this} key + * @param {number} [value] + * @param {number} [baseValue] + */ + subtractNumber(key, value, baseValue) { + if (typeof value != 'number') value = 0; + if (typeof this[key] == 'number') this[key] -= value; + else { + if (typeof baseValue != 'number') baseValue = 0; + this[key] = baseValue - value; + } + return this; + } + /** + * @param {Parameters[0]} type + * @param {GameEvent} event + * @param {{ + * state?: 'begin' | 'end'; + * }} option + * @returns {this} + */ + callHandler(type, event, option) { + if (this.hasHandler(type)) this.getHandler(type).forEach(handler => { + if (typeof handler == 'function') handler(event, option); + }); + return this; + } + getDefaultHandlerType() { + const eventName = this.name; + if (eventName) return `on${eventName[0].toUpperCase()}${eventName.slice(1)}`; + } + /** + * @param {Parameters[0]} [type] + * @returns {((event: GameEvent, option: { + * state?: 'begin' | 'end'; + * }) => void)[]} + */ + getHandler(type) { + if (!type) type = this.getDefaultHandlerType(); + const currentHandler = this[type]; + if (!currentHandler) this[type] = []; + else if (!Array.isArray(currentHandler)) this[type] = [currentHandler]; + return this[type]; + } + /** + * @param {`on${Capitalize}`} [type] + */ + hasHandler(type) { + if (!type) type = this.getDefaultHandlerType(); + return Boolean(this[type] && this.getHandler(type).length); + } + /** + * @overload + * @param {...((event: GameEvent, option: { + * state?: 'begin' | 'end'; + * }) => void)[]} handlers + * @returns {number} + */ + /** + * @overload + * @param {Parameters[0]} type + * @param {...((event: GameEvent, option: { + * state?: 'begin' | 'end'; + * }) => void)[]} handlers + * @returns {number} + */ + pushHandler(type) { + return typeof type == 'string' ? this.getHandler(type).push(...Array.from(arguments).slice(1)) : this.getHandler().push(...arguments); + } + changeToZero() { + this.num = 0; + this.numFixed = true; + return this; + } + finish() { + this.finished = true; + return this; + } + putStepCache(key, value) { + if (!this._stepCache) { + this._stepCache = {}; + } + this._stepCache[key] = value; + return this; + } + getStepCache(key) { + if (!this._stepCache) return undefined; + return this._stepCache[key]; + } + clearStepCache(key) { + if (key !== undefined && key !== null) { + delete this._stepCache[key]; + } + delete this._stepCache; + return this; + } + callFuncUseStepCache(prefix, func, params) { + if (typeof func != 'function') return; + if (_status.closeStepCache) return func.apply(null, params); + var cacheKey = "[" + prefix + "]" + get.paramToCacheKey.apply(null, params); + var ret = this.getStepCache(cacheKey); + if (ret === undefined || ret === null) { + ret = func.apply(null, params); + this.putStepCache(cacheKey, ret); + } + return ret; + } + putTempCache(key1, key2, value) { + if (!this._tempCache) { + this._tempCache = {}; + } + if (!this._tempCache[key1]) { + this._tempCache[key1] = {}; + } + this._tempCache[key1][key2] = value; + return value; + } + getTempCache(key1, key2) { + if (!this._tempCache) { + return undefined; + } + if (!this._tempCache[key1]) { + return undefined; + } + return this._tempCache[key1][key2]; + } + cancel(arg1, arg2, notrigger) { + this.untrigger(arg1, arg2); + this.finish(); + if (notrigger != 'notrigger') { + this.trigger(this.name + 'Cancelled'); + if (this.player && lib.phaseName.contains(this.name)) this.player.getHistory('skipped').add(this.name); + } + return this; + } + neutralize(event) { + this.untrigger(); + this.finish(); + this._neutralized = true; + this.trigger('eventNeutralized'); + this._neutralize_event = event || _status.event; + return this; + } + unneutralize() { + this.untrigger(); + delete this._neutralized; + delete this.finished; + if (this.type == 'card' && this.card && this.name == 'sha') this.directHit = true; + return this; + } + goto(step) { + this.step = step - 1; + return this; + } + redo() { + this.step--; + return this; + } + setHiddenSkill(skill) { + if (!this.player) return this; + var hidden = this.player.hiddenSkills.slice(0); + game.expandSkills(hidden); + if (hidden.contains(skill)) this.set('hsskill', skill); + return this; + } + set(key, value) { + if (arguments.length == 1 && Array.isArray(arguments[0])) { + for (var i = 0; i < arguments[0].length; i++) { + if (Array.isArray(arguments[0][i])) { + this.set(arguments[0][i][0], arguments[0][i][1]); + } + } + } + else { + if (typeof key != 'string') { + console.log('warning: using non-string object as event key'); + console.log(key, value); + console.log(_status.event); + } + this[key] = value; + this._set.push([key, value]); + } + return this; + } + /** + * @param {ArrayLike | Function | keyof typeof lib.element.content} item + */ + setContent(item) { + switch (typeof item) { + case "object": + case "function": + if (item instanceof AsyncFunction) { + this.content = item; + } + else this.content = lib.init.parsex(item); + break; + default: + try { + if (!(lib.element.content[item] instanceof AsyncFunction) && !lib.element.content[item]._parsed) { + lib.element.content[item] = lib.init.parsex(lib.element.content[item]); + lib.element.content[item]._parsed = true; + } + } + catch { + throw new Error(`Content ${item} may not exist.\nlib.element.content[${item}] = ${lib.element.content[item]}`); + } + this.content = lib.element.content[item]; + break; + } + return this; + } + + /** + * + * @param {import("../util/index.js").AsyncFunction[] | keyof typeof lib.element.contents} contents + * @returns {GameEvent} + */ + setContents(contents) { + if (Array.isArray(contents)) this.contents = contents; + else if (contents in lib.element.contents) return this.setContents(lib.element.contents[contents]); + else throw new Error('not supported value.'); + return this; + } + + getLogv() { + for (var i = 1; i <= 3; i++) { + var event = this.getParent(i); + if (event && event.logvid) return event.logvid; + } + return null; + } + send() { + this.player.send(function (name, args, set, event, skills) { + game.me.applySkills(skills); + var next = game.me[name].apply(game.me, args); + for (var i = 0; i < set.length; i++) { + next.set(set[i][0], set[i][1]); + } + if (next._backupevent) { + next.backup(next._backupevent); + } + next._modparent = event; + game.resume(); + }, this.name, this._args || [], this._set, + get.stringifiedResult(this.parent), get.skillState(this.player)); + this.player.wait(); + game.pause(); + return this; + } + resume() { + delete this._cardChoice; + delete this._targetChoice; + delete this._skillChoice; + return this; + } + /** + * 获取事件的父节点。 + * 获取事件链上的指定事件。 + * 默认获取上一个父节点(核心)。 + * @param {number|string|(evt:gameEvent)=>boolean} [level=1] 获取深度(number)/指定名字(string)/指定特征(function) + * @param {boolean} [forced] 若获取不到节点,默认返回{},若forced为true则返回null + * @param {boolean} [includeSelf] 若level不是数字,指定搜索时是否包含事件本身 + * @returns {GameEvent|{}|null} + */ + getParent(level = 1, forced, includeSelf) { + let event = this; + const toreturn = forced ? null : {}; + if (!includeSelf || typeof level === 'number') { + if (event._modparent && game.online) event = event._modparent; + else event = this.parent; + } + if (typeof level === 'number') { + for (let i = 1; i < level; i++) { + if (!event) return toreturn; + event = event.parent; + } + return event; + } + const historys = []; + const filter = typeof level === 'function' ? level : evt => evt.name === level; + while (true) { + if (!event) return toreturn; + historys.push(event); + if (filter(event)) return event; + event = event.parent; + if (historys.includes(event)) return toreturn; + } + } + getTrigger() { + return this.getParent('arrangeTrigger')._trigger; + } + getRand(name) { + if (name) { + if (!this._rand_map) this._rand_map = {}; + if (!this._rand_map[name]) this._rand_map[name] = Math.random(); + return this._rand_map[name]; + } + if (!this._rand) this._rand = Math.random(); + return this._rand; + } + insert(content, map) { + const next = (new lib.element.GameEvent(`${this.name}Inserted`, false)).toPromise(); + this.next.push(next); + next.setContent(content); + Object.entries(map).forEach(entry => next.set(entry[0], entry[1])); + return next; + } + insertAfter(content, map) { + const next = (new lib.element.GameEvent(`${this.name}Inserted`, false)).toPromise(); + this.after.push(next); + next.setContent(content); + Object.entries(map).forEach(entry => next.set(entry[0], entry[1])); + return next; + } + backup(skill) { + this._backup = { + filterButton: this.filterButton, + selectButton: this.selectButton, + filterTarget: this.filterTarget, + selectTarget: this.selectTarget, + filterCard: this.filterCard, + selectCard: this.selectCard, + position: this.position, + forced: this.forced, + fakeforce: this.fakeforce, + _aiexclude: this._aiexclude, + complexSelect: this.complexSelect, + complexCard: this.complexCard, + complexTarget: this.complexTarget, + _cardChoice: this._cardChoice, + _targetChoice: this._targetChoice, + _skillChoice: this._skillChoice, + ai1: this.ai1, + ai2: this.ai2, + filterOk: this.filterOk, + }; + if (skill) { + var info = get.info(skill); + this.skill = skill; + this._aiexclude = []; + if (typeof info.viewAs == 'function') { + if (info.filterButton != undefined) this.filterButton = get.filter(info.filterButton); + if (info.selectButton != undefined) this.selectButton = info.selectButton; + if (info.filterTarget != undefined) this.filterTarget = get.filter(info.filterTarget); + if (info.selectTarget != undefined) this.selectTarget = info.selectTarget; + if (info.filterCard != undefined) { + if (info.ignoreMod) this.ignoreMod = true; + this.filterCard2 = get.filter(info.filterCard); + this.filterCard = function (card, player, event) { + var evt = event || _status.event; + if (!evt.ignoreMod && player) { + var mod = game.checkMod(card, player, 'unchanged', 'cardEnabled2', player); + if (mod != 'unchanged') return mod; + } + return get.filter(evt.filterCard2).apply(this, arguments); + }; + } + if (info.filterOk == undefined) { + this.filterOk = function () { + var evt = _status.event; + var card = get.card(), player = get.player(); + var filter = evt._backup.filterCard; + if (filter && !filter(card, player, evt)) return false; + if (evt._backup.filterOk) return evt._backup.filterOk(); + return true; + }; + } + else this.filterOk = info.filterOk; + if (info.selectCard != undefined) this.selectCard = info.selectCard; + if (info.position != undefined) this.position = info.position; + //if(info.forced!=undefined) this.forced=info.forced; + if (info.complexSelect != undefined) this.complexSelect = info.complexSelect; + if (info.complexCard != undefined) this.complexCard = info.complexCard; + if (info.complexTarget != undefined) this.complexTarget = info.complexTarget; + if (info.ai1 != undefined) this.ai1 = info.ai1; + if (info.ai2 != undefined) this.ai2 = info.ai2; + } + else if (info.viewAs) { + if (info.filterButton != undefined) this.filterButton = get.filter(info.filterButton); + if (info.selectButton != undefined) this.selectButton = info.selectButton; + if (info.filterTarget != undefined) this.filterTarget = get.filter(info.filterTarget); + if (info.selectTarget != undefined) this.selectTarget = info.selectTarget; + if (info.filterCard != undefined) { + if (info.ignoreMod) this.ignoreMod = true; + this.filterCard2 = get.filter(info.filterCard); + this.filterCard = function (card, player, event) { + var evt = event || _status.event; + if (!evt.ignoreMod && player) { + var mod = game.checkMod(card, player, 'unchanged', 'cardEnabled2', player); + if (mod != 'unchanged') return mod; + } + return get.filter(evt.filterCard2).apply(this, arguments); + }; + } + if (info.filterOk == undefined) { + this.filterOk = function () { + var evt = _status.event; + var card = get.card(), player = get.player(); + var filter = evt._backup.filterCard; + if (filter && !filter(card, player, evt)) return false; + if (evt._backup.filterOk) return evt._backup.filterOk(); + return true; + }; + } + else this.filterOk = info.filterOk; + if (info.selectCard != undefined) this.selectCard = info.selectCard; + if (info.position != undefined) this.position = info.position; + //if(info.forced!=undefined) this.forced=info.forced; + if (info.complexSelect != undefined) this.complexSelect = info.complexSelect; + if (info.complexCard != undefined) this.complexCard = info.complexCard; + if (info.complexTarget != undefined) this.complexTarget = info.complexTarget; + if (info.ai1 != undefined) this.ai1 = info.ai1; + if (info.ai2 != undefined) this.ai2 = info.ai2; + } + else { + this.filterButton = info.filterButton ? get.filter(info.filterButton) : undefined; + this.selectButton = info.selectButton; + this.filterTarget = info.filterTarget ? get.filter(info.filterTarget) : undefined; + this.selectTarget = info.selectTarget; + this.filterCard = info.filterCard ? get.filter(info.filterCard) : undefined; + this.selectCard = info.selectCard; + this.position = info.position; + //this.forced=info.forced; + this.complexSelect = info.complexSelect; + this.complexCard = info.complexCard; + this.complexTarget = info.complexTarget; + if (info.ai1 != undefined) this.ai1 = info.ai1; + if (info.ai2 != undefined) this.ai2 = info.ai2; + this.filterOk = info.filterOk; + } + delete this.fakeforce; + } + delete this._cardChoice; + delete this._targetChoice; + delete this._skillChoice; + return this; + } + restore() { + if (this._backup) { + this.filterButton = this._backup.filterButton; + this.selectButton = this._backup.selectButton; + this.filterTarget = this._backup.filterTarget; + this.selectTarget = this._backup.selectTarget; + this.filterCard = this._backup.filterCard; + this.selectCard = this._backup.selectCard; + this.position = this._backup.position; + this.forced = this._backup.forced; + this.fakeforce = this._backup.fakeforce; + this._aiexclude = this._backup._aiexclude; + this.complexSelect = this._backup.complexSelect; + this.complexCard = this._backup.complexCard; + this.complexTarget = this._backup.complexTarget; + this.ai1 = this._backup.ai1; + this.ai2 = this._backup.ai2; + this._cardChoice = this._backup._cardChoice; + this._targetChoice = this._backup._targetChoice; + this._skillChoice = this._backup._skillChoice; + this.filterOk = this._backup.filterOk; + } + delete this.skill; + delete this.ignoreMod; + delete this.filterCard2; + return this; + } + isMine() { + return (this.player && this.player == game.me && !_status.auto && !this.player.isMad() && !game.notMe); + } + isOnline() { + return (this.player && this.player.isOnline()); + } + notLink() { + return this.getParent().name != '_lianhuan' && this.getParent().name != '_lianhuan2'; + } + isPhaseUsing(player) { + var evt = this.getParent('phaseUse'); + if (!evt || evt.name != 'phaseUse') return false; + return !player || player == evt.player; + } + addTrigger(skills, player) { + if (!player || !skills) return this; + let evt = this; + if (typeof skills == 'string') skills = [skills]; + game.expandSkills(skills); + while (true) { + evt = evt.getParent('arrangeTrigger'); + if (!evt || evt.name != 'arrangeTrigger' || !evt.doingList) return this; + const doing = evt.doingList.find(i => i.player === player); + const firstDo = evt.doingList.find(i => i.player === "firstDo"); + const lastDo = evt.doingList.find(i => i.player === "lastDo"); + + skills.forEach(skill => { + const info = lib.skill[skill]; + if (!info.trigger) return; + if (!Object.keys(info.trigger).some(i => { + if (Array.isArray(info.trigger[i])) return info.trigger[i].includes(evt.triggername); + return info.trigger[i] === evt.triggername; + })) return; + + const toadd = { + skill: skill, + player: player, + priority: get.priority(skill), + }; + const map = info.firstDo ? firstDo : info.lastDo ? lastDo : doing; + if (!map) return; + if (map.doneList.some(i => i.skill === toadd.skill && i.player === toadd.player)) return; + if (map.todoList.some(i => i.skill === toadd.skill && i.player === toadd.player)) return; + map.todoList.add(toadd); + if (typeof map.player === 'string') map.todoList.sort((a, b) => (b.priority - a.priority) || (evt.playerMap.indexOf(a) - evt.playerMap.indexOf(b))); + else map.todoList.sort((a, b) => b.priority - a.priority); + }); + } + } + removeTrigger(skills, player) { + if (!player || !skills) return this; + let evt = this; + if (typeof skills == 'string') skills = [skills]; + game.expandSkills(skills); + while (true) { + evt = evt.getParent('arrangeTrigger'); + if (!evt || evt.name != 'arrangeTrigger' || !evt.doingList) return this; + const doing = evt.doingList.find(i => i.player == player); + const firstDo = evt.doingList.find(i => i.player == "firstDo"); + const lastDo = evt.doingList.find(i => i.player == "lastDo"); + + skills.forEach(skill => [doing, firstDo, lastDo].forEach(map => { + if (!map) return; + const toremove = map.todoList.filter(i => i.skill == skill && i.player == player); + if (toremove.length > 0) map.todoList.removeArray(toremove); + })); + } + } + trigger(name) { + if (_status.video) return this; + if ((this.name === 'gain' || this.name === 'lose') && !_status.gameDrawed) return this; + if (name === 'gameDrawEnd') _status.gameDrawed = true; + if (name === 'gameStart') { + lib.announce.publish('gameStart', {}); + if (_status.brawl && _status.brawl.gameStart) _status.brawl.gameStart(); + if (lib.config.show_cardpile) ui.cardPileButton.style.display = ''; + _status.gameStarted = true; + game.showHistory(); + } + if (!lib.hookmap[name] && !lib.config.compatiblemode) return this; + if (!game.players || !game.players.length) return this; + const event = this; + let start = [_status.currentPhase, event.source, event.player, game.me, game.players[0]].find(i => get.itemtype(i) == 'player'); + if (!start) return this; + if (!game.players.includes(start) && !game.dead.includes(start)) start = game.findNext(start); + const firstDo = { + player: "firstDo", + todoList: [], + doneList: [], + }; + const lastDo = { + player: "lastDo", + todoList: [], + doneList: [], + }; + const doingList = []; + const roles = ['player', 'source', 'target', 'global']; + const playerMap = game.players.concat(game.dead).sortBySeat(start); + let player = start; + let allbool = false; + do { + const doing = { + player: player, + todoList: [], + doneList: [], + listAdded: {}, + addList(skill) { + if (!skill) return; + if (Array.isArray(skill)) return skill.forEach(i => this.addList(i)); + if (this.listAdded[skill]) return; + this.listAdded[skill] = true; + + const info = lib.skill[skill]; + const list = info.firstDo ? firstDo.todoList : info.lastDo ? lastDo.todoList : this.todoList; + list.push({ + skill: skill, + player: this.player, + priority: get.priority(skill), + }); + if (typeof list.player == 'string') list.sort((a, b) => (b.priority - a.priority) || (playerMap.indexOf(a) - playerMap.indexOf(b))); + else list.sort((a, b) => b.priority - a.priority); + allbool = true; + } + }; + + const notemp = player.skills.slice(); + for (const j in player.additionalSkills) { + if (!j.startsWith('hidden:')) notemp.addArray(player.additionalSkills[j]); + } + Object.keys(player.tempSkills).filter(skill => { + if (notemp.includes(skill)) return false; + const expire = player.tempSkills[skill]; + if (typeof expire === 'function') return expire(event, player, name); + if (get.objtype(expire) === 'object') return roles.some(role => { + if (role !== 'global' && player !== event[role]) return false; + if (Array.isArray(expire[role])) return expire[role].includes(name); + return expire[role] === name; + }); + }).forEach(skill => { + delete player.tempSkills[skill]; + player.removeSkill(skill); + }); + + if (lib.config.compatiblemode) { + doing.addList(game.expandSkills(player.getSkills('invisible').concat(lib.skill.global)).filter(skill => { + const info = get.info(skill); + if (!info || !info.trigger) return false; + return roles.some(role => { + if (info.trigger[role] === name) return true; + if (Array.isArray(info.trigger[role]) && info.trigger[role].includes(name)) return true; + }); + })); + } + else roles.forEach(role => { + doing.addList(lib.hook.globalskill[role + '_' + name]); + doing.addList(lib.hook[player.playerid + '_' + role + '_' + name]); + }); + delete doing.listAdded; + delete doing.addList; + doingList.push(doing); + player = player.nextSeat; + } while (player && player !== start); + doingList.unshift(firstDo); + doingList.push(lastDo); + // console.log(name,event.player,doingList.map(i=>({player:i.player,todoList:i.todoList.slice(),doneList:i.doneList.slice()}))) + + if (allbool) { + const next = game.createEvent('arrangeTrigger', false, event); + next.setContent('arrangeTrigger'); + next.doingList = doingList; + next._trigger = event; + next.triggername = name; + next.playerMap = playerMap; + event._triggering = next; + } + return this; + } + untrigger(all = true, player) { + const evt = this._triggering; + if (all) { + this._triggered = 5; + if (evt && evt.doingList) { + evt.doingList.forEach(doing => doing.todoList = []); + } + } + else if (player) { + this._notrigger.add(player); + // if(!evt||!evt.doingList) return this; + // const doing=evt.doingList.find(doing=>doing.player==player); + // if(doing) doing.todoList=[]; + } + return this; + } + /** + * 事件转为Promise化 + * + * @returns { import('../index.js').GameEventPromise } + */ + toPromise() { + if (!this.#promise) { + this.#promise = new lib.element.GameEventPromise(this); + } + return this.#promise; + } + /** + * @returns {never} + */ + typeAnnotation() { + /** + * @type {import('../index.js').Player} + */ + // @ts-ignore + this.source; + /** + * @type {import('../index.js').Player} + */ + // @ts-ignore + this.player; + /** + * @type {import('../index.js').Player} + */ + // @ts-ignore + this.target; + /** + * @type {import('../index.js').Player[]} + */ + // @ts-ignore + this.targets; + /** + * @type {import('../index.js').Card} + */ + // @ts-ignore + this.card; + /** + * @type {import('../index.js').Card[]} + */ + // @ts-ignore + this.cards; + /** + * @type {string} + */ + this.skill; + /** + * @type {boolean} + */ + this.forced; + /** + * @type {number} + */ + this.num; + /** + * @type {GameEvent} + */ + // @ts-ignore + this._trigger; + /** + * @type {Record} + */ + this._result; + /** + * @type {number} + */ + // @ts-ignore + this.baseDamage; + /** + * @type {import('../index.js').Player} + */ + // @ts-ignore + this.customSource; + /** + * @type {number} + */ + // @ts-ignore + this.extraDamage; + /** + * @type {string} + */ + // @ts-ignore + this.nature; + /** + * @type {boolean} + */ + // @ts-ignore + this.notrigger; + /** + * @type {number} + */ + // @ts-ignore + this.original_num; + /** + * @type {boolean} + */ + // @ts-ignore + this.unreal; + /** + * @type { import('../index.js').Button[] } + */ + // @ts-ignore + this.excludeButton; + throw new Error('Do not call this method'); + } +} \ No newline at end of file diff --git a/noname/library/element/gameEventPromise.js b/noname/library/element/gameEventPromise.js new file mode 100644 index 000000000..30d0ac8cd --- /dev/null +++ b/noname/library/element/gameEventPromise.js @@ -0,0 +1,161 @@ +import { AI as ai } from '../../ai/index.js'; +import { Get as get } from '../../get/index.js'; +import { Game as game } from '../../game/index.js'; +import { Library as lib } from "../index.js"; +import { status as _status } from '../../status/index.js'; +import { UI as ui } from '../../ui/index.js'; +import { AsyncFunction } from '../../util/index.js'; + +/** + * 将事件Promise化以使用async异步函数来执行事件。 + * + * 事件Promise化后,需要既能使用await等待事件完成, + * 又需要在执行之前对事件进行配置。 + * + * 所以这个类的实例集成了事件和Promise二者的所有属性, + * 且Promise的原有属性无法被修改,一切对这个类实例的属性修改,删除, + * 再配置等操作都会转发到事件对应的属性中。 + * + * @todo 需要完成异步事件的debugger方法 + * + * @example + * 使用await xx()等待异步事件执行: + * ```js + * await game.xxx().setContent('yyy').set(zzz, 'i'); + * ``` + * 使用await player.xxx()等待异步事件执行: + * ```js + * await player.draw(2); + * game.log('等待', player, '摸牌完成执行log'); + * ``` + */ +export class GameEventPromise extends Promise { + // 我谢谢你,这里是必须有的 + // 否则Promise的方法对其子类无效 + static get [Symbol.species]() { + return Promise; + } + #event; + /** + * @param { import('./gameEvent.js').default } event + * @returns { Promise & import('./gameEvent.js').default } + */ + constructor(event) { + super(resolve => { + // 设置为异步事件 + event.async = true; + // 事件结束后触发resolve + event.resolve = resolve; + if (!_status.event) return; + // game.createEvent的时候还没立即push到next里 + Promise.resolve().then(() => { + game.executingAsyncEventMap.set(_status.event.toEvent(), (game.executingAsyncEventMap.get(_status.event.toEvent()) || Promise.resolve()).then(() => { + let eventPromise = _status.event.next.find(e => e.toEvent() == event); + // 如果父级事件也是一个异步的话,那应该立即执行这个事件的 + // 如果在AsyncFunction执行过程中在别的位置新建了一个异步事件,那也直接(等会set配置完)执行 + if (eventPromise && (_status.event.content instanceof AsyncFunction || Array.isArray(_status.event.contents))) { + // 异步执行game.loop + // 不直接game.loop(event)是因为需要让别人可以手动set()和setContent() + // 再执行game.loop是因为原有的game.loop被await卡住了, + // 得新执行一个只执行这个异步事件的game.loop + + // 事件自行处理skip情况 + if (event.player && event.player.skipList.includes(event.name)) { + _status.event.trigger(event.name + 'Skipped'); + event.player.skipList.remove(event.name); + if (lib.phaseName.includes(event.name)) event.player.getHistory('skipped').add(event.name); + _status.event.next.remove(eventPromise); + event.finish(); + resolve(); + return eventPromise; + } + + if (_status.event != eventPromise) { + eventPromise.parent = _status.event; + _status.event = eventPromise; + game.getGlobalHistory('everything').push(eventPromise); + } + return game.loop(eventPromise).then(() => { + // 有时候event.finished还是false + return eventPromise; + }); + } + })); + }); + }); + this.#event = event; + return new Proxy(this, { + get(target, prop, receiver) { + const thisValue = Reflect.get(target, prop); + if (thisValue) { + if (typeof thisValue == 'function') { + return thisValue.bind(target); + } + return thisValue; + } + const eventValue = Reflect.get(event, prop); + return eventValue == event ? receiver : eventValue; + }, + set(target, prop, newValue) { + return Reflect.set(event, prop, newValue); + }, + deleteProperty(target, prop) { + return Reflect.deleteProperty(event, prop); + }, + defineProperty(target, prop, attributes) { + return Reflect.defineProperty(event, prop, attributes); + }, + has(target, prop) { + return Reflect.has(event, prop); + }, + ownKeys(target) { + return Reflect.ownKeys(event); + }, + getOwnPropertyDescriptor(target, prop) { + return Reflect.getOwnPropertyDescriptor(event, prop); + }, + }); + } + /** 获取原事件对象 */ + toEvent() { + return this.#event; + } + /** + * 在某个异步事件中调试变量信息 + * + * 注: 在调试步骤中`定义的变量只在当前输入的语句有效` + * + * @example + * 在技能中调试技能content相关的信息 + * ```js + * await event.debugger(); + * ``` + * 在技能中调试触发此技能事件的相关的信息 + * ```js + * await trigger.debugger(); + * ``` + */ + async debugger() { + return new Promise(resolve => { + const runCode = function (event, code) { + try { + // 为了使玩家调试时使用var player=xxx时不报错,故使用var + var { player, _trigger: trigger, _result: result } = event; + return eval(code); + } catch (error) { + return error; + } + }.bind(window); + const inputCallback = inputResult => { + if (inputResult === false) { + resolve(null); + } else { + const obj = runCode(this.toEvent(), inputResult); + alert((!obj || obj instanceof Error) ? String(obj) : get.stringify(obj)); + game.promises.prompt('debugger调试').then(inputCallback); + } + }; + game.promises.prompt('debugger调试').then(inputCallback); + }); + } +} \ No newline at end of file diff --git a/noname/library/element/index.js b/noname/library/element/index.js new file mode 100644 index 000000000..817d3cc2e --- /dev/null +++ b/noname/library/element/index.js @@ -0,0 +1,12 @@ +export { Button } from "./button.js"; +export { Card } from "./card.js"; +export { Client } from "./client.js"; +export { Content } from "./content.js"; +export { Contents } from "./contents.js"; +export { Control } from "./control.js"; +export { Dialog } from "./dialog.js"; +export { GameEvent } from "./gameEvent.js"; +export { GameEventPromise } from "./gameEventPromise.js"; +export { NodeWS } from "./nodeWs.js"; +export { Player } from "./player.js"; +export { VCard } from "./vcard.js"; \ No newline at end of file diff --git a/noname/library/element/nodeWS.js b/noname/library/element/nodeWS.js new file mode 100644 index 000000000..bcbf3588a --- /dev/null +++ b/noname/library/element/nodeWS.js @@ -0,0 +1,25 @@ +import { AI as ai } from '../../ai/index.js'; +import { Get as get } from '../../get/index.js'; +import { Game as game } from '../../game/index.js'; +import { Library as lib } from "../index.js"; +import { status as _status } from '../../status/index.js'; +import { UI as ui } from '../../ui/index.js'; +import { GNC as gnc } from '../../gnc/index.js'; + +export class NodeWS { + /** + * @param {string} id + */ + constructor(id) { + this.wsid = id; + } + send(message) { + game.send('server', 'send', this.wsid, message); + } + on(type, func) { + this['on' + type] = func; + } + close() { + game.send('server', 'close', this.wsid); + } +} \ No newline at end of file diff --git a/noname/library/element/player.js b/noname/library/element/player.js index 5b6f97fab..a4c04dcb5 100644 --- a/noname/library/element/player.js +++ b/noname/library/element/player.js @@ -5,9 +5,9 @@ import { Library as lib } from "../index.js"; import { status as _status } from '../../status/index.js'; import { UI as ui } from '../../ui/index.js'; -export default class extends HTMLDivElement { +export class Player extends HTMLDivElement { /** - * @param {HTMLDivElement} [position] + * @param {HTMLDivElement|DocumentFragment} [position] * @param {true} [noclick] */ // @ts-ignore @@ -17,7 +17,7 @@ export default class extends HTMLDivElement { */ // @ts-ignore const player = ui.create.div('.player', position); - Object.setPrototypeOf(player, lib.element.Player.prototype); + Object.setPrototypeOf(player, Player.prototype); player.build(noclick); return player; } diff --git a/noname/library/element/vcard.js b/noname/library/element/vcard.js new file mode 100644 index 000000000..3a8e73515 --- /dev/null +++ b/noname/library/element/vcard.js @@ -0,0 +1,114 @@ +import { AI as ai } from '../../ai/index.js'; +import { Get as get } from '../../get/index.js'; +import { Game as game } from '../../game/index.js'; +import { Library as lib } from "../index.js"; +import { status as _status } from '../../status/index.js'; +import { UI as ui } from '../../ui/index.js'; + +export class VCard { + /** + * @param {any} [suitOrCard] + * @param {number | Card[]} [numberOrCards] + * @param {string} [name] + * @param {string} [nature] + */ + constructor(suitOrCard, numberOrCards, name, nature) { + if (Array.isArray(suitOrCard)) { + /** + * @type {string} + */ + this.suit = suitOrCard[0]; + /** + * @type {number} + */ + this.number = suitOrCard[1]; + /** + * @type {string} + */ + this.name = suitOrCard[2]; + /** + * @type {string} + */ + this.nature = suitOrCard[3]; + } + else if (get.itemtype(suitOrCard) == 'card') { + this.name = get.name(suitOrCard); + this.suit = get.suit(suitOrCard); + this.color = get.color(suitOrCard); + this.number = get.number(suitOrCard); + this.nature = get.nature(suitOrCard); + this.isCard = true; + this.cardid = suitOrCard.cardid; + this.wunature = suitOrCard.wunature; + /** + * @type {Record} + */ + this.storage = get.copy(suitOrCard.storage); + if (Array.isArray(numberOrCards)) this.cards = numberOrCards.slice(); + else this.cards = [suitOrCard]; + const info = get.info(this, false); + if (info) { + const autoViewAs = info.autoViewAs; + if (typeof autoViewAs == 'string') this.name = autoViewAs; + } + } + else if (suitOrCard && typeof suitOrCard != 'string') { + Object.keys(suitOrCard).forEach(key => { + const propertyDescriptor = Object.getOwnPropertyDescriptor(suitOrCard, key), value = propertyDescriptor.value; + if (Array.isArray(value)) this[key] = value.slice(); + else Object.defineProperty(this, key, propertyDescriptor); + }); + if (Array.isArray(numberOrCards)) { + const noCards = !this.cards; + /** + * @type {Card[]} + */ + this.cards = numberOrCards.slice(); + if (noCards) { + if (!lib.suits.includes(this.suit)) this.suit = get.suit(this); + if (!Object.keys(lib.color).includes(this.color)) this.color = get.color(this); + if (typeof this.number != 'number') this.number = get.number(this); + if (!this.nature) this.nature = get.nature(this); + } + } + const info = get.info(this, false); + if (info) { + const autoViewAs = info.autoViewAs; + if (typeof autoViewAs == 'string') this.name = autoViewAs; + } + } + if (typeof suitOrCard == 'string') this.suit = suitOrCard; + if (typeof numberOrCards == 'number') this.number = numberOrCards; + if (typeof name == 'string') this.name = name; + if (typeof nature == 'string') this.nature = nature; + if (!this.storage) this.storage = {}; + if (!this.cards) this.cards = []; + } + sameSuitAs(card) { + return get.suit(this) == get.suit(card); + } + differentSuitFrom(card) { + return get.suit(this) != get.suit(card); + } + sameNumberAs(card) { + return get.number(this) == get.number(card); + } + differentNumberFrom(card) { + return get.number(this) != get.number(card); + } + sameNameAs(card) { + return get.name(this) == get.name(card); + } + differentNameFrom(card) { + return get.name(this) != get.name(card); + } + /** + * @param {Player} player + */ + hasNature(nature, player) { + const natures = get.natureList(this, player); + if (!nature) return natures.length > 0; + if (nature == 'linked') return natures.some(n => lib.linked.includes(n)); + return get.is.sameNature(natures, nature); + } +} \ No newline at end of file diff --git a/noname/library/index.js b/noname/library/index.js index 97162e6c8..8e42ec2fc 100644 --- a/noname/library/index.js +++ b/noname/library/index.js @@ -20,9 +20,8 @@ import { GNC as gnc } from '../gnc/index.js'; import { LibInit } from "./init/index.js"; import { Announce } from "./announce/index.js"; -import LibElementContent from "./element/content.js"; -import LibElementContents from "./element/contents.js"; -import LibElementPlayer from "./element/player.js"; +import * as Element from "./element/index.js"; + export class Library extends Uninstantable { static configprefix = 'noname_0.9_'; @@ -97,6 +96,25 @@ export class Library extends Uninstantable { */ // @ts-ignore this.videos; + /** + * @type { { + * fs: typeof import("fs"), + * path: typeof import("path"), + * debug: () => void, + * clients: Element.Client[], + * banned:[], + * observing:[], + * torespond:{}, + * torespondtimeout:{}, + * } } + */ + // @ts-ignore + this.node; + /** + * @type { { [key: string]: string } } + */ + // @ts-ignore + this.playerOL; throw new Error('Do not call this method'); } //函数钩子 @@ -9225,2268 +9243,18 @@ export class Library extends Uninstantable { _stratagem_add_buff: '强化' }; static element = { - content: LibElementContent, - contents: LibElementContents, - Player: LibElementPlayer, - Card: class extends HTMLDivElement { - /** - * @param {HTMLDivElement} [position] - * @param {'noclick'} [info] - * @param {true} [noclick] - */ - constructor(position, info, noclick) { - const card = ui.create.div('.card', position); - Object.setPrototypeOf(card, lib.element.Card.prototype); - card.build(info, noclick); - return card; - } - build(info, noclick) { - let card = this; - card.buildNode(); - card.buildIntro(noclick); - card.buildProperty(); - card.buildEventListener(info); - } - buildEventListener(info) { - let card = this; - if (info != 'noclick') { - card.addEventListener(lib.config.touchscreen ? 'touchend' : 'click', ui.click.card); - if (lib.config.touchscreen) { - card.addEventListener('touchstart', ui.click.cardtouchstart); - card.addEventListener('touchmove', ui.click.cardtouchmove); - } - if (lib.cardSelectObserver) lib.cardSelectObserver.observe(card, { - attributes: true - }); - } - } - buildProperty() { - let card = this; - card.storage = {}; - card.vanishtag = []; - card.gaintag = []; - card._uncheck = []; - } - buildNode() { - this.node = { - image: ui.create.div('.image', this), - info: ui.create.div('.info', this), - name: ui.create.div('.name', this), - name2: ui.create.div('.name2', this), - background: ui.create.div('.background', this), - intro: ui.create.div('.intro', this), - range: ui.create.div('.range', this), - gaintag: ui.create.div('.gaintag', this), - }; - this.node.intro.innerHTML = lib.config.intro; - } - buildIntro(noclick) { - if (!noclick) lib.setIntro(this); - } - //执行销毁一张牌的钩子函数 - selfDestroy(event) { - if (this._selfDestroyed) return; - this._selfDestroyed = true; - this.fix(); - this.delete(); - const info = get.info(this, false); - if (!info) return; - if (info.destroyLog !== false) game.log(this, '被销毁了'); - if (info.onDestroy) info.onDestroy(this, event); - } - //判断一张牌进入某个区域后是否会被销毁 - willBeDestroyed(targetPosition, player, event) { - const destroyed = this.destroyed; - if (typeof destroyed == 'function') { - return destroyed(this, targetPosition, player, event); - } - else if (lib.skill[destroyed]) { - if (player) { - if (player.hasSkill(destroyed)) { - delete this.destroyed; - return false; - } - } - return true; - } - else if (typeof destroyed == 'string') { - return (destroyed == targetPosition); - } - return destroyed; - } - hasNature(nature, player) { - return game.hasNature(this, nature, player); - } - //只针对【杀】起效果 - addNature(nature) { - let natures = []; - if (!this.nature) this.nature = ''; - else { - natures.addArray(get.natureList(this.nature)); - } - natures.addArray(get.natureList(nature)); - this.nature = get.nature(natures); - this.classList.add(nature); - let str = get.translation(this.nature) + '杀'; - this.node.name.innerText = str; - let name = get.name(this, false); - do { - if (name == 'sha') { - let _bg; - for (const n of natures) if (lib.natureBg.has(n)) _bg = n; - if (_bg) { - this.node.image.setBackgroundImage(lib.natureBg.get(_bg)); - break; - } - } - this.node.image.setBackgroundImage('image/card/' + name + '.png'); - } - while (0); - return this.nature; - } - removeNature(nature) { - if (!this.nature) return; - let natures = get.natureList(this.nature); - natures.remove(nature); - if (!natures.length) delete this.nature; - else this.nature = get.nature(natures); - this.classList.remove(nature); - let str = get.translation(this.nature) + '杀'; - this.node.name.innerText = str; - let name = get.name(this, false); - do { - if (name == 'sha') { - let _bg; - for (const n of natures) if (lib.natureBg.has(n)) _bg = n; - if (_bg) { - this.node.image.setBackgroundImage(lib.natureBg.get(_bg)); - break; - } - } - this.node.image.setBackgroundImage('image/card/' + name + '.png'); - } - while (0); - return this.nature; - } - addGaintag(gaintag) { - if (Array.isArray(gaintag)) this.gaintag = gaintag.slice(0); - else this.gaintag.add(gaintag); - var str = ''; - for (var gi = 0; gi < this.gaintag.length; gi++) { - var translate = get.translation(this.gaintag[gi]); - if (translate != 'invisible') { - str += translate; - if (gi < this.gaintag.length - 1) str += ' '; - } - } - this.node.gaintag.innerHTML = str; - } - removeGaintag(tag) { - if (tag === true) { - if (this.gaintag && this.gaintag.length || this.node.gaintag.innerHTML.length) this.addGaintag([]); - } - else if (this.hasGaintag(tag)) { - this.gaintag.remove(tag); - this.addGaintag(this.gaintag); - } - } - hasGaintag(tag) { - return this.gaintag && this.gaintag.contains(tag); - } - /** - * @param {[string, number, string, string] | { - * suit: string; - * number: number; - * name: string; - * nature: string; - * }} card - */ - init(card) { - if (Array.isArray(card)) { - if (card[2] == 'huosha') { - card[2] = 'sha'; - card[3] = 'fire'; - } - else if (card[2] == 'leisha') { - card[2] = 'sha'; - card[3] = 'thunder'; - } - else if (card[2] == 'cisha') { - card[2] = 'sha'; - card[3] = 'stab'; - } - else if (card[2].length > 3) { - let prefix = card[2].slice(0, card[2].lastIndexOf('sha')); - if (lib.nature.has(prefix)) { - if (prefix.length + 3 == card[2].length) { - card[2] = 'sha'; - card[3] = prefix; - } - } - if (card[2].startsWith('sha_')) { - let suffix = card[2].slice(4); - let natureList = suffix.split('_'); - card[2] = 'sha'; - card[3] = get.nature(natureList); - } - } - } - else if (typeof card == 'object') { - card = [card.suit, card.number, card.name, card.nature]; - } - var cardnum = card[1] || ''; - if (parseInt(cardnum) == cardnum) cardnum = parseInt(cardnum); - - if (!lib.card[card[2]]) { - lib.card[card[2]] = {}; - } - var info = lib.card[card[2]]; - if (info.global && !this.classList.contains('button')) { - if (Array.isArray(info.global)) { - while (info.global.length) { - game.addGlobalSkill(info.global.shift()); - } - } - else if (typeof info.global == 'string') { - game.addGlobalSkill(info.global); - } - delete info.global; - } - this.suit = card[0]; - this.number = parseInt(card[1]) || 0; - this.name = card[2]; - - if (info.destroy && (typeof info.destroy != 'boolean' && !lib.skill[info.destroy])) { - this.destroyed = info.destroy; - } - - if (_status.connectMode && !game.online && lib.cardOL && !this.cardid) { - this.cardid = get.id(); - lib.cardOL[this.cardid] = this; - } - if (!_status.connectMode && !_status.video) { - this.cardid = get.id(); - } - - this.$init(card); - - if (this.inits) { - for (var i = 0; i < this.inits.length; i++) { - this.inits[i](this); - } - } - if (typeof info.init == 'function') info.init(); - - return this; - } - /** - * @param {[string, number, string, string]} card - */ - $init(card) { - var info = lib.card[card[2]]; - var cardnum = card[1] || ''; - if (parseInt(cardnum) == cardnum) cardnum = parseInt(cardnum); - if (cardnum > 0 && cardnum < 14) { - cardnum = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'][cardnum - 1]; - } - if (this.name) { - this.classList.remove('epic'); - this.classList.remove('legend'); - this.classList.remove('gold'); - this.classList.remove('unique'); - this.style.background = ''; - var subtype = get.subtype(this, false); - if (subtype) { - this.classList.remove(subtype); - } - } - if (info.epic) { - this.classList.add('epic'); - } - else if (info.legend) { - this.classList.add('legend'); - } - else if (info.gold) { - this.classList.add('gold'); - } - else if (info.unique) { - this.classList.add('unique'); - } - var bg = card[2]; - if (info.cardimage) { - bg = info.cardimage; - } - var img = lib.card[bg].image; - if (img) { - if (img.startsWith('db:')) { - img = img.slice(3); - } - else if (!img.startsWith('ext:')) { - img = null; - } - } - this.classList.remove('fullskin'); - this.classList.remove('fullimage'); - this.classList.remove('fullborder'); - this.dataset.cardName = card[2]; - this.dataset.cardType = info.type || ''; - this.dataset.cardSubype = info.subtype || ''; - this.dataset.cardMultitarget = info.multitarget ? '1' : '0'; - this.node.name.dataset.nature = ''; - this.node.info.classList.remove('red'); - if (!lib.config.hide_card_image && lib.card[bg].fullskin) { - this.classList.add('fullskin'); - if (img) { - if (img.startsWith('ext:')) { - this.node.image.setBackgroundImage(img.replace(/^ext:/, 'extension/')); - } - else { - this.node.image.setBackgroundDB(img); - } - } - else { - if (lib.card[bg].modeimage) { - this.node.image.setBackgroundImage('image/mode/' + lib.card[bg].modeimage + '/card/' + bg + '.png'); - } - else { - do { - let nature = card[3]; - if (bg == 'sha' && typeof nature == 'string') { - let natures = get.natureList(nature), _bg; - for (const n of natures) if (lib.natureBg.has(n)) _bg = n; - if (_bg) { - this.node.image.setBackgroundImage(lib.natureBg.get(_bg)); - break; - } - } - this.node.image.setBackgroundImage('image/card/' + bg + '.png'); - } - while (0); - } - } - } - else if (lib.card[bg].image == 'background') { - if (card[3]) this.node.background.setBackground(bg + '_' + get.natureList(card[3])[0], 'card'); - else this.node.background.setBackground(bg, 'card'); - } - else if (lib.card[bg].fullimage) { - this.classList.add('fullimage'); - if (img) { - if (img.startsWith('ext:')) { - this.setBackgroundImage(img.replace(/^ext:/, 'extension/')); - this.style.backgroundSize = 'cover'; - } - else { - this.setBackgroundDB(img); - } - } - else if (lib.card[bg].image) { - if (lib.card[bg].image.startsWith('character:')) { - this.setBackground(lib.card[bg].image.slice(10), 'character'); - } - else { - this.setBackground(lib.card[bg].image); - } - } - else { - var cardPack = lib.cardPack['mode_' + get.mode()]; - if (Array.isArray(cardPack) && cardPack.contains(bg)) { - this.setBackground('mode/' + get.mode() + '/card/' + bg); - } - else { - this.setBackground('card/' + bg); - } - } - } - else if (lib.card[bg].fullborder) { - this.classList.add('fullborder'); - if (lib.card[bg].fullborder == 'gold') { - this.node.name.dataset.nature = 'metalmm'; - } - else if (lib.card[bg].fullborder == 'silver') { - this.node.name.dataset.nature = 'watermm'; - } - if (!this.node.avatar) { - this.node.avatar = ui.create.div('.cardavatar'); - this.insertBefore(this.node.avatar, this.firstChild); - } - if (!this.node.framebg) { - this.node.framebg = ui.create.div('.cardframebg'); - this.node.framebg.dataset.auto = lib.card[bg].fullborder; - this.insertBefore(this.node.framebg, this.firstChild); - } - if (img) { - if (img.startsWith('ext:')) { - this.node.avatar.setBackgroundImage(img.replace(/^ext:/, 'extension/')); - this.node.avatar.style.backgroundSize = 'cover'; - } - else { - this.node.avatar.setBackgroundDB(img); - } - } - else if (lib.card[bg].image) { - if (lib.card[bg].image.startsWith('character:')) { - this.node.avatar.setBackground(lib.card[bg].image.slice(10), 'character'); - } - else { - this.node.avatar.setBackground(lib.card[bg].image); - } - } - else { - var cardPack = lib.cardPack['mode_' + get.mode()]; - if (Array.isArray(cardPack) && cardPack.contains(bg)) { - this.node.avatar.setBackground('mode/' + get.mode() + '/card/' + bg); - } - else { - this.node.avatar.setBackground('card/' + bg); - } - } - } - else if (lib.card[bg].image == 'card') { - if (card[3]) this.setBackground(bg + '_' + get.natureList(card[3])[0], 'card'); - else this.setBackground(bg, 'card'); - } - else if (typeof lib.card[bg].image == 'string' && !lib.card[bg].fullskin) { - if (img) { - if (img.startsWith('ext:')) { - this.setBackgroundImage(img.replace(/^ext:/, 'extension/')); - this.style.backgroundSize = 'cover'; - } - else { - this.setBackgroundDB(img); - } - } - else { - this.setBackground(lib.card[bg].image); - } - } - else { - this.node.background.innerHTML = lib.translate[bg + '_cbg'] || lib.translate[bg + '_bg'] || get.translation(bg)[0]; - // this.node.background.style.fontFamily=lib.config.card_font; - if (this.node.background.innerHTML.length > 1) this.node.background.classList.add('tight'); - else this.node.background.classList.remove('tight'); - } - if (!lib.card[bg].fullborder && this.node.avatar && this.node.framebg) { - this.node.avatar.remove(); - this.node.framebg.remove(); - delete this.node.avatar; - delete this.node.framebg; - } - if (info.noname && !this.classList.contains('button')) { - this.node.name.style.display = 'none'; - } - if (info.color) { - this.style.color = info.color; - } - if (info.textShadow) { - this.style.textShadow = info.textShadow; - } - if (info.opacity) { - this.node.info.style.opacity = info.opacity; - this.node.name.style.opacity = info.opacity; - } - if (info.modinfo) { - this.node.info.innerHTML = info.modinfo; - } - else { - this.node.info.innerHTML = get.translation(card[0]) + ' ' + cardnum + ''; - } - if (info.addinfo) { - if (!this.node.addinfo) { - this.node.addinfo = ui.create.div('.range', this); - } - this.node.addinfo.innerHTML = info.addinfo; - } - else if (this.node.addinfo) { - this.node.addinfo.remove(); - delete this.node.addinfo; - } - if (card[0] == 'heart' || card[0] == 'diamond') { - this.node.info.classList.add('red'); - } - this.node.image.className = 'image'; - var name = get.translation(card[2]); - if (card[2] == 'sha') { - name = ''; - let nature = card[3]; - if (nature) { - let natures = get.natureList(nature); - natures.sort(lib.sort.nature); - for (let nature of natures) { - name += lib.translate['nature_' + nature] || lib.translate[nature] || ''; - if (nature != 'stab') this.node.image.classList.add(nature); - } - } - name += '杀'; - } - this.node.name.innerHTML = name; - if (name.length >= 5) { - this.node.name.classList.add('long'); - if (name.length >= 7) { - this.node.name.classList.add('longlong'); - } - } - this.node.name2.innerHTML = get.translation(card[0]) + cardnum + ' ' + name; - this.classList.add('card'); - if (card[3]) { - let natures = get.natureList(card[3]); - natures.forEach(n => { if (n) this.classList.add(n); }); - this.nature = natures.filter(n => lib.nature.has(n)).sort(lib.sort.nature).join(lib.natureSeparator); - } - else if (this.nature) { - this.classList.remove(this.nature); - delete this.nature; - } - if (info.subtype) this.classList.add(info.subtype); - this.node.range.innerHTML = ''; - switch (get.subtype(this, false)) { - case 'equip1': - var added = false; - if (lib.card[this.name] && lib.card[this.name].distance) { - var dist = lib.card[this.name].distance; - if (dist.attackFrom) { - added = true; - this.node.range.innerHTML = '范围: ' + (-dist.attackFrom + 1); - } - } - if (!added) { - this.node.range.innerHTML = '范围: 1'; - } - break; - case 'equip3': - if (info.distance && info.distance.globalTo) { - this.node.range.innerHTML = '防御: ' + info.distance.globalTo; - this.node.name2.innerHTML += '+'; - } - break; - case 'equip4': - if (info.distance && info.distance.globalFrom) { - this.node.range.innerHTML = '进攻: ' + (-info.distance.globalFrom); - this.node.name2.innerHTML += '-'; - } - break; - } - var tags = []; - if (Array.isArray(card[4])) { - tags.addArray(card[4]); - } - if (this.cardid) { - if (!_status.cardtag) { - _status.cardtag = {}; - } - for (var i in _status.cardtag) { - if (_status.cardtag[i].contains(this.cardid)) { - tags.add(i); - } - } - if (tags.length) { - var tagstr = ' '; - for (var i = 0; i < tags.length; i++) { - var tag = tags[i]; - if (!_status.cardtag[tag]) { - _status.cardtag[tag] = []; - } - _status.cardtag[tag].add(this.cardid); - tagstr += lib.translate[tag + '_tag']; - //if(i this._knowers.add(p.playerid)); - } - } - } - removeKnower(player) { - if (!this._knowers) { - return; - } - if (typeof player == 'string') { - this._knowers.remove(player); - } else { - let type = get.itemtype(player); - if (type == 'player') { - this._knowers.remove(player.playerid); - } else if (type == 'players') { - player.forEach(p => this._knowers.remove(p.playerid)); - } - } - } - //清除此牌的知情者。 - clearKnowers() { - if (this._knowers) delete this._knowers; - } - //判断玩家对此牌是否知情。 - isKnownBy(player) { - if (['e', 'j'].includes(get.position(this))) return true;//装备区或者判定区的牌,必知情。 - let owner = get.owner(this); - if (owner) { - if (owner == player) return true;//是牌主,必知情。 - if (player.hasSkillTag('viewHandcard', null, owner, true)) return true;//有viewHandcard标签,必知情。 - if (owner.isUnderControl(true, player)) return true;//被操控,必知情。 - } - if (get.is.shownCard(this)) return true;//此牌是明置牌,必知情。 - if (this._knowers) { - return this._knowers.includes('everyone') || this._knowers.includes(player.playerid); - } - return false; - } - getSource(name) { - if (this.name == name) return true; - var info = lib.card[this.name]; - if (info && Array.isArray(info.source)) { - return info.source.contains(name); - } - return false; - } - moveDelete(player) { - this.fixed = true; - if (!this._listeningEnd || this._transitionEnded) { - this.moveTo(player); - var that = this; - setTimeout(function () { - that.delete(); - }, 200); - } - else { - this._onEndMoveDelete = player; - } - } - moveTo(player) { - this.fixed = true; - var dx, dy; - if (this.classList.contains('center')) { - var nx = [50, -52]; - var ny = [50, -52]; - nx = nx[0] * ui.arena.offsetWidth / 100 + nx[1]; - ny = ny[0] * ui.arena.offsetHeight / 100 + ny[1]; - dx = player.getLeft() + player.offsetWidth / 2 - 52 - nx; - dy = player.getTop() + player.offsetHeight / 2 - 52 - ny; - } - else { - this.style.left = this.offsetLeft + 'px'; - this.style.top = this.offsetTop + 'px'; - - dx = player.getLeft() + player.offsetWidth / 2 - 52 - this.offsetLeft; - dy = player.getTop() + player.offsetHeight / 2 - 52 - this.offsetTop; - } - if (get.is.mobileMe(player)) { - dx += get.cardOffset(); - if (ui.arena.classList.contains('oblongcard')) { - dy -= 16; - } - } - - - if (this.style.transform && this.style.transform != 'none' && this.style.transform.indexOf('translate') == -1) { - this.style.transform += ' translate(' + dx + 'px,' + dy + 'px)'; - } - else { - this.style.transform = 'translate(' + dx + 'px,' + dy + 'px)'; - } - return this; - } - copy() { - /** - * @type {Card} - */ - var node = this.cloneNode(true); - node.style.transform = ''; - node.name = this.name; - node.suit = this.suit; - node.number = this.number; - node.nature = this.nature; - node.classList.remove('hidden'); - node.classList.remove('start'); - node.classList.remove('thrown'); - node.classList.remove('selectable'); - node.classList.remove('selected'); - node.classList.remove('removing'); - node.classList.remove('drawinghidden'); - node.classList.remove('glows'); - node.node = { - name: node.querySelector('.name'), - info: node.querySelector('.info'), - intro: node.querySelector('.intro'), - background: node.querySelector('.background'), - image: node.querySelector('.image'), - gaintag: node.querySelector('.gaintag'), - }; - node.node.gaintag.innerHTML = ''; - var clone = true; - var position; - for (var i = 0; i < arguments.length; i++) { - if (typeof arguments[i] == 'string') node.classList.add(arguments[i]); - else if (['div', 'fragment'].includes(get.objtype(arguments[i]))) position = arguments[i]; - else if (typeof arguments[i] == 'boolean') clone = arguments[i]; - } - node.moveTo = lib.element.Card.prototype.moveTo; - node.moveDelete = lib.element.Card.prototype.moveDelete; - if (clone) this.clone = node; - if (position) position.appendChild(node); - return node; - } - uncheck(skill) { - if (skill) this._uncheck.add(skill); - this.classList.add('uncheck'); - } - recheck(skill) { - if (skill) this._uncheck.remove(skill); - else this._uncheck.length = 0; - if (this._uncheck.length == 0) this.classList.remove('uncheck'); - } - discard(bool) { - if (!this._selfDestroyed) { - this.fix(); - ui.discardPile.appendChild(this); - } - this.classList.remove('glow'); - if (bool === false) { - ui.cardPile.insertBefore(this, ui.cardPile.childNodes[Math.floor(Math.random() * ui.cardPile.childNodes.length)]); - } - else { - if (_status.discarded) { - _status.discarded.add(this); - } - } - } - hasTag(tag) { - if (this.cardid && _status.cardtag && _status.cardtag[tag] && _status.cardtag[tag].contains(this.cardid)) { - return true; - } - return false; - } - hasPosition() { - return ['h', 'e', 'j', 's', 'x'].contains(get.position(this)); - } - isInPile() { - return ['c', 'd'].contains(get.position(this)); - } - }, - VCard: class { - /** - * @param {any} [suitOrCard] - * @param {number | Card[]} [numberOrCards] - * @param {string} [name] - * @param {string} [nature] - */ - constructor(suitOrCard, numberOrCards, name, nature) { - if (Array.isArray(suitOrCard)) { - /** - * @type {string} - */ - this.suit = suitOrCard[0]; - /** - * @type {number} - */ - this.number = suitOrCard[1]; - /** - * @type {string} - */ - this.name = suitOrCard[2]; - /** - * @type {string} - */ - this.nature = suitOrCard[3]; - } - else if (get.itemtype(suitOrCard) == 'card') { - this.name = get.name(suitOrCard); - this.suit = get.suit(suitOrCard); - this.color = get.color(suitOrCard); - this.number = get.number(suitOrCard); - this.nature = get.nature(suitOrCard); - this.isCard = true; - this.cardid = suitOrCard.cardid; - this.wunature = suitOrCard.wunature; - /** - * @type {Record} - */ - this.storage = get.copy(suitOrCard.storage); - if (Array.isArray(numberOrCards)) this.cards = numberOrCards.slice(); - else this.cards = [suitOrCard]; - const info = get.info(this, false); - if (info) { - const autoViewAs = info.autoViewAs; - if (typeof autoViewAs == 'string') this.name = autoViewAs; - } - } - else if (suitOrCard && typeof suitOrCard != 'string') { - Object.keys(suitOrCard).forEach(key => { - const propertyDescriptor = Object.getOwnPropertyDescriptor(suitOrCard, key), value = propertyDescriptor.value; - if (Array.isArray(value)) this[key] = value.slice(); - else Object.defineProperty(this, key, propertyDescriptor); - }); - if (Array.isArray(numberOrCards)) { - const noCards = !this.cards; - /** - * @type {Card[]} - */ - this.cards = numberOrCards.slice(); - if (noCards) { - if (!lib.suits.includes(this.suit)) this.suit = get.suit(this); - if (!Object.keys(lib.color).includes(this.color)) this.color = get.color(this); - if (typeof this.number != 'number') this.number = get.number(this); - if (!this.nature) this.nature = get.nature(this); - } - } - const info = get.info(this, false); - if (info) { - const autoViewAs = info.autoViewAs; - if (typeof autoViewAs == 'string') this.name = autoViewAs; - } - } - if (typeof suitOrCard == 'string') this.suit = suitOrCard; - if (typeof numberOrCards == 'number') this.number = numberOrCards; - if (typeof name == 'string') this.name = name; - if (typeof nature == 'string') this.nature = nature; - if (!this.storage) this.storage = {}; - if (!this.cards) this.cards = []; - } - sameSuitAs(card) { - return get.suit(this) == get.suit(card); - } - differentSuitFrom(card) { - return get.suit(this) != get.suit(card); - } - sameNumberAs(card) { - return get.number(this) == get.number(card); - } - differentNumberFrom(card) { - return get.number(this) != get.number(card); - } - sameNameAs(card) { - return get.name(this) == get.name(card); - } - differentNameFrom(card) { - return get.name(this) != get.name(card); - } - /** - * @param {Player} player - */ - hasNature(nature, player) { - const natures = get.natureList(this, player); - if (!nature) return natures.length > 0; - if (nature == 'linked') return natures.some(n => lib.linked.includes(n)); - return get.is.sameNature(natures, nature); - } - }, - Button: class extends HTMLDivElement { - /** - * @param {{}} item - * @param {keyof typeof ui.create.buttonPresets | (item: {}, type: Function, position?: HTMLDivElement, noClick?: true, button?: HTMLDivElement) => HTMLDivElement} type - * @param {HTMLDivElement|DocumentFragment} [position] - * @param {true} [noClick] - * @param {HTMLDivElement} [button] - */ - constructor(item, type, position, noClick, button) { - if (ui.create.buttonPresets[type]) button = ui.create.buttonPresets[type](item, type, position, noClick, button); - else if (typeof type == 'function') button = type(item, type, position, noClick, button); - Object.setPrototypeOf(button, lib.element.Button.prototype); - if (!noClick) button.addEventListener(lib.config.touchscreen ? 'touchend' : 'click', ui.click.button); - else { - button.classList.add('noclick'); - const intro = button.querySelector('.intro'); - if (intro) intro.remove(); - } - return button; - } - exclude() { - if (_status.event.excludeButton == undefined) { - _status.event.excludeButton = []; - } - _status.event.excludeButton.add(this); - } - get updateTransform() { - return lib.element.Card.prototype.updateTransform; - } - }, - GameEvent: class { - /** @type { GameEventPromise } */ - #promise; - /** - * @param {string} [name] - * @param {false} [trigger] - */ - constructor(name, trigger) { - if (typeof name == 'string') { - this.name = name; - const gameEvent = get.event(); - if (gameEvent) { - const type = `onNext${name[0].toUpperCase()}${name.slice(1)}`; - if (gameEvent.hasHandler(type)) this.pushHandler(...gameEvent.getHandler(type)); - } - game.globalEventHandlers.addHandlerToEvent(this); - } - this.step = 0; - this.finished = false; - /** - * @type {(GameEventPromise)[]} - */ - this.next = []; - /** - * @type {(GameEventPromise)[]} - */ - this.after = []; - this.custom = { - add: {}, - replace: {} - }; - this._aiexclude = []; - this._notrigger = []; - this._result = {}; - this._set = []; - /** - * @type {boolean} 这个事件是否使用异步函数处理 - **/ - this.async = false; - /** - * @type {null|(event: GameEvent)=>any} 这个异步事件对应Promise的resolve函数 - **/ - this.resolve = null; - if (trigger !== false && !game.online) this._triggered = 0; - } - static initialGameEvent() { - return new lib.element.GameEvent().finish().toPromise(); - } - /** - * @param {keyof this} key - * @param {number} [value] - * @param {number} [baseValue] - */ - addNumber(key, value, baseValue) { - if (typeof value != 'number') value = 0; - if (typeof this[key] == 'number') this[key] += value; - else { - if (typeof baseValue != 'number') baseValue = 0; - this[key] = baseValue + value; - } - return this; - } - /** - * @param {keyof this} key - * @param {number} [baseValue] - */ - decrease(key, baseValue) { - if (typeof this[key] == 'number') this[key]--; - else this.subtractNumber(key, 1, baseValue); - return this; - } - /** - * @param {keyof this} key - * @param {number} [baseValue] - */ - increase(key, baseValue) { - if (typeof this[key] == 'number') this[key]++; - else this.addNumber(key, 1, baseValue); - return this; - } - /** - * @param {keyof this} key - * @param {number} [value] - * @param {number} [baseValue] - */ - subtractNumber(key, value, baseValue) { - if (typeof value != 'number') value = 0; - if (typeof this[key] == 'number') this[key] -= value; - else { - if (typeof baseValue != 'number') baseValue = 0; - this[key] = baseValue - value; - } - return this; - } - /** - * @param {Parameters[0]} type - * @param {GameEvent} event - * @param {{ - * state?: 'begin' | 'end'; - * }} option - * @returns {this} - */ - callHandler(type, event, option) { - if (this.hasHandler(type)) this.getHandler(type).forEach(handler => { - if (typeof handler == 'function') handler(event, option); - }); - return this; - } - getDefaultHandlerType() { - const eventName = this.name; - if (eventName) return `on${eventName[0].toUpperCase()}${eventName.slice(1)}`; - } - /** - * @param {Parameters[0]} [type] - * @returns {((event: GameEvent, option: { - * state?: 'begin' | 'end'; - * }) => void)[]} - */ - getHandler(type) { - if (!type) type = this.getDefaultHandlerType(); - const currentHandler = this[type]; - if (!currentHandler) this[type] = []; - else if (!Array.isArray(currentHandler)) this[type] = [currentHandler]; - return this[type]; - } - /** - * @param {`on${Capitalize}`} [type] - */ - hasHandler(type) { - if (!type) type = this.getDefaultHandlerType(); - return Boolean(this[type] && this.getHandler(type).length); - } - /** - * @overload - * @param {...((event: GameEvent, option: { - * state?: 'begin' | 'end'; - * }) => void)[]} handlers - * @returns {number} - */ - /** - * @overload - * @param {Parameters[0]} type - * @param {...((event: GameEvent, option: { - * state?: 'begin' | 'end'; - * }) => void)[]} handlers - * @returns {number} - */ - pushHandler(type) { - return typeof type == 'string' ? this.getHandler(type).push(...Array.from(arguments).slice(1)) : this.getHandler().push(...arguments); - } - changeToZero() { - this.num = 0; - this.numFixed = true; - return this; - } - finish() { - this.finished = true; - return this; - } - putStepCache(key, value) { - if (!this._stepCache) { - this._stepCache = {}; - } - this._stepCache[key] = value; - return this; - } - getStepCache(key) { - if (!this._stepCache) return undefined; - return this._stepCache[key]; - } - clearStepCache(key) { - if (key !== undefined && key !== null) { - delete this._stepCache[key]; - } - delete this._stepCache; - return this; - } - callFuncUseStepCache(prefix, func, params) { - if (typeof func != 'function') return; - if (_status.closeStepCache) return func.apply(null, params); - var cacheKey = "[" + prefix + "]" + get.paramToCacheKey.apply(null, params); - var ret = this.getStepCache(cacheKey); - if (ret === undefined || ret === null) { - ret = func.apply(null, params); - this.putStepCache(cacheKey, ret); - } - return ret; - } - putTempCache(key1, key2, value) { - if (!this._tempCache) { - this._tempCache = {}; - } - if (!this._tempCache[key1]) { - this._tempCache[key1] = {}; - } - this._tempCache[key1][key2] = value; - return value; - } - getTempCache(key1, key2) { - if (!this._tempCache) { - return undefined; - } - if (!this._tempCache[key1]) { - return undefined; - } - return this._tempCache[key1][key2]; - } - cancel(arg1, arg2, notrigger) { - this.untrigger(arg1, arg2); - this.finish(); - if (notrigger != 'notrigger') { - this.trigger(this.name + 'Cancelled'); - if (this.player && lib.phaseName.contains(this.name)) this.player.getHistory('skipped').add(this.name); - } - return this; - } - neutralize(event) { - this.untrigger(); - this.finish(); - this._neutralized = true; - this.trigger('eventNeutralized'); - this._neutralize_event = event || _status.event; - return this; - } - unneutralize() { - this.untrigger(); - delete this._neutralized; - delete this.finished; - if (this.type == 'card' && this.card && this.name == 'sha') this.directHit = true; - return this; - } - goto(step) { - this.step = step - 1; - return this; - } - redo() { - this.step--; - return this; - } - setHiddenSkill(skill) { - if (!this.player) return this; - var hidden = this.player.hiddenSkills.slice(0); - game.expandSkills(hidden); - if (hidden.contains(skill)) this.set('hsskill', skill); - return this; - } - set(key, value) { - if (arguments.length == 1 && Array.isArray(arguments[0])) { - for (var i = 0; i < arguments[0].length; i++) { - if (Array.isArray(arguments[0][i])) { - this.set(arguments[0][i][0], arguments[0][i][1]); - } - } - } - else { - if (typeof key != 'string') { - console.log('warning: using non-string object as event key'); - console.log(key, value); - console.log(_status.event); - } - this[key] = value; - this._set.push([key, value]); - } - return this; - } - /** - * @param {ArrayLike | Function | keyof typeof lib.element.content} item - */ - setContent(item) { - switch (typeof item) { - case "object": - case "function": - if (item instanceof AsyncFunction) { - this.content = item; - } - else this.content = lib.init.parsex(item); - break; - default: - try { - if (!(lib.element.content[item] instanceof AsyncFunction) && !lib.element.content[item]._parsed) { - lib.element.content[item] = lib.init.parsex(lib.element.content[item]); - lib.element.content[item]._parsed = true; - } - } - catch { - throw new Error(`Content ${item} may not exist.\nlib.element.content[${item}] = ${lib.element.content[item]}`); - } - this.content = lib.element.content[item]; - break; - } - return this; - } - - /** - * - * @param {import("../util/index.js").AsyncFunction[] | keyof typeof lib.element.contents} contents - * @returns {GameEvent} - */ - setContents(contents) { - if (Array.isArray(contents)) this.contents = contents; - else if (contents in lib.element.contents) return this.setContents(lib.element.contents[contents]); - else throw new Error('not supported value.'); - return this; - } - - getLogv() { - for (var i = 1; i <= 3; i++) { - var event = this.getParent(i); - if (event && event.logvid) return event.logvid; - } - return null; - } - send() { - this.player.send(function (name, args, set, event, skills) { - game.me.applySkills(skills); - var next = game.me[name].apply(game.me, args); - for (var i = 0; i < set.length; i++) { - next.set(set[i][0], set[i][1]); - } - if (next._backupevent) { - next.backup(next._backupevent); - } - next._modparent = event; - game.resume(); - }, this.name, this._args || [], this._set, - get.stringifiedResult(this.parent), get.skillState(this.player)); - this.player.wait(); - game.pause(); - return this; - } - resume() { - delete this._cardChoice; - delete this._targetChoice; - delete this._skillChoice; - return this; - } - /** - * 获取事件的父节点。 - * 获取事件链上的指定事件。 - * 默认获取上一个父节点(核心)。 - * @param {number|string|(evt:gameEvent)=>boolean} [level=1] 获取深度(number)/指定名字(string)/指定特征(function) - * @param {boolean} [forced] 若获取不到节点,默认返回{},若forced为true则返回null - * @param {boolean} [includeSelf] 若level不是数字,指定搜索时是否包含事件本身 - * @returns {GameEvent|{}|null} - */ - getParent(level = 1, forced, includeSelf) { - let event = this; - const toreturn = forced ? null : {}; - if (!includeSelf || typeof level === 'number') { - if (event._modparent && game.online) event = event._modparent; - else event = this.parent; - } - if (typeof level === 'number') { - for (let i = 1; i < level; i++) { - if (!event) return toreturn; - event = event.parent; - } - return event; - } - const historys = []; - const filter = typeof level === 'function' ? level : evt => evt.name === level; - while (true) { - if (!event) return toreturn; - historys.push(event); - if (filter(event)) return event; - event = event.parent; - if (historys.includes(event)) return toreturn; - } - } - getTrigger() { - return this.getParent('arrangeTrigger')._trigger; - } - getRand(name) { - if (name) { - if (!this._rand_map) this._rand_map = {}; - if (!this._rand_map[name]) this._rand_map[name] = Math.random(); - return this._rand_map[name]; - } - if (!this._rand) this._rand = Math.random(); - return this._rand; - } - insert(content, map) { - const next = (new lib.element.GameEvent(`${this.name}Inserted`, false)).toPromise(); - this.next.push(next); - next.setContent(content); - Object.entries(map).forEach(entry => next.set(entry[0], entry[1])); - return next; - } - insertAfter(content, map) { - const next = (new lib.element.GameEvent(`${this.name}Inserted`, false)).toPromise(); - this.after.push(next); - next.setContent(content); - Object.entries(map).forEach(entry => next.set(entry[0], entry[1])); - return next; - } - backup(skill) { - this._backup = { - filterButton: this.filterButton, - selectButton: this.selectButton, - filterTarget: this.filterTarget, - selectTarget: this.selectTarget, - filterCard: this.filterCard, - selectCard: this.selectCard, - position: this.position, - forced: this.forced, - fakeforce: this.fakeforce, - _aiexclude: this._aiexclude, - complexSelect: this.complexSelect, - complexCard: this.complexCard, - complexTarget: this.complexTarget, - _cardChoice: this._cardChoice, - _targetChoice: this._targetChoice, - _skillChoice: this._skillChoice, - ai1: this.ai1, - ai2: this.ai2, - filterOk: this.filterOk, - }; - if (skill) { - var info = get.info(skill); - this.skill = skill; - this._aiexclude = []; - if (typeof info.viewAs == 'function') { - if (info.filterButton != undefined) this.filterButton = get.filter(info.filterButton); - if (info.selectButton != undefined) this.selectButton = info.selectButton; - if (info.filterTarget != undefined) this.filterTarget = get.filter(info.filterTarget); - if (info.selectTarget != undefined) this.selectTarget = info.selectTarget; - if (info.filterCard != undefined) { - if (info.ignoreMod) this.ignoreMod = true; - this.filterCard2 = get.filter(info.filterCard); - this.filterCard = function (card, player, event) { - var evt = event || _status.event; - if (!evt.ignoreMod && player) { - var mod = game.checkMod(card, player, 'unchanged', 'cardEnabled2', player); - if (mod != 'unchanged') return mod; - } - return get.filter(evt.filterCard2).apply(this, arguments); - }; - } - if (info.filterOk == undefined) { - this.filterOk = function () { - var evt = _status.event; - var card = get.card(), player = get.player(); - var filter = evt._backup.filterCard; - if (filter && !filter(card, player, evt)) return false; - if (evt._backup.filterOk) return evt._backup.filterOk(); - return true; - }; - } - else this.filterOk = info.filterOk; - if (info.selectCard != undefined) this.selectCard = info.selectCard; - if (info.position != undefined) this.position = info.position; - //if(info.forced!=undefined) this.forced=info.forced; - if (info.complexSelect != undefined) this.complexSelect = info.complexSelect; - if (info.complexCard != undefined) this.complexCard = info.complexCard; - if (info.complexTarget != undefined) this.complexTarget = info.complexTarget; - if (info.ai1 != undefined) this.ai1 = info.ai1; - if (info.ai2 != undefined) this.ai2 = info.ai2; - } - else if (info.viewAs) { - if (info.filterButton != undefined) this.filterButton = get.filter(info.filterButton); - if (info.selectButton != undefined) this.selectButton = info.selectButton; - if (info.filterTarget != undefined) this.filterTarget = get.filter(info.filterTarget); - if (info.selectTarget != undefined) this.selectTarget = info.selectTarget; - if (info.filterCard != undefined) { - if (info.ignoreMod) this.ignoreMod = true; - this.filterCard2 = get.filter(info.filterCard); - this.filterCard = function (card, player, event) { - var evt = event || _status.event; - if (!evt.ignoreMod && player) { - var mod = game.checkMod(card, player, 'unchanged', 'cardEnabled2', player); - if (mod != 'unchanged') return mod; - } - return get.filter(evt.filterCard2).apply(this, arguments); - }; - } - if (info.filterOk == undefined) { - this.filterOk = function () { - var evt = _status.event; - var card = get.card(), player = get.player(); - var filter = evt._backup.filterCard; - if (filter && !filter(card, player, evt)) return false; - if (evt._backup.filterOk) return evt._backup.filterOk(); - return true; - }; - } - else this.filterOk = info.filterOk; - if (info.selectCard != undefined) this.selectCard = info.selectCard; - if (info.position != undefined) this.position = info.position; - //if(info.forced!=undefined) this.forced=info.forced; - if (info.complexSelect != undefined) this.complexSelect = info.complexSelect; - if (info.complexCard != undefined) this.complexCard = info.complexCard; - if (info.complexTarget != undefined) this.complexTarget = info.complexTarget; - if (info.ai1 != undefined) this.ai1 = info.ai1; - if (info.ai2 != undefined) this.ai2 = info.ai2; - } - else { - this.filterButton = info.filterButton ? get.filter(info.filterButton) : undefined; - this.selectButton = info.selectButton; - this.filterTarget = info.filterTarget ? get.filter(info.filterTarget) : undefined; - this.selectTarget = info.selectTarget; - this.filterCard = info.filterCard ? get.filter(info.filterCard) : undefined; - this.selectCard = info.selectCard; - this.position = info.position; - //this.forced=info.forced; - this.complexSelect = info.complexSelect; - this.complexCard = info.complexCard; - this.complexTarget = info.complexTarget; - if (info.ai1 != undefined) this.ai1 = info.ai1; - if (info.ai2 != undefined) this.ai2 = info.ai2; - this.filterOk = info.filterOk; - } - delete this.fakeforce; - } - delete this._cardChoice; - delete this._targetChoice; - delete this._skillChoice; - return this; - } - restore() { - if (this._backup) { - this.filterButton = this._backup.filterButton; - this.selectButton = this._backup.selectButton; - this.filterTarget = this._backup.filterTarget; - this.selectTarget = this._backup.selectTarget; - this.filterCard = this._backup.filterCard; - this.selectCard = this._backup.selectCard; - this.position = this._backup.position; - this.forced = this._backup.forced; - this.fakeforce = this._backup.fakeforce; - this._aiexclude = this._backup._aiexclude; - this.complexSelect = this._backup.complexSelect; - this.complexCard = this._backup.complexCard; - this.complexTarget = this._backup.complexTarget; - this.ai1 = this._backup.ai1; - this.ai2 = this._backup.ai2; - this._cardChoice = this._backup._cardChoice; - this._targetChoice = this._backup._targetChoice; - this._skillChoice = this._backup._skillChoice; - this.filterOk = this._backup.filterOk; - } - delete this.skill; - delete this.ignoreMod; - delete this.filterCard2; - return this; - } - isMine() { - return (this.player && this.player == game.me && !_status.auto && !this.player.isMad() && !game.notMe); - } - isOnline() { - return (this.player && this.player.isOnline()); - } - notLink() { - return this.getParent().name != '_lianhuan' && this.getParent().name != '_lianhuan2'; - } - isPhaseUsing(player) { - var evt = this.getParent('phaseUse'); - if (!evt || evt.name != 'phaseUse') return false; - return !player || player == evt.player; - } - addTrigger(skills, player) { - if (!player || !skills) return this; - let evt = this; - if (typeof skills == 'string') skills = [skills]; - game.expandSkills(skills); - while (true) { - evt = evt.getParent('arrangeTrigger'); - if (!evt || evt.name != 'arrangeTrigger' || !evt.doingList) return this; - const doing = evt.doingList.find(i => i.player === player); - const firstDo = evt.doingList.find(i => i.player === "firstDo"); - const lastDo = evt.doingList.find(i => i.player === "lastDo"); - - skills.forEach(skill => { - const info = lib.skill[skill]; - if (!info.trigger) return; - if (!Object.keys(info.trigger).some(i => { - if (Array.isArray(info.trigger[i])) return info.trigger[i].includes(evt.triggername); - return info.trigger[i] === evt.triggername; - })) return; - - const toadd = { - skill: skill, - player: player, - priority: get.priority(skill), - }; - const map = info.firstDo ? firstDo : info.lastDo ? lastDo : doing; - if (!map) return; - if (map.doneList.some(i => i.skill === toadd.skill && i.player === toadd.player)) return; - if (map.todoList.some(i => i.skill === toadd.skill && i.player === toadd.player)) return; - map.todoList.add(toadd); - if (typeof map.player === 'string') map.todoList.sort((a, b) => (b.priority - a.priority) || (evt.playerMap.indexOf(a) - evt.playerMap.indexOf(b))); - else map.todoList.sort((a, b) => b.priority - a.priority); - }); - } - } - removeTrigger(skills, player) { - if (!player || !skills) return this; - let evt = this; - if (typeof skills == 'string') skills = [skills]; - game.expandSkills(skills); - while (true) { - evt = evt.getParent('arrangeTrigger'); - if (!evt || evt.name != 'arrangeTrigger' || !evt.doingList) return this; - const doing = evt.doingList.find(i => i.player == player); - const firstDo = evt.doingList.find(i => i.player == "firstDo"); - const lastDo = evt.doingList.find(i => i.player == "lastDo"); - - skills.forEach(skill => [doing, firstDo, lastDo].forEach(map => { - if (!map) return; - const toremove = map.todoList.filter(i => i.skill == skill && i.player == player); - if (toremove.length > 0) map.todoList.removeArray(toremove); - })); - } - } - trigger(name) { - if (_status.video) return this; - if ((this.name === 'gain' || this.name === 'lose') && !_status.gameDrawed) return this; - if (name === 'gameDrawEnd') _status.gameDrawed = true; - if (name === 'gameStart') { - lib.announce.publish('gameStart', {}); - if (_status.brawl && _status.brawl.gameStart) _status.brawl.gameStart(); - if (lib.config.show_cardpile) ui.cardPileButton.style.display = ''; - _status.gameStarted = true; - game.showHistory(); - } - if (!lib.hookmap[name] && !lib.config.compatiblemode) return this; - if (!game.players || !game.players.length) return this; - const event = this; - let start = [_status.currentPhase, event.source, event.player, game.me, game.players[0]].find(i => get.itemtype(i) == 'player'); - if (!start) return this; - if (!game.players.includes(start) && !game.dead.includes(start)) start = game.findNext(start); - const firstDo = { - player: "firstDo", - todoList: [], - doneList: [], - }; - const lastDo = { - player: "lastDo", - todoList: [], - doneList: [], - }; - const doingList = []; - const roles = ['player', 'source', 'target', 'global']; - const playerMap = game.players.concat(game.dead).sortBySeat(start); - let player = start; - let allbool = false; - do { - const doing = { - player: player, - todoList: [], - doneList: [], - listAdded: {}, - addList(skill) { - if (!skill) return; - if (Array.isArray(skill)) return skill.forEach(i => this.addList(i)); - if (this.listAdded[skill]) return; - this.listAdded[skill] = true; - - const info = lib.skill[skill]; - const list = info.firstDo ? firstDo.todoList : info.lastDo ? lastDo.todoList : this.todoList; - list.push({ - skill: skill, - player: this.player, - priority: get.priority(skill), - }); - if (typeof list.player == 'string') list.sort((a, b) => (b.priority - a.priority) || (playerMap.indexOf(a) - playerMap.indexOf(b))); - else list.sort((a, b) => b.priority - a.priority); - allbool = true; - } - }; - - const notemp = player.skills.slice(); - for (const j in player.additionalSkills) { - if (!j.startsWith('hidden:')) notemp.addArray(player.additionalSkills[j]); - } - Object.keys(player.tempSkills).filter(skill => { - if (notemp.includes(skill)) return false; - const expire = player.tempSkills[skill]; - if (typeof expire === 'function') return expire(event, player, name); - if (get.objtype(expire) === 'object') return roles.some(role => { - if (role !== 'global' && player !== event[role]) return false; - if (Array.isArray(expire[role])) return expire[role].includes(name); - return expire[role] === name; - }); - }).forEach(skill => { - delete player.tempSkills[skill]; - player.removeSkill(skill); - }); - - if (lib.config.compatiblemode) { - doing.addList(game.expandSkills(player.getSkills('invisible').concat(lib.skill.global)).filter(skill => { - const info = get.info(skill); - if (!info || !info.trigger) return false; - return roles.some(role => { - if (info.trigger[role] === name) return true; - if (Array.isArray(info.trigger[role]) && info.trigger[role].includes(name)) return true; - }); - })); - } - else roles.forEach(role => { - doing.addList(lib.hook.globalskill[role + '_' + name]); - doing.addList(lib.hook[player.playerid + '_' + role + '_' + name]); - }); - delete doing.listAdded; - delete doing.addList; - doingList.push(doing); - player = player.nextSeat; - } while (player && player !== start); - doingList.unshift(firstDo); - doingList.push(lastDo); - // console.log(name,event.player,doingList.map(i=>({player:i.player,todoList:i.todoList.slice(),doneList:i.doneList.slice()}))) - - if (allbool) { - const next = game.createEvent('arrangeTrigger', false, event); - next.setContent('arrangeTrigger'); - next.doingList = doingList; - next._trigger = event; - next.triggername = name; - next.playerMap = playerMap; - event._triggering = next; - } - return this; - } - untrigger(all = true, player) { - const evt = this._triggering; - if (all) { - this._triggered = 5; - if (evt && evt.doingList) { - evt.doingList.forEach(doing => doing.todoList = []); - } - } - else if (player) { - this._notrigger.add(player); - // if(!evt||!evt.doingList) return this; - // const doing=evt.doingList.find(doing=>doing.player==player); - // if(doing) doing.todoList=[]; - } - return this; - } - /** - * @returns {never} - */ - typeAnnotation() { - /** - * @type {Player} - */ - this.source; - /** - * @type {Player} - */ - this.player; - /** - * @type {Player} - */ - this.target; - /** - * @type {Player[]} - */ - this.targets; - /** - * @type {Card} - */ - this.card; - /** - * @type {Card[]} - */ - this.cards; - /** - * @type {string} - */ - this.skill; - /** - * @type {boolean} - */ - this.forced; - /** - * @type {number} - */ - this.num; - /** - * @type {GameEvent} - */ - this._trigger; - /** - * @type {Record} - */ - this._result; - /** - * @type {number} - */ - this.baseDamage; - /** - * @type {Player} - */ - this.customSource; - /** - * @type {number} - */ - this.extraDamage; - /** - * @type {string} - */ - this.nature; - /** - * @type {boolean} - */ - this.notrigger; - /** - * @type {number} - */ - this.original_num; - /** - * @type {boolean} - */ - this.unreal; - throw new Error('Do not call this method'); - } - /** - * 事件转为Promise化 - * - * @returns { GameEventPromise } - */ - toPromise() { - if (!this.#promise) { - this.#promise = new lib.element.GameEventPromise(this); - } - return this.#promise; - } - }, - /** - * 将事件Promise化以使用async异步函数来执行事件。 - * - * 事件Promise化后,需要既能使用await等待事件完成, - * 又需要在执行之前对事件进行配置。 - * - * 所以这个类的实例集成了事件和Promise二者的所有属性, - * 且Promise的原有属性无法被修改,一切对这个类实例的属性修改,删除, - * 再配置等操作都会转发到事件对应的属性中。 - * - * @todo 需要完成异步事件的debugger方法 - * - * @example - * 使用await xx()等待异步事件执行: - * ```js - * await game.xxx().setContent('yyy').set(zzz, 'i'); - * ``` - * 使用await player.xxx()等待异步事件执行: - * ```js - * await player.draw(2); - * game.log('等待', player, '摸牌完成执行log'); - * ``` - */ - GameEventPromise: class extends Promise { - // 我谢谢你,这里是必须有的 - // 否则Promise的方法对其子类无效 - static get [Symbol.species]() { - return Promise; - } - #event; - /** - * @param { GameEvent } event - * @returns { Promise & GameEvent } - */ - constructor(event) { - super(resolve => { - // 设置为异步事件 - event.async = true; - // 事件结束后触发resolve - event.resolve = resolve; - if (!_status.event) return; - // game.createEvent的时候还没立即push到next里 - Promise.resolve().then(() => { - game.executingAsyncEventMap.set(_status.event.toEvent(), (game.executingAsyncEventMap.get(_status.event.toEvent()) || Promise.resolve()).then(() => { - let eventPromise = _status.event.next.find(e => e.toEvent() == event); - // 如果父级事件也是一个异步的话,那应该立即执行这个事件的 - // 如果在AsyncFunction执行过程中在别的位置新建了一个异步事件,那也直接(等会set配置完)执行 - if (eventPromise && (_status.event.content instanceof AsyncFunction || Array.isArray(_status.event.contents))) { - // 异步执行game.loop - // 不直接game.loop(event)是因为需要让别人可以手动set()和setContent() - // 再执行game.loop是因为原有的game.loop被await卡住了, - // 得新执行一个只执行这个异步事件的game.loop - - // 事件自行处理skip情况 - if (event.player && event.player.skipList.includes(event.name)) { - _status.event.trigger(event.name + 'Skipped'); - event.player.skipList.remove(event.name); - if (lib.phaseName.includes(event.name)) event.player.getHistory('skipped').add(event.name); - _status.event.next.remove(eventPromise); - event.finish(); - resolve(); - return eventPromise; - } - - if (_status.event != eventPromise) { - eventPromise.parent = _status.event; - _status.event = eventPromise; - game.getGlobalHistory('everything').push(eventPromise); - } - return game.loop(eventPromise).then(() => { - // 有时候event.finished还是false - return eventPromise; - }); - } - })); - }); - }); - this.#event = event; - return new Proxy(this, { - get(target, prop, receiver) { - const thisValue = Reflect.get(target, prop); - if (thisValue) { - if (typeof thisValue == 'function') { - return thisValue.bind(target); - } - return thisValue; - } - const eventValue = Reflect.get(event, prop); - return eventValue == event ? receiver : eventValue; - }, - set(target, prop, newValue) { - return Reflect.set(event, prop, newValue); - }, - deleteProperty(target, prop) { - return Reflect.deleteProperty(event, prop); - }, - defineProperty(target, prop, attributes) { - return Reflect.defineProperty(event, prop, attributes); - }, - has(target, prop) { - return Reflect.has(event, prop); - }, - ownKeys(target) { - return Reflect.ownKeys(event); - }, - getOwnPropertyDescriptor(target, prop) { - return Reflect.getOwnPropertyDescriptor(event, prop); - }, - }); - } - /** 获取原事件对象 */ - toEvent() { - return this.#event; - } - /** - * 在某个异步事件中调试变量信息 - * - * 注: 在调试步骤中`定义的变量只在当前输入的语句有效` - * - * @example - * 在技能中调试技能content相关的信息 - * ```js - * await event.debugger(); - * ``` - * 在技能中调试触发此技能事件的相关的信息 - * ```js - * await trigger.debugger(); - * ``` - */ - async debugger() { - return new Promise(resolve => { - const runCode = function (event, code) { - try { - // 为了使玩家调试时使用var player=xxx时不报错,故使用var - var { player, _trigger: trigger, _result: result } = event; - return eval(code); - } catch (error) { - return error; - } - }.bind(window); - const inputCallback = inputResult => { - if (inputResult === false) { - resolve(null); - } else { - const obj = runCode(this.toEvent(), inputResult); - alert((!obj || obj instanceof Error) ? String(obj) : get.stringify(obj)); - game.promises.prompt('debugger调试').then(inputCallback); - } - }; - game.promises.prompt('debugger调试').then(inputCallback); - }); - } - }, - Dialog: class extends HTMLDivElement { - constructor() { - let hidden = false; - let noTouchScroll = false; - let forceButton = false; - let noForceButton = false; - /** @type {this} */ - const dialog = ui.create.div('.dialog'); - Object.setPrototypeOf(dialog, lib.element.Dialog.prototype); - dialog.contentContainer = ui.create.div('.content-container', dialog); - dialog.content = ui.create.div('.content', dialog.contentContainer); - dialog.bar1 = ui.create.div('.bar.top', dialog); - dialog.bar2 = ui.create.div('.bar.bottom', dialog); - dialog.buttons = []; - Array.from(arguments).forEach(argument => { - if (typeof argument == 'boolean') dialog.static = argument; - else if (argument == 'hidden') hidden = true; - else if (argument == 'notouchscroll') noTouchScroll = true; - else if (argument == 'forcebutton') forceButton = true; - else if (argument == 'noforcebutton') noForceButton = true; - else dialog.add(argument); - }); - if (!hidden) dialog.open(); - if (!lib.config.touchscreen) dialog.contentContainer.onscroll = ui.update; - if (!noTouchScroll) { - dialog.contentContainer.ontouchstart = ui.click.dialogtouchStart; - dialog.contentContainer.ontouchmove = ui.click.touchScroll; - dialog.contentContainer.style.webkitOverflowScrolling = 'touch'; - dialog.ontouchstart = ui.click.dragtouchdialog; - } - if (noForceButton) dialog.noforcebutton = true; - else if (forceButton) { - dialog.forcebutton = true; - dialog.classList.add('forcebutton'); - } - return dialog; - } - add(item, noclick, zoom) { - if (typeof item == 'string') { - if (item.startsWith('###')) { - var items = item.slice(3).split('###'); - this.add(items[0], noclick, zoom); - this.addText(items[1], items[1].length <= 20, zoom); - } - else if (noclick) { - var strstr = item; - item = ui.create.div('', this.content); - item.innerHTML = strstr; - } - else { - item = ui.create.caption(item, this.content); - } - } - else if (['div', 'fragment'].includes(get.objtype(item))) { - this.content.appendChild(item); - } - else if (get.itemtype(item) == 'cards') { - var buttons = ui.create.div('.buttons', this.content); - if (zoom) buttons.classList.add('smallzoom'); - this.buttons = this.buttons.concat(ui.create.buttons(item, 'card', buttons, noclick)); - } - else if (get.itemtype(item) == 'players') { - var buttons = ui.create.div('.buttons', this.content); - if (zoom) buttons.classList.add('smallzoom'); - this.buttons = this.buttons.concat(ui.create.buttons(item, 'player', buttons, noclick)); - } - else if (item[1] == 'textbutton') { - ui.create.textbuttons(item[0], this, noclick); - } - else { - var buttons = ui.create.div('.buttons', this.content); - if (zoom) buttons.classList.add('smallzoom'); - this.buttons = this.buttons.concat(ui.create.buttons(item[0], item[1], buttons, noclick)); - } - if (this.buttons.length) { - if (this.forcebutton !== false) this.forcebutton = true; - if (this.buttons.length > 3 || (zoom && this.buttons.length > 5)) { - this.classList.remove('forcebutton-auto'); - } - else if (!this.noforcebutton) { - this.classList.add('forcebutton-auto'); - } - } - ui.update(); - return item; - } - addText(str, center) { - if (str && str.startsWith('' + str + ''); - } - else { - this.add('
' + str + '
'); - } - return this; - } - addSmall(item, noclick) { - return this.add(item, noclick, true); - } - addAuto(content) { - if (content && content.length > 4 && !this._hovercustomed) { - this.addSmall(content); - } - else { - this.add(content); - } - } - open() { - if (this.noopen) return; - for (var i = 0; i < ui.dialogs.length; i++) { - if (ui.dialogs[i] == this) { - this.show(); - this.refocus(); - ui.dialogs.remove(this); - ui.dialogs.unshift(this); - ui.update(); - return this; - } - if (ui.dialogs[i].static) ui.dialogs[i].unfocus(); - else ui.dialogs[i].hide(); - } - ui.dialog = this; - var translate; - if (lib.config.remember_dialog && lib.config.dialog_transform && !this.classList.contains('fixed')) { - translate = lib.config.dialog_transform; - this._dragtransform = translate; - this.style.transform = 'translate(' + translate[0] + 'px,' + translate[1] + 'px) scale(0.8)'; - } - else { - this.style.transform = 'scale(0.8)'; - } - this.style.transitionProperty = 'opacity,transform'; - this.style.opacity = 0; - ui.arena.appendChild(this); - ui.dialogs.unshift(this); - ui.update(); - ui.refresh(this); - if (lib.config.remember_dialog && lib.config.dialog_transform && !this.classList.contains('fixed')) { - this.style.transform = 'translate(' + translate[0] + 'px,' + translate[1] + 'px) scale(1)'; - } - else { - this.style.transform = 'scale(1)'; - } - this.style.opacity = 1; - var that = this; - setTimeout(function () { - that.style.transitionProperty = ''; - }, 500); - return this; - } - close() { - ui.dialogs.remove(this); - this.delete(); - if (ui.dialogs.length > 0) { - ui.dialog = ui.dialogs[0]; - ui.dialog.show(); - ui.dialog.refocus(); - ui.update(); - } - // if(ui.arenalog){ - // ui.arenalog.classList.remove('withdialog'); - // } - return this; - } - setCaption(str) { - this.querySelector('.caption').innerHTML = str; - return this; - } - }, - Control: class extends HTMLDivElement { - constructor() { - const nc = !ui.control.querySelector('div:not(.removing):not(.stayleft)'); - const controls = Array.isArray(arguments[0]) ? arguments[0] : Array.from(arguments); - const control = ui.create.div('.control'); - Object.setPrototypeOf(control, lib.element.Control.prototype); - ui.control.insertBefore(control, _status.createControl || ui.confirm); - controls.forEach(argument => { - if (argument == 'nozoom') return; - if (typeof argument == 'function') control.custom = argument; - else if (argument == 'stayleft') { - control.stayleft = true; - control.classList.add('stayleft'); - } - else control.add(argument); - }); - ui.controls.unshift(control); - if (nc) ui.control.animate('nozoom', 100); - if (control.childNodes.length) { - control.style.transition = 'opacity 0.5s'; - control.animate('controlpressdownx', 500); - ui.refresh(control); - if (!control.stayleft) control.style.transform = `translateX(-${control.offsetWidth / 2}px)`; - control.style.opacity = 1; - ui.refresh(control); - control.style.transition = ''; - } - - control.addEventListener(lib.config.touchscreen ? 'touchend' : 'click', ui.click.control2); - - if (lib.config.button_press) { - control.addEventListener(lib.config.touchscreen ? 'touchstart' : 'mousedown', function () { - if (this.classList.contains('disabled')) return; - this.classList.add('controlpressdown'); - if (typeof this._offset == 'number') this.style.transform = `translateX(${this._offset}px) scale(0.97)`; - }); - control.addEventListener(lib.config.touchscreen ? 'touchend' : 'mouseup', function () { - this.classList.remove('controlpressdown'); - if (typeof this._offset == 'number') this.style.transform = `translateX(${this._offset}px)`; - }); - } - - ui.updatec(); - return control; - } - open() { - ui.control.insertBefore(this, _status.createControl || ui.confirm); - ui.controls.unshift(this); - if (this.childNodes.length) { - this.style.transition = 'opacity 0.5s'; - ui.refresh(this); - this.style.transform = 'translateX(-' + (this.offsetWidth / 2) + 'px)'; - this.style.opacity = 1; - ui.refresh(this); - this.style.transition = ''; - } - else { - this.animate('controlpressdownx', 500); - } - ui.updatec(); - return this; - } - add(item) { - var node = document.createElement('div'); - this.appendChild(node); - node.link = item; - node.innerHTML = get.translation(item); - node.addEventListener(lib.config.touchscreen ? 'touchend' : 'click', ui.click.control); - } - close() { - this.animate('controlpressdownx', 500); - - ui.controls.remove(this); - this.delete(); - - setTimeout(ui.updatec, 100); - - - if (ui.confirm == this) delete ui.confirm; - if (ui.skills == this) delete ui.skills; - if (ui.skills2 == this) delete ui.skills2; - if (ui.skills3 == this) delete ui.skills3; - } - replace() { - // this.animate('controlpressdownx',500); - if (this.replaceTransition === false) { - this.style.transitionProperty = 'none'; - ui.refresh(this); - } - - while (this.childNodes.length) this.firstChild.remove(); - var i, controls; - if (Array.isArray(arguments[0])) controls = arguments[0]; - else controls = arguments; - delete this.custom; - for (i = 0; i < controls.length; i++) { - if (typeof controls[i] == 'function') { - this.custom = controls[i]; - } - else { - this.add(controls[i]); - } - } - if (this.childNodes.length) { - var width = 0; - for (i = 0; i < this.childNodes.length; i++) width += this.childNodes[i].offsetWidth; - ui.refresh(this); - this.style.width = width + 'px'; - } - ui.updatec(); - if (this.replaceTransition === false) { - var that = this; - setTimeout(function () { - that.style.transitionProperty = ''; - }, 200); - } - return this; - } - }, - Client: class { - /** - * @param {NodeWS | InstanceType} ws - */ - constructor(ws) { - this.ws = ws; - this.id = ws.wsid || get.id(); - this.closed = false; - } - send() { - if (this.closed) return this; - var args = Array.from(arguments); - if (typeof args[0] == 'function') { - args.unshift('exec'); - } - for (var i = 1; i < args.length; i++) { - args[i] = get.stringifiedResult(args[i]); - } - try { - this.ws.send(JSON.stringify(args)); - } - catch (e) { - this.ws.close(); - } - return this; - } - close() { - lib.node.clients.remove(this); - lib.node.observing.remove(this); - if (ui.removeObserve && !lib.node.observing.length) { - ui.removeObserve.remove(); - delete ui.removeObserve; - } - this.closed = true; - if (_status.waitingForPlayer) { - for (var i = 0; i < game.connectPlayers.length; i++) { - if (game.connectPlayers[i].playerid == this.id) { - game.connectPlayers[i].uninitOL(); - delete game.connectPlayers[i].playerid; - } - } - if (game.onlinezhu == this.id) { - game.onlinezhu = null; - } - game.updateWaiting(); - } - else if (lib.playerOL[this.id]) { - var player = lib.playerOL[this.id]; - player.setNickname(player.nickname + ' - 离线'); - game.broadcast(function (player) { - player.setNickname(player.nickname + ' - 离线'); - }, player); - player.unwait('ai'); - } - - if (window.isNonameServer) { - document.querySelector('#server_count').innerHTML = lib.node.clients.length; - } - return this; - } - }, - NodeWS: class { - /** - * @param {string} id - */ - constructor(id) { - this.wsid = id; - } - send(message) { - game.send('server', 'send', this.wsid, message); - } - on(type, func) { - this['on' + type] = func; - } - close() { - game.send('server', 'close', this.wsid); - } - }, + content: Element.Content, + contents: Element.Contents, + Player: Element.Player, + Card: Element.Card, + VCard: Element.VCard, + Button: Element.Button, + GameEvent: Element.GameEvent, + GameEventPromise: Element.GameEventPromise, + Dialog: Element.Dialog, + Control: Element.Control, + Client: Element.Client, + NodeWS: Element.NodeWS, ws: { onopen: function () { if (_status.connectCallback) { @@ -13664,7 +11432,7 @@ export class Library extends Uninstantable { static message = { server: { init: function (version, config, banned_info) { - if (lib.node.banned.contains(banned_info)) { + if (lib.node.banned.includes(banned_info)) { this.send('denied', 'banned'); } else if (config.id && lib.playerOL && lib.playerOL[config.id]) { diff --git a/noname/util/index.js b/noname/util/index.js index 8d351134f..7d27f1050 100644 --- a/noname/util/index.js +++ b/noname/util/index.js @@ -14,8 +14,8 @@ export class Uninstantable { /** * - * @param {number} ms - * @returns {Promise} + * @param { number } ms + * @returns { Promise } */ export function delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms));