410 lines
15 KiB
Java
410 lines
15 KiB
Java
package net.citizensnpcs.trait.waypoint;
|
|
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
|
|
import net.citizensnpcs.api.CitizensAPI;
|
|
import net.citizensnpcs.api.ai.Goal;
|
|
import net.citizensnpcs.api.ai.GoalSelector;
|
|
import net.citizensnpcs.api.ai.Navigator;
|
|
import net.citizensnpcs.api.ai.event.NavigationCompleteEvent;
|
|
import net.citizensnpcs.api.event.NPCDespawnEvent;
|
|
import net.citizensnpcs.api.event.NPCRemoveEvent;
|
|
import net.citizensnpcs.api.npc.NPC;
|
|
import net.citizensnpcs.api.persistence.PersistenceLoader;
|
|
import net.citizensnpcs.api.util.DataKey;
|
|
import net.citizensnpcs.editor.Editor;
|
|
import net.citizensnpcs.trait.waypoint.triggers.TriggerEditPrompt;
|
|
import net.citizensnpcs.util.Messages;
|
|
import net.citizensnpcs.util.Messaging;
|
|
import net.citizensnpcs.util.NMS;
|
|
|
|
import org.bukkit.Bukkit;
|
|
import org.bukkit.ChatColor;
|
|
import org.bukkit.Location;
|
|
import org.bukkit.World;
|
|
import org.bukkit.conversations.Conversation;
|
|
import org.bukkit.entity.Entity;
|
|
import org.bukkit.entity.EntityType;
|
|
import org.bukkit.entity.Player;
|
|
import org.bukkit.event.EventHandler;
|
|
import org.bukkit.event.block.Action;
|
|
import org.bukkit.event.player.AsyncPlayerChatEvent;
|
|
import org.bukkit.event.player.PlayerInteractEntityEvent;
|
|
import org.bukkit.event.player.PlayerInteractEvent;
|
|
import org.bukkit.event.player.PlayerItemHeldEvent;
|
|
import org.bukkit.metadata.FixedMetadataValue;
|
|
|
|
import com.google.common.collect.Lists;
|
|
import com.google.common.collect.Maps;
|
|
|
|
public class LinearWaypointProvider implements WaypointProvider {
|
|
private LinearWaypointGoal currentGoal;
|
|
private NPC npc;
|
|
private final List<Waypoint> waypoints = Lists.newArrayList();
|
|
|
|
@Override
|
|
public WaypointEditor createEditor(Player player) {
|
|
return new LinearWaypointEditor(player);
|
|
}
|
|
|
|
@Override
|
|
public boolean isPaused() {
|
|
return currentGoal.isPaused();
|
|
}
|
|
|
|
@Override
|
|
public void load(DataKey key) {
|
|
for (DataKey root : key.getRelative("points").getIntegerSubKeys()) {
|
|
Waypoint waypoint = PersistenceLoader.load(Waypoint.class, root);
|
|
if (waypoint == null)
|
|
continue;
|
|
waypoints.add(waypoint);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onSpawn(NPC npc) {
|
|
this.npc = npc;
|
|
if (currentGoal == null) {
|
|
currentGoal = new LinearWaypointGoal();
|
|
CitizensAPI.registerEvents(currentGoal);
|
|
npc.getDefaultGoalController().addGoal(currentGoal, 1);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void save(DataKey key) {
|
|
key.removeKey("points");
|
|
key = key.getRelative("points");
|
|
for (int i = 0; i < waypoints.size(); ++i)
|
|
PersistenceLoader.save(waypoints.get(i), key.getRelative(i));
|
|
}
|
|
|
|
@Override
|
|
public void setPaused(boolean paused) {
|
|
currentGoal.setPaused(paused);
|
|
}
|
|
|
|
private final class LinearWaypointEditor extends WaypointEditor {
|
|
Conversation conversation;
|
|
boolean editing = true;
|
|
int editingSlot = waypoints.size() - 1;
|
|
private final Player player;
|
|
private boolean showPath;
|
|
Map<Waypoint, Entity> waypointMarkers = Maps.newHashMap();
|
|
|
|
private LinearWaypointEditor(Player player) {
|
|
this.player = player;
|
|
}
|
|
|
|
@Override
|
|
public void begin() {
|
|
Messaging.sendTr(player, Messages.LINEAR_WAYPOINT_EDITOR_BEGIN);
|
|
}
|
|
|
|
private void clearWaypoints() {
|
|
editingSlot = 0;
|
|
waypoints.clear();
|
|
onWaypointsModified();
|
|
destroyWaypointMarkers();
|
|
Messaging.sendTr(player, Messages.LINEAR_WAYPOINT_EDITOR_WAYPOINTS_CLEARED);
|
|
}
|
|
|
|
private void createWaypointMarker(int index, Waypoint waypoint) {
|
|
Entity entity = spawnMarker(player.getWorld(), waypoint.getLocation().add(0, 1, 0));
|
|
if (entity == null)
|
|
return;
|
|
entity.setMetadata("waypointindex", new FixedMetadataValue(CitizensAPI.getPlugin(), index));
|
|
waypointMarkers.put(waypoint, entity);
|
|
}
|
|
|
|
private void createWaypointMarkers() {
|
|
for (int i = 0; i < waypoints.size(); i++)
|
|
createWaypointMarker(i, waypoints.get(i));
|
|
}
|
|
|
|
private void destroyWaypointMarkers() {
|
|
for (Entity entity : waypointMarkers.values())
|
|
entity.remove();
|
|
waypointMarkers.clear();
|
|
}
|
|
|
|
@Override
|
|
public void end() {
|
|
if (!editing)
|
|
return;
|
|
if (conversation != null)
|
|
conversation.abandon();
|
|
Messaging.sendTr(player, Messages.LINEAR_WAYPOINT_EDITOR_END);
|
|
editing = false;
|
|
if (!showPath)
|
|
return;
|
|
destroyWaypointMarkers();
|
|
}
|
|
|
|
private String formatLoc(Location location) {
|
|
return String.format("[[%d]], [[%d]], [[%d]]", location.getBlockX(), location.getBlockY(),
|
|
location.getBlockZ());
|
|
}
|
|
|
|
@Override
|
|
public Waypoint getCurrentWaypoint() {
|
|
if (waypoints.size() == 0 || !editing)
|
|
return null;
|
|
normaliseEditingSlot();
|
|
return waypoints.get(editingSlot);
|
|
}
|
|
|
|
private Location getPreviousWaypoint(int fromSlot) {
|
|
if (waypoints.size() <= 1)
|
|
return null;
|
|
fromSlot--;
|
|
if (fromSlot < 0)
|
|
fromSlot = waypoints.size() - 1;
|
|
return waypoints.get(fromSlot).getLocation();
|
|
}
|
|
|
|
private void normaliseEditingSlot() {
|
|
editingSlot = Math.max(0, Math.min(waypoints.size() - 1, editingSlot));
|
|
}
|
|
|
|
@EventHandler
|
|
public void onNPCDespawn(NPCDespawnEvent event) {
|
|
if (event.getNPC().equals(npc))
|
|
Editor.leave(player);
|
|
}
|
|
|
|
@EventHandler
|
|
public void onNPCRemove(NPCRemoveEvent event) {
|
|
if (event.getNPC().equals(npc))
|
|
Editor.leave(player);
|
|
}
|
|
|
|
@EventHandler(ignoreCancelled = true)
|
|
public void onPlayerChat(AsyncPlayerChatEvent event) {
|
|
if (!event.getPlayer().equals(player))
|
|
return;
|
|
String message = event.getMessage();
|
|
if (message.equalsIgnoreCase("triggers")) {
|
|
Bukkit.getScheduler().scheduleSyncDelayedTask(CitizensAPI.getPlugin(), new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
conversation = TriggerEditPrompt.start(player, LinearWaypointEditor.this);
|
|
}
|
|
});
|
|
return;
|
|
} else if (message.equalsIgnoreCase("clear")) {
|
|
Bukkit.getScheduler().scheduleSyncDelayedTask(CitizensAPI.getPlugin(), new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
clearWaypoints();
|
|
}
|
|
});
|
|
return;
|
|
} else if (message.equalsIgnoreCase("toggle path")) {
|
|
event.setCancelled(true);
|
|
Bukkit.getScheduler().scheduleSyncDelayedTask(CitizensAPI.getPlugin(), new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
// we need to spawn entities on the main thread.
|
|
togglePath();
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
|
|
@EventHandler(ignoreCancelled = true)
|
|
public void onPlayerInteract(PlayerInteractEvent event) {
|
|
if (!event.getPlayer().equals(player) || event.getAction() == Action.PHYSICAL)
|
|
return;
|
|
if (event.getPlayer().getWorld() != npc.getBukkitEntity().getWorld())
|
|
return;
|
|
if (event.getAction() == Action.LEFT_CLICK_BLOCK || event.getAction() == Action.LEFT_CLICK_AIR) {
|
|
if (event.getClickedBlock() == null)
|
|
return;
|
|
event.setCancelled(true);
|
|
Location at = event.getClickedBlock().getLocation();
|
|
Location prev = getPreviousWaypoint(editingSlot);
|
|
|
|
if (prev != null) {
|
|
double distance = at.distanceSquared(prev);
|
|
double maxDistance = Math.pow(npc.getNavigator().getDefaultParameters().range(), 2);
|
|
if (distance > maxDistance) {
|
|
Messaging.sendErrorTr(player, Messages.LINEAR_WAYPOINT_EDITOR_RANGE_EXCEEDED,
|
|
Math.sqrt(distance), Math.sqrt(maxDistance), ChatColor.RED);
|
|
return;
|
|
}
|
|
}
|
|
|
|
Waypoint element = new Waypoint(at);
|
|
normaliseEditingSlot();
|
|
waypoints.add(editingSlot, element);
|
|
if (showPath)
|
|
createWaypointMarker(editingSlot, element);
|
|
editingSlot = Math.min(editingSlot + 1, waypoints.size());
|
|
Messaging.sendTr(player, Messages.LINEAR_WAYPOINT_EDITOR_ADDED_WAYPOINT, formatLoc(at),
|
|
editingSlot + 1, waypoints.size());
|
|
} else if (waypoints.size() > 0) {
|
|
event.setCancelled(true);
|
|
normaliseEditingSlot();
|
|
Waypoint waypoint = waypoints.remove(editingSlot);
|
|
if (showPath)
|
|
removeWaypointMarker(waypoint);
|
|
editingSlot = Math.max(0, editingSlot - 1);
|
|
Messaging.sendTr(player, Messages.LINEAR_WAYPOINT_EDITOR_REMOVED_WAYPOINT, waypoints.size(),
|
|
editingSlot + 1);
|
|
}
|
|
onWaypointsModified();
|
|
}
|
|
|
|
@EventHandler(ignoreCancelled = true)
|
|
public void onPlayerInteractEntity(PlayerInteractEntityEvent event) {
|
|
if (!player.equals(event.getPlayer()) || !showPath)
|
|
return;
|
|
if (!event.getRightClicked().hasMetadata("waypointindex"))
|
|
return;
|
|
editingSlot = event.getRightClicked().getMetadata("waypointindex").get(0).asInt();
|
|
Messaging.sendTr(player, Messages.LINEAR_WAYPOINT_EDITOR_EDIT_SLOT_SET, editingSlot,
|
|
formatLoc(waypoints.get(editingSlot).getLocation()));
|
|
}
|
|
|
|
@EventHandler
|
|
public void onPlayerItemHeldChange(PlayerItemHeldEvent event) {
|
|
if (!event.getPlayer().equals(player) || waypoints.size() == 0)
|
|
return;
|
|
int previousSlot = event.getPreviousSlot(), newSlot = event.getNewSlot();
|
|
// handle wrap-arounds
|
|
if (previousSlot == 0 && newSlot == LARGEST_SLOT) {
|
|
editingSlot--;
|
|
} else if (previousSlot == LARGEST_SLOT && newSlot == 0) {
|
|
editingSlot++;
|
|
} else {
|
|
int diff = newSlot - previousSlot;
|
|
if (Math.abs(diff) != 1)
|
|
return; // the player isn't scrolling
|
|
editingSlot += diff > 0 ? 1 : -1;
|
|
}
|
|
normaliseEditingSlot();
|
|
Messaging.sendTr(player, Messages.LINEAR_WAYPOINT_EDITOR_EDIT_SLOT_SET, editingSlot,
|
|
formatLoc(waypoints.get(editingSlot).getLocation()));
|
|
}
|
|
|
|
private void onWaypointsModified() {
|
|
if (currentGoal != null)
|
|
currentGoal.onProviderChanged();
|
|
}
|
|
|
|
private void removeWaypointMarker(Waypoint waypoint) {
|
|
Entity entity = waypointMarkers.remove(waypoint);
|
|
if (entity != null)
|
|
entity.remove();
|
|
}
|
|
|
|
private Entity spawnMarker(World world, Location at) {
|
|
return NMS.spawnCustomEntity(world, at, EntityEnderCrystalMarker.class, EntityType.ENDER_CRYSTAL);
|
|
}
|
|
|
|
private void togglePath() {
|
|
showPath = !showPath;
|
|
if (showPath) {
|
|
createWaypointMarkers();
|
|
Messaging.sendTr(player, Messages.LINEAR_WAYPOINT_EDITOR_SHOWING_MARKERS);
|
|
} else {
|
|
destroyWaypointMarkers();
|
|
Messaging.sendTr(player, Messages.LINEAR_WAYPOINT_EDITOR_NOT_SHOWING_MARKERS);
|
|
}
|
|
}
|
|
|
|
private static final int LARGEST_SLOT = 8;
|
|
}
|
|
|
|
private class LinearWaypointGoal implements Goal {
|
|
private Waypoint currentDestination;
|
|
private Iterator<Waypoint> itr;
|
|
private boolean paused;
|
|
private GoalSelector selector;
|
|
|
|
private void ensureItr() {
|
|
if (itr == null)
|
|
itr = waypoints.iterator();
|
|
else if (!itr.hasNext())
|
|
itr = getNewIterator();
|
|
}
|
|
|
|
private Navigator getNavigator() {
|
|
return npc.getNavigator();
|
|
}
|
|
|
|
private Iterator<Waypoint> getNewIterator() {
|
|
LinearWaypointsCompleteEvent event = new LinearWaypointsCompleteEvent(
|
|
LinearWaypointProvider.this, waypoints.iterator());
|
|
Bukkit.getPluginManager().callEvent(event);
|
|
Iterator<Waypoint> next = event.getNextWaypoints();
|
|
return next;
|
|
}
|
|
|
|
public boolean isPaused() {
|
|
return paused;
|
|
}
|
|
|
|
@EventHandler
|
|
public void onNavigationComplete(NavigationCompleteEvent event) {
|
|
if (selector == null || !event.getNavigator().equals(getNavigator()))
|
|
return;
|
|
Waypoint from = currentDestination;
|
|
selector.finish();
|
|
Location finished = event.getNavigator().getTargetAsLocation();
|
|
if (finished == null || from == null)
|
|
return;
|
|
if (finished.getWorld() != from.getLocation().getWorld())
|
|
return;
|
|
if (finished.equals(from.getLocation()))
|
|
from.onReach(npc);
|
|
}
|
|
|
|
public void onProviderChanged() {
|
|
itr = waypoints.iterator();
|
|
if (currentDestination != null)
|
|
selector.finish();
|
|
}
|
|
|
|
@Override
|
|
public void reset() {
|
|
currentDestination = null;
|
|
selector = null;
|
|
}
|
|
|
|
@Override
|
|
public void run(GoalSelector selector) {
|
|
if (!getNavigator().isNavigating())
|
|
selector.finish();
|
|
}
|
|
|
|
public void setPaused(boolean pause) {
|
|
if (pause && currentDestination != null)
|
|
selector.finish();
|
|
paused = pause;
|
|
}
|
|
|
|
@Override
|
|
public boolean shouldExecute(GoalSelector selector) {
|
|
if (paused || currentDestination != null || !npc.isSpawned() || getNavigator().isNavigating()) {
|
|
return false;
|
|
}
|
|
ensureItr();
|
|
boolean shouldExecute = itr.hasNext();
|
|
if (!shouldExecute)
|
|
return false;
|
|
this.selector = selector;
|
|
Waypoint next = itr.next();
|
|
if (npc.getBukkitEntity().getLocation().distanceSquared(next.getLocation()) < 3)
|
|
return false;
|
|
currentDestination = next;
|
|
getNavigator().setTarget(currentDestination.getLocation());
|
|
return true;
|
|
}
|
|
}
|
|
} |