On one of my larger Verse scripts, I noticed that the Map Indicator Devices would not stop pulsing when deactivated per player agent.
To debug this, I created a minimal test script to determine whether this issue was specific to my implementation (e.g., a race condition) or a global bug.
Unfortunately, it appears to be global. I was able to reproduce the issue in a clean test environment.
Expected behavior:
-When the switch is toggled ON, the pulse should start.
-When the switch is toggled OFF, the pulse should stop.
Actual behavior:
-After toggling the switch OFF, the pulse continues indefinitely and does not deactivate as expected.
Below is the Verse script I used to isolate and reproduce the issue:
using { /Verse.org/Simulation }
using { /Fortnite.com/Devices }
using { /Fortnite.com/Playspaces }
using { /Verse.org/Concurrency }
pulse_tester := class(creative_device):
@editable
TestSwitch: switch_device = switch_device{}
@editable
TestIndicator: map_indicator_device = map_indicator_device{}
# This function runs when the device starts
OnBegin<override>()<suspends>:void=
# Subscribe to the switch events
TestSwitch.TurnedOnEvent.Subscribe(HandleTurnedOn)
TestSwitch.TurnedOffEvent.Subscribe(HandleTurnedOff)
# Make sure the map indicator is enabled when the game starts
TestIndicator.Enable()
# Check if the switch is set to "Per Player"
if (TestSwitch.IsStatePerAgent[] = false):
Print("WARNING: For this test, TestSwitch should have 'Store State Per Player' set to 'Yes'.")
# Clean up any existing pulses for all players at the start
AllPlayers := GetPlayspace().GetPlayers()
for (Player : AllPlayers):
TestIndicator.DeactivateObjectivePulse(Player)
# This function runs when an agent turns the switch ON
HandleTurnedOn(Agent: agent):void=
Print("Pulse Tester: Switch Turned ON for player. Calling ActivateObjectivePulse...")
TestIndicator.ActivateObjectivePulse(Agent)
Print("Pulse Tester: ActivateObjectivePulse(Agent) has been called.")
# This function runs when an agent turns the switch OFF
HandleTurnedOff(Agent: agent):void=
Print("Pulse Tester: Switch Turned OFF for player. Calling DeactivateObjectivePulse...")
TestIndicator.DeactivateObjectivePulse(Agent)
Print("Pulse Tester: DeactivateObjectivePulse(Agent) has been called.")
Please select what you are reporting on:
Unreal Editor for Fortnite
What Type of Bug are you experiencing?
Verse
Steps to Reproduce
use DeactivateObjectivePulse(Agent) on Map Indicator Device in verse
Expected Result
When the switch is toggled ON, the pulse should start.
When the switch is toggled OFF, the pulse should stop.
Observed Result
After toggling the switch OFF, the pulse continues indefinitely and does not deactivate as expected.
Ran into this problem as well. Disabling the device also doesn’t remove the pulse effect, it seems to be stuck permanently once assigned. This is a critical bug as it is extremely helpful in onboarding new players/tutorials.
Also encountering this issue; critical for us one one island as we use ObjectivePulse for a custom quest system (so there are 5+ pulses active at all times now).
I can confirm this issue too in my maps where I am using Objective Pulse from a Map Indicator device. I am deactivating it by Direct Event Binding with a function “Deactivate Objective Pulse” by different actions, so it is not only related to Verse. It is happening in a map that was updated just a few hours ago and also in a map that was last updated a few weeks ago.
Here’s a custom workaround that lets you take any creative prop and have it rotate toward a Map Indicator Device. This setup is designed to work alongside my custom Quest Manager, but you can easily adjust it to fit your own project.
# Quest Arrow Manager 1.00.00
# A replacement for the Map Indicator Pulse. Works in tandem with my Quest Manager, but with minor adjustments it can be adapted for your own needs.
# If this code is helpful to you, please favorite me as a creator: https://www.fortnite.com/@graphicsurfer?lang=en-US
# It costs nothing and means a lot. Thank you -GraphicSurfer.
using { /Fortnite.com/Devices }
using { /Fortnite.com/Characters }
using { /Fortnite.com/Playspaces }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /Verse.org/Verse }
quest_arrow_manager_device := class(creative_device):
# --- Arrow Prop Arrays ---
@editable
MainQuestArrows: []creative_prop = array{}
@editable
SideQuestArrows: []creative_prop = array{}
@editable
OptionalQuestArrows: []creative_prop = array{}
# --- Shared Settings ---
@editable
ArrowOffset: vector3 = vector3{X := 0.0, Y := 0.0, Z := 150.0}
@editable
UpdateRate: float = 0.064
@editable
Smoothness: float = 20.0
# --- State Variables ---
var MainPropScales: []vector3 = array{}
var SidePropScales: []vector3 = array{}
var OptionalPropScales: []vector3 = array{}
var MainQuestTargetMap: [player]?map_indicator_device = map{}
var SideQuestTargetMap: [player]?map_indicator_device = map{}
var OptionalQuestTargetMap: [player]?map_indicator_device = map{}
var PlayerArrowIndexMap: [player]?int = map{}
var AvailableArrowIndices: []int = array{}
HiddenLocation: vector3 = vector3{X := 0.0, Y := 0.0, Z := -1000.0}
OnBegin<override>()<suspends>:void=
for (Arrow : MainQuestArrows):
set MainPropScales = MainPropScales + array{Arrow.GetTransform().Scale}
for (Arrow : SideQuestArrows):
set SidePropScales = SidePropScales + array{Arrow.GetTransform().Scale}
for (Arrow : OptionalQuestArrows):
set OptionalPropScales = OptionalPropScales + array{Arrow.GetTransform().Scale}
for (Index -> Arrow : MainQuestArrows):
set AvailableArrowIndices += array{Index}
Playspace := GetPlayspace()
for (Player : Playspace.GetPlayers()):
spawn{ OnPlayerAdded(Player) }
Playspace.PlayerAddedEvent().Subscribe(OnPlayerAddedWrapper)
Playspace.PlayerRemovedEvent().Subscribe(OnPlayerRemoved)
loop:
InterpFactor := Clamp(Smoothness * UpdateRate, 0.0, 1.0)
AllPlayers := Playspace.GetPlayers()
for (Player : AllPlayers):
if (StablePlayerIndex := PlayerArrowIndexMap[Player]?):
if (PlayerCharacter := Player.GetFortCharacter[]):
PlayerLocation := PlayerCharacter.GetTransform().Translation
# --- Main Quest ---
if (MainArrow := MainQuestArrows[StablePlayerIndex]):
if (MainScale := MainPropScales[StablePlayerIndex]):
if (CurrentTarget := MainQuestTargetMap[Player]?):
UpdateArrowMovement(MainArrow, CurrentTarget, PlayerLocation, MainScale, InterpFactor)
else:
HideArrow(MainArrow, MainScale)
# --- Side Quest ---
if (SideArrow := SideQuestArrows[StablePlayerIndex]):
if (SideScale := SidePropScales[StablePlayerIndex]):
if (CurrentTarget := SideQuestTargetMap[Player]?):
UpdateArrowMovement(SideArrow, CurrentTarget, PlayerLocation, SideScale, InterpFactor)
else:
HideArrow(SideArrow, SideScale)
# --- Optional Quest ---
if (OptionalArrow := OptionalQuestArrows[StablePlayerIndex]):
if (OptionalScale := OptionalPropScales[StablePlayerIndex]):
if (CurrentTarget := OptionalQuestTargetMap[Player]?):
UpdateArrowMovement(OptionalArrow, CurrentTarget, PlayerLocation, OptionalScale, InterpFactor)
else:
HideArrow(OptionalArrow, OptionalScale)
Sleep(UpdateRate)
OnPlayerAddedWrapper(Player: player):void=
spawn{ OnPlayerAdded(Player) }
OnPlayerAdded(Player: player)<suspends>:void=
if (PlayerArrowIndexMap[Player]?):
return
if (AvailableArrowIndices.Length = 0):
Print("QuestArrowManager: CRITICAL - No available arrow indices for new player!")
return
var CurrentIndex: int = 0
if (IndexValue := AvailableArrowIndices[0]):
set CurrentIndex = IndexValue
var NewAvailableIndices: []int = array{}
for (I := 1..AvailableArrowIndices.Length - 1):
if (Index := AvailableArrowIndices[I]):
set NewAvailableIndices += array{Index}
set AvailableArrowIndices = NewAvailableIndices
else:
Print("QuestArrowManager: Failed to get CurrentIndex from AvailableArrowIndices")
return
if (set PlayerArrowIndexMap[Player] = option{CurrentIndex}):
if (set MainQuestTargetMap[Player] = false):
else:
Print("QuestArrowManager: Failed to clear MainQuestTargetMap in OnPlayerAdded")
if (set SideQuestTargetMap[Player] = false):
else:
Print("QuestArrowManager: Failed to clear SideQuestTargetMap in OnPlayerAdded")
if (set OptionalQuestTargetMap[Player] = false):
else:
Print("QuestArrowManager: Failed to clear OptionalQuestTargetMap in OnPlayerAdded")
Print("QuestArrowManager: Assigned arrow index {CurrentIndex} to new player.")
else:
Print("QuestArrowManager: CRITICAL - Failed to set PlayerArrowIndexMap.")
OnPlayerRemoved(Player: player):void=
if (Index := PlayerArrowIndexMap[Player]?):
Print("QuestArrowManager: Player left. Cleaning up index {Index}.")
if (set MainQuestTargetMap[Player] = false):
else:
Print("QuestArrowManager: Failed to clear MainQuestTargetMap in OnPlayerRemoved")
if (set SideQuestTargetMap[Player] = false):
else:
Print("QuestArrowManager: Failed to clear SideQuestTargetMap in OnPlayerRemoved")
if (set OptionalQuestTargetMap[Player] = false):
else:
Print("QuestArrowManager: Failed to clear OptionalQuestTargetMap in OnPlayerRemoved")
if (set PlayerArrowIndexMap[Player] = false):
Print("QuestArrowManager: Player removed from index map.")
set AvailableArrowIndices += array{Index}
else:
Print("QuestArrowManager: FAILED to remove player from index map.")
UpdateArrowMovement(ArrowProp: creative_prop, TargetDevice: map_indicator_device, PlayerLocation: vector3, PropScale: vector3, InterpFactor: float)<suspends>:void=
TargetLocation := TargetDevice.GetTransform().Translation
TargetArrowLocation := PlayerLocation + ArrowOffset
Direction := vector3{
X := TargetLocation.X - PlayerLocation.X,
Y := TargetLocation.Y - PlayerLocation.Y,
Z := 0.0
}
if (NormalizedDirection := Direction.MakeUnitVector[]):
XForward := vector3{X := 1.0, Y := 0.0, Z := 0.0}
TargetArrowRotation := MakeShortestRotationBetween(XForward, NormalizedDirection)
CurrentTransform := ArrowProp.GetTransform()
CurrentLocation := CurrentTransform.Translation
CurrentRotation := CurrentTransform.Rotation
NewLocation := Lerp(CurrentLocation, TargetArrowLocation, InterpFactor)
if (NewRotation := Slerp[CurrentRotation, TargetArrowRotation, InterpFactor]):
NewTransform := transform{
Translation := NewLocation,
Rotation := NewRotation,
Scale := PropScale
}
if (ArrowProp.TeleportTo[NewTransform]):
else:
Print("QuestArrowManager: TeleportTo failed.")
HideArrow(ArrowToHide: creative_prop, PropScale: vector3)<transacts>:void=
HiddenTransform := transform{
Translation := HiddenLocation,
Rotation := IdentityRotation(),
Scale := PropScale
}
if (ArrowToHide.TeleportTo[HiddenTransform]):
else:
Print("QuestArrowManager: HideArrow TeleportTo failed.")
ActivateMainQuestArrow<public>(Player: player, Target: map_indicator_device)<transacts>:void=
if (set MainQuestTargetMap[Player] = option{Target}):
else:
Print("QuestArrowManager: Failed to set MainQuestTargetMap.")
DeactivateMainQuestArrow<public>(Player: player)<transacts>:void=
if (set MainQuestTargetMap[Player] = false):
else:
Print("QuestArrowManager: Failed to clear MainQuestTargetMap.")
ActivateSideQuestArrow<public>(Player: player, Target: map_indicator_device)<transacts>:void=
if (set SideQuestTargetMap[Player] = option{Target}):
else:
Print("QuestArrowManager: Failed to set SideQuestTargetMap.")
DeactivateSideQuestArrow<public>(Player: player)<transacts>:void=
if (set SideQuestTargetMap[Player] = false):
else:
Print("QuestArrowManager: Failed to clear SideQuestTargetMap.")
ActivateOptionalQuestArrow<public>(Player: player, Target: map_indicator_device)<transacts>:void=
if (set OptionalQuestTargetMap[Player] = option{Target}):
else:
Print("QuestArrowManager: Failed to set OptionalQuestTargetMap.")
DeactivateOptionalQuestArrow<public>(Player: player)<transacts>:void=
if (set OptionalQuestTargetMap[Player] = false):
else:
Print("QuestArrowManager: Failed to clear OptionalQuestTargetMap.")