diff --git a/src/main/java/com/thebrokenrail/scriptcraft/api/CustomBlock.java b/src/main/java/com/thebrokenrail/scriptcraft/api/CustomBlock.java index 08a9a27..4feb014 100644 --- a/src/main/java/com/thebrokenrail/scriptcraft/api/CustomBlock.java +++ b/src/main/java/com/thebrokenrail/scriptcraft/api/CustomBlock.java @@ -14,7 +14,7 @@ import net.minecraft.world.World; @SuppressWarnings("deprecation") public class CustomBlock extends Block { - private final Identifier id; + protected final Identifier id; public CustomBlock(Settings settings, Identifier id) { super(settings); diff --git a/src/main/java/com/thebrokenrail/scriptcraft/api/CustomBlockEntity.java b/src/main/java/com/thebrokenrail/scriptcraft/api/CustomBlockEntity.java new file mode 100644 index 0000000..0ce54b1 --- /dev/null +++ b/src/main/java/com/thebrokenrail/scriptcraft/api/CustomBlockEntity.java @@ -0,0 +1,77 @@ +package com.thebrokenrail.scriptcraft.api; + +import com.thebrokenrail.scriptcraft.quickjs.QuickJSManager; +import net.fabricmc.fabric.api.block.entity.BlockEntityClientSerializable; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.Identifier; +import net.minecraft.util.Tickable; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +public class CustomBlockEntity extends BlockEntity implements BlockEntityClientSerializable, Tickable { + private static int newObjID = 0; + + private final Identifier id; + private final int objID; + + public CustomBlockEntity(BlockEntityType type, Identifier id) { + super(type); + this.id = id; + objID = newObjID++; + + QuickJSManager.bridge("CustomBlockEntity.create", id.toString(), (double) objID); + } + + @Override + public void fromTag(CompoundTag tag) { + super.fromTag(tag); + QuickJSManager.bridge("CustomBlockEntity.fromTag", id.toString(), (double) objID, tag); + } + + @Override + public void fromClientTag(CompoundTag compoundTag) { + fromTag(compoundTag); + } + + @Override + public CompoundTag toTag(CompoundTag tag) { + return (CompoundTag) QuickJSManager.bridge("CustomBlockEntity.toTag", id.toString(), (double) objID, super.toTag(tag)); + } + + @Override + public CompoundTag toClientTag(CompoundTag compoundTag) { + return toTag(compoundTag); + } + + @Override + public void tick() { + QuickJSManager.bridge("CustomBlockEntity.tick", id.toString(), (double) objID); + } + + @Override + public void setLocation(World world, BlockPos pos) { + super.setLocation(world, pos); + QuickJSManager.bridge("CustomBlockEntity.setLocation", id.toString(), (double) objID, world, (double) pos.getX(), (double) pos.getY(), (double) pos.getZ()); + } + + @SuppressWarnings("deprecation") + @Override + protected void finalize() throws Throwable { + try { + QuickJSManager.bridge("CustomBlockEntity.free", id.toString(), (double) objID); + } finally { + super.finalize(); + } + } + + @Override + public void markDirty() { + super.markDirty(); + if (hasWorld() && getWorld() instanceof ServerWorld) { + sync(); + } + } +} diff --git a/src/main/java/com/thebrokenrail/scriptcraft/api/CustomBlockWithEntity.java b/src/main/java/com/thebrokenrail/scriptcraft/api/CustomBlockWithEntity.java new file mode 100644 index 0000000..378b47c --- /dev/null +++ b/src/main/java/com/thebrokenrail/scriptcraft/api/CustomBlockWithEntity.java @@ -0,0 +1,18 @@ +package com.thebrokenrail.scriptcraft.api; + +import net.minecraft.block.BlockEntityProvider; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.util.Identifier; +import net.minecraft.util.registry.Registry; +import net.minecraft.world.BlockView; + +public class CustomBlockWithEntity extends CustomBlock implements BlockEntityProvider { + public CustomBlockWithEntity(Settings settings, Identifier id) { + super(settings, id); + } + + @Override + public BlockEntity createBlockEntity(BlockView view) { + return new CustomBlockEntity(Registry.BLOCK_ENTITY_TYPE.get(id), id); + } +} diff --git a/src/main/java/com/thebrokenrail/scriptcraft/bridge/RegistryBridge.java b/src/main/java/com/thebrokenrail/scriptcraft/bridge/RegistryBridge.java index 4ae6bc8..74e7554 100644 --- a/src/main/java/com/thebrokenrail/scriptcraft/bridge/RegistryBridge.java +++ b/src/main/java/com/thebrokenrail/scriptcraft/bridge/RegistryBridge.java @@ -1,8 +1,11 @@ package com.thebrokenrail.scriptcraft.bridge; import com.thebrokenrail.scriptcraft.api.CustomBlock; +import com.thebrokenrail.scriptcraft.api.CustomBlockEntity; +import com.thebrokenrail.scriptcraft.api.CustomBlockWithEntity; import com.thebrokenrail.scriptcraft.api.CustomItem; import net.minecraft.block.Block; +import net.minecraft.block.entity.BlockEntityType; import net.minecraft.item.BlockItem; import net.minecraft.item.Item; import net.minecraft.util.Identifier; @@ -14,6 +17,11 @@ class RegistryBridge { Registry.register(Registry.BLOCK, new Identifier((String) args[0]), new CustomBlock((Block.Settings) args[1], new Identifier((String) args[0]))); return null; }); + Bridges.addBridge("Registry.registerBlockWithEntity", args -> { + Registry.register(Registry.BLOCK, new Identifier((String) args[0]), new CustomBlockWithEntity((Block.Settings) args[1], new Identifier((String) args[0]))); + Registry.register(Registry.BLOCK_ENTITY_TYPE, new Identifier((String) args[0]), BlockEntityType.Builder.create(() -> new CustomBlockEntity(Registry.BLOCK_ENTITY_TYPE.get(new Identifier((String) args[0])), new Identifier((String) args[0])), Registry.BLOCK.get(new Identifier((String) args[0]))).build(null)); + return null; + }); Bridges.addBridge("Registry.registerItem", args -> { Registry.register(Registry.ITEM, new Identifier((String) args[0]), new CustomItem((Item.Settings) args[1], new Identifier((String) args[0]))); return null; diff --git a/src/main/java/com/thebrokenrail/scriptcraft/bridge/WorldBridges.java b/src/main/java/com/thebrokenrail/scriptcraft/bridge/WorldBridges.java index 116139c..aa66a86 100644 --- a/src/main/java/com/thebrokenrail/scriptcraft/bridge/WorldBridges.java +++ b/src/main/java/com/thebrokenrail/scriptcraft/bridge/WorldBridges.java @@ -2,6 +2,7 @@ package com.thebrokenrail.scriptcraft.bridge; import com.thebrokenrail.scriptcraft.util.Util; import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntity; import net.minecraft.entity.Entity; import net.minecraft.util.Identifier; import net.minecraft.util.math.BlockPos; @@ -23,5 +24,15 @@ class WorldBridges { return null; } }); + + Bridges.addBridge("World.markBlockEntityDirty", args -> { + World world = (World) args[0]; + BlockPos pos = new BlockPos(Util.toDouble(args[1], 0), Util.toDouble(args[2], 0), Util.toDouble(args[3], 0)); + BlockEntity entity = world.getBlockEntity(pos); + if (entity != null) { + entity.markDirty(); + } + return null; + }); } } diff --git a/src/main/java/com/thebrokenrail/scriptcraft/quickjs/QuickJSManager.java b/src/main/java/com/thebrokenrail/scriptcraft/quickjs/QuickJSManager.java index 11e598b..8288da6 100644 --- a/src/main/java/com/thebrokenrail/scriptcraft/quickjs/QuickJSManager.java +++ b/src/main/java/com/thebrokenrail/scriptcraft/quickjs/QuickJSManager.java @@ -121,6 +121,7 @@ public class QuickJSManager { sendTaskToQuickJS(initTask); Runtime.getRuntime().addShutdownHook(new Thread(() -> { + started.set(false); Task task = new Task() { @Override protected Object run(QuickJS quickjs) { diff --git a/src/main/resources/scriptcraft/minecraft/block.ts b/src/main/resources/scriptcraft/minecraft/block.ts index 7b48d2e..933d189 100644 --- a/src/main/resources/scriptcraft/minecraft/block.ts +++ b/src/main/resources/scriptcraft/minecraft/block.ts @@ -1,6 +1,7 @@ import { World } from './world'; import { PlayerEntity } from './entity'; import { useBridge, addBridge, Identifier, Hand, Pos, ActionResult, Direction, SingleRegistry } from './core'; +import { CompoundTag } from './tag'; /** * Settings for {@link CustomBlock} @@ -131,6 +132,84 @@ export class BlockState { } } +/** + * Custom Block Entity + */ +export abstract class CustomBlockEntity { + /** + * World + */ + protected world: World; + /** + * Position + */ + protected pos: Pos; + + /** + * Set Location + * @param world New World + * @param pos New Position + */ + setLocation(world: World, pos: Pos) { + this.world = world; + this.pos = pos; + } + + /** + * Save Data To Tag + * @param tag Tag + * @returns Tag + */ + abstract toTag(tag: CompoundTag): CompoundTag; + + /** + * Load Data From Tag + * @param tag Tag + */ + abstract fromTag(tag: CompoundTag): void; + + /** + * Runs Every Tick + */ + abstract tick(): void; + + /** + * Get World + * @returns World + */ + getWorld(): World { + return this.world; + } + + /** + * Get Position + * @returns Position + */ + getPos(): Pos { + return this.pos; + } + + /** + * Mark Dirty + */ + markDirty() { + if (this.getWorld() != null) { + useBridge('World.markBlockEntityDirty', this.getWorld().javaObject, this.getPos().getX(), this.getPos().getY(), this.getPos().getZ()); + } + } +} + +/** + * {@link CustomBlock} With {@link CustomBlockEntity} + */ +export abstract class CustomBlockWithEntity extends CustomBlock { + /** + * Create Custom Block Entity + * @returns Custom Block Entity + */ + abstract createBlockEntity(): CustomBlockEntity; +} + /** * @internal */ @@ -139,21 +218,76 @@ export class BlockRegistry implements SingleRegistry { #blocks: Map; + #blockEntities: Map; + private constructor() { this.#blocks = new Map(); + this.#blockEntities = new Map(); } register(id: Identifier, obj: CustomBlock) { this.#blocks.set(id.toString(), obj); - useBridge('Registry.registerBlock', id.toString(), obj.settings.createJavaObject()); + if (obj instanceof CustomBlockWithEntity) { + useBridge('Registry.registerBlockWithEntity', id.toString(), obj.settings.createJavaObject()); + } else { + useBridge('Registry.registerBlock', id.toString(), obj.settings.createJavaObject()); + } } get(id: Identifier): CustomBlock { return this.#blocks.get(id.toString()); } + + getBlockEntity(id: Identifier, i: number): CustomBlockEntity { + return this.#blockEntities.get(id.toString())[i]; + } + + getBlockEntities(): CustomBlockEntity[] { + let out: CustomBlockEntity[] = []; + for (const key of this.#blockEntities.keys()) { + const list = this.#blockEntities.get(key); + out = out.concat(list); + } + return out; + } + + createBlockEntity(id: Identifier, i: number) { + const block = this.get(id) as CustomBlockWithEntity; + if (!this.#blockEntities.get(id.toString())) { + this.#blockEntities.set(id.toString(), []); + } + this.#blockEntities.get(id.toString()).push(block.createBlockEntity()); + } + + freeBlockEntity(id: Identifier, i: number) { + delete this.#blockEntities.get(id.toString())[i]; + } } +addBridge('CustomBlockEntity.create', (id: string, i: number) => { + BlockRegistry.INSTANCE.createBlockEntity(new Identifier(id), i); +}); + +addBridge('CustomBlockEntity.fromTag', (id: string, i: number, tag: JavaObject) => { + BlockRegistry.INSTANCE.getBlockEntity(new Identifier(id), i).fromTag(new CompoundTag(tag)); +}); + +addBridge('CustomBlockEntity.toTag', (id: string, i: number, tag: JavaObject): JavaObject => { + return BlockRegistry.INSTANCE.getBlockEntity(new Identifier(id), i).toTag(new CompoundTag(tag)).javaObject; +}); + +addBridge('CustomBlockEntity.tick', (id: string, i: number) => { + BlockRegistry.INSTANCE.getBlockEntity(new Identifier(id), i).tick(); +}); + +addBridge('CustomBlockEntity.setLocation', (id: string, i: number, world: JavaObject, x: number, y: number, z: number) => { + BlockRegistry.INSTANCE.getBlockEntity(new Identifier(id), i).setLocation(new World(world), new Pos(x, y, z)); +}); + +addBridge('CustomBlockEntity.free', (id: string, i: number) => { + BlockRegistry.INSTANCE.freeBlockEntity(new Identifier(id), i); +}); + addBridge('CustomBlock.onUse', (id: string, world: JavaObject, state: JavaObject, x: number, y: number, z: number, side: keyof typeof Direction, player: JavaObject, hand: keyof typeof Hand): string => { - console.log(typeof world); return BlockRegistry.INSTANCE.get(new Identifier(id)).onUse(new World(world), new BlockState(state), new Pos(x, y, z), Direction[side], new PlayerEntity(player), Hand[hand]); }); \ No newline at end of file diff --git a/src/main/resources/scriptcraft/minecraft/core.ts b/src/main/resources/scriptcraft/minecraft/core.ts index ef56bd8..2ab1c84 100644 --- a/src/main/resources/scriptcraft/minecraft/core.ts +++ b/src/main/resources/scriptcraft/minecraft/core.ts @@ -120,6 +120,15 @@ export class Identifier { toString(): string { return this.#namespace + ':' + this.#path; } + + /** + * Check Equality + * @param id Other Value + * @returns True If Equal + */ + equals(id: Identifier): boolean { + return id.getNamespace() === this.getNamespace() && id.getPath() === this.getPath(); + } } /** @@ -224,6 +233,15 @@ export class Pos { toString(): string { return 'Pos{' + this.getX() + ', ' + this.getY() + ', ' + this.getZ() + '}'; } + + /** + * Check Equality + * @param id Other Value + * @returns True If Equal + */ + equals(pos: Pos): boolean { + return pos.getX() === this.getX() && pos.getY() === this.getY() && pos.getZ() === this.getZ(); + } } /** diff --git a/src/main/resources/scriptcraft/minecraft/index.ts b/src/main/resources/scriptcraft/minecraft/index.ts index a676c4b..446cfd8 100644 --- a/src/main/resources/scriptcraft/minecraft/index.ts +++ b/src/main/resources/scriptcraft/minecraft/index.ts @@ -1,5 +1,5 @@ export { Identifier, ActionResult, Hand, Pos, Direction, DirectionUtil } from './core'; -export { CustomBlock, BlockSettings, BlockState } from './block'; +export { CustomBlock, CustomBlockEntity, CustomBlockWithEntity, BlockSettings, BlockState } from './block'; export { ItemStack, ItemSettings, CustomItem, BlockItem } from './item'; export { World } from './world'; export { LivingEntity, PlayerEntity } from './entity'; diff --git a/src/main/resources/scriptcraft/minecraft/tag.ts b/src/main/resources/scriptcraft/minecraft/tag.ts index 6ed22f9..a445f73 100644 --- a/src/main/resources/scriptcraft/minecraft/tag.ts +++ b/src/main/resources/scriptcraft/minecraft/tag.ts @@ -12,8 +12,10 @@ function getTagValue(obj: [boolean, BridgeValueType]): TagType { } else { return new CompoundTag(obj[1] as JavaObject); } - } else { + } else if (obj[1]) { return obj[1]; + } else { + return null; } } diff --git a/src/main/resources/scriptcraft/minecraft/world.ts b/src/main/resources/scriptcraft/minecraft/world.ts index 3b23296..382542c 100644 --- a/src/main/resources/scriptcraft/minecraft/world.ts +++ b/src/main/resources/scriptcraft/minecraft/world.ts @@ -1,4 +1,4 @@ -import { BlockState } from './block'; +import { BlockState, CustomBlockEntity, BlockRegistry } from './block'; import { Entity } from './entity'; import { useBridge, Pos, Identifier } from './core'; @@ -55,4 +55,27 @@ export class World { return null; } } + + /** + * Get All Custom Block Entities + * @returns All Custom Block Entities + */ + getCustomBlockEntities(): CustomBlockEntity[] { + return BlockRegistry.INSTANCE.getBlockEntities(); + } + + /** + * Get Custom Block Entity At Position + * @param pos Position + * @returns Custom Block Entity At Position + */ + getCustomBlockEntity(pos: Pos): CustomBlockEntity { + const list = this.getCustomBlockEntities(); + for (const entity of list) { + if (pos.equals(entity.getPos())) { + return entity; + } + } + return null; + } } \ No newline at end of file diff --git a/src/main/resources/scriptcraft/test/index.ts b/src/main/resources/scriptcraft/test/index.ts index 5cc9aea..83f425d 100644 --- a/src/main/resources/scriptcraft/test/index.ts +++ b/src/main/resources/scriptcraft/test/index.ts @@ -1,16 +1,47 @@ -import { CustomBlock, BlockSettings, Identifier, Registry, BlockState, ActionResult, World, Pos, Hand, PlayerEntity, BlockItem, ItemSettings, CustomItem, Direction, LivingEntity } from 'minecraft'; +import { BlockSettings, Identifier, Registry, BlockState, ActionResult, World, Pos, Hand, PlayerEntity, BlockItem, ItemSettings, CustomItem, Direction, LivingEntity, CustomBlockWithEntity, CustomBlockEntity, CompoundTag } from 'minecraft'; console.log('hello'); -class MyBlock extends CustomBlock { +class MyBlockEntity extends CustomBlockEntity { + ticks: number; + + toTag(tag: CompoundTag): CompoundTag { + tag.set('MyTicks', this.ticks); + return tag; + } + + fromTag(tag: CompoundTag) { + const obj = tag.get('MyTicks'); + if (typeof obj === 'number') { + this.ticks = obj; + } else { + this.ticks = 0; + } + } + + tick() { + this.ticks++; + this.markDirty(); + } +} + +class MyBlock extends CustomBlockWithEntity { constructor() { super(new BlockSettings('STONE', 'IRON')); } onUse(world: World, blockState: BlockState, blockPos: Pos, side: Direction, player: PlayerEntity, hand: Hand): ActionResult { + const entity = world.getCustomBlockEntity(blockPos); + if (entity instanceof MyBlockEntity) { + console.log('Ticks: ' + entity.ticks); + } world.setBlockState(blockPos.offset(side), BlockState.getDefaultState(new Identifier('minecraft:stone'))); return ActionResult.SUCCESS; } + + createBlockEntity(): CustomBlockEntity { + return new MyBlockEntity(); + } } Registry.register(Registry.BLOCK, new Identifier('test', 'my_block'), new MyBlock()); diff --git a/src/main/resources/scriptcraft/types/scriptcraft/index.d.ts b/src/main/resources/scriptcraft/types/scriptcraft/index.d.ts index 863f251..f53efe7 100644 --- a/src/main/resources/scriptcraft/types/scriptcraft/index.d.ts +++ b/src/main/resources/scriptcraft/types/scriptcraft/index.d.ts @@ -2,7 +2,7 @@ interface JavaObject { discriminator: 'JavaObject'; } -type BridgeValueType = string | number | boolean | JavaObject | BridgeValueType[]; +type BridgeValueType = void | string | number | boolean | JavaObject | BridgeValueType[]; type BridgeType = (...args: BridgeValueType[]) => BridgeValueType; interface ScriptCraft {