yoni-mcscripts-lib
Version:
为 Minecraft Script API 中的部分接口创建了 wrapper,并提供简单的事件管理器和任务管理器,另附有一些便于代码编写的一些小工具。
336 lines (335 loc) • 12.4 kB
JavaScript
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 };