diff --git a/index.js b/index.js index 66dfe0a..9cfa167 100644 --- a/index.js +++ b/index.js @@ -1,12 +1,16 @@ const NMS = `net.minecraft.server.${server.getClass().getCanonicalName().split('.')[3]}`; const ArrayList = core.type('java.util.ArrayList'); +const Block = core.type('org.bukkit.block.Block'); +const BlockStateMeta = core.type('org.bukkit.inventory.meta.BlockStateMeta'); const BoundingBox = core.type('org.bukkit.util.BoundingBox'); +const CommandSender = core.type('org.bukkit.command.CommandSender'); const Entity = core.type('org.bukkit.entity.Entity'); const EntityType = core.type('org.bukkit.entity.EntityType'); const ItemStack = core.type('org.bukkit.inventory.ItemStack'); const Location = core.type('org.bukkit.Location'); const Material = core.type('org.bukkit.Material'); +const NamespacedKey = core.type('org.bukkit.NamespacedKey'); const NBTTagByte = core.type(`${NMS}.NBTTagByte`); const NBTTagByteArray = core.type(`${NMS}.NBTTagByteArray`); const NBTTagCompound = core.type(`${NMS}.NBTTagCompound`); @@ -18,38 +22,132 @@ const NBTTagList = core.type(`${NMS}.NBTTagList`); const NBTTagLong = core.type(`${NMS}.NBTTagLong`); const NBTTagShort = core.type(`${NMS}.NBTTagShort`); const NBTTagString = core.type(`${NMS}.NBTTagString`); +const OfflinePlayer = core.type('org.bukkit.OfflinePlayer'); +const PersistentDataContainer = core.type('org.bukkit.persistence.PersistentDataContainer'); +const PersistentDataHolder = core.type('org.bukkit.persistence.PersistentDataHolder'); +const PersistentDataType = core.type('org.bukkit.persistence.PersistentDataType'); +const SpawnReason = core.type('org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason'); const UUID = core.type('java.util.UUID'); const Vector = core.type('org.bukkit.util.Vector'); +/** @type {import('./module').Main} */ const $ = { - parse: (object) => { + boundingBox (arg1) { + const thing = $.parse(arg1); + if (thing instanceof Block) { + const bounds = thing.getBoundingBox(); + if (bounds.volume === 0) { + // don't use default zero-volume bounding box + return BoundingBox.of(thing.getLocation(), 0, 0, 0); + } else { + return bounds; + } + } else if (thing instanceof BoundingBox) { + return thing; + } else if (thing instanceof Entity) { + return thing.getBoundingBox(); + } + }, + data (arg1, arg2 = 'default') { + const thing = $.parse(arg1); + const store = `mantle/${arg2}`; + if (typeof arg2 !== 'string') { + throw new TypeError('Argument 2 (if specified) must be of type "string"'); + } else if (thing instanceof Block) { + return core.data(`${store}/block/${thing.getWorld().getUID().toString()}/${thing.getBlockKey().toString(16)}`); + } else if (thing instanceof OfflinePlayer) { + return core.data(`${store}/player/${thing.getUniqueId().toString()}`); + } else if (thing instanceof Entity) { + return core.data(`${store}/entity/${thing.getUniqueId().toString()}`); + } else if (thing instanceof ItemStack) { + const meta = thing.getItemMeta(); + if (meta instanceof PersistentDataHolder) { + const key = [ new NamespacedKey(core.plugin, `mantle/data/${arg2}`), PersistentDataType.STRING ]; + const container = meta.getPersistentDataContainer(); + container.has(...key) || (container.set(...key, UUID.randomUUID().toString()), thing.setItemMeta(meta)); + // retroactively add key to serialized item stack input + arg1 && arg1.class === 'ItemStack' && Object.assign(arg1, $.serialize(thing)); + return core.data(`${store}/itemStack/${container.get(...key)}`); + } + } + }, + dist (arg1, arg2, arg3 = false) { + const from = $.vector(arg1); + const to = $.vector(arg2); + if (from === void 0) { + throw new TypeError('Argument 1 must be of type "HasVector"'); + } else if (to === void 0) { + throw new TypeError('Argument 2 must be of type "HasVector"'); + } else if (typeof arg3 !== 'boolean') { + throw new TypeError('Argument 3 (if specified) must be of type "boolean"'); + } else { + const fromLocation = $.location(arg1); + const toLocation = $.location(arg2); + if (fromLocation && toLocation && fromLocation.getWorld() !== toLocation.getWorld()) { + return Infinity; + } else { + const delta = $.vector(from).subtract(to); + arg3 && delta.setY(0); + return Math.sqrt(Math.pow(delta.getX(), 2), Math.pow(delta.getY(), 2), Math.pow(delta.getZ(), 2)); + } + } + }, + drop (arg1, arg2, arg3 = false) { + const location = $.location(arg1); + const item = $.itemStack(arg2); + if (location === void 0) { + throw new TypeError('Argument 1 must be of type "HasLocation"'); + } else if (item === void 0) { + throw new TypeError('Argument 2 must be of type "HasItemStack"'); + } else if (typeof arg3 !== 'boolean') { + throw new TypeError('Argument 3 (if specified) must be of type "boolean"'); + } else if (item) { + location.getWorld()[arg3 ? 'dropItemNaturally' : 'dropItem'](location, item); + } + }, + itemStack (arg1) { + const stuff = $.parse(arg1); + if (stuff instanceof Block) { + const item = new ItemStack(stuff.getType()); + const meta = item.getItemMeta(); + meta instanceof BlockStateMeta && (meta.setBlockState(stuff.getState()), item.setItemMeta(meta)); + return item; + } else if (stuff instanceof Entity) { + return stuff.getEquipment().getItemInMainHand(); + } else if (stuff instanceof ItemStack) { + return stuff; + } + }, + location (arg1) { + const stuff = $.parse(arg1); + if (stuff instanceof Block || stuff instanceof Entity) { + return stuff.getLocation(); + } else if (stuff instanceof Location) { + return stuff; + } + }, + parse (object) { if (object && typeof object.class === 'string') { switch (object.class) { case 'BoundingBox': return BoundingBox.of($.parse(object.min), $.parse(object.max)); case 'Entity': - const world = server.getWorld( - new UUID(object.nbt.value.WorldUUIDMost.value, object.nbt.value.WorldUUIDLeast.value) - ); - const entity = world.spawnEntity( - new Location( - world, - object.nbt.value.Pos.value[0].value, - object.nbt.value.Pos.value[1].value, - object.nbt.value.Pos.value[2].value, - object.nbt.value.Rotation.value[0].value, - object.nbt.value.Rotation.value[1].value - ), - EntityType[object.type] - ); - entity.getHandle().load($.parse(object.nbt)); - return entity; + try { + let entity = $.select().filter((entity) => object.uuid === entity.getUniqueId().toString())[0]; + if (!entity) { + const location = $.parse(object.location); + entity = location.getWorld().spawnEntity(location, EntityType[object.type], SpawnReason.CUSTOM); + } + entity.getHandle().load($.parse(object.nbt)); + return entity; + } catch (error) { + console.error('An error occured while attempting to parse a serialized entity!'); + console.error(error.stack || error.message || error); + return null; + } case 'ItemStack': - const item_stack = new ItemStack( - Material[object.type], - object.nbt.value.Count.value - ).ensureServerConversions(); - item_stack.getHandle().setTag($.parse(object.nbt.value.tag)); + const item_stack = new ItemStack(Material[object.type], object.amount).ensureServerConversions(); + item_stack.getHandle().setTag($.parse(object.nbt)); return item_stack; case 'Location': return new Location( @@ -64,11 +162,11 @@ const $ = { return NBTTagByte.a(object.value); case 'NBTTagByteArray': const nbt_tag_byte_array = new NBTTagByteArray(new ArrayList()); - for (const item of object.value) nbt_tag_byte_array.add(item); + for (const value of object.value) nbt_tag_byte_array.add(NBTTagByte.a(value)); return nbt_tag_byte_array; case 'NBTTagCompound': const nbt_tag_compound = new NBTTagCompound(); - for (const item in object.value) nbt_tag_compound.set(item, $.parse(object.value[item])); + for (const key in object.value) nbt_tag_compound.set(key, $.parse(object.value[key])); return nbt_tag_compound; case 'NBTTagDouble': return NBTTagDouble.a(object.value); @@ -78,27 +176,59 @@ const $ = { return NBTTagInt.a(object.value); case 'NBTTagIntArray': const nbt_tag_int_array = new NBTTagIntArray(new ArrayList()); - for (const item of object.value) nbt_tag_int_array.add(item); + for (const value of object.value) nbt_tag_int_array.add(NBTTagInt.a(value)); return nbt_tag_int_array; case 'NBTTagList': const nbt_tag_list = new NBTTagList(); - for (const item of object.value) nbt_tag_list.add($.parse(item)); + for (const value of object.value) nbt_tag_list.add($.parse(value)); return nbt_tag_list; case 'NBTTagLong': return NBTTagLong.a(object.value); case 'NBTTagLongArray': const nbt_tag_long_array = new NBTTagLongArray(new ArrayList()); - for (const item of object.value) nbt_tag_long_array.add(item); + for (const value of object.value) nbt_tag_long_array.add(NBTTagLong.a(value)); return nbt_tag_long_array; case 'NBTTagShort': return NBTTagShort.a(object.value); case 'NBTTagString': return NBTTagString.a(object.value); + case 'Vector': + return new Vector(object.x, object.y, object.z); } } return object; }, - serialize: (object) => { + player (arg1) { + if (typeof arg1 !== 'string') { + throw new TypeError('Argument 1 must be of type "string"'); + } else { + const online = server.getPlayer(arg1); + if (online) { + return online; + } else { + for (const offline of server.getOfflinePlayers()) { + if (offline.getName().toLowerCase().startsWith(arg1.toLowerCase())) { + return offline; + } + } + } + } + }, + select (arg1 = '@e', arg2 = server.getConsoleSender()) { + const context = $.parse(arg2); + if (typeof arg1 !== 'string') { + throw new TypeError('Argument 1 (if specified) must be of type "string"'); + } else if (context instanceof CommandSender) { + try { + return [ ...server.selectEntities(context, arg1) ]; + } catch (error) { + throw new SyntaxError('The provided selector was invalid and could not be parsed.'); + } + } else { + throw new TypeError('Argument 2 (if specified) must be of type "HasEntity" or "CommandSender"'); + } + }, + serialize (object) { if (object instanceof BoundingBox) { return { class: 'BoundingBox', @@ -108,13 +238,16 @@ const $ = { } else if (object instanceof Entity) { return { class: 'Entity', + location: $.serialize(object.getLocation()), nbt: $.serialize(object.getHandle().save(new NBTTagCompound())), - type: object.getType().name() + type: object.getType().name(), + uuid: object.getUniqueId().toString() }; } else if (object instanceof ItemStack) { return { + amount: object.getAmount(), class: 'ItemStack', - nbt: $.serialize(object.ensureServerConversions().getHandle().save(new NBTTagCompound())), + nbt: $.serialize(object.ensureServerConversions().getHandle().getTag()), type: object.getType().name() }; } else if (object instanceof Location) { @@ -159,6 +292,16 @@ const $ = { } else { return object; } + }, + vector (arg1) { + const stuff = $.parse(arg1); + if (stuff instanceof Block || stuff instanceof Entity) { + return stuff.getLocation().toVector(); + } else if (stuff instanceof Location) { + return stuff.toVector(); + } else if (stuff instanceof Vector) { + return stuff; + } } }; diff --git a/module.d.ts b/module.d.ts index cf6c9c4..e9805b3 100644 --- a/module.d.ts +++ b/module.d.ts @@ -1,35 +1,77 @@ //**/ import { obuBoundingBox, obeEntity, obiItemStack, obLocation, obuVector } from '../../../dict/classes'; /* -import { obuBoundingBox, obeEntity, obiItemStack, obLocation, obuVector } from '../core/dict/classes'; //*/ +import { obbBlock, obuBoundingBox, obeEntity, obiItemStack, obLocation, obuVector, obeItem, obcCommandSender, obOfflinePlayer } from '../core/dict/classes'; //*/ + +type HasBoundingBox = obuBoundingBox | SerialBoundingBox | IsPhysical; +type HasItemStack = obiItemStack | SerialItemStack | IsPhysical; +type HasLocation = obLocation | SerialLocation | IsPhysical; +type HasVector = obuVector | SerialVector | HasLocation; + +type isContainer = obbBlock | obeEntity | obiItemStack | SerialEntity | SerialItemStack; +type IsPhysical = obbBlock | obeEntity | SerialEntity; type SerialBoundingBox = { class: 'BoundingBox', min: SerialVector, max: SerialVector }; type SerialEntity = { class: 'Entity', nbt: SerialNBTTagCompound, type: string }; type SerialItemStack = { class: 'ItemStack', nbt: SerialNBTTagCompound, type: string }; type SerialLocation = { class: 'Location', pitch: number, world: string, x: number, y: number, yaw: number, z: number }; +type SerialNBTTag = SerialNBTTagByte | SerialNBTTagByteArray | SerialNBTTagCompound | SerialNBTTagDouble | SerialNBTTagFloat | SerialNBTTagInt | SerialNBTTagIntArray | SerialNBTTagList | SerialNBTTagLong | SerialNBTTagLongArray | SerialNBTTagShort | SerialNBTTagString type SerialNBTTagByte = { class: 'NBTTagByte', value: number }; -type SerialNBTTagByteArray = { class: 'NBTTagByteArray', value: SerialNBTTag[] }; -type SerialNBTTagCompound = { class: 'NBTTagCompound', value: any }; +type SerialNBTTagByteArray = { class: 'NBTTagByteArray', value: number[] }; +type SerialNBTTagCompound = { class: 'NBTTagCompound', value: { [x: string]: SerialNBTTag } }; type SerialNBTTagDouble = { class: 'NBTTagDouble', value: number }; type SerialNBTTagFloat = { class: 'NBTTagFloat', value: number }; type SerialNBTTagInt = { class: 'NBTTagInt', value: number }; -type SerialNBTTagIntArray = { class: 'NBTTagIntArray', value: SerialNBTTagInt[] }; -type SerialNBTTagList = { class: 'NBTTagList', value: any[] }; +type SerialNBTTagIntArray = { class: 'NBTTagIntArray', value: number[] }; +type SerialNBTTagList = { class: 'NBTTagList', value: SerialNBTTag[] }; type SerialNBTTagLong = { class: 'NBTTagLong', value: number }; -type SerialNBTTagLongArray = { class: 'NBTTagLongArray', value: SerialNBTTagLong[] }; +type SerialNBTTagLongArray = { class: 'NBTTagLongArray', value: number[] }; type SerialNBTTagShort = { class: 'NBTTagShort', value: number }; type SerialNBTTagString = { class: 'NBTTagString', value: string }; type SerialVector = { class: 'Vector', x: number, y: number, z: number }; export interface Main { - /** Parses a previously serialized Bounding Box, Entity, Item Stack, Location, or Vector. */ + /** Extrapolates a Bounding Box from the given input, if applicable. */ + boundingBox (object: HasBoundingBox): obuBoundingBox; + boundingBox (...args: any[]): void; + /** Returns a data store for the given input, if applicable. */ + data (object: IsContainer, prefix?: string): any; + data (...args: any[]): void; + /** Calculates the distance between two positional objects. If both objects are of type 'HasLocation' and are not in + * the same world, Infinity will be returned. If "flat" is true, the Y axis will be ignored in the calculation. */ + dist (from: HasVector, to: HasVector, flat?: boolean): number; + dist (...args: any[]): never; + /** Drops an item stack at a specific location. If "naturally" is true, will add randomness to the drop akin to + * the randomness of entity loot drops and mined block drops. */ + drop (location: HasLocation, item: HasItemStack, naturally?: boolean): obeItem; + drop (...args: any[]): never; + /** Extrapolates an Item Stack from the given input, if applicable. */ + itemStack (object: HasItemStack): obiItemStack; + itemStack (...args: any[]): void; + /** Extrapolates a Location from the given input, if applicable. */ + location (object: HasLocation): obLocation; + location (...args: any[]): void; + /** Parses a previously serialized Bounding Box, Entity, Item Stack, Location, or Vector. If the input does not match + * one of these types, the input itself will be returned instead. */ parse (object: SerialBoundingBox): obuBoundingBox; parse (object: SerialEntity): obeEntity; parse (object: SerialItemStack): obiItemStack; parse (object: SerialLocation): obLocation; parse (object: SerialVector): obuVector; - /** Serializes a Bounding Box, Entity, Item Stack, Location, or Vector. */ + parse (object: X): X; + /** */ + player (name: string): obOfflinePlayer; + player (...args: any[]): void; + /** Returns a list of entities matching the given selector and context. If unspecified, selector will default to '@e' + * and context will default to console. The console context targets from the spawn point of the main world. */ + select (query: string, context?: obcCommandSender): obeEntity[]; + /** Serializes a Bounding Box, Entity, Item Stack, Location, or Vector. If the input does not match + * one of these types, the input itself will be returned instead. */ serialize (object: obuBoundingBox): SerialBoundingBox; serialize (object: obeEntity): SerialEntity; serialize (object: obiItemStack): SerialItemStack; serialize (object: obLocation): SerialLocation; serialize (object: obuVector): SerialVector; + serialize (object: X): X; + /** Extrapolates a Vector from the given input, if applicable. */ + vector (object: HasVector): obuVector; + vector (...args: any[]): void; } \ No newline at end of file