update ai of gypsy

This commit is contained in:
2026-01-20 11:26:35 +01:00
parent 83870fc397
commit 5be20ee06a
2 changed files with 179 additions and 111 deletions

View File

@@ -1,6 +1,7 @@
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.LookAroundGoal;
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.world.World;
import java.util.EnumSet;
import java.util.List;
import java.util.*;
public class GypsyEntity extends PathAwareEntity {
private final DefaultedList<ItemStack> stolenItems = DefaultedList.of();
private int stealCooldown = 0;
private boolean fleeing = false;
private final Set<UUID> stolenFromPlayers = new HashSet<>();
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) {
super(type, world);
this.setCanPickUpLoot(true);
}
// ================= ATTRIBUTES =================
public static DefaultAttributeContainer.Builder createAttributes() {
return MobEntity.createMobAttributes()
.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 =================
@Override
protected void initGoals() {
this.goalSelector.add(0, new FleeWhenSeenGoal(this));
this.goalSelector.add(1, new SneakBehindPlayerGoal(this));
this.goalSelector.add(2, new WanderAroundFarGoal(this, 0.8));
this.goalSelector.add(3, new LookAroundGoal(this));
this.goalSelector.add(0, new PanicRandomlyGoal(this));
this.goalSelector.add(1, new FleeSpecificPlayerGoal(this));
this.goalSelector.add(2, new DefensiveAttackGoal(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 =================
@Override
public void tick() {
super.tick();
if (stealCooldown > 0) {
stealCooldown--;
if (stealCooldown > 0) 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 =================
/**
* True if the entity is anywhere on the player's screen (FOV-based)
*/
// ================= VISIBILITY =================
private boolean isOnPlayerScreen(PlayerEntity player) {
if (player.isCreative()) return false;
Vec3d look = player.getRotationVec(1.0F).normalize();
Vec3d toEntity = this.getPos().subtract(player.getEyePos()).normalize();
// Rough FOV check (~120° total)
return look.dotProduct(toEntity) > 0.3;
return look.dotProduct(toEntity) > 0.55;
}
// ================= STEALING =================
private void trySteal(PlayerEntity player) {
if (stealCooldown > 0 || player.isCreative() || this.getWorld().isClient) return;
@@ -89,20 +90,37 @@ public class GypsyEntity extends PathAwareEntity {
ItemStack stolen = chosen.split(1);
stolenItems.add(stolen);
stealCooldown = 20 * 20; // 20 seconds
fleeing = true;
equipStack(EquipmentSlot.MAINHAND, stolen);
stolenFromPlayers.add(player.getUuid());
fleeingFrom = player.getUuid();
stealCooldown = 20 * 20;
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
public boolean damage(DamageSource source, float amount) {
boolean result = super.damage(source, amount);
if (!this.getWorld().isClient && !stolenItems.isEmpty()) {
this.dropStack(stolenItems.remove(0));
if (!this.getWorld().isClient) {
panicTicks = 60 + random.nextInt(40);
if (!stolenItems.isEmpty()) this.dropStack(stolenItems.remove(0));
}
return result;
@@ -110,108 +128,158 @@ public class GypsyEntity extends PathAwareEntity {
@Override
protected void dropLoot(DamageSource source, boolean causedByPlayer) {
for (ItemStack stack : stolenItems) {
this.dropStack(stack);
}
for (ItemStack stack : stolenItems) this.dropStack(stack);
stolenItems.clear();
}
// ================= GOALS =================
/**
* Runs away when visible OR after stealing,
* stops once far enough away.
*/
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();
}
private static class PanicRandomlyGoal extends Goal {
private final GypsyEntity mob;
PanicRandomlyGoal(GypsyEntity mob) { this.mob = mob; this.setControls(EnumSet.of(Control.MOVE)); }
@Override public boolean canStart() { return mob.panicTicks > 0; }
@Override
public void tick() {
moveAway();
if (GypsyEntity.squaredDistanceTo(target) >= FLEE_DISTANCE * FLEE_DISTANCE) {
GypsyEntity.fleeing = false;
mob.panicTicks--;
if (mob.getNavigation().isIdle()) {
Vec3d dest = mob.getPos().add(
mob.random.nextGaussian() * 8,
0,
mob.random.nextGaussian() * 8
);
mob.getNavigation().startMovingTo(dest.x, dest.y, dest.z, 1.5);
}
}
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 {
// 🔴 Flee from only specific victim, hide behind others
private static class FleeSpecificPlayerGoal extends Goal {
private final GypsyEntity mob;
private PlayerEntity threat;
private final GypsyEntity GypsyEntity;
private PlayerEntity target;
public SneakBehindPlayerGoal(GypsyEntity GypsyEntity) {
this.GypsyEntity = GypsyEntity;
FleeSpecificPlayerGoal(GypsyEntity mob) {
this.mob = mob;
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;
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() {
Vec3d behind = target.getPos()
.subtract(target.getRotationVec(1.0F).normalize().multiply(2));
TargetPredicate predicate = TargetPredicate.createNonAttackable()
.setBaseMaxDistance(16)
.setPredicate(player -> !player.getUuid().equals(mob.fleeingFrom));
GypsyEntity.getNavigation().startMovingTo(
behind.x, behind.y, behind.z, 1.0
);
PlayerEntity shield = mob.getWorld().getClosestPlayer(predicate, mob);
if (GypsyEntity.distanceTo(target) < 1.5) {
GypsyEntity.trySteal(target);
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;
}
}
}