UNPKG

yoni-mcscripts-lib

Version:

为 Minecraft Script API 中的部分接口创建了 wrapper,并提供简单的事件管理器和任务管理器,另附有一些便于代码编写的一些小工具。

336 lines (335 loc) 12.4 kB
import { Gametest, Minecraft, StatusCode, VanillaScoreboard } from "../basis.js"; import { ScoreboardEntry } from "./ScoreboardEntry.js"; import { EntryType } from "./EntryType.js"; import { ScoreInfo } from "./ScoreInfo.js"; import { NameConflictError, ScoreRangeError, ObjectiveUnregisteredError, UnknownEntryError } from "./ScoreboardError.js"; import { EntityUtils } from "../EntityUtils.js"; import { Command } from "../command.js"; /** * 检查传入的参数是否为整数数字,并且在 [-2^31, 2^31-1] 的区间。 * @param {...any} scores 要检查的变量。 * @throws 若分数不在可用的范围,抛出 `ScoreRangeError`。 */ function checkScoreIsInRange(...scores) { for (let s of scores) { if (Number.isInteger(s) === false || s > 2147483647 || s < -2147483648) { throw new ScoreRangeError(); } } } /** * 记分项记录了参与者以及他们的分数。 */ class Objective { #scoreboard; /** * @type {string} */ #id; /** * @type {string} */ #criteria; /** * @type {string} */ #displayName; /** * @type {boolean} */ #unregistered = false; /** * @type {Minecraft.ScoreboardObjective} */ #vanillaObjective; get scoreboard() { return this.#scoreboard; } /** * 记分项的标识符。 * @returns {string} */ get id() { return this.#id; } /** * 记分项的准则,应该为 `"dummy"`。 * @returns {string} */ get criteria() { return this.#criteria; } /** * 返回此记分项的玩家可见名称。 * @returns {string} */ get displayName() { return this.#displayName; } /** * 检测此对象对应的记分项是否已经被移除。 * @returns {boolean} 检测结果。若已被移除,返回 `true`,否则返回 `false`。 */ isUnregistered() { if (!this.#unregistered && this.#scoreboard.tryGetObjective(this.#id) !== this) { this.#unregistered = true; } return this.#unregistered; } /** * 检查此对象对应的记分项是否被移除。 * @throws 当此对象对应的记分项被移除时,抛出 `ObjectiveUnregisteredError`。 */ checkUnregistered() { if (this.isUnregistered()) throw new ObjectiveUnregisteredError(this.#id); } /** * 原始记分项对象。 * @returns {Minecraft.ScoreboardObjective} 原始记分项对象。 */ get vanillaObjective() { return this.#vanillaObjective; } /** * 将此对象对应的记分项从记分板上移除。 */ unregister() { this.checkUnregistered(); VanillaScoreboard.removeObjective(this.#id); } constructor(scoreboard, name, criteria, displayName, options) { this.#scoreboard = scoreboard; this.#id = name; this.#criteria = criteria; this.#displayName = displayName; this.#vanillaObjective = options?.vanillaObjective; } /** * 获取分数持有者在记分项上的分数。 * @param {EntryValueType} one - 可能为分数持有者的值。 * @returns {number} 此分数持有者在记分项上的分数。若未设定,返回 `undefined`。 */ getScore(one) { let identity = ScoreboardEntry.getIdentity(one); if (identity instanceof Gametest.SimulatedPlayer) { identity = ScoreboardEntry.guessEntry(one).vanillaScoreboardIdentity; if (identity === undefined) return undefined; } try { return this.vanillaObjective.getScore(identity); } catch { this.checkUnregistered(); return undefined; } } /** * 获取在此记分项上拥有分数记录的分数持有者。 * @returns {ScoreboardEntry[]} 一个包含了在记分项上的分数持有者的数组。 */ getEntries() { this.checkUnregistered(); const entries = []; for (const identity of this.vanillaObjective.getParticipants()) { const entry = ScoreboardEntry.getEntry(identity.type, identity); entries.push(entry); } return entries; } /** * 遍历在此记分项上拥有分数记录的所有分数持有者,为其创建一个 * `ScoreInfo` 对象,表示了这些分数持有者在此记分项上的分数。 * @returns {ScoreInfo[]} 一个数组,包含了所有在此记分项上拥有分数记录的分数持有者的 `ScoreInfo` 对象。 */ getScoreInfos() { this.checkUnregistered(); const infos = []; for (const entry of this.getEntries()) { const info = this.getScoreInfo(entry); infos.push(info); } return infos; } /** * 获取一个 `ScoreInfo` 对象,表示了分数持有者以及他在此记分项上的分数。 * @param {EntryValueType} one - 可能为分数持有者的值。 * @param {boolean} autoInit - 如果为 `true` ,且指定的分数持有者在此记分项上的分数未定义,将会设置它的分数为0。 * @returns {ScoreInfo} */ getScoreInfo(one, autoInit = false) { let entry = ScoreboardEntry.guessEntry(one); let scoreInfo = new ScoreInfo(this, entry); if (autoInit == true && scoreInfo.score == null) scoreInfo.score = 0; return scoreInfo; } /** * 将分数持有者在记分项上的分数设置为指定的值。 * @param {EntryValueType} one - 可能为分数持有者的值。 * @param {number} score - 要设置的分数。 * @throws 若分数不在可用的范围,抛出 `ScoreRangeError`。 */ setScore(one, score) { checkScoreIsInRange(score); let identity = ScoreboardEntry.getIdentity(one); //目前Gametest.SimulatedPlayer无法传入,故使用兼容代码 if (identity instanceof Gametest.SimulatedPlayer) { Objective.playerCommand(this, "set", identity, score); return; } this.vanillaObjective.setScore(identity, score); } /** * 为分数持有者在记分项上增加分数。 * @param {EntryValueType} one - 可能为分数持有者的值。 * @param score - 要增加的分数。 * @throws 若分数不在可用的范围,抛出 `ScoreRangeError`。 */ addScore(one, score) { checkScoreIsInRange(score); score = (this.getScore(one) ?? 0) + score; //取32位整数 score = score >> 0; this.setScore(one, score); } /** * 为分数持有者在记分项上减少分数。 * @param {EntryValueType} one - 可能为分数持有者的值。 * @param {number} score - 要减少的分数。 * @throws 若分数不在可用的范围,抛出 `ScoreRangeError`。 */ removeScore(one, score) { checkScoreIsInRange(score); this.addScore(one, -score); } /** * 为分数持有者在记分项上设置一个随机的分数。随机分数使用 {@link Math.random} 生成。 * @param {EntryValueType} one - 可能为分数持有者的值。 * @param {number} min - 随机分数的最小值。 * @param {number} max - 随机分数的最大值。 * @returns {number} 随机得到的新分数。 * @throws 若分数不在可用的范围,抛出 `ScoreRangeError`。 */ randomScore(one, min = -2147483647, max = 2147483647) { checkScoreIsInRange(min, max); let vals = max - min; let randomScore = vals * Math.random(); let result = Math.round(randomScore + min); this.setScore(one, result); return result; } /** * 在记分项上重置指定分数持有者的分数。 * @param {EntryValueType} one - 分数持有者。 */ resetScore(one) { let identity = ScoreboardEntry.getIdentity(one); //目前Gametest.SimulatedPlayer无法传入,故使用兼容代码 if (identity instanceof Gametest.SimulatedPlayer) { Objective.playerCommand(this, "reset", identity); return; } if (this.vanillaObjective.hasParticipant(identity)) this.vanillaObjective.removeParticipant(identity); } /** * 重置在记分项上记录的所有分数。 */ resetScores() { for (const part of this.vanillaObjective.getParticipants()) { this.vanillaObjective.removeParticipant(part); } } /** * 为分数持有者在记分项上执行特定的操作(通过命令)。 * @param {string} option - 操作类型。 * @param {EntryValueType} one - 可能为分数持有者的值。 * @param {...any} args - 操作所需要的参数。 * @throws 未知的命令错误。 * @throws 若尝试为虚拟玩家设置分数,且世界中有相同名字的玩家时,抛出 `NameConflictError`。 */ static playerCommand(objective, option, one, ...args) { let { entity, name, type } = Objective.findCommandRequirement(one); if (type === EntryType.PLAYER || type === EntryType.ENTITY) { let cmd = Command.getCommandMoreStrict("scoreboard", "players", option, "@s", objective.#id); let result = Command.execute(entity, Command.getCommand(cmd, ...args)); if (result.statusCode === StatusCode.success && (result.successCount ?? 0) > 0) { return true; } objective.checkUnregistered(); //我觉得这里应该不会被执行到了,如果被执行到,请告诉我 throw new Error(`Could not ${option} score, ` + "maybe entity or player disappeared?" + "\n cause by: " + result.statusMessage); } else if (name) { if (EntityUtils.getWorldVanillaPlayers({ name }).length !== 0) throw new NameConflictError(name); let cmd = Command.getCommandMoreStrict("scoreboard", "players", option, name, objective.#id); let result = Command.run(Command.getCommand(cmd, ...args)); if (result.statusCode === StatusCode.success && (result.successCount ?? 0) > 0) { return true; } objective.checkUnregistered(); //我觉得这里应该不会被执行到了,如果被执行到,请告诉我 throw new Error(`Could not ${option} score, ` + "maybe entity or player disappeared?" + "\n cause by: " + result.statusMessage); } else { throw new Error("unknown error"); } } /** * 寻找用于在记分项上执行特定的操作的与分数持有者有关的信息。 * @param {EntryValueType} one - 可能为分数持有者的值。 */ static findCommandRequirement(one) { let name = undefined; let type; let entity = undefined; let scbid = (one instanceof Minecraft.ScoreboardIdentity) ? one : undefined; let entry = (one instanceof ScoreboardEntry) ? one : undefined; if (scbid != null || entry != null) { one = one; type = one.type; if (type === EntryType.ENTITY || type === EntryType.PLAYER) { try { entity = one.getEntity(); } catch { } if (entity == null) throw new Error("Could not find the entity"); } else { name = one.displayName; type = EntryType.FAKE_PLAYER; } } else if (EntityUtils.isEntity(one)) { if (EntityUtils.entityIsPlayer(one)) type = EntryType.PLAYER; else type = EntryType.ENTITY; entity = one; } else if (typeof one === "string") { type = EntryType.FAKE_PLAYER; name = one; } else { throw new UnknownEntryError(); } return { type, entity, name, scbid, entry }; } } export { Objective, ScoreInfo };