From 3fd1efb120fa1f35817ecbac007fafdd477ac0c6 Mon Sep 17 00:00:00 2001 From: Felix Cravic Date: Thu, 6 Aug 2020 11:56:43 +0200 Subject: [PATCH] Basic goal selector API --- src/main/java/fr/themode/demo/PlayerInit.java | 8 +- .../themode/demo/entity/ChickenCreature.java | 70 +------------- .../net/minestom/server/bossbar/BossBar.java | 19 ++++ .../server/entity/EntityCreature.java | 91 +++++++++++++++++++ .../server/entity/ai/GoalSelector.java | 66 ++++++++++++++ .../server/entity/ai/TargetSelector.java | 36 ++++++++ .../server/entity/ai/goal/DoNothingGoal.java | 54 +++++++++++ .../server/instance/batch/ChunkBatch.java | 1 + .../server/utils/chunk/ChunkUtils.java | 18 ++-- 9 files changed, 284 insertions(+), 79 deletions(-) create mode 100644 src/main/java/net/minestom/server/entity/ai/GoalSelector.java create mode 100644 src/main/java/net/minestom/server/entity/ai/TargetSelector.java create mode 100644 src/main/java/net/minestom/server/entity/ai/goal/DoNothingGoal.java diff --git a/src/main/java/fr/themode/demo/PlayerInit.java b/src/main/java/fr/themode/demo/PlayerInit.java index f3aec1100..ec402c7d5 100644 --- a/src/main/java/fr/themode/demo/PlayerInit.java +++ b/src/main/java/fr/themode/demo/PlayerInit.java @@ -1,5 +1,6 @@ package fr.themode.demo; +import fr.themode.demo.entity.ChickenCreature; import fr.themode.demo.generator.ChunkGeneratorDemo; import fr.themode.demo.generator.NoiseTestGenerator; import net.minestom.server.MinecraftServer; @@ -148,9 +149,8 @@ public class PlayerInit { p.teleport(player.getPosition()); }*/ - /*ChickenCreature chickenCreature = new ChickenCreature(player.getPosition()); + ChickenCreature chickenCreature = new ChickenCreature(player.getPosition()); chickenCreature.setInstance(player.getInstance()); - chickenCreature.setAttribute(Attribute.MOVEMENT_SPEED, 0.4f);*/ /*EntityZombie zombie = new EntityZombie(player.getPosition()); zombie.setAttribute(Attribute.MOVEMENT_SPEED, 0.25f); @@ -230,6 +230,7 @@ public class PlayerInit { AdvancementRoot root = new AdvancementRoot(ColoredText.of("title"), ColoredText.of(ChatColor.BLUE + "description"), Material.APPLE, FrameType.TASK, 0, 0, "minecraft:textures/block/red_wool.png"); + root.setAchieved(true); AdvancementTab tab = advancementManager.createTab("root", root); Advancement advancement = new Advancement(ColoredText.of("adv"), ColoredText.of("desc"), Material.WOODEN_AXE, FrameType.CHALLENGE, 1, 0) @@ -240,7 +241,8 @@ public class PlayerInit { root.setTitle(ColoredText.of("test ttlechange")); - Advancement advancement2 = new Advancement(ColoredText.of("adv"), ColoredText.of("desc"), + Advancement advancement2 = new Advancement(ColoredText.of(ChatColor.BLUE + "Title"), + ColoredText.of("description of the advancement"), Material.GOLD_BLOCK, FrameType.CHALLENGE, 3, 0) .showToast(true).setHidden(false); tab.createAdvancement("second2", advancement2, root); diff --git a/src/main/java/fr/themode/demo/entity/ChickenCreature.java b/src/main/java/fr/themode/demo/entity/ChickenCreature.java index a6973a7d5..94e2d7be7 100644 --- a/src/main/java/fr/themode/demo/entity/ChickenCreature.java +++ b/src/main/java/fr/themode/demo/entity/ChickenCreature.java @@ -1,83 +1,19 @@ package fr.themode.demo.entity; -import net.minestom.server.entity.Entity; -import net.minestom.server.entity.Player; +import net.minestom.server.entity.ai.goal.DoNothingGoal; import net.minestom.server.entity.type.EntityChicken; -import net.minestom.server.entity.vehicle.PlayerVehicleInformation; import net.minestom.server.utils.Position; -import net.minestom.server.utils.Vector; public class ChickenCreature extends EntityChicken { public ChickenCreature(Position defaultPosition) { super(defaultPosition); + + goalSelectors.add(new DoNothingGoal(this, 500, 0.1f)); } @Override public void spawn() { } - - @Override - public void update(long time) { - super.update(time); - float speed = 0.075f; - - if (hasPassenger()) { - Entity passenger = getPassengers().iterator().next(); - if (passenger instanceof Player) { - Player player = (Player) passenger; - PlayerVehicleInformation vehicleInformation = player.getVehicleInformation(); - float sideways = vehicleInformation.getSideways(); - float forward = vehicleInformation.getForward(); - - boolean jump = vehicleInformation.shouldJump(); - boolean unmount = vehicleInformation.shouldUnmount(); - - if (jump && isOnGround()) { - setVelocity(new Vector(0, 6, 0)); - } - - boolean updateView = forward > 0; - if (sideways == 0f && forward == 0f) - return; - - float yaw = player.getPosition().getYaw(); - yaw %= 360; - - sideways = yaw + (updateView ? -sideways * 90 : sideways * 90); - - if (forward > 0) { - forward = yaw * forward; - } else { - forward = yaw + forward * 360; - } - yaw = (forward + sideways) / 2 % 360; - double radian = Math.toRadians(yaw + 90); - double cos = Math.cos(radian); - double sin = Math.sin(radian); - float x = (float) cos * speed; - float z = (float) sin * speed; - - /*BlockPosition blockPosition = getPosition().toBlockPosition(); - BlockPosition belowPosition = blockPosition.clone().add(0, -1, 0); - BlockPosition upPosition = blockPosition.clone().add(0, 1, 0); - boolean airCurrent = getInstance().getBlockId(blockPosition) == 0; - boolean airBelow = getInstance().getBlockId(belowPosition) == 0; - boolean airUp = getInstance().getBlockId(upPosition) == 0; - boolean shouldJump = false; - int boundingBoxY = (int) Math.ceil(getBoundingBox().getY()); - for (int i = 0; i < boundingBoxY; i++) { - - }*/ - - //System.out.println("test: "+player.isVehicleJump()); - //System.out.println(getInstance().getBlockId(getPosition().toBlockPosition())); - - move(x, 0, z, updateView); - } - } else { - //move(-0.5f * speed, 0, 0.5f * speed, false); - } - } } diff --git a/src/main/java/net/minestom/server/bossbar/BossBar.java b/src/main/java/net/minestom/server/bossbar/BossBar.java index e3c4d7857..40b0162cb 100644 --- a/src/main/java/net/minestom/server/bossbar/BossBar.java +++ b/src/main/java/net/minestom/server/bossbar/BossBar.java @@ -133,6 +133,25 @@ public class BossBar implements Viewable { updateStyle(); } + /** + * Get the bossbar flags + * + * @return the flags + */ + public byte getFlags() { + return flags; + } + + /** + * Set the bossbar flags + * + * @param flags the bossbar flags + * @see Boss bar packet + */ + public void setFlags(byte flags) { + this.flags = flags; + } + /** * Delete the boss bar and remove all of its viewers */ diff --git a/src/main/java/net/minestom/server/entity/EntityCreature.java b/src/main/java/net/minestom/server/entity/EntityCreature.java index 97e7f6c58..d63aa6573 100644 --- a/src/main/java/net/minestom/server/entity/EntityCreature.java +++ b/src/main/java/net/minestom/server/entity/EntityCreature.java @@ -5,6 +5,8 @@ import com.extollit.gaming.ai.path.model.PathObject; import net.minestom.server.MinecraftServer; import net.minestom.server.attribute.Attribute; import net.minestom.server.collision.CollisionUtils; +import net.minestom.server.entity.ai.GoalSelector; +import net.minestom.server.entity.ai.TargetSelector; import net.minestom.server.entity.pathfinding.PFPathingEntity; import net.minestom.server.event.entity.EntityAttackEvent; import net.minestom.server.event.item.ArmorEquipEvent; @@ -19,12 +21,22 @@ import net.minestom.server.utils.item.ItemStackUtils; import net.minestom.server.utils.time.TimeUnit; import net.minestom.server.utils.validate.Check; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + public abstract class EntityCreature extends LivingEntity { private PFPathingEntity pathingEntity = new PFPathingEntity(this); private HydrazinePathFinder pathFinder; private PathObject path; + protected List goalSelectors = new ArrayList<>(); + protected List targetSelectors = new ArrayList<>(); + private GoalSelector currentGoalSelector; + + private Entity target; + // Equipments private ItemStack mainHandItem; private ItemStack offHandItem; @@ -51,6 +63,49 @@ public abstract class EntityCreature extends LivingEntity { @Override public void update(long time) { + + { + // Supplier used to get the next goal selector which should start + // (null if not found) + final Supplier goalSelectorSupplier = () -> { + for (GoalSelector goalSelector : goalSelectors) { + final boolean start = goalSelector.shouldStart(); + if (start) { + return goalSelector; + } + } + return null; + }; + + // true if the goal selector changed this tick + boolean newGoalSelector = false; + + if (currentGoalSelector == null) { + // No goal selector, get a new one + this.currentGoalSelector = goalSelectorSupplier.get(); + newGoalSelector = currentGoalSelector != null; + } else { + final boolean stop = currentGoalSelector.shouldEnd(); + if (stop) { + // The current goal selector stopped, find a new one + this.currentGoalSelector.end(); + this.currentGoalSelector = goalSelectorSupplier.get(); + newGoalSelector = currentGoalSelector != null; + } + } + + // Start the new goal selector + if (newGoalSelector) { + this.currentGoalSelector.start(); + } + + // Execute tick for the goal selector + if (currentGoalSelector != null) { + currentGoalSelector.tick(); + } + } + + // Path finding path = pathFinder.update(); if (path != null) { @@ -180,6 +235,42 @@ public abstract class EntityCreature extends LivingEntity { return result; } + /** + * Get the goal selectors of this entity + * + * @return a modifiable list containing the entity goal selectors + */ + public List getGoalSelectors() { + return goalSelectors; + } + + /** + * Get the target selectors of this entity + * + * @return a modifiable list containing the entity target selectors + */ + public List getTargetSelectors() { + return targetSelectors; + } + + /** + * Get the entity target + * + * @return the entity target + */ + public Entity getTarget() { + return target; + } + + /** + * Change the entity target + * + * @param target the new entity target + */ + public void setTarget(Entity target) { + this.target = target; + } + @Override public ItemStack getItemInMainHand() { return mainHandItem; diff --git a/src/main/java/net/minestom/server/entity/ai/GoalSelector.java b/src/main/java/net/minestom/server/entity/ai/GoalSelector.java new file mode 100644 index 000000000..664822160 --- /dev/null +++ b/src/main/java/net/minestom/server/entity/ai/GoalSelector.java @@ -0,0 +1,66 @@ +package net.minestom.server.entity.ai; + +import net.minestom.server.entity.Entity; +import net.minestom.server.entity.EntityCreature; + +public abstract class GoalSelector { + + private EntityCreature entityCreature; + + public GoalSelector(EntityCreature entityCreature) { + this.entityCreature = entityCreature; + } + + /** + * Whether or not this {@link GoalSelector} should start. + * + * @return true to start + */ + public abstract boolean shouldStart(); + + /** + * Start this {@link GoalSelector} + */ + public abstract void start(); + + /** + * Called every tick when this {@link GoalSelector} is running + */ + public abstract void tick(); + + /** + * Whether or not this {@link GoalSelector} should end. + * + * @return true to end + */ + public abstract boolean shouldEnd(); + + /** + * End this {@link GoalSelector} + */ + public abstract void end(); + + /** + * Get the entity linked to this goal selector + * + * @return the entity + */ + public EntityCreature getEntityCreature() { + return entityCreature; + } + + /** + * Find a target based on the entity {@link TargetSelector} + * + * @return the target entity, null if not found + */ + public Entity findTarget() { + for (TargetSelector targetSelector : entityCreature.getTargetSelectors()) { + final Entity entity = targetSelector.findTarget(); + if (entity != null) { + return entity; + } + } + return null; + } +} diff --git a/src/main/java/net/minestom/server/entity/ai/TargetSelector.java b/src/main/java/net/minestom/server/entity/ai/TargetSelector.java new file mode 100644 index 000000000..460181c9d --- /dev/null +++ b/src/main/java/net/minestom/server/entity/ai/TargetSelector.java @@ -0,0 +1,36 @@ +package net.minestom.server.entity.ai; + +import net.minestom.server.entity.Entity; +import net.minestom.server.entity.EntityCreature; + +/** + * The target selector is called each time the entity receives an "attack" instruction + * without having a target + */ +public abstract class TargetSelector { + + private final EntityCreature entityCreature; + + public TargetSelector(EntityCreature entityCreature) { + this.entityCreature = entityCreature; + } + + /** + * Find the target + *

+ * returning null means that this target selector didn't find any entity, + * the next {@link TargetSelector} will be called until the end of the list or an entity is found + * + * @return the target, null if not any + */ + public abstract Entity findTarget(); + + /** + * Get the entity linked to this target selector + * + * @return the entity + */ + public EntityCreature getEntityCreature() { + return entityCreature; + } +} diff --git a/src/main/java/net/minestom/server/entity/ai/goal/DoNothingGoal.java b/src/main/java/net/minestom/server/entity/ai/goal/DoNothingGoal.java new file mode 100644 index 000000000..e2f76f4f3 --- /dev/null +++ b/src/main/java/net/minestom/server/entity/ai/goal/DoNothingGoal.java @@ -0,0 +1,54 @@ +package net.minestom.server.entity.ai.goal; + +import net.minestom.server.entity.EntityCreature; +import net.minestom.server.entity.ai.GoalSelector; +import net.minestom.server.utils.MathUtils; + +import java.util.Random; + +public class DoNothingGoal extends GoalSelector { + + private static final Random RANDOM = new Random(); + + private long time; + private float chance; + private long startTime; + + /** + * Create a DoNothing goal + * + * @param entityCreature the entity + * @param time the time in milliseconds where nothing happen + * @param chance the chance to do nothing (0-1) + */ + public DoNothingGoal(EntityCreature entityCreature, long time, float chance) { + super(entityCreature); + this.time = time; + this.chance = MathUtils.setBetween(chance, 0, 1); + } + + @Override + public void end() { + this.startTime = 0; + } + + @Override + public boolean shouldEnd() { + return System.currentTimeMillis() - startTime >= time; + } + + @Override + public boolean shouldStart() { + return RANDOM.nextFloat() <= chance; + } + + @Override + public void start() { + this.startTime = System.currentTimeMillis(); + } + + @Override + public void tick() { + + } +} diff --git a/src/main/java/net/minestom/server/instance/batch/ChunkBatch.java b/src/main/java/net/minestom/server/instance/batch/ChunkBatch.java index af85f4b93..ac9ff1f4f 100644 --- a/src/main/java/net/minestom/server/instance/batch/ChunkBatch.java +++ b/src/main/java/net/minestom/server/instance/batch/ChunkBatch.java @@ -48,6 +48,7 @@ public class ChunkBatch implements InstanceBatch { } private void addBlockData(byte x, int y, byte z, boolean customBlock, short blockId, short customBlockId, Data data) { + // TODO store a single long with bitwise operators (xyz;boolean,short,short,boolean) with the data in a map BlockData blockData = new BlockData(); blockData.x = x; blockData.y = y; diff --git a/src/main/java/net/minestom/server/utils/chunk/ChunkUtils.java b/src/main/java/net/minestom/server/utils/chunk/ChunkUtils.java index ee87503cd..34759a066 100644 --- a/src/main/java/net/minestom/server/utils/chunk/ChunkUtils.java +++ b/src/main/java/net/minestom/server/utils/chunk/ChunkUtils.java @@ -23,10 +23,10 @@ public class ChunkUtils { * @return true if the chunk is unloaded, false otherwise */ public static boolean isChunkUnloaded(Instance instance, float x, float z) { - int chunkX = getChunkCoordinate((int) x); - int chunkZ = getChunkCoordinate((int) z); + final int chunkX = getChunkCoordinate((int) x); + final int chunkZ = getChunkCoordinate((int) z); - Chunk chunk = instance.getChunk(chunkX, chunkZ); + final Chunk chunk = instance.getChunk(chunkX, chunkZ); return isChunkUnloaded(chunk); } @@ -53,8 +53,8 @@ public class ChunkUtils { * @return an array containing both the chunk X and Z (index 0 = X; index 1 = Z) */ public static int[] getChunkCoord(long index) { - int chunkX = (int) (index >> 32); - int chunkZ = (int) index; + final int chunkX = (int) (index >> 32); + final int chunkZ = (int) index; return new int[]{chunkX, chunkZ}; } @@ -74,8 +74,8 @@ public class ChunkUtils { int counter = 0; for (int x = startLoop; x < endLoop; x++) { for (int z = startLoop; z < endLoop; z++) { - int chunkX = getChunkCoordinate((int) (position.getX() + Chunk.CHUNK_SIZE_X * x)); - int chunkZ = getChunkCoordinate((int) (position.getZ() + Chunk.CHUNK_SIZE_Z * z)); + final int chunkX = getChunkCoordinate((int) (position.getX() + Chunk.CHUNK_SIZE_X * x)); + final int chunkZ = getChunkCoordinate((int) (position.getZ() + Chunk.CHUNK_SIZE_Z * z)); visibleChunks[counter] = getChunkIndex(chunkX, chunkZ); counter++; } @@ -106,7 +106,7 @@ public class ChunkUtils { * @return the instance position of the block located in {@code index} */ public static BlockPosition getBlockPosition(int index, int chunkX, int chunkZ) { - int[] pos = indexToPosition(index, chunkX, chunkZ); + final int[] pos = indexToPosition(index, chunkX, chunkZ); return new BlockPosition(pos[0], pos[1], pos[2]); } @@ -119,7 +119,7 @@ public class ChunkUtils { */ public static int[] indexToPosition(int index, int chunkX, int chunkZ) { int z = (byte) (index >> 12 & 0xF); - int y = (index >>> 4 & 0xFF); + final int y = (index >>> 4 & 0xFF); int x = (byte) (index >> 0 & 0xF); x += 16 * chunkX;