diff --git a/src/net/citizensnpcs/NPCUpdater.java b/src/net/citizensnpcs/NPCUpdater.java index ec12a9642..9ccb0da24 100644 --- a/src/net/citizensnpcs/NPCUpdater.java +++ b/src/net/citizensnpcs/NPCUpdater.java @@ -1,8 +1,8 @@ package net.citizensnpcs; import net.citizensnpcs.api.npc.NPC; +import net.citizensnpcs.npc.CitizensNPC; import net.citizensnpcs.npc.CitizensNPCManager; -import net.citizensnpcs.npc.ai.CitizensNavigator; public class NPCUpdater implements Runnable { private final CitizensNPCManager npcManager; @@ -14,6 +14,6 @@ public class NPCUpdater implements Runnable { @Override public void run() { for (NPC npc : npcManager) - ((CitizensNavigator) npc.getNavigator()).update(); + ((CitizensNPC) npc).update(); } } \ No newline at end of file diff --git a/src/net/citizensnpcs/npc/CitizensNPC.java b/src/net/citizensnpcs/npc/CitizensNPC.java index f26b020a1..96139364b 100644 --- a/src/net/citizensnpcs/npc/CitizensNPC.java +++ b/src/net/citizensnpcs/npc/CitizensNPC.java @@ -3,26 +3,27 @@ package net.citizensnpcs.npc; import net.citizensnpcs.api.event.NPCDespawnEvent; import net.citizensnpcs.api.event.NPCSpawnEvent; import net.citizensnpcs.api.npc.AbstractNPC; -import net.citizensnpcs.api.npc.ai.Navigator; import net.citizensnpcs.api.npc.trait.trait.SpawnLocation; import net.citizensnpcs.api.npc.trait.trait.Spawned; -import net.citizensnpcs.npc.ai.CitizensNavigator; +import net.citizensnpcs.npc.ai.CitizensAI; import net.citizensnpcs.util.Messaging; +import net.minecraft.server.EntityLiving; import org.bukkit.Bukkit; import org.bukkit.Location; -import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; public abstract class CitizensNPC extends AbstractNPC { protected final CitizensNPCManager manager; - protected net.minecraft.server.Entity mcEntity; + protected final CitizensAI ai = new CitizensAI(this); + protected EntityLiving mcEntity; protected CitizensNPC(CitizensNPCManager manager, int id, String name) { super(id, name); this.manager = manager; } - protected abstract net.minecraft.server.Entity createHandle(Location loc); + protected abstract EntityLiving createHandle(Location loc); @Override public boolean despawn() { @@ -40,17 +41,17 @@ public abstract class CitizensNPC extends AbstractNPC { } @Override - public Entity getBukkitEntity() { - return getHandle().getBukkitEntity(); + public LivingEntity getBukkitEntity() { + return (LivingEntity) getHandle().getBukkitEntity(); } - public net.minecraft.server.Entity getHandle() { + public EntityLiving getHandle() { return mcEntity; } @Override - public Navigator getNavigator() { - return new CitizensNavigator(this); + public CitizensAI getAI() { + return new CitizensAI(this); } @Override @@ -86,4 +87,8 @@ public abstract class CitizensNPC extends AbstractNPC { addTrait(new Spawned(true)); return true; } + + public void update() { + ai.update(); + } } \ No newline at end of file diff --git a/src/net/citizensnpcs/npc/ai/CitizensNavigator.java b/src/net/citizensnpcs/npc/ai/CitizensAI.java similarity index 62% rename from src/net/citizensnpcs/npc/ai/CitizensNavigator.java rename to src/net/citizensnpcs/npc/ai/CitizensAI.java index adb647dc9..e39a13e30 100644 --- a/src/net/citizensnpcs/npc/ai/CitizensNavigator.java +++ b/src/net/citizensnpcs/npc/ai/CitizensAI.java @@ -1,43 +1,28 @@ package net.citizensnpcs.npc.ai; -import net.citizensnpcs.api.npc.ai.Navigator; -import net.citizensnpcs.api.npc.ai.NavigatorCallback; +import net.citizensnpcs.api.npc.ai.AI; +import net.citizensnpcs.api.npc.ai.Goal; +import net.citizensnpcs.api.npc.ai.NavigationCallback; import net.citizensnpcs.npc.CitizensNPC; -import net.citizensnpcs.trait.LookClose; +import net.citizensnpcs.util.Messaging; import org.bukkit.Location; import org.bukkit.entity.Entity; import org.bukkit.entity.LivingEntity; -public class CitizensNavigator implements Navigator { - private final CitizensNPC npc; +public class CitizensAI implements AI { private PathStrategy executing; + private Runnable ai; + private final CitizensNPC npc; - public CitizensNavigator(CitizensNPC npc) { + public CitizensAI(CitizensNPC npc) { this.npc = npc; } @Override - public void registerCallback(NavigatorCallback callback) { - } + public void addGoal(int priority, Goal goal) { + // TODO Auto-generated method stub - public void update() { - if (npc.getHandle() != null && npc.getHandle().world.findNearbyPlayer(npc.getHandle(), 5) != null) - if (npc.getTrait(LookClose.class).shouldLookClose() - && npc.getHandle().world.findNearbyPlayer(npc.getHandle(), 5) != null) - faceEntity(npc.getHandle().world.findNearbyPlayer(npc.getHandle(), 5).getBukkitEntity()); - if (executing != null) - executing.update(); - } - - @Override - public void setDestination(Location destination) { - executing = new MoveStrategy(npc, destination); - } - - @Override - public void setTarget(LivingEntity target, boolean aggressive) { - executing = new TargetStrategy(npc, target, aggressive); } private void faceEntity(Entity target) { @@ -61,4 +46,38 @@ public class CitizensNavigator implements Navigator { npc.getHandle().yaw = (float) yaw - 90; npc.getHandle().pitch = (float) pitch; } + + @Override + public void registerNavigationCallback(NavigationCallback callback) { + } + + @Override + public void setAI(Runnable ai) { + this.ai = ai; + } + + @Override + public void setDestination(Location destination) { + executing = new MoveStrategy(npc, destination); + } + + @Override + public void setTarget(LivingEntity target, boolean aggressive) { + executing = new TargetStrategy(npc, target, aggressive); + } + + public void update() { + if (executing != null && executing.update()) { + executing = null; + } + + if (ai != null) { + try { + ai.run(); + } catch (Throwable ex) { + Messaging.log("Unexpected error while running ai " + ai); + ex.printStackTrace(); + } + } + } } \ No newline at end of file diff --git a/src/net/citizensnpcs/npc/ai/TargetStrategy.java b/src/net/citizensnpcs/npc/ai/TargetStrategy.java index d6fec770d..1223a1cba 100644 --- a/src/net/citizensnpcs/npc/ai/TargetStrategy.java +++ b/src/net/citizensnpcs/npc/ai/TargetStrategy.java @@ -24,13 +24,27 @@ public class TargetStrategy implements PathStrategy { if (target == null || target.dead) return true; current = new MoveStrategy(handle, handle.world.findPath(handle, target, 16F)); - if (aggro) - if (handle instanceof EntityMonster) + if (aggro && canAttack()) { + if (handle instanceof EntityMonster) { ((EntityMonster) handle).d(target); - else if (handle instanceof EntityHuman) + } else if (handle instanceof EntityHuman) { ((EntityHuman) handle).attack(target); + } + } current.update(); return false; } + + private boolean canAttack() { + return handle.attackTicks == 0 + && (handle.boundingBox.e > target.boundingBox.b && handle.boundingBox.b < target.boundingBox.e) + && distanceSquared() <= ATTACK_DISTANCE && handle.g(target); + } + + private static final double ATTACK_DISTANCE = 1.75 * 1.75; + + private double distanceSquared() { + return handle.getBukkitEntity().getLocation().distanceSquared(target.getBukkitEntity().getLocation()); + } } \ No newline at end of file diff --git a/src/net/citizensnpcs/npc/entity/CitizensHumanNPC.java b/src/net/citizensnpcs/npc/entity/CitizensHumanNPC.java index 07ef9b1a5..cc068ae26 100644 --- a/src/net/citizensnpcs/npc/entity/CitizensHumanNPC.java +++ b/src/net/citizensnpcs/npc/entity/CitizensHumanNPC.java @@ -5,12 +5,9 @@ import net.citizensnpcs.npc.CitizensNPCManager; import net.citizensnpcs.resource.lib.EntityHumanNPC; import net.minecraft.server.EntityLiving; import net.minecraft.server.ItemInWorldManager; -import net.minecraft.server.MinecraftServer; import net.minecraft.server.WorldServer; import org.bukkit.Location; -import org.bukkit.Server; -import org.bukkit.craftbukkit.CraftServer; import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.entity.Player; @@ -24,8 +21,13 @@ public class CitizensHumanNPC extends CitizensNPC { return (Player) getHandle().getBukkitEntity(); } - protected static MinecraftServer getMinecraftServer(Server server) { - return ((CraftServer) server).getServer(); + @Override + public void update() { + super.update(); + if (mcEntity.noDamageTicks > 0) + mcEntity.noDamageTicks--; + if (mcEntity.attackTicks > 0) + mcEntity.attackTicks--; } @Override diff --git a/src/net/citizensnpcs/npc/entity/CitizensMobNPC.java b/src/net/citizensnpcs/npc/entity/CitizensMobNPC.java new file mode 100644 index 000000000..96ca55d30 --- /dev/null +++ b/src/net/citizensnpcs/npc/entity/CitizensMobNPC.java @@ -0,0 +1,83 @@ +package net.citizensnpcs.npc.entity; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.util.Map; + +import net.citizensnpcs.npc.CitizensNPC; +import net.citizensnpcs.npc.CitizensNPCManager; +import net.minecraft.server.Entity; +import net.minecraft.server.EntityLiving; +import net.minecraft.server.EntityTypes; + +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.entity.LivingEntity; + +public abstract class CitizensMobNPC extends CitizensNPC { + private final Constructor constructor; + + protected CitizensMobNPC(CitizensNPCManager manager, int id, String name, Class clazz) { + super(manager, id, name); + try { + this.constructor = clazz.getConstructor(World.class); + } catch (Exception ex) { + throw new IllegalStateException("unable to find an entity constructor"); + } + if (!classToInt.containsKey(clazz)) + registerEntityClass(clazz); + } + + private EntityLiving createEntityFromClass(net.minecraft.server.World world) { + try { + return constructor.newInstance(world); + } catch (Exception ex) { + ex.printStackTrace(); + return null; + } + } + + @Override + protected EntityLiving createHandle(Location loc) { + EntityLiving entity = createEntityFromClass(((CraftWorld) loc.getWorld()).getHandle()); + mcEntity.setPositionRotation(loc.getX(), loc.getY(), loc.getZ(), loc.getYaw(), loc.getPitch()); + return entity; + } + + @Override + public LivingEntity getBukkitEntity() { + return (LivingEntity) getHandle().getBukkitEntity(); + } + + private static Map, Integer> classToInt; + private static Map> intToClass; + + private static void registerEntityClass(Class clazz) { + Class search = clazz; + while ((search = search.getSuperclass()) != null && Entity.class.isAssignableFrom(search)) { + if (!classToInt.containsKey(search)) + continue; + int code = classToInt.get(search); + intToClass.put(code, clazz); + classToInt.put(clazz, code); + return; + } + throw new IllegalArgumentException("unable to find valid entity superclass"); + } + + static { + try { + Field field = EntityTypes.class.getDeclaredField("d"); + field.setAccessible(true); + intToClass = (Map>) field.get(null); + field = EntityTypes.class.getDeclaredField("e"); + field.setAccessible(true); + classToInt = (Map, Integer>) field.get(null); + } catch (Exception ex) { + ex.printStackTrace(); + throw new IllegalStateException( + "Unable to fetch entity class mapping - is Citizens updated for this version of CraftBukkit?"); + } + } +} diff --git a/src/net/citizensnpcs/resource/lib/entity/EntityHumanNPC.java b/src/net/citizensnpcs/resource/lib/entity/EntityHumanNPC.java new file mode 100644 index 000000000..73056702c --- /dev/null +++ b/src/net/citizensnpcs/resource/lib/entity/EntityHumanNPC.java @@ -0,0 +1,74 @@ +package net.citizensnpcs.resource.lib.entity; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.Map; + +import net.citizensnpcs.resource.lib.NPCNetHandler; +import net.citizensnpcs.resource.lib.NPCNetworkManager; +import net.citizensnpcs.resource.lib.NPCSocket; +import net.citizensnpcs.util.Messaging; +import net.minecraft.server.EntityPlayer; +import net.minecraft.server.ItemInWorldManager; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.NetHandler; +import net.minecraft.server.NetworkManager; +import net.minecraft.server.World; + +import org.bukkit.craftbukkit.entity.CraftEntity; +import org.bukkit.craftbukkit.entity.CraftPlayer; + +@SuppressWarnings("unchecked") +public class EntityHumanNPC extends EntityPlayer { + + public EntityHumanNPC(MinecraftServer minecraftServer, World world, String string, + ItemInWorldManager itemInWorldManager) { + super(minecraftServer, world, string, itemInWorldManager); + itemInWorldManager.setGameMode(0); + + NPCSocket socket = new NPCSocket(); + NetworkManager netMgr = new NPCNetworkManager(socket, "npc mgr", new NetHandler() { + @Override + public boolean c() { + return false; + } + }); + netServerHandler = new NPCNetHandler(minecraftServer, netMgr, this); + netMgr.a(netServerHandler); + + try { + socket.close(); + } catch (IOException ex) { + ex.printStackTrace(); + } + } + + @Override + public CraftPlayer getBukkitEntity() { + if (bukkitEntity == null) { + super.getBukkitEntity(); + removeFromPlayerMap(name); + // Bukkit uses a map of player names to CraftPlayer instances to + // solve a reconnect issue, so NPC names will conflict with ordinary + // player names. Workaround. + } + return super.getBukkitEntity(); + } + + public void removeFromPlayerMap(String name) { + if (players != null) + players.remove(name); + } + + private static Map players; + + static { + try { + Field f = CraftEntity.class.getDeclaredField("players"); + f.setAccessible(true); + players = (Map) f.get(null); + } catch (Exception ex) { + Messaging.log("Unable to fetch player map from CraftEntity: " + ex.getMessage()); + } + } +} \ No newline at end of file