mirror of
https://github.com/Minestom/Minestom.git
synced 2024-06-25 22:14:53 +02:00
commit
1477a9bd41
|
@ -3,6 +3,7 @@ package net.minestom.server.entity;
|
|||
import com.extollit.gaming.ai.path.HydrazinePathFinder;
|
||||
import net.minestom.server.attribute.Attributes;
|
||||
import net.minestom.server.entity.ai.EntityAI;
|
||||
import net.minestom.server.entity.ai.EntityAIGroup;
|
||||
import net.minestom.server.entity.ai.GoalSelector;
|
||||
import net.minestom.server.entity.ai.TargetSelector;
|
||||
import net.minestom.server.entity.pathfinding.NavigableEntity;
|
||||
|
@ -14,17 +15,13 @@ import net.minestom.server.utils.time.TimeUnit;
|
|||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
|
||||
public class EntityCreature extends LivingEntity implements NavigableEntity, EntityAI {
|
||||
|
||||
private int removalAnimationDelay = 1000;
|
||||
|
||||
protected final List<GoalSelector> goalSelectors = new ArrayList<>();
|
||||
protected final List<TargetSelector> targetSelectors = new ArrayList<>();
|
||||
private GoalSelector currentGoalSelector;
|
||||
private final Set<EntityAIGroup> aiGroups = new HashSet<>();
|
||||
|
||||
private final Navigator navigator = new Navigator(this);
|
||||
|
||||
|
@ -108,27 +105,9 @@ public class EntityCreature extends LivingEntity implements NavigableEntity, Ent
|
|||
this.removalAnimationDelay = removalAnimationDelay;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<GoalSelector> getGoalSelectors() {
|
||||
return goalSelectors;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<TargetSelector> getTargetSelectors() {
|
||||
return targetSelectors;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public GoalSelector getCurrentGoalSelector() {
|
||||
return currentGoalSelector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCurrentGoalSelector(GoalSelector currentGoalSelector) {
|
||||
this.currentGoalSelector = currentGoalSelector;
|
||||
public Collection<EntityAIGroup> getAIGroups() {
|
||||
return this.aiGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,102 +1,51 @@
|
|||
package net.minestom.server.entity.ai;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents an entity which can contain
|
||||
* {@link GoalSelector goal selectors} and {@link TargetSelector target selectors}.
|
||||
* <p>
|
||||
* Both types of selectors are being stored in {@link EntityAIGroup AI groups}.
|
||||
* For every group there could be only a single {@link GoalSelector goal selector} running at a time,
|
||||
* but multiple groups are independent of each other, so each of them can have own goal selector running.
|
||||
*/
|
||||
public interface EntityAI {
|
||||
|
||||
/**
|
||||
* Gets the goal selectors of this entity.
|
||||
* Gets the AI groups of this entity.
|
||||
*
|
||||
* @return a modifiable list containing the entity goal selectors
|
||||
* @return a modifiable collection of AI groups of this entity.
|
||||
*/
|
||||
@NotNull
|
||||
List<GoalSelector> getGoalSelectors();
|
||||
Collection<EntityAIGroup> getAIGroups();
|
||||
|
||||
/**
|
||||
* Gets the target selectors of this entity.
|
||||
* Adds new AI group to this entity.
|
||||
*
|
||||
* @return a modifiable list containing the entity target selectors
|
||||
* @param group a group to be added.
|
||||
*/
|
||||
@NotNull
|
||||
List<TargetSelector> getTargetSelectors();
|
||||
|
||||
/**
|
||||
* Gets the current entity goal selector.
|
||||
*
|
||||
* @return the current entity goal selector, null if not any
|
||||
*/
|
||||
@Nullable
|
||||
GoalSelector getCurrentGoalSelector();
|
||||
|
||||
/**
|
||||
* Changes the entity current goal selector.
|
||||
* <p>
|
||||
* Mostly unsafe since the current goal selector should normally
|
||||
* be chosen during the entity tick method.
|
||||
*
|
||||
* @param goalSelector the new entity goal selector, null to disable it
|
||||
*/
|
||||
void setCurrentGoalSelector(@Nullable GoalSelector goalSelector);
|
||||
|
||||
/**
|
||||
* Performs an AI tick, it includes finding a new {@link GoalSelector}
|
||||
* or tick the current one,
|
||||
*
|
||||
* @param time the tick time in milliseconds
|
||||
*/
|
||||
default void aiTick(long time) {
|
||||
GoalSelector currentGoalSelector = getCurrentGoalSelector();
|
||||
// true if the goal selector changed this tick
|
||||
boolean newGoalSelector = false;
|
||||
|
||||
if (currentGoalSelector == null) {
|
||||
// No goal selector, get a new one
|
||||
currentGoalSelector = findGoal();
|
||||
newGoalSelector = currentGoalSelector != null;
|
||||
} else {
|
||||
final boolean stop = currentGoalSelector.shouldEnd();
|
||||
if (stop) {
|
||||
// The current goal selector stopped, find a new one
|
||||
currentGoalSelector.end();
|
||||
currentGoalSelector = findGoal();
|
||||
newGoalSelector = currentGoalSelector != null;
|
||||
}
|
||||
}
|
||||
|
||||
// Start the new goal selector
|
||||
if (newGoalSelector) {
|
||||
setCurrentGoalSelector(currentGoalSelector);
|
||||
currentGoalSelector.start();
|
||||
}
|
||||
|
||||
// Execute tick for the current goal selector
|
||||
if (currentGoalSelector != null) {
|
||||
currentGoalSelector.tick(time);
|
||||
}
|
||||
default void addAIGroup(EntityAIGroup group) {
|
||||
getAIGroups().add(group);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a new {@link GoalSelector} for the entity.
|
||||
* <p>
|
||||
* Uses {@link GoalSelector#shouldStart()} and return the goal selector if true.
|
||||
* Adds new AI group to this entity, consisting of the given
|
||||
* {@link GoalSelector goal selectors} and {@link TargetSelector target selectors}.
|
||||
* Their order is also a priority: the lower element index is, the higher priority is.
|
||||
*
|
||||
* @return the goal selector found, null if not any
|
||||
* @param goalSelectors goal selectors of the group.
|
||||
* @param targetSelectors target selectors of the group.
|
||||
*/
|
||||
private GoalSelector findGoal() {
|
||||
for (GoalSelector goalSelector : getGoalSelectors()) {
|
||||
final boolean start = goalSelector.shouldStart();
|
||||
if (start) {
|
||||
return goalSelector;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
default void addAIGroup(List<GoalSelector> goalSelectors, List<TargetSelector> targetSelectors) {
|
||||
EntityAIGroup group = new EntityAIGroup();
|
||||
group.getGoalSelectors().addAll(goalSelectors);
|
||||
group.getTargetSelectors().addAll(targetSelectors);
|
||||
addAIGroup(group);
|
||||
}
|
||||
|
||||
default void aiTick(long time) {
|
||||
getAIGroups().forEach(group -> group.tick(time));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
150
src/main/java/net/minestom/server/entity/ai/EntityAIGroup.java
Normal file
150
src/main/java/net/minestom/server/entity/ai/EntityAIGroup.java
Normal file
|
@ -0,0 +1,150 @@
|
|||
package net.minestom.server.entity.ai;
|
||||
|
||||
import net.minestom.server.utils.validate.Check;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
/**
|
||||
* Represents a group of entity's AI.
|
||||
* It may contains {@link GoalSelector goal selectors} and {@link TargetSelector target selectors}.
|
||||
* All AI groups of a single entity are independent of each other.
|
||||
*/
|
||||
public class EntityAIGroup {
|
||||
|
||||
private GoalSelector currentGoalSelector;
|
||||
private final List<GoalSelector> goalSelectors = new GoalSelectorsArrayList();
|
||||
private final List<TargetSelector> targetSelectors = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Gets the goal selectors of this group.
|
||||
*
|
||||
* @return a modifiable list containing this group goal selectors
|
||||
*/
|
||||
@NotNull
|
||||
public List<GoalSelector> getGoalSelectors() {
|
||||
return this.goalSelectors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the target selectors of this group.
|
||||
*
|
||||
* @return a modifiable list containing this group target selectors
|
||||
*/
|
||||
@NotNull
|
||||
public List<TargetSelector> getTargetSelectors() {
|
||||
return this.targetSelectors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current goal selector of this group.
|
||||
*
|
||||
* @return the current goal selector of this group, null if not any
|
||||
*/
|
||||
@Nullable
|
||||
public GoalSelector getCurrentGoalSelector() {
|
||||
return this.currentGoalSelector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the current goal selector of this group.
|
||||
* <p>
|
||||
* Mostly unsafe since the current goal selector should normally
|
||||
* be chosen during the group tick method.
|
||||
*
|
||||
* @param goalSelector the new goal selector of this group, null to disable it
|
||||
*/
|
||||
public void setCurrentGoalSelector(@Nullable GoalSelector goalSelector) {
|
||||
Check.argCondition(
|
||||
goalSelector != null && goalSelector.getAIGroup() != this,
|
||||
"Tried to set goal selector attached to another AI group!"
|
||||
);
|
||||
this.currentGoalSelector = goalSelector;
|
||||
}
|
||||
|
||||
public void tick(long time) {
|
||||
GoalSelector currentGoalSelector = getCurrentGoalSelector();
|
||||
|
||||
if (currentGoalSelector != null && currentGoalSelector.shouldEnd()) {
|
||||
currentGoalSelector.end();
|
||||
currentGoalSelector = null;
|
||||
setCurrentGoalSelector(null);
|
||||
}
|
||||
|
||||
for (GoalSelector selector : getGoalSelectors()) {
|
||||
if (selector == currentGoalSelector) {
|
||||
break;
|
||||
}
|
||||
if (selector.shouldStart()) {
|
||||
if (currentGoalSelector != null) {
|
||||
currentGoalSelector.end();
|
||||
}
|
||||
currentGoalSelector = selector;
|
||||
setCurrentGoalSelector(currentGoalSelector);
|
||||
currentGoalSelector.start();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentGoalSelector != null) {
|
||||
currentGoalSelector.tick(time);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The purpose of this list is to guarantee that every {@link GoalSelector} added to that group
|
||||
* has a reference to it for some internal interactions. We don't provide developers with
|
||||
* methods like `addGoalSelector` or `removeGoalSelector`: instead we provide them with direct
|
||||
* access to list of goal selectors, so that they could use operations such as `clear`, `set`, `removeIf`, etc.
|
||||
*/
|
||||
private class GoalSelectorsArrayList extends ArrayList<GoalSelector> {
|
||||
|
||||
private GoalSelectorsArrayList() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public GoalSelector set(int index, GoalSelector element) {
|
||||
element.setAIGroup(EntityAIGroup.this);
|
||||
return super.set(index, element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(GoalSelector element) {
|
||||
element.setAIGroup(EntityAIGroup.this);
|
||||
return super.add(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(int index, GoalSelector element) {
|
||||
element.setAIGroup(EntityAIGroup.this);
|
||||
super.add(index, element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends GoalSelector> c) {
|
||||
c.forEach(goalSelector -> goalSelector.setAIGroup(EntityAIGroup.this));
|
||||
return super.addAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(int index, Collection<? extends GoalSelector> c) {
|
||||
c.forEach(goalSelector -> goalSelector.setAIGroup(EntityAIGroup.this));
|
||||
return super.addAll(index, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceAll(UnaryOperator<GoalSelector> operator) {
|
||||
super.replaceAll(goalSelector -> {
|
||||
goalSelector = operator.apply(goalSelector);
|
||||
goalSelector.setAIGroup(EntityAIGroup.this);
|
||||
return goalSelector;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package net.minestom.server.entity.ai;
|
||||
|
||||
public class EntityAIGroupBuilder {
|
||||
|
||||
private final EntityAIGroup group = new EntityAIGroup();
|
||||
|
||||
/**
|
||||
* Adds {@link GoalSelector} to the list of goal selectors of the building {@link EntityAIGroup}.
|
||||
* Addition order is also a priority: priority the higher the earlier selector was added.
|
||||
*
|
||||
* @param goalSelector goal selector to be added.
|
||||
* @return this builder.
|
||||
*/
|
||||
public EntityAIGroupBuilder addGoalSelector(GoalSelector goalSelector) {
|
||||
this.group.getGoalSelectors().add(goalSelector);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds {@link TargetSelector} to the list of target selectors of the building {@link EntityAIGroup}.
|
||||
* Addition order is also a priority: priority the higher the earlier selector was added.
|
||||
*
|
||||
* @param targetSelector target selector to be added.
|
||||
* @return this builder.
|
||||
*/
|
||||
public EntityAIGroupBuilder addTargetSelector(TargetSelector targetSelector) {
|
||||
this.group.getTargetSelectors().add(targetSelector);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link EntityAIGroup}.
|
||||
*
|
||||
* @return new {@link EntityAIGroup}.
|
||||
*/
|
||||
public EntityAIGroup build() {
|
||||
return this.group;
|
||||
}
|
||||
|
||||
}
|
|
@ -5,8 +5,11 @@ import net.minestom.server.entity.EntityCreature;
|
|||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
public abstract class GoalSelector {
|
||||
|
||||
private WeakReference<EntityAIGroup> aiGroupWeakReference;
|
||||
protected EntityCreature entityCreature;
|
||||
|
||||
public GoalSelector(@NotNull EntityCreature entityCreature) {
|
||||
|
@ -51,7 +54,11 @@ public abstract class GoalSelector {
|
|||
*/
|
||||
@Nullable
|
||||
public Entity findTarget() {
|
||||
for (TargetSelector targetSelector : entityCreature.getTargetSelectors()) {
|
||||
EntityAIGroup aiGroup = getAIGroup();
|
||||
if (aiGroup == null) {
|
||||
return null;
|
||||
}
|
||||
for (TargetSelector targetSelector : aiGroup.getTargetSelectors()) {
|
||||
final Entity entity = targetSelector.findTarget();
|
||||
if (entity != null) {
|
||||
return entity;
|
||||
|
@ -74,12 +81,22 @@ public abstract class GoalSelector {
|
|||
* Changes the entity affected by the goal selector.
|
||||
* <p>
|
||||
* WARNING: this does not add the goal selector to {@code entityCreature},
|
||||
* this only change the internal entity field. Be sure to remove the goal from
|
||||
* the previous entity and add it to the new one using {@link EntityCreature#getGoalSelectors()}.
|
||||
* this only change the internal entity AI group's field. Be sure to remove the goal from
|
||||
* the previous entity AI group and add it to the new one using {@link EntityAIGroup#getGoalSelectors()}.
|
||||
*
|
||||
* @param entityCreature the new affected entity
|
||||
*/
|
||||
public void setEntityCreature(@NotNull EntityCreature entityCreature) {
|
||||
this.entityCreature = entityCreature;
|
||||
}
|
||||
|
||||
void setAIGroup(@NotNull EntityAIGroup group) {
|
||||
this.aiGroupWeakReference = new WeakReference<>(group);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected EntityAIGroup getAIGroup() {
|
||||
return this.aiGroupWeakReference.get();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,41 +1,48 @@
|
|||
package demo.entity;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import net.minestom.server.attribute.Attributes;
|
||||
import net.minestom.server.entity.LivingEntity;
|
||||
import net.minestom.server.entity.ai.EntityAIGroupBuilder;
|
||||
import net.minestom.server.entity.ai.goal.DoNothingGoal;
|
||||
import net.minestom.server.entity.ai.goal.MeleeAttackGoal;
|
||||
import net.minestom.server.entity.ai.goal.RandomStrollGoal;
|
||||
import net.minestom.server.entity.ai.target.ClosestEntityTarget;
|
||||
import net.minestom.server.entity.ai.target.LastEntityDamagerTarget;
|
||||
import net.minestom.server.entity.damage.DamageType;
|
||||
import net.minestom.server.entity.type.animal.EntityChicken;
|
||||
import net.minestom.server.event.entity.EntityAttackEvent;
|
||||
import net.minestom.server.utils.Position;
|
||||
import net.minestom.server.utils.Vector;
|
||||
import net.minestom.server.utils.time.TimeUnit;
|
||||
|
||||
public class ChickenCreature extends EntityChicken {
|
||||
|
||||
public ChickenCreature(Position defaultPosition) {
|
||||
super(defaultPosition);
|
||||
|
||||
//goalSelectors.add(new DoNothingGoal(this, 500, 0.1f));
|
||||
//goalSelectors.add(new MeleeAttackGoal(this, 500, TimeUnit.MILLISECOND));
|
||||
goalSelectors.add(new RandomStrollGoal(this, 2));
|
||||
/*goalSelectors.add(new EatBlockGoal(this,
|
||||
new HashMap<>() {
|
||||
{
|
||||
put(Block.GRASS.getBlockId(), Block.AIR.getBlockId());
|
||||
}
|
||||
},
|
||||
new HashMap<>() {
|
||||
{
|
||||
put(Block.GRASS_BLOCK.getBlockId(), Block.DIRT.getBlockId());
|
||||
}
|
||||
},
|
||||
100))
|
||||
;
|
||||
//goalSelectors.add(new FollowTargetGoal(this));*/
|
||||
|
||||
|
||||
//targetSelectors.add(new LastEntityDamagerTarget(this, 15));
|
||||
//targetSelectors.add(new ClosestEntityTarget(this, 15, LivingEntity.class));
|
||||
addAIGroup(
|
||||
ImmutableList.of(
|
||||
// new DoNothingGoal(this, 500, 0.1f),
|
||||
// new MeleeAttackGoal(this, 500, 2, TimeUnit.MILLISECOND),
|
||||
new RandomStrollGoal(this, 2)
|
||||
),
|
||||
ImmutableList.of(
|
||||
// new LastEntityDamagerTarget(this, 15),
|
||||
// new ClosestEntityTarget(this, 15, LivingEntity.class)
|
||||
)
|
||||
);
|
||||
|
||||
// Another way to register previously added EntityAIGroup, using specialized builder:
|
||||
// addAIGroup(
|
||||
// new EntityAIGroupBuilder()
|
||||
// .addGoalSelector(new DoNothingGoal(this, 500, .1F))
|
||||
// .addGoalSelector(new MeleeAttackGoal(this, 500, 2, TimeUnit.MILLISECOND))
|
||||
// .addGoalSelector(new RandomStrollGoal(this, 2))
|
||||
// .addTargetSelector(new LastEntityDamagerTarget(this, 15))
|
||||
// .addTargetSelector(new ClosestEntityTarget(this, 15, LivingEntity.class))
|
||||
// .build()
|
||||
// );
|
||||
|
||||
getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(0.1f);
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package demo.entity;
|
||||
|
||||
import net.minestom.server.entity.ai.EntityAIGroupBuilder;
|
||||
import net.minestom.server.entity.ai.goal.RandomLookAroundGoal;
|
||||
import net.minestom.server.entity.type.monster.EntityZombie;
|
||||
import net.minestom.server.utils.Position;
|
||||
|
@ -8,6 +9,10 @@ public class ZombieCreature extends EntityZombie {
|
|||
|
||||
public ZombieCreature(Position spawnPosition) {
|
||||
super(spawnPosition);
|
||||
goalSelectors.add(new RandomLookAroundGoal(this, 20));
|
||||
addAIGroup(
|
||||
new EntityAIGroupBuilder()
|
||||
.addGoalSelector(new RandomLookAroundGoal(this, 20))
|
||||
.build()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user