L2JMobius

Epilogue Events randomly stops during Frintezza instance

Imendil · 6 · 1775

Offline Imendil

  • Vassal
  • *
    • Posts: 3
Hi!

I'm investigating issue with Frintezza instance on Epilogue. From what I have seen, event "waiting" never triggers from killing DarkChoirCaptain`s, room despawns and doors open, so `room2_del` is correctly handled.

When I've added "waiting" to Frintezza constructor, event starts as scheduled on server restart, however during fight while handling "morph_11" , "morph_12" was shot, but "morph_13" not. Resulting in player being held in place.
On the second run of it, after handling "camera_9b", event "camera_9c" was not started, which as well resulted in everything being frozen.

Logs with additional logging in onEvent:
Code: [Select]
[12/03 12:13:35] LoginServerThread: Registered on login as Server 2: Sieghardt
[12/03 12:14:33] Frintezza Event:waiting
[12/03 12:15:00] Frintezza Event:close
[12/03 12:15:03] Frintezza Event:camera_1
[12/03 12:15:03] GrandBossManager: Updated Frintezza(29045) status to 2
[12/03 12:15:03] Frintezza Event:stop_pc
[12/03 12:15:04] Frintezza Event:camera_2
[12/03 12:15:04] Frintezza Event:camera_2b
[12/03 12:15:04] Frintezza Event:camera_3
[12/03 12:15:11] Frintezza Event:camera_4
[12/03 12:15:12] Frintezza Event:camera_5
[12/03 12:15:16] Frintezza Event:camera_5b
[12/03 12:15:16] Frintezza Event:camera_6
[12/03 12:15:17] Frintezza Event:camera_7
[12/03 12:15:24] Frintezza Event:camera_8
[12/03 12:15:25] Frintezza Event:camera_9
[12/03 12:15:26] Frintezza Event:camera_9b
[12/03 12:21:55] GlobalVariablesManager: Stored 13 variables.

Another occurrence, events stopped triggering after camera_18
Code: [Select]
[12/03 12:45:25] Frintezza Event:waiting
[12/03 12:45:52] Frintezza Event:close
[12/03 12:45:55] Frintezza Event:camera_1
[12/03 12:45:55] GrandBossManager: Updated Frintezza(29045) status to 2
[12/03 12:45:55] Frintezza Event:stop_pc
[12/03 12:45:56] Frintezza Event:camera_2
[12/03 12:45:56] Frintezza Event:camera_2b
[12/03 12:45:56] Frintezza Event:camera_3
[12/03 12:46:03] Frintezza Event:camera_4
[12/03 12:46:04] Frintezza Event:camera_5
[12/03 12:46:08] Frintezza Event:camera_5b
[12/03 12:46:08] Frintezza Event:camera_6
[12/03 12:46:09] Frintezza Event:camera_7
[12/03 12:46:16] Frintezza Event:camera_8
[12/03 12:46:17] Frintezza Event:camera_9
[12/03 12:46:17] Frintezza Event:camera_9b
[12/03 12:46:17] Frintezza Event:camera_9c
[12/03 12:46:19] Frintezza Event:camera_10
[12/03 12:46:19] Frintezza Event:camera_11
[12/03 12:46:24] Frintezza Event:camera_12
[12/03 12:46:25] Frintezza Event:camera_13
[12/03 12:46:26] Frintezza Event:camera_14
[12/03 12:46:27] Frintezza Event:camera_16
[12/03 12:46:35] Frintezza Event:camera_17
[12/03 12:46:44] Frintezza Event:camera_18


Online Mobius

  • Distinguished King
  • *****
    • Posts: 19715

Offline Imendil

  • Vassal
  • *
    • Posts: 3
Does it mean that fix will be public anytime soon or what exactly?
I've tried to fix it by removing references to NPCs that just died, but that's not whole story. Even without those references, camera transitions get broken randomly.



Online Naker

  • Count
  • *****
    • Posts: 450
  • Coding Dreams
Does it mean that fix will be public anytime soon or what exactly?
I've tried to fix it by removing references to NPCs that just died, but that's not whole story. Even without those references, camera transitions get broken randomly.
You can wait until the revision with the change will be realesed or you can become a substriber and get access to it up to you.


Offline Imendil

  • Vassal
  • *
    • Posts: 3
I've fixed it myself.

Apart of issues with NPC references in Frintezza.java itself, there is race condition within org.l2jmobius.gameserver.model.quest.QuestTimer.java. I guess other places with single shot timers are affected as well.

Updated QuestTimer.java
Code: [Select]
package org.l2jmobius.gameserver.model.quest;

import java.util.Objects;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.l2jmobius.commons.threads.ThreadPool;
import org.l2jmobius.gameserver.model.actor.Npc;
import org.l2jmobius.gameserver.model.actor.Player;
import org.l2jmobius.gameserver.model.events.AbstractScript;

public class QuestTimer
{
public static final Logger LOGGER = Logger.getLogger(AbstractScript.class.getName());
protected final String _name;
protected final Quest _quest;
protected Npc _npc;
protected Player _player;
protected final boolean _isRepeating;
protected final AtomicReference<ScheduledFuture<?>> _scheduler = new AtomicReference<>();
protected volatile boolean _isActive;

public QuestTimer(Quest quest, String name, long time, Npc npc, Player player, boolean repeating)
{
_quest = quest;
_name = name;
_npc = npc;
_player = player;
_isRepeating = repeating;
_isActive = true;

if (repeating)
{
_scheduler.set(ThreadPool.scheduleAtFixedRate(new ScheduleTimerTask(), time, time)); // Prepare auto end task
}
else
{
_scheduler.set(ThreadPool.schedule(new ScheduleTimerTask(), time)); // Prepare auto end task
}

if (npc != null)
{
npc.addQuestTimer(this);
}

if (player != null)
{
player.addQuestTimer(this);
}
}

public void cancel()
{
cancelTask();

if (_npc != null)
{
_npc.removeQuestTimer(this);
}

if (_player != null)
{
_player.removeQuestTimer(this);
}

// Nullify references to avoid memory leaks
_npc = null;
_player = null;
}

public void cancelTask()
{
ScheduledFuture<?> scheduler = _scheduler.getAndSet(null); // Atomically get and nullify
if ((scheduler != null) && !scheduler.isDone() && !scheduler.isCancelled())
{
scheduler.cancel(false);
}

_quest.removeQuestTimer(this); // Ensure timer is removed before nullifying scheduler
}

/**
* public method to compare if this timer matches with the key attributes passed.
* @param quest : Quest instance to which the timer is attached
* @param name : Name of the timer
* @param npc : Npc instance attached to the desired timer (null if no npc attached)
* @param player : Player instance attached to the desired timer (null if no player attached)
* @return boolean
*/
public boolean equals(Quest quest, String name, Npc npc, Player player)
{
return (_quest == quest) && (Objects.equals(_name, name)) && // Handles null safety
(_npc == npc) && (_player == player);
}

public boolean isActive()
{
ScheduledFuture<?> scheduler = _scheduler.get(); // Get the current reference safely
return (scheduler != null) && !scheduler.isCancelled() && !scheduler.isDone() && _isActive;
}

public boolean isRepeating()
{
return _isRepeating;
}

public Quest getQuest()
{
return _quest;
}

public Npc getNpc()
{
return _npc;
}

public Player getPlayer()
{
return _player;
}

[member=79]override[/member]
public String toString()
{
return _name;
}

public class ScheduleTimerTask implements Runnable
{
[member=79]override[/member]
public void run()
{
ScheduledFuture<?> scheduler = _scheduler.get();
if (scheduler == null)
{
LOGGER.log(Level.WARNING, "Scheduler is NULL");
return;
}

if (!_isRepeating)
{
_isActive = false;
}

_quest.notifyEvent(_name, _npc, _player);

if (!_isRepeating)
{
cancel();
}

}
}
}

Plus changes to Quest.java :: getQuestTimer due to changes in QuestTimer
Code: [Select]
for (QuestTimer timer : timers)
{
if ((timer != null) && timer.equals(this, name, npc, player) && timer.isActive())
{
return timer;
}
}