Skip to content

Commit 29ecee5

Browse files
committed
add ability to directly listen to response packets
1 parent 385e9e8 commit 29ecee5

File tree

10 files changed

+277
-51
lines changed

10 files changed

+277
-51
lines changed

api/src/main/java/net/labymod/serverapi/api/Protocol.java

Lines changed: 109 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,14 @@
2525
package net.labymod.serverapi.api;
2626

2727
import net.labymod.serverapi.api.packet.Direction;
28+
import net.labymod.serverapi.api.packet.IdentifiablePacket;
2829
import net.labymod.serverapi.api.packet.OnlyWriteConstructor;
2930
import net.labymod.serverapi.api.packet.Packet;
3031
import net.labymod.serverapi.api.packet.PacketHandler;
3132
import net.labymod.serverapi.api.payload.PayloadChannelIdentifier;
3233
import net.labymod.serverapi.api.payload.io.PayloadReader;
3334
import net.labymod.serverapi.api.payload.io.PayloadWriter;
35+
import org.jetbrains.annotations.ApiStatus;
3436
import org.jetbrains.annotations.NotNull;
3537
import org.jetbrains.annotations.Nullable;
3638
import sun.misc.Unsafe;
@@ -49,6 +51,7 @@ public class Protocol {
4951
private final Set<ProtocolPacket> packets;
5052
private final AbstractProtocolService protocolService;
5153
private final AbstractProtocolService.Side protocolSide;
54+
private final Set<AwaitingResponse> awaitingResponses;
5255

5356
/**
5457
* Creates a new protocol.
@@ -67,6 +70,7 @@ public Protocol(
6770
this.protocolService = protocolService;
6871
this.protocolSide = protocolService.getSide();
6972
this.packets = new HashSet<>();
73+
this.awaitingResponses = new HashSet<>();
7074
}
7175

7276
/**
@@ -233,6 +237,29 @@ public void handlePacket(@NotNull UUID sender, @NotNull Packet packet) {
233237
return packet;
234238
}
235239

240+
/**
241+
* Sends a packet to the recipient and waits for the provided response
242+
*
243+
* @param recipient the recipient of the packet
244+
* @param packet the packet to send
245+
* @param responseClass the class of the response packet
246+
* @param responseCallback the callback for the response packet. Return {@code true} to wait
247+
* for another packet or {@code false} remove the callback.
248+
* @param <R> the type of the response packet
249+
* @throws NullPointerException If the recipient, packet, response class or response callback is
250+
* null.
251+
*/
252+
public <R extends IdentifiablePacket> void sendPacket(
253+
@NotNull UUID recipient,
254+
@NotNull IdentifiablePacket packet,
255+
@NotNull Class<R> responseClass,
256+
@NotNull Predicate<R> responseCallback
257+
) {
258+
Objects.requireNonNull(responseClass, "Response class cannot be null");
259+
Objects.requireNonNull(responseCallback, "Response callback cannot be null");
260+
this.sendPacketInternal(recipient, packet, responseClass, responseCallback);
261+
}
262+
236263
/**
237264
* Sends a packet to the recipient.
238265
*
@@ -241,24 +268,19 @@ public void handlePacket(@NotNull UUID sender, @NotNull Packet packet) {
241268
* @throws NullPointerException If the recipient or packet is null.
242269
*/
243270
public void sendPacket(@NotNull UUID recipient, @NotNull Packet packet) {
244-
Objects.requireNonNull(recipient, "Recipient cannot be null");
245-
Objects.requireNonNull(packet, "Packet cannot be null");
246-
ProtocolPacket protocolPacket = this.getProtocolPacketByClass(packet.getClass());
247-
if (protocolPacket == null) {
248-
throw new IllegalArgumentException(
249-
"No packet with the class " + packet.getClass().getSimpleName() + " in protocol "
250-
+ this.identifier + "found");
251-
}
271+
this.sendPacketInternal(recipient, packet, null, null);
272+
}
252273

253-
if (this.protocolSide.isAcceptingDirection(protocolPacket.direction)) {
254-
return;
274+
/**
275+
* Clears all awaiting responses for the recipient.
276+
*
277+
* @param recipient the recipient to clear the awaiting responses for
278+
*/
279+
@ApiStatus.Internal
280+
public void clearAwaitingResponsesFor(UUID recipient) {
281+
synchronized (this.awaitingResponses) {
282+
this.awaitingResponses.removeIf(response -> response.recipient.equals(recipient));
255283
}
256-
257-
PayloadWriter writer = new PayloadWriter();
258-
writer.writeVarInt(protocolPacket.id);
259-
packet.write(writer);
260-
this.protocolService.send(this.identifier, recipient, writer);
261-
this.protocolService.afterPacketSent(this, packet, recipient);
262284
}
263285

264286
private ProtocolPacket getProtocolPacket(Predicate<ProtocolPacket> filter) {
@@ -280,13 +302,84 @@ private ProtocolPacket getProtocolPacketById(int id) {
280302
}
281303

282304
private void handlePacket(ProtocolPacket protocolPacket, UUID sender, Packet packet) {
305+
if (packet instanceof IdentifiablePacket identifiablePacket) {
306+
AwaitingResponse responsePacket = null;
307+
for (AwaitingResponse awaitingResponse : this.awaitingResponses) {
308+
if (awaitingResponse.recipient.equals(sender)
309+
&& awaitingResponse.responseClass.equals(protocolPacket.packet)
310+
&& identifiablePacket.getIdentifier() == awaitingResponse.identifier) {
311+
responsePacket = awaitingResponse;
312+
break;
313+
}
314+
}
315+
316+
if (responsePacket != null) {
317+
try {
318+
if (!responsePacket.responseCallback.test(identifiablePacket)) {
319+
this.awaitingResponses.remove(responsePacket);
320+
}
321+
} catch (Exception e) {
322+
e.printStackTrace();
323+
}
324+
}
325+
}
326+
283327
for (PacketHandler handler : protocolPacket.handlers) {
284328
handler.handle(sender, packet);
285329
}
286330

287331
this.protocolService.afterPacketHandled(this, packet, sender);
288332
}
289333

334+
private void sendPacketInternal(
335+
@NotNull UUID recipient,
336+
@NotNull Packet packet,
337+
@Nullable Class<? extends IdentifiablePacket> responseClass,
338+
@Nullable Predicate<? extends IdentifiablePacket> responseCallback
339+
) {
340+
Objects.requireNonNull(recipient, "Recipient cannot be null");
341+
Objects.requireNonNull(packet, "Packet cannot be null");
342+
ProtocolPacket protocolPacket = this.getProtocolPacketByClass(packet.getClass());
343+
if (protocolPacket == null) {
344+
throw new IllegalArgumentException(
345+
"No packet with the class " + packet.getClass().getSimpleName() + " in protocol "
346+
+ this.identifier + "found");
347+
}
348+
349+
if (this.protocolSide.isAcceptingDirection(protocolPacket.direction)) {
350+
return;
351+
}
352+
353+
PayloadWriter writer = new PayloadWriter();
354+
writer.writeVarInt(protocolPacket.id);
355+
packet.write(writer);
356+
this.protocolService.send(this.identifier, recipient, writer);
357+
if (responseClass != null && responseCallback != null
358+
&& packet instanceof IdentifiablePacket identifiablePacket) {
359+
synchronized (this.awaitingResponses) {
360+
this.awaitingResponses.add(new AwaitingResponse(
361+
recipient,
362+
identifiablePacket,
363+
identifiablePacket.getIdentifier(),
364+
responseClass,
365+
responseCallback
366+
));
367+
}
368+
}
369+
370+
this.protocolService.afterPacketSent(this, packet, recipient);
371+
}
372+
373+
private record AwaitingResponse(
374+
UUID recipient,
375+
IdentifiablePacket initialPacket,
376+
int identifier,
377+
Class responseClass,
378+
Predicate responseCallback
379+
) {
380+
381+
}
382+
290383
private static class ProtocolPacket {
291384

292385
private static final UnsafeProvider UNSAFE_PROVIDER = new UnsafeProvider();

core/src/main/java/net/labymod/serverapi/core/AbstractLabyModPlayer.java

Lines changed: 134 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,20 @@
2626

2727
import net.labymod.serverapi.api.Protocol;
2828
import net.labymod.serverapi.api.model.component.ServerAPIComponent;
29+
import net.labymod.serverapi.api.packet.IdentifiablePacket;
2930
import net.labymod.serverapi.api.packet.Packet;
3031
import net.labymod.serverapi.core.integration.LabyModIntegrationPlayer;
3132
import net.labymod.serverapi.core.integration.LabyModProtocolIntegration;
3233
import net.labymod.serverapi.core.model.display.Subtitle;
3334
import net.labymod.serverapi.core.model.moderation.Permission;
35+
import net.labymod.serverapi.core.model.supplement.InputPrompt;
36+
import net.labymod.serverapi.core.model.supplement.ServerSwitchPrompt;
3437
import net.labymod.serverapi.core.packet.clientbound.game.display.SubtitlePacket;
3538
import net.labymod.serverapi.core.packet.clientbound.game.moderation.PermissionPacket;
39+
import net.labymod.serverapi.core.packet.clientbound.game.supplement.InputPromptPacket;
40+
import net.labymod.serverapi.core.packet.clientbound.game.supplement.ServerSwitchPromptPacket;
41+
import net.labymod.serverapi.core.packet.serverbound.game.supplement.InputPromptResponsePacket;
42+
import net.labymod.serverapi.core.packet.serverbound.game.supplement.ServerSwitchPromptResponsePacket;
3643
import org.jetbrains.annotations.NotNull;
3744
import org.jetbrains.annotations.Nullable;
3845

@@ -41,6 +48,7 @@
4148
import java.util.Objects;
4249
import java.util.UUID;
4350
import java.util.function.Consumer;
51+
import java.util.function.Predicate;
4452

4553
public abstract class AbstractLabyModPlayer<P extends AbstractLabyModPlayer<?>> {
4654

@@ -77,6 +85,13 @@ protected AbstractLabyModPlayer(
7785
}
7886
}
7987

88+
/**
89+
* Gets the integration player of the provided class
90+
*
91+
* @param clazz The class of the integration player
92+
* @param <T> The type of the integration player
93+
* @return The integration player or null if not found
94+
*/
8095
public <T extends LabyModIntegrationPlayer> @Nullable T getIntegrationPlayer(Class<T> clazz) {
8196
for (LabyModIntegrationPlayer integrationPlayer : this.integrationPlayers) {
8297
if (clazz == integrationPlayer.getClass()) {
@@ -87,30 +102,53 @@ protected AbstractLabyModPlayer(
87102
return null;
88103
}
89104

105+
/**
106+
* @return The unique identifier of the player
107+
*/
90108
public @NotNull UUID getUniqueId() {
91109
return this.uniqueId;
92110
}
93111

112+
/**
113+
* @return The LabyMod version of the player
114+
*/
94115
public @NotNull String getLabyModVersion() {
95116
return this.labyModVersion;
96117
}
97118

119+
/**
120+
* @return the currently set subtitle
121+
*/
98122
public @Nullable Subtitle getSubtitle() {
99123
return this.subtitle;
100124
}
101125

126+
/**
127+
* Sets the subtitle of the player and sends it to all other online players
128+
*
129+
* @param component The component to set as subtitle
130+
*/
102131
public void setSubtitle(@NotNull ServerAPIComponent component) {
103132
this.setSubtitleInternal(Subtitle.create(this.uniqueId, component));
104133
}
105134

106-
public void resetSubtitle(Consumer<P> consumer) {
107-
this.setSubtitleInternal(null);
108-
}
109-
135+
/**
136+
* Sets the subtitle of the player and sends it to all other online players
137+
*
138+
* @param component The component to set as subtitle
139+
* @param size The size of the subtitle
140+
*/
110141
public void setSubtitle(@NotNull ServerAPIComponent component, double size) {
111142
this.setSubtitleInternal(Subtitle.create(this.uniqueId, component, size));
112143
}
113144

145+
/**
146+
* Resets the subtitle of the player and sends it to all other online players
147+
*/
148+
public void resetSubtitle() {
149+
this.setSubtitleInternal(null);
150+
}
151+
114152
public void sendPacket(@NotNull Packet packet) {
115153
Class<? extends Packet> packetClass = packet.getClass();
116154
for (Protocol protocol : this.protocolService.registry().getProtocols()) {
@@ -120,15 +158,86 @@ public void sendPacket(@NotNull Packet packet) {
120158
}
121159
}
122160

161+
/**
162+
* Sends the provided permissions to the player
163+
*
164+
* @param permissions The permissions to send
165+
*/
123166
public void sendPermissions(@NotNull Permission.StatedPermission... permissions) {
124-
this.sendPacket(new PermissionPacket(permissions));
167+
this.sendLabyModPacket(new PermissionPacket(permissions));
125168
}
126169

170+
/**
171+
* Sends the provided permissions to the player
172+
*
173+
* @param permissions The permissions to send
174+
*/
127175
public void sendPermissions(@NotNull List<Permission.StatedPermission> permissions) {
128-
this.sendPacket(new PermissionPacket(permissions));
176+
this.sendLabyModPacket(new PermissionPacket(permissions));
177+
}
178+
179+
/**
180+
* Opens the provided input prompt for the player
181+
*
182+
* @param inputPrompt The input prompt to open
183+
*/
184+
public void openInputPrompt(@NotNull InputPrompt inputPrompt) {
185+
this.sendLabyModPacket(new InputPromptPacket(inputPrompt));
186+
}
187+
188+
/**
189+
* Opens the provided input prompt for the player and handle the response via the provided
190+
* consumer
191+
*
192+
* @param inputPrompt The input prompt to open
193+
* @param responseConsumer The consumer for the response
194+
*/
195+
public void openInputPrompt(
196+
@NotNull InputPrompt inputPrompt,
197+
@NotNull Consumer<String> responseConsumer
198+
) {
199+
Objects.requireNonNull(responseConsumer, "Response consumer cannot be null");
200+
this.sendLabyModPacket(
201+
new InputPromptPacket(inputPrompt),
202+
InputPromptResponsePacket.class,
203+
response -> {
204+
responseConsumer.accept(response.getValue());
205+
return false;
206+
}
207+
);
129208
}
130209

131-
private void setSubtitleInternal(Subtitle subtitle) {
210+
/**
211+
* Opens the provided server switch prompt
212+
*
213+
* @param serverSwitchPrompt The server switch prompt to open
214+
*/
215+
public void openServerSwitchPrompt(@NotNull ServerSwitchPrompt serverSwitchPrompt) {
216+
this.sendLabyModPacket(new ServerSwitchPromptPacket(serverSwitchPrompt));
217+
}
218+
219+
/**
220+
* Opens the provided server switch prompt and handle the response via the provided consumer
221+
*
222+
* @param serverSwitchPrompt The server switch prompt to open
223+
* @param responseConsumer The consumer for the response
224+
*/
225+
public void openServerSwitchPrompt(
226+
@NotNull ServerSwitchPrompt serverSwitchPrompt,
227+
@NotNull Consumer<ServerSwitchPromptResponsePacket> responseConsumer
228+
) {
229+
Objects.requireNonNull(responseConsumer, "Response consumer cannot be null");
230+
this.sendLabyModPacket(
231+
new ServerSwitchPromptPacket(serverSwitchPrompt),
232+
ServerSwitchPromptResponsePacket.class,
233+
response -> {
234+
responseConsumer.accept(response);
235+
return false;
236+
}
237+
);
238+
}
239+
240+
protected void setSubtitleInternal(@Nullable Subtitle subtitle) {
132241
if (Objects.equals(this.subtitle, subtitle)) {
133242
return;
134243
}
@@ -160,6 +269,23 @@ private void setSubtitleInternal(Subtitle subtitle) {
160269
return;
161270
}
162271

163-
this.sendPacket(new SubtitlePacket(subtitles));
272+
this.sendLabyModPacket(new SubtitlePacket(subtitles));
273+
}
274+
275+
private void sendLabyModPacket(@NotNull Packet packet) {
276+
this.protocolService.labyModProtocol.sendPacket(this.uniqueId, packet);
277+
}
278+
279+
private <T extends IdentifiablePacket> void sendLabyModPacket(
280+
@NotNull IdentifiablePacket packet,
281+
@NotNull Class<T> responseClass,
282+
@NotNull Predicate<T> responseConsumer
283+
) {
284+
this.protocolService.labyModProtocol.sendPacket(
285+
this.uniqueId,
286+
packet,
287+
responseClass,
288+
responseConsumer
289+
);
164290
}
165291
}

0 commit comments

Comments
 (0)