update ai of gypsy
This commit is contained in:
@@ -6,7 +6,7 @@ minecraft_version=1.20.1
|
|||||||
yarn_mappings=1.20.1+build.10
|
yarn_mappings=1.20.1+build.10
|
||||||
loader_version=0.18.3
|
loader_version=0.18.3
|
||||||
# Mod Properties
|
# Mod Properties
|
||||||
mod_version=1.0.3
|
mod_version=1.0.4
|
||||||
maven_group=dev.tggamesyt
|
maven_group=dev.tggamesyt
|
||||||
archives_base_name=szar
|
archives_base_name=szar
|
||||||
# Dependencies
|
# Dependencies
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package dev.tggamesyt.szar;
|
package dev.tggamesyt.szar;
|
||||||
|
|
||||||
import net.minecraft.entity.EntityType;
|
import net.minecraft.entity.*;
|
||||||
|
import net.minecraft.entity.ai.TargetPredicate;
|
||||||
import net.minecraft.entity.ai.goal.Goal;
|
import net.minecraft.entity.ai.goal.Goal;
|
||||||
import net.minecraft.entity.ai.goal.LookAroundGoal;
|
import net.minecraft.entity.ai.goal.LookAroundGoal;
|
||||||
import net.minecraft.entity.ai.goal.WanderAroundFarGoal;
|
import net.minecraft.entity.ai.goal.WanderAroundFarGoal;
|
||||||
@@ -15,67 +16,67 @@ import net.minecraft.util.collection.DefaultedList;
|
|||||||
import net.minecraft.util.math.Vec3d;
|
import net.minecraft.util.math.Vec3d;
|
||||||
import net.minecraft.world.World;
|
import net.minecraft.world.World;
|
||||||
|
|
||||||
import java.util.EnumSet;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class GypsyEntity extends PathAwareEntity {
|
public class GypsyEntity extends PathAwareEntity {
|
||||||
|
|
||||||
private final DefaultedList<ItemStack> stolenItems = DefaultedList.of();
|
private final DefaultedList<ItemStack> stolenItems = DefaultedList.of();
|
||||||
private int stealCooldown = 0;
|
private final Set<UUID> stolenFromPlayers = new HashSet<>();
|
||||||
private boolean fleeing = false;
|
|
||||||
|
|
||||||
private static final double FLEE_DISTANCE = 15.0;
|
private int stealCooldown = 0;
|
||||||
|
private int panicTicks = 0;
|
||||||
|
private UUID fleeingFrom = null;
|
||||||
|
private int heldItemSwapTicks = 0;
|
||||||
|
|
||||||
public GypsyEntity(EntityType<? extends PathAwareEntity> type, World world) {
|
public GypsyEntity(EntityType<? extends PathAwareEntity> type, World world) {
|
||||||
super(type, world);
|
super(type, world);
|
||||||
|
this.setCanPickUpLoot(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ================= ATTRIBUTES =================
|
// ================= ATTRIBUTES =================
|
||||||
|
|
||||||
public static DefaultAttributeContainer.Builder createAttributes() {
|
public static DefaultAttributeContainer.Builder createAttributes() {
|
||||||
return MobEntity.createMobAttributes()
|
return MobEntity.createMobAttributes()
|
||||||
.add(EntityAttributes.GENERIC_MAX_HEALTH, 20.0)
|
.add(EntityAttributes.GENERIC_MAX_HEALTH, 20.0)
|
||||||
.add(EntityAttributes.GENERIC_MOVEMENT_SPEED, 0.25);
|
.add(EntityAttributes.GENERIC_MOVEMENT_SPEED, 0.25)
|
||||||
|
.add(EntityAttributes.GENERIC_ATTACK_DAMAGE, 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ================= GOALS =================
|
// ================= GOALS =================
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void initGoals() {
|
protected void initGoals() {
|
||||||
this.goalSelector.add(0, new FleeWhenSeenGoal(this));
|
this.goalSelector.add(0, new PanicRandomlyGoal(this));
|
||||||
this.goalSelector.add(1, new SneakBehindPlayerGoal(this));
|
this.goalSelector.add(1, new FleeSpecificPlayerGoal(this));
|
||||||
this.goalSelector.add(2, new WanderAroundFarGoal(this, 0.8));
|
this.goalSelector.add(2, new DefensiveAttackGoal(this));
|
||||||
this.goalSelector.add(3, new LookAroundGoal(this));
|
this.goalSelector.add(3, new SneakBehindPlayerGoal(this));
|
||||||
|
this.goalSelector.add(4, new BiasedWanderGoal(this, 0.6));
|
||||||
|
this.goalSelector.add(5, new LookAroundGoal(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ================= TICK =================
|
// ================= TICK =================
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick() {
|
public void tick() {
|
||||||
super.tick();
|
super.tick();
|
||||||
|
|
||||||
if (stealCooldown > 0) {
|
if (stealCooldown > 0) stealCooldown--;
|
||||||
stealCooldown--;
|
|
||||||
|
if (!this.getWorld().isClient && !stolenItems.isEmpty()) {
|
||||||
|
heldItemSwapTicks++;
|
||||||
|
if (heldItemSwapTicks > 60) {
|
||||||
|
heldItemSwapTicks = 0;
|
||||||
|
stolenItems.add(stolenItems.remove(0));
|
||||||
|
}
|
||||||
|
equipStack(EquipmentSlot.MAINHAND, stolenItems.get(0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ================= VISIBILITY CHECK =================
|
// ================= VISIBILITY =================
|
||||||
|
|
||||||
/**
|
|
||||||
* True if the entity is anywhere on the player's screen (FOV-based)
|
|
||||||
*/
|
|
||||||
private boolean isOnPlayerScreen(PlayerEntity player) {
|
private boolean isOnPlayerScreen(PlayerEntity player) {
|
||||||
if (player.isCreative()) return false;
|
|
||||||
|
|
||||||
Vec3d look = player.getRotationVec(1.0F).normalize();
|
Vec3d look = player.getRotationVec(1.0F).normalize();
|
||||||
Vec3d toEntity = this.getPos().subtract(player.getEyePos()).normalize();
|
Vec3d toEntity = this.getPos().subtract(player.getEyePos()).normalize();
|
||||||
|
return look.dotProduct(toEntity) > 0.55;
|
||||||
// Rough FOV check (~120° total)
|
|
||||||
return look.dotProduct(toEntity) > 0.3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ================= STEALING =================
|
// ================= STEALING =================
|
||||||
|
|
||||||
private void trySteal(PlayerEntity player) {
|
private void trySteal(PlayerEntity player) {
|
||||||
if (stealCooldown > 0 || player.isCreative() || this.getWorld().isClient) return;
|
if (stealCooldown > 0 || player.isCreative() || this.getWorld().isClient) return;
|
||||||
|
|
||||||
@@ -89,20 +90,37 @@ public class GypsyEntity extends PathAwareEntity {
|
|||||||
ItemStack stolen = chosen.split(1);
|
ItemStack stolen = chosen.split(1);
|
||||||
|
|
||||||
stolenItems.add(stolen);
|
stolenItems.add(stolen);
|
||||||
stealCooldown = 20 * 20; // 20 seconds
|
equipStack(EquipmentSlot.MAINHAND, stolen);
|
||||||
fleeing = true;
|
|
||||||
|
|
||||||
|
stolenFromPlayers.add(player.getUuid());
|
||||||
|
fleeingFrom = player.getUuid();
|
||||||
|
|
||||||
|
stealCooldown = 20 * 20;
|
||||||
this.getNavigation().stop();
|
this.getNavigation().stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ================= DAMAGE & LOOT =================
|
// ================= ITEM PICKUP (WORLD ITEMS, NOT CRIME) =================
|
||||||
|
@Override
|
||||||
|
protected void loot(ItemEntity item) {
|
||||||
|
if (this.getWorld().isClient) return;
|
||||||
|
ItemStack stack = item.getStack();
|
||||||
|
if (stack.isEmpty()) return;
|
||||||
|
|
||||||
|
ItemStack taken = stack.split(1);
|
||||||
|
stolenItems.add(taken);
|
||||||
|
equipStack(EquipmentSlot.MAINHAND, taken);
|
||||||
|
|
||||||
|
if (stack.isEmpty()) item.discard();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================= DAMAGE =================
|
||||||
@Override
|
@Override
|
||||||
public boolean damage(DamageSource source, float amount) {
|
public boolean damage(DamageSource source, float amount) {
|
||||||
boolean result = super.damage(source, amount);
|
boolean result = super.damage(source, amount);
|
||||||
|
|
||||||
if (!this.getWorld().isClient && !stolenItems.isEmpty()) {
|
if (!this.getWorld().isClient) {
|
||||||
this.dropStack(stolenItems.remove(0));
|
panicTicks = 60 + random.nextInt(40);
|
||||||
|
if (!stolenItems.isEmpty()) this.dropStack(stolenItems.remove(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -110,108 +128,158 @@ public class GypsyEntity extends PathAwareEntity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void dropLoot(DamageSource source, boolean causedByPlayer) {
|
protected void dropLoot(DamageSource source, boolean causedByPlayer) {
|
||||||
for (ItemStack stack : stolenItems) {
|
for (ItemStack stack : stolenItems) this.dropStack(stack);
|
||||||
this.dropStack(stack);
|
|
||||||
}
|
|
||||||
stolenItems.clear();
|
stolenItems.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ================= GOALS =================
|
// ================= GOALS =================
|
||||||
|
|
||||||
/**
|
private static class PanicRandomlyGoal extends Goal {
|
||||||
* Runs away when visible OR after stealing,
|
private final GypsyEntity mob;
|
||||||
* stops once far enough away.
|
PanicRandomlyGoal(GypsyEntity mob) { this.mob = mob; this.setControls(EnumSet.of(Control.MOVE)); }
|
||||||
*/
|
@Override public boolean canStart() { return mob.panicTicks > 0; }
|
||||||
private static class FleeWhenSeenGoal extends Goal {
|
|
||||||
|
|
||||||
private final GypsyEntity GypsyEntity;
|
|
||||||
private PlayerEntity target;
|
|
||||||
|
|
||||||
public FleeWhenSeenGoal(GypsyEntity GypsyEntity) {
|
|
||||||
this.GypsyEntity = GypsyEntity;
|
|
||||||
this.setControls(EnumSet.of(Control.MOVE));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canStart() {
|
|
||||||
this.target = GypsyEntity.getWorld().getClosestPlayer(GypsyEntity, 20);
|
|
||||||
|
|
||||||
if (target == null || target.isCreative()) return false;
|
|
||||||
|
|
||||||
if (GypsyEntity.fleeing) return true;
|
|
||||||
|
|
||||||
return GypsyEntity.isOnPlayerScreen(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean shouldContinue() {
|
|
||||||
return GypsyEntity.fleeing
|
|
||||||
&& target != null
|
|
||||||
&& GypsyEntity.squaredDistanceTo(target) < FLEE_DISTANCE * FLEE_DISTANCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start() {
|
|
||||||
GypsyEntity.fleeing = true;
|
|
||||||
moveAway();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick() {
|
public void tick() {
|
||||||
moveAway();
|
mob.panicTicks--;
|
||||||
|
if (mob.getNavigation().isIdle()) {
|
||||||
if (GypsyEntity.squaredDistanceTo(target) >= FLEE_DISTANCE * FLEE_DISTANCE) {
|
Vec3d dest = mob.getPos().add(
|
||||||
GypsyEntity.fleeing = false;
|
mob.random.nextGaussian() * 8,
|
||||||
}
|
0,
|
||||||
}
|
mob.random.nextGaussian() * 8
|
||||||
|
|
||||||
private void moveAway() {
|
|
||||||
Vec3d away = GypsyEntity.getPos()
|
|
||||||
.subtract(target.getPos())
|
|
||||||
.normalize()
|
|
||||||
.multiply(10);
|
|
||||||
|
|
||||||
Vec3d dest = GypsyEntity.getPos().add(away);
|
|
||||||
GypsyEntity.getNavigation().startMovingTo(dest.x, dest.y, dest.z, 1.35);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sneaks behind players ONLY when unseen and not fleeing
|
|
||||||
*/
|
|
||||||
private static class SneakBehindPlayerGoal extends Goal {
|
|
||||||
|
|
||||||
private final GypsyEntity GypsyEntity;
|
|
||||||
private PlayerEntity target;
|
|
||||||
|
|
||||||
public SneakBehindPlayerGoal(GypsyEntity GypsyEntity) {
|
|
||||||
this.GypsyEntity = GypsyEntity;
|
|
||||||
this.setControls(EnumSet.of(Control.MOVE));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canStart() {
|
|
||||||
this.target = GypsyEntity.getWorld().getClosestPlayer(GypsyEntity, 12);
|
|
||||||
|
|
||||||
return target != null
|
|
||||||
&& !target.isCreative()
|
|
||||||
&& !GypsyEntity.isOnPlayerScreen(target)
|
|
||||||
&& GypsyEntity.stealCooldown == 0
|
|
||||||
&& !GypsyEntity.fleeing;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void tick() {
|
|
||||||
Vec3d behind = target.getPos()
|
|
||||||
.subtract(target.getRotationVec(1.0F).normalize().multiply(2));
|
|
||||||
|
|
||||||
GypsyEntity.getNavigation().startMovingTo(
|
|
||||||
behind.x, behind.y, behind.z, 1.0
|
|
||||||
);
|
);
|
||||||
|
mob.getNavigation().startMovingTo(dest.x, dest.y, dest.z, 1.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (GypsyEntity.distanceTo(target) < 1.5) {
|
// 🔴 Flee from only specific victim, hide behind others
|
||||||
GypsyEntity.trySteal(target);
|
private static class FleeSpecificPlayerGoal extends Goal {
|
||||||
|
private final GypsyEntity mob;
|
||||||
|
private PlayerEntity threat;
|
||||||
|
|
||||||
|
FleeSpecificPlayerGoal(GypsyEntity mob) {
|
||||||
|
this.mob = mob;
|
||||||
|
this.setControls(EnumSet.of(Control.MOVE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canStart() {
|
||||||
|
if (mob.fleeingFrom == null) return false;
|
||||||
|
PlayerEntity p = mob.getWorld().getPlayerByUuid(mob.fleeingFrom);
|
||||||
|
if (p == null || !mob.canSee(p) || !mob.isOnPlayerScreen(p)) return false;
|
||||||
|
threat = p;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tick() {
|
||||||
|
TargetPredicate predicate = TargetPredicate.createNonAttackable()
|
||||||
|
.setBaseMaxDistance(16)
|
||||||
|
.setPredicate(player -> !player.getUuid().equals(mob.fleeingFrom));
|
||||||
|
|
||||||
|
PlayerEntity shield = mob.getWorld().getClosestPlayer(predicate, mob);
|
||||||
|
|
||||||
|
Vec3d dest;
|
||||||
|
if (shield != null && !shield.isCreative()) {
|
||||||
|
dest = shield.getPos(); // hide behind other players
|
||||||
|
} else {
|
||||||
|
dest = mob.getPos().subtract(threat.getPos()).normalize().multiply(10).add(mob.getPos());
|
||||||
|
}
|
||||||
|
|
||||||
|
mob.getNavigation().startMovingTo(dest.x, dest.y, dest.z, 1.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ⚔ Attack only the player it stole from
|
||||||
|
private static class DefensiveAttackGoal extends Goal {
|
||||||
|
private final GypsyEntity mob;
|
||||||
|
private PlayerEntity target;
|
||||||
|
private int cooldown = 0;
|
||||||
|
|
||||||
|
DefensiveAttackGoal(GypsyEntity mob) { this.mob = mob; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canStart() {
|
||||||
|
if (mob.fleeingFrom == null) return false;
|
||||||
|
PlayerEntity p = mob.getWorld().getPlayerByUuid(mob.fleeingFrom);
|
||||||
|
if (p == null || mob.distanceTo(p) > 1.3) return false;
|
||||||
|
target = p;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tick() {
|
||||||
|
if (cooldown-- > 0) return;
|
||||||
|
cooldown = 20;
|
||||||
|
mob.getLookControl().lookAt(target);
|
||||||
|
mob.tryAttack(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🟡 Sneak steal
|
||||||
|
private static class SneakBehindPlayerGoal extends Goal {
|
||||||
|
private final GypsyEntity mob;
|
||||||
|
private PlayerEntity target;
|
||||||
|
private int cooldown = 0;
|
||||||
|
|
||||||
|
SneakBehindPlayerGoal(GypsyEntity mob) {
|
||||||
|
this.mob = mob;
|
||||||
|
this.setControls(EnumSet.of(Control.MOVE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canStart() {
|
||||||
|
target = mob.getWorld().getClosestPlayer(mob, 10);
|
||||||
|
return target != null
|
||||||
|
&& !mob.stolenFromPlayers.contains(target.getUuid())
|
||||||
|
&& mob.stealCooldown == 0
|
||||||
|
&& !mob.isOnPlayerScreen(target)
|
||||||
|
&& !target.isCreative();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tick() {
|
||||||
|
if (cooldown-- > 0) return;
|
||||||
|
cooldown = 5;
|
||||||
|
|
||||||
|
Vec3d behind = target.getPos().subtract(target.getRotationVec(1.0F).normalize());
|
||||||
|
mob.getNavigation().startMovingTo(behind.x, behind.y, behind.z, 1.15);
|
||||||
|
|
||||||
|
if (mob.distanceTo(target) < 1.3) {
|
||||||
|
mob.trySteal(target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🟢 Biased wander toward players when not guilty
|
||||||
|
private static class BiasedWanderGoal extends WanderAroundFarGoal {
|
||||||
|
private final GypsyEntity mob;
|
||||||
|
|
||||||
|
BiasedWanderGoal(GypsyEntity mob, double speed) {
|
||||||
|
super(mob, speed);
|
||||||
|
this.mob = mob;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Vec3d getWanderTarget() {
|
||||||
|
Vec3d base = super.getWanderTarget();
|
||||||
|
PlayerEntity player = mob.getWorld().getClosestPlayer(mob, 10);
|
||||||
|
|
||||||
|
if (player == null || base == null || mob.fleeingFrom != null) return base;
|
||||||
|
|
||||||
|
Vec3d best = base;
|
||||||
|
double bestDist = base.squaredDistanceTo(player.getPos());
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
Vec3d c = super.getWanderTarget();
|
||||||
|
if (c == null) continue;
|
||||||
|
double d = c.squaredDistanceTo(player.getPos());
|
||||||
|
if (d < bestDist) { // bias toward player
|
||||||
|
best = c;
|
||||||
|
bestDist = d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user