This guide presents a Demiguise Finder blueprint mod for Hogwarts Legacy and describes how it works.
(You will also find a link to the Level Blueprint for my Field Guide Page Finder which is very similar.)
Beginners should read the previous tutorials before this one. They can be found here:
I assume that you:
The idea is to somehow indicate where the nearest demiguise is located to allow users to collect it without hunting all over the map trying to remember where they saw one. The steps to do this will be:
Quite a bit of research and trial-and-error (or trail-and-error) went into creating this mod. Locating the demiguises turned out to be relatively easy: searching the nodes available in the Custom Editor
revealed that there are three Arrays
in memory that contain all the demiguise co-ordinates (one for Hogwarts, one for Hogsmead and one for the rest of the world). Figuring out the closest demiguise was also relatively easy. But leading users to the location in question proved to be much harder than it should have been. I tried and abandoned several ideas before settling on the one that worked. Here are some ideas that didn't work:
Function
called SetLocatorBeaconUsingType(Type,NameOrLocation)
with Type
as BEACONTPE_DEMIGUISE
. Doesn't work. It turns out Type
means literally Name
or Location
, where Name
means the name of a Student and Location
means something stored in the Locations
SQL data table. Sadly the locations stored in the latter are all junk and changing the co-ordinates of the former tends to crash the game.The solution I settled-on in the end was to use the game Function
SetLocatorBeaconUsingDBLocation(LocationID)
and use some SQL commands to add the location of the demiguise in question to the SQL database. That, it turned out, was relatively easy!
As part of this “research and debugging” process I needed to see what was going on inside the game. But the Print String
Function
that's usually used to see in-game info doesn't work with Shipping
builds like Hogwarts Legacy. So I had to create my own (using a Widget
). You'll see that in the screenshots. But I'll describe all that debug stuff in a separate wiki article. As you can see, it turned-out to be quite a lot of work. On the plus side, it forced me to learn about Widgets
.
A “quick and dirty” alternative (with thanks to Dekita for this suggestion):
UE4SS
.Custom Event
called PrintToModloader
.Print String
.This works because the UE4SS
modloader has been designed to listen for calls to a Function
with that name. Anything you print using that command will appear on the UE4SS
console screen. You don't even have to use UE4SS
as your modloader. You can use Blueprint Apparate Modloader
to load your mod and PrintToModloader
will still work. But of course you do need UE4SS
to be installed. My Debug Widget
does not require UE4SS
.
You can download the Level Blueprint
(.umap file) and Debug Widget
(.uasset file) using the links below. Download both.
Download the Level Blueprint for the Demiguise Finder from here. and the Debug Widget from here.
PLEASE NOTE THAT BOTH OF THESE HAVE BEEN UPDATED SINCE I WROTE THIS TUTORIAL, SO THE VARIABLES AND FUNCTIONS YOU SEE ON THE LEFT OF YOUR PROJECT EDITOR MAY BE DIFFERENT TO WHAT YOU SEE IN THE SCREENSHOTS OF THIS TUTORIAL. SOME OF THE BLUEPRINT MAPS I PRESENT IN THIS TUTORIAL MAY ALSO HAVE CHANGED SLIGHTLY. BUT THE MAIN FUNCTIONALITY DESCRIBED HEREIN IS UNCHANGED SO YOU SHOULD BE ABLE TO FOLLOW IT WITHOUT DIFFICULTY. IF NOT, DROP ME A MESSAGE ON THE DISCORD AND I'LL BE HAPPY TO CLARIFY ANYTHING.
Download the Level Blueprint for the Field Guide Page Finder from here.
Download the Level Blueprint for the Collection Chest Finder from here.
Remember to rename them to MyDemiguiseFinder.umap
and MyDebugWidget.uasset
(noting the uppercase letters) and use Windows Explorer to drop them them in PhoenixUProj\Content\CustomContent\
. Once you've done that they'll appear as MyDemiguiseFinder
and MyDebugWidget
in the Editor
:
There's quite a lot going on here but most of it is related to the Debug Widget
which I'll discuss in a later tutorial. Only the bits circled in red are relevant right now:
Here's what they do:
Event BeginPlay
is triggered when the mod is loaded. It calls the MyInitialize
Function
and then uses Set Timer by Event
to trigger a Custom Event
called MyMonitoringEvent
. All that does is call my Function
MyMonitoringFunction
. Why do we need this? Because when the user collects a demiguise we need to delete the path from the map. (I also use this Function
to delete the path if the user hits SHIFT
-Home
to cancel the search).Keyboard Input Event
is set for when the user presses the Home
key. We'll discuss this below.SHIFT
-Home
. All this does is set MyNearestDemiguise
to 99999
. This is a magic number that the MyMonitoringFunction
watches out for. When it sees it, it knows that the user wants to cancel the search, so it deletes the path from the map.This is largely the same as before, but with some extra stuff, circled in red.
I have used a Sequence
node but that is a mistake. Please make this one long chain in your version!
Here's what the extra bits do (we'll ignore the MyDebugPrintString
nodes for now):
NavigationManager
and BeaconManager
. These will be needed later.Debug Widget
, create a Reference
to it (so we can use it later), add it to the Viewport
(i.e. screen) and make it Invisible
for now.Beacon Info Object Ref
s called MyDemiguiseObjRefArray
that combines (by Append
ing them to itself) the three arrays containing the locations of the demiguises.This is where all the action takes place. It works like this:
MyFindNearestDemiguise
to find the nearest uncollected demiguise. If there aren't any display a message and delete any old paths from the map.Variable
called MyDemiguiseLocation
to the Vector
location of that demiguise then call MySQLAddLocation
to add that location to the Locations
table in the SQL database. Don't worry if you don't know what SQL is. You don't need to. Next it calls a game Function
called SetLocatorBeaconUsingDBLocation
. Well discuss all of that below.For Each Loop
to search through all the items in an array called Beacon Objects
looking for any whose Beacon Type
is listed as BEACON_FINDLOCATION
. That's the Beacon
we just added with SetLocatorBeaconUsingDBLocation
. When it finds it, it sets the icon to a demiguise icon and marks it as Not Suppressed
. Sadly this doesn't actually work because (for some reason) none of the SetLocator
Functions
(there are four) have an icon, and adding one had no effect. This whole Loop
could actually be deleted, but I left it in to show you how it could be done (if you ever need to).This is pretty simple. All it does is:
Loop
through all the demiguises in the MyDemiguiseObjRefArray
executing a custom Function
called MyFindSmallestDistance
each time. All that does is check whether the distance to that demiguise is less than the distance currently stored in a Variable
called MyNearestDistance
. That Variable
is set up with an initial value of 100000000
. Any demiguises found will be nearer than that, so they'll get remembered (in Variable
MyNearestElement
). Finally, Variable
MyNearestDemiguise
is set to the Index
number of the closest demiguise. So if the 5th demiguise in MyDemiguiseObjRefArray
is the closest, MyNearestDemiguise
will be set to 4 (because elements of an array are numbered starting with 0 not 1).Loop
is complete, it checks whether MyNearestDistance
is >
99998976
(which is the closest you can get to 100000000
without the Editor
rounding up). I didn't want to use 100000000
because when there are rounding errors unexpected things can happen. If it is then no uncollected demiguise was found, so the user must have collected them all.Debug Widget
.PlayerLocation
input here. But I do. The Function
's input pin isn't linked to anything, but Function
inputs are available inside the Function
as Local Variables
so I used a Get Player Location
rather than have a long twisting link from the starting node to the node where I needed it. 😎This is even simpler. It uses the Engine
's Distance (Vector)
node to calculate the distance between two Vector
s. Don't worry about why it's called a Vector
. It just means position. In other words, the (x,y,z)
coordinates. We wouldn't really need to do this inside our own Function
except that we need to exclude Vector
s for demiguises that have already been collected. Note the use of the ABS
node as well. This just converts negative numbers to positive, because it doesn't matter whether Vector2
has larger or smaller coordinates than Vector1
(which would cause the distance between them to be positive or negative, respectively). All we care about is the distance between them.
Don't worry too much about what this node does. Basically the only reliable way I could find to draw a path to a destination was using the SetLocatorBeaconUsingDBLocation(LocationID)
node, and the LocationID
is an entry in the SQL database. So I had to find a way to create an entry in the database. This is the node that does that. But it's extremely unlikely you'll ever need to do this. Still, in case you're interested, here's how it works:
Location
input is the Vector
position of the demiguise we're after, and the ZRot
is its rotation (which isn't important so we'll assume it's always zero). I convert this to Xpos
, YPos
, ZPos
and Rot
String
values because if I just plugged them straight into the Format text
node they'd be formatted with commas signifying thousands, which messes up the SQL command.Format Text
node formats the SQL command we'll need.Sequence
Node
above, which: 1) Uses Make Literal String
to construct another SQL command to delete any previous entries we created. The Db Operate
Node
then executes that SQL command. A debug message is printed to the debug widget indicating success or failure. 2) Passes execution to another Sequence
Node
(which is just there to stop the blueprint map getting too wide). This prints the SQL command created earlier to the Debug Widget
and then calls Db Operate
to execute it. Again, success or failure is reported to the Debug Widget
.Apart from the stupid name, this Function
is largely self-explanatory. it just converts floats to strings and removes the commas:
Once the path is displayed on-screen we need to watch for the user actually collecting the demiguise. When that happens we need to delete the path from the map. We also need to delete the path if the user decides to abandon the search for now. This Function
is called every 1 second
by the SetTimeByEvent
Node
in the Event Graph
and does both of those things, as follows:
Locator Beacons
there are (BeaconType
= BEACONTYPE_FINDLOCATION
).>0
stop immediately. (This is important to reduce performance impact. With anything that's constantly executing you need to terminate it ASAP.)>0
then continue because we are (or were) searching for a demiguise.Get
s the N
th item in the MydemiguiseObjRefArray
where N
is the MyNearestDemisguise
Integer
we calculated earlier.BeaconState
= BEACONSTATE_COMPLETED
).RemoveStudentLocatorBeacon
to delete the path (which works even though this isn't a Student
), and sets MyNearestDemiguise
to -1
.MyNearestDemiguise
= 99999
. If so, the user must have hit SHIFT
-Home
to abandon the search, so delete the path, etc.A simple Function
to count the number of Beacons
of a specified type. Note the use of the ++
Increment Node
which adds 1 to MyCount
and then sets MyCount
to this new value.
And that's it (apart from the Debug Widget
stuff). Not so complicated really. 😁
Many thanks to Darkstar for their invaluable help and advice during the creation of this mod.