#multiplayer
1 messages · Page 283 of 1
but the world only created at request.
it's not like dangling like battlefield servers
You'd probably be better off letting your users host severs, then you don't need to worry about server costs.
Player A login, the cloud service will host a dedicated server for player A.
Player A Join that server (his world that exist on the cloud).
Player B wants to join player A world.
Player B travel to player A dedicated server.
Player A left, since dedicated. Server will remain alive until all player left the world.
can you look dm ?
why not just post it here?
I'll send you an example game.
you have other people with actual networking expertise.
mine is almost non existence.
it's fine to post examples, it doesn't count as advertising.
Actually, there's a 2D game, and I'm making a 3D version of it.
write google growtopia
simplicity of the game doesn't correlate much with the difficulty here imo.
but yeah I googled it.
So how does the logic of that game work then?
I don't have the source code, wouldn't know.
but not made by UE so, kinda irrelevant?
Most likely, only one server is opening it in the world that's opened. I'll probably do it this way; there's no other way left.
not sure what 'one server is opening it in the world that's opened' mean.
level map
The reason I’m having this issue is because there are two maps.
The first map is used for entering and selecting the world name, and from there it switches to a second map that is used as a template.
When leaving the first map and entering the second map, the server connection is lost. Because of this, players cannot see each other.
At the moment, it feels like only one option is left, and I think this will consume a lot of RAM:
WORLD_A opens → Server starts (Port 7777)
When WORLD_A has 0 players → Wait 5 minutes → Server shuts down, RAM is freed
WORLD_B opens → Server starts (Port 7778)
While WORLD_B is active → Server keeps running
7777 = World Select (Lobby)
7778 = WORLD_A
7779 = WORLD_B
7780 = WORLD_C
...
Each world = separate port = separate server instance.
My English isn't very good, I hope I've been able to explain it.
Player X request AWS backend (serverless / aws lambda) -> Hey create me instance of World A -> Player joins World A.
The server can run timer inside the game world. If no player within 1 minute -> Make API call to shutdown the server.
If you actually have player base, you definitely will need to keep track on the bills.
Thank you, I will do some research.
All good, but I think most people here would be in agreement that dedicated server per world is the way forward for what you want to do if you are going to use the networking framework that comes with Unreal.
I’m planning to rent dedicated servers with fixed RAM, for example 256 GB.
If this gets full, I’ll move to a second server.
I think this approach would be better for me.
With systems like AWS, if an exploit or abuse happens, I could lose a lot of money.
Server 1 (EU) - 256GB → ~600 active world
Server 2 (EU) - 256GB → ~600 active world
Server 3 (EU) - 256GB → ~600 active world
─────────────────────────────────────────
total: 768GB → ~1800 active world
Does that make sense to you?
If there are 0 people in the world, the server will shut down.
That would be extremly not cost effective.
the point of AWS fleet is that you can create instances on demand.
So you just make the World if there isn't a server for that world yet and a player wants to connect to that World.
if there is already one and there is still slots for player, then other player can just join existing world.
and when there are no players on world Z, simply do nothing. You don't need to make an instance to host world Z.
Scenario 1.
No player, no active server.
Scenario 2.
Server1 -> 7 player, 1 Active World.
Server2-> 1 player, 1 Active World.
Server3-> 1 player, 1 active world.
But the price will remain fixed on the private server.
I'll switch to AWS later if there are many players; for now, I'll stick with the private, fixed server.
I'm so confused xd
For game testing, a VDS or a dedicated server will be sufficient for now.
I’ll evaluate things later; I realized that I can’t move forward by overthinking like this.
It’s been 4 days :/
thank you ❤️
How do Palworld dungeons work you think?
From what I recall from them.
There just a sub level under the map sorta deal?
From what I've played of the game, I'd be honestly shocked if they were anything more than a blackbox under the world using a separate lighting channel. If even a separate lighting channel.
anyone know why when i put my project on ue 5.6.1 multiplayer testing just broke. client window opens and no hud or abilty to move plus can even see the server walking around.
As in updating the engine version caused this?
yes
is the player controller/gamemode setup the same? are you seeing your intended gameplay classes for things?
Curious where you create your HUD or add your mapping contexts?
im using steam subsystem but im not sure how relevant this is for in editor testing
yeah no HUD would be my first thing to look at too.. I would assume the player controller is doing something weird
Steam stuff won't matter here.
showdebug EnhancedInput can reveal if you have any IMCs mapped right now at all
ah im seeing it fire on the client from the player
i also setting input maping in the controller. I think this is an issue with local player creation
show how you set it
whats even more interesting is the host is standing infront of the client in that SS and its not even showing them. however if I pickup one of the items as the host on the table it disappears
skow your pie settings
its set to play as listen server
anything in the output log?
if i kill the player it throws errors regarding being unable to access the clinets player controller
I suppose before that
no error besides that
if i remove the local player bool i get these errors tho
I am referring to the output log
ok i figuered it out. at some point i must have set my gamestate as a child of gamestate base which was casuing the issue
What's a good way of making a certain event not trigger until all players are loaded into the map
you could have an actor that on beginplay the client calls an RPC through its PC or playerstate or something to indicate its ready
and not play the event until the server recieves RPC's from all clients
there might be a better way tho
What can cause to fail replication of sight attachment on other clients?
but it always prints All Conditions Passed, this means the OnRep_EquipStatusChanged is called on all clients and should work fine, but its not
weapon calls UseAccessory and passed self
Are clients making it all the way into the EquipAccessory and past the if checks?
yes
WeaponSlot is a replicated uint8 just in case to use it inside OnRep_EquipStatusChanged to get a target weapon from this slot and reuse it
I tried to have a replicated weapon directly insted of the slot but it changes nothing
Got nothing off hand. If those components are replicated, it's worth noting that most of this is unnecessary, as replicated components will replicate attachment automatically. I'm unsure if that'd mess with this, but you wouldn't think so.
Can you see the mesh anywhere? Is it setting the correct mesh, it's just not attaching?
yes setting the correct mesh only on server and owning client
on other clients there is nothing attached
Where are the other clients failing at setting the mesh?
I can't find it, since it passes all checks
That implies that GetSlotStaticMeshComponent is returning null, it's not passing CanEquippAccessory, or the Accessory->Mesh is not set.
that works fine it returns true
UPROPERTY(BlueprintReadOnly, Category = Mesh) TMap<EWeaponAccSlot, class UStaticMeshComponent*> WeaponMeshes;
class UStaticMeshComponent* AWeapon::GetSlotStaticMeshComponent(const EWeaponAccSlot Slot)
{
if (WeaponMeshes.Contains(Slot))
{
return *WeaponMeshes.Find(Slot);
}
return nullptr;
}
initialized on weapon::beginplay
What about the mesh on the other clients? On the accessory.
beginplay calls on other clients also so it will be initialized on all clients
I mean the UStaticMesh.
Where you're calling Mesh->SetStaticMesh(Accessory->Mesh);
This Mesh property in Accessory->Mesh
ah, this is also fine since UAccessoryItem is derived from a replicated UItemBase and the state of it is replicatd via
MarkDirtyForReplication();
void UItemBase::MarkDirtyForReplication()
{
++RepKey;
if (OwningInventory)
{
++OwningInventory->ReplicatedItemsKey;
}
}
Unsure then. Either mesh isn't where you thought it was and the mesh property is fine and just attachement is failing.
Are there any logs? There's usually an excessive amount of logs around attachment code.
no warnings or anything which can tell why it fails
even with custom uelog it says attachment success
but there is no mesh, and the sight slot static mesh is also empty in the details property after doing f8
but when I change the world to server, the mesh is there
on other client
If you're 100% that that code is running on other clients and passing those checks then that means either that mesh is null in the Accessory->Mesh. Only other option is that something is overriding it later on with another SetMesh call on that client, setting it back to empty.
The only place setting it to null is UnEquipAccessory(UAccessoryItem* Accessory)
break pointed it, its not called in this process
Can you break just past this If on the other client it's not working on and see what is in the Accessory?
if (UStaticMeshComponent* Mesh = GetSlotStaticMeshComponent(Accessory->Slot))
{
Mesh->SetStaticMesh(Accessory->Mesh);```
Sure, trying it now
breakpoint hits it but no visual static mesh on other clients
I tried declaring a replicated UAccessoryItem ptr, using it works fine
but this is already going bad, since UItem is also replicated 😄 and I should not be needed to have this extra replicated accessory object
new cleaned refactored code
but it doesn't solve the issue
maybe I need to call return EquipAccessory(AccItem); with a delay?
@kindred widget Issue is solved
was related to WeaponMeshes initialization on beginplay
moving it inside postbeginplay, solve the issue
but still not sure why beginplay will init it not correctly
even the check was passed, the mesh in the slot was found but no visual replication
need help with something ..
packaging for server with a server default map 'x',
packaging for client with a game default map 'y'.
when I launch the game, it start a server instance, joins that ip:port ..
as per logs, its joining to the map x , I can literally see the character in the map x on screen too..
but when the map opens, it starts loading again and it default to the map 'y' (no longer in the server)
log says, game thread blocked for 5 seconds or 7 seconds like that
how can I fix this?
It probably fails to load the map and then opens the default one.
Answer: package the correct map.
yeah, what I think is that, because of it says game thread blocked for 5 seconds, it thinks that the map is failed to load .. but in truth, the map is opened and I can see it tbh .. but it reloads it to the default map saying things like 'cooked map does not exist' .. "if its not there, then how does it even opened in the first place" .. so, map exist .. it just falsely thinks no map cause it takes too much time to load ig ..
Do you have LevelStreaming in your game?
Is maybe one of the instances you are loading not packaged?
Also which log says that it's blocked, the Client or the Server?
And what does the other log say then? Does the Client just time out and gets sent back to the original map?
So I have a question, anyone able to figure out why exactly this seems to be giving me different results when playing as client vs when playing as server? Obviously it would be because the servers rotation (or location) are different compared to the local player but why is that other than the regular latency of course.
When shooting a bolt as client it's coming a slight bit more from the "right", while as server it's going exactly to where the middle of my screen/crosshair is
When moving or standing still?
doesn't matter
happens the same regardless
start printing those locations, it's one of them
I know if moving it's just the latency diff
ControlRotation is compressed when sent to the Server.
GetBaseAimRotation still uses that under the hood.
Is it still inaccurate if you shoot from the client, but you made sure to not move your aim for couple of seconds before shooting?
Honestly, I'd just let the client send their rotation to the server. The client may cheat and modify that value at will, but I'd argue that can counts as "auto-aim" cheat
Yes, even with 0 latency when shooting from client the bolt is always going a bit more to the right instead of dead center. And yea I think i'll do exactly that
Client already sends the rotation anyway
Via the cmc
Just that it's compressed a lot
Which is why they see the difference between the center of the screen and the actual shot.
🤔
So if I want it to go exactly where Im looking I gotta do it in a way that doesn't compress the rotation?
Is that how fps games do it?
Won't there still be a discrepancy between client & server's rotation if the latency is high enough?
yes always
That means just sending the fact that you fired isn't enough
I'd probably send over "Hey I fired in this direction"
direction being a unit vector
I did that with my game. Just with rotator
Still not enough to perfectly duplicate the shot, it really should be "I fired in this direction at this location"
Depends on what you're after really
This is why multiplayer is hard
Yeah, I also send the location, but the server checks if the location is "too far" from the server location. I also have a lag compensation system, which rewinds everything to the time when client fired. Any particular reason on why you are using vector direction instead of a rotator?
I just like vectors better and my aim direction is a vector fundamentally anyway
Probably get a bit more precision for the bits but I'm not sure
Well yeah, but the CMC uses the compressed version locally. Otherwise, you'd have constant corrections
May sound silly. If you use the unreal's FPS template, the crosshair actually isn't centered (a bit to the left)
That is probably unrelated :P
I love working on co-op games.
Client: "Hey - I hit this target!"
Server: "Alright"
Done.
nah I don't haha
🤔 then how would you fix the difference
Either you compress is locally before using it to shoot. Or you send an uncompressed rotation/direction and don't use the control rotation
https://tenor.com/view/sure-oh-ok-if-you-say-so-gif-3474500996908065231
Server to client:
It's probably better to send the rotation/direction, because the control rotation is communicated via the CMC and might not be in sync with the RPC for shooting.
actually I think I know why i had issues with the camera
@haughty ingot or just a couch coop game so all the problems originating from ping don't even exist.
hello
anyone who can help with unreal multiplayer the problem im facing is - im opening level using servertravel , i have 2 maps one with thirdpersonpawn and other with explorerpawn already placed in maps using autopossess, i travel from thirpersonpawn level to explorerpawnlevel it works fine but when i come from explorerpawn to thirdpersonpawn map there is no thirdperson character why so?
even if first i open explorerpawn level and then go to thirdpersonpawn level it doesnt work pawn is not there
Couch coop - that's rich. In the year of our lord 2025.
So 5.6 introduced a bug with non-seamless server travel. From what I could debug, the client closes a sock and then when trying to connect to the server again, uses the same socket and throws an error saying it can't use it because that socket was already specifically closed.
Not sure if that's the final issue, but this bug list indicates the issue is known and there is a workaround. However, the workaround didn't work for me. https://eoshelp.epicgames.com/s/article/Why-is-a-P2P-connection-failing-after-a-non-seamless-hard-server-travel?language=en_US
Anyone else encountering issues with non-seamless server travel after upgrading to 5.6? I'm using a Lyra stack, so it might be realted to that.
Thank You for the questions xd!,
I tried with both packaged once before and it didn't work ..
and since it takes so much time, after that I was testing client from editor itself .. (even in local editor server it didn't work though)
anyway, tried packaging both again yesterday, just tried them and it works!!
using World Partition btw .. idk WP maps must be cooked! Thank You
Hey, one big point here is that you shouldn't non-seamless ServerTravel, even if that's "possible". Subsystems like Steam will throw a fit and it's generally not correct to force a client through the whole login process again.
Doesn't mean that it should be ignored that there is a potential bug, but in theory you shouldn't even use that anyway.
Direction is only accurate if the start point is same on client and server
I've found a starting position to be best. I just check against the bounding box
Does it cost enything for a server?
A server is just another computer. Either you have your own, or pay to rent one.
I dont know how to make one on ue5
ok
How do I make one ?
Start by reading this
https://cedric-neukirchen.net/docs/category/multiplayer-network-compendium/
This compendium is meant to give you a good start into multiplayer programming for Unreal Engine.
Ok thanks
question about rpc multicasting.... lets say i have a replicated game object "Fire".
"Fire" applies damage to any unit that walks in it based on collision detection.
e.g. something like rpcMulticastApplyFireDamage(TArray<ASomeActor>& actors)
my issue is, the server spawns a replicated actor that happens to be in the fire. in the same frame that BeginPlay() is called, it is rpc multicasted to clients, where it hasn't spawned yet, so the pointer is invalid on clients and game crashes. whats the proper way to handle this
WHy are you multicasting to apply damage?
why not? it doesn't need to be damage though, it can be anything. some multicast on a newly spawned actor reference not available on clients yet
also to be clear, i'm only talking about reliable transmission
health is a variable, you update it on the server when the actor touches the fire and it will automatically replicate to all clients
(assuming you already have replicated attributes working)
i'm not using replicatd attributes though
assume it's not health for sake of discussion. assume it's receiving an item or something
any state change on a multicast for an actor that exists on server but not client yet
the concept is that if you have a variable that represents a state of something you shouldn't use a multicast to update it, it should be a replicated variable
otherwise if you update everything with multicasts how would you handle late joins and actors that just started being net relevant?
take damage > reduce health variable on server > it replicates to clients
receive an item > server updates array of items > it replicates to clients
if you join the game afterwards the server will already send you the most recent variable value
that makes assumption on genre of game though
if i'm working on a rts where you control multiple units, by the time game starts there are no late joiners
but it's all related... how to handle state replication when actor doesn't quite exist yet on client
well the concept is the same regardless of genre, you basically only use multicasts to communicate one time and transient events
agreed.. but going back to the original question, say you need to communicate an actor that does not exist yet everywhere
do you send an id instead of a actor pointer, and then manually queue it and process it once you know it's been initialized?
If you end up spawning a new Actor and instantly sending it out via an RPC, then you can totally run into a situation where the Actor is simply not valid yet on the receiving end.
There are probably a few ways to handle this. Pre-spawning the Actor (e.g. pooling), or communicating it with an ID instead, as you said. You might also swap the RPC with a RepNotify, or maybe you notice that you can just use the BeginPlay of the Actor itself.
if it's like a list of actions I would probably use a queue
the client can signal to the server that the actor is "ready" and it can send everything already queued, or you can send the actions to an actor that you already know exists on the client and it can handle its own queue
but it still sounds something that could be a replicated variable, the engine would take care of replicating it once the client version exists
cool. follow up question, how are replicated state variables implemented underneath the hood? for example, a health variable. i imagine the system is sending delta health via reliable multicast (until a ack is received)? fundametally you have to rpc a value and manage state updates via a queue, it's not like it magically appears on clients
I wouldn't call that RPCs anymore. Very low level it's just all parsed into bytes and then sent to the Client via the Socket connection.
RPCs or replicated variables, they all end up going through the same stuff in the end. Just that, when parsed back on the other side, they either end up being filled into whatever Actor/Object they should be in, or end up being a function call.
gotcha. yeah thats helpful
I wouldn't worry too much about that. The main benefit of UE is that you don't have to care.
Hey, does UE combine RPCs from a single tick into one? For example, I have a for-each loop with up to 10 iterations, and I’m sending an RPC with actor input each time. Does UE combine this into one RPC with all actors, or should I use one RPC with an array of actors as input and do the for-each loop on the client side?
why would it?
every rpc is a single thing, rpcs dont fire the instant you call them, they are done on the net tick
but 10 rpcs per net tick if reliable could overflow the reliable buffer
if its unreliable its fine, this is wht CMC does
every tick it sends the data to the server, unreliable
I think I heard in some talk that UE is optimizing RPCs and combining them if it can, but I couldn’t find it, so I asked
Iris might do optimzations
but still, it has to be a unique one cause the data will be different
batching only works if the data is the same to all recepients
also you should never send reliable rpc's inside tick or things that run all the time
cause the reliable buffer is only so much
which is why CMC uses unreliable rpc
cause if a rpc doesnt make it, no big deal, cause it will get the next one possibly
it would be far cheaper to send input compressed down to represent the actual changes from the first element
if you rely on a perfect stream of inputs you can just ack the latest sent turn/frame or some timestamp
I am unsure if there are any built-in ways it tries to batch RPCs but larger RPCs must be split into separate packets and reformed on the other side
I send inputs in an unreliable in the form of a buffer of elements that represents (input) (num after that are identical to this on)
which is a relatively dumb but effective way of compression
in my case my inputs are a simple struct with a vector and some booleans. as you can probably imagine the vector is the lion's share of the bandwidth
WIthin the same actor replication channel, so if I have 5 variables that are replicated on an actor, and I set them all, if I set the LAST one to be rep notify, I could guarantee the other 4 would be valid and replicated down to the client by the time the on rep fires (order within the 4 isn't deterministic/guaranteed, but they all would be ready when the on rep hit). HOWEVER, when I upgraded from 5.4 -> 5.7; this no longer seems guaranteed. Anyone have any insights?
I'm surprised this was guaranteed on any version. The general rule is that if you want variables to replicate together for sure, put them in a struct.
Yeah a unique quirk of the putting just the LAST one set as rep notify w/ actors replicating atomically; was definitely a little sketch but it did work reliably
but
now there are challenges
Yeah wasn't an actual guarantee by the engine, guess it may have been the case until that version
Interesting though
Iris will also cause any of such guarantees to be gone, btw.
There is a talk about Iris, where Epic outlines the headache they had when moving Fortnite over to it. They had a lot of cases where code was living based on those vague guarantees, which then caused lots of bugs.
It's def better to be explicit for this stuff.
oh god
You could put the variables on a struct which is marked as atomic.
But that has other implications.
please tell me Iris keeps the guarantee that (non-mapped) replicated properties of an actor are available at begin play on clients
it's my favorite and it'd be a shame to see it gone
That should be kept, because the properties are replicated via the initial bunch that also spawns the Actor.
In this session recorded at Unreal Fest Orlando 2025, we dive into the design behind the Iris replication system, an opt-in replication system that works alongside Unreal Engine's existing replication system.
You'll learn about the compromises made during design to remain as backward-compatible as possible, and about the most efficient path fo...
(Timestamp)
What he lists was already sketchy without Iris, but we did notice similar problems when upgrading to it.
Basically, stuff that was otherwise magically working.
I'm conflicted because in my mind you should not be relying on that stuff in the first place but I never realized it was really gauranteed
Iris has like 20 cvars for mimicing how the old ordering works
it's kind of funny how everything is really orderly and raw C arrays and then the instant it gets to actually applying state they have to shuffle and reorder, defer things etc like 20 different ways to make it fit replication of actors
WHAT
goddamnit
I missed that... that would have been nice
There is a bug in this code where backpack will still be equipped even if the old backpack capacity check fails.
this happens 1/10 times at random attempt
if (Item->ItemType == EItemType::E_Gear)
{
UGearItem* GearItem = Cast<UGearItem>(Item);
if (!IsValid(GearItem))
{
FText UnKnownItemName = FText::FromString("Unknown Item");
FFormatNamedArguments Args;
Args.Add(TEXT("ItemName"), UnKnownItemName);
return FItemAddResult::AddedNone(0, FText::Format(FText::FromString("Couldn't Equip {ItemName}, Invalid Item"), Args));
}
// Special handling for backpacks
if (GearItem->Slot == EEquippableSlot::E_Backpack)
{
const bool bCanAddBackpack = GetCurrentWeight() <= GearItem->WeightCapacity;
// Check if current weight would fit in the new backpack's capacity
if (!bCanAddBackpack)
{
FText ItemName = GearItem->ItemDisplayName.IsEmpty() ? FText::FromString("Unknown Item") : GearItem->ItemDisplayName;
FFormatNamedArguments Args;
Args.Add(TEXT("ItemName"), ItemName);
return FItemAddResult::AddedNone(GearItem->GetQuantity(), FText::Format(FText::FromString("Couldn't Equip {ItemName}, Capacity out of bounds"), Args));
}
}
// Add Item and will be auto equipped/replaced since its a gear item
UItem* NewItem = AddItem(GearItem, GearItem->GetQuantity());
return FItemAddResult::AddedAll(NewItem->GetQuantity(), NewItem->UniqueItemGuid);
}
I think it just happens really quickly that one misses some edge cases in ordering and they never really show up as bugs.
Replicating a variable and then sending an RPC is too obvious to count as an edge case here though. It's more things like variables replicating together and such things. There is a docs page about RPCs and Property replication, but that only covers non-Iris.
you left out how this relates to multiplayer
This is called by server RPC
Since you early return, it can't really equip the backpack, even in 1 out of 10. So the check either succeeds, which you aren't expecting, or the equipping happens elsewhere
Equipping logic only reach once item is added
and equipping also checks on server if this item exists in inventory
Yeah but that's still all I can tell you based on what you shared.
Also no clue what is passed into the RPC or what the rest of this function does
uitem is passed after a successful with item on server
and it directly checks the type of the item, so backpacks are gear items, it will not reach other types
FItemAddResult UInventoryComponent::TryAddItem_Internal(class UItem* Item)
{
if (!GetOwner() || !GetOwner()->HasAuthority())
{
// AddItem should never be called on a client
return FItemAddResult::AddedNone(-1, LOCTEXT("ErrorMessage", "Server Blocked Suspicious or Invalid Operations!"));
}
if (GetOwner() && GetOwner()->HasAuthority())
{
if (Item->ItemType == EItemType::E_Gear)
{
"backpack will still be equipped even if the old backpack capacity check fails"
remember: we can ONLY see what you tell us
I have no clue what happened before to reach this point, what the state of the weight is, what the old backpack does etc
Yeah, like level 2 backpack is full and player trying to equp level 1, it should fail
should return: return FItemAddResult::AddedNone(GearItem->GetQuantity(), FText::Format(FText::FromString("Couldn't Equip {ItemName}, Capacity out of bounds"), Args));
It's like me sending you a picture of a light switch, telling you the light sometimes doesn't turn on.
Like, the hell we gonna do with that info.
If it SHOULD do something, start placing ensures.
Breakpoints, UE_LOGs, whatever.
We have way less info than you. Your code looks fine, so there is something only you can figure out by debugging it.
const bool bCanAddBackpack = GetCurrentWeight() <= GearItem->WeightCapacity;
// Check if current weight would fit in the new backpack's capacity
if (!bCanAddBackpack)
it is not posssible for me to guess what happens here
I breakpoint this return, and it reach when weight check fails
Yeah, but you said it sometimes passes it.
current weight is the current used weight of inventory
So put some UE_LOGs up.
how can we possibly know what GetCurrentWeight() does? does it rely on the input rpc? some other value?
I assume the logic wants to allow adding a backpack if it can carry the current inventory.
Yeah when backpack equipped it not reach the breakpoint
So you can swap out the current Backpack with a new one, as long as it doesn't end up being too small.
that is not nearly enough information to remotely guess what happens here
Does the Backpack have a weight on its own, maybe, and that is disregarded in your check?
Either way, please place a bunch of UE_LOGs across that function.
And try to reproduce the issue.
Then check what prints in the case that it fails.
I guess if it helps I don't want you to describe the name of the function in english... I want you to describe the actual data flow this takes and what can change it and when
And try to understand why.
float UInventoryComponent::GetCurrentWeight() const
{
float Weight = 0.f;
for (auto& Item : Items)
{
if (Item)
{
Weight += Item->GetStackWeight();
}
}
return Weight;
}
We can't remote debug it this way.
Yes, it applies its weight to the player inventory weight
const float CurrentWeight = GetCurrentWeight();
const bool bCanAddBackpack = CurrentWeight <= GearItem->WeightCapacity;
// Check if current weight would fit in the new backpack's capacity
if (!bCanAddBackpack)
Trying to make this take less lines will make it harder to debug... assign it to a local so you can reason about the actual value imo
increasing it based on level
I really need to make that extra float? or it is just worth to try?
This actually solved it
tried 100 times replacing backpack, it fails always if the weight not match
the idea here is so you can see what it actually is in the debugger
the compiler will instantly smash this into nothing anyways
I like to assign the results of things I compre to local variables so it's not impossible to decipher what happens with a breakpoint
you don't always need to and you can put it back to being inline if you want, it's just something I prefer to do because I use breakpoints to watch values
you can actually just call the function I guess in the debugger but Rider lldb is just about useless at doing that I find lol (requires module prefix which is annoying)
I printed the return values from both local/inline and both are same
not sure why making it explicitly local solves the issue
maybe its a float overflow?
can you explain what a float overflow is??
do you assign it to infinity or something?
that's more of a "floating point precision" thing if you are trying to find the word
but doing greater/less than is perfectly fine
yeah, cause my items has weights like 0.25f 0.013f etc
but if it's expecting it to be like near zero and the result of the weight is from a lot of math it could be that the actual value is like 0.00000000001 or something of the sort... a lot of vector math for example must use smaller values above zero
but that's unlikely to be what is happening here unless the weight of each item changes from something external in a weird way
its changing like if each ammo box can contain limited bullets amount and each bullet has weight
so on each fire, the inventory weight changes
so once it reaches zero ammo does it actually hit zero/below zero or some small value?
I think so, and saving the return in a local variable makes it sure to not depend on the new changed weight maybe
I will change the system to use the inventory weight based on uin8
and each will have wight also in uint8
that will actually overflow almost immediately...
I like the idea of keeping the individual items as a byte max to just reduce bandwidth but honestly uint16s are already sent packed down to that range
if the total can be derived locally there is no reason to try to reduce the range of values into a byte when summing them
I have an interactable draw bridge that I am animating via a level sequence. The final state allows the player to walk across it. The Playback is synchronized, but I have a problem with late join, or players who become relevant after the animation is finished.
To handle this case I call Go to End and Stop Level Sequence Player node on these clients. Visually it now looks correct, but when walking on it I get a lot of net corrections, as if the physics state does not match.
Clients who observed the animation work fine.
any ideas?
OK I think this is an issue with the Go to End and Stop node, rather than a multiplayer thing. I tried calling it on server in Begin Play and I'm not getting corrections, but when I jump while stood on the bridge I am flying very high in the air.
Is each block an Actor? 😭
Because they have 3 different properties: when they break, there is a chance to drop gems, blocks, and seeds.
I couldn’t do this with ISM. Additionally, I made trees by using child actors.
ISM also caused problems for tree growth.
I’m wondering if there is a more logical approach.
Definitely is
Each block could be an ISM or mass actor (I forget what they call it - but they used it in Lego FN). Then, you could store what they could drop somewhere else. And when the ISM is destroyed, look up what it should drop by id and drop the thing.
Something like that - roughly at least.
But Mass instanced actors were made for this exact scenario
hmm
I will work on it.
Okay, what if I just replicate the actors once instead of constantly replicating them with RPC?
"Hey Mr. Block Manager, I want to break the block at 234,5363,23, thanks"
it then nukes the instance, updates whatever ISM stuff needs updating, and drops the loot
Minecraft as 1 actor per block is never going to scale, at all
You don't even need ISM, another route is to have the manager keep track of the LOGICAL state of the world, and have some localized "renderer" that meshifies it around a player.
Minecraft doesn't even store the state of the world's blocks, only those that have been changed from the generators output. It really only saves diffs vs the generated. Even then it's gobs of data.
2b2t server's world was around 50TB
Scratch that, 80
You'll still want ISM but that's not impossible
I think we might have messed up. I didn’t expect the multiplayer part to be like this; I thought I could handle it.
I’ll have to go back to ISM.
I’ll come back once I solve this issue xd
Start with getting the server and client to agree on what the 50k blocks are.
I don't mean block actors, just 1 byte per block or an int per block is plenty
Single-Player could also suffer here. You aren't the first one to try this with Actors. You can always hotswap the ISMs into Actors when they are hit if you need the state, but even that is potentially overkill.
ISMs can have Per Instance Properties in Materials, so you can even drive the visuals for the blocks while keeping them as ISMs
Which is better, ISM or HIMS?
It works like this: when I use HISM or ISM, some blocks (for example, rocks) end up being triangular.
But normal blocks are square, and the character can get stuck on those triangular rocks.
I had the blocks with different sizes generated by AI.
For example, the rock mesh has no scale settings, so I can’t adjust it.
There is no single-player mode in my game; it’s directly online multiplayer only.
when a struct property is replicated, does unreal update a delta or whole struct?
I believe since like 5.0 (or roughly around there) structs are default atomically replicated.
Idk about Iris though
*Edit: Wrong, In Iris they are atomic, by default they are not.
I thought that wasn't the case
Well I could be wrong, also I believe FHitResult has a custom net serializer
I know there's an atomic specifier but I think it's deprecated
ye
makes sense it has one
Ye so Iris they're atomic, otherwise no.
I open the engine once every 5 decades now
yet somehow slackers is so alluring enough to hang out
It keeps me fresh with the times
Hip, if you will
Otherwise I'll forget everything, I'm also over exaggerating. I still have some game ideas I open a project for every few days
https://youtu.be/K472O2rVvG0?si=qBZgkvXgNKFPMV5G&t=1066
It's talked about here, if you want a deeper dive
They do a lot of other stuff behind the scenes now, I haven't done much looking into Iris to know any details.
Usually for stuff that isn't one-off transient data, atomicity might be more preferred then not because of the issues non-atomic replication has with packet loss
Because previously that bunch was still going to try to get there eventually anyway
Yes. It does have a custom serializer in iris at least
it's mostly just forwarding the serialization work to the inner types but just skipping doing things that it doesn't need to
It depends on if the object class has delta serialization enabled
in Iris each struct has a member bitset
so it can basically just include the members it knows are different from the last ack'd baseline iirc
I think ye olden replication can be done atomically in a single struct with some setting but yeah
Iris will make them atomic
My hot take is that if you are making a fast-paced game you should try to clump replicated variables that rely on being received close together in one atomic struct
or even using some kind of generation id incrementing number if that's not possible
some games might not ever need to care if an onrep fires one frame later but for some it will create massive visual artifacts
Sounds like a setup issue. Especially if you use AI instead of doing it properly.
I know, I merely meant that so many actors are generally not a good idea
I thought you just defined net serialize and manually archived it all to make it atomic
Is there another way
huh, I must be an LLM because I could swear there was a trait
It really is just netserialize or else I guess
Someone said that earlier too, it might be some old deprecated thing
Someone said something about an atomic Meta but I’ve never seen it
oh well
I ended up doing that for my items.
The base engine replication stuff, felt way to limiting, and issue prone for my item system.
But, it did help me improve my overall code, so its not nearly as cursed/ manual and stuff.
Ended up making my own fake MARK AS DIRTY system. lol
do you mean using an rpc or actually making a new replication system alongside
Using an RPC.
Ain't no way I could make a new replication system
rpcs can tank fairly high amounts of data just fine but they are not useful for truly large amounts of data unless you manually slow them down
if you cap out reliables your game will break in subtle ways
so be careful to not spam more than your current rpc queue settings and limits if you can help it
Probably explains why ARK breaks in subtle ways.
Everything is reliable. 😛
Also, I thought reliable cap, just disconnects the client?
Or, you talking like right under/ near the limit causes issues.
not always
at least not in iris
there are also multiple separate caps and limits along the way, both for the separate number of rpcs and the actual size of the individual set of data sent
Sending a packet is a bit simpler then you’d think. Unreal (like everything) has an abstraction for it
yeah personally I don't really want to make my own networking layer but it's honestly pretty simple in isolation given the actual code involved
I personally do not know how I would do it from 0 with just the windows API but writing bits into a header is pretty much just... put the bits in the bag
read them the same way on other side... send integer saying "I got your packet bro"
What would be the benefit?
No limit to break shit, or what?
Or, just a separate limit, for the individual layer?
well, in unreal you don't really have to go SUPER low level... you could make a data stream or new uchannel
the main benefit would be ignoring the limitations of the gameplay replication system and not stomping on the work it does
for sending streams of data or sending them in a way that is unique to your needs
in terms of timing etc
unreal currently runs replication in a way that is kind of just main-threaded but in theory you could get better ping times but doing it only on another thread etc
but idk if datstreams or channels can do that on their own easily
Something to look into later, at least.
I didn't even think about straight-up custom networking/ replication.
I really just need slightly more manual control over the UE replication.
Which the RPCs do for now.
and, not like I'm anywhere near game completion.
In theory, I could do this custom networking at the very end, since its just the data transfer method, not the actual data usage.
I have seen some people actually even use http connections to send game data for consistency
and yeah if you can make it work with just RPCs stick with that 100% if they don't break things... that is far more simple than the alternative I think
Yeah, 100% simpler.
But, talking about these other methods have still helped me realize new ways to do data.
My original was individual RPC's for every possible value rep.
Now I do just rep the raw bytes around by looping over all properties marked for replication, with the net serialization
Interesting.
For what I wonder?
IIRC, doesn't the game "look basic" on the networking front?
Just glorified weapon actors, with replicated vars?
Inventory, skills, etc i know for a fact they have there own UChannel for them
i made my own UChannel for ingame pings
Tournament did some funky shit for projectiles back in the day with em
This is ancient history
Curious.
That seems like a pretty basic thing for RPC to do?
Why have a separate Channel for it?
i cant remember this was back in UE 4.18
He’s drunk
lol
It just makes me curious to learn from others, with more experience, especially in the specific thing being spoken about. 😛
Iris is the future though
our net tick in our game is almost half the cost compared to non iris
UDataStream is the fancy new UChannel which Iris uses
the main ones are replication stream (well, everything) and the net export (nettoken) stream which negotiates path -> id conversion etc
also iris has spatial stuff like ReplicationGraph
which is really nice
though i had to silence a warning in it
was bugging me
Any specifier on UFUNCTION to ensure that it only runs on server?
there is BlueprintAuthority only but I want it on my cpp function, not the bp version.
Not to my knowledge.
There's one to make it only run on client. (random facts)
What would that be
the ol reliable ensure(HasAuthority)
BlueprintCosmetic iirc
checkf the shit out of it 😏
HasAuthority doesn't necessarily mean just the server.
Thats probably the only option left (good enough for my use case on replicated actor). I was just looking if there's an option with specifier instead checking in the function body.
Wrap the function body in #if UE_SERVER fwiw
Then clients can call the method and nothing will happen
Put the ensure into an #else
That's only true on a dedicated server build, is it not? It won't work for listen servers. And if it does, it'll still work on clients in listen server builds.
Authority will effectively mean the server though unless it's a clientside spawned actor or an actor that's been torn off. Though when you absolutely need to be sure it's a server of some kind then you can use GetNetMode()
'tis what all our checks do.
I don't think it really matters if it works or not if one targets listen servers :D
Was somewhat implied that it's specifically for dedi servers.
Authority is usually fine. Using NetMode is probably better, although I would suggest covering all Servers + Standalone (so != Client) instead of only Dedi or Listen. One might make a tutorial or shooting range or whatever, and suddenly half the code doesn't work cause everything is behing NM_DediServer or NM_ListenServer.
That code would then also not be wrapped by UE_SERVER of course.
So, will I have to rewrite it?
Pretty sure multiple people told you by now that you should do it differently, yeah.
So, what do you think would happen if I didn't install something like World Manager and instead used the Voxel plugin?
No clue. Haven't used either.
It will take me a lot of time to rewrite it from scratch; I even made the multiplayer version...
thanks
If you don't have issues with it at the moment and it all runs fine, then leave it.
It's just that Actors have quite the overhead if used to that amount. Most Minecraft-like games simply draw the outside shell of the world with a custom runtime mesh. In UE you should use (H)ISMC for it fwiw.
Having 1 Actor with a MeshComponent per Cube will also mean you have tons and tons of draw calls.
It just all screams like something that will quickly eat up all available performance.
I know someone who made a Minecraft-like game a few years ago in UE and they started with Actors too, before dropping that idea for the same reasons.
The reason I’m using Actors is that the number of blocks is limited.
In one world, there will be a maximum of 50k blocks, possibly even fewer.
With Actors, customization is very easy, and I can add new blocks without much effort.
With HISM, you have to add components, and block sizes need to be the same.
If you place blocks with different sizes, you need to write special code for them.
But with Actors, it’s not like that; using a collision box, the block can even be round, and the collision handles everything.
Okey
i understand u
Keep in mind that you have some control over Meshes through their Materials.
And Instanced Meshes can have Per Instances properties inside Materials.
Idk if that can resize them, but it can def do quite a lot.
Not sure how much that nukes the draw call stuff though. Not a graphics person.
that's true, I guess in my mind the two terms are interchangeable most of the time but it does matter
I find myself doing "wait, is this a non-dedicated server that cares about visuals?" stuff alot etc
A ton of people think Authority == Server, because Epic and YouTube teaches it like that throughout tons and tons of resources.
One can probably ship a game without a single bug related to using Authority NetRole over NetMode, and stay oblivious to the fact that it doesn't guarantee that it's a Server.
I guess, one "guarantee" that it's the Server originates from the given programmer. If they only ever do stuff in a way that Authority is true on the Server (aka no Actors that are locally spawned), it is probably fine.
It's also, tbh, pretty rare to even run into this "problem".
local role of one actor vs world netmode I guess is the distinction here?
I hear netmode has its own issues, though also very remote. Before the world's netmode is correctly set, it can lead to errors.
I'm trying Chaos Mover and just have
So I'm having an issue. I'm creating a local projectile (for local prediction) and then I call a server RPC to then create a server projectile. The issue I'm having is that, I've done a million NetMode debugs to check authority, the projectile has authority when its created, it has authority when applying the stats, however, the issue is that once it triggers the OnComponentHit the NetMode is now 3 instead of 1 for the server. I can't understand what's happening but basically it's not allowing me to apply gameplay effects because of lack of authority somehow even though it's the server projectile. (And I can see it moving and stopping/hitting players).
@thin stratus Right now, only the mesh sizes are different; I'll try to fix that.
What is wrong with this flow of code?
issue I am getting is when attempted to drop the item, the item in the TMap slot bugged out, and can't unequipped properly.
If I comment this line everything works properly to unequip the item.
Character->DropItem(AlreadyEquippedItem, Quantity);
what I want to do is when an item is added to the inventory of the same slot, drop the old slot and equip the new one.
1 find the already equipped item
2 call AlreadyEquippedItem->SetEquipped(false)
3 triggers OnRep_EquipStatusChanged() which calls UnEquip(Character)
4 UnEquip calls Character->UnEquipItem(this) which unequip item and remove it from EquippedItems map
5 Character->DropItem(AlreadyEquippedItem, Quantity)
6 SetEquipped(!IsEquipped()) for the new item to equip it immediately
step 4 bugged out if step 5 is implemented
if I comment step 5 everything works fine to unequip item but it will still be in the inventory, I want to drop it using step 5
Why are you using a map here anyway?
I found it flexible, are there issues with it in my use case?
What is the replicated state representing which items a character has equipped?
I know tmaps don't support replication, that's why taking benefits of it by setting it using onrep of equipped item
I load into the game, and walk towards another character. How do I know what they are wearing?
EquippedItems.Add(Item->Slot, Item); set by onrep
I'm a late joiner, I wasn't around to hear that
onrep will set it
onrep of what
UItem
void UEquippable::OnRep_EquipStatusChanged()
{
if (AArmaCharacter* Character = Cast<AArmaCharacter>(GetOuter()))
{
UseActionText = bEquipped ? LOCTEXT("UnequipText", "Unequip") : LOCTEXT("EquipText", "Equip");
if (bEquipped)
{
Equip(Character);
}
else
{
UnEquip(Character);
}
}
OnItemModified.Broadcast();
}
with replication there is no issue, it works perfectly
the issue is I guess if I remove item quickly, the onrep has not yet finished its job and bugged out
If i call Character->DropItem(AlreadyEquippedItem, Quantity) with .5sec delay it works fine just tested
since dropitem is only server method, I can't cal lit under this onrep
the delay making it work tells me you have some fundamental problem here.
Driving the state by a repnotify should be designed in such a way that the order of things happening, or anything being missed, shouldn't matter.
It'd be much simpler just to have a replicated array representing what's equipped and onrepping that.
so I have to swith to TArray from TMap, maybe there is still some solution to solve it under my current system
according to the code flow, there should not be an issue, but not sure why this bugged out, since I am unequipping the item before removing this from inventory
You need to think about how it can fail given that things can arrive out of order or not arrive at all on clients
I guessed alot, and don't have any clue why this will fail
the only thing I am thinking that TMaps hashing is slow
before it does the job, the remove item fired
or: since onrep calling on clients and server I think it first unequip on server and dropitem calls , when it want to unequip for clients it fails because the item was removed by server using DropItem
why would it being slow matter here?
how would that affect the order of how anything occurs
just some vibes cause I have no other clue why it should fail
well
I thought it gives signal it does the job so DropItem executes before properly unequipping the item
I have no other clue why DropItem will exec if the item has not yet unequipped
One issue I think is that an onrep only shows the most recent value, it is not gauranteed to show EVERY SINGLE CHANGE of a value of every frame on the server. Clients just happen to receive the most recent value
so if you change a replicated property and change it back to something else there is no gaurantee the in-between value will be sent (And no, this is not going to change if your tmap is nanoseconds longer to hash... this is more overall frame time vs net broadcast tick and replication rate etc)
For this reason it is often useful to have an incremementing number of some sort to represent "how much changed" since the last onrep in the same onreping property
AlreadyEquippedItem->SetEquipped(false); means thsi can be the issue?
I don't know what that does and I do not know if I have time to ask you to explain that
I am doing !bool , its same like incrementing a value I guess
why is that the same as an incrementing value
you are not showing the code
you are showing a word with an exclamation point in front of it... I don't know what that actually does or applies to
all related code is posted
#multiplayer message
you are leaving out what kind of property this is
we cannot EVER guess what code you have not described or shown us
short descriptions of things that don't actually describe what is going on are not going to work, we need to see what is actually happening
which code? there is everything in the message
how can I know what type it is from the cpp file?
type of item?
idk why you don't just replicate the state you care about and call it a day, why jump through all these hoops with race conditions all over the place
issue is not with replication, its already replicated properly
I think the issue is that they have a replicated boolean that is flip-flopping so the actual change is never seen on clients but they do not seem to want to share what is happening
The problem most likely comes from your items replicating over, in their bEquipped onrep they are modifying the state of that TMap.
You aren't guaranteed to get all intermediate values of bEquipped AND you aren't guaranteed that the bEquippeds fire in any order
You have a bunch of items saying if they are equipped or not instead of the character just saying what items it has equipped
seems ass backwards to me
its not an issue, If I don't call the DropItem()
which is why I am trying to see what the onrep ACTUALLY REPRESENTS but "its an item" is not useful here
ItemB.Equip() can fire before ItemA.Unequip(), and vice versa. You have no way to know which order they will call in
if item is equipped its true, else its false
I don't see any issue with this
UPROPERTY(ReplicatedUsing = OnRep_EquipStatusChanged)
bool bEquipped;
Why is that not just named OnRep_bEquipped?
is the ground pickup also supposed to have the character as an outer? UnEquip will silently fail if the outer isn't a character as well
The whole flow of references seems just ass backwards
if the slot is already occupied it will change the state of the old item first AlreadyEquippedItem->SetEquipped(false);
if (Character->GetEquippedItems().Contains(Slot) && !bEquipped)
{
UEquippable* AlreadyEquippedItem = *Character->GetEquippedItems().Find(Slot);
if (IsValid(AlreadyEquippedItem))
{
AlreadyEquippedItem->SetEquipped(false);
Character->DropItem(AlreadyEquippedItem, Quantity); // When dropped this item is removed from inventory and spawned a ground pickup
}
}
SetEquipped(!IsEquipped());
Because you have another source of truth in the map
doing true, false, true inside of 1 frame could completely break the client as they could miss the onrep due to the value not changing for long enough to matter to them
you have 2 places that say if something is equipped or not instead of 1
and yes it's very confusing to have multiple places this will be
if it was a simple fast array you would easily be able to say "ah the item appeared" or it was removed
a boolean onrep is going to be ENTIRELY RANDOM relative to any other property
plus why does the item get to say if it's equipped or not, the item should just be a dumb thing that doesn't know or care
this is drop item in character class
ensure(Item->PickupClasss);
APickup* Pickup = GetWorld()->SpawnActor<APickup>(Item->PickupClasss, SpawnTransform, SpawnParams);
if (IsValid(Pickup))
{
Pickup->InitializePickup(Item->GetClass(), DroppedQuantity);
Pickup->AlignWithGround();
return true;
}
it could be fine if it was for mostly visuals etc or something separate, but replication does not care about your order on the server at all
Lose the TMap
if you must query for what's equipped then iterate over the owned items
its a replicated UObject, to avoid reinforcing replication by character, item handles itself
that's fine? it doesn't need to be what control if its unequipped or not though
The item can still be independent, just have Character be the thing that says what's equipped to it
Item is handled by inventory which is owned by character
it can absolutely have its own data about its current statistics etc but multiple replicating things = random every time (in terms of order)
then why do you let the item say whether it's equipped or not instead of the inventory/character?
Character
Inventory
Inventory
Equipped
Stored
Item
Done.
If you are in a really crazy situation where you need consistency you can use an incrementing "generation id" on two distinct replicating properties but you do need that here at all... that would be more for large arbitrary hierarchies of things etc that are just not simple to represent atomically
this is 1 thing
its character is telling it to change its state
those are two things
two things = random order
you have shown use that the item has internal state that onreps to say if it's equipped
on the client that is NOT the character telling it to do that. it is ONLY a result of the onrep firing from reading the packet in the beginning of the frame in normal settings
there are two different computers here, it does not matter what happens on the server if the client gets things in random order
character->interact->inventory->added-> item state
void UEquippable::OnRep_EquipStatusChanged()
{
if (AArmaCharacter* Character = Cast<AArmaCharacter>(GetOuter()))
{
UseActionText = bEquipped ? LOCTEXT("UnequipText", "Unequip") : LOCTEXT("EquipText", "Equip");
if (bEquipped)
{
Equip(Character);
}
else
{
UnEquip(Character);
}
}
OnItemModified.Broadcast();
}
no amount of functions in a row matter if this onrep doesn't fire in the order you expect if your code assumes these are consistent
If I do character->interact->inventory->added->character->getanyitem->statuschange this is just not clean to me
replication does not care. It is EVENTUALLY consistent inside of the current bandwidth limits and network conditions
What is this by mean?
OnRep_EquipStatusChanged is called when bEquipped is changed and this is changed by inventory when item is added
does each item have an UEquippable object or not
inventory checks the type of the item which is currently added to tell it what to do
no, only UEquippable has this
fine, but what does the inventory have to represent the item BEING THERE in the first place?
its a TArray of UItems
replicated TArray
Change it to a fast array
then you can use replication callbacks
and you can react to new items being added
TArray onreps are not useful as it just has the entire array in it
I am in this issue because I am not using fast array?
you can use normal onrep, but you need to compare previous to new values in the array
in the onrep, to filter new stuff
fast arrays handle this nicely using deltas, etc, with client side callbacks
UPROPERTY(ReplicatedUsing = OnRep_EquipStatusChanged)
bool bEquipped;
also just to be clear: Even if this was on its own this replicated boolean has NO gaurantee it will always onrep changes. Replication always considers the most RECENT state and not every single time it ever changed in between network broadcasts especially when packet loss is involved
If it's staying in one distinct state for over a half second or so it should show up but it's entirely just happenstance
and I am doing this
UItem* UInventoryComponent::ServerAdditem_Internal(class UItem* Item, const int32 Quantity)
{
if (!GetWorld() || !GetOwner() || !GetOwner()->HasAuthority() || !Item->GetClass())
return nullptr;
UItem* NewItem = NewObject<UItem>(GetOwner(), Item->GetClass());
NewItem->World = GetWorld();
NewItem->SetQuantity(Quantity);
NewItem->OwningInventory = this;
NewItem->UniqueItemGuid = Item->UniqueItemGuid;
NewItem->OnAddedToInventory(this); // Item call back
Items.Add(NewItem);
NewItem->MarkDirtyForReplication();
// Notify listeners immediately
OnItemAdded.Broadcast(NewItem);
OnRep_Items();
return NewItem;
}
Silently failying from 4 different conditions is very sad... at least error/ensure unless it is expected that this fails a lot (e.g. just convenient to call on non authority etc)
also what gaurantee is there that this is a unique item?
Issue is not with the replication at all, the issue is when an item is in progress of UNequipped, the DropItem calls and the item is removed from inventory, the progress of unequip bugged out
this just seems very weird
and all those if conditions are very concerning at the top, with no ensures or w/e
and there is no way Item->GetClass will ever be null
trying to save lines but increasing random hard to debug stuff is not nice in gameplay code
and your not even chekcing if Item is valid before derferenceing
Yeah, the map has 10 Skeletal Mesh components mapped with a UEquippable slot
which is not in that code or any code you posted earlier
see every inventory system i have ever done uses FastArraySerializer
we can only see what you show us
What it means? adding an item to a TArray is not unique by its index?
i don't even know what the issue is
i assume its OnRep_EquipStatusChanged not being called when needed?
you can make it RepNotify_Always
yeah I'm insanely lost and I don't know if short sentence answers split across 20 messages will help... I would honestly rather you edit the actual info into the original question than to constantly have to piece together the actual problem from random fragments
still possible to miss if it's a boolean as it will not need to be sent at all
Item successfully UnEquipped if I don't call DropItem on the already equipped Item which swapped the already equipped item with the new once and placed in the reserve inventory slot
I am trying to not place the unequipped item in inventory but drop it
which happens on which side? client or server?
see this is where my fast array approach really shines
it just takes out so much of the headache
DOREPLIFETIME(UEquippable, bEquipped); currently I have this in this state
RepNotify_Always worth to test?
sure, but I would strongly suggest never relying on separate objects to replicate in the same order
also please do not mislead us with things like OnRep_EquipStatusChanged which are a totally different name unless you actually post the header that shows what it maps to
I just call the function of it with more meaningful name
it's not meaningful to us, we can ONLY SEE WHAT YOU SHOW US
you don't need to mindlessly adhere to whatever coding standard but we can only see the letters on our screen... you did not show this boolean having that onrep so we had no idea what it actually was
void UEquippable::SetEquipped(bool bNewEquipped)
{
bEquipped = bNewEquipped;
OnRep_EquipStatusChanged();
MarkDirtyForReplication();
}
I show what I have, well it can be changed to OnRep_bEquipped if You like this name 😄
🤮
i hate stuff that calls OnRep_Blah
i have SetEquipped_Internal for example that both paths call
that kinda stuff
OnRep_EquipStatusChanged named it like this cause its handling the equip/unequip state
that's fine but read what I said
btw wtf is this MarkDirtyForReplication
I don't care about why you chose to name it that, we can ONLY SEE what you show us
just enlighten me
MarkDirtyForReplication is an ancient way to update subobjects isnt it?
also notices lack of PushModel going on here, in 2025/2026, there's no reason to not be using PushModel for the small peformance gain
I do not know what version they are on (we generally assume you are on a recent ue5 version unless you tell us)
void UItem::MarkDirtyForReplication()
{
++RepKey;
if (OwningInventory)
{
++OwningInventory->ReplicatedItemsKey;
}
}
yeah I'm only seeing this in ancient actor channel code
this is not the intended way to replicate a subobject in ue5 afaik
yeah
but it works , I shifted this project from 4
this looks like the old 4.12 thing
a replication key is only useful here if it is inside of 1 atomic property or represents information itself, not just to dirty an object
cause they took this out way back in 4.14 iirc
time to read the documentation
my whole project depends on this legacy system and it works fine, have no intentions to change it for this project 😄
but i can see what this is doing its basically not replicating the object at all if nothing changes
but no one uses the old way anymore
if you want help and are doing stuff in a weird way we can never assume that or possibly reason about this
that's why calling that dirty function when item changed
it's fine to not know how this works in detail... I don't think I personally understand actor channel objects but doing unorthodox stuff we can't see = we cannot give advice on what to do
"it works" is fine, but if you have a totally custom setup we can't see that
we can only see what you show us
in ue5 you just need to call ``` /**
* Register a SubObject that will get replicated along with the actor.
* The subobject needs to be manually removed from the list before it gets deleted.
* @param SubObject The SubObject to replicate
* @param NetCondition Optional condition to select which type of connection we will replicate the object to.
/
ENGINE_API void AddReplicatedSubObject(UObject SubObject, ELifetimeCondition NetCondition = COND_None);
/**
* Unregister a SubObject to stop replicating it's properties to clients.
* This does not remove or delete it from connections where it was already replicated.
* By default a replicated subobject gets deleted on clients when the original pointer on the authority becomes invalid.
* If you want to immediately remove it from client use the DestroyReplicatedSubObjectOnRemotePeers or TearOffReplicatedSubObject functions instead of this one.
* @param SubObject The SubObject to remove
*/
ENGINE_API void RemoveReplicatedSubObject(UObject* SubObject);```
these funcs, thats it
and let unreal networking handle it for you
doing stuff in a different way is not always a bad thing but when you want help we are going to assume you are doing things in a certain way
unless you show us
we can only see what you show us and have NO choice but to assume everything else
I have shown everything related to the issue in the above post, let me share it again
nope, that's not what I mean and you know it
OnServerItemAddedToInventory calls by addedtoinventory callback
If you saw this C++ code how would you know what the header looks like or how the subobject is replicated? or which sides this actually gets called on
MarkDirtyForReplication replicates the subobject
OnServerItemAddedToInventory is self explaining called by server
and the header yeah its a 6k lines of header, so i posted only the related part to the issue in the code operation flow
at no point did I say I needed to read the entire header, no need to imply that
UCLASS(Abstract, Blueprintable, EditInlineNew, DefaultToInstanced)
class ARMA_API UItem : public UObject
UCLASS(Blueprintable)
class ARMA_API UEquippable : public UItem
{
GENERATED_BODY()
public:
if You need I can post it but i'm afraid you don't have time to read 6k lines of code
I would say in the future you should consider stuff like the specific properties and ufunctions involved as kind of the bare minumum when they are doing something unexpected. If you have to rename things to make it more private or easier to read that's fine of course.
Also with netcode problems it extremely important to list out which "perspective" each side was called on and not only when asked multiple times... Leaving stuff out makes it hard for us to follow with a giant chain of replies later and vastly reduces the chances we can help you
I guess my answer here is mostly the same though: If the onrep relies on the current state of an external replicated TArray you cannot possibly assume one always fires befor the other OR that they even fire in all cases if changes are fast enough on the server
The reason pretty much every project just uses a fast array is because they make an "on added" callback simple to do and only send what changed in the array (delta serializing the items and sending their ids etc)
You aren't required to use a fast array of course but it would make this a lot easier imo... if you need help setting it up we can help with that too
I am compiling with a change of DOREPLIFETIME_CONDITION_NOTIFY(UEquippable, bEquipped, COND_None, REPNOTIFY_Always);
lets see if this solved the issue
It will not change the order replication occurs in
there is no gaurantee it is in the same packet as the one that says the array changed to a new array
but it's worth a try I guess... I still think that you are better off long-term not having multiple objects with protential race conditions
Yeah nothing changed, it behaves same as before....
Item dropped but not properly Unequipped, stays as garbage in the inventory and the mesh related to this item still equipped by character
for example if you really needed to you could have a replicationindex change with the array element and the object, to represent the most recent change when both are the same on the client
I don't recall you mentioning "stays as garbage " before...
Yeah missed that information
PIE: Error: Blueprint Runtime Error: "Attempted to access OBJ_Helmet_lv2_C_0 via property CallFunc_Map_Find_Value_2, but OBJ_Helmet_lv2_C_0 is not valid (pending kill or garbage)". Node: Set Brush from Texture Graph: EventGraph Function: Execute Ubergraph WBP Inventory Blueprint: WBP_Inventory
relying on unrelated onreps to pop the value from the tmap = random
you could even have the item replicate it's current "owner" as the character or a pickup or whatever and have the tmap be local only
I read in the past that reliable rpc from the same actor, guarantees to come in order. Is that true?
I believe that is still true, yeah. IIRC it's not just the same actor but that actor channel. So like any of it's subobjects and such that do them.
Good news I think I found a Solution.
bool UEquippable::UnEquip(class AArmaCharacter* Character)
{
if (Character)
return Character->UnEquipItem(this); // Here I am passing this which was removed by dropitem
return false;
}
bool AArmaCharacter::UnEquipItem(UEquippable* Item)
{
if (!IsValid(Item) || !EquippedItems.Contains(Item->Slot))
return false;
if (Item != *EquippedItems.Find(Item->Slot))
return false;
EquippedItems.Remove(Item->Slot);
OnEquippedItemsChanged.Broadcast(Item->Slot, nullptr);
UnEquipGear(Item);
return true;
}
void AArmaCharacter::UnEquipGear(UEquippable* Gear) // Gear is used to get the slot but it was removed/garbaged
{
if (!IsValid(Gear)) {
return;
}
if (USkeletalMeshComponent* EquippableMesh = GetSlotSkeletalMeshComponent(Gear->Slot))
{
if (USkeletalMesh* BodyMesh = *NakedMeshes.Find(Gear->Slot))
{
EquippableMesh->SetSkeletalMesh(BodyMesh);
}
}
}
I think I should pass the cached slot
fun fact is these two guys never returns
if (!IsValid(Item) || !EquippedItems.Contains(Item->Slot))
return false;
if (Item != *EquippedItems.Find(Item->Slot))
return false;
and this IsValid returns
void AArmaCharacter::UnEquipGear(UEquippable* Gear) // Gear is used to get the slot but it was removed/garbaged
{
if (!IsValid(Gear)) {
return;
}
use ensures and error logging unless this failing is expected
Passing by slot fixed the issue on server for UnEquipping
this only logs : OnRep_bEquipped called for SERVER
If I comment the DropItem(); it calls for both client/server
void UEquippable::OnRep_bEquipped()
{
if (AArmaCharacter* Character = Cast<AArmaCharacter>(GetOuter()))
{
UE_LOG(LogTemp, Warning, TEXT("[%s] OnRep_bEquipped called for %s"),
Character->HasAuthority() ? TEXT("SERVER") : TEXT("CLIENT"),
*GetNameSafe(this));
UseActionText = bEquipped ? LOCTEXT("UnequipText", "Unequip") : LOCTEXT("EquipText", "Equip");
if (bEquipped)
{
Equip(Character);
}
else
{
UnEquip(Character);
}
}
OnItemModified.Broadcast();
}
Use GetDebugStringForWorld instead
Added To Inventory after interaction on server, Auto equip which will unequip the old and drop if exists
LogTemp: Warning: [SERVER][DedicatedServer][World: Dedicated Server] OnRep_bEquipped called for OBJ_Helmet_lv1_C_0. bEquipped: 0
LogTemp: Warning: Unequipping OBJ_Helmet_lv1_C_0 from character BP_MaleCharacter_C_0
LogTemp: Warning: Character Role: LocalRole=3, RemoteRole=2
LogTemp: Warning: [SERVER][DedicatedServer][World: Dedicated Server] OnRep_bEquipped called for OBJ_Helmet_lv1_C_1. bEquipped: 1
LogTemp: Warning: Equipping OBJ_Helmet_lv1_C_1 on character BP_MaleCharacter_C_0
LogTemp: Warning: Character Role: LocalRole=3, RemoteRole=2
LogTemp: Warning: [CLIENT][Client][World: Client 1] OnRep_bEquipped called for OBJ_Helmet_lv1_C_1. bEquipped: 1
LogTemp: Warning: Equipping OBJ_Helmet_lv1_C_1 on character BP_MaleCharacter_C_0
LogTemp: Warning: Character Role: LocalRole=2, RemoteRole=3
Manual slot drop unequip on Server RPC
LogTemp: Warning: [SERVER][DedicatedServer][World: Dedicated Server] OnRep_bEquipped called for OBJ_Helmet_lv1_C_1. bEquipped: 0
LogTemp: Warning: Unequipping OBJ_Helmet_lv1_C_1 from character BP_MaleCharacter_C_0
LogTemp: Warning: Character Role: LocalRole=3, RemoteRole=2
Interesting why it works when I interact with item on server and it added to inventory and calling the same function
void UEquippable::OnServerItemAddedToInventory(class UInventoryComponent* Inventory)
I also call this function on an item using server rpc
some of these logs leave out which world they are on
I assume they relate to the one they are right below
Maybe Anything more I need to add to log?
I would suggest making a logging macro that actually uses GetDebugStringForWorld every time instead of some of the time
but that's not really required
I'm still not sure which one is even the "wrong" case anymore
it is hard to understand the way you phrase it earlier
"Interesting why it works when I interact with item on server and it added to inventory and calling the same function" What does this refer to ? the first set of logs? the second?
case of manual slot drop unequip on Server RPC is missing to reach clients
second set is partial, on server works on clients not
and this only the case if I uncomment the dropitem function
if I comment it, both logs cases are same
put this context in the original question
Item gets garbaged, before it reach clients
I had this issue as well X_X.
ends up just hiding the physical item and giving a life time of 5 seconds.
feels like a hack but that may do for now.
if i call the drop item with .5sec delay, it works fine but its bad It should be fixed without timer
you are free to follow our directions earlier to simplify this and have the item list by one thing and not 2 things that will not get timed correctly
you could arguably move the item instance to be owned by the dropped item or something too... not sure what happens to the equipable object here on the server when it is unequipped
But the first case works and has not this issue, this only happened if I manually want to drop the item
on the server RPC from character class
trying to save time by skipping words is not helping me follow this
so the server rpc that is from client to server that says they want to drop the item is the broken state here (or at least what starts it)
what decides to equip the new item anyways? is it just an item in the same "slot" in the map?
Client from the inventory passed the equipped item to server, server checks if this item exists in server inventory and , and server drops it
by dropping I mean it removes it from the inventory and spawn a pickup of this item
also server, from client
so there are two rpcs here that we can't see that are unknown
client interact with item on server, added to inventory, server check if this item type was equippable, auto equip by calling
void UEquippable::OnServerItemAddedToInventory(class UInventoryComponent* Inventory)
think about an RPC... what are some interesting facts we might have needed to know earlier
is it reliable? unreliable?
It being reliable here imo is fine, this is definitely a situation you don't want the server to miss it
there are two RPC's but both not executes at the same time, one is executed when server added an item to the inventory, second is executed only if I want to manually UnEquip an item and drop it
both Reliable
does this still silently fail or does this log or ensure when it fails?
if (!IsValid(Item) || !EquippedItems.Contains(Item->Slot))
return false;
if (Item != *EquippedItems.Find(Item->Slot))
return false;
fails on client only
that is not what I asked
if it's expected that the item might not be in equipped items that is fine and not an ensure... my issue is having dozens of early outs you can't track easily that make it basically just silently not do something
what does "in the second case" mean... the second if statement??
also why bother doing both Contains and Find... Find will already return null if it's not there
this is doing two hashed finds into the map when you want one
we also don't know if Item->Slot is a meaningful value or not
can it be none? is that okay?
Also it would be a bit scuffed but you could have the client RPC back an ack that says the object being unequipped was received (so it stays alive on server a bit longer)... seriously just use a normal fast array though because there is no need for complexity here with subobjects that leave containers early
just make the element being removed be what unequips it or the selected index/ptr changing
by second case I mean with DropItem(...) function call after AlreadeEquipped->SetEquipped(false)
Find was crashing when the slot was null
Item->Slot now refactored to pass just Slot and client/server will use this slot find the reset mesh for the slot
it can't be none;, if its none player will not rest the default mesh if any fashion item is unequipped
also IMO if each subobject is the same kind of thing... just use a damn struct
replicated subobjects are tediously overcomplicated if it's all some small value and you don't need conditional netgroups etc
only uequippables are same
there are uammoitem
uweapon etc
notice how the context of what you said matters... I cannot deduce which pair of things you mean unless it is clear
you could say "the second of the two sets of logs earlier" if that helps... to be clear this is not a language barrier thing as much as just needing to actually include what you are talking about when you talk about it
the more separate messages it takes to explain something the more I have to map constantly back and forth to see what it was you are referring to especially if you reply and are talking about something not what I asked about
If I ever use a word that is hard to understand feel free to ask me to type it in a different way, I do not want to use strange english that nobody else can understand easily
I will post the new refactored function now
its better explained with case one and two
void UEquippable::OnServerItemAddedToInventory(class UInventoryComponent* Inventory)
{
AArmaCharacter* Character = Cast<AArmaCharacter>(Inventory->GetOwner());
if (!Character || !Character->HasAuthority())
return;
if (Character->GetEquippedItems().Contains(Slot))
{
UEquippable* AlreadyEquippedItem = *Character->GetEquippedItems().Find(Slot);
if (!IsValid(AlreadyEquippedItem))
return;
//Case One
if (!bEquipped)
{
AlreadyEquippedItem->SetEquipped(false);
Character->DropItem(AlreadyEquippedItem, Quantity);
}
else
{
//Case Two
SetEquipped(false);
Character->DropItem(this, Quantity);
return;
}
}
//finally equip new item
SetEquipped(!IsEquipped());
}
case one only reached if new Equippable item is added to inventory and the same slot item is already quipped in this slot
already immediately silently failing of the Owner is null
I have to go to bed but I am seriously going to say it's better to make these check or ensure/log every time if they are assumed to be true
the authority check is fine of course but clients should never be calling this! it should immediately trigger annoying ensures if someone makes that mistake
if owner is failing, why the item is dropped successfully
on server which is correct
I don't know and I can't keep track of the 5 separate functions and objects here
I assume it's not the real issue but every single "oops this early outed" is a case I can't ingore when you go "why isn't this called"
the parts where it is supposed to do X or Y when something isn't there is fine, but when trying to see why something is not happening we start from the top of the function
Ok, let me re mention it, when the dropitem is uncommented, the item drops correctly and the issue started with client side UnEquip
even if nothing else here says DropItem it's still easier for me to see where it is with the whole line like that I guess
bool AArmaCharacter::DropItem(UItem* Item, const int32 Quantity)
{
if (Quantity >= 1 && GetInventoryManager() && IsValid(Item) && GetInventoryManager()->FindItem(Item))
{
if (HasAuthority())
{
const int32 DroppedQuantity = GetInventoryManager()->ConsumeItem(Item, Quantity);
FActorSpawnParameters SpawnParams;
SpawnParams.Owner = this;
SpawnParams.bNoFail = true;
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;
FVector SpawnLocation = GetActorLocation();
SpawnLocation.Z -= GetCapsuleHalfHeight();
FTransform SpawnTransform(GetActorRotation(), SpawnLocation);
ensure(Item->PickupClasss);
APickup* Pickup = GetWorld()->SpawnActor<APickup>(Item->PickupClasss, SpawnTransform, SpawnParams);
if (IsValid(Pickup))
{
Pickup->InitializePickup(Item->GetClass(), DroppedQuantity);
Pickup->AlignWithGround();
}
return true;
}
ServerDropItem(Item, Quantity);
}
return false;
}
bool UInventoryComponent::RemoveItem(class UItem* Item)
{
if (GetOwner() && GetOwner()->HasAuthority())
{
if (Item)
{
OnItemRemoved.Broadcast(Item);
Items.RemoveSingle(Item);
OnRep_Items();
ReplicatedItemsKey++;
return true;
}
}
return false;
}
does Character->DropItem remove it from the list of items on the server?
yeah... it might be better to keep the object around but just owned by another actor
or even something else temporarilly
I'm personally not clear on how ownership transfer works for replicated subobjects
I also don't know
So both server and client call ServerDropItem(Item, Quantity);?
ah no, authority early outs here (that's fine)
yeah I guess as I said earlier I don't see how this works unless the item persists long enough to onrep without being destroyed
Unrelated note. The pickup spawn part could benefit from deferred spawning.
yeah it doesn't make sense to trigger beginplay before setting the pickup
I agree
but probably not what is broken here unless somehow the item pickup affects the object (?) (I know you said unrelated, just checking)
Just need to make sure that you know what stuff can be called before finishing spawning and what has to be called afterwards
Na, hence the "unrelated note"
Ah the sneaky edit
It would save a lot of time to make this just a struct that is like
struct {
TSubClassOf<UItem> Item
uint16 Num = 0;
bool bIsEquippable = false;
bool bIsEquipped= false;
}
unless these need to actually be polymorphic
I didn't read anything about the actual issue
if there are just two kinds of things there is no need to suffer through subobject replication
I assume the item is removed and replication nukes it before client can react to the removal?
basically they are relying on an onrep of a subobject to fire that gets removed on the server and isn't happening somehow (which is GC related? or not? I can't tell anymore)
but that second part might be me misunderstanding
Aka something along the lines of shooting a projectile into a wall and triggering an explosion on impact on server and client, but the server calls destroy actor and that nukes the actor before it reaches the wall on the client.
I guess while you are here I am curious if it's simple to just transfer ownership of a replicated subobject to the new actor on the floor... that might kinda help keep it alive
I have +120 kinds of things and all are polymorphic already , all are overriding base functions from UItem for specific logics
are those 120 things mostly the enum you showed earlier?
Which is usually fixed by not destroying the actor instantly but rather turning off collision and visuals and setting the lifetime to like 5 seconds, so client can still do stuf
For an inventory of UObjects that's a different story though
it's just not a good practice at all for something like an inventory... for a cue or something I think that's perfectly sensible
if it is visual only maybe okay though
Why does the client need to have the actual UObject still?
Is this not something you can communicate via some meta data of the item?
yeah, they have ETypes, ESlots, EIDs etc
yeah, those are 3 numbers lol
sheesh... just send those unless they all have unique data in them or rpcs
If you were smart you made the fixed meta data of your items a DataAsset
So unless you need the state of the UObject, you can just point to that
and yeah EType::Weapon , EWeapon::AKM, EAKM::47, EAKM::48 etc
remember what I asked earlier
That should have been done with GameplayTags. But well.
I asked if they were POLYMORPHIC
those are just enum values... they are literally numbers witha name
Yeah they are all Polymorphic
Didn't Missty already write that the items override functions from UItem?
Yeah , and they derived from the UItemBase : UObject, UItem : UItemBase
okay, do they also have unique replicating properties?
it might be easier to just create the item instance on the client and have its replicated data come from a simple struct that is just an integer and the class or something
I would need to read through everything to even get what this is all about.
It reads like Missty needs the UItem instance on the client on removal from the inventory to do something and that item is dead before the client can do its thing
But no clue why
yeah I think if I had a stronger understanding of why the subobject is actually gone I would know but so far I don't think they want to follow our advice earlier of "just use a fast array"
The UItem should only be needed for state
Anything else should only need some meta data that the UItem should point to.
the onrep of the item being unequipped or the item being removed should probably do the same thing on the client... it might be the client gets the destroy packet which you can use to go "oh, time to unequip this"
A slot in an inventory could very much just be MetaData + ItemState
When removing it, the ItemState could die but the MetaData should be available to show notifications of removal with icons or other stuff
Or for locally spawning a pickup, which is not necessarily needed anyway as the server should spawn that
yeah,
UEquippable -> bEquipped
UWeapon -> Ammo
UVehicle -> driver, passenger1, passenger 2
and more
I think item replicating subobjects is perfectly justified here but the state of it being "atomic" as in equipped etc should not be from the object itself... that is the part I think is just inherently not ever going to be nice because of how replication works
the inventory should have what is equipped
the item should definitely know about if it is equipped, but maybe only via it being set from something on the inventory telling it that it is
an internal onrep of being equipped on the object = random chance it shows up
It's one of those cases where Actors have the TearOff logic for.
as I explained earlier onreps do not care if you change the value back and forth quickly etc
yep, there might actually be a callback on uobjects here too
ah. maybe PreDestroyFromReplication
yep! looks like GAS uses this
this might be useful here
@nocturne quail I would say consider trying to override PreDestroyFromReplication on your equippable and make that unequip it on the client
Yop. Due to replication the server has authority over the lifetime of the UObject. If you need the UObject to exist on Clients beyond whatever the Server says, then that's a conflict
I still think the equip state should be external though... (not discounting what exi said)
I am honestly surprised a replicating object can get GC'd because Iris kinda maintains references to roots at least... I wonder if Iris would handle this differently (it's not a root so probably not) (I don't think Missty is on Iris, just me yapping about Iris to myself)
I think the core setup is a bit faulty. One basically shouldn't design the inventory in a way that if the server destroys an object that the client can run into a scenario where it doesn't know what was in that slot
Iris doesn't work with UObjects 90% of the time.
the bridge does
the very lower level parts don't know what they are beyond network handles and member descriptors but Iris absolutely has object containers
Guys I saw unreal 5.7 has new system for replication named IRIS, is that system good for pawns and actors replication for multiplayer like character movement controller for character?
I don't think the object is being GCd in this case. It's a sub object anyway. Or does Missty crash?
Iris is pretty low level and replaces the original actor channel replication and rep graph. From an outside perspective you wouldn't even notice if it's on or off
So the question doesn't necessarily make sense
Don't even thnk about it unless you have a project that needs better server performance and you are willing to deal with netcode behaving differently internally. It's more applicable to projects that need faster network filtering/polling on the server
Iris is in a decent state and is not hard to swap in or out but when it breaks you will get things breaking in subtle ways that you may not be equipped to figure out quickly (at least I am not)
It's not broken enough to not recommend at all fwiw, just not a production ready feature that you can expect to work perfectly
Ahhh Iris. The "faster" solution that polls all objects if one sub object is dirty. The solution that doesn't actually handle per property dertiness but just the whole Object. The solution that breaks apart when actors constantly dirty themselves. Which of course would never happen, right? Unless Epic would be placing code into tick with comments that say "might not be changed but we dirty just in case."
is this the right use case?
void UEquippable::PreDestroyFromReplication()
{
if (GetWorld()->GetNetMode() == NM_Client)
{
if (AArmaCharacter* Character = Cast<AArmaCharacter>(GetOuter()))
{
SetEquipped(false);
}
}
Super::PreDestroyFromReplication();
}
calling super after, to first unequip
Sounds reasonable
But I still think that this might just be patching a broken core setup
I don't think you need to check for client netmode on here honestly but I'm not sure
on server it was already unequipped
and yeah still not a fan of this setup with the onrep on the item here... but maybe this can help
sorry, to be clear I think that PreDestroyFromReplication might not ever run anywhere but the client that destroys it from the server telling it to
but I'm not sure so I think you are making the right call to check just in case
Also not convinced this ever calls on the server
Function name kinda rules that out
Shouldn't there be a comment on the function potentially explaining that?
I guess to whoever wrote this it's "implied" lol
also looks like Actor->OnSubobjectDestroyFromReplication(SubObject); is a thing
that way the actor owner itself can listen to it
which might be nice here if it's not easy to see the inventory that owns this
works but with a 3sec delay
I thought it didn't work and was about to click the stop button and suddenly it works :d
LogTemp: Warning: [SERVER][DedicatedServer][World: Dedicated Server] OnRep_bEquipped called for OBJ_Helmet_lv1_C_0. bEquipped: 0
LogTemp: Warning: Unequipping OBJ_Helmet_lv1_C_0 from character BP_MaleCharacter_C_0
LogTemp: Warning: Character Role: LocalRole=3, RemoteRole=2
. 1sec
. 2sed
. 3sec
LogTemp: Warning: [CLIENT][Client][World: Client 1] OnRep_bEquipped called for OBJ_Helmet_lv1_C_0. bEquipped: 0
LogTemp: Warning: Unequipping OBJ_Helmet_lv1_C_0 from character BP_MaleCharacter_C_0
LogTemp: Warning: Character Role: LocalRole=2, RemoteRole=3
maybe I wrote my question in a wrong way, Now i have dedicated server and multiplayer games where I am using Pawn replication made just with RPC I am just replicating their whole transform every tick ( sometimes its laggy), I thought that IRIS is some sort of plugin which I can turn on at pawn or actor and it will handle replication for me.
Well, the answer remains the same.
Iris is low level replication code for the whole Engine. If you turn it on, it replaces the replication system that UE used until now. High level abstractions, such as RPCs and Property Replication, will continue to work as before. At least for the most part.
Iris is nothing that specifically targets a given Actor or Pawn Class and handles replication in some confined environment. It's literally either all or nothing. You will interact with Iris as much as you interact right now with the replication code that drives your game. Some parts work different, such as filtering and prioritization, and it works quite a lot different under the hood, but you wouldn't necessarily notice that if you just flip the switch.
If you enable Iris it won't fix any replication issue you have right now. It won't magically make your Transform Replication non-laggy.
Outer fails for the item, but it calls after 3 to 10 sec random base
first time it calls after 3 sec
now with two tries, it calls after 5 sec
Might be that it's just marked for "Destroy from Replication" and that kicks in from GC, which is why it's a random delay before it calls.
I stick with my comment that your underlying system for the inventory is faulty.
Yes its marked for pending destroy
If the Client needs the UObject to unequip it but the UObject is destroyed by the Server without the Client being able to handle this locally, then this is faulty at its core.
If the UObject gets destroyed, there shouldn't be a need to call SetEquipped(false) on it anymore.
The UObject should only be for internal state of the item, specific to the item.
E.g. Ammo Count if its a Magazine.
Yes client needs becasue I am using TMap for the sake of unique slots with an internet onrep state of UEquipable items
Doesn't change that I think the setup is faulty.
Your Inventory should be set up in a way, that the Client doesn't need the UObject.
A single Inventory Slot should have a pointer to the UObject and a pointer to the MetaData. And the MetaData should prefereably be something like a DataAsset with a proper PrimaryAssetId.
If an Item is equipped, only the Inventory Container really needs to know about that.
If the pointer to the UObject dies, the Client would still have the MetaData pointer and the PrimaryAssetId to communicate everything else.
If you then plug that properly into a FastArray, you end up with a proper PreRemove call for the Entry, when the Server replicates the Array having changed, so the Client can react to the Unequip part and still use the MetaData and PrimaryAssetId to handle it.
The UObject shouldn't be needed for that in theory.
The fact that the Slot has a valid MetaData assigned means the item is equipped.
The UObject shouldn't need that info.
ah okay i get it, I thought its something different like helping smoothly replicate everything else not just character which have native replication, thanks for answer
I will rethink the UEquipable part to make it use fast array maybe
If we take a Magazine as an item as an example.
The MetaData would have:
- Inventory Icon
- Name
- Description
- Gameplay Tags to describe the Item
- Can Item Stack?
- Max Stack Size
- Mesh Asset (for Pickup)
- Max Clip Size
- etc.
The UObject would have:
- Current Bullets in Magazine
- Either a number or even an Array with custom info if you can have different bullets in a magazine
If the item gets removed from the Inventory, and the UObject dies before the Client can react to it, then the only thing that would be lost is the Current Bullets in Magazine info fwiw.
The Client still knows all the MetaData of the Item that was removed.
Can show a notification with an Icon and a Name "AK74 Magazine Dropped".
Perfect Idea, and this will make life easier
The Pickup, for visuals, would also only need the MetaData to drive the rest of the stuff. Although, if the item isn't fully consumed, you'd just move the UObject to the Pickup Actor.
The Pickup, for simplicity, could just be a 1 Slot Inventory.
You want to design the Inventory in a way that almost all non-state data can be inferred at all times. If you still end up running into a situation where the UObject gets destroyed on the Server but you needed information that lives inside of it on the Client at the time the Client learns about the item being removed, then you can still think about that specific case.
If you generally need that UObject to always be available, like now, but for more valid reasons than calling SetEquipped, then you simply can't destroy it on the Server.
That's the same as the Projectile Actor example I posted.
If you handle impact on Client and Server with an explosion effect, and the Server additionally calls DestroyActor on Impact, then the Client might never even get the impact, cause the Actor gets destroyed too early. In which case the Actor has to be "hidden" for ~5 seconds and "turned off", so that the Client can locally still do its thing.
That's potentially less easy to do with a UObject, so you want to think about how you can get this to work. A delay is always a bit shitty. You could argue that the UObject shouldn't be replicated and the Client handles the UObject locally, but that adds a lot of new problems.
But tbh, if you change the setup to what I described and stop handling Equipment State inside the Item itself, then you are probably already good.
If you still run into an issue with the UObject being needed, then we can discuss this based on that exact issue you have. Anything else is just theory atm.
Also, might be a bit late, but Inheritance for a complex Inventory/Item system, is a nightmare.
You would have been a lot happier with a composition-based setup.
Where an Item being Equippable is defined by it having that "trait/fragment/whatever". That way you don't ever need multiple Item subclasses.
Only the Traits (you can call this a lot of things) would have inheritance going on and an Item would have an Array of Traits.
That would also remove any future headache if you ever run into something like "Oh, I have this one specific version of this Item that can be equipped, but all others can't, so now I have to reparent that Item class to be a child of UEquippableItem just for that, or duplicate all the custom logic into a new UEquippableItem child class that lives alongside the non-equippable version".
Inheritance is just not nice for complex Inventories/Items.
But that's besides the point. You are quite far with your stuff, so yeah, for the future maybe.
also I am still stucked with the case one, when the same operation is called from interaction, it works fine.
UItem* UInventoryComponent::AddItem(class UItem* Item, const int32 Quantity)
{
if (GetOwner() && GetOwner()->HasAuthority())
{
// Create new item instance
UItem* NewItem = NewObject<UItem>(GetOwner(), Item->GetClass());
NewItem->World = GetWorld();
NewItem->SetQuantity(Quantity);
NewItem->OwningInventory = this;
NewItem->UniqueItemGuid = Item->UniqueItemGuid;
NewItem->OnServerItemAddedToInventory(this);
Items.Add(NewItem);
NewItem->MarkDirtyForReplication();
OnItemAdded.Broadcast(NewItem);
OnRep_Items();
return NewItem;
}
return nullptr;
}
void UEquippable::OnServerItemAddedToInventory(class UInventoryComponent* Inventory)
{
AArmaCharacter* Character = Cast<AArmaCharacter>(Inventory->GetOwner());
if (!Character || !Character->HasAuthority())
return;
if (Character->GetEquippedItems().Contains(Slot))
{
UEquippable* AlreadyEquippedItem = *Character->GetEquippedItems().Find(Slot);
if (!IsValid(AlreadyEquippedItem))
return;
//Case One
if (!bEquipped)
{
AlreadyEquippedItem->SetEquipped(false);
Character->DropItem(AlreadyEquippedItem, Quantity);
}
else
{
//Case Two
SetEquipped(false);
Character->DropItem(this, Quantity);
return;
}
}
//finally equip new item
SetEquipped(!IsEquipped());
}
What's the issue with that?
this reached case one if the slot is already occupied, and it unequip and drop successfully without issues
but if i call OnServerItemAddedToInventory manually on an item it is an issue
AddItem is only server
Hm. I get why you handle that in the Item, but I would have done that in the InventoryComponent.
Look how many calls to external functions you have in there.
How can this reach case 2?
if this is true if (Character->GetEquippedItems().Contains(Slot))
the mapped slot already have this item
You add ItemA to the Inventory. ItemA then checks if the Slot is already occupied. If it is, then you either drop the ItemA or the already existing ItemB.
But I don't get how Case 2 can happen.
The item was just added, why would it be equipped?
And why do you call DropItem on it in that case? You haven't even called Items.Add(..) yet at that point.
helmet lv1, helmet lv2 both are derived from UEquippable
Yeah but you are call OnServerItemAddedToInventory on the NewItem
and TMap hold them like this
//Add modular meshes to TMap slots
PlayerMeshes.Add(EEquippableSlot::E_Hair, SKM_Hair);
PlayerMeshes.Add(EEquippableSlot::E_Chest, SKM_Chest);
PlayerMeshes.Add(EEquippableSlot::E_Legs, SKM_Legs);
PlayerMeshes.Add(EEquippableSlot::E_Feet, SKM_Feet);
PlayerMeshes.Add(EEquippableSlot::E_Helmet, SKM_Helmet);
PlayerMeshes.Add(EEquippableSlot::E_Armor, SKM_Armor);
PlayerMeshes.Add(EEquippableSlot::E_Backpack, SKM_Backpack);
How is NewItem already equipped at that point? That makes no sense to me.
New Item is not equipped, but another item of the same slot can be equipped possibly
Yeah, but that's Case 1 or not?
yeah case one
So how does Case 2 ever happen?
My bad 😄 miss spelled that sorry
bEquipped is false on NewItem. I don't see how an AddToInventory call could ever fall into Case 2.
Yeah it enters in case1 if there is an item
I know, why do you have Case 2?
if the slot is already occupied, and I want to unequip it specifically using slot drag drop in UI