diff --git a/trunk/dist/game/data/scripts.cfg b/trunk/dist/game/data/scripts.cfg
index e28e186569..3e1bd77e3c 100644
--- a/trunk/dist/game/data/scripts.cfg
+++ b/trunk/dist/game/data/scripts.cfg
@@ -82,6 +82,7 @@ ai/npc/Zephyra/Zephyra.java
# Fantasy Isle
ai/fantasy_isle/MC_Show.java
ai/fantasy_isle/HandysBlockCheckerEvent.java
+ai/fantasy_isle/Parade.java
# Group Template
ai/group_template/AdenReconstructorManager.java
diff --git a/trunk/dist/game/data/scripts/ai/fantasy_isle/Parade.java b/trunk/dist/game/data/scripts/ai/fantasy_isle/Parade.java
new file mode 100644
index 0000000000..60133b740f
--- /dev/null
+++ b/trunk/dist/game/data/scripts/ai/fantasy_isle/Parade.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2004-2014 L2J DataPack
+ *
+ * This file is part of L2J DataPack.
+ *
+ * L2J DataPack is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * L2J DataPack is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package ai.fantasy_isle;
+
+import java.text.SimpleDateFormat;
+import java.util.Iterator;
+import java.util.concurrent.ScheduledFuture;
+
+import javolution.util.FastList;
+import ai.npc.AbstractNpcAI;
+
+import com.l2jserver.gameserver.GameTimeController;
+import com.l2jserver.gameserver.ThreadPoolManager;
+import com.l2jserver.gameserver.ai.CtrlIntention;
+import com.l2jserver.gameserver.model.Location;
+import com.l2jserver.gameserver.model.actor.L2Npc;
+
+/**
+ * Fantasy Isle Parade
+ * @author JOJO, Pandragon
+ */
+public class Parade extends AbstractNpcAI
+{
+ // @formatter:off
+ protected final int[] ACTORS =
+ {
+ 32379, 0, 32379,
+ 32379, 0, 32379,
+ 32379, 0, 32379,
+ 0, 0, 0,
+ 32380, 0, 32380,
+ 32380, 32381, 32380,
+ 32380, 0, 32380,
+ 32380, 32381, 32380,
+ 0, 0, 0,
+ 32382, 32382, 32382,
+ 32382, 32383, 32382,
+ 32383, 32384, 32383,
+ 32383, 32384, 32383,
+ 0, 0, 0,
+ 0, 32385, 0,
+ 32385, 0, 32385,
+ 0, 32385, 0,
+ 0, 0, 0,
+ 32412, 0, 32411,
+ 0, 0, 0,
+ 32421, 0, 32409,
+ 32423, 0, 32422,
+ 0, 0, 0,
+ 32420, 32419, 32417,
+ 32418, 0, 32416,
+ 0, 0, 0,
+ 32414, 0, 32414,
+ 0, 32413, 0,
+ 32414, 0, 32414,
+ 0, 0, 0,
+ 32393, 0, 32394,
+ 0, 32430, 0,
+ 32392, 0, 32391,
+ 0, 0, 0,
+ 0, 32404, 0,
+ 32403, 0, 32401,
+ 0, 0, 0,
+ 0, 32408, 0,
+ 32406, 0, 32407,
+ 0, 32405, 0,
+ 0, 0, 0,
+ 32390, 32389, 32387,
+ 32388, 0, 32386,
+ 0, 0, 0,
+ 0, 32400, 0,
+ 32397, 32398, 32396,
+ 0, 0, 0,
+ 0, 32450, 0,
+ 32448, 32449, 32447,
+ 0, 0, 0,
+ 32380, 0, 32380,
+ 32380, 32381, 32380,
+ 32380, 0, 32380,
+ 32380, 32381, 32380,
+ 0, 0, 0,
+ 32379, 0, 32379,
+ 32379, 0, 32379,
+ 32379, 0, 32379,
+ 0, 0, 0,
+ 0, 32415, 0
+ };
+
+ //(Northbound 270 degrees) Route 1
+ private final int[][] START1 = {{-54780, -56810, -2015, 49152},{-54860, -56810, -2015, 49152},{-54940, -56810, -2015, 49152}};
+ private final int[][] GOAL1 = {{-54780, -57965, -2015, 49152},{-54860, -57965, -2015, 49152},{-54940, -57965, -2015, 49152}};
+ //(Westbound 180 degrees) Route 2
+ private final int[][] START2 = {{-55715, -58900, -2015, 32768},{-55715, -58820, -2015, 32768},{-55715, -58740, -2015, 32768}};
+ private final int[][] GOAL2 = {{-60850, -58900, -2015, 32768},{-60850, -58820, -2015, 32768},{-60850, -58740, -2015, 32768}};
+ //(Southbound 90 degrees) Route 3
+ private final int[][] START3 = {{-61790, -57965, -2015, 16384},{-61710, -57965, -2015, 16384},{-61630, -57965, -2015, 16384}};
+ private final int[][] GOAL3 = {{-61790, -53890, -2116, 16384},{-61710, -53890, -2116, 16384},{-61630, -53890, -2116, 16384}};
+ //(Eastbound 0 degrees) Route 4
+ private final int[][] START4 = {{-60840, -52990, -2108, 0},{-60840, -53070, -2108, 0},{-60840, -53150, -2108, 0}};
+ private final int[][] GOAL4 = {{-58620, -52990, -2015, 0},{-58620, -53070, -2015, 0},{-58620, -53150, -2015, 0}};
+ //(To 315 degrees northeast) Route 5
+ private final int[][] START5 = {{-57233, -53554, -2015, 57344},{-57290, -53610, -2015, 57344},{-57346, -53667, -2015, 57344}};
+ private final int[][] GOAL5 = {{-55338, -55435, -2015, 57344},{-55395, -55491, -2015, 57344},{-55451, -55547, -2015, 57344}};
+
+ protected final int[][][] START = {START1, START2, START3, START4, START5};
+ protected final int[][][] GOAL = {GOAL1, GOAL2, GOAL3, GOAL4, GOAL5};
+ // @formatter:on
+
+ protected ScheduledFuture> spawnTask;
+ protected ScheduledFuture> deleteTask;
+ protected ScheduledFuture> cleanTask;
+
+ protected int npcIndex;
+ protected FastList spawns;
+
+ public Parade()
+ {
+ super(Parade.class.getSimpleName(), "ai/fantasy_isle");
+
+ // Starts at 8:00 and repeats every 6 hours.
+ final long diff = timeLeftMilli(8, 0, 0), cycle = 3600000L;
+ ThreadPoolManager.getInstance().scheduleGeneralAtFixedRate(new Start(), diff, cycle);
+
+ // Test - Starts 3 minutes after server startup and repeats every 20 minutes.
+ // final long diff = timeLeftMilli(8, 0, 0), cycle = 600000L;
+ // ThreadPoolManager.getInstance().scheduleGeneralAtFixedRate(new Start(), 180000L, cycle);
+
+ final SimpleDateFormat format = new SimpleDateFormat("yyyy/MM/dd HH:mm");
+ _log.info("Fantasy Isle: Parade starting at " + format.format(System.currentTimeMillis() + diff) + " and is scheduled each next " + (cycle / 3600000) + " hours.");
+ }
+
+ protected void load()
+ {
+ npcIndex = 0;
+ spawns = new FastList().shared();
+ }
+
+ protected void clean()
+ {
+ if (spawns != null)
+ {
+ spawns.forEach(L2Npc::deleteMe);
+ }
+ spawns = null;
+ }
+
+ private long timeLeftMilli(int hh, int mm, int ss)
+ {
+ int now = (GameTimeController.getInstance().getGameTicks() * 60) / 100;
+ int dd = ((hh * 3600) + (mm * 60) + ss) - (now % 86400);
+ if (dd < 0)
+ {
+ dd += 86400;
+ }
+
+ return (dd * 1000L) / 6L;
+ }
+
+ protected class Start implements Runnable
+ {
+ @Override
+ public void run()
+ {
+ load();
+ spawnTask = ThreadPoolManager.getInstance().scheduleGeneralAtFixedRate(new Spawn(), 0, 5000);
+ deleteTask = ThreadPoolManager.getInstance().scheduleGeneralAtFixedRate(new Delete(), 10000, 1000);
+ cleanTask = ThreadPoolManager.getInstance().scheduleGeneral(new Clean(), 420000);
+ }
+ }
+
+ protected class Spawn implements Runnable
+ {
+ @Override
+ public void run()
+ {
+ for (int i = 0; i < 3; ++i)
+ {
+ if (npcIndex >= ACTORS.length)
+ {
+ spawnTask.cancel(false);
+ break;
+ }
+ int npcId = ACTORS[npcIndex++];
+ if (npcId == 0)
+ {
+ continue;
+ }
+ for (int route = 0; route < 5; ++route)
+ {
+ int[] start = START[route][i];
+ int[] goal = GOAL[route][i];
+ L2Npc actor = addSpawn(npcId, start[0], start[1], start[2], start[3], false, 0);
+ actor.getAI().setIntention(CtrlIntention.AI_INTENTION_MOVE_TO, new Location(goal[0], goal[1], goal[2], goal[3]));
+ spawns.add(actor);
+ }
+ }
+ }
+ }
+
+ protected class Delete implements Runnable
+ {
+ @Override
+ public void run()
+ {
+ if (spawns.size() > 0)
+ {
+ for (Iterator it = spawns.iterator(); it.hasNext();)
+ {
+ L2Npc actor = it.next();
+ if (actor.calculateDistance(actor.getXdestination(), actor.getYdestination(), 0, false, true) < (100 * 100))
+ {
+ actor.deleteMe();
+ it.remove();
+ }
+ else if (!actor.isMoving())
+ {
+ actor.getAI().setIntention(CtrlIntention.AI_INTENTION_MOVE_TO, new Location(actor.getXdestination(), actor.getYdestination(), actor.getZdestination(), actor.getHeading()));
+ }
+ }
+ if (spawns.size() == 0)
+ {
+ deleteTask.cancel(false);
+ }
+ }
+ }
+ }
+
+ protected class Clean implements Runnable
+ {
+ @Override
+ public void run()
+ {
+ spawnTask.cancel(false);
+ spawnTask = null;
+ deleteTask.cancel(false);
+ deleteTask = null;
+ cleanTask.cancel(false);
+ cleanTask = null;
+ clean();
+ }
+ }
+
+ public static void main(String[] args)
+ {
+ new Parade();
+ }
+}
\ No newline at end of file