Jump to content

[1.15.2] Capability Synchronization


lisilew

Recommended Posts

Quote

By default, Capability data is not sent to clients. In order to change this, the mods have to manage their own synchronization code using packets.

There are three different situation in which you may want to send synchronization packets, all of them optional:

1. When the entity spawns in the world, or the block is placed, you may want to share the initialization-assigned values with the clients.

2. When the stored data changes, you may want to notify some or all of the watching clients.

3. When a new client starts viewing the entity or block, you may want to notify it of the existing data.

I have a capability that is attached to monsters.

When I sync the capability, which events should I listen to?

I was thinking LivingSpawnEvent for the first case and PlayerEvent.StartTracking for the third case.

Are those two events enough or are there any events should I listen to other than those two?

Edited by lisilew
Link to comment
Share on other sites

3 hours ago, lisilew said:

PlayerEvent.StartTracking for the third case.

Only this event and when it changes. A player in a completely different area of the world doesn't know about an Entity near another player far away and therefore does not need to know about any Capability changes.

3 hours ago, lisilew said:

When the stored data changes, you may want to notify some or all of the watching clients.

There are two ways to do this. Have your Capability on each entity keep track of each player that is tracking it on the server. Get the tracking Player in the PlayerEvent.StartTracking when you initially send the data. And don't forget to remove the player from the Capability tracking list in PlayerEvent.StopTracking.

VANILLA MINECRAFT CLASSES ARE THE BEST RESOURCES WHEN MODDING

I will be posting 1.15.2 modding tutorials on this channel. If you want to be notified of it do the normal YouTube stuff like subscribing, ect.

Forge and vanilla BlockState generator.

Link to comment
Share on other sites

1 hour ago, Animefan8888 said:

There are two ways to do this. Have your Capability on each entity keep track of each player that is tracking it on the server. Get the tracking Player in the PlayerEvent.StartTracking when you initially send the data. And don't forget to remove the player from the Capability tracking list in PlayerEvent.StopTracking.

Can storing players in the capability be replaced with

// Send to all players tracking this chunk
INSTANCE.send(PacketDistributor.TRACKING_CHUNK.with(chunk), new MyMessage());

?

Link to comment
Share on other sites

9 minutes ago, lisilew said:

Can storing players in the capability be replaced with

I think it can it would be best to test this yourself however.

VANILLA MINECRAFT CLASSES ARE THE BEST RESOURCES WHEN MODDING

I will be posting 1.15.2 modding tutorials on this channel. If you want to be notified of it do the normal YouTube stuff like subscribing, ect.

Forge and vanilla BlockState generator.

Link to comment
Share on other sites

5 hours ago, diesieben07 said:

All that is left to do is send the data when it changes, which you can do using PacketDistributor.TRACKING_ENTITY (this sends to all players who can "see" or rather who "are aware of" the entity).

If I send packet using PacketDistributor.TRACKING_ENTITY whenever capability changes, will players who were not tracking that entity at that moment get updated when they starts tracking that entity?

5 hours ago, diesieben07 said:

If you have data attached to players it gets a little more complicated.

I am also going to add different capability to players so can you elaborate more on this or are you talking about PlayerEvent.Clone?

Link to comment
Share on other sites

3 hours ago, diesieben07 said:

Of course, because at the moment they start tracking it, PlayerEvent.StartTracking will fire and you will send the (then current) data.

I apologize if this question is redundant and annoys you.

I just started to understand capability system so I want to make everything clear.

When you say PlayerEvent.StartTracking will be fired to send modified data from untracked entities, do you mean I have to do something when PlayerEvent.StartTracking is fired or Forge's capability system does the job of taking care of capability modification on untracked entities as long as I use PacketDistributor.TRACKING_ENTITY?

Link to comment
Share on other sites

10 minutes ago, diesieben07 said:

You have to subscribe to PlayerEvent.StartTracking and then send the packet to the player who is now tracking (not PacketDistributor.TRACKING_ENTITY!).

public class MyPacket {
    private int id;
    private boolean flag;
    
    public MyPacket(int id, boolean flag) {
        this.id = id;
        this.flag = flag;
    }
    
    public MyPacket(PacketBuffer packetBuffer) {
        id = packetBuffer.readInt();
        flag = packetBuffer.readBoolean();
    }
    
    public void encode(PacketBuffer packetBuffer) {
        packetBuffer.writeInt(id);
        packetBuffer.writeBoolean(flag);
    }
    
    public void handle(Supplier<NetworkEvent.Context> supplier) {
        NetworkEvent.Contxext context = supplier.get();
        context.enqueueWork(() -> {
            ServerPlayerEntity player = context.getSender();
            if (player != null) {
                Entity entity = player.world.getEntityByID(id);
                if (entity != null) {
                    entity.getCapability(Capabilities.MY_CAPABILITY).ifPresent(capability -> {
                        capability.setFlag(flag);
                    });
                }
            }
        });
        context.setPacketHandled(true);
}

Above is the packet that is sent using

PacketHandler.INSTANCE.send(PacketDistributor.TRACKING_ENTITY.with(() -> entity), new MyPacket(entity.getEntityId(), true));

when the capability changes from initially false to true under a condition.

@SubscribeEvent
public static void onStartTracking(PlayerEvent.StartTracking event) {
    event.getTarget().getCapability(Capabilities.MY_CAPABILITY).ifPresent(capability -> {
        PacketHandler.INSTANCE.send(PacketDistributor.PLAYER.with(event.getPlayer()), new MyPacket(capability.getEntityId(), capability.getFlag()));
    })
}

Is this correct way to synchronize the capability?

Link to comment
Share on other sites

1 minute ago, diesieben07 said:

This is a server to client packet. Doing this here does not make any sense (the sender is the server, not a player and there is no ServerPlayerEntity on the client).

Then, do I need different packet that uses Minecraft.getInstance().player instead of ServerPlayerEntity or just change declaration type to PlayerEntity?

Link to comment
Share on other sites

5 minutes ago, diesieben07 said:

All your synchronization packets are sent from server to client...?

Oh, so when the packet is sent to PacketDistributor.TRACKING_ENTITY, the packet is sent from server to players, thus, handled by clients and Minecraft.getInstance().world is safe to access.

Edited by lisilew
Link to comment
Share on other sites

1 hour ago, diesieben07 said:

Yes. The only packet type that would be client to server would be PacketDistributor.SERVER. All others send from the server to zero or more clients (controlled by specifying which server side players to send the packet to, it will arrive on those player's clients).

The class where onStartTracking is located is annotated with @Mod.EventBusSubscriber(modid = "examplemod").

Unless events are annotated with @OnlyIn(Dist.CLIENT), I listen to events in that class.

PacketDistributor.PLAYER.with requires a supplier of ServerPlayerEntity.

I casted event.getPlayer() to ServerPlayerEntity.

Is this safe or should I divide the current class into client and server events?

I am still quite confused about sides and in which sides events are fired.

Link to comment
Share on other sites

On 4/17/2020 at 7:37 PM, diesieben07 said:

In addition you have to use PacketDistributor.TRACKING_ENTITY whenever your data changes to propagate this change to all tracking players.

Sorry for bringing this old topic to life.

When I update the capability in getCapability().ifPresent(), do I have to update local capability and send packet or is just sending packet to PacketDistributor.TRACKING_ENTITY enough?

entity.getCapability(Capabilities.CAPABILITY).ifPresent(capability -> {
    if (!capability.flag) {
        capability.flag = true;
        PacketHandler.INSTANCE.send(PacketDistributor.TRACKING_ENTITY.with(() -> entity), new Packet(entity.getEntityId(), true));
    }
});

In other words, is the third line, capability.flag = true;, needed?

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Announcements



×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.