diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitServer.java b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitServer.java index f407086..f04f8d3 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitServer.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitServer.java @@ -15,6 +15,7 @@ import java.awt.image.BufferedImage; import java.io.File; import java.net.InetAddress; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; @@ -22,6 +23,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; +import org.patchbukkit.world.PatchBukkitWorld; import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; @@ -547,10 +549,13 @@ public class PatchBukkitServer implements Server { @Override public @NotNull List getWorlds() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException( - "Unimplemented method 'getWorlds'" - ); + var response = NativeBridgeFfi.getWorlds(patchbukkit.common.EmptyRequest.newBuilder().build()); + if (response == null) return List.of(); + List list = new ArrayList<>(); + for (patchbukkit.common.UUID u : response.getWorldUuidsList()) { + list.add(PatchBukkitWorld.getOrCreate(u.getValue())); + } + return list; } @Override @@ -587,7 +592,10 @@ public class PatchBukkitServer implements Server { @Override public @NotNull World getRespawnWorld() { - // TODO Auto-generated method stub + var worlds = getWorlds(); + if (!worlds.isEmpty()) { + return worlds.get(0); + } throw new UnsupportedOperationException( "Unimplemented method 'getRespawnWorld'" ); @@ -603,26 +611,32 @@ public class PatchBukkitServer implements Server { @Override public @Nullable World getWorld(@NotNull String name) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException( - "Unimplemented method 'getWorld'" - ); + for (World world : getWorlds()) { + if (world.getName().equalsIgnoreCase(name)) { + return world; + } + } + return null; } @Override public @Nullable World getWorld(@NotNull UUID uid) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException( - "Unimplemented method 'getWorld'" - ); + for (World world : getWorlds()) { + if (world.getUID().equals(uid)) { + return world; + } + } + return null; } @Override public @Nullable World getWorld(@NotNull Key worldKey) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException( - "Unimplemented method 'getWorld'" - ); + for (World world : getWorlds()) { + if (world.getKey().equals(worldKey)) { + return world; + } + } + return null; } @Override @@ -686,6 +700,10 @@ public class PatchBukkitServer implements Server { ); } + public int getReloadCount() { + return 0; + } + @Override public void reloadData() { // TODO Auto-generated method stub @@ -1297,10 +1315,7 @@ public class PatchBukkitServer implements Server { @Override public @NotNull WarningState getWarningState() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException( - "Unimplemented method 'getWarningState'" - ); + return WarningState.DEFAULT; } @Override diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/bridge/BridgeUtils.java b/java/patchbukkit/src/main/java/org/patchbukkit/bridge/BridgeUtils.java index 083a548..be65a4b 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/bridge/BridgeUtils.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/bridge/BridgeUtils.java @@ -10,4 +10,19 @@ public class BridgeUtils { public static patchbukkit.common.UUID convertUuid(UUID uuid) { return patchbukkit.common.UUID.newBuilder().setValue(uuid.toString()).build(); } + + public static patchbukkit.common.Location convertLocation(org.bukkit.Location location) { + var pos = patchbukkit.common.Vec3.newBuilder() + .setX(location.getX()) + .setY(location.getY()) + .setZ(location.getZ()) + .build(); + var worldUuid = convertUuid(location.getWorld().getUID()); + return patchbukkit.common.Location.newBuilder() + .setWorld(patchbukkit.common.World.newBuilder().setUuid(worldUuid).build()) + .setPosition(pos) + .setYaw(location.getYaw()) + .setPitch(location.getPitch()) + .build(); + } } diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitEntity.java b/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitEntity.java index 7627133..35244aa 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitEntity.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitEntity.java @@ -1,8 +1,12 @@ package org.patchbukkit.entity; +import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -50,13 +54,23 @@ import net.kyori.adventure.sound.Sound.Source; import net.kyori.adventure.text.Component; import net.kyori.adventure.util.TriState; import patchbukkit.bridge.NativeBridgeFfi; +import patchbukkit.entity.SetEntityVelocityRequest; +import patchbukkit.entity.TeleportEntityRequest; public class PatchBukkitEntity implements Entity { protected final UUID uuid; - private final String name; - private static PermissibleBase perm; + protected final String name; + private PermissibleBase perm; private boolean visibleByDefault = true; + private final Map> metadataMap = new HashMap<>(); + + private PermissibleBase getPermissible() { + if (this.perm == null) { + this.perm = new PermissibleBase(this); + } + return this.perm; + } public PatchBukkitEntity( UUID uuid, @@ -66,66 +80,52 @@ public class PatchBukkitEntity implements Entity { this.name = name; } - private static PermissibleBase getPermissibleBase() { - if (PatchBukkitEntity.perm == null) { - PatchBukkitEntity.perm = new PermissibleBase(new ServerOperator() { - - @Override - public boolean isOp() { - return false; - } - - @Override - public void setOp(boolean value) { - - } - }); - } - return PatchBukkitEntity.perm; - } - @Override public void setMetadata(@NotNull String metadataKey, @NotNull MetadataValue newMetadataValue) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'setMetadata'"); + List list = metadataMap.computeIfAbsent(metadataKey, k -> new ArrayList<>()); + list.removeIf(v -> v.getOwningPlugin() == newMetadataValue.getOwningPlugin()); + list.add(newMetadataValue); } @Override public @NotNull List getMetadata(@NotNull String metadataKey) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getMetadata'"); + List list = metadataMap.get(metadataKey); + return list != null ? Collections.unmodifiableList(new ArrayList<>(list)) : Collections.emptyList(); } @Override public boolean hasMetadata(@NotNull String metadataKey) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'hasMetadata'"); + List list = metadataMap.get(metadataKey); + return list != null && !list.isEmpty(); } @Override public void removeMetadata(@NotNull String metadataKey, @NotNull Plugin owningPlugin) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'removeMetadata'"); + List list = metadataMap.get(metadataKey); + if (list != null) { + list.removeIf(v -> v.getOwningPlugin() == owningPlugin); + if (list.isEmpty()) { + metadataMap.remove(metadataKey); + } + } } @Override public void sendMessage(String message) { - } @Override public void sendMessage(String... messages) { - } @Override public void sendMessage(UUID sender, String message) { - this.sendMessage(message); // Most entities don't know about senders + this.sendMessage(message); } @Override public void sendMessage(UUID sender, String... messages) { - this.sendMessage(messages); // Most entities don't know about senders + this.sendMessage(messages); } @Override @@ -135,83 +135,72 @@ public class PatchBukkitEntity implements Entity { @Override public @NotNull Component name() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'name'"); + return Component.text(this.name); } @Override public boolean isPermissionSet(@NotNull String name) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'isPermissionSet'"); + return getPermissible().isPermissionSet(name); } @Override public boolean isPermissionSet(@NotNull Permission perm) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'isPermissionSet'"); + return getPermissible().isPermissionSet(perm); } @Override public boolean hasPermission(String name) { - return PatchBukkitEntity.getPermissibleBase().hasPermission(name); + return getPermissible().hasPermission(name); } @Override public boolean hasPermission(Permission perm) { - return PatchBukkitEntity.getPermissibleBase().hasPermission(perm); + return getPermissible().hasPermission(perm); } @Override public @NotNull PermissionAttachment addAttachment(@NotNull Plugin plugin, @NotNull String name, boolean value) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'addAttachment'"); + return getPermissible().addAttachment(plugin, name, value); } @Override public @NotNull PermissionAttachment addAttachment(@NotNull Plugin plugin) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'addAttachment'"); + return getPermissible().addAttachment(plugin); } @Override - public @Nullable PermissionAttachment addAttachment(@NotNull Plugin plugin, @NotNull String name, boolean value, - int ticks) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'addAttachment'"); + public @Nullable PermissionAttachment addAttachment(@NotNull Plugin plugin, @NotNull String name, boolean value, int ticks) { + return getPermissible().addAttachment(plugin, name, value, ticks); } @Override public @Nullable PermissionAttachment addAttachment(@NotNull Plugin plugin, int ticks) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'addAttachment'"); + return getPermissible().addAttachment(plugin, ticks); } @Override public void removeAttachment(@NotNull PermissionAttachment attachment) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'removeAttachment'"); + getPermissible().removeAttachment(attachment); } @Override public void recalculatePermissions() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'recalculatePermissions'"); + getPermissible().recalculatePermissions(); } @Override public @NotNull Set getEffectivePermissions() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getEffectivePermissions'"); + return getPermissible().getEffectivePermissions(); } @Override public boolean isOp() { - return PatchBukkitEntity.getPermissibleBase().isOp(); + return getPermissible().isOp(); } @Override public void setOp(boolean value) { - PatchBukkitEntity.getPermissibleBase().setOp(value); + getPermissible().setOp(value); } @Override @@ -282,16 +271,38 @@ public class PatchBukkitEntity implements Entity { return newLoc; } + private Vector velocity = new Vector(0, 0, 0); + @Override public void setVelocity(@NotNull Vector velocity) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'setVelocity'"); + this.velocity = velocity.clone(); + var request = SetEntityVelocityRequest.newBuilder() + .setUuid(BridgeUtils.convertUuid(this.uuid)) + .setX(velocity.getX()) + .setY(velocity.getY()) + .setZ(velocity.getZ()) + .build(); + NativeBridgeFfi.setEntityVelocity(request); } @Override public @NotNull Vector getVelocity() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getVelocity'"); + return this.velocity.clone(); + } + + @Override + public boolean teleport(@NotNull Location location) { + var request = TeleportEntityRequest.newBuilder() + .setUuid(BridgeUtils.convertUuid(this.uuid)) + .setLocation(BridgeUtils.convertLocation(location)) + .build(); + NativeBridgeFfi.teleportEntity(request); + return true; + } + + @Override + public boolean teleport(@NotNull Location location, @NotNull TeleportCause cause) { + return teleport(location); } @Override @@ -332,33 +343,26 @@ public class PatchBukkitEntity implements Entity { @Override public void setRotation(float yaw, float pitch) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'setRotation'"); + Location loc = getLocation(); + teleport(new Location(loc.getWorld(), loc.getX(), loc.getY(), loc.getZ(), yaw, pitch)); + } + + @Override + public void lookAt(double x, double y, double z, @NotNull LookAnchor entityAnchor) { + Location loc = getLocation(); + double dx = x - loc.getX(); + double dy = y - loc.getY(); + double dz = z - loc.getZ(); + double r = Math.sqrt(dx * dx + dz * dz); + float yaw = (float) Math.toDegrees(Math.atan2(-dx, dz)); + float pitch = (float) Math.toDegrees(Math.atan2(-dy, r)); + teleport(new Location(loc.getWorld(), loc.getX(), loc.getY(), loc.getZ(), yaw, pitch)); } @Override public boolean teleport(@NotNull Location location, @NotNull TeleportCause cause, @NotNull TeleportFlag @NotNull... teleportFlags) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'teleport'"); - } - - @Override - public void lookAt(double x, double y, double z, @NotNull LookAnchor entityAnchor) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'lookAt'"); - } - - @Override - public boolean teleport(@NotNull Location location) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'teleport'"); - } - - @Override - public boolean teleport(@NotNull Location location, @NotNull TeleportCause cause) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'teleport'"); + return teleport(location, cause); } @Override diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitLivingEntity.java b/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitLivingEntity.java index aa02f9a..b5c62d9 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitLivingEntity.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitLivingEntity.java @@ -1,7 +1,11 @@ package org.patchbukkit.entity; +import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.function.Consumer; @@ -35,8 +39,13 @@ import org.checkerframework.checker.index.qual.NonNegative; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Range; +import org.patchbukkit.bridge.BridgeUtils; import org.patchbukkit.PatchBukkitServer; +import patchbukkit.bridge.NativeBridgeFfi; +import patchbukkit.entity.DamageEntityRequest; +import patchbukkit.entity.SetEntityHealthRequest; + import com.destroystokyo.paper.block.TargetBlockInfo; import com.destroystokyo.paper.block.TargetBlockInfo.FluidMode; import com.destroystokyo.paper.entity.TargetEntityInfo; @@ -54,383 +63,415 @@ public class PatchBukkitLivingEntity String name) { super(uuid, name); } - @Override - public @Nullable AttributeInstance getAttribute(@NotNull Attribute attribute) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getAttribute'"); - } - - @Override - public void registerAttribute(@NotNull Attribute attribute) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'registerAttribute'"); - } - - @Override - public void damage(double amount) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'damage'"); - } - - @Override - public void damage(double amount, @Nullable Entity source) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'damage'"); - } - - @Override - public void damage(double amount, @NotNull DamageSource damageSource) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'damage'"); - } - - @Override - public double getHealth() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getHealth'"); - } - - @Override - public void setHealth(double health) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'setHealth'"); - } - - @Override - public void heal(double amount, @NotNull RegainReason reason) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'heal'"); - } - - @Override - public double getAbsorptionAmount() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getAbsorptionAmount'"); - } - - @Override - public void setAbsorptionAmount(double amount) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'setAbsorptionAmount'"); - } - - @Override - public double getMaxHealth() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getMaxHealth'"); - } - - @Override - public void setMaxHealth(double health) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'setMaxHealth'"); - } - - @Override - public void resetMaxHealth() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'resetMaxHealth'"); - } - - @Override - public @NotNull T launchProjectile(@NotNull Class projectile, - @Nullable Vector velocity, @Nullable Consumer function) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'launchProjectile'"); - } - - @Override - public TriState getFrictionState() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getFrictionState'"); - } - - @Override - public void setFrictionState(TriState state) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'setFrictionState'"); - } - @Override public double getEyeHeight() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getEyeHeight'"); + return 1.62; } @Override public double getEyeHeight(boolean ignorePose) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getEyeHeight'"); + return 1.62; } @Override public @NotNull Location getEyeLocation() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getEyeLocation'"); - } - - @Override - public @NotNull List getLineOfSight(@Nullable Set transparent, int maxDistance) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getLineOfSight'"); - } - - @Override - public @NotNull Block getTargetBlock(@Nullable Set transparent, int maxDistance) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getTargetBlock'"); - } - - @Override - public @Nullable Block getTargetBlock(int maxDistance, @NotNull FluidMode fluidMode) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getTargetBlock'"); - } - - @Override - public @Nullable BlockFace getTargetBlockFace(int maxDistance, @NotNull FluidMode fluidMode) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getTargetBlockFace'"); - } - - @Override - public @Nullable BlockFace getTargetBlockFace(int maxDistance, @NotNull FluidCollisionMode fluidMode) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getTargetBlockFace'"); - } - - @Override - public @Nullable TargetBlockInfo getTargetBlockInfo(int maxDistance, @NotNull FluidMode fluidMode) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getTargetBlockInfo'"); - } - - @Override - public @Nullable Entity getTargetEntity(int maxDistance, boolean ignoreBlocks) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getTargetEntity'"); - } - - @Override - public @Nullable TargetEntityInfo getTargetEntityInfo(int maxDistance, boolean ignoreBlocks) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getTargetEntityInfo'"); - } - - @Override - public @Nullable RayTraceResult rayTraceEntities(int maxDistance, boolean ignoreBlocks) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'rayTraceEntities'"); - } - - @Override - public @NotNull List getLastTwoTargetBlocks(@Nullable Set transparent, int maxDistance) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getLastTwoTargetBlocks'"); - } - - @Override - public @Nullable Block getTargetBlockExact(int maxDistance, @NotNull FluidCollisionMode fluidCollisionMode) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getTargetBlockExact'"); + return getLocation().add(0, getEyeHeight(), 0); } @Override public @Nullable RayTraceResult rayTraceBlocks(double maxDistance, @NotNull FluidCollisionMode fluidCollisionMode) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'rayTraceBlocks'"); + return getLocation().getWorld().rayTraceBlocks(getEyeLocation(), getEyeLocation().getDirection(), maxDistance, fluidCollisionMode); } @Override - public int getRemainingAir() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getRemainingAir'"); + public @Nullable Block getTargetBlockExact(int maxDistance, @NotNull FluidCollisionMode fluidCollisionMode) { + RayTraceResult result = rayTraceBlocks(maxDistance, fluidCollisionMode); + return result != null ? result.getHitBlock() : null; } @Override - public void setRemainingAir(int ticks) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'setRemainingAir'"); + public @NotNull Block getTargetBlock(@Nullable Set transparent, int maxDistance) { + Block hit = getTargetBlockExact(maxDistance, FluidCollisionMode.NEVER); + return hit != null ? hit : getLocation().getBlock(); } @Override - public int getMaximumAir() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getMaximumAir'"); + public @NotNull List getLineOfSight(@Nullable Set transparent, int maxDistance) { + List blocks = new ArrayList<>(); + Block target = getTargetBlock(transparent, maxDistance); + if (target != null) { + blocks.add(target); + } + return blocks; } @Override - public void setMaximumAir(int ticks) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'setMaximumAir'"); + public @Nullable Block getTargetBlock(int maxDistance, @NotNull FluidMode fluidMode) { + return getTargetBlock(null, maxDistance); } @Override - public @Nullable ItemStack getItemInUse() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getItemInUse'"); + public @NotNull List getLastTwoTargetBlocks(@Nullable Set transparent, int maxDistance) { + Block target = getTargetBlock(transparent, maxDistance); + return List.of(target, target); } @Override - public int getItemInUseTicks() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getItemInUseTicks'"); + public @Nullable Entity getTargetEntity(int maxDistance, boolean ignoreBlocks) { + RayTraceResult result = rayTraceEntities(maxDistance, ignoreBlocks); + return result != null ? result.getHitEntity() : null; } @Override - public void setItemInUseTicks(int ticks) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'setItemInUseTicks'"); + public @Nullable TargetEntityInfo getTargetEntityInfo(int maxDistance, boolean ignoreBlocks) { + Entity entity = getTargetEntity(maxDistance, ignoreBlocks); + return entity != null ? new TargetEntityInfo(entity, entity.getLocation().toVector().subtract(getEyeLocation().toVector())) : null; } @Override - public @NonNegative int getArrowCooldown() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getArrowCooldown'"); + public @Nullable TargetBlockInfo getTargetBlockInfo(int maxDistance, @NotNull FluidMode fluidMode) { + Block block = getTargetBlockExact(maxDistance, FluidCollisionMode.NEVER); + return block != null ? new TargetBlockInfo(block, BlockFace.UP) : null; } @Override - public void setArrowCooldown(@NonNegative int ticks) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'setArrowCooldown'"); + public @Nullable BlockFace getTargetBlockFace(int maxDistance, @NotNull FluidCollisionMode fluidCollisionMode) { + RayTraceResult result = rayTraceBlocks(maxDistance, fluidCollisionMode); + return result != null ? result.getHitBlockFace() : null; } @Override - public @NonNegative int getArrowsInBody() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getArrowsInBody'"); + public @Nullable BlockFace getTargetBlockFace(int maxDistance, @NotNull FluidMode fluidMode) { + return getTargetBlockFace(maxDistance, FluidCollisionMode.NEVER); } @Override - public void setArrowsInBody(@NonNegative int count, boolean fireEvent) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'setArrowsInBody'"); + public @Nullable RayTraceResult rayTraceEntities(int maxDistance, boolean ignoreBlocks) { + return getLocation().getWorld().rayTraceEntities(getEyeLocation(), getEyeLocation().getDirection(), (double) maxDistance, (double) maxDistance); + } + private TriState frictionState = TriState.NOT_SET; + + @Override + public TriState getFrictionState() { + return this.frictionState; } @Override - public @NonNegative int getBeeStingerCooldown() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getBeeStingerCooldown'"); + public void setFrictionState(TriState state) { + this.frictionState = state != null ? state : TriState.NOT_SET; + } + + private final Map attributes = new HashMap<>(); + + @Override + public @Nullable AttributeInstance getAttribute(@NotNull Attribute attribute) { + return attributes.get(attribute); } @Override - public void setBeeStingerCooldown(@NonNegative int ticks) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'setBeeStingerCooldown'"); + public void registerAttribute(@NotNull Attribute attribute) { } @Override - public @NonNegative int getBeeStingersInBody() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getBeeStingersInBody'"); + public @NotNull T launchProjectile(@NotNull Class projectileClass) { + return launchProjectile(projectileClass, null); } @Override - public void setBeeStingersInBody(@NonNegative int count) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'setBeeStingersInBody'"); + public @NotNull T launchProjectile(@NotNull Class projectileClass, @Nullable Vector velocity) { + return launchProjectile(projectileClass, velocity, null); } @Override - public int getMaximumNoDamageTicks() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getMaximumNoDamageTicks'"); + public @NotNull T launchProjectile(@NotNull Class projectileClass, @Nullable Vector velocity, @Nullable Consumer function) { + Location spawnLoc = getEyeLocation(); + Vector dir = velocity != null ? velocity : spawnLoc.getDirection(); + T entity = spawnLoc.getWorld().spawn(spawnLoc, projectileClass, function); + entity.setVelocity(dir); + entity.setShooter(this); + return entity; } - @Override - public void setMaximumNoDamageTicks(int ticks) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'setMaximumNoDamageTicks'"); - } - @Override - public double getLastDamage() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getLastDamage'"); - } - @Override - public void setLastDamage(double damage) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'setLastDamage'"); - } - - @Override - public int getNoDamageTicks() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getNoDamageTicks'"); - } - - @Override - public void setNoDamageTicks(int ticks) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'setNoDamageTicks'"); - } - - @Override - public int getNoActionTicks() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getNoActionTicks'"); - } - - @Override - public void setNoActionTicks(int ticks) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'setNoActionTicks'"); - } + private Player killer; @Override public @Nullable Player getKiller() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getKiller'"); + return this.killer; } @Override public void setKiller(@Nullable Player killer) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'setKiller'"); + this.killer = killer; + } + + private double health = 20.0; + private double maxHealth = 20.0; + private double absorption = 0.0; + private int arrowsInBody = 0; + private int arrowCooldown = 0; + private int beeStingersInBody = 0; + private int beeStingerCooldown = 0; + + @Override + public int getArrowsInBody() { + return this.arrowsInBody; + } + + @Override + public void setArrowsInBody(int count) { + this.arrowsInBody = count; + } + + @Override + public void setArrowsInBody(int count, boolean fireEvent) { + this.arrowsInBody = count; + } + + @Override + public int getArrowCooldown() { + return this.arrowCooldown; + } + + @Override + public void setArrowCooldown(int ticks) { + this.arrowCooldown = ticks; + } + + @Override + public int getBeeStingersInBody() { + return this.beeStingersInBody; + } + + @Override + public void setBeeStingersInBody(int count) { + this.beeStingersInBody = count; + } + + @Override + public int getBeeStingerCooldown() { + return this.beeStingerCooldown; + } + + @Override + public void setBeeStingerCooldown(int ticks) { + this.beeStingerCooldown = ticks; + } + + private int noActionTicks = 0; + + @Override + public int getNoActionTicks() { + return this.noActionTicks; + } + + @Override + public void setNoActionTicks(int ticks) { + this.noActionTicks = ticks; + } + + private int itemInUseTicks = 0; + private ItemStack itemInUse = null; + + @Override + public @Nullable ItemStack getItemInUse() { + return this.itemInUse; + } + + @Override + public int getItemInUseTicks() { + return this.itemInUseTicks; + } + + @Override + public void setItemInUseTicks(int ticks) { + this.itemInUseTicks = ticks; + } + + private int remainingAir = 300; + private int maximumAir = 300; + private int noDamageTicks = 0; + private int maximumNoDamageTicks = 20; + private double lastDamage = 0.0; + private final Map potionEffects = new HashMap<>(); + + @Override + public double getHealth() { + try { + var response = NativeBridgeFfi.getEntityHealth(BridgeUtils.convertUuid(this.getUniqueId())); + if (response != null) { + return response.getHealth(); + } + } catch (Throwable ignored) {} + return this.health; + } + + @Override + public void setHealth(double health) { + this.health = Math.max(0.0, Math.min(health, getMaxHealth())); + var request = SetEntityHealthRequest.newBuilder() + .setUuid(BridgeUtils.convertUuid(this.getUniqueId())) + .setHealth(this.health) + .build(); + NativeBridgeFfi.setEntityHealth(request); + } + + @Override + public void damage(double amount) { + var request = DamageEntityRequest.newBuilder() + .setUuid(BridgeUtils.convertUuid(this.getUniqueId())) + .setAmount(amount) + .build(); + NativeBridgeFfi.damageEntity(request); + } + + @Override + public void damage(double amount, @Nullable Entity source) { + setHealth(getHealth() - amount); + } + + @Override + public void damage(double amount, @NotNull DamageSource damageSource) { + setHealth(getHealth() - amount); + } + + @Override + public void heal(double amount, @NotNull RegainReason reason) { + setHealth(getHealth() + amount); + } + + @Override + public double getMaxHealth() { + return this.maxHealth; + } + + @Override + public void setMaxHealth(double health) { + this.maxHealth = Math.max(0.1, health); + if (this.health > this.maxHealth) { + this.health = this.maxHealth; + } + } + + @Override + public void resetMaxHealth() { + setMaxHealth(20.0); + } + + @Override + public double getAbsorptionAmount() { + return this.absorption; + } + + @Override + public void setAbsorptionAmount(double amount) { + this.absorption = Math.max(0.0, amount); + } + + @Override + public int getRemainingAir() { + return this.remainingAir; + } + + @Override + public void setRemainingAir(int ticks) { + this.remainingAir = ticks; + } + + @Override + public int getMaximumAir() { + return this.maximumAir; + } + + @Override + public void setMaximumAir(int ticks) { + this.maximumAir = ticks; + } + + @Override + public int getNoDamageTicks() { + return this.noDamageTicks; + } + + @Override + public void setNoDamageTicks(int ticks) { + this.noDamageTicks = ticks; + } + + @Override + public int getMaximumNoDamageTicks() { + return this.maximumNoDamageTicks; + } + + @Override + public void setMaximumNoDamageTicks(int ticks) { + this.maximumNoDamageTicks = ticks; + } + + @Override + public double getLastDamage() { + return this.lastDamage; + } + + @Override + public void setLastDamage(double damage) { + this.lastDamage = damage; + } + + @Override + public boolean addPotionEffect(@NotNull PotionEffect effect) { + return addPotionEffect(effect, false); } @Override public boolean addPotionEffect(@NotNull PotionEffect effect, boolean force) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'addPotionEffect'"); + PotionEffectType type = effect.getType(); + PotionEffect existing = potionEffects.get(type); + if (existing == null || force || effect.getAmplifier() > existing.getAmplifier() + || (effect.getAmplifier() == existing.getAmplifier() && effect.getDuration() > existing.getDuration())) { + potionEffects.put(type, effect); + return true; + } + return false; } @Override public boolean addPotionEffects(@NotNull Collection effects) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'addPotionEffects'"); + boolean success = false; + for (PotionEffect effect : effects) { + if (addPotionEffect(effect)) { + success = true; + } + } + return success; } @Override public boolean hasPotionEffect(@NotNull PotionEffectType type) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'hasPotionEffect'"); + return potionEffects.containsKey(type); } @Override public @Nullable PotionEffect getPotionEffect(@NotNull PotionEffectType type) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getPotionEffect'"); + return potionEffects.get(type); } @Override public void removePotionEffect(@NotNull PotionEffectType type) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'removePotionEffect'"); + potionEffects.remove(type); } @Override public @NotNull Collection getActivePotionEffects() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getActivePotionEffects'"); + return Collections.unmodifiableCollection(potionEffects.values()); } @Override public boolean clearActivePotionEffects() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'clearActivePotionEffects'"); + boolean hadEffects = !potionEffects.isEmpty(); + potionEffects.clear(); + return hadEffects; } @Override @@ -846,11 +887,6 @@ public class PatchBukkitLivingEntity throw new UnsupportedOperationException("Unimplemented method 'kill'"); } @Override - public boolean addPotionEffect(@NotNull PotionEffect effect) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'addPotionEffect'"); - } - @Override public float getSoundVolume() { // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'getSoundVolume'"); diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventFactory.java b/java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventFactory.java index 14c24f1..7addd61 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventFactory.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventFactory.java @@ -46,6 +46,7 @@ public class PatchBukkitEventFactory { LOGGER.warning("EventFactory: Received Event with no data"); yield null; } + default -> null; }; } diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/world/PatchBukkitBlock.java b/java/patchbukkit/src/main/java/org/patchbukkit/world/PatchBukkitBlock.java new file mode 100644 index 0000000..53836cf --- /dev/null +++ b/java/patchbukkit/src/main/java/org/patchbukkit/world/PatchBukkitBlock.java @@ -0,0 +1,437 @@ +package org.patchbukkit.world; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.inventory.ItemStack; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.block.Biome; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.BlockState; +import org.bukkit.block.PistonMoveReaction; +import org.bukkit.block.data.BlockData; +import org.bukkit.SoundGroup; +import org.bukkit.Chunk; +import org.bukkit.FluidCollisionMode; +import org.bukkit.util.BoundingBox; +import org.bukkit.util.RayTraceResult; +import org.bukkit.util.Vector; +import org.bukkit.util.VoxelShape; +import org.bukkit.metadata.MetadataValue; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class PatchBukkitBlock implements Block { + private final World world; + private final int x; + private final int y; + private final int z; + private final Map> metadataMap = new HashMap<>(); + + public PatchBukkitBlock(World world, int x, int y, int z) { + this.world = world; + this.x = x; + this.y = y; + this.z = z; + } + + @Override + public @NotNull World getWorld() { + return this.world; + } + + @Override + public int getX() { + return this.x; + } + + @Override + public int getY() { + return this.y; + } + + @Override + public int getZ() { + return this.z; + } + + @Override + public @NotNull Location getLocation() { + return new Location(this.world, this.x, this.y, this.z); + } + + @Override + public @Nullable Location getLocation(@Nullable Location loc) { + if (loc != null) { + loc.setWorld(this.world); + loc.setX(this.x); + loc.setY(this.y); + loc.setZ(this.z); + return loc; + } + return getLocation(); + } + + @Override + public @NotNull BlockData getBlockData() { + return this.world.getBlockData(this.x, this.y, this.z); + } + + @Override + public void setBlockData(@NotNull BlockData data) { + this.world.setBlockData(this.x, this.y, this.z, data); + } + + @Override + public void setBlockData(@NotNull BlockData data, boolean applyPhysics) { + this.world.setBlockData(this.x, this.y, this.z, data); + } + + @Override + public boolean isEmpty() { + return getType().isAir(); + } + + @Override + public boolean isLiquid() { + Material type = getType(); + return type == Material.WATER || type == Material.LAVA; + } + + @Override + public boolean isPassable() { + return isEmpty() || isLiquid(); + } + + @Override + public boolean isSolid() { + return getType().isSolid(); + } + + @Override + public boolean isReplaceable() { + return isEmpty() || getType() == Material.TALL_GRASS || getType() == Material.SHORT_GRASS; + } + + @Override + public boolean isBuildable() { + return !isEmpty(); + } + + @Override + public int getBlockPower(@NotNull BlockFace face) { + return 0; + } + + @Override + public int getBlockPower() { + return 0; + } + + @Override + public @NotNull BlockState getState() { + return getState(true); + } + + @Override + public @NotNull BlockState getState(boolean useSnapshot) { + return new PatchBukkitBlockState(this); + } + + @Override + public boolean isBlockPowered() { + return false; + } + + @Override + public boolean isBlockIndirectlyPowered() { + return false; + } + + @Override + public boolean isBlockFacePowered(@NotNull BlockFace face) { + return false; + } + + @Override + public boolean isBlockFaceIndirectlyPowered(@NotNull BlockFace face) { + return false; + } + + @Override + public boolean isSuffocating() { + return !isPassable(); + } + + @Override + public boolean isBurnable() { + return getType().isBurnable(); + } + + @Override + public boolean isCollidable() { + return !isPassable(); + } + + @Override + public @NotNull String getTranslationKey() { + return getType().getTranslationKey(); + } + + @Override + public @NotNull com.destroystokyo.paper.block.BlockSoundGroup getSoundGroup() { + return (com.destroystokyo.paper.block.BlockSoundGroup) (Object) getBlockData().getSoundGroup(); + } + + @Override + public @NotNull SoundGroup getBlockSoundGroup() { + return getBlockData().getSoundGroup(); + } + + @Override + public boolean canPlace(@NotNull BlockData data) { + return true; + } + + @Override + public @NotNull BoundingBox getBoundingBox() { + return BoundingBox.of(this, this); + } + + @Override + public float getBreakSpeed(@NotNull Player player) { + return 1.0f; + } + + @Override + public boolean isPreferredTool(@NotNull ItemStack tool) { + return false; + } + + @Override + public @NotNull Collection getDrops() { + return getDrops(null); + } + + @Override + public @NotNull Collection getDrops(@Nullable ItemStack tool) { + return getDrops(tool, null); + } + + @Override + public @NotNull Collection getDrops(@Nullable ItemStack tool, @Nullable Entity entity) { + return List.of(); + } + + @Override + public boolean applyBoneMeal(@NotNull BlockFace targetFace) { + return false; + } + + @Override + public void randomTick() { + } + + @Override + public boolean breakNaturally() { + return breakNaturally(null); + } + + @Override + public boolean breakNaturally(@Nullable ItemStack tool) { + return breakNaturally(tool, true); + } + + @Override + public boolean breakNaturally(boolean triggerEffect) { + return breakNaturally(null, triggerEffect); + } + + @Override + public boolean breakNaturally(boolean triggerEffect, boolean dropExperience) { + return breakNaturally(null, triggerEffect, dropExperience); + } + + @Override + public boolean breakNaturally(@Nullable ItemStack tool, boolean triggerEffect) { + return breakNaturally(tool, triggerEffect, true); + } + + @Override + public boolean breakNaturally(@Nullable ItemStack tool, boolean triggerEffect, boolean dropExperience) { + return breakNaturally(tool, triggerEffect, dropExperience, true); + } + + @Override + public boolean breakNaturally(@Nullable ItemStack tool, boolean triggerEffect, boolean dropExperience, boolean dropAsItem) { + setType(Material.AIR); + return true; + } + + @Override + public @NotNull Biome getBiome() { + return Biome.PLAINS; + } + + @Override + public @NotNull Biome getComputedBiome() { + return getBiome(); + } + + @Override + public void setBiome(@NotNull Biome bio) { + } + + @Override + public double getTemperature() { + return 0.8; + } + + @Override + public double getHumidity() { + return 0.4; + } + + @Override + public @NotNull PistonMoveReaction getPistonMoveReaction() { + return PistonMoveReaction.MOVE; + } + + @Override + public void fluidTick() { + } + + @Override + public void tick() { + } + + @Override + public @Nullable RayTraceResult rayTrace(@NotNull Location start, @NotNull Vector direction, double maxDistance, @NotNull FluidCollisionMode fluidCollisionMode) { + return getBoundingBox().rayTrace(start.toVector(), direction, maxDistance); + } + + @Override + public @NotNull VoxelShape getCollisionShape() { + return getBlockData().getCollisionShape(getLocation()); + } + + @Override + public @NotNull Material getType() { + return getBlockData().getMaterial(); + } + + @Override + public void setType(@NotNull Material type) { + setBlockData(org.bukkit.Bukkit.createBlockData(type)); + } + + @Override + public void setType(@NotNull Material type, boolean applyPhysics) { + setBlockData(org.bukkit.Bukkit.createBlockData(type), applyPhysics); + } + + @Override + public @NotNull Block getRelative(int modX, int modY, int modZ) { + return this.world.getBlockAt(this.x + modX, this.y + modY, this.z + modZ); + } + + @Override + public @NotNull Block getRelative(@NotNull BlockFace face) { + return getRelative(face.getModX(), face.getModY(), face.getModZ()); + } + + @Override + public @NotNull Block getRelative(@NotNull BlockFace face, int distance) { + return getRelative(face.getModX() * distance, face.getModY() * distance, face.getModZ() * distance); + } + + @Override + public @Nullable BlockFace getFace(@NotNull Block block) { + int modX = block.getX() - this.x; + int modY = block.getY() - this.y; + int modZ = block.getZ() - this.z; + for (BlockFace face : BlockFace.values()) { + if (face.getModX() == modX && face.getModY() == modY && face.getModZ() == modZ) { + return face; + } + } + return null; + } + + @Override + public void setMetadata(@NotNull String metadataKey, @NotNull MetadataValue newMetadataValue) { + metadataMap.computeIfAbsent(metadataKey, k -> new ArrayList<>()).add(newMetadataValue); + } + + @Override + public @NotNull List getMetadata(@NotNull String metadataKey) { + return metadataMap.getOrDefault(metadataKey, List.of()); + } + + @Override + public boolean hasMetadata(@NotNull String metadataKey) { + return metadataMap.containsKey(metadataKey); + } + + @Override + public void removeMetadata(@NotNull String metadataKey, @NotNull Plugin owningPlugin) { + List list = metadataMap.get(metadataKey); + if (list != null) { + list.removeIf(v -> v.getOwningPlugin() == owningPlugin); + if (list.isEmpty()) { + metadataMap.remove(metadataKey); + } + } + } + + @Override + public String translationKey() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'translationKey'"); + } + + @Override + public byte getData() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getData'"); + } + + @Override + public byte getLightLevel() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getLightLevel'"); + } + + @Override + public byte getLightFromSky() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getLightFromSky'"); + } + + @Override + public byte getLightFromBlocks() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getLightFromBlocks'"); + } + + @Override + public boolean isValidTool(@NotNull ItemStack tool) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'isValidTool'"); + } + + @Override + public @NotNull Chunk getChunk() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getChunk'"); + } +} diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/world/PatchBukkitBlockState.java b/java/patchbukkit/src/main/java/org/patchbukkit/world/PatchBukkitBlockState.java new file mode 100644 index 0000000..dc4a275 --- /dev/null +++ b/java/patchbukkit/src/main/java/org/patchbukkit/world/PatchBukkitBlockState.java @@ -0,0 +1,180 @@ +package org.patchbukkit.world; + +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.block.data.BlockData; +import org.bukkit.material.MaterialData; +import org.bukkit.metadata.MetadataValue; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; +import org.bukkit.entity.Entity; +import org.bukkit.inventory.ItemStack; +import java.util.Collection; +import java.util.List; + +public class PatchBukkitBlockState implements BlockState { + private final Block block; + private BlockData data; + + public PatchBukkitBlockState(Block block) { + this.block = block; + this.data = block.getBlockData().clone(); + } + + @Override + public @NotNull Block getBlock() { + return this.block; + } + + @Override + public @NotNull Material getType() { + return this.data.getMaterial(); + } + + @Override + public void setType(@NotNull Material type) { + this.data = org.bukkit.Bukkit.createBlockData(type); + } + + @Override + public @NotNull BlockData getBlockData() { + return this.data; + } + + @Override + public void setBlockData(@NotNull BlockData data) { + this.data = data.clone(); + } + + @Override + public byte getRawData() { + return 0; + } + + @Override + public void setRawData(byte data) { + } + + @Override + public boolean update() { + return update(false); + } + + @Override + public boolean update(boolean force) { + return update(force, true); + } + + @Override + public boolean update(boolean force, boolean applyPhysics) { + this.block.setBlockData(this.data, applyPhysics); + return true; + } + + @Override + public @NotNull World getWorld() { + return this.block.getWorld(); + } + + @Override + public int getX() { + return this.block.getX(); + } + + @Override + public int getY() { + return this.block.getY(); + } + + @Override + public int getZ() { + return this.block.getZ(); + } + + @Override + public @NotNull Location getLocation() { + return this.block.getLocation(); + } + + @Override + public @Nullable Location getLocation(@Nullable Location loc) { + return this.block.getLocation(loc); + } + + @Override + public @NotNull Chunk getChunk() { + return this.block.getChunk(); + } + + @Override + public void setMetadata(@NotNull String metadataKey, @NotNull MetadataValue newMetadataValue) { + this.block.setMetadata(metadataKey, newMetadataValue); + } + + @Override + public @NotNull List getMetadata(@NotNull String metadataKey) { + return this.block.getMetadata(metadataKey); + } + + @Override + public boolean hasMetadata(@NotNull String metadataKey) { + return this.block.hasMetadata(metadataKey); + } + + @Override + public void removeMetadata(@NotNull String metadataKey, @NotNull Plugin owningPlugin) { + this.block.removeMetadata(metadataKey, owningPlugin); + } + + @Override + public boolean isPlaced() { + return true; + } + + @Override + public boolean isCollidable() { + return this.block.isCollidable(); + } + + @Override + public boolean isSuffocating() { + return this.block.isSuffocating(); + } + + @Override + public @NotNull MaterialData getData() { + return new MaterialData(getType()); + } + + @Override + public void setData(@NotNull MaterialData data) { + } + + @Override + public @NotNull BlockState copy() { + return new PatchBukkitBlockState(this.block); + } + + @Override + public @NotNull BlockState copy(@NotNull Location location) { + return new PatchBukkitBlockState(location.getBlock()); + } + + @Override + public byte getLightLevel() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getLightLevel'"); + } + + @Override + public @NotNull @Unmodifiable Collection getDrops(@Nullable ItemStack tool, @Nullable Entity entity) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getDrops'"); + } +} diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/world/PatchBukkitRegionAccessor.java b/java/patchbukkit/src/main/java/org/patchbukkit/world/PatchBukkitRegionAccessor.java index 6229c48..b8d298b 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/world/PatchBukkitRegionAccessor.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/world/PatchBukkitRegionAccessor.java @@ -21,6 +21,10 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Unmodifiable; import org.jspecify.annotations.NonNull; +import org.patchbukkit.bridge.BridgeUtils; +import patchbukkit.bridge.NativeBridgeFfi; +import patchbukkit.world.GetBlockDataRequest; +import patchbukkit.world.SetBlockDataRequest; public class PatchBukkitRegionAccessor implements RegionAccessor { @@ -65,32 +69,42 @@ public class PatchBukkitRegionAccessor implements RegionAccessor { } @Override - public @NotNull BlockData getBlockData(int x, int y, int z) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException( - "Unimplemented method 'getBlockData'" - ); + public @org.jetbrains.annotations.NotNull BlockData getBlockData(int x, int y, int z) { + if (this instanceof PatchBukkitWorld world) { + var request = GetBlockDataRequest.newBuilder() + .setWorldUuid(BridgeUtils.convertUuid(world.getUID())) + .setX(x) + .setY(y) + .setZ(z) + .build(); + try { + var response = NativeBridgeFfi.getBlockData(request); + if (response != null && !response.getBlockState().isEmpty()) { + return Bukkit.createBlockData(Material.STONE); + } + } catch (Throwable ignored) {} + } + return Bukkit.createBlockData(Material.AIR); } @Override - public @NotNull Material getType(int x, int y, int z) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException( - "Unimplemented method 'getType'" - ); + public @org.jetbrains.annotations.NotNull Material getType(int x, int y, int z) { + return getBlockData(x, y, z).getMaterial(); } @Override - public void setBlockData( - int x, - int y, - int z, - @NotNull BlockData blockData - ) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException( - "Unimplemented method 'setBlockData'" - ); + public void setBlockData(int x, int y, int z, @org.jetbrains.annotations.NotNull BlockData blockData) { + if (this instanceof PatchBukkitWorld world) { + var request = SetBlockDataRequest.newBuilder() + .setWorldUuid(BridgeUtils.convertUuid(world.getUID())) + .setX(x) + .setY(y) + .setZ(z) + .setBlockState(blockData.getAsString()) + .setApplyPhysics(true) + .build(); + NativeBridgeFfi.setBlockData(request); + } } @Override diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/world/PatchBukkitWorld.java b/java/patchbukkit/src/main/java/org/patchbukkit/world/PatchBukkitWorld.java index 46a28b5..a20e9e4 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/world/PatchBukkitWorld.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/world/PatchBukkitWorld.java @@ -6,7 +6,14 @@ import io.papermc.paper.entity.poi.PoiType.Occupancy; import io.papermc.paper.math.Position; import io.papermc.paper.raytracing.PositionedRayTraceConfigurationBuilder; import java.nio.file.Path; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; import java.util.function.Consumer; import java.util.function.Predicate; import org.bukkit.*; @@ -27,7 +34,11 @@ import org.bukkit.metadata.MetadataValue; import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.plugin.Plugin; import org.bukkit.util.*; -import org.bukkit.util.Vector; +import org.patchbukkit.bridge.BridgeUtils; +import patchbukkit.bridge.NativeBridgeFfi; +import patchbukkit.world.GetBlockDataRequest; +import patchbukkit.world.SetBlockDataRequest; +import patchbukkit.world.SpawnParticleRequest; import org.checkerframework.checker.index.qual.Positive; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -153,10 +164,7 @@ public class PatchBukkitWorld @Override public @NotNull Block getBlockAt(int x, int y, int z) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException( - "Unimplemented method 'getBlockAt'" - ); + return new PatchBukkitBlock(this, x, y, z); } @Override @@ -1585,10 +1593,7 @@ public class PatchBukkitWorld @Override public @NotNull String getName() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException( - "Unimplemented method 'getName'" - ); + return "world"; } @Override diff --git a/proto/patchbukkit/bridge.proto b/proto/patchbukkit/bridge.proto index 85df0ae..ef83899 100644 --- a/proto/patchbukkit/bridge.proto +++ b/proto/patchbukkit/bridge.proto @@ -6,16 +6,29 @@ import "google/protobuf/wrappers.proto"; import "patchbukkit/abilities.proto"; import "patchbukkit/common/types.proto"; import "patchbukkit/config.proto"; +import "patchbukkit/entity.proto"; import "patchbukkit/events/event.proto"; import "patchbukkit/log.proto"; import "patchbukkit/message.proto"; import "patchbukkit/registry.proto"; import "patchbukkit/sound.proto"; +import "patchbukkit/world.proto"; service NativeBridge { rpc GetAbilities(patchbukkit.common.UUID) returns (patchbukkit.abilities.Abilities); rpc SetAbilities(patchbukkit.abilities.SetAbilitiesRequest) returns (patchbukkit.abilities.SetAbilitiesResponse); + rpc GetEntityHealth(patchbukkit.common.UUID) returns (patchbukkit.entity.EntityHealthResponse); + rpc SetEntityHealth(patchbukkit.entity.SetEntityHealthRequest) returns (google.protobuf.Empty); + rpc DamageEntity(patchbukkit.entity.DamageEntityRequest) returns (google.protobuf.Empty); + rpc SetEntityVelocity(patchbukkit.entity.SetEntityVelocityRequest) returns (google.protobuf.Empty); + rpc TeleportEntity(patchbukkit.entity.TeleportEntityRequest) returns (google.protobuf.Empty); + + rpc GetBlockData(patchbukkit.world.GetBlockDataRequest) returns (patchbukkit.world.GetBlockDataResponse); + rpc SetBlockData(patchbukkit.world.SetBlockDataRequest) returns (google.protobuf.Empty); + rpc SpawnParticle(patchbukkit.world.SpawnParticleRequest) returns (google.protobuf.Empty); + rpc GetWorlds(patchbukkit.common.EmptyRequest) returns (patchbukkit.world.GetWorldsResponse); + rpc RegisterEvent(patchbukkit.events.RegisterEventRequest) returns (google.protobuf.Empty); rpc CallEvent(patchbukkit.events.CallEventRequest) returns (patchbukkit.events.CallEventResponse); diff --git a/proto/patchbukkit/entity.proto b/proto/patchbukkit/entity.proto new file mode 100644 index 0000000..a66c856 --- /dev/null +++ b/proto/patchbukkit/entity.proto @@ -0,0 +1,41 @@ +syntax = "proto3"; + +package patchbukkit.entity; + +import "patchbukkit/common/types.proto"; + +option java_multiple_files = true; +option java_package = "patchbukkit.entity"; + +message EntityHealthResponse { + double health = 1; + double max_health = 2; +} + +message SetEntityHealthRequest { + patchbukkit.common.UUID uuid = 1; + double health = 2; +} + +message DamageEntityRequest { + patchbukkit.common.UUID uuid = 1; + double amount = 2; +} + +message SetEntityVelocityRequest { + patchbukkit.common.UUID uuid = 1; + double x = 2; + double y = 3; + double z = 4; +} + +message TeleportEntityRequest { + patchbukkit.common.UUID uuid = 1; + patchbukkit.common.Location location = 2; +} + +message SetEntityPoseRequest { + patchbukkit.common.UUID uuid = 1; + string pose = 2; +} + diff --git a/proto/patchbukkit/events/event.proto b/proto/patchbukkit/events/event.proto index f678892..777eac3 100644 --- a/proto/patchbukkit/events/event.proto +++ b/proto/patchbukkit/events/event.proto @@ -4,6 +4,7 @@ package patchbukkit.events; import "patchbukkit/common/types.proto"; import "patchbukkit/events/player_events.proto"; +import "patchbukkit/events/server_events.proto"; option java_multiple_files = true; option java_package = "patchbukkit.events"; @@ -11,6 +12,11 @@ option java_package = "patchbukkit.events"; message Event { oneof data { PlayerJoinEvent player_join = 1; + PluginEnableEvent plugin_enable = 2; + PluginDisableEvent plugin_disable = 3; + PlayerQuitEvent player_quit = 4; + PlayerInteractEvent player_interact = 5; + PlayerGameModeChangeEvent player_gamemode_change = 6; } } diff --git a/proto/patchbukkit/events/player_events.proto b/proto/patchbukkit/events/player_events.proto index cd92e96..d643070 100644 --- a/proto/patchbukkit/events/player_events.proto +++ b/proto/patchbukkit/events/player_events.proto @@ -10,3 +10,24 @@ message PlayerJoinEvent { patchbukkit.common.UUID player_uuid = 1; string join_message = 2; } + +message PlayerQuitEvent { + patchbukkit.common.UUID player_uuid = 1; + string quit_message = 2; +} + +message PlayerInteractEvent { + patchbukkit.common.UUID player_uuid = 1; + string action = 2; + int32 clicked_x = 3; + int32 clicked_y = 4; + int32 clicked_z = 5; + string block_face = 6; + string hand = 7; +} + +message PlayerGameModeChangeEvent { + patchbukkit.common.UUID player_uuid = 1; + string new_gamemode = 2; +} + diff --git a/proto/patchbukkit/events/server_events.proto b/proto/patchbukkit/events/server_events.proto new file mode 100644 index 0000000..ad08eae --- /dev/null +++ b/proto/patchbukkit/events/server_events.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; +package patchbukkit.events; + +import "patchbukkit/common/types.proto"; + +option java_multiple_files = true; +option java_package = "patchbukkit.events"; + +message PluginEnableEvent { + string plugin_name = 1; +} + +message PluginDisableEvent { + string plugin_name = 1; +} diff --git a/proto/patchbukkit/world.proto b/proto/patchbukkit/world.proto new file mode 100644 index 0000000..e0108b8 --- /dev/null +++ b/proto/patchbukkit/world.proto @@ -0,0 +1,46 @@ +syntax = "proto3"; + +package patchbukkit.world; + +import "patchbukkit/common/types.proto"; + +option java_multiple_files = true; +option java_package = "patchbukkit.world"; + +message GetBlockDataRequest { + patchbukkit.common.UUID world_uuid = 1; + int32 x = 2; + int32 y = 3; + int32 z = 4; +} + +message GetBlockDataResponse { + string block_state = 1; +} + +message SetBlockDataRequest { + patchbukkit.common.UUID world_uuid = 1; + int32 x = 2; + int32 y = 3; + int32 z = 4; + string block_state = 5; + bool apply_physics = 6; +} + +message SpawnParticleRequest { + patchbukkit.common.UUID world_uuid = 1; + string particle = 2; + double x = 3; + double y = 4; + double z = 5; + int32 count = 6; + double offset_x = 7; + double offset_y = 8; + double offset_z = 9; + double extra = 10; +} + +message GetWorldsResponse { + repeated patchbukkit.common.UUID world_uuids = 1; +} + diff --git a/rust/build/protobufs.rs b/rust/build/protobufs.rs index c99387f..a1291ea 100644 --- a/rust/build/protobufs.rs +++ b/rust/build/protobufs.rs @@ -107,12 +107,16 @@ pub unsafe extern "C" fn {fn_name}( ) -> *mut u8 {{ use prost::Message; tracing::debug!("{fn_name}: called with input_ptr={{:?}}, input_len={{}}", input_ptr, input_len); - if input_ptr.is_null() || input_len == 0 {{ - tracing::warn!("{fn_name}: null or empty input (ptr={{:?}}, len={{}})", input_ptr, input_len); + if input_ptr.is_null() {{ + tracing::warn!("{fn_name}: null input (ptr={{:?}}, len={{}})", input_ptr, input_len); unsafe {{ *output_len = 0 }}; return std::ptr::null_mut(); }} - let input_slice = unsafe {{ std::slice::from_raw_parts(input_ptr, input_len) }}; + let input_slice = if input_len == 0 {{ + &[] + }} else {{ + unsafe {{ std::slice::from_raw_parts(input_ptr, input_len) }} + }}; let Ok(request) = {input_type}::decode(input_slice) else {{ tracing::warn!("{fn_name}: failed to decode request"); unsafe {{ *output_len = 0 }}; diff --git a/rust/src/config/spigot.rs b/rust/src/config/spigot.rs index b9bb5ac..bc0f418 100644 --- a/rust/src/config/spigot.rs +++ b/rust/src/config/spigot.rs @@ -145,7 +145,11 @@ pub struct SpigotPluginYml { #[serde(default)] pub default_permission: Option, - // Paper-specific fields + // Paper/Folia-specific fields + /// Folia region threading support flag + #[serde(rename = "folia-supported")] + #[serde(default)] + pub folia_supported: Option, /// Paper plugin loader class #[serde(rename = "paper-plugin-loader")] #[serde(default)] diff --git a/rust/src/events/handler.rs b/rust/src/events/handler.rs index 6705e20..90c9a60 100644 --- a/rust/src/events/handler.rs +++ b/rust/src/events/handler.rs @@ -47,11 +47,9 @@ impl PatchBukkitEvent for pumpkin::plugin::player::player_join::PlayerJoinEvent } fn apply_modifications(&mut self, server: &Arc, data: Data) -> Option<()> { - match data { - Data::PlayerJoin(event) => { - self.join_message = serde_json::from_str(&event.join_message).ok()?; - server.get_player_by_uuid(uuid::Uuid::from_str(&event.player_uuid?.value).ok()?)?; - } + if let Data::PlayerJoin(event) = data { + self.join_message = serde_json::from_str(&event.join_message).ok()?; + server.get_player_by_uuid(uuid::Uuid::from_str(&event.player_uuid?.value).ok()?)?; } Some(()) diff --git a/rust/src/java/native_callbacks/entity.rs b/rust/src/java/native_callbacks/entity.rs new file mode 100644 index 0000000..d25842e --- /dev/null +++ b/rust/src/java/native_callbacks/entity.rs @@ -0,0 +1,76 @@ +use pumpkin::entity::EntityBase; + +use crate::{ + java::native_callbacks::{CALLBACK_CONTEXT, utils::with_player}, + proto::patchbukkit::{ + common::Uuid, + entity::{ + DamageEntityRequest, EntityHealthResponse, SetEntityHealthRequest, + SetEntityVelocityRequest, TeleportEntityRequest, + }, + }, +}; + +pub fn ffi_native_bridge_get_entity_health_impl(request: Uuid) -> Option { + let ctx = CALLBACK_CONTEXT.get()?; + with_player(Some(&request), |player| { + let health = tokio::task::block_in_place(|| { + ctx.runtime + .block_on(async { f64::from(player.living_entity.health.load()) }) + }); + + EntityHealthResponse { + health, + max_health: 20.0, + } + }) +} + +pub fn ffi_native_bridge_set_entity_health_impl(request: SetEntityHealthRequest) -> Option<()> { + let ctx = CALLBACK_CONTEXT.get()?; + with_player(request.uuid.as_ref(), |player| { + let health = request.health as f32; + let player = player.clone(); + ctx.runtime.spawn(async move { + player.set_health(health).await; + }); + }) +} + +pub fn ffi_native_bridge_damage_entity_impl(request: DamageEntityRequest) -> Option<()> { + let ctx = CALLBACK_CONTEXT.get()?; + with_player(request.uuid.as_ref(), |player| { + let amount = request.amount as f32; + let player = player.clone(); + ctx.runtime.spawn(async move { + let current = player.living_entity.health.load(); + player.set_health((current - amount).max(0.0)).await; + }); + }) +} + +pub fn ffi_native_bridge_set_entity_velocity_impl(request: SetEntityVelocityRequest) -> Option<()> { + with_player(request.uuid.as_ref(), |player| { + let velocity = pumpkin_util::math::vector3::Vector3::new(request.x, request.y, request.z); + player.living_entity.entity.set_velocity(velocity); + }) +} + +pub fn ffi_native_bridge_teleport_entity_impl(request: TeleportEntityRequest) -> Option<()> { + let ctx = CALLBACK_CONTEXT.get()?; + let loc = request.location?; + let pos = loc.position?; + let yaw = loc.yaw; + let pitch = loc.pitch; + + with_player(request.uuid.as_ref(), |player| { + let position = pumpkin_util::math::vector3::Vector3::new(pos.x, pos.y, pos.z); + let world = player.living_entity.entity.world.load_full(); + let player = player.clone(); + ctx.runtime.spawn(async move { + player + .teleport(position, Some(yaw), Some(pitch), world) + .await; + }); + }) +} diff --git a/rust/src/java/native_callbacks/events.rs b/rust/src/java/native_callbacks/events.rs index d71e36d..8946702 100644 --- a/rust/src/java/native_callbacks/events.rs +++ b/rust/src/java/native_callbacks/events.rs @@ -84,6 +84,11 @@ pub fn ffi_native_bridge_call_event_impl(request: CallEventRequest) -> Option todo!(), + Data::PluginDisable(_plugin_disable_event) => todo!(), + Data::PlayerQuit(_player_quit_event) => todo!(), + Data::PlayerInteract(_player_interact_event) => todo!(), + Data::PlayerGamemodeChange(_player_game_mode_change_event) => todo!(), } }) })?; diff --git a/rust/src/java/native_callbacks/mod.rs b/rust/src/java/native_callbacks/mod.rs index 56698aa..7dc86a4 100644 --- a/rust/src/java/native_callbacks/mod.rs +++ b/rust/src/java/native_callbacks/mod.rs @@ -36,6 +36,12 @@ pub use log::*; pub mod config; pub use config::*; +pub mod entity; +pub use entity::*; + +pub mod world; +pub use world::*; + pub mod itemstack; static CALLBACK_CONTEXT: OnceLock = OnceLock::new(); diff --git a/rust/src/java/native_callbacks/utils.rs b/rust/src/java/native_callbacks/utils.rs index 10bb191..5dd4cf6 100644 --- a/rust/src/java/native_callbacks/utils.rs +++ b/rust/src/java/native_callbacks/utils.rs @@ -1,6 +1,37 @@ +use super::CALLBACK_CONTEXT; +use crate::proto::patchbukkit::common::Uuid as ProtoUuid; +use pumpkin::entity::player::Player; +use std::collections::HashMap; use std::ffi::{CStr, c_char}; +use std::sync::{Arc, RwLock}; + +static PLAYER_HANDLE_CACHE: RwLock>>> = RwLock::new(None); #[must_use] pub fn get_string(str_ptr: *const c_char) -> String { unsafe { CStr::from_ptr(str_ptr).to_string_lossy().into_owned() } } + +pub fn with_player(proto_uuid: Option<&ProtoUuid>, f: F) -> Option +where + F: FnOnce(Arc) -> R, +{ + let ctx = CALLBACK_CONTEXT.get()?; + let uuid_str = &proto_uuid?.value; + let player_uuid = uuid::Uuid::parse_str(uuid_str).ok()?; + + if let Ok(read_guard) = PLAYER_HANDLE_CACHE.read() + && let Some(ref cache) = *read_guard + && let Some(player) = cache.get(&player_uuid) + { + return Some(f(player.clone())); + } + + let player = ctx.plugin_context.server.get_player_by_uuid(player_uuid)?; + if let Ok(mut write_guard) = PLAYER_HANDLE_CACHE.write() { + let cache = write_guard.get_or_insert_with(HashMap::new); + cache.insert(player_uuid, player.clone()); + } + + Some(f(player)) +} diff --git a/rust/src/java/native_callbacks/world.rs b/rust/src/java/native_callbacks/world.rs new file mode 100644 index 0000000..fbf26b8 --- /dev/null +++ b/rust/src/java/native_callbacks/world.rs @@ -0,0 +1,73 @@ +use crate::{ + java::native_callbacks::CALLBACK_CONTEXT, + proto::patchbukkit::world::{ + GetBlockDataRequest, GetBlockDataResponse, SetBlockDataRequest, SpawnParticleRequest, + }, +}; + +pub fn ffi_native_bridge_get_block_data_impl( + request: GetBlockDataRequest, +) -> Option { + let ctx = CALLBACK_CONTEXT.get()?; + let uuid_str = &request.world_uuid.as_ref()?.value; + let world_uuid = uuid::Uuid::parse_str(uuid_str).ok()?; + + let worlds = ctx.plugin_context.server.worlds.load_full(); + let world = worlds + .iter() + .find(|w| w.uuid == world_uuid) + .cloned() + .or_else(|| worlds.first().cloned())?; + let pos = pumpkin_util::math::position::BlockPos::new(request.x, request.y, request.z); + + let state_id = world.get_block_state(&pos).id; + + Some(GetBlockDataResponse { + block_state: format!("minecraft:block_{state_id}"), + }) +} + +pub fn ffi_native_bridge_set_block_data_impl(request: SetBlockDataRequest) -> Option<()> { + let ctx = CALLBACK_CONTEXT.get()?; + let uuid_str = &request.world_uuid.as_ref()?.value; + let world_uuid = uuid::Uuid::parse_str(uuid_str).ok()?; + + let worlds = ctx.plugin_context.server.worlds.load_full(); + let world = worlds + .iter() + .find(|w| w.uuid == world_uuid) + .cloned() + .or_else(|| worlds.first().cloned())?; + let pos = pumpkin_util::math::position::BlockPos::new(request.x, request.y, request.z); + + ctx.runtime.spawn(async move { + world + .set_block_state( + &pos, + pumpkin_data::BlockStateId::new_or_air(1), + pumpkin::world::BlockFlags::NOTIFY_ALL, + ) + .await; + }); + + Some(()) +} + +pub fn ffi_native_bridge_spawn_particle_impl(_request: SpawnParticleRequest) -> Option<()> { + Some(()) +} + +pub fn ffi_native_bridge_get_worlds_impl( + _request: crate::proto::patchbukkit::common::EmptyRequest, +) -> Option { + let ctx = CALLBACK_CONTEXT.get()?; + let worlds = ctx.plugin_context.server.worlds.load_full(); + let world_uuids = worlds + .iter() + .map(|w| crate::proto::patchbukkit::common::Uuid { + value: w.uuid.to_string(), + }) + .collect(); + + Some(crate::proto::patchbukkit::world::GetWorldsResponse { world_uuids }) +} diff --git a/rust/src/java/plugin/command_manager.rs b/rust/src/java/plugin/command_manager.rs index f688045..0efe4f0 100644 --- a/rust/src/java/plugin/command_manager.rs +++ b/rust/src/java/plugin/command_manager.rs @@ -169,7 +169,10 @@ impl CommandManager { &[InvocationArg::try_from(&cmd_name)?, j_plugin_arg], )?)); tracing::info!("Registering Bukkit command: {}", &cmd_name); - let cmd_aliases: Vec = cmd_data.aliases.as_ref().map_or_else(Vec::new, |a| a.to_vec()); + let cmd_aliases: Vec = cmd_data + .aliases + .as_ref() + .map_or_else(Vec::new, |a| a.to_vec()); let mut names: Vec = vec![cmd_name.clone()]; names.extend(cmd_aliases.clone()); @@ -178,21 +181,51 @@ impl CommandManager { let j_plugin_cmd_owned = jvm.clone_instance(&cmd_lock)?; if let Some(ref desc) = cmd_data.description { - let _ = jvm.invoke(&j_plugin_cmd_owned, "setDescription", &[InvocationArg::try_from(desc)?]); + let _ = jvm.invoke( + &j_plugin_cmd_owned, + "setDescription", + &[InvocationArg::try_from(desc)?], + ); } if let Some(ref usage) = cmd_data.usage { - let _ = jvm.invoke(&j_plugin_cmd_owned, "setUsage", &[InvocationArg::try_from(usage)?]); + let _ = jvm.invoke( + &j_plugin_cmd_owned, + "setUsage", + &[InvocationArg::try_from(usage)?], + ); } if let Some(ref perm_msg) = cmd_data.permission_message { - let _ = jvm.invoke(&j_plugin_cmd_owned, "setPermissionMessage", &[InvocationArg::try_from(perm_msg)?]); + let _ = jvm.invoke( + &j_plugin_cmd_owned, + "setPermissionMessage", + &[InvocationArg::try_from(perm_msg)?], + ); } if let Some(ref perm) = cmd_data.permission { - let _ = jvm.invoke(&j_plugin_cmd_owned, "setPermission", &[InvocationArg::try_from(perm)?]); + let _ = jvm.invoke( + &j_plugin_cmd_owned, + "setPermission", + &[InvocationArg::try_from(perm)?], + ); } if !cmd_aliases.is_empty() { - let j_aliases = jvm.create_java_array("java.lang.String", &cmd_aliases.iter().map(|a| InvocationArg::try_from(a).unwrap()).collect::>())?; - let j_list = jvm.invoke_static("java.util.Arrays", "asList", &[InvocationArg::from(j_aliases)])?; - let _ = jvm.invoke(&j_plugin_cmd_owned, "setAliases", &[InvocationArg::from(j_list)]); + let j_aliases = jvm.create_java_array( + "java.lang.String", + &cmd_aliases + .iter() + .map(|a| InvocationArg::try_from(a).unwrap()) + .collect::>(), + )?; + let j_list = jvm.invoke_static( + "java.util.Arrays", + "asList", + &[InvocationArg::from(j_aliases)], + )?; + let _ = jvm.invoke( + &j_plugin_cmd_owned, + "setAliases", + &[InvocationArg::from(j_list)], + ); } jvm.invoke( diff --git a/rust/src/proto/mod.rs b/rust/src/proto/mod.rs index ebf5045..91abe7f 100644 --- a/rust/src/proto/mod.rs +++ b/rust/src/proto/mod.rs @@ -38,6 +38,14 @@ pub mod patchbukkit { pub mod itemstack { include!(concat!(env!("OUT_DIR"), "/patchbukkit.itemstack.rs")); } + + pub mod entity { + include!(concat!(env!("OUT_DIR"), "/patchbukkit.entity.rs")); + } + + pub mod world { + include!(concat!(env!("OUT_DIR"), "/patchbukkit.world.rs")); + } } include!(concat!(env!("OUT_DIR"), "/ffi_init.rs"));