#multiplayer

1 messages · Page 283 of 1

latent heart
#

That's the usual UE way.

dark parcel
#

but the world only created at request.

#

it's not like dangling like battlefield servers

latent heart
#

You'd probably be better off letting your users host severs, then you don't need to worry about server costs.

dark parcel
#

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.

dark parcel
#

why not just post it here?

slate phoenix
#

I'll send you an example game.

dark parcel
#

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.

slate phoenix
#

Actually, there's a 2D game, and I'm making a 3D version of it.

#

write google growtopia

dark parcel
#

simplicity of the game doesn't correlate much with the difficulty here imo.

#

but yeah I googled it.

slate phoenix
#

So how does the logic of that game work then?

dark parcel
#

I don't have the source code, wouldn't know.

#

but not made by UE so, kinda irrelevant?

slate phoenix
#

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.

dark parcel
#

not sure what 'one server is opening it in the world that's opened' mean.

slate phoenix
#

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.

dark parcel
#

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.

slate phoenix
#

Thank you, I will do some research.

dark parcel
# slate phoenix 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.

slate phoenix
#

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.

dark parcel
#

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.

slate phoenix
#

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 ❤️

dark edge
#

How do Palworld dungeons work you think?

halcyon ore
#

From what I recall from them.
There just a sub level under the map sorta deal?

kindred widget
gray blade
#

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.

kindred widget
nova wasp
#

is the player controller/gamemode setup the same? are you seeing your intended gameplay classes for things?

kindred widget
#

Curious where you create your HUD or add your mapping contexts?

gray blade
#

im using steam subsystem but im not sure how relevant this is for in editor testing

nova wasp
#

yeah no HUD would be my first thing to look at too.. I would assume the player controller is doing something weird

kindred widget
#

Steam stuff won't matter here.

nova wasp
#

showdebug EnhancedInput can reveal if you have any IMCs mapped right now at all

gray blade
#

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

gray blade
#

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

nova wasp
#

skow your pie settings

gray blade
nova wasp
#

anything in the output log?

gray blade
nova wasp
#

I suppose before that

gray blade
gray blade
nova wasp
#

I am referring to the output log

gray blade
fast yoke
#

What's a good way of making a certain event not trigger until all players are loaded into the map

tardy fossil
#

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

nocturne quail
#

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

kindred widget
nocturne quail
#

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

kindred widget
#

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?

nocturne quail
#

yes setting the correct mesh only on server and owning client

#

on other clients there is nothing attached

kindred widget
#

Where are the other clients failing at setting the mesh?

nocturne quail
#

I can't find it, since it passes all checks

kindred widget
#

That implies that GetSlotStaticMeshComponent is returning null, it's not passing CanEquippAccessory, or the Accessory->Mesh is not set.

nocturne quail
#
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

kindred widget
#

What about the mesh on the other clients? On the accessory.

nocturne quail
#

beginplay calls on other clients also so it will be initialized on all clients

kindred widget
#

I mean the UStaticMesh.

#

Where you're calling Mesh->SetStaticMesh(Accessory->Mesh);

This Mesh property in Accessory->Mesh

nocturne quail
#

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;
    }
}
kindred widget
#

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.

nocturne quail
#

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

kindred widget
#

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.

nocturne quail
#

break pointed it, its not called in this process

kindred widget
#

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);```
nocturne quail
#

Sure, trying it now

nocturne quail
#

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

nocturne quail
#

new cleaned refactored code

#

but it doesn't solve the issue

#

maybe I need to call return EquipAccessory(AccItem); with a delay?

nocturne quail
#

@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

crystal anchor
#

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?

latent heart
#

It probably fails to load the map and then opens the default one.

#

Answer: package the correct map.

crystal anchor
#

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 ..

thin stratus
#

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?

frosty harbor
#

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

frosty harbor
#

happens the same regardless

dark edge
frosty harbor
#

I know if moving it's just the latency diff

thin stratus
#

GetBaseAimRotation still uses that under the hood.

rustic ore
frosty harbor
thin stratus
#

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.

frosty harbor
#

🤔

#

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?

rustic ore
dark edge
#

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

rustic ore
dark edge
#

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

rustic ore
dark edge
#

Probably get a bit more precision for the bits but I'm not sure

thin stratus
rustic ore
thin stratus
#

That is probably unrelated :P

quasi tide
#

I love working on co-op games.
Client: "Hey - I hit this target!"
Server: "Alright"
Done.

frosty harbor
thin stratus
thin stratus
#

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.

frosty harbor
#

actually I think I know why i had issues with the camera

thin stratus
hallow dune
#

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

quasi tide
vapid fog
#

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.

crystal anchor
# thin stratus Is maybe one of the instances you are loading not packaged?

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

thin stratus
#

Doesn't mean that it should be ignored that there is a potential bug, but in theory you shouldn't even use that anyway.

exotic wasp
#

I've found a starting position to be best. I just check against the bounding box

river jewel
#

Does it cost enything for a server?

dark edge
river jewel
dark edge
#

ok

river jewel
dark edge
young spoke
#

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

dark edge
young spoke
#

also to be clear, i'm only talking about reliable transmission

reef bison
#

(assuming you already have replicated attributes working)

young spoke
#

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

reef bison
#

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?

young spoke
#

with a queue system i guess

#

but thast basically my question

reef bison
#

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

young spoke
#

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

reef bison
#

well the concept is the same regardless of genre, you basically only use multicasts to communicate one time and transient events

young spoke
#

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?

thin stratus
# young spoke do you send an id instead of a actor pointer, and then manually queue it and pro...

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.

reef bison
#

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

young spoke
thin stratus
#

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.

thin stratus
#

I wouldn't worry too much about that. The main benefit of UE is that you don't have to care.

torn hull
#

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?

meager spade
#

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

torn hull
#

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

meager spade
#

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

nova wasp
#

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

red musk
#

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?

kindred widget
#

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.

red musk
#

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

verbal ice
#

Yeah wasn't an actual guarantee by the engine, guess it may have been the case until that version

#

Interesting though

thin stratus
#

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.

verbal ice
#

oh god

latent heart
#

You could put the variables on a struct which is marked as atomic.

#

But that has other implications.

verbal ice
#

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

thin stratus
#

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...

▶ Play video
#

(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.

nova wasp
#

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

nocturne quail
#

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);
        }
thin stratus
#

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.

nova wasp
nocturne quail
thin stratus
nocturne quail
#

and equipping also checks on server if this item exists in inventory

thin stratus
#

Yeah but that's still all I can tell you based on what you shared.

nova wasp
#

Also no clue what is passed into the RPC or what the rest of this function does

nocturne quail
#

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)
        {
nova wasp
#

"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

nocturne quail
#

Yeah, like level 2 backpack is full and player trying to equp level 1, it should fail

thin stratus
#

And?

#

Do we have to go through this again? You aren't sharing enough information.

nocturne quail
#

should return: return FItemAddResult::AddedNone(GearItem->GetQuantity(), FText::Format(FText::FromString("Couldn't Equip {ItemName}, Capacity out of bounds"), Args));

thin stratus
#

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.

nova wasp
#
                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

nocturne quail
thin stratus
#

Yeah, but you said it sometimes passes it.

nocturne quail
thin stratus
#

So put some UE_LOGs up.

nova wasp
#

how can we possibly know what GetCurrentWeight() does? does it rely on the input rpc? some other value?

thin stratus
#

I assume the logic wants to allow adding a backpack if it can carry the current inventory.

nocturne quail
thin stratus
#

So you can swap out the current Backpack with a new one, as long as it doesn't end up being too small.

nova wasp
thin stratus
#

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.

nova wasp
#

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

thin stratus
#

And try to understand why.

nocturne quail
thin stratus
#

We can't remote debug it this way.

nocturne quail
nova wasp
#
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

nocturne quail
#

increasing it based on level

nocturne quail
nocturne quail
#

tried 100 times replacing backpack, it fails always if the weight not match

nova wasp
#

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)

nocturne quail
#

not sure why making it explicitly local solves the issue

#

maybe its a float overflow?

nova wasp
#

do you assign it to infinity or something?

nocturne quail
#

I mean float has issues with ==

#

and most of the time i use isnearlyequal

nova wasp
#

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

nocturne quail
#

yeah, cause my items has weights like 0.25f 0.013f etc

nova wasp
#

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

nocturne quail
#

so on each fire, the inventory weight changes

nova wasp
#

so once it reaches zero ammo does it actually hit zero/below zero or some small value?

nocturne quail
#

I will change the system to use the inventory weight based on uin8

#

and each will have wight also in uint8

nova wasp
#

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

silent valley
#

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.

slate phoenix
#

finally work 😄

#

but have ping 300ms

thin stratus
#

Is each block an Actor? 😭

slate phoenix
# thin stratus 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.

quasi tide
#

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

slate phoenix
#

hmm

#

I will work on it.

#

Okay, what if I just replicate the actors once instead of constantly replicating them with RPC?

dark edge
#

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

slate phoenix
#

my game not minecraft

#

50x50x40

#

max world

#

50x50x20 =50k block

#

20 z= air

dark edge
#

You'll still want ISM but that's not impossible

slate phoenix
#

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

dark edge
#

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

slate phoenix
#

👍🏻 🫡

#

i will back 😄

thin stratus
#

ISMs can have Per Instance Properties in Materials, so you can even drive the visuals for the blocks while keeping them as ISMs

slate phoenix
slate phoenix
slate phoenix
peak sentinel
#

when a struct property is replicated, does unreal update a delta or whole struct?

haughty ingot
exotic wasp
peak sentinel
#

so FHitResult

#

would send 280 bytes

#

yay

haughty ingot
#

Well I could be wrong, also I believe FHitResult has a custom net serializer

exotic wasp
#

I know there's an atomic specifier but I think it's deprecated

haughty ingot
#

Oh I had it backwards

#

In IRIS they are atomic

#

Default they are not

peak sentinel
#

makes sense it has one

haughty ingot
#

Ye so Iris they're atomic, otherwise no.

#

I open the engine once every 5 decades now

peak sentinel
#

yet somehow slackers is so alluring enough to hang out

haughty ingot
#

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

peak sentinel
#

i dont get it

#

isnt iris suppose to be very fast

#

how is this efficient

haughty ingot
#

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

nova wasp
#

it's mostly just forwarding the serialization work to the inner types but just skipping doing things that it doesn't need to

nova wasp
#

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

nova wasp
#

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

thin stratus
thin stratus
haughty ingot
#

Is there another way

nova wasp
#

huh, I must be an LLM because I could swear there was a trait

#

It really is just netserialize or else I guess

haughty ingot
#

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

nova wasp
#

oh well

haughty ingot
#

Just disable replication and send the packet yourself

#

Be your own boss

nova wasp
#

it's actually possible to do that

#

and actually useful to do so in some cases

halcyon ore
#

Ended up making my own fake MARK AS DIRTY system. lol

nova wasp
halcyon ore
#

Using an RPC.
Ain't no way I could make a new replication system

nova wasp
#

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

halcyon ore
#

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.

nova wasp
#

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

haughty ingot
nova wasp
#

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"

halcyon ore
#

What would be the benefit?
No limit to break shit, or what?
Or, just a separate limit, for the individual layer?

nova wasp
#

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

halcyon ore
#

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.

nova wasp
#

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

halcyon ore
#

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

meager spade
#

borderlands has multiple channels

#

iirc it has UChannels for all kinds of things

halcyon ore
#

Interesting.
For what I wonder?
IIRC, doesn't the game "look basic" on the networking front?
Just glorified weapon actors, with replicated vars?

meager spade
#

Inventory, skills, etc i know for a fact they have there own UChannel for them

#

i made my own UChannel for ingame pings

haughty ingot
#

Tournament did some funky shit for projectiles back in the day with em

#

This is ancient history

halcyon ore
meager spade
#

i cant remember this was back in UE 4.18

halcyon ore
#

Oh. lol

#

I thought it was recent

meager spade
#

cause you have control over the packet

#

and where it goes

#

how its decoded, etc

haughty ingot
#

He’s drunk

halcyon ore
#

lol

meager spade
#

im not that drunk

#

i baded it on UVoiceChannel

#

based

halcyon ore
#

It just makes me curious to learn from others, with more experience, especially in the specific thing being spoken about. 😛

meager spade
#

Iris is the future though

#

our net tick in our game is almost half the cost compared to non iris

nova wasp
#

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

meager spade
#

also iris has spatial stuff like ReplicationGraph

#

which is really nice

#

though i had to silence a warning in it

#

was bugging me

dark parcel
#

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.

thin stratus
latent heart
#

There's one to make it only run on client. (random facts)

dark parcel
nova wasp
#

the ol reliable ensure(HasAuthority)

latent heart
thin stratus
latent heart
dark parcel
#

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.

thin stratus
#

Then clients can call the method and nothing will happen

#

Put the ensure into an #else

latent heart
#

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.

lost inlet
#

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()

latent heart
#

'tis what all our checks do.

thin stratus
#

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.

slate phoenix
thin stratus
#

Pretty sure multiple people told you by now that you should do it differently, yeah.

slate phoenix
#

So, what do you think would happen if I didn't install something like World Manager and instead used the Voxel plugin?

thin stratus
#

No clue. Haven't used either.

slate phoenix
#

It will take me a lot of time to rewrite it from scratch; I even made the multiplayer version...

#

thanks

thin stratus
#

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.

slate phoenix
#

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.

thin stratus
#

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.

nova wasp
thin stratus
#

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".

nova wasp
#

local role of one actor vs world netmode I guess is the distinction here?

latent heart
#

I hear netmode has its own issues, though also very remote. Before the world's netmode is correctly set, it can lead to errors.

weary bay
#

I'm trying Chaos Mover and just have

frosty harbor
#

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).

slate phoenix
#

@thin stratus Right now, only the mesh sizes are different; I'll try to fix that.

nocturne quail
#

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

dark edge
#

Why are you using a map here anyway?

nocturne quail
dark edge
#

What is the replicated state representing which items a character has equipped?

nocturne quail
#

I know tmaps don't support replication, that's why taking benefits of it by setting it using onrep of equipped item

dark edge
#

I load into the game, and walk towards another character. How do I know what they are wearing?

nocturne quail
dark edge
dark edge
#

onrep of what

nocturne quail
#

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

dark edge
#

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.

nocturne quail
#

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

dark edge
#

You need to think about how it can fail given that things can arrive out of order or not arrive at all on clients

nocturne quail
#

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

nova wasp
#

how would that affect the order of how anything occurs

nocturne quail
nova wasp
#

well

nocturne quail
#

I have no other clue why DropItem will exec if the item has not yet unequipped

nova wasp
#

these all happen on one thread

#

unless you are tasking away work somehow

nocturne quail
#

hmm, not tasking away

#

so all on same thread but still bugged

nova wasp
#

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

nocturne quail
nova wasp
#

I don't know what that does and I do not know if I have time to ask you to explain that

nocturne quail
nova wasp
#

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

nocturne quail
nova wasp
#

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

nocturne quail
nova wasp
#

how can I know what type it is from the cpp file?

nocturne quail
dark edge
#

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

nocturne quail
nova wasp
#

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

dark edge
#

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

nocturne quail
nova wasp
#

which is why I am trying to see what the onrep ACTUALLY REPRESENTS but "its an item" is not useful here

dark edge
#

ItemB.Equip() can fire before ItemA.Unequip(), and vice versa. You have no way to know which order they will call in

nocturne quail
#

if item is equipped its true, else its false
I don't see any issue with this

    UPROPERTY(ReplicatedUsing = OnRep_EquipStatusChanged)
    bool bEquipped;
dark edge
nova wasp
#

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

dark edge
#

The whole flow of references seems just ass backwards

nocturne quail
# dark edge ItemB.Equip() can fire before ItemA.Unequip(), and vice versa. You have no way ...

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());
dark edge
nova wasp
dark edge
#

you have 2 places that say if something is equipped or not instead of 1

nova wasp
#

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

dark edge
#

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

nocturne quail
nova wasp
#

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

dark edge
#

if you must query for what's equipped then iterate over the owned items

nocturne quail
nova wasp
#

that's fine? it doesn't need to be what control if its unequipped or not though

dark edge
#

The item can still be independent, just have Character be the thing that says what's equipped to it

nocturne quail
nova wasp
#

it can absolutely have its own data about its current statistics etc but multiple replicating things = random every time (in terms of order)

dark edge
#

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.

nova wasp
#

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

nocturne quail
nova wasp
#

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

nocturne quail
nova wasp
#
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

nocturne quail
#

If I do character->interact->inventory->added->character->getanyitem->statuschange this is just not clean to me

nova wasp
#

replication does not care. It is EVENTUALLY consistent inside of the current bandwidth limits and network conditions

nocturne quail
#

OnRep_EquipStatusChanged is called when bEquipped is changed and this is changed by inventory when item is added

nova wasp
#

does each item have an UEquippable object or not

nocturne quail
#

inventory checks the type of the item which is currently added to tell it what to do

nocturne quail
nova wasp
#

fine, but what does the inventory have to represent the item BEING THERE in the first place?

nocturne quail
#

replicated TArray

nova wasp
#

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

nocturne quail
#

I am in this issue because I am not using fast array?

meager spade
#

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

nova wasp
#
    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

nocturne quail
# nova wasp then you can use replication callbacks

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;
}
nova wasp
#

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?

nocturne quail
meager spade
#

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

nova wasp
#

trying to save lines but increasing random hard to debug stuff is not nice in gameplay code

meager spade
#

and your not even chekcing if Item is valid before derferenceing

nocturne quail
nova wasp
#

which is not in that code or any code you posted earlier

meager spade
#

see every inventory system i have ever done uses FastArraySerializer

nova wasp
#

we can only see what you show us

nocturne quail
meager spade
#

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

nova wasp
#

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

nova wasp
nocturne quail
#

I am trying to not place the unequipped item in inventory but drop it

nova wasp
#

which happens on which side? client or server?

meager spade
#

see this is where my fast array approach really shines

#

it just takes out so much of the headache

nocturne quail
#

DOREPLIFETIME(UEquippable, bEquipped); currently I have this in this state

#

RepNotify_Always worth to test?

nova wasp
#

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

nocturne quail
nova wasp
#

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

meager spade
#

normally you name the OnRep the same as the property

#

OnRep_Equipped or something

nocturne quail
meager spade
#

🤮

#

i hate stuff that calls OnRep_Blah

#

i have SetEquipped_Internal for example that both paths call

#

that kinda stuff

nocturne quail
#

OnRep_EquipStatusChanged named it like this cause its handling the equip/unequip state

nova wasp
#

that's fine but read what I said

meager spade
#

btw wtf is this MarkDirtyForReplication

nova wasp
#

I don't care about why you chose to name it that, we can ONLY SEE what you show us

meager spade
#

just enlighten me

nova wasp
#

MarkDirtyForReplication is an ancient way to update subobjects isnt it?

meager spade
#

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

nova wasp
#

I do not know what version they are on (we generally assume you are on a recent ue5 version unless you tell us)

nocturne quail
meager spade
#

hmm

#

looks like a bad implementation of some delta thing

nova wasp
#

yeah I'm only seeing this in ancient actor channel code

#

this is not the intended way to replicate a subobject in ue5 afaik

meager spade
#

yeah

nocturne quail
#

but it works , I shifted this project from 4

meager spade
#

this looks like the old 4.12 thing

nova wasp
#

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

meager spade
#

cause they took this out way back in 4.14 iirc

nova wasp
meager spade
#

we have subobject lists in UE5

#

far superior

nocturne quail
#

my whole project depends on this legacy system and it works fine, have no intentions to change it for this project 😄

meager spade
#

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

nova wasp
#

if you want help and are doing stuff in a weird way we can never assume that or possibly reason about this

nocturne quail
nova wasp
#

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

meager spade
#

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

nova wasp
#

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

nocturne quail
nova wasp
#

nope, that's not what I mean and you know it

nocturne quail
#

OnServerItemAddedToInventory calls by addedtoinventory callback

nova wasp
#

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

nocturne quail
#

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

nova wasp
#

at no point did I say I needed to read the entire header, no need to imply that

nocturne quail
#
UCLASS(Abstract, Blueprintable, EditInlineNew, DefaultToInstanced)
class ARMA_API UItem : public UObject
UCLASS(Blueprintable)
class ARMA_API UEquippable : public UItem
{
    GENERATED_BODY()
public:
nocturne quail
nova wasp
#

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

nocturne quail
#

I am compiling with a change of DOREPLIFETIME_CONDITION_NOTIFY(UEquippable, bEquipped, COND_None, REPNOTIFY_Always);

#

lets see if this solved the issue

nova wasp
#

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

nocturne quail
#

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

nova wasp
#

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...

nocturne quail
#
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 
nova wasp
#

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

dark parcel
#

I read in the past that reliable rpc from the same actor, guarantees to come in order. Is that true?

kindred widget
nocturne quail
# nova wasp relying on unrelated onreps to pop the value from the tmap = random

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;
}

nova wasp
#

use ensures and error logging unless this failing is expected

nocturne quail
#

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();
}
nocturne quail
#

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

nova wasp
#

some of these logs leave out which world they are on

#

I assume they relate to the one they are right below

nocturne quail
nova wasp
#

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?

nocturne quail
#

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

nova wasp
#

put this context in the original question

nocturne quail
#

Item gets garbaged, before it reach clients

dark parcel
#

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.

nocturne quail
nova wasp
#

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

nocturne quail
nova wasp
#

what does "if I manually want to drop the item" mean

#

on the server? client?

nocturne quail
nova wasp
#

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?

nocturne quail
nova wasp
#

so there are two rpcs here that we can't see that are unknown

nocturne quail
#

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)
nova wasp
#

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

nocturne quail
nocturne quail
nova wasp
#

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;
nova wasp
#

that is not what I asked

nocturne quail
#

in the second case

#

doing ensure was breakpointing it

nova wasp
#

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

nocturne quail
# nova wasp what does "in the second case" mean... the second if statement??

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

nova wasp
#

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

nocturne quail
#

there are uammoitem

#

uweapon etc

nova wasp
#

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

nocturne quail
#

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

nova wasp
#

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

nocturne quail
#

on server which is correct

nova wasp
#

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

nocturne quail
nova wasp
#

Character->DropItem(AlreadyEquippedItem, Quantity); , right?

#

remember: be specific

nocturne quail
#

yes

#

let me post that drop item function also

nova wasp
#

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

nocturne quail
#
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;
}
nova wasp
#

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

nocturne quail
#

I also don't know

nova wasp
#

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

thin stratus
#

Unrelated note. The pickup spawn part could benefit from deferred spawning.

nova wasp
#

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)

thin stratus
#

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

nova wasp
#

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

thin stratus
#

I didn't read anything about the actual issue

nova wasp
#

if there are just two kinds of things there is no need to suffer through subobject replication

thin stratus
#

I assume the item is removed and replication nukes it before client can react to the removal?

nova wasp
#

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

thin stratus
#

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.

nova wasp
#

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

nocturne quail
nova wasp
#

are those 120 things mostly the enum you showed earlier?

thin stratus
#

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

nova wasp
#

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

thin stratus
#

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?

nocturne quail
nova wasp
#

yeah, those are 3 numbers lol

#

sheesh... just send those unless they all have unique data in them or rpcs

thin stratus
#

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

nocturne quail
#

and yeah EType::Weapon , EWeapon::AKM, EAKM::47, EAKM::48 etc

nova wasp
#

remember what I asked earlier

thin stratus
nova wasp
#

I asked if they were POLYMORPHIC

#

those are just enum values... they are literally numbers witha name

nocturne quail
nova wasp
#

this means being actual unique classes of uobject

#

not just "it has an enum value"

thin stratus
#

Didn't Missty already write that the items override functions from UItem?

nocturne quail
#

Yeah , and they derived from the UItemBase : UObject, UItem : UItemBase

nova wasp
#

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

thin stratus
#

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

nova wasp
#

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"

thin stratus
#

The UItem should only be needed for state

#

Anything else should only need some meta data that the UItem should point to.

nova wasp
#

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"

thin stratus
#

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

nocturne quail
#

yeah,
UEquippable -> bEquipped
UWeapon -> Ammo
UVehicle -> driver, passenger1, passenger 2

and more

nova wasp
#

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

thin stratus
#

It's one of those cases where Actors have the TearOff logic for.

nova wasp
#

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

thin stratus
#

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

nova wasp
#

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)

thin stratus
#

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

thin stratus
nova wasp
#

the very lower level parts don't know what they are beyond network handles and member descriptors but Iris absolutely has object containers

real ridge
#

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?

thin stratus
#

I don't think the object is being GCd in this case. It's a sub object anyway. Or does Missty crash?

thin stratus
#

So the question doesn't necessarily make sense

nova wasp
#

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

thin stratus
#

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."

nova wasp
#

that was fixed in .7

#

but yes that was like "wait what??" lol

#

what was the point...

nocturne quail
#

calling super after, to first unequip

thin stratus
#

Sounds reasonable

#

But I still think that this might just be patching a broken core setup

nova wasp
#

I don't think you need to check for client netmode on here honestly but I'm not sure

nocturne quail
nova wasp
#

and yeah still not a fan of this setup with the onrep on the item here... but maybe this can help

nocturne quail
#

for clients only fails

#

compiling it now, maybe it will solve the issue very hope

nova wasp
#

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

thin stratus
#

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?

nova wasp
#

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

nocturne quail
#

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
real ridge
# thin stratus So the question doesn't necessarily make sense

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.

thin stratus
#

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.

nocturne quail
#

first time it calls after 3 sec

#

now with two tries, it calls after 5 sec

thin stratus
#

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.

nocturne quail
thin stratus
#

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.

nocturne quail
thin stratus
#

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.

real ridge
nocturne quail
thin stratus
#

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".

nocturne quail
#

Perfect Idea, and this will make life easier

thin stratus
#

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.

nocturne quail
# thin stratus That's potentially less easy to do with a UObject, so you want to think about ho...

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());
}
thin stratus
#

What's the issue with that?

nocturne quail
#

but if i call OnServerItemAddedToInventory manually on an item it is an issue

#

AddItem is only server

thin stratus
#

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.

nocturne quail
#

if this is true if (Character->GetEquippedItems().Contains(Slot))

#

the mapped slot already have this item

thin stratus
#

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.

nocturne quail
thin stratus
#

Yeah but you are call OnServerItemAddedToInventory on the NewItem

nocturne quail
#

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);
thin stratus
#

How is NewItem already equipped at that point? That makes no sense to me.

nocturne quail
thin stratus
#

Yeah, but that's Case 1 or not?

nocturne quail
#

yeah case one

thin stratus
#

So how does Case 2 ever happen?

nocturne quail
#

My bad 😄 miss spelled that sorry

thin stratus
#

bEquipped is false on NewItem. I don't see how an AddToInventory call could ever fall into Case 2.

nocturne quail
thin stratus
#

I know, why do you have Case 2?

nocturne quail
#

if the slot is already occupied, and I want to unequip it specifically using slot drag drop in UI