Help with Actor Representation not updating on Mini Map
Hey all!
Since yesterday I have been working on this code. My goal is: If a wheeled vehicle (tractor, truck or explorer) have a Path loaded in, it will show the name of the path as the name of the vehicle in the Map/Compass.
This is my current C++ Code (All temporary, it's mainly for me to understand how the systems work and what the functions do)
The output shows that I managed to update the actor name of the vehicle correctly, I managed to find the representation of the actor correctly as well, but when I update the representation, nothing happens in the game map, the map marker stays as "Tractor"
I thought that changing the vehicle name would change the whole "Tooltip content" chain, but doesn't look like.
Also when I hook into
Hook_GetActorRepresentationText
the vehicle object is always null for some reason that I couldn't understand
Can anyone give me a direction on what to do? I'm a bit lost now. Thank you193 Replies
Update:
I'm pretty sure that this function is the one used to get the marker tooltip text (because when I hook and override it I can see the change in the map):
Funny thing is by using the Accessors, I got access to the
and the
Even changing both of them with the new accessors and updating the representation object:
I still get the word "Tractor" instead -.-
I will keep investigating 🥲 I feel that I'm almost there
Might be that this is related to network as well, I saw some representations that are replicated. Oh no
mRepresentationText
is re-read from the representation object each time the representation is updated. you should not touch that field.Even setting it in the representation object itself?
rep->SetmRepresentationText(newVehicleName);
sorry, I was not clear, when i was talking about representation object i meant the represented object
which is this object for wheeled vehicles
GitHub
SatisfactoryModLoader/Source/FactoryGame/Public/WheeledVehicles/FGW...
SatisfactoryModLoader is an unofficial tool to load mods for the game Satisfactory. - satisfactorymodding/SatisfactoryModLoader
so you might want to probably either hook
GetActorRepresentationText
there or see what it returns and override thatI did already and it works if I override the scope return
But then I was thinking as that method is called many times, it would be better to somehow replace the value where that method gets it from
it is called once when the representation is updated
you are good with just hooking it
all of this data is cached
Ok then! I will try out this way
I was also thinking in adding a text field in the vehicle UI so the player can give it a custom name (like in trains for example), that's why I went down the path of replacing the value in the representation object
if you really want to go crazy you can override AFGWheeledVehicleInfo::AddRepresentation and make it use your own representation subclass
then you can alter all aspects of how the representation appears on the map and the compass
well, mostly compass, map is a bit hard-coded right now
Is it easy to replace the
AFGWheeledVehicleInfo
class a base game vehicle uses?probably not
and i don't really see why you would want to
You meant
AddAsRepresentation
right?yeah
representation system nowadays is very powerful and allows you to do some really cool things
So basically I would override the
AFGWheeledVehicleInfo::AddAsRepresentation
, create my own version of the UFGActorRepresentation
with the whatever naming I need/want and then call AFGActorRepresentationManager::AddRepresentation
Am I in the right path? 😂yeah
might want to derive from wheeled vehicle representation specifically tho
it has it's own small custom behavior
Thank you very much for the explanation @Archengius! This will take me some hours to try out now :LUL:
Ok, this is what I got so far...
But it's crashing:
I think I'm not initializing the Custom Representation class correctly. Feels like a null pointer because I missed setting something
If you have any idea why, please let me know, I will keep trying
Ok, no progress so far, I'm stuck :Sadge:
you are using wrong functions. You only need to call
AFGActorRepresentationManager::CreateAndAddNewRepresentation
and nothing else
you also do not really need to check HasAuthority, this function will never be called on the client i'm pretty sureAh ok! Thanks for the hint!
I updated the code to this:
But looks like I never manage to get the instance of the Representation Manager
Interesting. It should generally always be set.
I found this from someone called Archengius :LUL: :
https://github.com/Archengius/BPPseudoCodeGen/blob/0baffb28ccc98daad944e688a2f4089b4052ffd1/Outputs/Game/FactoryGame/Character/Player/BP_DeathMarker.BP_DeathMarker_C.txt#L124-L132
But it's 4 years old, so, not sure if is still correct. But here it also checks for the HasAuthority()
I have been searching for examples in github, that's why I found this 😂
From what I see in the code, it retrieves the
AFGActorRepresentationManager
from the game state (AFGGameState
)But the method signature uses the UWorld:
Not sure if you mean even deeper in the code
I decompiled the actual code
What you see there is a stub, a placeholder to make the headers compile
I guess there is no tutorial on how to do this right? 😂
It's neither easy nor self-explanatory
In case you're curious, this is what the original implementations probably looked like
This is what the decompiler shows me:
Actually, I think I omitted a check: second function checks object flags on the world
So that you have an idea,
((param_1 != NULL) && ((*(uint *)¶m_1->field_0x8 & 0x60000000) == 0)
is equivalent to IsValid(param_1)
You rewrote this output from the decompiler manually?
Yes
And the decompiler output for these functions looks pretty good already
vanilla code is just this
so I am struggling to see how you're getting null there
if you replace world context with just
self
it should workI tried that but I can't remember the output
I will do it again
Is this on a multiplayer client? I see the actor doesn't have authority for some reason
No, Singleplayer in my machine (only 1 instance)
Crashed
Just for matters of updating you, this is the current code:
And before the crash, this was the output:
Hmmm, I feel that
self
isn't fully initialised yetI ran again just to be sure, I did the exact same thing in game (build a tractor) and now this
vehicle was set somehow in the previous run, now is not anymore
As it didn't crash, I waited a bit and tried again, crashed now:
So I think you are correct @Rex [they/them]
This is the call that get the vehicle object
This puzzles me
I don't have a lot of knowledge yet, but it really feels like something was still loading/populating objects.
Because in my previous save I had vehicles created already (saved) and it was crashing right after loading the session.
Now I created a new save and I don't have any vehicle, that's how I managed to see that difference of the Vehicle being set or not
Is the crash info correct? I'm not seeing how the heck
UEngine::GetWorldFromContextObject
could possibly call AFGWheeledVehicleInfo::execGetVehicleStatus()
I will make it crash again and copy, one minute
Next test (I waited for some minutes before creating a tractor)
So far what I could see is:
self->GetVehicle();
returns a nullptr
right after loading a savegameI still don't understand the callstack
You could open up the minidump file in VS, it's in Saved/Crashes (sort by most recent modification date)
Ok, it's huge, what do you want to see specifically?
The minidump file can be opened in VS
And you can run the debugger on that to see what happened
Yes, I opened it, but it's a UI with a lot of dlls infos
Uff, ok, let me see how can I do that
I guess is here... Which one?
Native Only
Do you need the logs?
You'd want to look at the call stack and the local variables
I don't have much time atm, unfortunately
No problem at all! Thanks for the help Rex
Feels like a Déjà vu
The
FGWheeledVehicle
should have access to the GetWorld method right?yes
I'm lost :LUL:
Or am I too dumb to see what is wrong here or something Supernatural is happening
That address is really weird
This is crazy for me hahaha
The other test, I placed 2 vehicles, 1 right after the other, first one the hook didn't manage to get the Vehicle Object, second one that I placed right after manage to get it and then crash
This is what you wanted to see before Rex?
There's a window for locals, that can show you what the vehicle's properties look like
This?
Yeah, you can expand that and dig around to see if the actor is initialised and such
But I'm honestly stumped
Yeah, I started writing C++ on Friday, so this is way complex for me right now :LUL:
Okay, vehicle is invalid memory, it seems
What about
self
?Do you crash if you try to print
self->GetPathName()
?Lemme see
Looks like I don't
Can you please show the code?
Why is the hook logging a... Material instance?
lol, I can't sent the code
What?!
Oh, did you try DMing me?
It might work here
No, I did not
Well, as a file worked -.-
Okay, what the actual ficsit
self
is a material instanceBut it's typed as
AFGWheeledVehicleInfo* self
whaatTry
self->GetClass()->GetName()
expects :bricko: to be shatAh moment, I packed the wrong mod hehehe
wtf
added this:
UE_LOG(YouNameIt_Log, Verbose, TEXT("Class Name: %s"), *self->GetClass()->GetName());
Ah,
self
was NULL this timeNo, it had value:
Path name was logged
Huuuuuh, does it not have a valid class?
Idk :NOOOO:
I think I understand what is happening
You are trying to hook an interface function. Hooking is a bit weird when it comes to multiple inheritance and can hook wrong function
Which hooking macro are you using?
Yes, from
IFGActorRepresentationInterface
Try using the _MANUAL macro instead
There's none
Idk what's there to replace it then
Nothing, actually
Until the other day, when I made https://github.com/satisfactorymodding/SatisfactoryModLoader/pull/303
I've never seen the _MANUAL macros before, not sure if they're the same as _EXPLICIT ones
But you should hook IFGActorRepresentationInterface::AddRepresentation, and pass (IFGActorRepresentationInterface*) GetMutableDefault<AFGWheeledVehicleInfo>() as object instance
I think that should be it
The signature of the Hook stays the same?
self might have to become IFGActorRepresentationInterface
You can static cast it back to wheeled vehicle info tho
Or Cast<> it back which might be a bit safer
lol, I just saw it in that interface
Ah
I see
Fixed
AddAsRepresentation I think is the name
Indeed
Too far from my knowledge 😂
Call scope argument also is IFGActorRepresentationInterface
Not just self
Ok now seems all fine, besides that I will need to cast
self
correct?Yeah
Ok! Cross your fingers 🤞🏿 I will compile
My neighboors will be mad at me, I just screamed very loud "It f* Worked!!"
Oh wow! That was a long journey, thank you very very very much both of you!
Glad to see it's working now!
I guess somebody really needs to look into why hooking does not work correctly for this case. I was fairly confident that we fixed it at some point
Noted down for now https://github.com/satisfactorymodding/SatisfactoryModLoader/issues/306
I will not be able to help to fix it (cause I have no knowledge) but I'm glad that I at least contributed to find the bug 😂
Finally 🤩
Hey all!
This is what I have done so far
My idea here is that I will need to change the vehicle name in some occasions e.g:
- Vehicle was created when map was loaded (check if vehicle has a path set and use its name)
- Player created a new route and saved it (update the vehicle name - set path name)
- Player deleted the route (update the vehicle name - set default name)
- Player assigned vehicle to existing route (update vehicle name - set path name)
- Player unassign vehicle from existing route (update vehicle name - set default name)
I see that with
AFGVehicleSubsystem
I have many ways to get Vehicles by the TargetList and some other methods but not the opposite... and I failed hard trying to find a way that from the Vehicle
or VehicleInfo
object I can get the AFGSavedWheeledVehiclePath
so then I would have access to its name (via accessor transformer).
Could someone give me some direction here please?
(I don't need the hooks and functions that I should hook, I already identified that, I just need help understanding how to get the path from the Vehicle).
I didn't want to use FindSavedWheeledVehiclePaths
but rather get the association directly, but not sure if is possibleAlso this is always false. I noticed that once I load the save game, the game takes a bit of time to start the auto-drive bot
This worked, I can find the Path with this, but also I can find the path if the
VehicleInfo
is not the one in that path 😂
But I also didn't like this approachWorking fine but still can't get the correct vehicle path specifically for the current Vehicle
Both of them show Path Count = 1 even though the VehicleInfo object is different and the last one has no path loaded
Saw this in the discord:
But also don't work 🥲
Another day, another failure :hypers:
By using this function from
AFGVehicleSubsystem
:
Shouldn't the parameter const AFGWheeledVehicleInfo* vehicle
filter for the saved path for that specific vehicle provided?There's a separate
TSubclassOf< AFGWheeledVehicle > typeFilter
But I think that one is for which vehicle type:
explorer
, tractor
, truck
no?That is just a class
I guess it could be nullptr or something to represent "any path"
So, I tried with this approach first:
This is directly called from the vehicle object itself, my assumption was that it would return only the Saved Paths for that specific vehicle, but looks like no, it's showing for both vehicles in the world, even though one of them has no path assigned to it
Are the vehicles of the same type?
Yeah, 2 tractors
Try adding a different vehicle to the mix to see what happens
ok
Added one explorer first and one truck
Seems to me that
FindSavedPaths
will return all possible paths that can be loaded into that vehicle. Not the path that is actually loaded on it
I will do another test and add a second tractor with a different saved pathI don't think the vehicle saves the saved path
I couldn't find it yet
Or it's in a superclass
I think is here:
(At least I hooked here and I managed to catch the event when I saved the path)
Save the path where? To the vehicle?
I don't know where it's saved, I know that this function is called when you press the button to save the Path in the vehicle
Looks like this is true, because now for all the tractors, the Path count is 2.
So this function does not get the assigned path to that tractor, but rather get all available paths by the Vehicle Type:
Tried using
false
?No, but I will do it now
with
false
the function also gets the paths for other vehicle types (in this case truck
)Hmmm
I really don't understand how this whole structure works, by using the following method I thought that I could check if the Vehicle was using that Target List or not:
Like this:
But then the logs:
Dammit 🥲 This idea it's hard to implement, omg
Which path is the vehicle using?
Should be
First Test Track Name
And this is how I know that this one is the
FGWheeledVehicleInfo_2147474260
:
Wheeled vehicles have this, try comparing this to the list you have here?
just a normal
==
you mean?For starters I'd just log the
GetPathName()
of all to see what you getOh, we are into something
was the comparison between the Path->targetList and the Vehicle->targetList
Please show me the line that prints
Vehicle PathName:
and stuffI don't see
GetPathName()
for any paths
Oh, you only called it on the vehicle
And I said paths when I meant target listsAh ok, I will add it
But it shouldn't be needed if pointer equality works
Now I'm curious what this function
VehicleSubsystem->IsWheeledVehiclePathInUse(Path->mTargetList, VehicleInfo)
supposed to do then?reluctantly starts up Ghidra, defying their own laziness
No no, it's ok, you don't need to, I was just saying
I am curious, though
By looking at this, feels like almost exactly what I wrote :wonke:
IT WORKS @Rex [they/them] !!!!!! What would I be without your help, thank you ❤️
Suffering
exactly what I did .-. Right?
it's comparing the vehicle target list with the target list that you provide in the parameter
In my case: I was providing the VehicleInfo and the targetlist from the Path
I don't know what your code is
This uses
AFGSavedWheeledVehiclePath
Ah, it's getting it from...
UFGSplinePathMovementComponent
?I mean, not sure, what?
I mean
IsWheeledVehiclePathInUse
because it calls vehicleInfo->GetTargetList()
Yeah and then compares
vehicleInfo->GetTargetList()
with the TargetList
in the parameters (in my case Path->mTargetList
)
returns false,
But with the vehicleInfo->GetTargetList() == Path->mTargetList
that I added later, it works fineThe types are wrong, but
field_0x148
is mTargetList
I'm saying that IsWheeledVehiclePathInUse
is doing something else
Hmmm, were your vehicles actually following the path?yes
no change in the savegame
Just from this:
To this:
That's a different function
This is
AFGWheeledVehicleInfo::GetTargetList
Hmmmmmmm, true
You're calling
AFGDrivingTargetList* AFGWheeledVehicleInfo::GetTargetList() const{ return nullptr; }
The result must be different then from Vehicle->GetTargetList()
and AFGWheeledVehicleInfo::GetTargetList()
thenThat's the stub in the project
This I reconstructed myself
Based on this
Actual implementation would be equivalent to this
Then it would be generated somewhere else not even from the Vehicle inside the VehicleInfo
Uh, what is generated elsewhere?
So far I understood:
VehicleInfo
has a reference to a Vehicle
(both of them have TargetLists) but they are differentHence my question, maybe the simulation movement doesn't have a valid target list if the vehicle can't run
VehicleInfo does not have a target list
It gets a target list out of the simulation movement
It does here
ah yeah yeah
I meant the method
There's two methods
Ignore first and last (those are UE magic stuff)
Ok, got it
Either way I will not be able to use
IsWheeledVehiclePathInUse
because the TargetList from the Path is not the same used in the VehicleInfo
So this worked like a charm :alpacool:Yeah, that's why I asked if the vehicles were moving