Firefly in the Dusk🌙

DuskEngine Devlog 6 - Town Extension Edition

Most of the work this past month has been done on the gameplay side of things, rather than the engine. Here's all the new stuff from my not yet announced RPG.

The Town of Kukelberg Got an Extension

There's now a proper town square. Here's a before & after:

gallery image
gallery image

Still placeholders though...

The central area buildings are all made of modular pieces. I think I'll make it so when the player is inside a building, the second floor and roof get hidden, so you can see inside the building properly.

Dialogues and the First Quest

NPCs can now be talked to. I implemented a basic dialogue system with options, and with it, the first ever quest.

Maki, our placeholder NPC, asks you for several items that just happen to lie around the town (two eggs, two healing plants and two mana plants, if you're curious).

Screenshot of a dialogue panel

Getting ready to turn the items in.

Here's the code (Lua) behind Maki's dialogue and quest operations:

View Code
CharaDialogue_Maki = {}

function CharaDialogue_Maki:PlayerHasPlantQuestItems(player)
    return player.ItemInventory:HasItem(InventoryItems.HealthPlant, 2)
        and player.ItemInventory:HasItem(InventoryItems.ManaPlant, 2)
        and player.ItemInventory:HasItem(InventoryItems.RawEgg, 2)
end

function CharaDialogue_Maki:GivePlantQuestItems(player, character)
    return {
        Text = { "Thanks!" },
        OnFinishedTelling = function()
            if self:PlayerHasPlantQuestItems(player) then
                player.ItemInventory:RemoveItem(InventoryItems.HealthPlant, 2)
                player.ItemInventory:RemoveItem(InventoryItems.ManaPlant, 2)
                player.ItemInventory:RemoveItem(InventoryItems.RawEgg, 2)
                StoryVars.CompleteQuest(Quests.MakisPlants)
            end
        end,
        GetOptions = function()
            return self:MakeBaseOptions(player, character)
        end
    }
end

function CharaDialogue_Maki:MakeBaseOptions(player, character)
    local options = {}

    if not StoryVars.PlayerKnowsCharacter(NPCData.npc_kukelberg_maki) then
        table.insert(options,
            {
                Label = "What's your name?",
                OnChosen = function()
                    return self.TellName
                end
            })
    elseif StoryVars.QuestNotYetStarted(Quests.MakisPlants) then
        table.insert(options,
            {
                Label = "Do you need help with anything?",
                OnChosen = function()
                    return self.TellAboutPlantQuest
                end
            })
    end

    if StoryVars.QuestIsInProgress(Quests.MakisPlants)
            and self:PlayerHasPlantQuestItems(player) then
        table.insert(options,
            {
                Label = "I've got your items.",
                OnChosen = function()
                    return self.GivePlantQuestItems
                end
            })
    end

    return options
end

function CharaDialogue_Maki:PlantQuestAccepted(player, character)
    return {
        Text = {
            "Great, do hurry now."
        },
        GetOptions = function()
            return self:MakeBaseOptions(player, character)
        end
    }
end

function CharaDialogue_Maki:PlantQuestRefused(player, character)
    return {
        Text = {
            "What, you don't like doing chores for free?"
        },
        GetOptions = function()
            return self:MakeBaseOptions(player, character)
        end
    }
end

function CharaDialogue_Maki:TellAboutPlantQuest(player, character)
    return {
        Text = {
            "I do need some help.",
            "There are some healing and magic plants around here somewhere.",
            "Bring me two of each, please.",
            "And a couple of eggs too."
        },
        DisallowExit = true,
        GetOptions = function()
            return {
                {
                    Label = "Guess I'll do it...",
                    OnChosen = function()
                        StoryVars.StartQuest(Quests.MakisPlants)
                        return self.PlantQuestAccepted
                    end
                },
                {
                    Label = "Nah, not interested.",
                    OnChosen = function()
                        return self.PlantQuestRefused
                    end
                }
            }
        end
    }
end

function CharaDialogue_Maki:TellName(player, character)
    return {
        Text = { "My name is Maki." },
        OnFinishedTelling = function()
            StoryVars.MakeCharacterKnown(NPCData.npc_kukelberg_maki)
        end,
        GetOptions = function()
            return self:MakeBaseOptions(player, character)
        end
    }
end

function CharaDialogue_Maki:GetStartState(player, character)
    local text = "Hello."
    if StoryVars.PlayerKnowsCharacter(NPCData.npc_kukelberg_maki) then
        text = "Nice to see you again."
    end

    if StoryVars.QuestIsInProgress(Quests.MakisPlants) then
        text = "Have you brought the items?"
    end

    return {
        Text = { text },
        GetOptions = function()
            return self:MakeBaseOptions(player, character)
        end
    }
end

I'm not entirely sure if I want to go with a data driven approach for this (such as encoding dialogues in XML or some other third party tool). I quite like the ability to run arbitrary Lua as the dialogue progresses.

However, the dialogue lines themselves will have to be moved somewhere else, for localization purposes.

Dynamic Parties

The player's party used to be hardcoded for testing purposes, but now it's dynamic, NPCs can join and leave whenever the player wishes. It happens via the dialogue system, right now.

It works like this...

Also noticeable in the video is the fact that Rina's (the NPC) name is shown as "Townsperson" until the player asks for her name. This will apply to all NPCs.

New Sitting Animation and Character Routines

I modeled a bench and added a sitting animation. This, paired with resurrecting the old NPC routine code (its first usage can be seen in this older bsky post), means NPCs can walk around the town and sit on benches to take breaks.

Screenshot of characters sitting on benches

There's a lack of unique NPCs so I copy pasted some.

A Chest You can Open

The first openable and usable container! A chest with items inside. The panel showing the items inside is using the newly added support for Grid Layouts in the gameplay UI system. All item container type UIs use such a grid layout, now.

gallery image
gallery image

Closed and opened...

Calendar in the Diary

Screenshot of an in-game calendar

This is also using a Grid Layout.

It's quite barren right now, but as the game progresses, the plan for it is to contain character birthdays, important events as well as quest deadlines, if any.

The entire diary UI, not just the calendar, will receive extra attention in the near future...

Finally, DuskEngine Improvements