View difference between Paste ID: BzxVUCdt and UgZ1xan4
SHOW: | | - or go back to the newest paste.
1
diff --git a/aCis_gameserver/data/html/admin/eventos.htm b/aCis_gameserver/data/html/admin/eventos.htm
2
index 25a894c..10d1f7a 100644
3
--- a/aCis_gameserver/data/html/admin/eventos.htm
4
+++ b/aCis_gameserver/data/html/admin/eventos.htm
5
@@ -1,63 +1,103 @@
6
+      <tr>
7
+        <td align=left>
8
+          <font color=9FB6CD>Spoil Event:</font>
9
+        </td>
10
+        <td align=center><a action="bypass -h admin_spoilevent_start">
11
+            <font color=FFF000>Start</font>
12
+          </a></td>
13
+        <td align=center><a action="bypass -h admin_spoilevent_stop">
14
+            <font color=FF0000>Stop</font>
15
+          </a></td>
16
+        <td align=center><a action="bypass -h admin_spoilevent_status">Status</a></td>
17
+      </tr>
18
+      <tr></tr>
19
+ No newline at end of file
20
diff --git a/aCis_gameserver/data/xml/adminCommands.xml b/aCis_gameserver/data/xml/adminCommands.xml
21
index d3d3c1b..2573280 100644
22
--- a/aCis_gameserver/data/xml/adminCommands.xml
23
+++ b/aCis_gameserver/data/xml/adminCommands.xml
24
@@ -60,6 +60,12 @@
25
 	<aCar name="admin_soloboss_next" accessLevel="1" />
26
 	<aCar name="admin_soloboss_info" accessLevel="1" />
27
 
28
+	<!-- Spoil Event -->
29
+	<aCar name="admin_spoilevent" accessLevel="1" />
30
+	<aCar name="admin_spoilevent_start" accessLevel="1" />
31
+	<aCar name="admin_spoilevent_stop" accessLevel="1" />
32
+	<aCar name="admin_spoilevent_status" accessLevel="1" />
33
+
34
 	<!-- ADMIN TOURNAMENT -->
35
 	<aCar name="admin_tour" accessLevel="7"/>
36
 	<aCar name="admin_tour_reset" accessLevel="7"/>
37
diff --git a/aCis_gameserver/data/xml/events/SpoilEvent.xml b/aCis_gameserver/data/xml/events/SpoilEvent.xml
38
new file mode 100644
39
index 0000000..8b3d462
40
--- /dev/null
41
+++ b/aCis_gameserver/data/xml/events/SpoilEvent.xml
42
@@ -0,0 +1,30 @@
43
+<?xml version="1.0" encoding="UTF-8"?>
44
+<list enabled="true">
45
+	<event name="SpoilEvent" durationMinutes="60" blockAutofarm="true" allowAutoRescheduleAfterManualStop="false">
46
+		<schedule startTimes="20:00,23:00,02:00" />
47
+		
48
+		<!-- Allowed Classes Configuration -->
49
+		<!-- Use "all" to allow any class inside the zone. -->
50
+		<!-- Use specific Class IDs separated by commas (e.g., "53,54,117") to restrict access. -->
51
+		<!-- Default (if removed): 53,54,117 (Scavenger, Bounty Hunter, Fortune Seeker) -->
52
+		<classes allowed="53,54,117" />
53
+		
54
+		<!-- Spoil Mobs -->
55
+		<monster id="21084" respawnSeconds="10"> <!-- Example: Lilim Knight -->
56
+			<spawn x="82984" y="140104" z="-3552"/>
57
+			<spawn x="82136" y="140056" z="-3568"/>
58
+			<spawn x="82456" y="139704" z="-3568"/>
59
+		</monster>
60
+		
61
+		<!-- Gatekeepers -->
62
+		<gatekeeper id="30080"> <!-- Example: Monument -->
63
+			<spawn x="83320" y="148632" z="-3408"/>
64
+		</gatekeeper>
65
+		
66
+		<!-- Teleport Location (Where players go when accepting the popup) -->
67
+		<teleport x="82984" y="140104" z="-3552" />
68
+		
69
+		<!-- Return Location (if player is not allowed in zone) -->
70
+		<return x="82488" y="149064" z="-3464"/> <!-- Giran -->
71
+	</event>
72
+</list>
73
diff --git a/aCis_gameserver/data/xml/zones/SpoilZone.xml b/aCis_gameserver/data/xml/zones/SpoilZone.xml
74
new file mode 100644
75
index 0000000..4b72ce4
76
--- /dev/null
77
+++ b/aCis_gameserver/data/xml/zones/SpoilZone.xml
78
@@ -0,0 +1,17 @@
79
+<?xml version="1.0" encoding="UTF-8"?>
80
+<list>
81
+	<!-- Example Spoil Zone -->
82
+  <zone shape="Cylinder" type="SpoilZone" minZ="-3560" maxZ="-3560" rad="2500"> <!-- spoil1 -->
83
+    <node x="83220" y="140200"/>
84
+  </zone>
85
+
86
+</list>
87
diff --git a/aCis_gameserver/java/net/sf/l2j/gameserver/GameServer.java b/aCis_gameserver/java/net/sf/l2j/gameserver/GameServer.java
88
index 618d515..01e0c42 100644
89
--- a/aCis_gameserver/java/net/sf/l2j/gameserver/GameServer.java
90
+++ b/aCis_gameserver/java/net/sf/l2j/gameserver/GameServer.java
91
@@ -78,6 +78,8 @@ import net.sf.l2j.gameserver.data.xml.StaticObjectData;
92
 import net.sf.l2j.gameserver.data.xml.SummonItemData;
93
 import net.sf.l2j.gameserver.data.xml.TeleportLocationData;
94
 import net.sf.l2j.gameserver.data.xml.WalkerRouteData;
95
+import net.sf.l2j.gameserver.events.spoil.SpoilEventData;
96
+import net.sf.l2j.gameserver.events.spoil.SpoilEventScheduler;
97
 import net.sf.l2j.gameserver.geoengine.GeoEngine;
98
 import net.sf.l2j.gameserver.handler.AdminCommandHandler;
99
 import net.sf.l2j.gameserver.handler.BypassHandler;
100
@@ -313,6 +315,8 @@ public class GameServer
101
 		TvTManager.getInstance();
102
 		
103
 		SoloBossEvent.getInstance().init();
104
+		SpoilEventData.getInstance();
105
+		SpoilEventScheduler.getInstance().init();
106
 		
107
 		if (Config.CKM_ENABLED)
108
 			CharacterKillingManager.getInstance().init();
109
110
diff --git a/aCis_gameserver/java/net/sf/l2j/gameserver/enums/ZoneId.java b/aCis_gameserver/java/net/sf/l2j/gameserver/enums/ZoneId.java
111
index 9a5b012..7648da8 100644
112
--- a/aCis_gameserver/java/net/sf/l2j/gameserver/enums/ZoneId.java
113
+++ b/aCis_gameserver/java/net/sf/l2j/gameserver/enums/ZoneId.java
114
@@ -22,7 +22,9 @@ public enum ZoneId
115
 	NO_RESTART(17),
116
 	SCRIPT(18),
117
 	BOSS(19),
118
-	TOURNAMENT(20);
119
+	TOURNAMENT(20),
120
+	SPOIL_AREA(21),
121
+	FLAG_ZONE(22);
122
 	
123
 	private final int _id;
124
 	
125
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
126
new file mode 100644
127
index 0000000..f0e12a3
128
--- /dev/null
129
+++ b/aCis_gameserver/java/net/sf/l2j/gameserver/events/spoil/SpoilEventData.java
130
@@ -0,0 +1,225 @@
131
+package net.sf.l2j.gameserver.events.spoil;
132
+
133
+import java.nio.file.Path;
134
+import java.util.ArrayList;
135
+import java.util.List;
136
+
137
+import net.sf.l2j.commons.data.xml.IXmlReader;
138
+
139
+import net.sf.l2j.gameserver.model.location.Location;
140
+
141
+import org.w3c.dom.Document;
142
+import org.w3c.dom.NamedNodeMap;
143
+
144
+/**
145
+ * @author Jhon
146
+ */
147
+public class SpoilEventData implements IXmlReader
148
+{
149
+    private boolean _enabled = false;
150
+    private int _durationMinutes = 60;
151
+    private boolean _blockAutofarm = true;
152
+    private boolean _allowAutoReschedule = false;
153
+    private Location _returnLoc = null;
154
+    private Location _teleportLoc = null;
155
+    
156
+    private final List<String> _scheduleTimes = new ArrayList<>();
157
+    private final List<SpawnInfo> _monsterSpawns = new ArrayList<>();
158
+    private final List<SpawnInfo> _gkSpawns = new ArrayList<>();
159
+    // Default: Scavenger(53), Bounty Hunter(54), Fortune Seeker(117)
160
+    private final List<Integer> _allowedClassIds = new ArrayList<>();
161
+    private boolean _allClassesAllowed = false;
162
+    {
163
+        _allowedClassIds.add(53);
164
+        _allowedClassIds.add(54);
165
+        _allowedClassIds.add(117);
166
+    }
167
+    
168
+    public static class SpawnInfo
169
+    {
170
+        public final int npcId;
171
+        public final int respawnDelay;
172
+        public final List<Location> locations = new ArrayList<>();
173
+        
174
+        public SpawnInfo(int id, int respawn)
175
+        {
176
+            npcId = id;
177
+            respawnDelay = respawn;
178
+        }
179
+    }
180
+    
181
+    protected SpoilEventData()
182
+    {
183
+        load();
184
+    }
185
+    
186
+    @Override
187
+    public void load()
188
+    {
189
+        _scheduleTimes.clear();
190
+        _monsterSpawns.clear();
191
+        _gkSpawns.clear();
192
+        _allowedClassIds.clear();
193
+        _allClassesAllowed = false;
194
+        _allowedClassIds.add(53);
195
+        _allowedClassIds.add(54);
196
+        _allowedClassIds.add(117);
197
+        
198
+        parseFile("./data/xml/events/SpoilEvent.xml");
199
+        LOGGER.info("SpoilEventData: Loaded {} schedule times, {} monster types, {} GK types.", _scheduleTimes.size(), _monsterSpawns.size(), _gkSpawns.size());
200
+    }
201
+    
202
+    @Override
203
+    public void parseDocument(Document doc, Path path)
204
+    {
205
+        forEach(doc, "list", listNode ->
206
+        {
207
+            forEach(listNode, "event", eventNode ->
208
+            {
209
+                final NamedNodeMap attrs = eventNode.getAttributes();
210
+                _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.
211
+                // Actually user xml has <list enabled="true"> and inside <event ...>.
212
+                // Let's check the list enabled attribute.
213
+                if (!_enabled)
214
+                    return;
215
+                
216
+                _durationMinutes = parseInteger(attrs, "durationMinutes", 60);
217
+                _blockAutofarm = parseBoolean(attrs, "blockAutofarm", true);
218
+                _allowAutoReschedule = parseBoolean(attrs, "allowAutoRescheduleAfterManualStop", false);
219
+                
220
+                forEach(eventNode, "classes", classNode ->
221
+                {
222
+                    final String classes = parseString(classNode.getAttributes(), "allowed");
223
+                    if (classes != null && !classes.isEmpty())
224
+                    {
225
+                        if (classes.equalsIgnoreCase("all"))
226
+                        {
227
+                            _allClassesAllowed = true;
228
+                            _allowedClassIds.clear();
229
+                        }
230
+                        else
231
+                        {
232
+                            _allowedClassIds.clear(); // Override defaults if config exists
233
+                            for (String id : classes.split(","))
234
+                                _allowedClassIds.add(Integer.parseInt(id.trim()));
235
+                        }
236
+                    }
237
+                });
238
+                
239
+                forEach(eventNode, "schedule", scheduleNode ->
240
+                {
241
+                    final String times = parseString(scheduleNode.getAttributes(), "startTimes");
242
+                    if (times != null && !times.isEmpty())
243
+                    {
244
+                        for (String time : times.split(","))
245
+                            _scheduleTimes.add(time.trim());
246
+                    }
247
+                });
248
+                
249
+                forEach(eventNode, "monster", mobNode ->
250
+                {
251
+                    final NamedNodeMap mobAttrs = mobNode.getAttributes();
252
+                    final int id = parseInteger(mobAttrs, "id");
253
+                    final int respawn = parseInteger(mobAttrs, "respawnSeconds", 60);
254
+                    
255
+                    final SpawnInfo info = new SpawnInfo(id, respawn);
256
+                    
257
+                    forEach(mobNode, "spawn", spawnNode ->
258
+                    {
259
+                        info.locations.add(parseLocation(spawnNode));
260
+                    });
261
+                    _monsterSpawns.add(info);
262
+                });
263
+                
264
+                forEach(eventNode, "gatekeeper", gkNode ->
265
+                {
266
+                    final NamedNodeMap gkAttrs = gkNode.getAttributes();
267
+                    final int id = parseInteger(gkAttrs, "id");
268
+                    
269
+                    final SpawnInfo info = new SpawnInfo(id, 0); // GK usually doesn't respawn or instant? Let's say 0 (standard or non-respawn)
270
+                    
271
+                    forEach(gkNode, "spawn", spawnNode ->
272
+                    {
273
+                        info.locations.add(parseLocation(spawnNode));
274
+                    });
275
+                    _gkSpawns.add(info);
276
+                });
277
+                
278
+                forEach(eventNode, "return", returnNode ->
279
+                {
280
+                    _returnLoc = parseLocation(returnNode);
281
+                });
282
+                
283
+                forEach(eventNode, "teleport", arg ->
284
+                {
285
+                    _teleportLoc = parseLocation(arg);
286
+                });
287
+            });
288
+        });
289
+    }
290
+    
291
+    public boolean isEnabled()
292
+    {
293
+        return _enabled;
294
+    }
295
+    
296
+    public int getDurationMinutes()
297
+    {
298
+        return _durationMinutes;
299
+    }
300
+    
301
+    public boolean isBlockAutofarm()
302
+    {
303
+        return _blockAutofarm;
304
+    }
305
+    
306
+    public boolean isAllowAutoReschedule()
307
+    {
308
+        return _allowAutoReschedule;
309
+    }
310
+    
311
+    public Location getReturnLoc()
312
+    {
313
+        return _returnLoc;
314
+    }
315
+    
316
+    public Location getTeleportLoc()
317
+    {
318
+        return _teleportLoc;
319
+    }
320
+    
321
+    public List<String> getScheduleTimes()
322
+    {
323
+        return _scheduleTimes;
324
+    }
325
+    
326
+    public List<SpawnInfo> getMonsterSpawns()
327
+    {
328
+        return _monsterSpawns;
329
+    }
330
+    
331
+    public List<SpawnInfo> getGkSpawns()
332
+    {
333
+        return _gkSpawns;
334
+    }
335
+    
336
+    public List<Integer> getAllowedClassIds()
337
+    {
338
+        return _allowedClassIds;
339
+    }
340
+    
341
+    public boolean isAllClassesAllowed()
342
+    {
343
+        return _allClassesAllowed;
344
+    }
345
+    
346
+    public static SpoilEventData getInstance()
347
+    {
348
+        return SingletonHolder.INSTANCE;
349
+    }
350
+    
351
+    private static class SingletonHolder
352
+    {
353
+        protected static final SpoilEventData INSTANCE = new SpoilEventData();
354
+    }
355
+}
356
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
357
new file mode 100644
358
index 0000000..ca0d13f
359
--- /dev/null
360
+++ b/aCis_gameserver/java/net/sf/l2j/gameserver/events/spoil/SpoilEventManager.java
361
@@ -0,0 +1,312 @@
362
+package net.sf.l2j.gameserver.events.spoil;
363
+
364
+import java.util.concurrent.ScheduledFuture;
365
+
366
+import net.sf.l2j.commons.concurrent.ThreadPool;
367
+import net.sf.l2j.commons.logging.CLogger;
368
+
369
+import net.sf.l2j.gameserver.model.World;
370
+import net.sf.l2j.gameserver.data.manager.ZoneManager;
371
+import net.sf.l2j.gameserver.model.actor.Player;
372
+import net.sf.l2j.gameserver.model.location.Location;
373
+import net.sf.l2j.gameserver.model.zone.type.SpoilZone;
374
+import net.sf.l2j.gameserver.network.serverpackets.ConfirmDlg;
375
+import net.sf.l2j.gameserver.network.serverpackets.ExShowScreenMessage;
376
+import net.sf.l2j.gameserver.network.serverpackets.ExShowScreenMessage.SMPOS;
377
+import net.sf.l2j.gameserver.network.SystemMessageId;
378
+import net.sf.l2j.gameserver.model.entity.Events.CaptureTheFlag.CTFEvent;
379
+import net.sf.l2j.gameserver.model.entity.Events.DeathMatch.DMEvent;
380
+import net.sf.l2j.gameserver.model.entity.Events.TeamVsTeam.TvTEvent;
381
+
382
+/**
383
+ * @author Jhon
384
+ */
385
+public class SpoilEventManager
386
+{
387
+    private static final CLogger LOGGER = new CLogger(SpoilEventManager.class.getName());
388
+    
389
+    private boolean _active = false;
390
+    private boolean _registrationOpen = false;
391
+    private ScheduledFuture<?> _endTask = null;
392
+    private ScheduledFuture<?> _countdownTask = null;
393
+    private ScheduledFuture<?> _registrationTask = null;
394
+    private long _endTime = 0;
395
+    
396
+    protected SpoilEventManager()
397
+    {
398
+    }
399
+    
400
+    public synchronized void startEvent()
401
+    {
402
+        if (_active)
403
+        {
404
+            LOGGER.warn("SpoilEvent: Attempt to start event but it is already active.");
405
+            return;
406
+        }
407
+        
408
+        if (!SpoilEventData.getInstance().isEnabled())
409
+        {
410
+            LOGGER.warn("SpoilEvent: Attempt to start event but it is disabled in config.");
411
+            return;
412
+        }
413
+        
414
+        _active = true;
415
+        
416
+        // Spawn NPCs
417
+        SpoilEventSpawner.getInstance().spawnAll();
418
+        
419
+        // Announcement
420
+        World.announceToOnlinePlayers("[Spoil Event]: The event has started!");
421
+        World.announceToOnlinePlayers("[Spoil Event]: Duration: " + SpoilEventData.getInstance().getDurationMinutes() + " minute(s)!");
422
+        World.toAllOnlinePlayers(new ExShowScreenMessage("Spoil Event Started! Teleport Now!", 5000, SMPOS.TOP_CENTER, true));
423
+        
424
+        // Schedule end
425
+        final long durationMillis = SpoilEventData.getInstance().getDurationMinutes() * 60 * 1000L;
426
+        _endTime = System.currentTimeMillis() + durationMillis;
427
+        
428
+        if (_endTask != null)
429
+            _endTask.cancel(false);
430
+        
431
+        _endTask = ThreadPool.schedule(() -> stopEvent(false), durationMillis);
432
+        
433
+        // Schedule countdowns
434
+        scheduleCountdown(durationMillis);
435
+        
436
+        // Open Registration/Popup for 30 seconds
437
+        _registrationOpen = true;
438
+        _registrationTask = ThreadPool.schedule(() -> _registrationOpen = false, 30000);
439
+        
440
+        // Broadcast Popup to eligible players
441
+        for (Player player : World.getInstance().getPlayers())
442
+        {
443
+            if (player == null || player.isOnline() == false)
444
+                continue;
445
+            
446
+            // Skip if in other events or busy
447
+            if (player.isInOlympiadMode() || player.isAlikeDead() || player.isTeleporting() || player.isInObserverMode() || player.isInStoreMode() || TvTEvent.isPlayerParticipant(player.getObjectId()) || CTFEvent.isPlayerParticipant(player.getObjectId()) || DMEvent.isPlayerParticipant(player.getObjectId()))
448
+                continue;
449
+            
450
+            // Skip unauthorized classes if restriction is on
451
+            if (!SpoilEventData.getInstance().isAllClassesAllowed() && !SpoilEventData.getInstance().getAllowedClassIds().contains(player.getClassId().getId()) && !player.isGM())
452
+                continue;
453
+            
454
+            ConfirmDlg confirm = new ConfirmDlg(SystemMessageId.S1.getId());
455
+            confirm.addString("Spoil Event started! Do you wish to join?");
456
+            confirm.addTime(30000);
457
+            confirm.addRequesterId(player.getObjectId());
458
+            player.sendPacket(confirm);
459
+        }
460
+        
461
+        // Validate players already inside (anti-camping non-spoilers)
462
+        for (SpoilZone zone : ZoneManager.getInstance().getAllZones(SpoilZone.class))
463
+        {
464
+            for (Player player : zone.getKnownTypeInside(Player.class))
465
+            {
466
+                if (player != null)
467
+                {
468
+                    zone.revalidateInZone(player); // Trigger onEnter check again or manual check?
469
+                    // SpoilZone.onEnter checks logic. But setInsideZone might stay true.
470
+                    // Let's call a manual check method to be safe, or just reuse logic.
471
+                    // Simpler: Check rules manually here or call zone.onEnter(player) effectively?
472
+                    // onEnter guards with 'isActive' which is now true. So logic works.
473
+                    // But onEnter also checks if player is already inside internal list?
474
+                    // ZoneType: onEnter often checks if (!isInsideZone(object)).
475
+                    // So we might need to manually check requirements here.
476
+                    
477
+                    if (!player.isGM() && player.getClassId().getId() != 53 && // Scavenger
478
+                        player.getClassId().getId() != 54 && // Bounty Hunter
479
+                        player.getClassId().getId() != 117) // Fortune Seeker
480
+                    {
481
+                        player.sendMessage("Spoil Event started! Non-spoilers are moved out.");
482
+                        Location loc = SpoilEventData.getInstance().getReturnLoc();
483
+                        if (loc != null)
484
+                            player.teleportTo(loc.getX(), loc.getY(), loc.getZ(), 0);
485
+                        else
486
+                            player.teleportTo(81260, 148607, -3440, 0);
487
+                    }
488
+                }
489
+            }
490
+        }
491
+    }
492
+    
493
+    public synchronized void stopEvent(boolean manual)
494
+    {
495
+        if (!_active)
496
+            return;
497
+        
498
+        _active = false;
499
+        _endTime = 0;
500
+        
501
+        // Cancel tasks
502
+        if (_endTask != null)
503
+        {
504
+            _endTask.cancel(false);
505
+            _endTask = null;
506
+        }
507
+        
508
+        if (_registrationTask != null)
509
+        {
510
+            _registrationTask.cancel(false);
511
+            _registrationTask = null;
512
+            _registrationOpen = false;
513
+        }
514
+        
515
+        if (_countdownTask != null)
516
+        {
517
+            _countdownTask.cancel(false);
518
+            _countdownTask = null;
519
+        }
520
+        
521
+        // Despawn NPCs
522
+        SpoilEventSpawner.getInstance().despawnAll();
523
+        
524
+        // Teleport players out of the zone
525
+        for (SpoilZone zone : ZoneManager.getInstance().getAllZones(SpoilZone.class))
526
+        {
527
+            for (Player player : zone.getKnownTypeInside(Player.class))
528
+            {
529
+                if (player != null && !player.isGM()) // Optionally keep GMs?
530
+                {
531
+                    Location loc = SpoilEventData.getInstance().getReturnLoc();
532
+                    if (loc != null)
533
+                        player.teleportTo(loc.getX(), loc.getY(), loc.getZ(), 0);
534
+                    else
535
+                        player.teleportTo(81260, 148607, -3440, 0);
536
+                    
537
+                    player.sendMessage("Spoil Event has ended. You have been teleported out.");
538
+                }
539
+            }
540
+        }
541
+        
542
+        // Announcement
543
+        World.announceToOnlinePlayers("[Spoil Event]: The event has finished!");
544
+        World.toAllOnlinePlayers(new ExShowScreenMessage("Spoil Event Finished!", 5000, SMPOS.TOP_CENTER, true));
545
+        
546
+        // Reschedule next
547
+        if (!manual || SpoilEventData.getInstance().isAllowAutoReschedule())
548
+        {
549
+            SpoilEventScheduler.getInstance().scheduleNext();
550
+        }
551
+    }
552
+    
553
+    private void scheduleCountdown(long remainingMillis)
554
+    {
555
+        // Simple recursive countdown scheduler or just scheduling multiple tasks?
556
+        // To be safe and clean, we can schedule unique tasks for each checkpoint.
557
+        // Checkpoints: 30m, 15m, 10m, 5m, 3m, 2m, 1m, 30s, 15s, 10s, 5s, 3s, 2s, 1s
558
+        
559
+        int[] checks =
560
+        {
561
+            30 * 60,
562
+            15 * 60,
563
+            10 * 60,
564
+            5 * 60,
565
+            3 * 60,
566
+            2 * 60,
567
+            60,
568
+            30,
569
+            15,
570
+            10,
571
+            5,
572
+            3,
573
+            2,
574
+            1
575
+        };
576
+        
577
+        for (int sec : checks)
578
+        {
579
+            long notifyMillis = remainingMillis - (sec * 1000L);
580
+            if (notifyMillis > 0)
581
+            {
582
+                ThreadPool.schedule(() ->
583
+                {
584
+                    if (_active)
585
+                    {
586
+                        String timeMsg = (sec >= 60) ? (sec / 60) + " minute(s)" : sec + " second(s)";
587
+                        World.announceToOnlinePlayers("[Spoil Event]: " + timeMsg + " remaining!");
588
+                    }
589
+                }, notifyMillis);
590
+            }
591
+        }
592
+    }
593
+    
594
+    public boolean isActive()
595
+    {
596
+        return _active;
597
+    }
598
+    
599
+    public long getEndTime()
600
+    {
601
+        return _endTime;
602
+    }
603
+    
604
+    public static SpoilEventManager getInstance()
605
+    {
606
+        return SingletonHolder.INSTANCE;
607
+    }
608
+    
609
+    public boolean isRegistrationOpen()
610
+    {
611
+        return _registrationOpen;
612
+    }
613
+    
614
+    public void registerPlayer(Player player)
615
+    {
616
+        if (!_active || player == null)
617
+            return;
618
+        
619
+        // Re-check conditions in case they changed since popup
620
+        if (player.isInOlympiadMode() || player.isAlikeDead() || player.isTeleporting() || player.isInObserverMode() || player.isInStoreMode())
621
+        {
622
+            player.sendMessage("You cannot join the event in your current state.");
623
+            return;
624
+        }
625
+        
626
+        if (!SpoilEventData.getInstance().isAllClassesAllowed() && !SpoilEventData.getInstance().getAllowedClassIds().contains(player.getClassId().getId()) && !player.isGM())
627
+        {
628
+            player.sendMessage("Your class is not allowed in this event.");
629
+            return;
630
+        }
631
+        
632
+        // Teleport to Gatekeeper spawn or random spawn?
633
+        // Typically events teleport to a starting area.
634
+        // Let's use the first GK spawn location if available, or just the first monster spawn area?
635
+        // Actually, users usually walk in. If we teleport, we should teleport to a safe spot inside.
636
+        // Let's us the RETURN location? No, that's for exit.
637
+        // Let's pick the first Monster or GK location as a "drop in" point.
638
+        // Ideally we'd have a specific "enter" location in XML, but we don't.
639
+        // We will peek at monster spawns and pick one.
640
+        
641
+        // Use configured teleport location
642
+        Location targetLoc = SpoilEventData.getInstance().getTeleportLoc();
643
+        
644
+        // Fallback if not configured (try monster spawns -> GK spawns)
645
+        if (targetLoc == null)
646
+        {
647
+            if (!SpoilEventData.getInstance().getMonsterSpawns().isEmpty())
648
+            {
649
+                Location l = SpoilEventData.getInstance().getMonsterSpawns().get(0).locations.get(0);
650
+                targetLoc = new Location(l.getX(), l.getY(), l.getZ());
651
+            }
652
+            else if (!SpoilEventData.getInstance().getGkSpawns().isEmpty())
653
+            {
654
+                Location l = SpoilEventData.getInstance().getGkSpawns().get(0).locations.get(0);
655
+                targetLoc = new Location(l.getX(), l.getY(), l.getZ());
656
+            }
657
+        }
658
+        
659
+        if (targetLoc != null)
660
+        {
661
+            player.teleportTo(targetLoc.getX(), targetLoc.getY(), targetLoc.getZ(), 0);
662
+        }
663
+        else
664
+        {
665
+            player.sendMessage("No spawn location found for event.");
666
+        }
667
+    }
668
+    
669
+    private static class SingletonHolder
670
+    {
671
+        protected static final SpoilEventManager INSTANCE = new SpoilEventManager();
672
+    }
673
+}
674
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
675
new file mode 100644
676
index 0000000..7e60483
677
--- /dev/null
678
+++ b/aCis_gameserver/java/net/sf/l2j/gameserver/events/spoil/SpoilEventScheduler.java
679
@@ -0,0 +1,119 @@
680
+package net.sf.l2j.gameserver.events.spoil;
681
+
682
+import java.util.Calendar;
683
+import java.util.Collections;
684
+import java.util.List;
685
+
686
+import net.sf.l2j.commons.concurrent.ThreadPool;
687
+import net.sf.l2j.commons.logging.CLogger;
688
+
689
+/**
690
+ * @author Jhon
691
+ */
692
+public class SpoilEventScheduler
693
+{
694
+    private static final CLogger LOGGER = new CLogger(SpoilEventScheduler.class.getName());
695
+    
696
+    private String _nextEventTime = "N/A";
697
+    private long _nextEventMillis = 0;
698
+    
699
+    protected SpoilEventScheduler()
700
+    {
701
+    }
702
+    
703
+    public void init()
704
+    {
705
+        if (!SpoilEventData.getInstance().isEnabled())
706
+            return;
707
+        
708
+        scheduleNext();
709
+    }
710
+    
711
+    public void scheduleNext()
712
+    {
713
+        final List<String> times = SpoilEventData.getInstance().getScheduleTimes();
714
+        if (times.isEmpty())
715
+        {
716
+            _nextEventTime = "None";
717
+            return;
718
+        }
719
+        
720
+        long currentTime = System.currentTimeMillis();
721
+        long nextTime = 0;
722
+        String nextTimeString = "";
723
+        
724
+        // Sort times to ensure correct order if XML is mixed
725
+        Collections.sort(times);
726
+        
727
+        Calendar test = Calendar.getInstance();
728
+        test.setLenient(true);
729
+        
730
+        // Find closest future time
731
+        boolean found = false;
732
+        for (String t : times)
733
+        {
734
+            String[] split = t.split(":");
735
+            if (split.length != 2)
736
+                continue;
737
+            
738
+            test.setTimeInMillis(currentTime);
739
+            test.set(Calendar.HOUR_OF_DAY, Integer.parseInt(split[0]));
740
+            test.set(Calendar.MINUTE, Integer.parseInt(split[1]));
741
+            test.set(Calendar.SECOND, 0);
742
+            test.set(Calendar.MILLISECOND, 0);
743
+            
744
+            if (test.getTimeInMillis() > currentTime)
745
+            {
746
+                nextTime = test.getTimeInMillis();
747
+                nextTimeString = t;
748
+                found = true;
749
+                break;
750
+            }
751
+        }
752
+        
753
+        // If no time found for today, pick the first time for tomorrow
754
+        if (!found)
755
+        {
756
+            String t = times.get(0);
757
+            String[] split = t.split(":");
758
+            
759
+            test.setTimeInMillis(currentTime);
760
+            test.add(Calendar.DAY_OF_YEAR, 1); // Tomorrow
761
+            test.set(Calendar.HOUR_OF_DAY, Integer.parseInt(split[0]));
762
+            test.set(Calendar.MINUTE, Integer.parseInt(split[1]));
763
+            test.set(Calendar.SECOND, 0);
764
+            test.set(Calendar.MILLISECOND, 0);
765
+            
766
+            nextTime = test.getTimeInMillis();
767
+            nextTimeString = t;
768
+        }
769
+        
770
+        _nextEventTime = nextTimeString;
771
+        _nextEventMillis = nextTime;
772
+        
773
+        final long delay = nextTime - currentTime;
774
+        LOGGER.info("SpoilEvent: Next event scheduled at {} (in {} ms).", _nextEventTime, delay);
775
+        
776
+        ThreadPool.schedule(SpoilEventManager.getInstance()::startEvent, delay);
777
+    }
778
+    
779
+    public String getNextEventTimeString()
780
+    {
781
+        return _nextEventTime;
782
+    }
783
+    
784
+    public long getNextStartTime()
785
+    {
786
+        return _nextEventMillis;
787
+    }
788
+    
789
+    public static SpoilEventScheduler getInstance()
790
+    {
791
+        return SingletonHolder.INSTANCE;
792
+    }
793
+    
794
+    private static class SingletonHolder
795
+    {
796
+        protected static final SpoilEventScheduler INSTANCE = new SpoilEventScheduler();
797
+    }
798
+}
799
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
800
new file mode 100644
801
index 0000000..fe665c7
802
--- /dev/null
803
+++ b/aCis_gameserver/java/net/sf/l2j/gameserver/events/spoil/SpoilEventSpawner.java
804
@@ -0,0 +1,111 @@
805
+package net.sf.l2j.gameserver.events.spoil;
806
+
807
+import java.util.ArrayList;
808
+import java.util.List;
809
+
810
+import net.sf.l2j.commons.logging.CLogger;
811
+
812
+import net.sf.l2j.gameserver.data.sql.SpawnTable;
813
+import net.sf.l2j.gameserver.data.xml.NpcData;
814
+import net.sf.l2j.gameserver.events.spoil.SpoilEventData.SpawnInfo;
815
+import net.sf.l2j.gameserver.model.actor.Npc;
816
+import net.sf.l2j.gameserver.model.actor.template.NpcTemplate;
817
+import net.sf.l2j.gameserver.model.location.Location;
818
+import net.sf.l2j.gameserver.model.spawn.L2Spawn;
819
+
820
+/**
821
+ * @author Jhon
822
+ */
823
+public class SpoilEventSpawner
824
+{
825
+    private static final CLogger LOGGER = new CLogger(SpoilEventSpawner.class.getName());
826
+    
827
+    private final List<L2Spawn> _activeSpawns = new ArrayList<>();
828
+    
829
+    protected SpoilEventSpawner()
830
+    {
831
+    }
832
+    
833
+    /**
834
+     * Spawns all configured monsters and gatekeepers.
835
+     */
836
+    public void spawnAll()
837
+    {
838
+        spawnGroup(SpoilEventData.getInstance().getMonsterSpawns());
839
+        spawnGroup(SpoilEventData.getInstance().getGkSpawns());
840
+        
841
+        LOGGER.info("SpoilEvent: Spawned {} NPCs.", _activeSpawns.size());
842
+    }
843
+    
844
+    private void spawnGroup(List<SpawnInfo> group)
845
+    {
846
+        for (SpawnInfo info : group)
847
+        {
848
+            final NpcTemplate template = NpcData.getInstance().getTemplate(info.npcId);
849
+            if (template == null)
850
+            {
851
+                LOGGER.warn("SpoilEvent: Invalid NPC ID {}, skipping.", info.npcId);
852
+                continue;
853
+            }
854
+            
855
+            for (Location loc : info.locations)
856
+            {
857
+                try
858
+                {
859
+                    final L2Spawn spawn = new L2Spawn(template);
860
+                    spawn.setLoc(loc.getX(), loc.getY(), loc.getZ(), 0);
861
+                    spawn.setRespawnDelay(info.respawnDelay);
862
+                    
863
+                    SpawnTable.getInstance().addSpawn(spawn, false);
864
+                    spawn.doSpawn(false);
865
+                    
866
+                    if (info.respawnDelay > 0)
867
+                        spawn.setRespawnState(true);
868
+                    else
869
+                        spawn.setRespawnState(false);
870
+                    
871
+                    _activeSpawns.add(spawn);
872
+                }
873
+                catch (Exception e)
874
+                {
875
+                    LOGGER.error("SpoilEvent: Failed to spawn NPC {}.", e, info.npcId);
876
+                }
877
+            }
878
+        }
879
+    }
880
+    
881
+    /**
882
+     * Despawns all event NPCs and clears the list.
883
+     */
884
+    public void despawnAll()
885
+    {
886
+        if (_activeSpawns.isEmpty())
887
+            return;
888
+        
889
+        for (L2Spawn spawn : _activeSpawns)
890
+        {
891
+            if (spawn == null)
892
+                continue;
893
+            
894
+            spawn.setRespawnState(false);
895
+            SpawnTable.getInstance().deleteSpawn(spawn, false);
896
+            
897
+            final Npc npc = spawn.getNpc();
898
+            if (npc != null)
899
+                npc.deleteMe();
900
+        }
901
+        
902
+        LOGGER.info("SpoilEvent: Despawned {} NPCs.", _activeSpawns.size());
903
+        _activeSpawns.clear();
904
+    }
905
+    
906
+    public static SpoilEventSpawner getInstance()
907
+    {
908
+        return SingletonHolder.INSTANCE;
909
+    }
910
+    
911
+    private static class SingletonHolder
912
+    {
913
+        protected static final SpoilEventSpawner INSTANCE = new SpoilEventSpawner();
914
+    }
915
+}
916
diff --git a/aCis_gameserver/java/net/sf/l2j/gameserver/handler/AdminCommandHandler.java b/aCis_gameserver/java/net/sf/l2j/gameserver/handler/AdminCommandHandler.java
917
index 7d373be..d5b9ba4 100644
918
--- a/aCis_gameserver/java/net/sf/l2j/gameserver/handler/AdminCommandHandler.java
919
+++ b/aCis_gameserver/java/net/sf/l2j/gameserver/handler/AdminCommandHandler.java
920
@@ -58,6 +58,7 @@ import net.sf.l2j.gameserver.handler.admincommandhandlers.AdminSiege;
921
 import net.sf.l2j.gameserver.handler.admincommandhandlers.AdminSkill;
922
 import net.sf.l2j.gameserver.handler.admincommandhandlers.AdminSoloBoss;
923
 import net.sf.l2j.gameserver.handler.admincommandhandlers.AdminSpawn;
924
+import net.sf.l2j.gameserver.handler.admincommandhandlers.AdminSpoilEvent;
925
 import net.sf.l2j.gameserver.handler.admincommandhandlers.AdminTarget;
926
 import net.sf.l2j.gameserver.handler.admincommandhandlers.AdminTeleport;
927
 import net.sf.l2j.gameserver.handler.admincommandhandlers.AdminTvTEvent;
928
@@ -133,6 +134,7 @@ public class AdminCommandHandler
929
 		registerHandler(new AdminZoneCreate());
930
 		registerHandler(new AdminClanFull());
931
 		registerHandler(new AdminSetHero());
932
+		registerHandler(new AdminSpoilEvent());
933
 	}
934
 	
935
 	public void registerHandler(IAdminCommandHandler handler)
936
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
937
new file mode 100644
938
index 0000000..a53e75a
939
--- /dev/null
940
+++ b/aCis_gameserver/java/net/sf/l2j/gameserver/handler/admincommandhandlers/AdminSpoilEvent.java
941
@@ -0,0 +1,72 @@
942
+package net.sf.l2j.gameserver.handler.admincommandhandlers;
943
+
944
+import net.sf.l2j.gameserver.events.spoil.SpoilEventManager;
945
+import net.sf.l2j.gameserver.events.spoil.SpoilEventScheduler;
946
+import net.sf.l2j.gameserver.handler.IAdminCommandHandler;
947
+import net.sf.l2j.gameserver.model.actor.Player;
948
+
949
+/**
950
+ * @author Jhon
951
+ */
952
+public class AdminSpoilEvent implements IAdminCommandHandler
953
+{
954
+    private static final String[] ADMIN_COMMANDS =
955
+    {
956
+        "admin_spoilevent",
957
+        "admin_spoilevent_start",
958
+        "admin_spoilevent_stop",
959
+        "admin_spoilevent_status"
960
+    };
961
+    
962
+    @Override
963
+    public boolean useAdminCommand(String command, Player activeChar)
964
+    {
965
+        if (activeChar == null)
966
+            return false;
967
+        
968
+        if (command.equals("admin_spoilevent") || command.equals("admin_spoilevent_status"))
969
+        {
970
+            showStatus(activeChar);
971
+        }
972
+        else if (command.equals("admin_spoilevent_start"))
973
+        {
974
+            if (SpoilEventManager.getInstance().isActive())
975
+                activeChar.sendMessage("Spoil Event is already active.");
976
+            else
977
+            {
978
+                activeChar.sendMessage("Starting Spoil Event manually...");
979
+                SpoilEventManager.getInstance().startEvent();
980
+            }
981
+        }
982
+        else if (command.equals("admin_spoilevent_stop"))
983
+        {
984
+            if (!SpoilEventManager.getInstance().isActive())
985
+                activeChar.sendMessage("Spoil Event is not active.");
986
+            else
987
+            {
988
+                activeChar.sendMessage("Stopping Spoil Event manually...");
989
+                SpoilEventManager.getInstance().stopEvent(true);
990
+            }
991
+        }
992
+        
993
+        return true;
994
+    }
995
+    
996
+    private static void showStatus(Player activeChar)
997
+    {
998
+        boolean active = SpoilEventManager.getInstance().isActive();
999
+        activeChar.sendMessage("Spoil Event Status: " + (active ? "ACTIVE" : "INACTIVE"));
1000
+        if (active)
1001
+        {
1002
+            long remaining = SpoilEventManager.getInstance().getEndTime() - System.currentTimeMillis();
1003
+            activeChar.sendMessage("Time Remaining: " + (remaining / 60000) + " minutes.");
1004
+        }
1005
+        activeChar.sendMessage("Next Scheduled: " + SpoilEventScheduler.getInstance().getNextEventTimeString());
1006
+    }
1007
+    
1008
+    @Override
1009
+    public String[] getAdminCommandList()
1010
+    {
1011
+        return ADMIN_COMMANDS;
1012
+    }
1013
+}
1014
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
1015
index 7c1d236..649f20f 100644
1016
--- a/aCis_gameserver/java/net/sf/l2j/gameserver/handler/voicedcommandhandlers/VoicedMenuSettings.java
1017
+++ b/aCis_gameserver/java/net/sf/l2j/gameserver/handler/voicedcommandhandlers/VoicedMenuSettings.java
1018
@@ -15,6 +15,8 @@ import java.util.List;
1019
 import java.util.TimeZone;
1020
 
1021
 import net.sf.l2j.gameserver.model.entity.Events.SoloBossEvent;
1022
+import net.sf.l2j.gameserver.events.spoil.SpoilEventManager;
1023
+import net.sf.l2j.gameserver.events.spoil.SpoilEventScheduler;
1024
 import net.sf.l2j.gameserver.model.entity.Events.CaptureTheFlag.CTFEvent;
1025
 import net.sf.l2j.gameserver.model.entity.Events.CaptureTheFlag.CTFManager;
1026
 import net.sf.l2j.gameserver.model.entity.Events.DeathMatch.DMEvent;
1027
@@ -127,6 +129,7 @@ public class VoicedMenuSettings implements IVoicedCommandHandler
1028
 		html.replace("%tournament%", getEventStatus(TournamentManager.getInstance().getCurrentFights().size() > 0, TournamentManager.getInstance().getNextStartTime()));
1029
 		html.replace("%soloboss%", getEventStatus(SoloBossEvent.getInstance().getCurrentNpcId() != 0, SoloBossEvent.getInstance().getNextStartTime()));
1030
 		html.replace("%partyfarm%", getEventStatus(PartyFarm.is_started(), InitialPartyFarm.getInstance().getNextStartTime()));
1031
+		html.replace("%spoilevent%", getEventStatus(SpoilEventManager.getInstance().isActive(), SpoilEventScheduler.getInstance().getNextStartTime()));
1032
 		
1033
 		String nextEventString = "No events";
1034
 		
1035
@@ -144,6 +147,8 @@ public class VoicedMenuSettings implements IVoicedCommandHandler
1036
 			nextEventString = "Solo Boss is now in progress!";
1037
 		else if (PartyFarm.is_started())
1038
 			nextEventString = "Party Farm is now in progress!";
1039
+		else if (SpoilEventManager.getInstance().isActive())
1040
+			nextEventString = "Spoil Event is now in progress!";
1041
 		else
1042
 		{
1043
 			long currentTime = System.currentTimeMillis();
1044
@@ -154,6 +159,7 @@ public class VoicedMenuSettings implements IVoicedCommandHandler
1045
 			long nextTour = TournamentManager.getInstance().getNextStartTime();
1046
 			long nextSolo = SoloBossEvent.getInstance().getNextStartTime();
1047
 			long nextFarm = InitialPartyFarm.getInstance().getNextStartTime();
1048
+			long nextSpoil = SpoilEventScheduler.getInstance().getNextStartTime();
1049
 			
1050
 			List<EventInfo> events = new ArrayList<>();
1051
 			if (nextTvT > currentTime)
1052
@@ -170,6 +176,8 @@ public class VoicedMenuSettings implements IVoicedCommandHandler
1053
 				events.add(new EventInfo("Solo Boss will start in", nextSolo));
1054
 			if (nextFarm > currentTime)
1055
 				events.add(new EventInfo("Party Farm will start in", nextFarm));
1056
+			if (nextSpoil > currentTime)
1057
+				events.add(new EventInfo("Spoil Event will start in", nextSpoil));
1058
 			
1059
 			if (!events.isEmpty())
1060
 			{
1061
1062
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
1063
new file mode 100644
1064
index 0000000..f1836ce
1065
--- /dev/null
1066
+++ b/aCis_gameserver/java/net/sf/l2j/gameserver/model/zone/type/SpoilZone.java
1067
@@ -0,0 +1,60 @@
1068
+package net.sf.l2j.gameserver.model.zone.type;
1069
+
1070
+import net.sf.l2j.gameserver.enums.ZoneId;
1071
+import net.sf.l2j.gameserver.events.spoil.SpoilEventData;
1072
+import net.sf.l2j.gameserver.events.spoil.SpoilEventManager;
1073
+import net.sf.l2j.gameserver.model.actor.Creature;
1074
+import net.sf.l2j.gameserver.model.actor.Player;
1075
+import net.sf.l2j.gameserver.model.location.Location;
1076
+import net.sf.l2j.gameserver.model.zone.ZoneType;
1077
+
1078
+/**
1079
+ * @author Jhon
1080
+ */
1081
+public class SpoilZone extends ZoneType
1082
+{
1083
+	public SpoilZone(int id)
1084
+	{
1085
+		super(id);
1086
+	}
1087
+	
1088
+	@Override
1089
+	protected void onEnter(Creature character)
1090
+	{
1091
+		// Only check rules if event is running
1092
+		if (!SpoilEventManager.getInstance().isActive())
1093
+		{
1094
+			character.setInsideZone(ZoneId.SPOIL_AREA, true);
1095
+			return;
1096
+		}
1097
+		if (character instanceof Player)
1098
+		{
1099
+			Player player = (Player) character;
1100
+			
1101
+			// 1. Check Class Restriction
1102
+			if (!player.isGM() && !SpoilEventData.getInstance().isAllClassesAllowed() && !SpoilEventData.getInstance().getAllowedClassIds().contains(player.getClassId().getId()))
1103
+			{
1104
+				player.sendMessage("Only authorized classes are allowed in this area.");
1105
+				teleportOut(player);
1106
+				return;
1107
+			}
1108
+			
1109
+			character.setInsideZone(ZoneId.SPOIL_AREA, true);
1110
+		}
1111
+	}
1112
+	
1113
+	@Override
1114
+	protected void onExit(Creature character)
1115
+	{
1116
+		character.setInsideZone(ZoneId.SPOIL_AREA, false);
1117
+	}
1118
+	
1119
+	private static void teleportOut(Player player)
1120
+	{
1121
+		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().
1122
+		if (loc != null)
1123
+			player.teleportTo(loc.getX(), loc.getY(), loc.getZ(), 0);
1124
+		else
1125
+			player.teleportTo(81260, 148607, -3440, 0); // Default Giran fallback
1126
+	}
1127
+}
1128
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
1129
index 90e1548..ad1cb97 100644
1130
--- a/aCis_gameserver/java/net/sf/l2j/gameserver/network/clientpackets/DlgAnswer.java
1131
+++ b/aCis_gameserver/java/net/sf/l2j/gameserver/network/clientpackets/DlgAnswer.java
1132
@@ -13,6 +13,7 @@ import net.sf.l2j.gameserver.model.entity.Events.LastMan.LMEvent;
1133
 import net.sf.l2j.gameserver.model.entity.Events.LastMan.LMManager;
1134
 import net.sf.l2j.gameserver.model.entity.Events.TeamVsTeam.TvTEvent;
1135
 import net.sf.l2j.gameserver.model.entity.Events.TeamVsTeam.TvTManager;
1136
+import net.sf.l2j.gameserver.events.spoil.SpoilEventManager;
1137
 import net.sf.l2j.gameserver.model.entity.Tournament.TournamentManager;
1138
 import net.sf.l2j.gameserver.model.entity.Tournament.Data.TournamentTeam;
1139
 import net.sf.l2j.gameserver.model.group.Party;
1140
@@ -63,12 +64,10 @@ public final class DlgAnswer extends L2GameClientPacket
1141
 				LMEvent.onBypass("lm_event_participation", activeChar);
1142
 			else if (TvTManager.getInstance().register_tvt == true)
1143
 				TvTEvent.onBypass("tvt_event_participation", activeChar);
1144
+			else if (SpoilEventManager.getInstance().isRegistrationOpen())
1145
+				SpoilEventManager.getInstance().registerPlayer(activeChar);
1146
+			else if (SoloBossEvent.getInstance().getCurrentNpcId() > 0)
1147
+				SoloBossEvent.getInstance().teleportPlayerToEvent(activeChar);
1148
 		}
1149
 		
1150
 		else if (_messageId == SystemMessageId.S1.getId())
1151