Merge pull request #767 from nofficalfs/Dev-Enhancement-DivideMore

Fix `get.copy`
This commit is contained in:
Spmario233 2024-01-06 23:35:46 +08:00 committed by GitHub
commit 43d1fb4394
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1063 additions and 995 deletions

View File

@ -0,0 +1,221 @@
export class DynamicStyle {
/**
* Object of style
* 表示样式的对象
*
* @typedef {Record<string, string | number>} StyleObject
*/
/**
* Rule to record style info.
* 用于记录样式信息的规则
*
* @typedef {[string, StyleObject]} Rule
*/
/**
* Type used to declare the place to store css info.
* 用来存CSS信息的空间的类型
*
* @typedef {object} DynamicStyleCache
* @property {Rule[]} rules 记录的规则
* @property {HTMLStyleElement} style 全局Style标签
* @property {CSSStyleSheet} sheet Style标签的Sheet
*/
/**
* Place to store css info.
* 存CSS信息的空间
*
* @type {DynamicStyleCache}
*/
#cache;
/**
* Initialize dynamicStyle.
* 初始化数据
*/
constructor() {
/**
* @type {DynamicStyleCache}
*/
const cache = Object.create(null);
cache.rules = [];
cache.style = document.createElement("style");
cache.style.id = "game.dynamicStyle";
document.head.appendChild(cache.style);
cache.sheet = cache.style.sheet;
this.#cache = cache;
}
/**
* Turn the Object Style to string format.
* 将给定的对象样式转换成字符串的形式
*
* @param {StyleObject} style 给定的对象样式
* @returns {string} 样式的字符串形式
*/
translate(style) {
return Object.entries(style).map(item =>
`${item[0].replace(/([A-Z])/g, match =>
`-${match.toLowerCase()}`)}: ${item[1]};`).join(" ");
}
/**
* Generate the common css selector.
* 生成标准的CSS样式
*
* @param {string} name 选择器
* @param {StyleObject} style 对象样式
* @returns {string} 标准的CSS样式
*/
generate(name, style) {
return `${name} { ${this.translate(style)} }`;
}
/**
* Determine the selector is in rules.
* 检查是否存在对应选择器的规则
*
* @param {string} name 选择器
* @returns {boolean}
*/
has(name) {
return this.#cache.rules.some(item => item[0] === name);
}
/**
* Get the style of given selector, or return null.
* 获得对应选择器的样式对象若不存在则返回`null`
*
* @param {string} name 选择器
* @returns {?StyleObject}
*/
get(name) {
const result = this.find(item => item[0] === name);
return result ? result[1] : null;
}
/**
* Callback of `DynamicStyle#find`, getting the rule wanted.
* `DynamicStyle#find`的回调函数用于获取符合要求的规则
*
* @callback FindCallback
* @param {Rule} rule 样式规则
* @param {number} index 样式编号
* @param {Rule[]} rules 规则集
* @returns {boolean}
*/
/**
* Get the rule wanted by given function.
* 通过给定的函数获取符合要求的规则
*
* @param {FindCallback} fn 用于检查的函数
* @returns {Rule}
*/
find(fn) {
return this.#cache.rules.find(fn);
}
/**
* Length of rules.
* 规则集的长度
*
* @returns {number}
*/
size() {
return this.#cache.rules.length;
}
/**
* Get the index of given selector, or return `-1`.
* 获得对应选择器的位置若不存在则返回`-1`
*
* @param {string} name 选择器
* @returns {number}
*/
indexOf(name) {
for (let i = 0; i < this.#cache.rules.length; ++i) {
if (name === this.#cache.rules[i][0]) return i;
}
return -1;
}
// 后面部分就不说明了,可以顾名思义
/**
* @param {string} name 选择器
* @param {StyleObject} style 要添加的样式对象
* @returns {boolean} 添加的结果`true`则添加成功`false`则添加失败
*/
add(name, style) {
return this.update(name, this.has(name) ? Object.assign({}, this.get(name), style) : style);
}
/**
* @param {Record<string, StyleObject>} object `name: style`存储的映射
* @returns {boolean[]} 添加的结果`true`则添加成功`false`则添加失败
*/
addObject(object) {
return Object.entries(object).map(item => this.add(item[0], item[1]));
}
/**
* @param {string} name 要移除规则的选择器
* @returns {boolean} 移除的结果`true`则移除成功`false`则移除失败
*/
remove(name) {
if (!this.has(name)) return false;
try {
const index = this.indexOf(name);
this.#cache.rules.splice(index, 1);
this.#cache.sheet.deleteRule(index);
return true;
}
catch (e) {
console.log(e);
return false;
}
}
/**
* @param {string} name 要移除规则的选择器
* @param {string[]} styles 要移除的样式
* @returns {boolean} 移除的结果`true`则移除成功`false`则移除失败
*/
removeStyles(name, styles) {
if (!this.has(name)) return false;
const style = this.get(name);
styles.forEach(styleName => {
delete style[styleName];
});
return this.update(name, style);
}
/**
* 添加或修改一个规则所对应的样式
*
* @param {string} name 要变更规则的选择器
* @param {StyleObject} style 变更规则的样式
* @returns {boolean} 更新的结果`true`则更新成功`false`则更新失败
*/
update(name, style) {
try {
if (this.has(name)) {
const index = this.indexOf(name);
this.#cache.sheet.deleteRule(index);
this.#cache.sheet.insertRule(this.generate(name, style), index);
this.#cache.rules[index] = [name, style];
} else {
const index = this.#cache.rules.length;
this.#cache.rules.push([name, style]);
this.#cache.sheet.insertRule(this.generate(name, style), index);
}
return true;
}
catch (e) {
console.log(e);
return false;
}
}
}

View File

@ -18,6 +18,9 @@ import { UI as ui } from '../ui/index.js';
import { GNC as gnc } from '../gnc/index.js';
import { userAgent, Uninstantable, GeneratorFunction, AsyncFunction, delay } from "../util/index.js";
import { DynamicStyle } from "./dynamic-style/index.js";
import { GamePromises } from "./promises.js";
export class Game extends Uninstantable {
static online = false;
static onlineID = null;
@ -38,98 +41,7 @@ export class Game extends Uninstantable {
static phaseNumber = 0;
static roundNumber = 0;
static shuffleNumber = 0;
static promises = {
/**
* 模仿h5的prompt用于显示可提示用户进行输入的对话框
*
* : 由于参数列表是随意的在这里我准备限制一下这个函数的参数顺序
*
* @type {{
* (title: string): Promise<string | false>;
* (title: string, forced: true): Promise<string>;
* (alertOption: 'alert', title: string): Promise<true>;
* }}
*
* @param { string } title 设置prompt标题与input内容
* @param { boolean } [forced] 为true的话将没有"取消按钮"
* @param { string } alertOption 设置prompt是否模拟alert
* @example
* ```js
* // 只设置标题(但是input的初始值就变成了undefined)
* game.promises.prompt('###prompt标题').then(value => console.log(value));
* // 设置标题和input初始内容
* game.promises.prompt('###prompt标题###input初始内容').then(value => console.log(value));
* ```
* @returns { Promise<string> }
*/
// @ts-ignore
prompt(alertOption, title, forced) {
return new Promise((resolve, reject) => {
if (alertOption != 'alert') {
// @ts-ignore
forced = title || false;
title = alertOption;
game.prompt(title, forced, resolve);
} else {
game.prompt(title, alertOption, resolve);
}
});
},
/**
* 模仿h5的alert用于显示信息的对话框
*
* @param { string } title
* @example
* ```js
* await game.promises.alert('弹窗内容');
* ```
* @returns { Promise<true> }
*/
alert(title) {
return new Promise((resolve, reject) => {
game.prompt(title, 'alert', resolve);
});
},
// 读写函数promises化(不用考虑其对应函数是否存在)
download(url, folder, dev, onprogress) {
return new Promise((resolve, reject) => {
game.download(url, folder, resolve, reject, dev, onprogress);
});
},
readFile(filename) {
return new Promise((resolve, reject) => {
game.readFile(filename, resolve, reject);
});
},
readFileAsText(filename) {
return new Promise((resolve, reject) => {
game.readFileAsText(filename, resolve, reject);
});
},
writeFile(data, path, name) {
return (new Promise((resolve, reject) => {
game.writeFile(data, path, name, resolve);
})).then(result => {
return new Promise((resolve, reject) => {
if (result instanceof Error) {
reject(result);
} else {
resolve(result);
}
});
});
},
ensureDirectory(list, callback, file) {
return new Promise((resolve, reject) => {
game.ensureDirectory(list, callback, file).then(resolve).catch(reject);
});
},
createDir(directory) {
return new Promise((resolve, reject) => {
game.createDir(directory, resolve, reject);
});
},
}
static promises = GamePromises;
static globalEventHandlers = new class {
constructor() {
this._handlers = {};
@ -361,223 +273,7 @@ export class Game extends Uninstantable {
* textAlign: "center"
* });
*/
static dynamicStyle = new class {
/**
* Object of style
* 表示样式的对象
*
* @typedef {Record<string, string | number>} StyleObject
*/
/**
* Rule to record style info.
* 用于记录样式信息的规则
*
* @typedef {[string, StyleObject]} Rule
*/
/**
* Type used to declare the place to store css info.
* 用来存CSS信息的空间的类型
*
* @typedef {object} DynamicStyleCache
* @property {Rule[]} rules 记录的规则
* @property {HTMLStyleElement} style 全局Style标签
* @property {CSSStyleSheet} sheet Style标签的Sheet
*/
/**
* Initialize dynamicStyle.
* 初始化数据
*/
constructor() {
/**
* @type {DynamicStyleCache}
*/
let cache = Object.create(null);
cache.rules = new Array;
cache.style = document.createElement("style");
cache.style.id = "game.dynamicStyle";
document.head.appendChild(cache.style);
cache.sheet = cache.style.sheet;
/**
* Place to store css info.
* 存CSS信息的空间
*
* @type {DynamicStyleCache}
*/
this._cache = cache;
}
/**
* Turn the Object Style to string format.
* 将给定的对象样式转换成字符串的形式
*
* @param {StyleObject} style 给定的对象样式
* @returns {string} 样式的字符串形式
*/
translate(style) {
return Object.entries(style).map(item =>
`${item[0].replace(/([A-Z])/g, match =>
`-${match.toLowerCase()}`)}: ${item[1]};`).join(" ");
}
/**
* Generate the common css selector.
* 生成标准的CSS样式
*
* @param {string} name 选择器
* @param {StyleObject} style 对象样式
* @returns {string} 标准的CSS样式
*/
generate(name, style) {
return `${name} { ${this.translate(style)} }`;
}
/**
* Determine the selector is in rules.
* 检查是否存在对应选择器的规则
*
* @param {string} name 选择器
* @returns {boolean}
*/
has(name) {
return this._cache.rules.some(item => item[0] == name);
}
/**
* Get the style of given selector, or return null.
* 获得对应选择器的样式对象若不存在则返回`null`
*
* @param {string} name 选择器
* @returns {?StyleObject}
*/
get(name) {
const result = this.find(item => item[0] == name);
return result ? result[1] : null;
}
/**
* Callback of `DynamicStyle#find`, getting the rule wanted.
* `DynamicStyle#find`的回调函数用于获取符合要求的规则
*
* @callback FindCallback
* @param {Rule} rule 样式规则
* @param {number} index 样式编号
* @param {Rule[]} rules 规则集
* @returns {boolean}
*/
/**
* Get the rule wanted by given function.
* 通过给定的函数获取符合要求的规则
*
* @param {FindCallback} fn 用于检查的函数
* @returns {?StyleObject}
*/
find(fn) {
return this._cache.rules.find(fn);
}
/**
* Length of rules.
* 规则集的长度
*
* @returns {number}
*/
size() {
return this._cache.rules.length;
}
/**
* Get the index of given selector, or return `-1`.
* 获得对应选择器的位置若不存在则返回`-1`
*
* @param {string} name 选择器
* @returns {number}
*/
indexOf(name) {
for (let i = 0; i < this._cache.rules.length; ++i) {
if (name == this._cache.rules[i][0]) return i;
}
return -1;
}
// 后面部分就不说明了,可以顾名思义
/**
* @param {string} name 选择器
* @param {StyleObject} style 要添加的样式对象
* @returns {boolean} 添加的结果`true`则添加成功`false`则添加失败
*/
add(name, style) {
return this.update(name, this.has(name) ? Object.assign({}, this.get(name), style) : style);
}
/**
* @param {Record<string, StyleObject>} object `name: style`存储的映射
* @returns {boolean} 添加的结果`true`则添加成功`false`则添加失败
*/
addObject(object) {
return Object.entries(object).map(item => this.add(item[0], item[1]));
}
/**
* @param {string} name 要移除规则的选择器
* @returns {boolean} 移除的结果`true`则移除成功`false`则移除失败
*/
remove(name) {
if (!this.has(name)) return false;
try {
const index = this.indexOf(name);
this._cache.rules.splice(index, 1);
this._cache.sheet.deleteRule(index);
return true;
}
catch (e) {
console.log(e);
return false;
}
}
/**
* @param {string} name 要移除规则的选择器
* @param {string[]} styles 要移除的样式
* @returns {boolean} 移除的结果`true`则移除成功`false`则移除失败
*/
removeStyles(name, styles) {
if (!this.has(name)) return false;
const style = this.get(name);
styles.forEach(styleName => {
delete style[styleName];
});
return this.update(name, style);
}
/**
* 添加或修改一个规则所对应的样式
*
* @param {string} name 要变更规则的选择器
* @param {StyleObject} style 变更规则的样式
* @returns {boolean} 更新的结果`true`则更新成功`false`则更新失败
*/
update(name, style) {
try {
if (this.has(name)) {
const index = this.indexOf(name);
this._cache.sheet.deleteRule(index);
this._cache.sheet.insertRule(this.generate(name, style), index);
this._cache.rules[index] = [name, style];
} else {
const index = this._cache.rules.length;
this._cache.rules.push([name, style]);
this._cache.sheet.insertRule(this.generate(name, style), index);
}
return true;
}
catch (e) {
console.log(e);
return false;
}
}
}
static dynamicStyle = new DynamicStyle()
/**
* Add a background music to the config option
*

95
noname/game/promises.js Normal file
View File

@ -0,0 +1,95 @@
import { Uninstantable } from "../util/index.js";
import { game, Game } from "./index.js";
export class GamePromises extends Uninstantable {
/**
* 模仿h5的prompt用于显示可提示用户进行输入的对话框
*
* : 由于参数列表是随意的在这里我准备限制一下这个函数的参数顺序
*
* @type {{
* (title: string): Promise<string | false>;
* (title: string, forced: true): Promise<string>;
* (alertOption: 'alert', title: string): Promise<true>;
* }}
*
* @param { string } title 设置prompt标题与input内容
* @param { boolean } [forced] 为true的话将没有"取消按钮"
* @param { string } alertOption 设置prompt是否模拟alert
* @example
* ```js
* // 只设置标题(但是input的初始值就变成了undefined)
* game.promises.prompt('###prompt标题').then(value => console.log(value));
* // 设置标题和input初始内容
* game.promises.prompt('###prompt标题###input初始内容').then(value => console.log(value));
* ```
* @returns { Promise<string> }
*/
// @ts-ignore
static prompt(alertOption, title, forced) {
return new Promise((resolve, reject) => {
if (alertOption !== 'alert') {
// @ts-ignore
forced = title || false;
title = alertOption;
game.prompt(title, forced, resolve);
} else {
game.prompt(title, alertOption, resolve);
}
});
}
/**
* 模仿h5的alert用于显示信息的对话框
*
* @param { string } title
* @example
* ```js
* await game.promises.alert('弹窗内容');
* ```
* @returns { Promise<true> }
*/
static alert(title) {
return new Promise((resolve, reject) => {
game.prompt(title, 'alert', resolve);
});
}
// 读写函数promises化(不用考虑其对应函数是否存在)
static download(url, folder, dev, onprogress) {
return new Promise((resolve, reject) => {
game.download(url, folder, resolve, reject, dev, onprogress);
});
}
static readFile(filename) {
return new Promise((resolve, reject) => {
game.readFile(filename, resolve, reject);
});
}
static readFileAsText(filename) {
return new Promise((resolve, reject) => {
game.readFileAsText(filename, resolve, reject);
});
}
static writeFile(data, path, name) {
return (new Promise((resolve, reject) => {
game.writeFile(data, path, name, resolve);
})).then(result => {
return new Promise((resolve, reject) => {
if (result instanceof Error) {
reject(result);
} else {
resolve(result);
}
});
});
}
static ensureDirectory(list, callback, file) {
return new Promise((resolve, reject) => {
game.ensureDirectory(list, callback, file).then(resolve).catch(reject);
});
}
static createDir(directory) {
return new Promise((resolve, reject) => {
game.createDir(directory, resolve, reject);
});
}
}

View File

@ -6,441 +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 class Is extends Uninstantable {
/**
* 判断是否为进攻坐骑
* @param { Card | VCard } card
* @param { false | Player } [player]
* @returns { boolean }
*/
static attackingMount(card, player) {
const subtype = get.subtype(card, player);
if (subtype == 'equip4') return true;
else if (subtype == 'equip6') {
const info = get.info(card, player), distance = info.distance;
if (!distance) return false;
if (distance.globalFrom && !info.notMount) return true;
}
return false;
}
/**
* 判断是否为防御坐骑
* @param { Card | VCard } card
* @param { false | Player } [player]
* @returns { boolean }
*/
static defendingMount(card, player) {
const subtype = get.subtype(card, player);
if (subtype == 'equip3') return true;
else if (subtype == 'equip6') {
const info = get.info(card, player), distance = info.distance;
if (!distance) return false;
if (distance.globalTo && !info.notMount) return true;
}
return false;
}
/**
* 判断坐骑栏是否被合并
* @returns { boolean }
*/
static mountCombined() {
if (lib.configOL.mount_combine) {
return lib.configOL.mount_combine;
}
else if (typeof _status.mountCombined != 'boolean') {
_status.mountCombined = lib.config.mount_combine;
}
return _status.mountCombined;
}
/**
* 判断传入的参数的属性是否相同参数可以为卡牌卡牌信息属性等
* @param {...} infos 要判断的属性列表
* @param {boolean} every 是否判断每一个传入的属性是否完全相同而不是存在部分相同
*/
static sameNature() {
let processedArguments = [], every = false;
Array.from(arguments).forEach(argument => {
if (typeof argument == 'boolean') every = argument;
else if (argument) processedArguments.push(argument);
});
if (!processedArguments.length) return true;
if (processedArguments.length == 1) {
const argument = processedArguments[0];
if (!Array.isArray(argument)) return false;
if (!argument.length) return true;
if (argument.length == 1) return false;
processedArguments = argument;
}
const naturesList = processedArguments.map(card => {
if (typeof card == 'string') return card.split(lib.natureSeparator);
else if (Array.isArray(card)) return card;
return get.natureList(card || {});
});
const testingNaturesList = naturesList.slice(0, naturesList.length - 1);
if (every) return testingNaturesList.every((natures, index) => naturesList.slice(index + 1).every(testingNatures => testingNatures.length == natures.length && testingNatures.every(nature => natures.includes(nature))));
return testingNaturesList.every((natures, index) => {
const comparingNaturesList = naturesList.slice(index + 1);
if (natures.length) return natures.some(nature => comparingNaturesList.every(testingNatures => testingNatures.includes(nature)));
return comparingNaturesList.every(testingNatures => !testingNatures.length);
});
}
/**
* 判断传入的参数的属性是否不同参数可以为卡牌卡牌信息属性等
* @param ...infos 要判断的属性列表
* @param every {boolean} 是否判断每一个传入的属性是否完全不同而不是存在部分不同
*/
static differentNature() {
let processedArguments = [], every = false;
Array.from(arguments).forEach(argument => {
if (typeof argument == 'boolean') every = argument;
else if (argument) processedArguments.push(argument);
});
if (!processedArguments.length) return false;
if (processedArguments.length == 1) {
const argument = processedArguments[0];
if (!Array.isArray(argument)) return true;
if (!argument.length) return false;
if (argument.length == 1) return true;
processedArguments = argument;
}
const naturesList = processedArguments.map(card => {
if (typeof card == 'string') return card.split(lib.natureSeparator);
else if (Array.isArray(card)) return card;
return get.natureList(card || {});
});
const testingNaturesList = naturesList.slice(0, naturesList.length - 1);
if (every) return testingNaturesList.every((natures, index) => naturesList.slice(index + 1).every(testingNatures => testingNatures.every(nature => !natures.includes(nature))));
return testingNaturesList.every((natures, index) => {
const comparingNaturesList = naturesList.slice(index + 1);
if (natures.length) return natures.some(nature => comparingNaturesList.every(testingNatures => !testingNatures.length || testingNatures.some(testingNature => testingNature != nature)));
return comparingNaturesList.every(testingNatures => testingNatures.length);
});
}
/**
* 判断一张牌是否为明置手牌
* @param { Card } card
*/
static shownCard(card) {
if (!card) return false;
const gaintag = card.gaintag;
return Array.isArray(gaintag) && gaintag.some(tag => tag.startsWith('visible_'));
}
/**
* 是否是虚拟牌
* @param { Card | VCard } card
*/
// @ts-ignore
static vituralCard(card) {
return card.isCard || (!("cards" in card) || !Array.isArray(card.cards) || card.cards.length == 0);
}
/**
* 是否是转化牌
* @param { Card | VCard } card
*/
// @ts-ignore
static convertedCard(card) {
return !card.isCard && ("cards" in card) && Array.isArray(card.cards) && card.cards.length > 0;
}
/**
* 是否是实体牌
* @param { Card | VCard } card
*/
// @ts-ignore
static ordinaryCard(card) { return card.isCard && ("cards" in card) && Array.isArray(card.cards) && card.cards.length == 1 }
/**
* 押韵判断
* @param { string } str1
* @param { string } str2
*/
static yayun(str1, str2) {
if (str1 == str2) return true;
let pinyin1 = get.pinyin(str1, false), pinyin2 = get.pinyin(str2, false);
if (!pinyin1.length || !pinyin2.length) return false;
let pron1 = pinyin1[pinyin1.length - 1], pron2 = pinyin2[pinyin2.length - 1];
if (pron1 == pron2) return true;
return get.yunjiao(pron1) == get.yunjiao(pron2);
}
/**
* @param { string } skill 技能id
* @param { Player } player 玩家
* @returns
*/
static blocked(skill, player) {
if (!player.storage.skill_blocker || !player.storage.skill_blocker.length) return false;
for (let i of player.storage.skill_blocker) {
if (lib.skill[i] && lib.skill[i].skillBlocker && lib.skill[i].skillBlocker(skill, player)) return true;
}
return false;
}
/**
* 是否是双势力武将
* @param { string } name
* @param { string[] } array
* @returns { boolean | string[] }
*/
static double(name, array) {
const extraInformations = get.character(name, 4);
if (!extraInformations) return false;
for (const extraInformation of extraInformations) {
if (!extraInformation.startsWith('doublegroup:')) continue;
return array ? extraInformation.split(':').slice(1) : true;
}
return false;
}
/**
* Check if the card has a Yingbian condition
*
* 检测此牌是否具有应变条件
* @param { Card | VCard } card
*/
static yingbianConditional(card) { return get.is.complexlyYingbianConditional(card) || get.is.simplyYingbianConditional(card) }
/**
* @param { Card | VCard } card
*/
static complexlyYingbianConditional(card) {
for (const key of lib.yingbian.condition.complex.keys()) {
if (get.cardtag(card, `yingbian_${key}`)) return true;
}
return false;
}
/**
* @param { Card | VCard } card
*/
static simplyYingbianConditional(card) {
for (const key of lib.yingbian.condition.simple.keys()) {
if (get.cardtag(card, `yingbian_${key}`)) return true;
}
return false;
}
/**
* Check if the card has a Yingbian effect
*
* 检测此牌是否具有应变效果
*
* @param { Card | VCard } card
*/
static yingbianEffective(card) {
for (const key of lib.yingbian.effect.keys()) {
if (get.cardtag(card, `yingbian_${key}`)) return true;
}
return false;
}
/**
* @param { Card | VCard } card
*/
static yingbian(card) { return get.is.yingbianConditional(card) || get.is.yingbianEffective(card) }
/**
* @param { string } [substring]
*/
static emoji(substring) {
if (substring) {
const reg = new RegExp("[~#^$@%&!?%*]", 'g');
if (substring.match(reg)) {
return true;
}
for (let i = 0; i < substring.length; i++) {
const hs = substring.charCodeAt(i);
if (0xd800 <= hs && hs <= 0xdbff) {
if (substring.length > 1) {
const ls = substring.charCodeAt(i + 1);
const uc = ((hs - 0xd800) * 0x400) + (ls - 0xdc00) + 0x10000;
if (0x1d000 <= uc && uc <= 0x1f77f) {
return true;
}
}
}
else if (substring.length > 1) {
const ls = substring.charCodeAt(i + 1);
if (ls == 0x20e3) {
return true;
}
}
else {
if (0x2100 <= hs && hs <= 0x27ff) {
return true;
}
else if (0x2B05 <= hs && hs <= 0x2b07) {
return true;
}
else if (0x2934 <= hs && hs <= 0x2935) {
return true;
}
else if (0x3297 <= hs && hs <= 0x3299) {
return true;
}
else if (hs == 0xa9 || hs == 0xae || hs == 0x303d || hs == 0x3030
|| hs == 0x2b55 || hs == 0x2b1c || hs == 0x2b1b
|| hs == 0x2b50) {
return true;
}
}
}
}
return false;
}
/**
* @param { string } str
*/
static banWords(str) { return get.is.emoji(str) || window.bannedKeyWords.some(item => str.includes(item)) }
/**
* @param { GameEventPromise } event
*/
// @ts-ignore
static converted(event) { return !(event.card && event.card.isCard) }
static safari() { return userAgent.indexOf('safari') != -1 && userAgent.indexOf('chrome') == -1 }
/**
* @param { (Card | VCard)[]} cards
*/
// @ts-ignore
static freePosition(cards) { return !cards.some(card => !card.hasPosition || card.hasPosition()) }
/**
* @param { string } name
* @param { boolean } item
*/
static nomenu(name, item) {
const menus = ['system', 'menu'];
const configs = {
show_round_menu: lib.config.show_round_menu,
round_menu_func: lib.config.round_menu_func,
touchscreen: lib.config.touchscreen,
swipe_up: lib.config.swipe_up,
swipe_down: lib.config.swipe_down,
swipe_left: lib.config.swipe_left,
swipe_right: lib.config.swipe_right,
right_click: lib.config.right_click,
phonelayout: lib.config.phonelayout
};
configs[name] = item;
if (!configs.phonelayout) return false;
if (configs.show_round_menu && menus.includes(configs.round_menu_func)) {
return false;
}
if (configs.touchscreen) {
if (menus.includes(configs.swipe_up)) return false;
if (menus.includes(configs.swipe_down)) return false;
if (menus.includes(configs.swipe_left)) return false;
if (menus.includes(configs.swipe_right)) return false;
}
else {
if (configs.right_click == 'config') return false;
}
if (name) {
setTimeout(function () {
alert('请将至少一个操作绑定为显示按钮或打开菜单,否则将永远无法打开菜单');
});
}
return true;
}
static altered() { return false; }
/*
skill=>{
return false;
// if(_status.connectMode) return true;
// return !lib.config.vintageSkills.includes(skill);
},
*/
/**
* @param { any } obj
* @returns { boolean }
*/
static node(obj) {
return Object.prototype.toString.call(obj).startsWith('[object HTML');
}
/**
* @param { any } obj
*/
static div(obj) { return Object.prototype.toString.call(obj) === '[object HTMLDivElement]' }
/**
* @param { any } obj
*/
static map(obj) { return Object.prototype.toString.call(obj) === '[object Map]' }
/**
* @param { any } obj
*/
static set(obj) { return Object.prototype.toString.call(obj) === '[object Set]' }
/**
* @param { any } obj
*/
static object(obj) { return Object.prototype.toString.call(obj) === '[object Object]' }
/**
* @overload
* @param { Function } func
* @returns { false }
*/
/**
* @overload
* @param { number | [number, number] } func
* @returns { boolean }
*/
static singleSelect(func) {
if (typeof func == 'function') return false;
const select = get.select(func);
return select[0] == 1 && select[1] == 1;
}
/**
* @param { string | Player } name
*/
static jun(name) {
if (get.mode() == 'guozhan') {
if (name instanceof lib.element.Player) {
if (name.isUnseen && name.isUnseen(0)) return false;
name = name.name1;
}
if (typeof name == 'string' && name.startsWith('gz_jun_')) {
return true;
}
}
return false;
}
static versus() { return !_status.connectMode && get.mode() == 'versus' && _status.mode == 'three' }
static changban() { return get.mode() == 'single' && _status.mode == 'changban' }
static single() { return get.mode() == 'single' && _status.mode == 'normal' }
/**
* @param { Player } [player]
*/
static mobileMe(player) { return (game.layout == 'mobile' || game.layout == 'long') && !game.chess && player && player.dataset.position == '0' }
static newLayout() { return game.layout != 'default' }
static phoneLayout() {
if (!lib.config.phonelayout) return false;
return (game.layout == 'mobile' || game.layout == 'long' || game.layout == 'long2' || game.layout == 'nova');
}
static singleHandcard() { return game.singleHandcard || game.layout == 'mobile' || game.layout == 'long' || game.layout == 'long2' || game.layout == 'nova' }
/**
* @param { Player } player
*/
static linked2(player) {
if (game.chess) return true;
if (lib.config.link_style2 != 'rotate') return true;
// if(game.chess) return false;
if (game.layout == 'long' || game.layout == 'long2' || game.layout == 'nova') return true;
if (player.dataset.position == '0') {
return ui.arena.classList.contains('oblongcard');
}
return false;
}
/**
* @param { {} } obj
*/
static empty(obj) { return Object.keys(obj).length == 0 }
/**
* @param { string } str
*/
static pos(str) { return str == 'h' || str == 'e' || str == 'j' || str == 'he' || str == 'hj' || str == 'ej' || str == 'hej' }
/**
* @param { string } skill
* @param { Player } player
* @returns
*/
static locked(skill, player) {
const info = lib.skill[skill];
if (typeof info.locked == 'function') return info.locked(skill, player);
if (info.locked == false) return false;
if (info.trigger && info.forced) return true;
if (info.mod) return true;
if (info.locked) return true;
return false;
}
}
import { Is } from "./is.js";
export class Get extends Uninstantable {
static is = Is;
@ -676,7 +242,7 @@ export class Get extends Uninstantable {
}
static yunjiao(str) {
const util = window.pinyinUtilx;
if (util) str = util.removeTone(str)
if (util) str = util.removeTone(str);
if (lib.pinyins._metadata.zhengtirendu.includes(str)) {
str = ('-' + str[str.length - 1]);
}
@ -1227,107 +793,85 @@ export class Get extends Uninstantable {
}
}
/**
* 深拷贝函数虽然只处理了部分情况
*
* 除了普通的Object和NullObject均不考虑自行赋值的数据但会原样将Symbol复制过去
*
* @template T
* @param {T} obj
* @returns {T}
* @param {T} obj - 要复制的对象若不是对象则直接返回原值
* @param {WeakMap<object, unknown>} [map] - 拷贝用的临时存储用于处理循环引用请勿自行赋值
* @returns {T} - 深拷贝后的对象若传入值不是对象则为传入值
*/
static copy(obj, map = new WeakMap()) {
try {
return structuredClone(obj);
}
catch {
// obj不可序列化时参考[这里](https://juejin.cn/post/7315612852890026021)实现深拷贝
const getType = (obj) => Object.prototype.toString.call(obj);
// 参考[这里](https://juejin.cn/post/7315612852890026021)实现深拷贝
// 不再判断是否能structuredClone是因为structuredClone会把Symbol给毙了
const getType = (obj) => Object.prototype.toString.call(obj);
const canTranverse = {
"[object Map]": true,
"[object Set]": true,
"[object Object]": true,
"[object Array]": true,
"[object Arguments]": true,
};
const canTranverse = {
"[object Map]": true,
"[object Set]": true,
"[object Object]": true,
"[object Array]": true,
"[object Arguments]": true,
};
const functionMap = {
"[object Function]": true,
"[object AsyncFunction]": true,
"[object GeneratorFunction]": true,
};
if (typeof obj !== "object" || obj === null)
return obj;
const transformFunction = (fn) => {
const str = fn.toString();
// 箭头函数
if (/^\s*(?:async)?\s*\(.*\)\s*=>/.test(str)) return str;
// 带function标识的
if (/^\s*(?:async)?\s*function/.test(str)) return str;
const hasAsync = /^\s*(?:async)/.test(str);
return `${hasAsync ? "async " : ""}function ${str.replace(/^\s*(?:async)/, '')}`;
}
// @ts-ignore
if (map.has(obj)) return map.get(obj);
const createFunction = (fn) => {
let cloneFn = null;
const str = `cloneFn = ${transformFunction(fn)}`;
try {
eval(str);
} catch (error) {
console.error(fn.toString())
console.error(str);
throw error;
}
return cloneFn;
};
const cloneSymbol = (s) => {
const key = Symbol.keyFor(s);
if (key) return Symbol.for(key);
const desc = s.description;
if (desc) return Symbol(desc);
return Symbol();
};
if (typeof obj !== "object" || obj === null) {
const constructor = obj.constructor;
let target;
if (!canTranverse[getType(obj)]) {
try {
// @ts-ignore
return typeof obj === "symbol"
? cloneSymbol(obj)
: functionMap[getType(obj)]
? createFunction(obj)
: obj;
target = new constructor(obj);
} catch (error) {
if (obj instanceof HTMLElement) {
target = obj.cloneNode(true); // 不能cloneNode就寄吧累了
} else throw error
}
}
// @ts-ignore
else target = constructor ? new constructor() : Object.create(null);
map.set(obj, target);
const constructor = obj.constructor;
// @ts-ignore
if (!canTranverse[getType(obj)]) return new constructor(obj);
if (map.has(obj)) return map.get(obj);
// @ts-ignore
const target = new constructor();
map.set(obj, target);
if (obj instanceof Map) {
obj.forEach((value, key) => {
target.set(get.copy(key, map), get.copy(value, map));
});
} else if (obj instanceof Set) {
obj.forEach((value) => {
target.add(get.copy(value, map));
});
}
if (obj instanceof Map) {
obj.forEach((value, key) => {
target.set(get.copy(key, map), get.copy(value, map));
});
return target;
}
if (obj instanceof Set) {
obj.forEach((value) => {
target.add(get.copy(value, map));
});
return target;
}
for (const key in obj) {
const descriptors = Object.getOwnPropertyDescriptors(obj);
if (descriptors) {
for (const [key, descriptor] of Object.entries(descriptors)) {
const { enumerable, configurable } = descriptor;
if (obj.hasOwnProperty(key)) {
target[key] = get.copy(obj[key], map);
const result = { enumerable, configurable };
if (descriptor.hasOwnProperty('value')) {
result.value = get.copy(descriptor.value, map);
result.writable = descriptor.writable;
} else {
const { get, set } = descriptor;
result.get = get;
result.set = set;
}
Reflect.defineProperty(target, key, result);
}
}
const symbols = Object.getOwnPropertySymbols(obj);
symbols.forEach((s) => {
target[cloneSymbol(s)] = get.copy(obj[s], map);
});
return target;
}
const symbols = Object.getOwnPropertySymbols(obj);
symbols.forEach((symbol) => {
target[symbol] = get.copy(obj[symbol], map);
});
return target;
}
static inpilefull(type) {
var list = [];
@ -2551,7 +2095,7 @@ export class Get extends Uninstantable {
if (str.suit && str.number || str.isCard) {
var cardnum = get.number(str, false) || '';
if ([1, 11, 12, 13].includes(cardnum)) {
cardnum = { '1': 'A', '11': 'J', '12': 'Q', '13': 'K' }[cardnum]
cardnum = { '1': 'A', '11': 'J', '12': 'Q', '13': 'K' }[cardnum];
}
if (arg == 'viewAs' && str.viewAs != str.name && str.viewAs) {
str2 += '' + get.translation(str) + '';
@ -2917,7 +2461,7 @@ export class Get extends Uninstantable {
*/
static cardtag(item, tag) {
return (item.cardid && (get.itemtype(item) == 'card' || !item.cards || !item.cards.length || item.name == item.cards[0].name) && _status.cardtag && _status.cardtag[tag] && _status.cardtag[tag].includes(item.cardid))
|| (item.cardtags && item.cardtags.includes(tag))
|| (item.cardtags && item.cardtags.includes(tag));
}
static tag(item, tag, item2, bool) {
var result;
@ -5004,3 +4548,7 @@ export class Get extends Uninstantable {
}
export const get = Get;
export {
Is
};

444
noname/get/is.js Normal file
View File

@ -0,0 +1,444 @@
import { userAgent, Uninstantable, GeneratorFunction, AsyncFunction } from "../util/index.js";
import { AI as ai } from '../ai/index.js';
import { Game as game } from '../game/index.js';
import { Library as lib } from '../library/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';
import { Get as get } from "./index.js";
export class Is extends Uninstantable {
/**
* 判断是否为进攻坐骑
* @param { Card | VCard } card
* @param { false | Player } [player]
* @returns { boolean }
*/
static attackingMount(card, player) {
const subtype = get.subtype(card, player);
if (subtype == 'equip4') return true;
else if (subtype == 'equip6') {
const info = get.info(card, player), distance = info.distance;
if (!distance) return false;
if (distance.globalFrom && !info.notMount) return true;
}
return false;
}
/**
* 判断是否为防御坐骑
* @param { Card | VCard } card
* @param { false | Player } [player]
* @returns { boolean }
*/
static defendingMount(card, player) {
const subtype = get.subtype(card, player);
if (subtype == 'equip3') return true;
else if (subtype == 'equip6') {
const info = get.info(card, player), distance = info.distance;
if (!distance) return false;
if (distance.globalTo && !info.notMount) return true;
}
return false;
}
/**
* 判断坐骑栏是否被合并
* @returns { boolean }
*/
static mountCombined() {
if (lib.configOL.mount_combine) {
return lib.configOL.mount_combine;
}
else if (typeof _status.mountCombined != 'boolean') {
_status.mountCombined = lib.config.mount_combine;
}
return _status.mountCombined;
}
/**
* 判断传入的参数的属性是否相同参数可以为卡牌卡牌信息属性等
* @param {...} infos 要判断的属性列表
* @param {boolean} every 是否判断每一个传入的属性是否完全相同而不是存在部分相同
*/
static sameNature() {
let processedArguments = [], every = false;
Array.from(arguments).forEach(argument => {
if (typeof argument == 'boolean') every = argument;
else if (argument) processedArguments.push(argument);
});
if (!processedArguments.length) return true;
if (processedArguments.length == 1) {
const argument = processedArguments[0];
if (!Array.isArray(argument)) return false;
if (!argument.length) return true;
if (argument.length == 1) return false;
processedArguments = argument;
}
const naturesList = processedArguments.map(card => {
if (typeof card == 'string') return card.split(lib.natureSeparator);
else if (Array.isArray(card)) return card;
return get.natureList(card || {});
});
const testingNaturesList = naturesList.slice(0, naturesList.length - 1);
if (every) return testingNaturesList.every((natures, index) => naturesList.slice(index + 1).every(testingNatures => testingNatures.length == natures.length && testingNatures.every(nature => natures.includes(nature))));
return testingNaturesList.every((natures, index) => {
const comparingNaturesList = naturesList.slice(index + 1);
if (natures.length) return natures.some(nature => comparingNaturesList.every(testingNatures => testingNatures.includes(nature)));
return comparingNaturesList.every(testingNatures => !testingNatures.length);
});
}
/**
* 判断传入的参数的属性是否不同参数可以为卡牌卡牌信息属性等
* @param ...infos 要判断的属性列表
* @param every {boolean} 是否判断每一个传入的属性是否完全不同而不是存在部分不同
*/
static differentNature() {
let processedArguments = [], every = false;
Array.from(arguments).forEach(argument => {
if (typeof argument == 'boolean') every = argument;
else if (argument) processedArguments.push(argument);
});
if (!processedArguments.length) return false;
if (processedArguments.length == 1) {
const argument = processedArguments[0];
if (!Array.isArray(argument)) return true;
if (!argument.length) return false;
if (argument.length == 1) return true;
processedArguments = argument;
}
const naturesList = processedArguments.map(card => {
if (typeof card == 'string') return card.split(lib.natureSeparator);
else if (Array.isArray(card)) return card;
return get.natureList(card || {});
});
const testingNaturesList = naturesList.slice(0, naturesList.length - 1);
if (every) return testingNaturesList.every((natures, index) => naturesList.slice(index + 1).every(testingNatures => testingNatures.every(nature => !natures.includes(nature))));
return testingNaturesList.every((natures, index) => {
const comparingNaturesList = naturesList.slice(index + 1);
if (natures.length) return natures.some(nature => comparingNaturesList.every(testingNatures => !testingNatures.length || testingNatures.some(testingNature => testingNature != nature)));
return comparingNaturesList.every(testingNatures => testingNatures.length);
});
}
/**
* 判断一张牌是否为明置手牌
* @param { Card } card
*/
static shownCard(card) {
if (!card) return false;
const gaintag = card.gaintag;
return Array.isArray(gaintag) && gaintag.some(tag => tag.startsWith('visible_'));
}
/**
* 是否是虚拟牌
* @param { Card | VCard } card
*/
// @ts-ignore
static vituralCard(card) {
return card.isCard || (!("cards" in card) || !Array.isArray(card.cards) || card.cards.length == 0);
}
/**
* 是否是转化牌
* @param { Card | VCard } card
*/
// @ts-ignore
static convertedCard(card) {
return !card.isCard && ("cards" in card) && Array.isArray(card.cards) && card.cards.length > 0;
}
/**
* 是否是实体牌
* @param { Card | VCard } card
*/
// @ts-ignore
static ordinaryCard(card) { return card.isCard && ("cards" in card) && Array.isArray(card.cards) && card.cards.length == 1 }
/**
* 押韵判断
* @param { string } str1
* @param { string } str2
*/
static yayun(str1, str2) {
if (str1 == str2) return true;
let pinyin1 = get.pinyin(str1, false), pinyin2 = get.pinyin(str2, false);
if (!pinyin1.length || !pinyin2.length) return false;
let pron1 = pinyin1[pinyin1.length - 1], pron2 = pinyin2[pinyin2.length - 1];
if (pron1 == pron2) return true;
return get.yunjiao(pron1) == get.yunjiao(pron2);
}
/**
* @param { string } skill 技能id
* @param { Player } player 玩家
* @returns
*/
static blocked(skill, player) {
if (!player.storage.skill_blocker || !player.storage.skill_blocker.length) return false;
for (let i of player.storage.skill_blocker) {
if (lib.skill[i] && lib.skill[i].skillBlocker && lib.skill[i].skillBlocker(skill, player)) return true;
}
return false;
}
/**
* 是否是双势力武将
* @param { string } name
* @param { string[] } array
* @returns { boolean | string[] }
*/
static double(name, array) {
const extraInformations = get.character(name, 4);
if (!extraInformations) return false;
for (const extraInformation of extraInformations) {
if (!extraInformation.startsWith('doublegroup:')) continue;
return array ? extraInformation.split(':').slice(1) : true;
}
return false;
}
/**
* Check if the card has a Yingbian condition
*
* 检测此牌是否具有应变条件
* @param { Card | VCard } card
*/
static yingbianConditional(card) { return get.is.complexlyYingbianConditional(card) || get.is.simplyYingbianConditional(card) }
/**
* @param { Card | VCard } card
*/
static complexlyYingbianConditional(card) {
for (const key of lib.yingbian.condition.complex.keys()) {
if (get.cardtag(card, `yingbian_${key}`)) return true;
}
return false;
}
/**
* @param { Card | VCard } card
*/
static simplyYingbianConditional(card) {
for (const key of lib.yingbian.condition.simple.keys()) {
if (get.cardtag(card, `yingbian_${key}`)) return true;
}
return false;
}
/**
* Check if the card has a Yingbian effect
*
* 检测此牌是否具有应变效果
*
* @param { Card | VCard } card
*/
static yingbianEffective(card) {
for (const key of lib.yingbian.effect.keys()) {
if (get.cardtag(card, `yingbian_${key}`)) return true;
}
return false;
}
/**
* @param { Card | VCard } card
*/
static yingbian(card) { return get.is.yingbianConditional(card) || get.is.yingbianEffective(card) }
/**
* @param { string } [substring]
*/
static emoji(substring) {
if (substring) {
const reg = new RegExp("[~#^$@%&!?%*]", 'g');
if (substring.match(reg)) {
return true;
}
for (let i = 0; i < substring.length; i++) {
const hs = substring.charCodeAt(i);
if (0xd800 <= hs && hs <= 0xdbff) {
if (substring.length > 1) {
const ls = substring.charCodeAt(i + 1);
const uc = ((hs - 0xd800) * 0x400) + (ls - 0xdc00) + 0x10000;
if (0x1d000 <= uc && uc <= 0x1f77f) {
return true;
}
}
}
else if (substring.length > 1) {
const ls = substring.charCodeAt(i + 1);
if (ls == 0x20e3) {
return true;
}
}
else {
if (0x2100 <= hs && hs <= 0x27ff) {
return true;
}
else if (0x2B05 <= hs && hs <= 0x2b07) {
return true;
}
else if (0x2934 <= hs && hs <= 0x2935) {
return true;
}
else if (0x3297 <= hs && hs <= 0x3299) {
return true;
}
else if (hs == 0xa9 || hs == 0xae || hs == 0x303d || hs == 0x3030
|| hs == 0x2b55 || hs == 0x2b1c || hs == 0x2b1b
|| hs == 0x2b50) {
return true;
}
}
}
}
return false;
}
/**
* @param { string } str
*/
static banWords(str) { return get.is.emoji(str) || window.bannedKeyWords.some(item => str.includes(item)) }
/**
* @param { GameEventPromise } event
*/
// @ts-ignore
static converted(event) { return !(event.card && event.card.isCard) }
static safari() { return userAgent.indexOf('safari') != -1 && userAgent.indexOf('chrome') == -1 }
/**
* @param { (Card | VCard)[]} cards
*/
// @ts-ignore
static freePosition(cards) { return !cards.some(card => !card.hasPosition || card.hasPosition()) }
/**
* @param { string } name
* @param { boolean } item
*/
static nomenu(name, item) {
const menus = ['system', 'menu'];
const configs = {
show_round_menu: lib.config.show_round_menu,
round_menu_func: lib.config.round_menu_func,
touchscreen: lib.config.touchscreen,
swipe_up: lib.config.swipe_up,
swipe_down: lib.config.swipe_down,
swipe_left: lib.config.swipe_left,
swipe_right: lib.config.swipe_right,
right_click: lib.config.right_click,
phonelayout: lib.config.phonelayout
};
configs[name] = item;
if (!configs.phonelayout) return false;
if (configs.show_round_menu && menus.includes(configs.round_menu_func)) {
return false;
}
if (configs.touchscreen) {
if (menus.includes(configs.swipe_up)) return false;
if (menus.includes(configs.swipe_down)) return false;
if (menus.includes(configs.swipe_left)) return false;
if (menus.includes(configs.swipe_right)) return false;
}
else {
if (configs.right_click == 'config') return false;
}
if (name) {
setTimeout(function () {
alert('请将至少一个操作绑定为显示按钮或打开菜单,否则将永远无法打开菜单');
});
}
return true;
}
static altered() { return false; }
/*
skill=>{
return false;
// if(_status.connectMode) return true;
// return !lib.config.vintageSkills.includes(skill);
},
*/
/**
* @param { any } obj
* @returns { boolean }
*/
static node(obj) {
return Object.prototype.toString.call(obj).startsWith('[object HTML');
}
/**
* @param { any } obj
*/
static div(obj) { return Object.prototype.toString.call(obj) === '[object HTMLDivElement]' }
/**
* @param { any } obj
*/
static map(obj) { return Object.prototype.toString.call(obj) === '[object Map]' }
/**
* @param { any } obj
*/
static set(obj) { return Object.prototype.toString.call(obj) === '[object Set]' }
/**
* @param { any } obj
*/
static object(obj) { return Object.prototype.toString.call(obj) === '[object Object]' }
/**
* @overload
* @param { Function } func
* @returns { false }
*/
/**
* @overload
* @param { number | [number, number] } func
* @returns { boolean }
*/
static singleSelect(func) {
if (typeof func == 'function') return false;
const select = get.select(func);
return select[0] == 1 && select[1] == 1;
}
/**
* @param { string | Player } name
*/
static jun(name) {
if (get.mode() == 'guozhan') {
if (name instanceof lib.element.Player) {
if (name.isUnseen && name.isUnseen(0)) return false;
name = name.name1;
}
if (typeof name == 'string' && name.startsWith('gz_jun_')) {
return true;
}
}
return false;
}
static versus() { return !_status.connectMode && get.mode() == 'versus' && _status.mode == 'three' }
static changban() { return get.mode() == 'single' && _status.mode == 'changban' }
static single() { return get.mode() == 'single' && _status.mode == 'normal' }
/**
* @param { Player } [player]
*/
static mobileMe(player) { return (game.layout == 'mobile' || game.layout == 'long') && !game.chess && player && player.dataset.position == '0' }
static newLayout() { return game.layout != 'default' }
static phoneLayout() {
if (!lib.config.phonelayout) return false;
return (game.layout == 'mobile' || game.layout == 'long' || game.layout == 'long2' || game.layout == 'nova');
}
static singleHandcard() { return game.singleHandcard || game.layout == 'mobile' || game.layout == 'long' || game.layout == 'long2' || game.layout == 'nova' }
/**
* @param { Player } player
*/
static linked2(player) {
if (game.chess) return true;
if (lib.config.link_style2 != 'rotate') return true;
// if(game.chess) return false;
if (game.layout == 'long' || game.layout == 'long2' || game.layout == 'nova') return true;
if (player.dataset.position == '0') {
return ui.arena.classList.contains('oblongcard');
}
return false;
}
/**
* @param { {} } obj
*/
static empty(obj) { return Object.keys(obj).length == 0 }
/**
* @param { string } str
*/
static pos(str) { return str == 'h' || str == 'e' || str == 'j' || str == 'he' || str == 'hj' || str == 'ej' || str == 'hej' }
/**
* @param { string } skill
* @param { Player } player
* @returns
*/
static locked(skill, player) {
const info = lib.skill[skill];
if (typeof info.locked == 'function') return info.locked(skill, player);
if (info.locked == false) return false;
if (info.trigger && info.forced) return true;
if (info.mod) return true;
if (info.locked) return true;
return false;
}
}

View File

@ -2,21 +2,25 @@ import { Game as game } from '../game/index.js';
/**
* @param {string} name - 卡牌包名
* @returns {Promise<void>}
*/
export const importCardPack = generateImportFunction('card', (name) => `../../card/${name}.js`)
/**
* @param {string} name - 武将包名
* @returns {Promise<void>}
*/
export const importCharacterPack = generateImportFunction('character', (name) => `../../character/${name}.js`)
/**
* @param {string} name - 扩展名
* @returns {Promise<void>}
*/
export const importExtension = generateImportFunction('extension', (name) => `../../extension/${name}/extension.js`)
/**
* @param {string} name - 模式名
* @param {string} name - 模式名
* @returns {Promise<void>}
*/
export const importMode = generateImportFunction('mode', (name) => `../../mode/${name}.js`)
@ -28,14 +32,10 @@ export const importMode = generateImportFunction('mode', (name) => `../../mode/$
* @returns {(name: string) => Promise<void>}
*/
function generateImportFunction(type, pathParser) {
return async function (name) {
try {
const modeContent = await import(pathParser(name));
if (!modeContent.type) return;
if (modeContent.type !== type) throw new Error(`Loaded Content doesnt conform to "${type}"`);
await game.import(type, modeContent.default);
} catch (e) {
console.error(e);
}
return async (name) => {
const modeContent = await import(pathParser(name));
if (!modeContent.type) return;
if (modeContent.type !== type) throw new Error(`Loaded Content doesn't conform to "${type}" but "${modeContent.type}".`);
await game.import(type, modeContent.default);
}
}

View File

@ -24,144 +24,150 @@ export async function onload(resetGameTimeout) {
if (lib.config.touchscreen) createTouchDraggedFilter();
// 不拆分,太玄学了
if (lib.config.card_style == 'custom') {
game.getDB('image', 'card_style', function (fileToLoad) {
if (!fileToLoad) return;
var fileReader = new FileReader();
fileReader.onload = function (fileLoadedEvent) {
if (ui.css.card_stylesheet) {
ui.css.card_stylesheet.remove();
}
ui.css.card_stylesheet = lib.init.sheet('.card:not(*:empty){background-image:url(' + fileLoadedEvent.target.result + ')}');
};
fileReader.readAsDataURL(fileToLoad, "UTF-8");
});
if (lib.config.card_style === 'custom') {
const fileToLoad = await game.getDB('image', 'card_style')
if (fileToLoad) {
const fileLoadedEvent = await new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.onload = resolve;
fileReader.onerror = reject;
fileReader.readAsDataURL(fileToLoad, "UTF-8");
})
if (ui.css.card_stylesheet) ui.css.card_stylesheet.remove();
ui.css.card_stylesheet = lib.init.sheet(`.card:not(*:empty){background-image:url(${fileLoadedEvent.target.result})}`);
}
}
if (lib.config.cardback_style == 'custom') {
game.getDB('image', 'cardback_style', function (fileToLoad) {
if (!fileToLoad) return;
var fileReader = new FileReader();
fileReader.onload = function (fileLoadedEvent) {
if (ui.css.cardback_stylesheet) {
ui.css.cardback_stylesheet.remove();
}
ui.css.cardback_stylesheet = lib.init.sheet('.card:empty,.card.infohidden{background-image:url(' + fileLoadedEvent.target.result + ')}');
};
fileReader.readAsDataURL(fileToLoad, "UTF-8");
});
game.getDB('image', 'cardback_style2', function (fileToLoad) {
if (!fileToLoad) return;
var fileReader = new FileReader();
fileReader.onload = function (fileLoadedEvent) {
if (ui.css.cardback_stylesheet2) {
ui.css.cardback_stylesheet2.remove();
}
ui.css.cardback_stylesheet2 = lib.init.sheet('.card.infohidden:not(.infoflip){background-image:url(' + fileLoadedEvent.target.result + ')}');
};
fileReader.readAsDataURL(fileToLoad, "UTF-8");
});
if (lib.config.cardback_style === 'custom') {
const fileToLoad1 = await game.getDB('image', 'cardback_style');
if (fileToLoad1) {
const fileLoadedEvent = await new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.onload = resolve;
fileReader.onerror = reject;
fileReader.readAsDataURL(fileToLoad1, "UTF-8");
})
if (ui.css.cardback_stylesheet) ui.css.cardback_stylesheet.remove();
ui.css.cardback_stylesheet = lib.init.sheet(`.card:empty,.card.infohidden{background-image:url(${fileLoadedEvent.target.result})}`);
}
const fileToLoad2 = await game.getDB('image', 'cardback_style2')
if (fileToLoad2) {
const fileLoadedEvent = await new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.onload = resolve;
fileReader.onerror = reject;
fileReader.readAsDataURL(fileToLoad2, "UTF-8");
})
if (ui.css.cardback_stylesheet2) ui.css.cardback_stylesheet2.remove();
ui.css.cardback_stylesheet2 = lib.init.sheet(`.card.infohidden:not(.infoflip){background-image:url(${fileLoadedEvent.target.result})}`);
}
}
if (lib.config.hp_style == 'custom') {
game.getDB('image', 'hp_style1', function (fileToLoad) {
if (!fileToLoad) return;
var fileReader = new FileReader();
fileReader.onload = function (fileLoadedEvent) {
if (ui.css.hp_stylesheet1) {
ui.css.hp_stylesheet1.remove();
}
ui.css.hp_stylesheet1 = lib.init.sheet('.hp:not(.text):not(.actcount)[data-condition="high"]>div:not(.lost){background-image:url(' + fileLoadedEvent.target.result + ')}');
};
fileReader.readAsDataURL(fileToLoad, "UTF-8");
});
game.getDB('image', 'hp_style2', function (fileToLoad) {
if (!fileToLoad) return;
var fileReader = new FileReader();
fileReader.onload = function (fileLoadedEvent) {
if (ui.css.hp_stylesheet2) {
ui.css.hp_stylesheet2.remove();
}
ui.css.hp_stylesheet2 = lib.init.sheet('.hp:not(.text):not(.actcount)[data-condition="mid"]>div:not(.lost){background-image:url(' + fileLoadedEvent.target.result + ')}');
};
fileReader.readAsDataURL(fileToLoad, "UTF-8");
});
game.getDB('image', 'hp_style3', function (fileToLoad) {
if (!fileToLoad) return;
var fileReader = new FileReader();
fileReader.onload = function (fileLoadedEvent) {
if (ui.css.hp_stylesheet3) {
ui.css.hp_stylesheet3.remove();
}
ui.css.hp_stylesheet3 = lib.init.sheet('.hp:not(.text):not(.actcount)[data-condition="low"]>div:not(.lost){background-image:url(' + fileLoadedEvent.target.result + ')}');
};
fileReader.readAsDataURL(fileToLoad, "UTF-8");
});
game.getDB('image', 'hp_style4', function (fileToLoad) {
if (!fileToLoad) return;
var fileReader = new FileReader();
fileReader.onload = function (fileLoadedEvent) {
if (ui.css.hp_stylesheet4) {
ui.css.hp_stylesheet4.remove();
}
ui.css.hp_stylesheet4 = lib.init.sheet('.hp:not(.text):not(.actcount)>.lost{background-image:url(' + fileLoadedEvent.target.result + ')}');
};
fileReader.readAsDataURL(fileToLoad, "UTF-8");
});
if (lib.config.hp_style === 'custom') {
const fileToLoad1 = await game.getDB('image', 'hp_style1')
if (fileToLoad1) {
const fileLoadedEvent = await new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.onload = resolve;
fileReader.onerror = reject;
fileReader.readAsDataURL(fileToLoad1, "UTF-8");
})
if (ui.css.hp_stylesheet1) ui.css.hp_stylesheet1.remove();
ui.css.hp_stylesheet1 = lib.init.sheet(`.hp:not(.text):not(.actcount)[data-condition="high"]>div:not(.lost){background-image:url(${fileLoadedEvent.target.result})}`);
}
const fileToLoad2 = await game.getDB('image', 'hp_style2');
if (fileToLoad2) {
const fileReader = new FileReader();
const fileLoadedEvent = await new Promise((resolve, reject) => {
fileReader.onload = resolve;
fileReader.onerror = reject;
fileReader.readAsDataURL(fileToLoad2, "UTF-8");
});
if (ui.css.hp_stylesheet2) ui.css.hp_stylesheet2.remove();
ui.css.hp_stylesheet2 = lib.init.sheet(`.hp:not(.text):not(.actcount)[data-condition="mid"]>div:not(.lost){background-image:url(${fileLoadedEvent.target.result})}`);
}
const fileToLoad3 = await game.getDB('image', 'hp_style3');
if (fileToLoad3) {
const fileReader = new FileReader();
const fileLoadedEvent = await new Promise((resolve, reject) => {
fileReader.onload = resolve;
fileReader.onerror = reject;
fileReader.readAsDataURL(fileToLoad3, "UTF-8");
});
if (ui.css.hp_stylesheet3) ui.css.hp_stylesheet3.remove();
ui.css.hp_stylesheet3 = lib.init.sheet(`.hp:not(.text):not(.actcount)[data-condition="low"]>div:not(.lost){background-image:url(${fileLoadedEvent.target.result})}`);
}
const fileToLoad4 = await game.getDB('image', 'hp_style4');
if (fileToLoad4) {
const fileReader = new FileReader();
const fileLoadedEvent = await new Promise((resolve, reject) => {
fileReader.onload = resolve;
fileReader.onerror = reject;
fileReader.readAsDataURL(fileToLoad4, "UTF-8");
});
if (ui.css.hp_stylesheet4) ui.css.hp_stylesheet4.remove();
ui.css.hp_stylesheet4 = lib.init.sheet(`.hp:not(.text):not(.actcount)>.lost{background-image:url(${fileLoadedEvent.target.result})}`);
}
}
if (lib.config.player_style == 'custom') {
ui.css.player_stylesheet = lib.init.sheet('#window .player{background-image:none;background-size:100% 100%;}');
game.getDB('image', 'player_style', function (fileToLoad) {
if (!fileToLoad) return;
var fileReader = new FileReader();
fileReader.onload = function (fileLoadedEvent) {
if (ui.css.player_stylesheet) {
ui.css.player_stylesheet.remove();
}
ui.css.player_stylesheet = lib.init.sheet('#window .player{background-image:url("' + fileLoadedEvent.target.result + '");background-size:100% 100%;}');
};
fileReader.readAsDataURL(fileToLoad, "UTF-8");
});
if (lib.config.player_style === 'custom') {
const fileToLoad = await game.getDB('image', 'player_style');
if (fileToLoad) {
const fileReader = new FileReader();
const fileLoadedEvent = await new Promise((resolve, reject) => {
fileReader.onload = resolve;
fileReader.onerror = reject;
fileReader.readAsDataURL(fileToLoad, "UTF-8");
});
if (ui.css.player_stylesheet) ui.css.player_stylesheet.remove();
ui.css.player_stylesheet = lib.init.sheet(`#window .player{background-image:url("${fileLoadedEvent.target.result}");background-size:100% 100%;}`);
} else {
ui.css.player_stylesheet = lib.init.sheet('#window .player{background-image:none;background-size:100% 100%;}');
}
}
if (lib.config.border_style == 'custom') {
game.getDB('image', 'border_style', function (fileToLoad) {
if (!fileToLoad) return;
var fileReader = new FileReader();
fileReader.onload = function (fileLoadedEvent) {
if (ui.css.border_stylesheet) {
ui.css.border_stylesheet.remove();
}
ui.css.border_stylesheet = lib.init.sheet();
ui.css.border_stylesheet.sheet.insertRule('#window .player>.framebg{display:block;background-image:url("' + fileLoadedEvent.target.result + '")}', 0);
ui.css.border_stylesheet.sheet.insertRule('.player>.count{z-index: 3 !important;border-radius: 2px !important;text-align: center !important;}', 0);
};
fileReader.readAsDataURL(fileToLoad, "UTF-8");
});
if (lib.config.border_style === 'custom') {
const fileToLoad = await game.getDB('image', 'border_style');
if (fileToLoad) {
const fileReader = new FileReader();
const fileLoadedEvent = await new Promise((resolve, reject) => {
fileReader.onload = resolve;
fileReader.onerror = reject;
fileReader.readAsDataURL(fileToLoad, "UTF-8");
});
if (ui.css.border_stylesheet) ui.css.border_stylesheet.remove();
ui.css.border_stylesheet = lib.init.sheet();
ui.css.border_stylesheet.sheet.insertRule(`#window .player>.framebg{display:block;background-image:url("${fileLoadedEvent.target.result}")}`, 0);
ui.css.border_stylesheet.sheet.insertRule('.player>.count{z-index: 3 !important;border-radius: 2px !important;text-align: center !important;}', 0);
}
}
if (lib.config.control_style == 'custom') {
game.getDB('image', 'control_style', function (fileToLoad) {
if (!fileToLoad) return;
var fileReader = new FileReader();
fileReader.onload = function (fileLoadedEvent) {
if (ui.css.control_stylesheet) {
ui.css.control_stylesheet.remove();
}
ui.css.control_stylesheet = lib.init.sheet('#window .control,.menubutton:not(.active):not(.highlight):not(.red):not(.blue),#window #system>div>div{background-image:url("' + fileLoadedEvent.target.result + '")}');
};
fileReader.readAsDataURL(fileToLoad, "UTF-8");
});
if (lib.config.control_style === 'custom') {
const fileToLoad = await game.getDB('image', 'control_style');
if (fileToLoad) {
const fileReader = new FileReader();
const fileLoadedEvent = await new Promise((resolve, reject) => {
fileReader.onload = resolve;
fileReader.onerror = reject;
fileReader.readAsDataURL(fileToLoad, "UTF-8");
});
if (ui.css.control_stylesheet) ui.css.control_stylesheet.remove();
ui.css.control_stylesheet = lib.init.sheet(`#window .control,.menubutton:not(.active):not(.highlight):not(.red):not(.blue),#window #system>div>div{background-image:url("${fileLoadedEvent.target.result}")}`);
}
}
if (lib.config.menu_style == 'custom') {
game.getDB('image', 'menu_style', function (fileToLoad) {
if (!fileToLoad) return;
var fileReader = new FileReader();
fileReader.onload = function (fileLoadedEvent) {
if (ui.css.menu_stylesheet) {
ui.css.menu_stylesheet.remove();
}
ui.css.menu_stylesheet = lib.init.sheet('html #window>.dialog.popped,html .menu,html .menubg{background-image:url("' + fileLoadedEvent.target.result + '");background-size:cover}');
};
fileReader.readAsDataURL(fileToLoad, "UTF-8");
});
if (lib.config.menu_style === 'custom') {
const fileToLoad = await game.getDB('image', 'menu_style');
if (fileToLoad) {
const fileReader = new FileReader();
const fileLoadedEvent = await new Promise((resolve, reject) => {
fileReader.onload = resolve;
fileReader.onerror = reject;
fileReader.readAsDataURL(fileToLoad, "UTF-8");
});
if (ui.css.menu_stylesheet) ui.css.menu_stylesheet.remove();
ui.css.menu_stylesheet = lib.init.sheet(`html #window>.dialog.popped,html .menu,html .menubg{background-image:url("${fileLoadedEvent.target.result}");background-size:cover}`);
}
}
// 改不动,暂时不改了

View File

@ -15,6 +15,11 @@ export class Button extends HTMLDivElement {
*/
// @ts-ignore
constructor(item, type, position, noClick, button) {
if (item instanceof Button) {
const other = item;
// @ts-ignore
[item, type, position, noClick, button] = other._args;
}
if (typeof type == 'function') button = type(item, type, position, noClick, button);
else if (ui.create.buttonPresets[type]) button = ui.create.buttonPresets[type](item, type, position, noClick, button);
if (button) {
@ -25,6 +30,8 @@ export class Button extends HTMLDivElement {
const intro = button.querySelector('.intro');
if (intro) intro.remove();
}
// @ts-ignore
button._args = [item, type, position, noClick, button];
return button;
} else {
console.error([item, type, position, noClick, button]);
@ -40,4 +47,4 @@ export class Button extends HTMLDivElement {
get updateTransform() {
return lib.element.Card.prototype.updateTransform;
}
}
}

View File

@ -13,6 +13,11 @@ export class Card extends HTMLDivElement {
*/
// @ts-ignore
constructor(position, info, noclick) {
if (position instanceof Card) {
const other = position;
// @ts-ignore
[position, info, noclick] = other._args;
}
/**
* @type {this}
*/
@ -20,6 +25,8 @@ export class Card extends HTMLDivElement {
const card = ui.create.div('.card', position);
Object.setPrototypeOf(card, Card.prototype);
card.build(info, noclick);
// @ts-ignore
card._args = [position, info, noclick];
return card;
}
build(info, noclick) {
@ -784,4 +791,4 @@ export class Card extends HTMLDivElement {
isInPile() {
return ['c', 'd'].includes(get.position(this));
}
}
}

View File

@ -8,9 +8,10 @@ import { GNC as gnc } from '../../gnc/index.js';
export class Client {
/**
* @param {import('../index.js').NodeWS | InstanceType<typeof import('ws').WebSocket>} ws
* @param {import('../index.js').NodeWS | InstanceType<typeof import('ws').WebSocket> | Client} ws
*/
constructor(ws) {
if (ws instanceof Client) throw new Error('Client cannot copy.')
this.ws = ws;
/**
* @type { string }
@ -72,4 +73,4 @@ export class Client {
}
return this;
}
}
}

View File

@ -8,9 +8,15 @@ import { GNC as gnc } from '../../gnc/index.js';
export class Control extends HTMLDivElement {
// @ts-ignore
constructor() {
constructor(...args) {
if (args[0] instanceof Control) {
const other = args[0];
// @ts-ignore
args = other._args;
}
const nc = !ui.control.querySelector('div:not(.removing):not(.stayleft)');
const controls = Array.isArray(arguments[0]) ? arguments[0] : Array.from(arguments);
const controls = Array.isArray(args[0]) ? args[0] : args;
/**
* @type {this}
*/
@ -54,6 +60,8 @@ export class Control extends HTMLDivElement {
}
ui.updatec();
// @ts-ignore
control._args = args;
return control;
}
open() {
@ -129,4 +137,4 @@ export class Control extends HTMLDivElement {
}
return this;
}
}
}

View File

@ -7,7 +7,13 @@ import { UI as ui } from '../../ui/index.js';
export class Dialog extends HTMLDivElement {
// @ts-ignore
constructor() {
constructor(...args) {
if (args[0] instanceof Dialog) {
const other = args[0];
// @ts-ignore
args = other._args;
}
let hidden = false;
let noTouchScroll = false;
let forceButton = false;
@ -21,7 +27,7 @@ export class Dialog extends HTMLDivElement {
dialog.bar1 = ui.create.div('.bar.top', dialog);
dialog.bar2 = ui.create.div('.bar.bottom', dialog);
dialog.buttons = [];
Array.from(arguments).forEach(argument => {
Array.from(args).forEach(argument => {
if (typeof argument == 'boolean') dialog.static = argument;
else if (argument == 'hidden') hidden = true;
else if (argument == 'notouchscroll') noTouchScroll = true;
@ -42,6 +48,8 @@ export class Dialog extends HTMLDivElement {
dialog.forcebutton = true;
dialog.classList.add('forcebutton');
}
// @ts-ignore
dialog._args = args;
return dialog;
}
add(item, noclick, zoom) {
@ -175,4 +183,4 @@ export class Dialog extends HTMLDivElement {
this.querySelector('.caption').innerHTML = str;
return this;
}
}
}

View File

@ -10,15 +10,21 @@ export class GameEvent {
/** @type { GameEventPromise } */
#promise;
/**
* @param {string} [name]
* @param {string | GameEvent} [name]
* @param {false} [trigger]
*/
constructor(name, trigger) {
if (name instanceof GameEvent) {
const other = name;
[name, trigger] = other.__args;
}
if (typeof name == 'string') {
this.name = name;
const gameEvent = get.event();
if (gameEvent) {
const type = `onNext${name[0].toUpperCase()}${name.slice(1)}`;
// @ts-ignore
if (gameEvent.hasHandler(type)) this.pushHandler(...gameEvent.getHandler(type));
}
game.globalEventHandlers.addHandlerToEvent(this);
@ -50,6 +56,7 @@ export class GameEvent {
**/
this.resolve = null;
if (trigger !== false && !game.online) this._triggered = 0;
this.__args = [name, trigger];
}
static initialGameEvent() {
return new GameEvent().finish().toPromise();
@ -852,4 +859,4 @@ export class GameEvent {
this.excludeButton;
throw new Error('Do not call this method');
}
}
}

View File

@ -37,9 +37,15 @@ export class GameEventPromise extends Promise {
}
#event;
/**
* @param { GameEvent } event
* @param { GameEvent | GameEventPromise } arg
*/
constructor(event) {
constructor(arg) {
if (arg instanceof GameEventPromise)
throw new Error("GameEventPromise cannot copy.")
/**
* @type {GameEvent}
*/
const event = arg;
super(resolve => {
// 设置为异步事件
event.async = true;
@ -157,4 +163,4 @@ export class GameEventPromise extends Promise {
game.promises.prompt('debugger调试').then(inputCallback);
});
}
}
}

View File

@ -8,10 +8,10 @@ import { GNC as gnc } from '../../gnc/index.js';
export class NodeWS {
/**
* @param {string} id
* @param {string | NodeWS} id
*/
constructor(id) {
this.wsid = id;
this.wsid = (id instanceof NodeWS) ? id.wsid : id;
}
send(message) {
game.send('server', 'send', this.wsid, message);
@ -22,4 +22,4 @@ export class NodeWS {
close() {
game.send('server', 'close', this.wsid);
}
}
}

View File

@ -12,6 +12,11 @@ export class Player extends HTMLDivElement {
*/
// @ts-ignore
constructor(position, noclick) {
if (position instanceof Player) {
const other = position;
[position, noclick] = other._args;
}
/**
* @type {this}
*/
@ -19,6 +24,8 @@ export class Player extends HTMLDivElement {
const player = ui.create.div('.player', position);
Object.setPrototypeOf(player, Player.prototype);
player.build(noclick);
// @ts-ignore
player._args = [position, noclick];
return player;
}
/**
@ -6193,8 +6200,8 @@ export class Player extends HTMLDivElement {
if (mark) this.markAuto(name);
return value;
}
getStorage(name) {
return this.storage[name] || [];
getStorage(name, defaultValue = []) {
return this.hasStorage(name) ? this.storage[name] : defaultValue;
}
hasStorage(name, value) {
if (!(name in this.storage)) return false;

View File

@ -13,6 +13,11 @@ export class VCard {
* @param { string } [nature]
*/
constructor(suitOrCard, numberOrCards, name, nature) {
if (suitOrCard instanceof VCard) {
const other = suitOrCard;
[suitOrCard, numberOrCards, name, nature] = other._args;
}
if (Array.isArray(suitOrCard)) {
/**
* @type {string}
@ -91,6 +96,8 @@ export class VCard {
if (typeof nature == 'string') this.nature = nature;
if (!this.storage) this.storage = {};
if (!this.cards) this.cards = [];
this._args = [suitOrCard, numberOrCards, name, nature];
}
sameSuitAs(card) {
return get.suit(this) == get.suit(card);
@ -119,4 +126,4 @@ export class VCard {
if (nature == 'linked') return natures.some(n => lib.linked.includes(n));
return get.is.sameNature(natures, nature);
}
}
}