Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- diff --git a/aCis_gameserver/data/html/admin/eventos.htm b/aCis_gameserver/data/html/admin/eventos.htm
- index 25a894c..10d1f7a 100644
- --- a/aCis_gameserver/data/html/admin/eventos.htm
- +++ b/aCis_gameserver/data/html/admin/eventos.htm
- @@ -1,63 +1,103 @@
- + <tr>
- + <td align=left>
- + <font color=9FB6CD>Spoil Event:</font>
- + </td>
- + <td align=center><a action="bypass -h admin_spoilevent_start">
- + <font color=FFF000>Start</font>
- + </a></td>
- + <td align=center><a action="bypass -h admin_spoilevent_stop">
- + <font color=FF0000>Stop</font>
- + </a></td>
- + <td align=center><a action="bypass -h admin_spoilevent_status">Status</a></td>
- + </tr>
- + <tr></tr>
- + No newline at end of file
- diff --git a/aCis_gameserver/data/xml/adminCommands.xml b/aCis_gameserver/data/xml/adminCommands.xml
- index d3d3c1b..2573280 100644
- --- a/aCis_gameserver/data/xml/adminCommands.xml
- +++ b/aCis_gameserver/data/xml/adminCommands.xml
- @@ -60,6 +60,12 @@
- <aCar name="admin_soloboss_next" accessLevel="1" />
- <aCar name="admin_soloboss_info" accessLevel="1" />
- + <!-- Spoil Event -->
- + <aCar name="admin_spoilevent" accessLevel="1" />
- + <aCar name="admin_spoilevent_start" accessLevel="1" />
- + <aCar name="admin_spoilevent_stop" accessLevel="1" />
- + <aCar name="admin_spoilevent_status" accessLevel="1" />
- +
- <!-- ADMIN TOURNAMENT -->
- <aCar name="admin_tour" accessLevel="7"/>
- <aCar name="admin_tour_reset" accessLevel="7"/>
- diff --git a/aCis_gameserver/data/xml/events/SpoilEvent.xml b/aCis_gameserver/data/xml/events/SpoilEvent.xml
- new file mode 100644
- index 0000000..8b3d462
- --- /dev/null
- +++ b/aCis_gameserver/data/xml/events/SpoilEvent.xml
- @@ -0,0 +1,30 @@
- +<?xml version="1.0" encoding="UTF-8"?>
- +<list enabled="true">
- + <event name="SpoilEvent" durationMinutes="60" blockAutofarm="true" allowAutoRescheduleAfterManualStop="false">
- + <schedule startTimes="20:00,23:00,02:00" />
- +
- + <!-- Allowed Classes Configuration -->
- + <!-- Use "all" to allow any class inside the zone. -->
- + <!-- Use specific Class IDs separated by commas (e.g., "53,54,117") to restrict access. -->
- + <!-- Default (if removed): 53,54,117 (Scavenger, Bounty Hunter, Fortune Seeker) -->
- + <classes allowed="53,54,117" />
- +
- + <!-- Spoil Mobs -->
- + <monster id="21084" respawnSeconds="10"> <!-- Example: Lilim Knight -->
- + <spawn x="82984" y="140104" z="-3552"/>
- + <spawn x="82136" y="140056" z="-3568"/>
- + <spawn x="82456" y="139704" z="-3568"/>
- + </monster>
- +
- + <!-- Gatekeepers -->
- + <gatekeeper id="30080"> <!-- Example: Monument -->
- + <spawn x="83320" y="148632" z="-3408"/>
- + </gatekeeper>
- +
- + <!-- Teleport Location (Where players go when accepting the popup) -->
- + <teleport x="82984" y="140104" z="-3552" />
- +
- + <!-- Return Location (if player is not allowed in zone) -->
- + <return x="82488" y="149064" z="-3464"/> <!-- Giran -->
- + </event>
- +</list>
- diff --git a/aCis_gameserver/data/xml/zones/SpoilZone.xml b/aCis_gameserver/data/xml/zones/SpoilZone.xml
- new file mode 100644
- index 0000000..4b72ce4
- --- /dev/null
- +++ b/aCis_gameserver/data/xml/zones/SpoilZone.xml
- @@ -0,0 +1,17 @@
- +<?xml version="1.0" encoding="UTF-8"?>
- +<list>
- + <!-- Example Spoil Zone -->
- + <zone shape="Cylinder" type="SpoilZone" minZ="-3560" maxZ="-3560" rad="2500"> <!-- spoil1 -->
- + <node x="83220" y="140200"/>
- + </zone>
- +
- +</list>
- diff --git a/aCis_gameserver/java/net/sf/l2j/gameserver/GameServer.java b/aCis_gameserver/java/net/sf/l2j/gameserver/GameServer.java
- index 618d515..01e0c42 100644
- --- a/aCis_gameserver/java/net/sf/l2j/gameserver/GameServer.java
- +++ b/aCis_gameserver/java/net/sf/l2j/gameserver/GameServer.java
- @@ -78,6 +78,8 @@ import net.sf.l2j.gameserver.data.xml.StaticObjectData;
- import net.sf.l2j.gameserver.data.xml.SummonItemData;
- import net.sf.l2j.gameserver.data.xml.TeleportLocationData;
- import net.sf.l2j.gameserver.data.xml.WalkerRouteData;
- +import net.sf.l2j.gameserver.events.spoil.SpoilEventData;
- +import net.sf.l2j.gameserver.events.spoil.SpoilEventScheduler;
- import net.sf.l2j.gameserver.geoengine.GeoEngine;
- import net.sf.l2j.gameserver.handler.AdminCommandHandler;
- import net.sf.l2j.gameserver.handler.BypassHandler;
- @@ -313,6 +315,8 @@ public class GameServer
- TvTManager.getInstance();
- SoloBossEvent.getInstance().init();
- + SpoilEventData.getInstance();
- + SpoilEventScheduler.getInstance().init();
- if (Config.CKM_ENABLED)
- CharacterKillingManager.getInstance().init();
- diff --git a/aCis_gameserver/java/net/sf/l2j/gameserver/enums/ZoneId.java b/aCis_gameserver/java/net/sf/l2j/gameserver/enums/ZoneId.java
- index 9a5b012..7648da8 100644
- --- a/aCis_gameserver/java/net/sf/l2j/gameserver/enums/ZoneId.java
- +++ b/aCis_gameserver/java/net/sf/l2j/gameserver/enums/ZoneId.java
- @@ -22,7 +22,9 @@ public enum ZoneId
- NO_RESTART(17),
- SCRIPT(18),
- BOSS(19),
- - TOURNAMENT(20);
- + TOURNAMENT(20),
- + SPOIL_AREA(21),
- + FLAG_ZONE(22);
- private final int _id;
- diff --git a/aCis_gameserver/java/net/sf/l2j/gameserver/events/spoil/SpoilEventData.java b/aCis_gameserver/java/net/sf/l2j/gameserver/events/spoil/SpoilEventData.java
- new file mode 100644
- index 0000000..f0e12a3
- --- /dev/null
- +++ b/aCis_gameserver/java/net/sf/l2j/gameserver/events/spoil/SpoilEventData.java
- @@ -0,0 +1,225 @@
- +package net.sf.l2j.gameserver.events.spoil;
- +
- +import java.nio.file.Path;
- +import java.util.ArrayList;
- +import java.util.List;
- +
- +import net.sf.l2j.commons.data.xml.IXmlReader;
- +
- +import net.sf.l2j.gameserver.model.location.Location;
- +
- +import org.w3c.dom.Document;
- +import org.w3c.dom.NamedNodeMap;
- +
- +/**
- + * @author Jhon
- + */
- +public class SpoilEventData implements IXmlReader
- +{
- + private boolean _enabled = false;
- + private int _durationMinutes = 60;
- + private boolean _blockAutofarm = true;
- + private boolean _allowAutoReschedule = false;
- + private Location _returnLoc = null;
- + private Location _teleportLoc = null;
- +
- + private final List<String> _scheduleTimes = new ArrayList<>();
- + private final List<SpawnInfo> _monsterSpawns = new ArrayList<>();
- + private final List<SpawnInfo> _gkSpawns = new ArrayList<>();
- + // Default: Scavenger(53), Bounty Hunter(54), Fortune Seeker(117)
- + private final List<Integer> _allowedClassIds = new ArrayList<>();
- + private boolean _allClassesAllowed = false;
- + {
- + _allowedClassIds.add(53);
- + _allowedClassIds.add(54);
- + _allowedClassIds.add(117);
- + }
- +
- + public static class SpawnInfo
- + {
- + public final int npcId;
- + public final int respawnDelay;
- + public final List<Location> locations = new ArrayList<>();
- +
- + public SpawnInfo(int id, int respawn)
- + {
- + npcId = id;
- + respawnDelay = respawn;
- + }
- + }
- +
- + protected SpoilEventData()
- + {
- + load();
- + }
- +
- + @Override
- + public void load()
- + {
- + _scheduleTimes.clear();
- + _monsterSpawns.clear();
- + _gkSpawns.clear();
- + _allowedClassIds.clear();
- + _allClassesAllowed = false;
- + _allowedClassIds.add(53);
- + _allowedClassIds.add(54);
- + _allowedClassIds.add(117);
- +
- + parseFile("./data/xml/events/SpoilEvent.xml");
- + LOGGER.info("SpoilEventData: Loaded {} schedule times, {} monster types, {} GK types.", _scheduleTimes.size(), _monsterSpawns.size(), _gkSpawns.size());
- + }
- +
- + @Override
- + public void parseDocument(Document doc, Path path)
- + {
- + forEach(doc, "list", listNode ->
- + {
- + forEach(listNode, "event", eventNode ->
- + {
- + final NamedNodeMap attrs = eventNode.getAttributes();
- + _enabled = parseBoolean(listNode.getAttributes(), "enabled", true); // Check list enabled first, or event enabled? User example has list enabled=true. I will check both or stick to list.
- + // Actually user xml has <list enabled="true"> and inside <event ...>.
- + // Let's check the list enabled attribute.
- + if (!_enabled)
- + return;
- +
- + _durationMinutes = parseInteger(attrs, "durationMinutes", 60);
- + _blockAutofarm = parseBoolean(attrs, "blockAutofarm", true);
- + _allowAutoReschedule = parseBoolean(attrs, "allowAutoRescheduleAfterManualStop", false);
- +
- + forEach(eventNode, "classes", classNode ->
- + {
- + final String classes = parseString(classNode.getAttributes(), "allowed");
- + if (classes != null && !classes.isEmpty())
- + {
- + if (classes.equalsIgnoreCase("all"))
- + {
- + _allClassesAllowed = true;
- + _allowedClassIds.clear();
- + }
- + else
- + {
- + _allowedClassIds.clear(); // Override defaults if config exists
- + for (String id : classes.split(","))
- + _allowedClassIds.add(Integer.parseInt(id.trim()));
- + }
- + }
- + });
- +
- + forEach(eventNode, "schedule", scheduleNode ->
- + {
- + final String times = parseString(scheduleNode.getAttributes(), "startTimes");
- + if (times != null && !times.isEmpty())
- + {
- + for (String time : times.split(","))
- + _scheduleTimes.add(time.trim());
- + }
- + });
- +
- + forEach(eventNode, "monster", mobNode ->
- + {
- + final NamedNodeMap mobAttrs = mobNode.getAttributes();
- + final int id = parseInteger(mobAttrs, "id");
- + final int respawn = parseInteger(mobAttrs, "respawnSeconds", 60);
- +
- + final SpawnInfo info = new SpawnInfo(id, respawn);
- +
- + forEach(mobNode, "spawn", spawnNode ->
- + {
- + info.locations.add(parseLocation(spawnNode));
- + });
- + _monsterSpawns.add(info);
- + });
- +
- + forEach(eventNode, "gatekeeper", gkNode ->
- + {
- + final NamedNodeMap gkAttrs = gkNode.getAttributes();
- + final int id = parseInteger(gkAttrs, "id");
- +
- + final SpawnInfo info = new SpawnInfo(id, 0); // GK usually doesn't respawn or instant? Let's say 0 (standard or non-respawn)
- +
- + forEach(gkNode, "spawn", spawnNode ->
- + {
- + info.locations.add(parseLocation(spawnNode));
- + });
- + _gkSpawns.add(info);
- + });
- +
- + forEach(eventNode, "return", returnNode ->
- + {
- + _returnLoc = parseLocation(returnNode);
- + });
- +
- + forEach(eventNode, "teleport", arg ->
- + {
- + _teleportLoc = parseLocation(arg);
- + });
- + });
- + });
- + }
- +
- + public boolean isEnabled()
- + {
- + return _enabled;
- + }
- +
- + public int getDurationMinutes()
- + {
- + return _durationMinutes;
- + }
- +
- + public boolean isBlockAutofarm()
- + {
- + return _blockAutofarm;
- + }
- +
- + public boolean isAllowAutoReschedule()
- + {
- + return _allowAutoReschedule;
- + }
- +
- + public Location getReturnLoc()
- + {
- + return _returnLoc;
- + }
- +
- + public Location getTeleportLoc()
- + {
- + return _teleportLoc;
- + }
- +
- + public List<String> getScheduleTimes()
- + {
- + return _scheduleTimes;
- + }
- +
- + public List<SpawnInfo> getMonsterSpawns()
- + {
- + return _monsterSpawns;
- + }
- +
- + public List<SpawnInfo> getGkSpawns()
- + {
- + return _gkSpawns;
- + }
- +
- + public List<Integer> getAllowedClassIds()
- + {
- + return _allowedClassIds;
- + }
- +
- + public boolean isAllClassesAllowed()
- + {
- + return _allClassesAllowed;
- + }
- +
- + public static SpoilEventData getInstance()
- + {
- + return SingletonHolder.INSTANCE;
- + }
- +
- + private static class SingletonHolder
- + {
- + protected static final SpoilEventData INSTANCE = new SpoilEventData();
- + }
- +}
- diff --git a/aCis_gameserver/java/net/sf/l2j/gameserver/events/spoil/SpoilEventManager.java b/aCis_gameserver/java/net/sf/l2j/gameserver/events/spoil/SpoilEventManager.java
- new file mode 100644
- index 0000000..ca0d13f
- --- /dev/null
- +++ b/aCis_gameserver/java/net/sf/l2j/gameserver/events/spoil/SpoilEventManager.java
- @@ -0,0 +1,312 @@
- +package net.sf.l2j.gameserver.events.spoil;
- +
- +import java.util.concurrent.ScheduledFuture;
- +
- +import net.sf.l2j.commons.concurrent.ThreadPool;
- +import net.sf.l2j.commons.logging.CLogger;
- +
- +import net.sf.l2j.gameserver.model.World;
- +import net.sf.l2j.gameserver.data.manager.ZoneManager;
- +import net.sf.l2j.gameserver.model.actor.Player;
- +import net.sf.l2j.gameserver.model.location.Location;
- +import net.sf.l2j.gameserver.model.zone.type.SpoilZone;
- +import net.sf.l2j.gameserver.network.serverpackets.ConfirmDlg;
- +import net.sf.l2j.gameserver.network.serverpackets.ExShowScreenMessage;
- +import net.sf.l2j.gameserver.network.serverpackets.ExShowScreenMessage.SMPOS;
- +import net.sf.l2j.gameserver.network.SystemMessageId;
- +import net.sf.l2j.gameserver.model.entity.Events.CaptureTheFlag.CTFEvent;
- +import net.sf.l2j.gameserver.model.entity.Events.DeathMatch.DMEvent;
- +import net.sf.l2j.gameserver.model.entity.Events.TeamVsTeam.TvTEvent;
- +
- +/**
- + * @author Jhon
- + */
- +public class SpoilEventManager
- +{
- + private static final CLogger LOGGER = new CLogger(SpoilEventManager.class.getName());
- +
- + private boolean _active = false;
- + private boolean _registrationOpen = false;
- + private ScheduledFuture<?> _endTask = null;
- + private ScheduledFuture<?> _countdownTask = null;
- + private ScheduledFuture<?> _registrationTask = null;
- + private long _endTime = 0;
- +
- + protected SpoilEventManager()
- + {
- + }
- +
- + public synchronized void startEvent()
- + {
- + if (_active)
- + {
- + LOGGER.warn("SpoilEvent: Attempt to start event but it is already active.");
- + return;
- + }
- +
- + if (!SpoilEventData.getInstance().isEnabled())
- + {
- + LOGGER.warn("SpoilEvent: Attempt to start event but it is disabled in config.");
- + return;
- + }
- +
- + _active = true;
- +
- + // Spawn NPCs
- + SpoilEventSpawner.getInstance().spawnAll();
- +
- + // Announcement
- + World.announceToOnlinePlayers("[Spoil Event]: The event has started!");
- + World.announceToOnlinePlayers("[Spoil Event]: Duration: " + SpoilEventData.getInstance().getDurationMinutes() + " minute(s)!");
- + World.toAllOnlinePlayers(new ExShowScreenMessage("Spoil Event Started! Teleport Now!", 5000, SMPOS.TOP_CENTER, true));
- +
- + // Schedule end
- + final long durationMillis = SpoilEventData.getInstance().getDurationMinutes() * 60 * 1000L;
- + _endTime = System.currentTimeMillis() + durationMillis;
- +
- + if (_endTask != null)
- + _endTask.cancel(false);
- +
- + _endTask = ThreadPool.schedule(() -> stopEvent(false), durationMillis);
- +
- + // Schedule countdowns
- + scheduleCountdown(durationMillis);
- +
- + // Open Registration/Popup for 30 seconds
- + _registrationOpen = true;
- + _registrationTask = ThreadPool.schedule(() -> _registrationOpen = false, 30000);
- +
- + // Broadcast Popup to eligible players
- + for (Player player : World.getInstance().getPlayers())
- + {
- + if (player == null || player.isOnline() == false)
- + continue;
- +
- + // Skip if in other events or busy
- + if (player.isInOlympiadMode() || player.isAlikeDead() || player.isTeleporting() || player.isInObserverMode() || player.isInStoreMode() || TvTEvent.isPlayerParticipant(player.getObjectId()) || CTFEvent.isPlayerParticipant(player.getObjectId()) || DMEvent.isPlayerParticipant(player.getObjectId()))
- + continue;
- +
- + // Skip unauthorized classes if restriction is on
- + if (!SpoilEventData.getInstance().isAllClassesAllowed() && !SpoilEventData.getInstance().getAllowedClassIds().contains(player.getClassId().getId()) && !player.isGM())
- + continue;
- +
- + ConfirmDlg confirm = new ConfirmDlg(SystemMessageId.S1.getId());
- + confirm.addString("Spoil Event started! Do you wish to join?");
- + confirm.addTime(30000);
- + confirm.addRequesterId(player.getObjectId());
- + player.sendPacket(confirm);
- + }
- +
- + // Validate players already inside (anti-camping non-spoilers)
- + for (SpoilZone zone : ZoneManager.getInstance().getAllZones(SpoilZone.class))
- + {
- + for (Player player : zone.getKnownTypeInside(Player.class))
- + {
- + if (player != null)
- + {
- + zone.revalidateInZone(player); // Trigger onEnter check again or manual check?
- + // SpoilZone.onEnter checks logic. But setInsideZone might stay true.
- + // Let's call a manual check method to be safe, or just reuse logic.
- + // Simpler: Check rules manually here or call zone.onEnter(player) effectively?
- + // onEnter guards with 'isActive' which is now true. So logic works.
- + // But onEnter also checks if player is already inside internal list?
- + // ZoneType: onEnter often checks if (!isInsideZone(object)).
- + // So we might need to manually check requirements here.
- +
- + if (!player.isGM() && player.getClassId().getId() != 53 && // Scavenger
- + player.getClassId().getId() != 54 && // Bounty Hunter
- + player.getClassId().getId() != 117) // Fortune Seeker
- + {
- + player.sendMessage("Spoil Event started! Non-spoilers are moved out.");
- + Location loc = SpoilEventData.getInstance().getReturnLoc();
- + if (loc != null)
- + player.teleportTo(loc.getX(), loc.getY(), loc.getZ(), 0);
- + else
- + player.teleportTo(81260, 148607, -3440, 0);
- + }
- + }
- + }
- + }
- + }
- +
- + public synchronized void stopEvent(boolean manual)
- + {
- + if (!_active)
- + return;
- +
- + _active = false;
- + _endTime = 0;
- +
- + // Cancel tasks
- + if (_endTask != null)
- + {
- + _endTask.cancel(false);
- + _endTask = null;
- + }
- +
- + if (_registrationTask != null)
- + {
- + _registrationTask.cancel(false);
- + _registrationTask = null;
- + _registrationOpen = false;
- + }
- +
- + if (_countdownTask != null)
- + {
- + _countdownTask.cancel(false);
- + _countdownTask = null;
- + }
- +
- + // Despawn NPCs
- + SpoilEventSpawner.getInstance().despawnAll();
- +
- + // Teleport players out of the zone
- + for (SpoilZone zone : ZoneManager.getInstance().getAllZones(SpoilZone.class))
- + {
- + for (Player player : zone.getKnownTypeInside(Player.class))
- + {
- + if (player != null && !player.isGM()) // Optionally keep GMs?
- + {
- + Location loc = SpoilEventData.getInstance().getReturnLoc();
- + if (loc != null)
- + player.teleportTo(loc.getX(), loc.getY(), loc.getZ(), 0);
- + else
- + player.teleportTo(81260, 148607, -3440, 0);
- +
- + player.sendMessage("Spoil Event has ended. You have been teleported out.");
- + }
- + }
- + }
- +
- + // Announcement
- + World.announceToOnlinePlayers("[Spoil Event]: The event has finished!");
- + World.toAllOnlinePlayers(new ExShowScreenMessage("Spoil Event Finished!", 5000, SMPOS.TOP_CENTER, true));
- +
- + // Reschedule next
- + if (!manual || SpoilEventData.getInstance().isAllowAutoReschedule())
- + {
- + SpoilEventScheduler.getInstance().scheduleNext();
- + }
- + }
- +
- + private void scheduleCountdown(long remainingMillis)
- + {
- + // Simple recursive countdown scheduler or just scheduling multiple tasks?
- + // To be safe and clean, we can schedule unique tasks for each checkpoint.
- + // Checkpoints: 30m, 15m, 10m, 5m, 3m, 2m, 1m, 30s, 15s, 10s, 5s, 3s, 2s, 1s
- +
- + int[] checks =
- + {
- + 30 * 60,
- + 15 * 60,
- + 10 * 60,
- + 5 * 60,
- + 3 * 60,
- + 2 * 60,
- + 60,
- + 30,
- + 15,
- + 10,
- + 5,
- + 3,
- + 2,
- + 1
- + };
- +
- + for (int sec : checks)
- + {
- + long notifyMillis = remainingMillis - (sec * 1000L);
- + if (notifyMillis > 0)
- + {
- + ThreadPool.schedule(() ->
- + {
- + if (_active)
- + {
- + String timeMsg = (sec >= 60) ? (sec / 60) + " minute(s)" : sec + " second(s)";
- + World.announceToOnlinePlayers("[Spoil Event]: " + timeMsg + " remaining!");
- + }
- + }, notifyMillis);
- + }
- + }
- + }
- +
- + public boolean isActive()
- + {
- + return _active;
- + }
- +
- + public long getEndTime()
- + {
- + return _endTime;
- + }
- +
- + public static SpoilEventManager getInstance()
- + {
- + return SingletonHolder.INSTANCE;
- + }
- +
- + public boolean isRegistrationOpen()
- + {
- + return _registrationOpen;
- + }
- +
- + public void registerPlayer(Player player)
- + {
- + if (!_active || player == null)
- + return;
- +
- + // Re-check conditions in case they changed since popup
- + if (player.isInOlympiadMode() || player.isAlikeDead() || player.isTeleporting() || player.isInObserverMode() || player.isInStoreMode())
- + {
- + player.sendMessage("You cannot join the event in your current state.");
- + return;
- + }
- +
- + if (!SpoilEventData.getInstance().isAllClassesAllowed() && !SpoilEventData.getInstance().getAllowedClassIds().contains(player.getClassId().getId()) && !player.isGM())
- + {
- + player.sendMessage("Your class is not allowed in this event.");
- + return;
- + }
- +
- + // Teleport to Gatekeeper spawn or random spawn?
- + // Typically events teleport to a starting area.
- + // Let's use the first GK spawn location if available, or just the first monster spawn area?
- + // Actually, users usually walk in. If we teleport, we should teleport to a safe spot inside.
- + // Let's us the RETURN location? No, that's for exit.
- + // Let's pick the first Monster or GK location as a "drop in" point.
- + // Ideally we'd have a specific "enter" location in XML, but we don't.
- + // We will peek at monster spawns and pick one.
- +
- + // Use configured teleport location
- + Location targetLoc = SpoilEventData.getInstance().getTeleportLoc();
- +
- + // Fallback if not configured (try monster spawns -> GK spawns)
- + if (targetLoc == null)
- + {
- + if (!SpoilEventData.getInstance().getMonsterSpawns().isEmpty())
- + {
- + Location l = SpoilEventData.getInstance().getMonsterSpawns().get(0).locations.get(0);
- + targetLoc = new Location(l.getX(), l.getY(), l.getZ());
- + }
- + else if (!SpoilEventData.getInstance().getGkSpawns().isEmpty())
- + {
- + Location l = SpoilEventData.getInstance().getGkSpawns().get(0).locations.get(0);
- + targetLoc = new Location(l.getX(), l.getY(), l.getZ());
- + }
- + }
- +
- + if (targetLoc != null)
- + {
- + player.teleportTo(targetLoc.getX(), targetLoc.getY(), targetLoc.getZ(), 0);
- + }
- + else
- + {
- + player.sendMessage("No spawn location found for event.");
- + }
- + }
- +
- + private static class SingletonHolder
- + {
- + protected static final SpoilEventManager INSTANCE = new SpoilEventManager();
- + }
- +}
- diff --git a/aCis_gameserver/java/net/sf/l2j/gameserver/events/spoil/SpoilEventScheduler.java b/aCis_gameserver/java/net/sf/l2j/gameserver/events/spoil/SpoilEventScheduler.java
- new file mode 100644
- index 0000000..7e60483
- --- /dev/null
- +++ b/aCis_gameserver/java/net/sf/l2j/gameserver/events/spoil/SpoilEventScheduler.java
- @@ -0,0 +1,119 @@
- +package net.sf.l2j.gameserver.events.spoil;
- +
- +import java.util.Calendar;
- +import java.util.Collections;
- +import java.util.List;
- +
- +import net.sf.l2j.commons.concurrent.ThreadPool;
- +import net.sf.l2j.commons.logging.CLogger;
- +
- +/**
- + * @author Jhon
- + */
- +public class SpoilEventScheduler
- +{
- + private static final CLogger LOGGER = new CLogger(SpoilEventScheduler.class.getName());
- +
- + private String _nextEventTime = "N/A";
- + private long _nextEventMillis = 0;
- +
- + protected SpoilEventScheduler()
- + {
- + }
- +
- + public void init()
- + {
- + if (!SpoilEventData.getInstance().isEnabled())
- + return;
- +
- + scheduleNext();
- + }
- +
- + public void scheduleNext()
- + {
- + final List<String> times = SpoilEventData.getInstance().getScheduleTimes();
- + if (times.isEmpty())
- + {
- + _nextEventTime = "None";
- + return;
- + }
- +
- + long currentTime = System.currentTimeMillis();
- + long nextTime = 0;
- + String nextTimeString = "";
- +
- + // Sort times to ensure correct order if XML is mixed
- + Collections.sort(times);
- +
- + Calendar test = Calendar.getInstance();
- + test.setLenient(true);
- +
- + // Find closest future time
- + boolean found = false;
- + for (String t : times)
- + {
- + String[] split = t.split(":");
- + if (split.length != 2)
- + continue;
- +
- + test.setTimeInMillis(currentTime);
- + test.set(Calendar.HOUR_OF_DAY, Integer.parseInt(split[0]));
- + test.set(Calendar.MINUTE, Integer.parseInt(split[1]));
- + test.set(Calendar.SECOND, 0);
- + test.set(Calendar.MILLISECOND, 0);
- +
- + if (test.getTimeInMillis() > currentTime)
- + {
- + nextTime = test.getTimeInMillis();
- + nextTimeString = t;
- + found = true;
- + break;
- + }
- + }
- +
- + // If no time found for today, pick the first time for tomorrow
- + if (!found)
- + {
- + String t = times.get(0);
- + String[] split = t.split(":");
- +
- + test.setTimeInMillis(currentTime);
- + test.add(Calendar.DAY_OF_YEAR, 1); // Tomorrow
- + test.set(Calendar.HOUR_OF_DAY, Integer.parseInt(split[0]));
- + test.set(Calendar.MINUTE, Integer.parseInt(split[1]));
- + test.set(Calendar.SECOND, 0);
- + test.set(Calendar.MILLISECOND, 0);
- +
- + nextTime = test.getTimeInMillis();
- + nextTimeString = t;
- + }
- +
- + _nextEventTime = nextTimeString;
- + _nextEventMillis = nextTime;
- +
- + final long delay = nextTime - currentTime;
- + LOGGER.info("SpoilEvent: Next event scheduled at {} (in {} ms).", _nextEventTime, delay);
- +
- + ThreadPool.schedule(SpoilEventManager.getInstance()::startEvent, delay);
- + }
- +
- + public String getNextEventTimeString()
- + {
- + return _nextEventTime;
- + }
- +
- + public long getNextStartTime()
- + {
- + return _nextEventMillis;
- + }
- +
- + public static SpoilEventScheduler getInstance()
- + {
- + return SingletonHolder.INSTANCE;
- + }
- +
- + private static class SingletonHolder
- + {
- + protected static final SpoilEventScheduler INSTANCE = new SpoilEventScheduler();
- + }
- +}
- diff --git a/aCis_gameserver/java/net/sf/l2j/gameserver/events/spoil/SpoilEventSpawner.java b/aCis_gameserver/java/net/sf/l2j/gameserver/events/spoil/SpoilEventSpawner.java
- new file mode 100644
- index 0000000..fe665c7
- --- /dev/null
- +++ b/aCis_gameserver/java/net/sf/l2j/gameserver/events/spoil/SpoilEventSpawner.java
- @@ -0,0 +1,111 @@
- +package net.sf.l2j.gameserver.events.spoil;
- +
- +import java.util.ArrayList;
- +import java.util.List;
- +
- +import net.sf.l2j.commons.logging.CLogger;
- +
- +import net.sf.l2j.gameserver.data.sql.SpawnTable;
- +import net.sf.l2j.gameserver.data.xml.NpcData;
- +import net.sf.l2j.gameserver.events.spoil.SpoilEventData.SpawnInfo;
- +import net.sf.l2j.gameserver.model.actor.Npc;
- +import net.sf.l2j.gameserver.model.actor.template.NpcTemplate;
- +import net.sf.l2j.gameserver.model.location.Location;
- +import net.sf.l2j.gameserver.model.spawn.L2Spawn;
- +
- +/**
- + * @author Jhon
- + */
- +public class SpoilEventSpawner
- +{
- + private static final CLogger LOGGER = new CLogger(SpoilEventSpawner.class.getName());
- +
- + private final List<L2Spawn> _activeSpawns = new ArrayList<>();
- +
- + protected SpoilEventSpawner()
- + {
- + }
- +
- + /**
- + * Spawns all configured monsters and gatekeepers.
- + */
- + public void spawnAll()
- + {
- + spawnGroup(SpoilEventData.getInstance().getMonsterSpawns());
- + spawnGroup(SpoilEventData.getInstance().getGkSpawns());
- +
- + LOGGER.info("SpoilEvent: Spawned {} NPCs.", _activeSpawns.size());
- + }
- +
- + private void spawnGroup(List<SpawnInfo> group)
- + {
- + for (SpawnInfo info : group)
- + {
- + final NpcTemplate template = NpcData.getInstance().getTemplate(info.npcId);
- + if (template == null)
- + {
- + LOGGER.warn("SpoilEvent: Invalid NPC ID {}, skipping.", info.npcId);
- + continue;
- + }
- +
- + for (Location loc : info.locations)
- + {
- + try
- + {
- + final L2Spawn spawn = new L2Spawn(template);
- + spawn.setLoc(loc.getX(), loc.getY(), loc.getZ(), 0);
- + spawn.setRespawnDelay(info.respawnDelay);
- +
- + SpawnTable.getInstance().addSpawn(spawn, false);
- + spawn.doSpawn(false);
- +
- + if (info.respawnDelay > 0)
- + spawn.setRespawnState(true);
- + else
- + spawn.setRespawnState(false);
- +
- + _activeSpawns.add(spawn);
- + }
- + catch (Exception e)
- + {
- + LOGGER.error("SpoilEvent: Failed to spawn NPC {}.", e, info.npcId);
- + }
- + }
- + }
- + }
- +
- + /**
- + * Despawns all event NPCs and clears the list.
- + */
- + public void despawnAll()
- + {
- + if (_activeSpawns.isEmpty())
- + return;
- +
- + for (L2Spawn spawn : _activeSpawns)
- + {
- + if (spawn == null)
- + continue;
- +
- + spawn.setRespawnState(false);
- + SpawnTable.getInstance().deleteSpawn(spawn, false);
- +
- + final Npc npc = spawn.getNpc();
- + if (npc != null)
- + npc.deleteMe();
- + }
- +
- + LOGGER.info("SpoilEvent: Despawned {} NPCs.", _activeSpawns.size());
- + _activeSpawns.clear();
- + }
- +
- + public static SpoilEventSpawner getInstance()
- + {
- + return SingletonHolder.INSTANCE;
- + }
- +
- + private static class SingletonHolder
- + {
- + protected static final SpoilEventSpawner INSTANCE = new SpoilEventSpawner();
- + }
- +}
- diff --git a/aCis_gameserver/java/net/sf/l2j/gameserver/handler/AdminCommandHandler.java b/aCis_gameserver/java/net/sf/l2j/gameserver/handler/AdminCommandHandler.java
- index 7d373be..d5b9ba4 100644
- --- a/aCis_gameserver/java/net/sf/l2j/gameserver/handler/AdminCommandHandler.java
- +++ b/aCis_gameserver/java/net/sf/l2j/gameserver/handler/AdminCommandHandler.java
- @@ -58,6 +58,7 @@ import net.sf.l2j.gameserver.handler.admincommandhandlers.AdminSiege;
- import net.sf.l2j.gameserver.handler.admincommandhandlers.AdminSkill;
- import net.sf.l2j.gameserver.handler.admincommandhandlers.AdminSoloBoss;
- import net.sf.l2j.gameserver.handler.admincommandhandlers.AdminSpawn;
- +import net.sf.l2j.gameserver.handler.admincommandhandlers.AdminSpoilEvent;
- import net.sf.l2j.gameserver.handler.admincommandhandlers.AdminTarget;
- import net.sf.l2j.gameserver.handler.admincommandhandlers.AdminTeleport;
- import net.sf.l2j.gameserver.handler.admincommandhandlers.AdminTvTEvent;
- @@ -133,6 +134,7 @@ public class AdminCommandHandler
- registerHandler(new AdminZoneCreate());
- registerHandler(new AdminClanFull());
- registerHandler(new AdminSetHero());
- + registerHandler(new AdminSpoilEvent());
- }
- public void registerHandler(IAdminCommandHandler handler)
- diff --git a/aCis_gameserver/java/net/sf/l2j/gameserver/handler/admincommandhandlers/AdminSpoilEvent.java b/aCis_gameserver/java/net/sf/l2j/gameserver/handler/admincommandhandlers/AdminSpoilEvent.java
- new file mode 100644
- index 0000000..a53e75a
- --- /dev/null
- +++ b/aCis_gameserver/java/net/sf/l2j/gameserver/handler/admincommandhandlers/AdminSpoilEvent.java
- @@ -0,0 +1,72 @@
- +package net.sf.l2j.gameserver.handler.admincommandhandlers;
- +
- +import net.sf.l2j.gameserver.events.spoil.SpoilEventManager;
- +import net.sf.l2j.gameserver.events.spoil.SpoilEventScheduler;
- +import net.sf.l2j.gameserver.handler.IAdminCommandHandler;
- +import net.sf.l2j.gameserver.model.actor.Player;
- +
- +/**
- + * @author Jhon
- + */
- +public class AdminSpoilEvent implements IAdminCommandHandler
- +{
- + private static final String[] ADMIN_COMMANDS =
- + {
- + "admin_spoilevent",
- + "admin_spoilevent_start",
- + "admin_spoilevent_stop",
- + "admin_spoilevent_status"
- + };
- +
- + @Override
- + public boolean useAdminCommand(String command, Player activeChar)
- + {
- + if (activeChar == null)
- + return false;
- +
- + if (command.equals("admin_spoilevent") || command.equals("admin_spoilevent_status"))
- + {
- + showStatus(activeChar);
- + }
- + else if (command.equals("admin_spoilevent_start"))
- + {
- + if (SpoilEventManager.getInstance().isActive())
- + activeChar.sendMessage("Spoil Event is already active.");
- + else
- + {
- + activeChar.sendMessage("Starting Spoil Event manually...");
- + SpoilEventManager.getInstance().startEvent();
- + }
- + }
- + else if (command.equals("admin_spoilevent_stop"))
- + {
- + if (!SpoilEventManager.getInstance().isActive())
- + activeChar.sendMessage("Spoil Event is not active.");
- + else
- + {
- + activeChar.sendMessage("Stopping Spoil Event manually...");
- + SpoilEventManager.getInstance().stopEvent(true);
- + }
- + }
- +
- + return true;
- + }
- +
- + private static void showStatus(Player activeChar)
- + {
- + boolean active = SpoilEventManager.getInstance().isActive();
- + activeChar.sendMessage("Spoil Event Status: " + (active ? "ACTIVE" : "INACTIVE"));
- + if (active)
- + {
- + long remaining = SpoilEventManager.getInstance().getEndTime() - System.currentTimeMillis();
- + activeChar.sendMessage("Time Remaining: " + (remaining / 60000) + " minutes.");
- + }
- + activeChar.sendMessage("Next Scheduled: " + SpoilEventScheduler.getInstance().getNextEventTimeString());
- + }
- +
- + @Override
- + public String[] getAdminCommandList()
- + {
- + return ADMIN_COMMANDS;
- + }
- +}
- diff --git a/aCis_gameserver/java/net/sf/l2j/gameserver/handler/voicedcommandhandlers/VoicedMenuSettings.java b/aCis_gameserver/java/net/sf/l2j/gameserver/handler/voicedcommandhandlers/VoicedMenuSettings.java
- index 7c1d236..649f20f 100644
- --- a/aCis_gameserver/java/net/sf/l2j/gameserver/handler/voicedcommandhandlers/VoicedMenuSettings.java
- +++ b/aCis_gameserver/java/net/sf/l2j/gameserver/handler/voicedcommandhandlers/VoicedMenuSettings.java
- @@ -15,6 +15,8 @@ import java.util.List;
- import java.util.TimeZone;
- import net.sf.l2j.gameserver.model.entity.Events.SoloBossEvent;
- +import net.sf.l2j.gameserver.events.spoil.SpoilEventManager;
- +import net.sf.l2j.gameserver.events.spoil.SpoilEventScheduler;
- import net.sf.l2j.gameserver.model.entity.Events.CaptureTheFlag.CTFEvent;
- import net.sf.l2j.gameserver.model.entity.Events.CaptureTheFlag.CTFManager;
- import net.sf.l2j.gameserver.model.entity.Events.DeathMatch.DMEvent;
- @@ -127,6 +129,7 @@ public class VoicedMenuSettings implements IVoicedCommandHandler
- html.replace("%tournament%", getEventStatus(TournamentManager.getInstance().getCurrentFights().size() > 0, TournamentManager.getInstance().getNextStartTime()));
- html.replace("%soloboss%", getEventStatus(SoloBossEvent.getInstance().getCurrentNpcId() != 0, SoloBossEvent.getInstance().getNextStartTime()));
- html.replace("%partyfarm%", getEventStatus(PartyFarm.is_started(), InitialPartyFarm.getInstance().getNextStartTime()));
- + html.replace("%spoilevent%", getEventStatus(SpoilEventManager.getInstance().isActive(), SpoilEventScheduler.getInstance().getNextStartTime()));
- String nextEventString = "No events";
- @@ -144,6 +147,8 @@ public class VoicedMenuSettings implements IVoicedCommandHandler
- nextEventString = "Solo Boss is now in progress!";
- else if (PartyFarm.is_started())
- nextEventString = "Party Farm is now in progress!";
- + else if (SpoilEventManager.getInstance().isActive())
- + nextEventString = "Spoil Event is now in progress!";
- else
- {
- long currentTime = System.currentTimeMillis();
- @@ -154,6 +159,7 @@ public class VoicedMenuSettings implements IVoicedCommandHandler
- long nextTour = TournamentManager.getInstance().getNextStartTime();
- long nextSolo = SoloBossEvent.getInstance().getNextStartTime();
- long nextFarm = InitialPartyFarm.getInstance().getNextStartTime();
- + long nextSpoil = SpoilEventScheduler.getInstance().getNextStartTime();
- List<EventInfo> events = new ArrayList<>();
- if (nextTvT > currentTime)
- @@ -170,6 +176,8 @@ public class VoicedMenuSettings implements IVoicedCommandHandler
- events.add(new EventInfo("Solo Boss will start in", nextSolo));
- if (nextFarm > currentTime)
- events.add(new EventInfo("Party Farm will start in", nextFarm));
- + if (nextSpoil > currentTime)
- + events.add(new EventInfo("Spoil Event will start in", nextSpoil));
- if (!events.isEmpty())
- {
- diff --git a/aCis_gameserver/java/net/sf/l2j/gameserver/model/zone/type/SpoilZone.java b/aCis_gameserver/java/net/sf/l2j/gameserver/model/zone/type/SpoilZone.java
- new file mode 100644
- index 0000000..f1836ce
- --- /dev/null
- +++ b/aCis_gameserver/java/net/sf/l2j/gameserver/model/zone/type/SpoilZone.java
- @@ -0,0 +1,60 @@
- +package net.sf.l2j.gameserver.model.zone.type;
- +
- +import net.sf.l2j.gameserver.enums.ZoneId;
- +import net.sf.l2j.gameserver.events.spoil.SpoilEventData;
- +import net.sf.l2j.gameserver.events.spoil.SpoilEventManager;
- +import net.sf.l2j.gameserver.model.actor.Creature;
- +import net.sf.l2j.gameserver.model.actor.Player;
- +import net.sf.l2j.gameserver.model.location.Location;
- +import net.sf.l2j.gameserver.model.zone.ZoneType;
- +
- +/**
- + * @author Jhon
- + */
- +public class SpoilZone extends ZoneType
- +{
- + public SpoilZone(int id)
- + {
- + super(id);
- + }
- +
- + @Override
- + protected void onEnter(Creature character)
- + {
- + // Only check rules if event is running
- + if (!SpoilEventManager.getInstance().isActive())
- + {
- + character.setInsideZone(ZoneId.SPOIL_AREA, true);
- + return;
- + }
- + if (character instanceof Player)
- + {
- + Player player = (Player) character;
- +
- + // 1. Check Class Restriction
- + if (!player.isGM() && !SpoilEventData.getInstance().isAllClassesAllowed() && !SpoilEventData.getInstance().getAllowedClassIds().contains(player.getClassId().getId()))
- + {
- + player.sendMessage("Only authorized classes are allowed in this area.");
- + teleportOut(player);
- + return;
- + }
- +
- + character.setInsideZone(ZoneId.SPOIL_AREA, true);
- + }
- + }
- +
- + @Override
- + protected void onExit(Creature character)
- + {
- + character.setInsideZone(ZoneId.SPOIL_AREA, false);
- + }
- +
- + private static void teleportOut(Player player)
- + {
- + Location loc = SpoilEventData.getInstance().getReturnLoc(); // This will error because SpoilEventData.getInstance() is static but method was instance context? No, static context is fine for getInstance().
- + if (loc != null)
- + player.teleportTo(loc.getX(), loc.getY(), loc.getZ(), 0);
- + else
- + player.teleportTo(81260, 148607, -3440, 0); // Default Giran fallback
- + }
- +}
- diff --git a/aCis_gameserver/java/net/sf/l2j/gameserver/network/clientpackets/DlgAnswer.java b/aCis_gameserver/java/net/sf/l2j/gameserver/network/clientpackets/DlgAnswer.java
- index 90e1548..ad1cb97 100644
- --- a/aCis_gameserver/java/net/sf/l2j/gameserver/network/clientpackets/DlgAnswer.java
- +++ b/aCis_gameserver/java/net/sf/l2j/gameserver/network/clientpackets/DlgAnswer.java
- @@ -13,6 +13,7 @@ import net.sf.l2j.gameserver.model.entity.Events.LastMan.LMEvent;
- import net.sf.l2j.gameserver.model.entity.Events.LastMan.LMManager;
- import net.sf.l2j.gameserver.model.entity.Events.TeamVsTeam.TvTEvent;
- import net.sf.l2j.gameserver.model.entity.Events.TeamVsTeam.TvTManager;
- +import net.sf.l2j.gameserver.events.spoil.SpoilEventManager;
- import net.sf.l2j.gameserver.model.entity.Tournament.TournamentManager;
- import net.sf.l2j.gameserver.model.entity.Tournament.Data.TournamentTeam;
- import net.sf.l2j.gameserver.model.group.Party;
- @@ -63,12 +64,10 @@ public final class DlgAnswer extends L2GameClientPacket
- LMEvent.onBypass("lm_event_participation", activeChar);
- else if (TvTManager.getInstance().register_tvt == true)
- TvTEvent.onBypass("tvt_event_participation", activeChar);
- + else if (SpoilEventManager.getInstance().isRegistrationOpen())
- + SpoilEventManager.getInstance().registerPlayer(activeChar);
- + else if (SoloBossEvent.getInstance().getCurrentNpcId() > 0)
- + SoloBossEvent.getInstance().teleportPlayerToEvent(activeChar);
- }
- else if (_messageId == SystemMessageId.S1.getId())
Advertisement
Add Comment
Please, Sign In to add comment