VerseIsland
Feed Learn Leaderboard Guides Reference Explore Search Docs Verse Library
Sign in
Feed Learn 📖 Codex Leaderboard Guides Reference Explore Search Docs Verse Library
Verse Library › NPCs & AI
Browse Verse Library

Library

Devices 634
  • Devices171
  • Triggers & Logic118
  • UI & HUD40
  • Audio39
  • Items & Inventory29
  • Events27
  • Scoring & Objectives25
  • Spawning19
  • Classes & Interfaces18
  • Game Modes17
  • Props & Meshes14
  • Variables & Mutability12
  • Player Management11
  • Modules & Structure10
  • Round Flow8
  • Animation6
  • Concurrency6
  • Functions6
  • Getting Started6
  • Teams & Classes6
  • NPCs & AI5
  • Tutorials5
  • VFX & Particles5
  • Arrays & Maps4
  • Cameras4
  • Debugging4
  • Control Flow3
  • Cinematics2
  • Other2
  • Persistence2
  • Scene Graph2
  • Structs & Enums2
  • Building1
  • Physics1
  • Publishing & Islands1
  • Syntax & Types1
  • Vehicles1
  • Weapons1
Verse Language 257
  • Control Flow35
  • Functions26
  • Arrays & Maps25
  • Classes & Interfaces22
  • Variables & Mutability22
  • Concurrency21
  • Syntax & Types18
  • Modules & Structure12
  • Debugging8
  • Devices8
  • Events7
  • Player Management6
  • Structs & Enums6
  • Triggers & Logic6
  • Game Modes5
  • Failure & Options4
  • Scene Graph4
  • UI & HUD4
  • Getting Started3
  • Round Flow3
  • Spawning3
  • VFX & Particles3
  • Reference2
  • Animation1
  • Best Practices1
  • Other1
  • Props & Meshes1
Materials/VFX 154
  • Triggers & Logic27
  • UI & HUD17
  • Devices16
  • VFX & Particles14
  • Props & Meshes11
  • Scoring & Objectives9
  • Game Modes7
  • Items & Inventory7
  • Functions6
  • Arrays & Maps5
  • Animation3
  • Cinematics3
  • Classes & Interfaces3
  • Control Flow3
  • Events3
  • Modules & Structure3
  • Round Flow3
  • Spawning3
  • Getting Started2
  • Scene Graph2
  • Variables & Mutability2
  • Concurrency1
  • Persistence1
  • Player Management1
  • Structs & Enums1
  • Syntax & Types1
General 153
  • Devices22
  • Events20
  • Triggers & Logic20
  • Classes & Interfaces10
  • Variables & Mutability8
  • Round Flow6
  • Scoring & Objectives6
  • UI & HUD6
  • Functions5
  • Player Management5
  • Game Modes4
  • Getting Started4
  • Props & Meshes4
  • Control Flow3
  • Syntax & Types3
  • VFX & Particles3
  • Arrays & Maps2
  • Best Practices2
  • NPCs & AI2
  • Other2
  • Physics2
  • Scene Graph2
  • Spawning2
  • Structs & Enums2
  • Tutorials2
  • Building1
  • Cameras1
  • Cinematics1
  • Failure & Options1
  • Items & Inventory1
  • Teams & Classes1
Player 133
  • Player Management22
  • Teams & Classes19
  • Classes & Interfaces12
  • Devices11
  • Events9
  • Persistence7
  • Triggers & Logic7
  • Arrays & Maps6
  • Functions4
  • Game Modes4
  • Items & Inventory4
  • Round Flow4
  • Debugging3
  • Getting Started3
  • Modules & Structure3
  • Concurrency2
  • Control Flow2
  • Scoring & Objectives2
  • Spawning2
  • Structs & Enums2
  • Physics1
  • Props & Meshes1
  • UI & HUD1
  • Variables & Mutability1
  • Weapons1
Building & Props 131
  • Variables & Mutability15
  • Props & Meshes14
  • Triggers & Logic12
  • Classes & Interfaces10
  • Devices10
  • UI & HUD10
  • Events7
  • Teams & Classes7
  • Animation5
  • Building5
  • Functions5
  • VFX & Particles5
  • Structs & Enums4
  • Control Flow3
  • Game Modes3
  • Items & Inventory3
  • Round Flow3
  • Scene Graph3
  • Arrays & Maps2
  • Concurrency2
  • Reference2
  • Modules & Structure1
NPCs & AI 76
  • NPCs & AI35
  • Spawning7
  • Animation5
  • Variables & Mutability5
  • Classes & Interfaces3
  • Debugging3
  • UI & HUD3
  • Audio2
  • Concurrency2
  • Game Modes2
  • Props & Meshes2
  • Triggers & Logic2
  • Devices1
  • Functions1
  • Getting Started1
  • Reference1
  • VFX & Particles1
Spawning 73
  • Spawning31
  • Events7
  • Concurrency6
  • Player Management6
  • Vehicles5
  • Props & Meshes4
  • Devices3
  • Scene Graph3
  • Items & Inventory2
  • Teams & Classes2
  • Control Flow1
  • Publishing & Islands1
  • Reference1
  • Scoring & Objectives1
Cinematics 71
  • Cameras19
  • Cinematics15
  • Devices11
  • Audio5
  • Animation4
  • Triggers & Logic4
  • Functions2
  • Game Modes2
  • UI & HUD2
  • Classes & Interfaces1
  • Concurrency1
  • Control Flow1
  • Items & Inventory1
  • Modules & Structure1
  • Scene Graph1
  • Templates1
UI 57
  • UI & HUD35
  • Devices5
  • Scoring & Objectives4
  • Classes & Interfaces3
  • Round Flow2
  • Scene Graph2
  • Arrays & Maps1
  • Props & Meshes1
  • Structs & Enums1
  • Templates1
  • Triggers & Logic1
  • Variables & Mutability1
Scene Graph 40
  • Scene Graph9
  • Items & Inventory8
  • Classes & Interfaces3
  • Events3
  • Props & Meshes3
  • Functions2
  • Arrays & Maps1
  • Building1
  • Control Flow1
  • Debugging1
  • Modules & Structure1
  • Player Management1
  • Reference1
  • Structs & Enums1
  • Syntax & Types1
  • Triggers & Logic1
  • UI & HUD1
  • VFX & Particles1
Vehicles 38
  • Spawning11
  • Devices7
  • Vehicles7
  • Props & Meshes4
  • Functions2
  • Game Modes2
  • Failure & Options1
  • Items & Inventory1
  • Reference1
  • Teams & Classes1
  • Tutorials1
Tutorials 37
  • Game Modes6
  • Triggers & Logic5
  • Classes & Interfaces4
  • Events4
  • Props & Meshes3
  • Building2
  • Devices2
  • Items & Inventory2
  • Scoring & Objectives2
  • Animation1
  • Arrays & Maps1
  • Cameras1
  • Concurrency1
  • Physics1
  • Player Management1
  • Structs & Enums1
Weapons 30
  • Game Modes6
  • Weapons6
  • Devices5
  • Items & Inventory3
  • Scoring & Objectives3
  • Spawning2
  • Teams & Classes2
  • Best Practices1
  • Functions1
  • Triggers & Logic1
Items 28
  • Items & Inventory23
  • Devices2
  • Triggers & Logic2
  • Spawning1
Assets 20
  • Modules & Structure6
  • Arrays & Maps3
  • Functions3
  • Scene Graph2
  • Best Practices1
  • Devices1
  • Events1
  • Failure & Options1
  • Physics1
  • Variables & Mutability1
Animation 19
  • Animation10
  • Variables & Mutability4
  • Devices1
  • Player Management1
  • Props & Meshes1
  • Scene Graph1
  • Syntax & Types1
Release Notes 17
  • Classes & Interfaces5
  • Modules & Structure3
  • Concurrency2
  • Control Flow2
  • Variables & Mutability2
  • Debugging1
  • Devices1
  • Items & Inventory1
Input & Controls 16
  • Functions4
  • UI & HUD3
  • Control Flow2
  • Animation1
  • Arrays & Maps1
  • Cinematics1
  • Debugging1
  • Devices1
  • Events1
  • Items & Inventory1
Publishing 8
  • Control Flow5
  • Arrays & Maps1
  • Devices1
  • Syntax & Types1
Audio 7
  • Audio5
  • UI & HUD2
Landscape 7
  • Other4
  • VFX & Particles2
  • Scene Graph1
Persistence 6
  • Structs & Enums2
  • Items & Inventory1
  • Persistence1
  • Publishing & Islands1
  • Variables & Mutability1
Physics 5
  • Functions2
  • Classes & Interfaces1
  • Devices1
  • Syntax & Types1
Creator Economy 3
  • Publishing & Islands2
  • Modules & Structure1
Discovery 3
  • Devices1
  • NPCs & AI1
  • Structs & Enums1
Editor & Project 3
  • Other1
  • Props & Meshes1
  • Tutorials1

LEGO Quest System With NPC Behavior

Module — 6 files
These files compile together (same module folder).
file_1.verse
# This is the LEGO Quest Giver system built for the Action Adventure Template. 
# In order to utilize this you'll need to create a Verse script for each snippet

# The LEGO Quest Giver Device
#   Controls the quest system and is the central device that runs this system. 

# The LEGO Quest Class
#   Defines the quest object and is referenced in the Quest Array in the LEGO Quest 
#   Giver Device. This is currently dependent on the LEGO_Stud_Rewards module as the quest rewards players on 
#   Completion. If you are not using that module you'll need to comment out (#) The Using on Line 7, the 
#   LEGO_stud_Spawner reference and tooltip in the field definitions, as well as any call to that LEGO_Stud_Spawner 
#   called Reward

# The LEGO Quest Dialogue
#   Creates and populates the quest UI window with information sent in from the LEGO 
#   Quest Device
#   Line 138/139 define a texture block that requires a DefaultImage. Currently the
#   code references 'T_LEGO_Quests_Dialog_Divider' which is provided in the Action # 
#   Adventure Template. You can copy the UAsset from the project into the same 
#   directory as your Quest_Dialogue class, or create your own divider image. 

# The LEGO Persistent Quest Log
#   Enables the quests to be saved. This persistency is on a per-player basis
#   the initiating player will be the one that the data is associated with). 

# The Quest Giver NPC Behavior
#   is the custom behaviour you will assign in your NPC spawner. This will put the NPC #   into stasis, so that it will stand still to interact with your player. 

# Full setup guide can be found here: 
# https://www.youtube.com/live/VyaB47DHt4gsi=rWqu22dLykl_WVAl&t=1409
lego_quest_giver_device.verse
using { /Fortnite.com/Characters }
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }

# ====================================================================================================================================
# LEGO quest giver device that manages a linear quest system
# ====================================================================================================================================
lego_quest_giver_device_log<public> := class(log_channel) {}

lego_quest_giver_device := class(creative_device):

    @editable:
        ToolTip := Tooltip_QuestGiver_Name
    QuestGiverName<public> : string = "Quest Giver"

    @editable:
        ToolTip := Tooltip_Button_Interact
    Button_Interact : button_device = button_device{}

    @editable:
        Categories := array{Category_FinishedGuidance}
        ToolTip := Tooltip_QuestGiver_Tracker
    TrackerDevice<public> : tracker_device = tracker_device{}

    @editable:
        Categories := array{Category_FinishedGuidance}
        ToolTip := Tooltip_BeaconReturn
    ReturnBeacon<public> : beacon_device = beacon_device{}

    @editable:
        ToolTip := Tooltip_AutoProgressDialog
        Categories := array{Category_Settings}
    AutoProgressDialog<public> : logic = true

    @editable:
        ToolTip := Tooltip_ResetProgress
        Categories := array{Category_Settings}
    Trigger_ResetProgress : ?trigger_device = false

    @editable:
        ToolTip := Tooltip_Quests
    Quests : []lego_quest = array{}

    Logger<protected>: log = log{Channel := lego_quest_giver_device_log, DefaultLevel:= log_level.Normal}

    OnBegin<override>()<suspends>:void=
        # Bind to button interaction event
        Button_Interact.InteractedWithEvent.Subscribe(OnRequestInteraction)
        Button_Interact.SetInteractionText(ToMessage("Talk to {QuestGiverName}"))

        # Bind to Reset request trigger
        if (ValidTrigger := Trigger_ResetProgress?):
            ValidTrigger.TriggeredEvent.Subscribe(OnResetProgress)
        
        # Setup Tracker Device (Return to QuestGiver message)
        TrackerDevice.SetTitleText(ToMessage("Completed Quest"))
        TrackerDevice.SetDescriptionText(ToMessage("Return to {QuestGiverName}"))

        # Setup Event subscribes for all quests
        for (Quest : Quests):
            Quest.Setup(Self, TrackerDevice, ReturnBeacon)

    # ---------------------------------------------------------------------------------------------------------------------------------
    # Player Interaction with QuestGiver
    # 1) Show Dialog UI
    # 2) Wait for player interaction that closes the UI
    # 3) Process and apply Response from Dialog UI (Start / Complete / Abort quest / Close)
    # ---------------------------------------------------------------------------------------------------------------------------------
    OnRequestInteraction<public>(InAgent : agent): void=
        if (Quests.Length = 0):
            Logger.Print("OnRequestInteraction failed - no quests found.", ?Level := log_level.Error)
            return

        TrackerDevice.Complete(InAgent)
        spawn{InteractWithQuestGiver(InAgent)}

    InteractWithQuestGiver<private>(InAgent : agent)<suspends>: void=
        TrackerDevice.Complete(InAgent)
        QuestProgressData := GetQuestLineProgression(InAgent)

        # Create UI to be shown to the player
        DialogUI := lego_quest_dialog{}

        # Configure UI based on QuestLine progression 
        # iE. what button text to display and what Dialog option to show
        Title := if(ValidQuest := QuestProgressData(1)?) {ToMessage(ValidQuest.Name)} else {ToMessage("")}
        Dialog_Start := if(ValidQuest := QuestProgressData(1)?) {ToMessage(ValidQuest.DisplayText.DialogText_Start)} else {ToMessage("I don`t have any quests for you right now.")}
        Dialog_Complete := if(ValidQuest := QuestProgressData(1)?) {ToMessage(ValidQuest.DisplayText.DialogText_Complete)} else {ToMessage("")}

        var Text : ?message = false
        var Button_1_Text : string = ""
        var Button_2_Text : string = ""

        case (QuestProgressData(0)):
            en_lego_quest_progress.NotStarted =>
                set Button_1_Text = "Accept Quest "
                set Button_2_Text = "Decline Quest"
                set Text = option{Dialog_Start}
            en_lego_quest_progress.IsActive =>
                set Button_2_Text = "Abort Quest"
                set Text = option{Dialog_Start}
            en_lego_quest_progress.CanBeCompleted =>
                set Button_1_Text = "Finish Quest"
                set Button_2_Text = "Abort Quest"
                set Text = option{Dialog_Complete}
            en_lego_quest_progress.None =>
                set Text = option{Dialog_Start}
            _=>

        Button_1_Message : message = ToMessage(Button_1_Text)
        Button_2_Message : message = ToMessage(Button_2_Text)

        # Data to be passed to the UI class, the LEGO_quest_dialog will then process and display it
        DialogConfig := lego_dialog_config:
            Title := option{Title},
            Text := Text, 
            Button_1_Text := if(Button_1_Text.Length > 0) {option{Button_1_Message}} else {false},
            Button_2_Text := if(Button_2_Text.Length > 0) {option{Button_2_Message}} else {false}

        # 1) Show UI
        DialogUI.ShowDialog(InAgent, DialogConfig)

        # 2) Wait for player interaction
        DialogResponse := DialogUI.OnDialogCompleted.Await()

        # 3) Process Response from the UI
        #    Which Button did the player press? Start / Complete / Abort / Close
        if (DialogResponse = en_lego_quest_dialog_response.Close) {return}

        # Process Response based on the players progress through the questline
        if (ValidQuest := QuestProgressData(1)?):
            case (QuestProgressData(0)):
                # Start new quest
                en_lego_quest_progress.NotStarted =>
                    if (DialogResponse = en_lego_quest_dialog_response.Accept):
                        ValidQuest.StartQuest(InAgent)

                # Complete active quest
                en_lego_quest_progress.CanBeCompleted =>
                    if (DialogResponse = en_lego_quest_dialog_response.Accept):
                        ValidQuest.CompleteQuest(InAgent)
                        
                        # Open next quest Dialog (if auto Dialog progression is enabled)
                        if (AutoProgressDialog?) {spawn{InteractWithQuestGiver(InAgent)}}

                # Abort quest
                    if (DialogResponse = en_lego_quest_dialog_response.Decline):
                        ValidQuest.AbortQuest(InAgent)

                # Abort quest
                en_lego_quest_progress.IsActive =>
                    if (DialogResponse = en_lego_quest_dialog_response.Decline):
                        ValidQuest.AbortQuest(InAgent)

                _=>
                    # No Response to process
        else:
            Logger.Print("InteractWithQuestGiver failed - no valid quest found.", ?Level := log_level.Error)

    # ---------------------------------------------------------------------------------------------------------------------------------


    # ---------------------------------------------------------------------------------------------------------------------------------
    # QuestLine Progression
    # Loop over all the quests and return the first quest that can either be started, completed or is active
    # ---------------------------------------------------------------------------------------------------------------------------------
    GetQuestLineProgression<public>(InAgent : agent)<transacts>: tuple(en_lego_quest_progress, ?lego_quest)=
        if (Quests.Length = 0):
            Logger.Print("GetQuestLineProgression: QuestGiver has no quests.", ?Level := log_level.Warning)

        for (Quest : Quests):
            # Quest can be started
            if (Quest.CanBeStarted[InAgent]):
                return (en_lego_quest_progress.NotStarted, option{Quest})

            # Quest is active and can be completed or aborted
            if (Quest.IsActive[InAgent]):
                if (Quest.CanBeCompleted[InAgent]):
                    return (en_lego_quest_progress.CanBeCompleted, option{Quest})
                return (en_lego_quest_progress.IsActive, option{Quest})

        # No available quests
        return (en_lego_quest_progress.None, false)
    # ---------------------------------------------------------------------------------------------------------------------------------

    # This function is called when the Trigger_ResetProgress is activated and resets all quest progress for the activating player
    OnResetProgress<public>(InAgent : ?agent): void=
        if:
            ValidAgent := InAgent?
            ValidQuestLog := ValidAgent.LoadLegoQuestLog[]
        then:
            UpdatedQuestLog := LEGO_persistent_quest_log:
                ActiveQuests := array{},
                CompletedQuests := array{}
            if (ValidAgent.SaveLegoQuestLog[UpdatedQuestLog]) {}
        else:
            Logger.Print("ResetPersistentData failed - no quest log found.", ?Level := log_level.Error)


Tooltip_QuestGiver_Name<public><localizes>:message = "The name of the quest giver as it will appear in the tracker UI and button interaction text."
Tooltip_Button_Interact<public><localizes>:message = "The button that the player must press to interact with the quest giver."
Tooltip_QuestGiver_Tracker<public><localizes>:message = "The tracker_device used to display the return to questgiver message."
Tooltip_BeaconReturn<public><localizes>:message = "This beacon can be used to guide the player back to the quest giver once a quest is completed."
Tooltip_AutoProgressDialog<public><localizes>:message = "If true, the quest giver will automatically show the next quest Dialog after the player has completed a quest."
Tooltip_ResetProgress<public><localizes>:message = "Trigger device that will reset the progress in all quests for the activating player."
Tooltip_Quests<public><localizes>:message = "The quests that the quest giver can give to the player."

Category_Settings<public><localizes>:message = "Settings"
Category_FinishedGuidance<public><localizes>:message = "Quest finished guidance"
file_3.verse
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /Verse.org/Simulation }
using { /Fortnite.com/Characters }
using { /Fortnite.com/Devices }

using { LEGOModule_StudRewards}

# ============================================================================================================================================
# Definition of a completable quest with dialog setup and reward
# ============================================================================================================================================
en_lego_quest_progress<public> := enum{ None, NotStarted, IsActive, CanBeCompleted, Completed }
lego_quest_log<public> := class(log_channel) {}

lego_quest_repeatable<public> := class<concrete><unique>(lego_quest) {}
lego_quest<public> := class<concrete><unique>():
    @editable:
        ToolTip := ToolTip_Quest_Name
    Name<public> : string = "UNIQUE Quest Name"

    @editable
    DisplayText<public> : lego_quest_info = lego_quest_info{}

    @editable:
        ToolTip := ToolTip_EventBindings
    EventBindings<public> : ?lego_quest_eventbindings = false
    
    @editable:
        ToolTip := Tooltip_Tracker
    TrackerDevice<public> : tracker_device = tracker_device{}
    
    @editable:
        ToolTip := Tooltip_Beacon
    GuidingBeacons<public> : []beacon_device = array{}

    @editable:
        ToolTip := ToolTip_Quest_Reward
    Reward<public> : lego_stud_spawner = lego_stud_spawner{}

    var CompletedObjectives : [agent]logic = map{}
    var TrackerDeviceQuestGiver : tracker_device = tracker_device{}
    var ReturnBeacon : beacon_device = beacon_device{}
    Logger<protected>: log = log{Channel := lego_quest_log, DefaultLevel:= log_level.Normal}

    # --------------------------------------------------------------------------------------------------------------------------------------------
    # API
    # --------------------------------------------------------------------------------------------------------------------------------------------
    # This is called by the quest giver to setup the quest with the required devices
    Setup<public>(InCreativeDevice : creative_device, InTrackerDeviceQuestGiver : tracker_device, InReturnBeacon : beacon_device): void=
        set TrackerDeviceQuestGiver = InTrackerDeviceQuestGiver
        set ReturnBeacon = InReturnBeacon
        TrackerDevice.CompleteEvent.Subscribe(OnCompletedObjective)            
        TrackerDevice.SetTitleText(ToMessage(Name))
        TrackerDevice.SetDescriptionText(ToMessage(DisplayText.Description))
        Reward.Init(InCreativeDevice)


    # Start the quest for a player, add it to the quest log and update the tracker device
    StartQuest<public>(InAgent : agent): void=
        if (ValidQuestLog := InAgent.LoadLegoQuestLog[]):
            TrackerDevice.Assign(InAgent)
        
            for (Beacon : GuidingBeacons):
                Beacon.AddToShowList(InAgent)

            if (ValidTrigger := EventBindings?.Trigger_OnStartedQuest?):
                ValidTrigger.Trigger(InAgent)
            
            # Update persistent quest log
            UpdatedQuestLog := LEGO_persistent_quest_log:
                ActiveQuests := ValidQuestLog.ActiveQuests + array{Name},
                CompletedQuests := ValidQuestLog.CompletedQuests

            if (InAgent.SaveLegoQuestLog[UpdatedQuestLog]):
                Logger.Print("Quest Log updated.")
        else:
            Logger.Print("StartQuest failed - no quest log found.", ?Level := log_level.Error)

    # Abort the quest for a player, remove it from the quest log and update the tracker device
    AbortQuest<public>(InAgent : agent): void=
        if (ValidQuestLog := InAgent.LoadLegoQuestLog[]): 

            TrackerDevice.Reset(InAgent)

            for (Beacon : GuidingBeacons):
                Beacon.RemoveFromShowList(InAgent)

            if (ValidTrigger := EventBindings?.Trigger_OnAbortedQuest?):
                ValidTrigger.Trigger(InAgent)

            # Update persistent quest log
            var UpdatedActiveQuests : []string = array{}

            for (ActiveQuest : ValidQuestLog.ActiveQuests; ActiveQuest <> Name):
                set UpdatedActiveQuests += array{ActiveQuest}

            UpdatedQuestLog := LEGO_persistent_quest_log{
                ActiveQuests := UpdatedActiveQuests,
                CompletedQuests := ValidQuestLog.CompletedQuests}

            if (InAgent.SaveLegoQuestLog[UpdatedQuestLog]) {}
        else:
            Logger.Print("AbortQuest failed - no quest log found.", ?Level := log_level.Error)

    # Complete the quest for a player, remove it from the quest log and update the tracker device, handle rewards
    CompleteQuest<public>(InAgent : agent): void=
        if (ValidQuestLog := InAgent.LoadLegoQuestLog[]):

            TrackerDevice.Complete(InAgent)
            ReturnBeacon.RemoveFromShowList(InAgent)

            if (lego_quest_repeatable[Self]):
                TrackerDevice.Reset(InAgent)

            for (Beacon : GuidingBeacons):
                Beacon.RemoveFromShowList(InAgent)

            if (ValidCharacter := InAgent.GetFortCharacter[]):
                Reward.SpawnStudsAtLocation(ValidCharacter.GetTransform().Translation)
            else:
                Print("CompleteQuest failed - no valid FortCharacter found.")

            if (ValidTrigger := EventBindings?.Trigger_OnCompletedQuest?):
                ValidTrigger.Trigger(InAgent)

            # Update persistent quest log
            var UpdatedActiveQuests : []string = array{}

            for (ActiveQuest : ValidQuestLog.ActiveQuests; ActiveQuest <> Name):
                set UpdatedActiveQuests += array{ActiveQuest}
                
            UpdatedQuestLog := LEGO_persistent_quest_log{
                ActiveQuests := UpdatedActiveQuests,
                CompletedQuests := ValidQuestLog.CompletedQuests + array{Name}}

            if (InAgent.SaveLegoQuestLog[UpdatedQuestLog]) {}
        else:
            Logger.Print("CompleteQuest failed - no quest log found.", ?Level := log_level.Error)

    # --------------------------------------------------------------------------------------------------------------------------------------------
    # Helper functions to determine the state of the quest
    # --------------------------------------------------------------------------------------------------------------------------------------------
    IsActive<public>(InAgent : agent)<transacts><decides>: void=
        var Result : logic = false

        if (ValidQuestLog := InAgent.LoadLegoQuestLog[]):
            if (ValidQuestLog.ActiveQuests.Find[Name]):
                set Result = true
        Result?

    CanBeStarted<public>(InAgent : agent)<transacts><decides>: void=
        var Result : logic = true

        if (ValidQuestLog := InAgent.LoadLegoQuestLog[]):

            if (ValidQuestLog.ActiveQuests.Find[Name]):
                set Result = false

            if (ValidQuestLog.CompletedQuests.Find[Name]):
                if (lego_quest_repeatable[Self]) {}
                else: 
                    set Result = false
        else:
            Logger.Print("No quest log found for agent.")
            set Result = false

        Result?
        
    CanBeCompleted<public>(InAgent : agent)<transacts><decides>: void=
        var Result : logic = false

        if (CompletedObjectives[InAgent] = true):
            set Result = true

        Result?

    HasBeenCompleted<public>(InAgent : agent)<transacts><decides>: void=
        var Result : logic = false

        if (ValidQuestLog := InAgent.LoadLegoQuestLog[]):
            if (ValidQuestLog.CompletedQuests.Find[Name]):
                set Result = true
    
        Result?
    
    OnCompletedObjective<public>(InAgent : agent): void=
        if (set CompletedObjectives[InAgent] = true) {}
        
        TrackerDeviceQuestGiver.Assign(InAgent)
        ReturnBeacon.AddToShowList(InAgent)
        
# --------------------------------------------------------------------------------------------------------------------------------------------
# Quest Info class, used to pass the quest description and dialog texts to the dialog UI
# --------------------------------------------------------------------------------------------------------------------------------------------
lego_quest_info<public> := class<concrete>():
    @editable:
        ToolTip := Tooltip_ObjectiveDescription
    Description<public> : string = "Quest description"

    @editable_text_box:
        ToolTip := Tooltip_DialogText_Start
        MultiLine := true
    DialogText_Start<public> : string = ""
    
    @editable_text_box:
        ToolTip := Tooltip_DialogText_Complete
        MultiLine := true
    DialogText_Complete<public> : string = ""
        
# ============================================================================================================================================
# Event binding interface
# ============================================================================================================================================
lego_quest_eventbindings<public> := class<concrete>():
    @editable
    Trigger_OnStartedQuest : ?trigger_device = false
    @editable
    Trigger_OnAbortedQuest : ?trigger_device = false
    @editable
    Trigger_OnCompletedQuest : ?trigger_device = false

ToolTip_Quest_Name<public><localizes>:message = "The name of the quest as it will appear in the tracker_device and dialog UI. MUST be unique!"
ToolTip_EventBindings<public><localizes>:message = "Trigger Devices for linking other Fortnite Creative devices to this quest."
Tooltip_Tracker<public><localizes>:message = "The tracker_device that is required to complete for finishing the quest."
Tooltip_Beacon<public><localizes>:message = "This beacon can be used to guide the player towards the objective of the quest."
ToolTip_Quest_Reward<public><localizes>:message = "The Stud reward that will be given to the player when the quest is completed."
Tooltip_ObjectiveDescription<public><localizes>:message = "The description as visible in the tracker_device UI."
Tooltip_DialogText_Start<public><localizes>:message = "The text that will be displayed in the dialog when the quest is started."
Tooltip_DialogText_Complete<public><localizes>:message = "The text that will be displayed in the dialog when the quest is completed."
lego_quest_dialog.verse
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /UnrealEngine.com/Temporary/UI }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /Verse.org/Simulation }
using { /Fortnite.com/UI }
using { /Verse.org/Assets }
using { /Verse.org/Colors }
using { /Verse.org/Colors/NamedColors }

ButtonCloseText<localizes><public> : message = "Close"
ToMessage<public><localizes>(InString : string)<transacts>: message = "{InString}"

# =====================================================================================================================================
# Quest giver UI
# Dialog Box
# =====================================================================================================================================
en_lego_quest_dialog_response<public> := enum{ Accept, Decline, Close }

lego_dialog_config<public> := struct():
    Title<public> : ?message = false
    Text : ?message = false
    Button_1_Text : ?message = false
    Button_2_Text : ?message = false

lego_quest_dialog := class():
    var OwningPlayer : ?agent = false

    var Canvas : ?widget = false
    var Button_1 : text_button_base = button_loud{}
    var Callback_ButtonClicked_1 : ?cancelable = false
    var Button_2 : text_button_base = button_loud{}
    var Callback_ButtonClicked_2 : ?cancelable = false

    Button_Close : text_button_base = button_quiet{ DefaultText := ButtonCloseText}
    var Callback_ButtonClicked_Close : ?cancelable = false

    Logger<protected>: log = log{Channel := lego_quest_giver_device_log, DefaultLevel:= log_level.Normal}
    
    # ---------------------------------------------------------------------------------------------------------------------------------
    # Dialog UI Flow:
    # 1) Open
    # 2) Wait for player response
    # 3) Close
    # 4) Emit Dialog response as a command
    # ---------------------------------------------------------------------------------------------------------------------------------
    ShowDialog<public>(InAgent : agent, InDialogConfig : lego_dialog_config): void=
        set OwningPlayer = option{InAgent}

        # Setup Buttons
        Callback_Close := Button_Close.OnClick().Subscribe(OnButtonClicked_Close)
        set Callback_ButtonClicked_Close = option{Callback_Close}

        if (ButtonText_1 := InDialogConfig.Button_1_Text?):
            Button_1.SetText(ButtonText_1)
            CallBack_Button1 := Button_1.OnClick().Subscribe(OnButtonClicked_1)
            set Callback_ButtonClicked_1 = option{CallBack_Button1}

        if (ButtonText_2 := InDialogConfig.Button_2_Text?):
            Button_2.SetText(ButtonText_2)
            CallBack_Button2 := Button_2.OnClick().Subscribe(OnButtonClicked_2)
            set Callback_ButtonClicked_2 = option{CallBack_Button2}

        # Create Canvas and add to Player Screen
        set Canvas = option{MakeDialogCanvas(InDialogConfig)}

        if:
            PlayerUI := GetPlayerUI[player[OwningPlayer?]]
            ValidCanvas := Canvas?
        then:
            PlayerUI.AddWidget(ValidCanvas, player_ui_slot{ZOrder := 3, InputMode := ui_input_mode.All})
        else:
            Logger.Print("Failed to show Dialog UI for player.", ?Level := log_level.Error)

    # Accept / Complete Button
    OnButtonClicked_1(Message : widget_message): void=
        CompleteDialog(en_lego_quest_dialog_response.Accept)

    # Decline / Abort Button
    OnButtonClicked_2(Message : widget_message): void=
        CompleteDialog(en_lego_quest_dialog_response.Decline)

    # Close Button
    OnButtonClicked_Close(Message : widget_message): void=
        CompleteDialog(en_lego_quest_dialog_response.Close)


    OnDialogCompleted<public> : event(agent; en_lego_quest_dialog_response) = event(agent; en_lego_quest_dialog_response){}
    CompleteDialog<public>(InDialogCommand : en_lego_quest_dialog_response): void=
        # Unsubscribe from Button clicks
        if (ValidCallback := Callback_ButtonClicked_Close?):
            ValidCallback.Cancel()

        if (ValidCallback := Callback_ButtonClicked_1?):
            ValidCallback.Cancel()

        if (ValidCallback := Callback_ButtonClicked_2?):
            ValidCallback.Cancel()

        # Remove Canvas from Player Screen
        if: 
            ValidOwningPlayer := OwningPlayer?
            PlayerUI := GetPlayerUI[player[ValidOwningPlayer]]
            ValidCanvas := Canvas?
        then:
            PlayerUI.RemoveWidget(ValidCanvas)
            set Canvas = false
            set OwningPlayer = false
            OnDialogCompleted.Signal(ValidOwningPlayer; InDialogCommand)
    # ---------------------------------------------------------------------------------------------------------------------------------


    # ---------------------------------------------------------------------------------------------------------------------------------
    # VERSE UI creation
    # Create a canvas with the Dialog UI
    # ---------------------------------------------------------------------------------------------------------------------------------
    MakeDialogCanvas<protected>(InDialogConfig : lego_dialog_config)<transacts>: canvas=
        Anchor_Root := vector2{X := 0.5, Y := 0.6}

        OutCanvas : canvas = canvas:
            Slots := array:
                
                # Background
                canvas_slot:
                    Anchors := anchors{ Minimum := Anchor_Root, Maximum := Anchor_Root}
                    SizeToContent := true
                    Offsets := margin{Top := 0.0, Left := -765.0}
                    Widget := texture_block:
                        DefaultImage := T_LEGO_Quests_Dialog_Background
                        DefaultDesiredSize := vector2{X:= 1580.0, Y := 394.0}

                # Divider
                canvas_slot:
                    Anchors := anchors{ Minimum := Anchor_Root, Maximum := Anchor_Root}
                    SizeToContent := true
                    Offsets := margin{Top := 0.0, Left := -765.0}
                    Widget := texture_block:
                        DefaultImage := T_LEGO_Quests_Dialog_Divider
                        DefaultDesiredSize := vector2{X:= 1580.0, Y := 394.0}

                # Text
                canvas_slot:
                    Anchors := anchors{ Minimum := Anchor_Root + vector2{ X := -0.0, Y := 0.0}, Maximum := Anchor_Root + vector2{ X := -0.0, Y := 0.0}}
                    Widget := overlay:
                        Slots := array:
                            # Title
                            overlay_slot:
                                VerticalAlignment := vertical_alignment.Top
                                HorizontalAlignment := horizontal_alignment.Left
                                Padding := margin{Top := 80.0, Left := -600.0}
                                Widget := if (ValidContent := InDialogConfig.Title?) 
                                    {text_block:
                                        DefaultText := ValidContent
                                        DefaultTextColor := Orange
                                        DefaultJustification := text_justification.Left
                                        DefaultShadowColor:= MakeColorFromSRGB(0.05, 0.05, 0.05)
                                        DefaultShadowOffset := option{vector2{X := 4.0, Y := 4.0}}
                                        DefaultShadowOpacity := 1.0
                                    }
                                    else {text_block{}}

                            # Dialog Body
                            overlay_slot:
                                VerticalAlignment := vertical_alignment.Top
                                HorizontalAlignment := horizontal_alignment.Left
                                Padding := margin{Top := 135.0, Left := -600.0}
                                Widget := if (ValidContent := InDialogConfig.Text?) 
                                    {text_block:
                                        DefaultText := ValidContent
                                        DefaultTextColor := White
                                        DefaultJustification := text_justification.Left
                                        DefaultShadowColor:= MakeColorFromSRGB(0.05, 0.05, 0.05)
                                        DefaultShadowOffset := option{vector2{X := 2.0, Y := 2.0}}
                                        DefaultShadowOpacity := 1.0
                                    } 
                                    else {text_block{}}

                # Button 1
                canvas_slot:
                    Anchors := anchors{ Minimum := Anchor_Root + vector2{ X := -0.18, Y := 0.25 }, Maximum := Anchor_Root + vector2{ X := -0.18, Y := 0.25 }}
                    Widget := if (ValidContent := InDialogConfig.Button_1_Text?) { Button_1 } else {text_block{}}
                # Button 2
                canvas_slot:
                    Anchors := anchors{ Minimum := Anchor_Root + vector2{ X := 0.0225, Y := 0.25 }, Maximum := Anchor_Root + vector2{ X := 0.0225, Y := 0.25 }}
                    Widget := if (ValidContent := InDialogConfig.Button_2_Text?) { Button_2 } else {text_block{}}
                # Button Close
                canvas_slot:
                    Anchors := anchors{ Minimum := Anchor_Root + vector2{ X := 0.275, Y := 0.05 }, Maximum := Anchor_Root + vector2{ X := 0.275, Y := 0.05  }}
                    Widget := Button_Close
npc_behavior_quest_giver.verse
using { /Fortnite.com/AI }
using { /Fortnite.com/Characters }
using { /Verse.org/Simulation }
 
# ============================================================================================================================================
# QuestGiver NPC Behavior
# -> This NPC just stands in place
# ============================================================================================================================================
npc_behavior_quest_giver := class(npc_behavior):

    OnBegin<override>()<suspends>:void=
        if:
            NPCCharacter := GetAgent[].GetFortCharacter[]
        then:
            NPCCharacter.PutInStasis(stasis_args{AllowTurning := true, AllowFalling := false, AllowEmotes := false})
file_6.verse
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /Verse.org/Simulation }
using { /Fortnite.com/Devices }


# ====================================================================================================================================
# Stores the quest progress per-player - this is persistent data
# ====================================================================================================================================
var GlobalPersistentData_QuestLog : weak_map(player, LEGO_persistent_quest_log) = map{}

LEGO_persistent_quest_log<public> := class<persistable><final>:
    CompletedQuests : []string = array{}
    ActiveQuests : []string = array{}

(InAgent : agent).LoadLegoQuestLog()<decides><transacts>: LEGO_persistent_quest_log=
    var Result : ?LEGO_persistent_quest_log = false

    if(FoundData := GlobalPersistentData_QuestLog[player[InAgent]]):
        set Result = option{FoundData}
    else:
        set Result = option{LEGO_persistent_quest_log{}}
        if (InAgent.SaveLegoQuestLog[Result?]) {}

    Result?

# Set the persistent data stored for this player.
(InAgent : agent).SaveLegoQuestLog<public>(InQuestLog : LEGO_persistent_quest_log)<decides><transacts>: void=
    var Result : logic = false

    if (set GlobalPersistentData_QuestLog[player[InAgent]] = InQuestLog):
        set Result = true
    else:
        Logger: log = log{Channel:= lego_quest_log, DefaultLevel:= log_level.Normal}
        Logger.Print("Failed to save LEGO_fortplayer_persistence_data.", ?Level:= log_level.Error)

    Result?
Sign in to download module

Copy-paste each file above is always free.

Comments

    Sign in to vote, comment, or suggest an edit. Sign in
    📄
    Source
    @ToolTip
    Epic Games Community
    @ToolTip

    Created by a member of the Epic Games community. Rights remain with the original author, under Epic's community content terms. Terms ↗

    View original Sources & licensing
    Request removal
    Last updated Jun 23, 2026
    Verse module (6 files)
    Build failed · 5 errors

    Download the full module (zip) from the code panel above.

    Keep exploring
    More in NPCs & AI · 7 of 10
    Prev
    fortnite patrol path manager v2
    Next
    fortnite guard continuously navigating to set target
    Related topics
    • Lego Action Adventure In Unreal Editor For Fortnite Tutorials
    • Lego Action Adventure In Unreal Editor For Fortnite Tutorials
    • Lego Bloom Tycoon Levels And Devices In Unreal Editor For Fortnite Devices
    • Lego Asset Inventory In Fortnite Creative Assets
    • Using Lego Collectible Devices In Fortnite Creative Devices

    Related

    Open in graph →

    Related topics

    • Lego Action Adventure In Unreal Editor For Fortnite
    • Lego Action Adventure In Unreal Editor For Fortnite
    • Lego Bloom Tycoon Levels And Devices In Unreal Editor For Fortnite
    • Lego Asset Inventory In Fortnite Creative
    • Using Lego Collectible Devices In Fortnite Creative
    VerseIsland · an archipelago of Verse & UEFN knowledge
    🗺️ Island atlas Learn Guides History About & Press Sources & Licensing Status

    Not affiliated with Epic Games. Fortnite, UEFN, Unreal Engine, and Verse are trademarks of Epic Games, Inc. Content is attributed to its source — see Sources & Licensing.

    🦜

    Scout · your island guide

    The Isle of Verse