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
|
||||
loader_version=0.18.3
|
||||
# Mod Properties
|
||||
mod_version=1.0.3
|
||||
mod_version=1.0.4
|
||||
maven_group=dev.tggamesyt
|
||||
archives_base_name=szar
|
||||
# Dependencies
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user