Prep for RP

This commit is contained in:
ForestOfLight
2025-04-15 09:14:10 -07:00
Unverified
parent 53c886dd17
commit 6a0f2ba376
34 changed files with 0 additions and 0 deletions
+69
View File
@@ -0,0 +1,69 @@
/**
* Part of ItemStack Database by @gameza_src
* Unknown author
*/
const isVec3Symbol = Symbol("isVec3");
export function Vector(x = 0, y = 0, z = 0) {
if (new.target) {
this.x = Number(x);
this.y = Number(y);
this.z = Number(z);
} else {return { x: Number(x), y: Number(y), z: Number(z), __proto__: Vector.prototype };}
}
Vector.magnitude = function magnitude(vec) { return Math.sqrt(vec.x * vec.x + vec.y * vec.y + vec.z * vec.z); }
Vector.normalize = function normalize(vec) { const l = Vector.magnitude(vec); return { x: vec.x / l, y: vec.y / l, z: vec.z / l, __proto__: Vector.prototype }; }
Vector.cross = function crossProduct(a, b) { return { x: a.y * b.z - a.z * b.y, y: a.x * b.z - a.z * b.x, z: a.x * b.y - a.y * b.x, __proto__: Vector.prototype }; }
Vector.dot = function dot(a, b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
Vector.angleBetween = function angleBetween(a, b) { return Math.acos(Vector.dot(a, b) / (Vector.magnitude(a) * Vector.magnitude(b))); }
Vector.subtract = function subtract(a, b) { return { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z, __proto__: Vector.prototype } };
Vector.add = function add(a, b) { return { x: a.x + b.x, y: a.y + b.y, z: a.z + b.z, __proto__: Vector.prototype } };
Vector.multiply = function multiply(vec, num) {
if (typeof num == "number") return { x: vec.x * num, y: vec.y * num, z: vec.z * num, __proto__: Vector.prototype };
return { x: vec.x * num.x, y: vec.y * num.y, z: vec.z * num.z, __proto__: Vector.prototype };
}
Vector.isVec3 = function isVec3(vec) { return vec[isVec3Symbol] === true; }
Vector.floor = function floor(vec) { return { x: Math.floor(vec.x), y: Math.floor(vec.y), z: Math.floor(vec.z), __proto__: Vector.prototype }; }
Vector.projection = function projection(a, b) { return Vector.multiply(b, Vector.dot(a, b) / ((b.x * b.x + b.y * b.y + b.z * b.z) ** 2)); }
Vector.rejection = function rejection(a, b) { return Vector.subtract(a, Vector.projection(a, b)); }
Vector.reflect = function reflect(v, n) { return Vector.subtract(v, Vector.multiply(n, 2 * Vector.dot(v, n))); }
Vector.lerp = function lerp(a, b, t) { return Vector.multiply(a, 1 - t).add(Vector.multiply(b, t)); }
Vector.distance = function distance(a, b) { return Vector.magnitude(Vector.subtract(a, b)); }
Vector.from = function from(object) {
if (Vector.isVec3(object)) return object;
if (Array.isArray(object)) return new Vector(object[0], object[1], object[2]);
const { x = 0, y = 0, z = 0 } = object ?? {};
return { x: Number(x), y: Number(y), z: Number(z), __proto__: Vector.prototype };
}
Vector.sort = function sort(vec1, vec2) {
const [x1, x2] = vec1.x < vec2.x ? [vec1.x, vec2.x] : [vec2.x, vec1.x];
const [y1, y2] = vec1.y < vec2.y ? [vec1.y, vec2.y] : [vec2.y, vec1.y];
const [z1, z2] = vec1.z < vec2.z ? [vec1.z, vec2.z] : [vec2.z, vec1.z];
return [{ x: x1, y: y1, z: z1, __proto__: Vector.prototype }, { x: x2, y: y2, z: z2, __proto__: Vector.prototype }];
}
Vector.up = { x: 0, y: 1, z: 0, __proto__: Vector.prototype };
Vector.down = { x: 0, y: -1, z: 0, __proto__: Vector.prototype };
Vector.right = { x: 1, y: 0, z: 0, __proto__: Vector.prototype };
Vector.left = { x: -1, y: 0, z: 0, __proto__: Vector.prototype };
Vector.forward = { x: 0, y: 0, z: 1, __proto__: Vector.prototype };
Vector.backward = { x: 0, y: 0, z: -1, __proto__: Vector.prototype };
Vector.zero = { x: 0, y: 0, z: 0, __proto__: Vector.prototype };
Vector.prototype = {
distance(vec) { return Vector.distance(this, vec); },
lerp(vec, t) { return Vector.lerp(this, vec, t); },
projection(vec) { return Vector.projection(this, vec); },
reflect(vec) { return Vector.reflect(this, vec); },
rejection(vec) { return Vector.rejection(this, vec); },
cross(vec) { return Vector.cross(this, vec); },
dot(vec) { return Vector.dot(this, vec); },
floor() { return Vector.floor(this); },
add(vec) { return Vector.add(this, vec); },
subtract(vec) { return Vector.subtract(this, vec); },
multiply(num) { return Vector.multiply(this, num); },
get length() { return Vector.magnitude(this); },
get normalized() { return Vector.normalize(this); },
x: 0,
y: 0,
z: 0,
[isVec3Symbol]: true,
toString() { return `<${this.x}, ${this.y}, ${this.z}>`; }
}
@@ -0,0 +1,166 @@
/**
* @license
* MIT License
*
* Copyright (c) 2024 ForestOfLight
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import { world } from '@minecraft/server';
import IPC from '../../lib/ipc/ipc';
import Command from './Command';
import Rule from './Rule';
import { CommandCallbackRequest, CommandPrefixRequest, Ready, RegisterCommand, RegisterExtension, RegisterRule, RuleValueRequest, RuleValueSet, CommandPrefixResponse, RuleValueResponse } from './extension.ipc';
class CanopyExtension {
name;
version;
author;
description;
#commands = {};
#rules = {};
#isRegistrationReady = false;
constructor({ name = 'Unnamed', version = '1.0.0', author = 'Unknown', description = { text: '' } }) {
this.id = this.#makeID(name);
this.name = name;
this.version = version;
this.author = author;
this.description = description;
this.#registerExtension();
this.#setupCommandPrefix();
this.#handleCommandCallbacks();
this.#handleRuleValueRequests();
this.#handleRuleValueSetters();
}
addCommand(command) {
if (!(command instanceof Command))
throw new Error('Command must be an instance of Command.');
this.#commands[command.getName()] = command;
if (this.#isRegistrationReady)
this.#registerCommand(command);
}
addRule(rule) {
if (!(rule instanceof Rule))
throw new Error('Rule must be an instance of Rule.');
this.#rules[rule.getID()] = rule;
if (this.#isRegistrationReady)
this.#registerRule(rule);
}
getRuleValue(ruleID) {
return this.#rules[ruleID].getValue();
}
#makeID(name) {
if (typeof name !== 'string')
throw new Error(`[${name}] Could not register extension. Extension name must be a string.`);
const id = name.toLowerCase().replace(/[^a-z0-9 ]/g, '').replace(/ /g, '_');
if (id.length === 0)
throw new Error(`[${name}] Could not register extension. Extension name must contain at least one alphanumeric character.`);
return id;
}
#registerExtension() {
IPC.once('canopyExtension:ready', Ready, () => {
IPC.send('canopyExtension:registerExtension', RegisterExtension, {
name: this.name,
version: this.version,
author: this.author,
description: this.description
});
});
IPC.once(`canopyExtension:${this.id}:ready`, Ready, () => {
this.#isRegistrationReady = true;
for (const rule of Object.values(this.#rules))
this.#registerRule(rule);
for (const command of Object.values(this.#commands))
this.#registerCommand(command);
});
}
#registerCommand(command) {
IPC.send(`canopyExtension:${this.id}:registerCommand`, RegisterCommand, {
name: command.getName(),
description: command.getDescription(),
usage: command.getUsage(),
callback: false,
args: command.getArgs(),
contingentRules: command.getContingentRules(),
adminOnly: command.isAdminOnly(),
helpEntries: command.getHelpEntries(),
helpHidden: command.isHelpHidden(),
extensionName: this.name
});
}
#handleCommandCallbacks() {
IPC.on(`canopyExtension:${this.id}:commandCallbackRequest`, CommandCallbackRequest, (cmdData) => {
if (cmdData.senderName === undefined)
return;
const sender = world.getPlayers({ name: cmdData.senderName })[0];
if (!sender)
throw new Error(`Sender ${cmdData.senderName} of ${cmdData.commandName} not found.`);
const parsedArgs = JSON.parse(cmdData.args);
this.#commands[cmdData.commandName].runCallback(sender, parsedArgs);
});
}
#registerRule(rule) {
IPC.send(`canopyExtension:${this.id}:registerRule`, RegisterRule, {
identifier: rule.getID(),
description: rule.getDescription(),
contingentRules: rule.getContigentRules(),
independentRules: rule.getIndependentRules(),
extensionName: this.name
});
if (rule.getValue() === true)
rule.onEnable();
}
#handleRuleValueRequests() {
IPC.handle(`canopyExtension:${this.id}:ruleValueRequest`, RuleValueRequest, RuleValueResponse, (data) => {
const rule = this.#rules[data.ruleID];
if (!rule)
throw new Error(`Rule ${data.ruleID} not found.`);
const value = rule.getValue();
return { value };
});
}
#handleRuleValueSetters() {
IPC.on(`canopyExtension:${this.id}:ruleValueSet`, RuleValueSet, (data) => {
const rule = this.#rules[data.ruleID];
if (!rule)
throw new Error(`Rule ${data.ruleID} not found.`);
rule.setValue(data.value);
});
}
#setupCommandPrefix() {
const prefix = IPC.invoke(`canopyExtension:commandPrefixRequest`, CommandPrefixRequest, void 0, CommandPrefixResponse).then(result => {
Command.setPrefix(result.prefix);
});
}
}
export { CanopyExtension, Command, Rule };
@@ -0,0 +1,103 @@
/**
* @license
* MIT License
*
* Copyright (c) 2024 ForestOfLight
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
class Command {
#name;
#description;
#usage;
#callback;
#args;
#contingentRules;
#adminOnly;
#helpEntries;
#helpHidden;
static #prefix = '';
constructor({ name, description = '', usage, callback, args = [], contingentRules = [], adminOnly = false, helpEntries = [], helpHidden = false }) {
this.#name = name;
this.#description = description;
this.#usage = usage;
this.#callback = callback;
this.#args = args;
this.#contingentRules = contingentRules;
this.#adminOnly = adminOnly;
this.#helpEntries = helpEntries;
this.#helpHidden = helpHidden;
}
getName() {
return this.#name;
}
getDescription() {
return this.#description;
}
getUsage() {
return this.#usage;
}
getCallback() {
return this.#callback;
}
getArgs() {
return this.#args;
}
getContingentRules() {
return this.#contingentRules;
}
isAdminOnly() {
return this.#adminOnly;
}
getHelpEntries() {
return this.#helpEntries;
}
isHelpHidden() {
return this.#helpHidden;
}
runCallback(sender, args) {
this.#callback(sender, args);
}
sendUsage(sender) {
sender.sendMessage(`§cUsage: ${Command.#prefix}${this.#usage}`);
}
static setPrefix(prefix) {
Command.#prefix = prefix;
}
static getPrefix() {
return Command.#prefix;
}
}
export default Command;
+76
View File
@@ -0,0 +1,76 @@
/**
* @license
* MIT License
*
* Copyright (c) 2024 ForestOfLight
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import { world } from '@minecraft/server';
class Rule {
#identifier;
#description;
#contingentRules;
#independentRules;
constructor({ identifier, description, contingentRules = [], independentRules = [], onEnableCallback = () => {}, onDisableCallback = () => {} }) {
this.#identifier = identifier;
this.#description = description;
this.#contingentRules = contingentRules;
this.#independentRules = independentRules;
this.onEnable = onEnableCallback;
this.onDisable = onDisableCallback;
}
getID() {
return this.#identifier;
}
getDescription() {
return this.#description;
}
getContigentRules() {
return this.#contingentRules;
}
getIndependentRules() {
return this.#independentRules;
}
getValue() {
const value = world.getDynamicProperty(this.#identifier);
if (String(value) === 'true')
return true;
if (['false', 'undefined'].includes(String(value)))
return false;
throw new Error(`Rule ${this.#identifier} has an invalid value: ${value} (${typeof value})`);
}
setValue(value) {
if (value === true)
this.onEnable();
else
this.onDisable();
world.setDynamicProperty(this.#identifier, value);
}
}
export default Rule;
@@ -0,0 +1,70 @@
/* eslint-disable new-cap */
import { PROTO } from '../ipc/ipc'
const description = PROTO.Object({
text: PROTO.Optional(PROTO.String),
translate: PROTO.Optional(PROTO.String),
with: PROTO.Optional(PROTO.Array(PROTO.String))
});
export const Ready = PROTO.Void;
export const RegisterExtension = PROTO.Object({
name: PROTO.String,
version: PROTO.String,
author: PROTO.String,
description: description,
isEndstone: PROTO.Boolean
});
export const RegisterCommand = PROTO.Object({
name: PROTO.String,
description: description,
usage: PROTO.String,
callback: PROTO.Optional(PROTO.Undefined),
args: PROTO.Optional(PROTO.Array(PROTO.Object({
type: PROTO.String,
name: PROTO.String
}))),
contingentRules: PROTO.Optional(PROTO.Array(PROTO.String)),
adminOnly: PROTO.Optional(PROTO.Boolean),
helpEntries: PROTO.Optional(PROTO.Array(PROTO.Object({
usage: PROTO.String,
description: description
}))),
helpHidden: PROTO.Optional(PROTO.Boolean),
extensionName: PROTO.Optional(PROTO.String)
});
export const RegisterRule = PROTO.Object({
identifier: PROTO.String,
description: description,
contingentRules: PROTO.Optional(PROTO.Array(PROTO.String)),
independentRules: PROTO.Optional(PROTO.Array(PROTO.String)),
extensionName: PROTO.Optional(PROTO.String)
});
export const RuleValueRequest = PROTO.Object({
ruleID: PROTO.String
});
export const RuleValueResponse = PROTO.Object({
value: PROTO.Boolean
});
export const RuleValueSet = PROTO.Object({
ruleID: PROTO.String,
value: PROTO.Boolean
});
export const CommandCallbackRequest = PROTO.Object({
commandName: PROTO.String,
senderName: PROTO.Optional(PROTO.String),
args: PROTO.String
});
export const CommandPrefixRequest = PROTO.Void;
export const CommandPrefixResponse = PROTO.Object({
prefix: PROTO.String
});
+103
View File
@@ -0,0 +1,103 @@
/**
* @license
* MIT License
*
* Copyright (c) 2025 OmniacDev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
export declare namespace PROTO {
interface Serializable<T> {
serialize(value: T, stream: ByteQueue): Generator<void, void, void>;
deserialize(stream: ByteQueue): Generator<void, T, void>;
}
class ByteQueue {
private _buffer;
private _data_view;
private _length;
private _offset;
get end(): number;
get front(): number;
get data_view(): DataView;
constructor(size?: number);
write(...values: number[]): void;
read(amount?: number): number[];
ensure_capacity(size: number): void;
static from_uint8array(array: Uint8Array): ByteQueue;
to_uint8array(): Uint8Array;
}
namespace MIPS {
function serialize(byte_queue: PROTO.ByteQueue): Generator<void, string, void>;
function deserialize(str: string): Generator<void, PROTO.ByteQueue, void>;
}
const Void: PROTO.Serializable<void>;
const Null: PROTO.Serializable<null>;
const Undefined: PROTO.Serializable<undefined>;
const Int8: PROTO.Serializable<number>;
const Int16: PROTO.Serializable<number>;
const Int32: PROTO.Serializable<number>;
const UInt8: PROTO.Serializable<number>;
const UInt16: PROTO.Serializable<number>;
const UInt32: PROTO.Serializable<number>;
const UVarInt32: PROTO.Serializable<number>;
const Float32: PROTO.Serializable<number>;
const Float64: PROTO.Serializable<number>;
const String: PROTO.Serializable<string>;
const Boolean: PROTO.Serializable<boolean>;
const UInt8Array: PROTO.Serializable<Uint8Array>;
const Date: PROTO.Serializable<Date>;
function Object<T extends object>(obj: {
[K in keyof T]: PROTO.Serializable<T[K]>;
}): PROTO.Serializable<T>;
function Array<T>(value: PROTO.Serializable<T>): PROTO.Serializable<T[]>;
function Tuple<T extends any[]>(...values: {
[K in keyof T]: PROTO.Serializable<T[K]>;
}): PROTO.Serializable<T>;
function Optional<T>(value: PROTO.Serializable<T>): PROTO.Serializable<T | undefined>;
function Map<K, V>(key: PROTO.Serializable<K>, value: PROTO.Serializable<V>): PROTO.Serializable<Map<K, V>>;
function Set<V>(value: PROTO.Serializable<V>): PROTO.Serializable<Set<V>>;
type Endpoint = string;
type Header = {
guid: string;
encoding: string;
index: number;
final: boolean;
};
const Endpoint: PROTO.Serializable<Endpoint>;
const Header: PROTO.Serializable<Header>;
}
export declare namespace NET {
function serialize(byte_queue: PROTO.ByteQueue, max_size?: number): Generator<void, string[], void>;
function deserialize(strings: string[]): Generator<void, PROTO.ByteQueue, void>;
function emit<S extends PROTO.Serializable<T>, T>(endpoint: string, serializer: S & PROTO.Serializable<T>, value: T): Generator<void, void, void>;
function listen<T, S extends PROTO.Serializable<T>>(endpoint: string, serializer: S & PROTO.Serializable<T>, callback: (value: T) => Generator<void, void, void>): () => void;
}
export declare namespace IPC {
/** Sends a message with `args` to `channel` */
function send<S extends PROTO.Serializable<T>, T>(channel: string, serializer: S & PROTO.Serializable<T>, value: T): void;
/** Sends an `invoke` message through IPC, and expects a result asynchronously. */
function invoke<TS extends PROTO.Serializable<T>, T, RS extends PROTO.Serializable<R>, R>(channel: string, serializer: TS & PROTO.Serializable<T>, value: T, deserializer: RS & PROTO.Serializable<R>): Promise<R>;
/** Listens to `channel`. When a new message arrives, `listener` will be called with `listener(args)`. */
function on<S extends PROTO.Serializable<T>, T>(channel: string, deserializer: S & PROTO.Serializable<T>, listener: (value: T) => void): () => void;
/** Listens to `channel` once. When a new message arrives, `listener` will be called with `listener(args)`, and then removed. */
function once<S extends PROTO.Serializable<T>, T>(channel: string, deserializer: S & PROTO.Serializable<T>, listener: (value: T) => void): () => void;
/** Adds a handler for an `invoke` IPC. This handler will be called whenever `invoke(channel, ...args)` is called */
function handle<TS extends PROTO.Serializable<T>, T, RS extends PROTO.Serializable<R>, R>(channel: string, deserializer: TS & PROTO.Serializable<T>, serializer: RS & PROTO.Serializable<R>, listener: (value: T) => R): () => void;
}
export default IPC;
+593
View File
@@ -0,0 +1,593 @@
/**
* @license
* MIT License
*
* Copyright (c) 2025 OmniacDev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import { ScriptEventSource, system, world } from '@minecraft/server';
export var PROTO;
(function (PROTO) {
class ByteQueue {
get end() {
return this._length + this._offset;
}
get front() {
return this._offset;
}
get data_view() {
return this._data_view;
}
constructor(size = 256) {
this._buffer = new Uint8Array(size);
this._data_view = new DataView(this._buffer.buffer);
this._length = 0;
this._offset = 0;
}
write(...values) {
this.ensure_capacity(values.length);
this._buffer.set(values, this.end);
this._length += values.length;
}
read(amount = 1) {
if (this._length > 0) {
const max_amount = amount > this._length ? this._length : amount;
const values = this._buffer.subarray(this._offset, this._offset + max_amount);
this._length -= max_amount;
this._offset += max_amount;
return globalThis.Array.from(values);
}
return [];
}
ensure_capacity(size) {
if (this.end + size > this._buffer.length) {
const larger_buffer = new Uint8Array((this.end + size) * 2);
larger_buffer.set(this._buffer.subarray(this._offset, this.end), 0);
this._buffer = larger_buffer;
this._offset = 0;
this._data_view = new DataView(this._buffer.buffer);
}
}
static from_uint8array(array) {
const byte_queue = new ByteQueue();
byte_queue._buffer = array;
byte_queue._length = array.length;
byte_queue._offset = 0;
byte_queue._data_view = new DataView(array.buffer);
return byte_queue;
}
to_uint8array() {
return this._buffer.subarray(this._offset, this.end);
}
}
PROTO.ByteQueue = ByteQueue;
let MIPS;
(function (MIPS) {
function* serialize(byte_queue) {
const uint8array = byte_queue.to_uint8array();
let str = '(0x';
for (let i = 0; i < uint8array.length; i++) {
const hex = uint8array[i].toString(16).padStart(2, '0').toUpperCase();
str += hex;
yield;
}
str += ')';
return str;
}
MIPS.serialize = serialize;
function* deserialize(str) {
if (str.startsWith('(0x') && str.endsWith(')')) {
const result = [];
const hex_str = str.slice(3, str.length - 1);
for (let i = 0; i < hex_str.length; i++) {
const hex = hex_str[i] + hex_str[++i];
result.push(parseInt(hex, 16));
yield;
}
return ByteQueue.from_uint8array(new Uint8Array(result));
}
return new ByteQueue();
}
MIPS.deserialize = deserialize;
})(MIPS = PROTO.MIPS || (PROTO.MIPS = {}));
PROTO.Void = {
*serialize() { },
*deserialize() { }
};
PROTO.Null = {
*serialize() { },
*deserialize() {
return null;
}
};
PROTO.Undefined = {
*serialize() { },
*deserialize() {
return undefined;
}
};
PROTO.Int8 = {
*serialize(value, stream) {
const length = 1;
stream.write(...globalThis.Array(length).fill(0));
stream.data_view.setInt8(stream.end - length, value);
},
*deserialize(stream) {
const value = stream.data_view.getInt8(stream.front);
stream.read(1);
return value;
}
};
PROTO.Int16 = {
*serialize(value, stream) {
const length = 2;
stream.write(...globalThis.Array(length).fill(0));
stream.data_view.setInt16(stream.end - length, value);
},
*deserialize(stream) {
const value = stream.data_view.getInt16(stream.front);
stream.read(2);
return value;
}
};
PROTO.Int32 = {
*serialize(value, stream) {
const length = 4;
stream.write(...globalThis.Array(length).fill(0));
stream.data_view.setInt32(stream.end - length, value);
},
*deserialize(stream) {
const value = stream.data_view.getInt32(stream.front);
stream.read(4);
return value;
}
};
PROTO.UInt8 = {
*serialize(value, stream) {
const length = 1;
stream.write(...globalThis.Array(length).fill(0));
stream.data_view.setUint8(stream.end - length, value);
},
*deserialize(stream) {
const value = stream.data_view.getUint8(stream.front);
stream.read(1);
return value;
}
};
PROTO.UInt16 = {
*serialize(value, stream) {
const length = 2;
stream.write(...globalThis.Array(length).fill(0));
stream.data_view.setUint16(stream.end - length, value);
},
*deserialize(stream) {
const value = stream.data_view.getUint16(stream.front);
stream.read(2);
return value;
}
};
PROTO.UInt32 = {
*serialize(value, stream) {
const length = 4;
stream.write(...globalThis.Array(length).fill(0));
stream.data_view.setUint32(stream.end - length, value);
},
*deserialize(stream) {
const value = stream.data_view.getUint32(stream.front);
stream.read(4);
return value;
}
};
PROTO.UVarInt32 = {
*serialize(value, stream) {
while (value >= 0x80) {
stream.write((value & 0x7f) | 0x80);
value >>= 7;
yield;
}
stream.write(value);
},
*deserialize(stream) {
let value = 0;
let size = 0;
let byte;
do {
byte = stream.read()[0];
value |= (byte & 0x7f) << (size * 7);
size += 1;
yield;
} while ((byte & 0x80) !== 0 && size < 10);
return value;
}
};
PROTO.Float32 = {
*serialize(value, stream) {
const length = 4;
stream.write(...globalThis.Array(length).fill(0));
stream.data_view.setFloat32(stream.end - length, value);
},
*deserialize(stream) {
const value = stream.data_view.getFloat32(stream.front);
stream.read(4);
return value;
}
};
PROTO.Float64 = {
*serialize(value, stream) {
const length = 8;
stream.write(...globalThis.Array(length).fill(0));
stream.data_view.setFloat64(stream.end - length, value);
},
*deserialize(stream) {
const value = stream.data_view.getFloat64(stream.front);
stream.read(8);
return value;
}
};
PROTO.String = {
*serialize(value, stream) {
yield* PROTO.UVarInt32.serialize(value.length, stream);
for (let i = 0; i < value.length; i++) {
const code = value.charCodeAt(i);
yield* PROTO.UVarInt32.serialize(code, stream);
}
},
*deserialize(stream) {
const length = yield* PROTO.UVarInt32.deserialize(stream);
let value = '';
for (let i = 0; i < length; i++) {
const code = yield* PROTO.UVarInt32.deserialize(stream);
value += globalThis.String.fromCharCode(code);
}
return value;
}
};
PROTO.Boolean = {
*serialize(value, stream) {
stream.write(value ? 1 : 0);
},
*deserialize(stream) {
const value = stream.read()[0];
return value === 1;
}
};
PROTO.UInt8Array = {
*serialize(value, stream) {
yield* PROTO.UVarInt32.serialize(value.length, stream);
stream.write(...value);
},
*deserialize(stream) {
const length = yield* PROTO.UVarInt32.deserialize(stream);
return new Uint8Array(stream.read(length));
}
};
PROTO.Date = {
*serialize(value, stream) {
yield* PROTO.Float64.serialize(value.getTime(), stream);
},
*deserialize(stream) {
return new globalThis.Date(yield* PROTO.Float64.deserialize(stream));
}
};
function Object(obj) {
return {
*serialize(value, stream) {
for (const key in obj) {
yield* obj[key].serialize(value[key], stream);
}
},
*deserialize(stream) {
const result = {};
for (const key in obj) {
result[key] = yield* obj[key].deserialize(stream);
}
return result;
}
};
}
PROTO.Object = Object;
function Array(value) {
return {
*serialize(array, stream) {
yield* PROTO.UVarInt32.serialize(array.length, stream);
for (const item of array) {
yield* value.serialize(item, stream);
}
},
*deserialize(stream) {
const result = [];
const length = yield* PROTO.UVarInt32.deserialize(stream);
for (let i = 0; i < length; i++) {
result[i] = yield* value.deserialize(stream);
}
return result;
}
};
}
PROTO.Array = Array;
function Tuple(...values) {
return {
*serialize(tuple, stream) {
for (let i = 0; i < values.length; i++) {
yield* values[i].serialize(tuple[i], stream);
}
},
*deserialize(stream) {
const result = [];
for (let i = 0; i < values.length; i++) {
result[i] = yield* values[i].deserialize(stream);
}
return result;
}
};
}
PROTO.Tuple = Tuple;
function Optional(value) {
return {
*serialize(optional, stream) {
yield* PROTO.Boolean.serialize(optional !== undefined, stream);
if (optional !== undefined) {
yield* value.serialize(optional, stream);
}
},
*deserialize(stream) {
const defined = yield* PROTO.Boolean.deserialize(stream);
if (defined) {
return yield* value.deserialize(stream);
}
return undefined;
}
};
}
PROTO.Optional = Optional;
function Map(key, value) {
return {
*serialize(map, stream) {
yield* PROTO.UVarInt32.serialize(map.size, stream);
for (const [k, v] of map.entries()) {
yield* key.serialize(k, stream);
yield* value.serialize(v, stream);
}
},
*deserialize(stream) {
const size = yield* PROTO.UVarInt32.deserialize(stream);
const result = new globalThis.Map();
for (let i = 0; i < size; i++) {
const k = yield* key.deserialize(stream);
const v = yield* value.deserialize(stream);
result.set(k, v);
}
return result;
}
};
}
PROTO.Map = Map;
function Set(value) {
return {
*serialize(set, stream) {
yield* PROTO.UVarInt32.serialize(set.size, stream);
for (const [_, v] of set.entries()) {
yield* value.serialize(v, stream);
}
},
*deserialize(stream) {
const size = yield* PROTO.UVarInt32.deserialize(stream);
const result = new globalThis.Set();
for (let i = 0; i < size; i++) {
const v = yield* value.deserialize(stream);
result.add(v);
}
return result;
}
};
}
PROTO.Set = Set;
PROTO.Endpoint = PROTO.String;
PROTO.Header = PROTO.Object({
guid: PROTO.String,
encoding: PROTO.String,
index: PROTO.UVarInt32,
final: PROTO.Boolean
});
})(PROTO || (PROTO = {}));
export var NET;
(function (NET) {
const FRAG_MAX = 2048;
const ENCODING = 'mcbe-ipc:v3';
const ENDPOINTS = new Map();
function* serialize(byte_queue, max_size = Infinity) {
const uint8array = byte_queue.to_uint8array();
const result = [];
let acc_str = '';
let acc_size = 0;
for (let i = 0; i < uint8array.length; i++) {
const char_code = uint8array[i] | (uint8array[++i] << 8);
const utf16_size = char_code <= 0x7f ? 1 : char_code <= 0x7ff ? 2 : char_code <= 0xffff ? 3 : 4;
const char_size = char_code > 0xff ? utf16_size : 2;
if (acc_size + char_size > max_size) {
result.push(acc_str);
acc_str = '';
acc_size = 0;
}
if (char_code > 0xff) {
acc_str += String.fromCharCode(char_code);
acc_size += utf16_size;
}
else {
acc_str += char_code.toString(16).padStart(2, '0').toUpperCase();
acc_size += 2;
}
yield;
}
result.push(acc_str);
return result;
}
NET.serialize = serialize;
function* deserialize(strings) {
const result = [];
for (let i = 0; i < strings.length; i++) {
const str = strings[i];
for (let j = 0; j < str.length; j++) {
const char_code = str.charCodeAt(j);
if (char_code <= 0xff) {
const hex = str[j] + str[++j];
const hex_code = parseInt(hex, 16);
result.push(hex_code & 0xff);
result.push(hex_code >> 8);
}
else {
result.push(char_code & 0xff);
result.push(char_code >> 8);
}
yield;
}
yield;
}
return PROTO.ByteQueue.from_uint8array(new Uint8Array(result));
}
NET.deserialize = deserialize;
system.afterEvents.scriptEventReceive.subscribe(event => {
system.runJob((function* () {
const [serialized_endpoint, serialized_header] = event.id.split(':');
const endpoint_stream = yield* PROTO.MIPS.deserialize(serialized_endpoint);
const endpoint = yield* PROTO.Endpoint.deserialize(endpoint_stream);
const listeners = ENDPOINTS.get(endpoint);
if (event.sourceType === ScriptEventSource.Server && listeners) {
const header_stream = yield* PROTO.MIPS.deserialize(serialized_header);
const header = yield* PROTO.Header.deserialize(header_stream);
for (let i = 0; i < listeners.length; i++) {
yield* listeners[i](header, event.message);
}
}
})());
});
function create_listener(endpoint, listener) {
let listeners = ENDPOINTS.get(endpoint);
if (!listeners) {
listeners = new Array();
ENDPOINTS.set(endpoint, listeners);
}
listeners.push(listener);
return () => {
const idx = listeners.indexOf(listener);
if (idx !== -1)
listeners.splice(idx, 1);
if (listeners.length === 0) {
ENDPOINTS.delete(endpoint);
}
};
}
function generate_id() {
const r = (Math.random() * 0x100000000) >>> 0;
return ((r & 0xff).toString(16).padStart(2, '0') +
((r >> 8) & 0xff).toString(16).padStart(2, '0') +
((r >> 16) & 0xff).toString(16).padStart(2, '0') +
((r >> 24) & 0xff).toString(16).padStart(2, '0')).toUpperCase();
}
function* emit(endpoint, serializer, value) {
const guid = generate_id();
const endpoint_stream = new PROTO.ByteQueue();
yield* PROTO.Endpoint.serialize(endpoint, endpoint_stream);
const serialized_endpoint = yield* PROTO.MIPS.serialize(endpoint_stream);
const RUN = function* (header, serialized_packet) {
const header_stream = new PROTO.ByteQueue();
yield* PROTO.Header.serialize(header, header_stream);
const serialized_header = yield* PROTO.MIPS.serialize(header_stream);
world
.getDimension('overworld')
.runCommand(`scriptevent ${serialized_endpoint}:${serialized_header} ${serialized_packet}`);
};
const packet_stream = new PROTO.ByteQueue();
yield* serializer.serialize(value, packet_stream);
const serialized_packets = yield* serialize(packet_stream, FRAG_MAX);
for (let i = 0; i < serialized_packets.length; i++) {
const serialized_packet = serialized_packets[i];
yield* RUN({ guid, encoding: ENCODING, index: i, final: i === serialized_packets.length - 1 }, serialized_packet);
}
}
NET.emit = emit;
function listen(endpoint, serializer, callback) {
const buffer = new Map();
const listener = function* (payload, serialized_packet) {
let fragment = buffer.get(payload.guid);
if (!fragment) {
fragment = { size: -1, serialized_packets: [], data_size: 0 };
buffer.set(payload.guid, fragment);
}
if (payload.final) {
fragment.size = payload.index + 1;
}
fragment.serialized_packets[payload.index] = serialized_packet;
fragment.data_size += payload.index + 1;
if (fragment.size !== -1 && fragment.data_size === (fragment.size * (fragment.size + 1)) / 2) {
const stream = yield* deserialize(fragment.serialized_packets);
const value = yield* serializer.deserialize(stream);
yield* callback(value);
buffer.delete(payload.guid);
}
};
return create_listener(endpoint, listener);
}
NET.listen = listen;
})(NET || (NET = {}));
export var IPC;
(function (IPC) {
/** Sends a message with `args` to `channel` */
function send(channel, serializer, value) {
system.runJob(NET.emit(`ipc:${channel}:send`, serializer, value));
}
IPC.send = send;
/** Sends an `invoke` message through IPC, and expects a result asynchronously. */
function invoke(channel, serializer, value, deserializer) {
system.runJob(NET.emit(`ipc:${channel}:invoke`, serializer, value));
return new Promise(resolve => {
const terminate = NET.listen(`ipc:${channel}:handle`, deserializer, function* (value) {
resolve(value);
terminate();
});
});
}
IPC.invoke = invoke;
/** Listens to `channel`. When a new message arrives, `listener` will be called with `listener(args)`. */
function on(channel, deserializer, listener) {
return NET.listen(`ipc:${channel}:send`, deserializer, function* (value) {
listener(value);
});
}
IPC.on = on;
/** Listens to `channel` once. When a new message arrives, `listener` will be called with `listener(args)`, and then removed. */
function once(channel, deserializer, listener) {
const terminate = NET.listen(`ipc:${channel}:send`, deserializer, function* (value) {
listener(value);
terminate();
});
return terminate;
}
IPC.once = once;
/** Adds a handler for an `invoke` IPC. This handler will be called whenever `invoke(channel, ...args)` is called */
function handle(channel, deserializer, serializer, listener) {
return NET.listen(`ipc:${channel}:invoke`, deserializer, function* (value) {
const result = listener(value);
yield* NET.emit(`ipc:${channel}:handle`, serializer, result);
});
}
IPC.handle = handle;
})(IPC || (IPC = {}));
export default IPC;