Skip to content
This repository has been archived by the owner on Jan 21, 2021. It is now read-only.

Commit

Permalink
guccious gamerous
Browse files Browse the repository at this point in the history
added .boundingBox (normalizer)
added .itemStack (normalizer)
added .location (normalizer)
added .vector (normalizer)
improved NBT parser/serializer
fixed strange entity parse behavior
new serialized item format, added amount value and used better NBT
added .data, unique stores for items, entities, and block positions
- stores player data seperately from other entity types
- can also handle OfflinePlayer objects as players
added .dist, compute distance between two positional objects
- if both objects are in-world, return infinity if not same world
- optional "flat" toggle for ignoring Y-axis in distance check
added .drop function, drop an item at a location
- can optionally add randomness with "natural" toggle
- natural toggle will emulate death drops and block mine drops
added .player, get player by name, partial match and case-insensitive
- if no online player found, searches from previously connected players
added .select, select entities with vanilla selectors
- defaults to console if no context specified
  • Loading branch information
hb432 committed Nov 12, 2020
1 parent 2ed677b commit 60fcb33
Show file tree
Hide file tree
Showing 2 changed files with 223 additions and 38 deletions.
203 changes: 173 additions & 30 deletions index.js
Original file line number Diff line number Diff line change
@@ -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`);
Expand All @@ -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(
Expand All @@ -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);
Expand All @@ -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',
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
}
}
};

Expand Down
58 changes: 50 additions & 8 deletions module.d.ts
Original file line number Diff line number Diff line change
@@ -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 <X> (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 <X> (object: X): X;
/** Extrapolates a Vector from the given input, if applicable. */
vector (object: HasVector): obuVector;
vector (...args: any[]): void;
}

0 comments on commit 60fcb33

Please sign in to comment.