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 Refs called MyDemiguiseObjRefArray that combines (by Appending 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 Vectors. 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 Vectors 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.Gets the Nth 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.