重新拆分lib.element

This commit is contained in:
shijian 2023-12-28 00:43:55 +08:00
parent ea754c9642
commit ba438ef817
16 changed files with 2429 additions and 2277 deletions

View File

@ -2229,10 +2229,13 @@ export class Get extends Uninstantable {
} }
/** /**
* @template T * @template T
* @type {{ * @overload
* (key: T) => GameEvent[T]; * @param {T} key
* () => GameEvent; * @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 event(key) { return key ? _status.event[key] : _status.event }
static player() { return _status.event.player } static player() { return _status.event.player }

View File

@ -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;
}
}

View File

@ -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]) + '<span style="font-family:xinwei"> </span><span style="font-family:xinwei">' + cardnum + '</span>';
}
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 = ' <span class="cardtag">';
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<tags.length-1) tagstr+=' ';
}
tagstr += '</span>';
this.node.range.innerHTML += tagstr;
}
}
return this;
}
updateTransform(bool, delay) {
if (delay) {
var that = this;
setTimeout(function () {
that.updateTransform(that.classList.contains('selected'));
}, delay);
}
else {
if (_status.event.player != game.me) return;
if (this._transform && this.parentNode && this.parentNode.parentNode &&
this.parentNode.parentNode.parentNode == ui.me &&
(!_status.mousedown || _status.mouseleft) &&
(!this.parentNode.parentNode.classList.contains('scrollh') || (game.layout == 'long2' || game.layout == 'nova'))) {
if (bool) {
this.style.transform = this._transform + ' translateY(-20px)';
}
else {
this.style.transform = this._transform || '';
}
}
}
}
aiexclude() {
_status.event._aiexclude.add(this);
}
//为此牌添加知情者。参数可为数组,若参数为字符串'everyone',则所有玩家均为知情者。
addKnower(player) {
if (!this._knowers) {
this._knowers = [];
}
if (typeof player == 'string') {
this._knowers.add(player);
} else {
let type = get.itemtype(player);
if (type == 'player') {
this._knowers.add(player.playerid);
} else if (type == 'players') {
player.forEach(p => 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));
}
}

View File

@ -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<typeof import('ws').WebSocket>} 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;
}
}

View File

@ -7,7 +7,7 @@ import { UI as ui } from '../../ui/index.js';
import { GNC as gnc } from '../../gnc/index.js'; import { GNC as gnc } from '../../gnc/index.js';
// 未来再改 // 未来再改
export default { export const Content = {
emptyEvent: async (event) => { emptyEvent: async (event) => {
event.trigger(event.name); event.trigger(event.name);
}, },

View File

@ -6,7 +6,7 @@ import { status as _status } from '../../status/index.js';
import { UI as ui } from '../../ui/index.js'; import { UI as ui } from '../../ui/index.js';
import { GNC as gnc } from '../../gnc/index.js'; import { GNC as gnc } from '../../gnc/index.js';
export default { export const Contents = {
phase: [ phase: [
async (event, _trigger, player) => { async (event, _trigger, player) => {
// 初始化阶段列表 // 初始化阶段列表

View File

@ -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;
}
}

View File

@ -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('<div')) this.add(str);
else if (center !== false) {
this.add('<div class="text center">' + str + '</div>');
}
else {
this.add('<div class="text">' + str + '</div>');
}
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;
}
}

View File

@ -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<typeof this.hasHandler>[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<typeof this.hasHandler>[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<string>}`} [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<typeof this.hasHandler>[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> | 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<string, any>}
*/
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');
}
}

View File

@ -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> & 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);
});
}
}

View File

@ -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";

View File

@ -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);
}
}

View File

@ -5,9 +5,9 @@ import { Library as lib } from "../index.js";
import { status as _status } from '../../status/index.js'; import { status as _status } from '../../status/index.js';
import { UI as ui } from '../../ui/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] * @param {true} [noclick]
*/ */
// @ts-ignore // @ts-ignore
@ -17,7 +17,7 @@ export default class extends HTMLDivElement {
*/ */
// @ts-ignore // @ts-ignore
const player = ui.create.div('.player', position); const player = ui.create.div('.player', position);
Object.setPrototypeOf(player, lib.element.Player.prototype); Object.setPrototypeOf(player, Player.prototype);
player.build(noclick); player.build(noclick);
return player; return player;
} }

View File

@ -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<string, any>}
*/
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);
}
}

File diff suppressed because it is too large Load Diff