starblast-modding
Version:
A powerful library for interacting with the Starblast Modding API
348 lines (279 loc) • 9.33 kB
JavaScript
'use strict';
const createUUID = require("../../utils/createUUID.js");
const defineProperties = require("../../utils/defineProperties.js");
const limitedJSON = require("../../utils/limitedJSON.js");
const toString = require("../../utils/toString.js");
const UIElementGroup = require("./Group.js");
const specialComponents = new Map([
["scoreboard", {
position: Object.freeze([80, 0, 20, 52]),
visible: true,
clickable: false,
shortcut: String.fromCharCode(9)
}],
["radar_background", {
position: Object.freeze([0, 0, 100, 100]),
visible: true,
clickable: false,
shortcut: "R"
}]
])
/**
* The UI Component instance, representing visual and interactable components in-game.
* This is an extension of UIElementGroup, but has more features and acts at the upmost parent of any UI Component.
* @extends {UIElementGroup}
* @param {string} data.id Custom ID for this UI Component
* @param {boolean} [data.visible=true] Component visibility
* @param {boolean} [data.clickable=false] Component clickability (and will yield event when clicked)
* @param {boolean} [data.persistent=false] Whether this component will also show for ships joining later from this set (only available for UI Component whose parent manager is {@link ShipManager#ui_components})
* @param {string | null} data.shortcut Shortcut of this UI Component (if clickable)
* @since 1.4.2-alpha6
*/
class UIComponent extends UIElementGroup {
constructor (data, strictMode = false) {
super(data, strictMode);
/**
* Custom ID for this UI Component
* @name UIComponent#id
* @type {string}
* @readonly
*/
/**
* UUID for this UI Component
* @name UIComponent#uuid
* @type {string}
* @readonly
*/
let id = data?.id;
if ("string" !== typeof id) {
if (strictMode) throw new Error(`Expects string value from ${this.constructor.name}.id. Got ${toString(id)} instead.`);
id = toString(id);
}
defineProperties(this, { id, uuid: createUUID() });
this.setVisible(data?.visible, strictMode).setClickable(data?.clickable, strictMode).setPersistent(data?.persistent).setShortcut(data?.shortcut, strictMode);
this.raw.lastClickable = this.clickable;
let _this = this;
let specialComponent = specialComponents.get(this.id);
if (specialComponent != null) Object.assign(this.raw, specialComponent);
Object.defineProperty(this.raw, 'backup', {
get () { return this._backup ?? _this.#backup() },
set (val) { this._backup = _this.#backup() }
});
}
#backup () {
let clone = this.clone();
clone.raw.parent = this.raw.parent;
clone.raw.active = true;
return clone;
}
set (data, strictMode = false) {
super.set(data, strictMode);
data = data || {};
// can't do this since visibility at nullish is "true"
// if ("visible" in data)
this.setVisible(data.visible, strictMode);
// same with clickable
// if ("clickable" in data)
this.setClickable(data.clickable, strictMode);
if ("persistent" in data) this.setPersistent(data.persistent);
// shortcut also
// if ("shortcut" in data)
this.setShortcut(data.shortcut, strictMode);
return this;
}
setPosition (position, strictMode = false) {
// handle scoreboard and radar background component (You can't set it btw)
if (specialComponents.has(this.id)) {
if (strictMode) throw new Error("Scoreboard and Background components' position cannot be altered.");
}
else super.setPosition(position, strictMode);
return this;
}
/**
* Sets visibility of this UI Component
* @param {boolean} visible - Visibility to set
* @param {boolean} [strictMode = false] Whether strict mode will be enabled (invalid value will be silently replaced with default value) or throw an error instead
* @returns {UIComponent} The UI Component in question
*/
setVisible (visible, strictMode = false) {
if (specialComponents.has(this.id)) {
if (strictMode) throw new Error("Scoreboard and Background components' visibility cannot be altered.");
}
else this.raw.visible = visible == null || !!visible;
return this;
}
/**
* Sets clickability of this UI Component
* @param {boolean} clickable - Clickability to set
* @param {boolean} [strictMode = false] Whether strict mode will be enabled (invalid value will be silently replaced with default value) or throw an error instead
* @returns {UIComponent} The UI Component in question
*/
setClickable (clickable, strictMode = false) {
if (specialComponents.has(this.id)) {
if (strictMode) throw new Error("Scoreboard and Background components' clickability cannot be altered.");
}
else this.raw.clickable = !!clickable;
return this;
}
/**
* Sets persistency of this UI Component
* @param {boolean} persistent - Persistency to set
* @returns {UIComponent} The UI Component in question
*/
setPersistent (persistent) {
this.raw.persistent = !!persistent;
return this;
}
/**
* Sets shortcut assigned to this UI Component
* @param {string | null} shortcut - Shortcut to set
* @param {boolean} [strictMode = false] Whether strict mode will be enabled (invalid value will be silently replaced with default value) or throw an error instead
* @returns {UIComponent} The UI Component in question
*/
setShortcut (shortcut, strictMode) {
if (specialComponents.has(this.id)) {
if (strictMode) throw new Error("Scoreboard and Background components' shortcut cannot be altered.");
}
else {
if (shortcut != null) {
if ("string" !== typeof shortcut) {
if (strictMode) throw new Error(`Failed to parse UI component shortcut. Given value is non-null and not a string: ${shortcut}`);
shortcut = toString(shortcut);
}
if (shortcut.length < 1) {
if (strictMode) throw new Error("Failed to parse UI component shortcut. It must not be an empty string (Maybe you want to use `null` instead?)");
shortcut = null;
}
else if (shortcut.length > 1) {
if (strictMode) throw new Error(`Failed to parse UI component shortcut. Expect one-byte string, got more: "${shortcut}"`);
shortcut = shortcut[0].toUpperCase();
}
}
else shortcut = null;
this.raw.shortcut = shortcut;
}
return this;
}
/**
* Hide this component from player
* @returns {UIComponent} The UI Component in question
*/
hide () {
if (specialComponents.has(this.id)) {
if (strictMode) throw new Error("Scoreboard and Background components cannot be hidden through Modding API.");
}
else if (this.isActive()) {
this.raw.active = false;
this.update(true);
}
return this;
}
/**
* Hide this component from player
* @returns {UIComponent} The UI Component in question
*/
remove () {
return this.hide();
}
/**
* Show this component to player (from hidden status)
* @returns {UIComponent} The UI Component in question
*/
show () {
this.raw.active = true;
return this.update();
}
/**
* Update the content of this component and show to player
* @param {boolean} forceRefresh - Whether to force update to this component
* @returns {UIComponent} The UI Component in question
*/
update (forceRefresh = false) {
let parent = this.raw.parent;
if (parent?.isActive?.() || forceRefresh) parent.updateById?.(this.id);
return this;
}
/**
* Visibility of this UI Component
* @type {boolean}
* @readonly
*/
get visible () {
return this.raw.visible;
}
/**
* Clickability of this UI Component
* @type {boolean}
* @readonly
*/
get clickable () {
return this.raw.clickable;
}
/**
* Persistency of this UI Component
* @type {boolean}
* @readonly
*/
get persistent () {
return this.raw.persistent;
}
/**
* Shortcut for this UI Component
* @type {string}
* @readonly
*/
get shortcut () {
return this.raw.shortcut;
}
/**
* Parent manager of this UI Component
* @type {UIComponentManager}
* @readonly
*/
get parent () {
return this.raw.parent;
}
/**
* Whether this component is active (active status indicates that this UI component has an active parent and is shown to player)
* @returns {boolean} The active status
*/
isActive () {
return !!this.parent?.isActive?.() && this.raw.active;
}
markAsInactive () {
}
serialize () {
let raw = {
...limitedJSON(this, ["id", "visible", "clickable", "shortcut"]),
...super.serialize()
};
delete raw.type;
return raw;
}
toJSON () {
let raw = limitedJSON(this, ["id", "position", "visible", "clickable", "shortcut", "components"]);
raw.components = raw.components.map(e => e.toJSON()).flat(Infinity);
if (!this.isActive()) raw.visible = raw.clickable = false;
if (!raw.visible || (!raw.position[2] && !raw.position[3])) {
raw.components = [];
}
if (!raw.visible) {
raw.position = [0, 0, 0, 0];
if (!raw.clickable) delete raw.visible;
}
if (raw.components.length < 1) delete raw.components;
if (raw.visible) delete raw.visible;
if (!raw.clickable) {
delete raw.shortcut;
delete raw.clickable;
}
else if (raw.shortcut == null) delete raw.shortcut;
if (specialComponents.has(this.id)) delete raw.position;
else {
// hide UIs with zero width and height so they won't prompt warning
if (!raw.position[2] && !raw.position[3]) raw.position = [-1, 0, 0, 0];
}
return raw;
}
}
module.exports = UIComponent;