diff --git a/src/main/java/com/atsuishio/superbwarfare/annotation/ServerOnly.java b/src/main/java/com/atsuishio/superbwarfare/annotation/ServerOnly.java new file mode 100644 index 000000000..989d3d7bf --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/annotation/ServerOnly.java @@ -0,0 +1,11 @@ +package com.atsuishio.superbwarfare.annotation; + +import java.lang.annotation.*; + +/** + * 将指定字段标记为仅服务端启用,不会同步给客户端 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD}) +public @interface ServerOnly { +} diff --git a/src/main/java/com/atsuishio/superbwarfare/data/vehicle/DefaultVehicleData.java b/src/main/java/com/atsuishio/superbwarfare/data/vehicle/DefaultVehicleData.java index 7d80d4f62..04767c0bd 100644 --- a/src/main/java/com/atsuishio/superbwarfare/data/vehicle/DefaultVehicleData.java +++ b/src/main/java/com/atsuishio/superbwarfare/data/vehicle/DefaultVehicleData.java @@ -1,5 +1,6 @@ package com.atsuishio.superbwarfare.data.vehicle; +import com.atsuishio.superbwarfare.annotation.ServerOnly; import com.atsuishio.superbwarfare.config.server.VehicleConfig; import com.atsuishio.superbwarfare.entity.vehicle.damage.DamageModify; import com.google.gson.annotations.SerializedName; @@ -13,9 +14,11 @@ public class DefaultVehicleData { @SerializedName("MaxHealth") public float maxHealth = 50; + @ServerOnly @SerializedName("RepairCooldown") public int repairCooldown = VehicleConfig.REPAIR_COOLDOWN.get(); + @ServerOnly @SerializedName("RepairAmount") public float repairAmount = VehicleConfig.REPAIR_AMOUNT.get().floatValue(); @@ -31,9 +34,11 @@ public class DefaultVehicleData { @SerializedName("ApplyDefaultDamageModifiers") public boolean applyDefaultDamageModifiers = true; + @ServerOnly @SerializedName("DamageModifiers") public List damageModifiers = List.of(); + @ServerOnly @SerializedName("Mass") public float mass = 1; } diff --git a/src/main/java/com/atsuishio/superbwarfare/data/vehicle/VehicleDataTool.java b/src/main/java/com/atsuishio/superbwarfare/data/vehicle/VehicleDataTool.java index c7cd04716..b76ed7412 100644 --- a/src/main/java/com/atsuishio/superbwarfare/data/vehicle/VehicleDataTool.java +++ b/src/main/java/com/atsuishio/superbwarfare/data/vehicle/VehicleDataTool.java @@ -1,12 +1,17 @@ package com.atsuishio.superbwarfare.data.vehicle; import com.atsuishio.superbwarfare.Mod; +import com.atsuishio.superbwarfare.network.message.receive.GunsDataMessage; +import com.atsuishio.superbwarfare.network.message.receive.VehiclesDataMessage; import com.google.gson.Gson; +import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.packs.resources.ResourceManager; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.neoforge.event.OnDatapackSyncEvent; +import net.neoforged.neoforge.event.entity.player.PlayerEvent; import net.neoforged.neoforge.event.server.ServerStartedEvent; +import net.neoforged.neoforge.network.PacketDistributor; import java.io.InputStreamReader; import java.util.HashMap; @@ -37,6 +42,13 @@ public class VehicleDataTool { } } + @SubscribeEvent + public static void onPlayerLogin(PlayerEvent.PlayerLoggedInEvent event) { + if (event.getEntity() instanceof ServerPlayer player) { + PacketDistributor.sendToPlayer(player, VehiclesDataMessage.create()); + } + } + @SubscribeEvent public static void serverStarted(ServerStartedEvent event) { initJsonData(event.getServer().getResourceManager()); @@ -45,5 +57,7 @@ public class VehicleDataTool { @SubscribeEvent public static void onDataPackSync(OnDatapackSyncEvent event) { initJsonData(event.getPlayerList().getServer().getResourceManager()); + + event.getRelevantPlayers().forEach(player -> PacketDistributor.sendToPlayer(player, VehiclesDataMessage.create())); } } \ No newline at end of file diff --git a/src/main/java/com/atsuishio/superbwarfare/network/NetworkRegistry.java b/src/main/java/com/atsuishio/superbwarfare/network/NetworkRegistry.java index 714b2e2fd..0f4680455 100644 --- a/src/main/java/com/atsuishio/superbwarfare/network/NetworkRegistry.java +++ b/src/main/java/com/atsuishio/superbwarfare/network/NetworkRegistry.java @@ -24,6 +24,7 @@ public class NetworkRegistry { registrar.playToClient(SimulationDistanceMessage.TYPE, SimulationDistanceMessage.STREAM_CODEC, SimulationDistanceMessage::handler); registrar.playToClient(ClientTacticalSprintSyncMessage.TYPE, ClientTacticalSprintSyncMessage.STREAM_CODEC, (msg, ctx) -> ClientTacticalSprintSyncMessage.handler(msg)); registrar.playToClient(DogTagEditorMessage.TYPE, DogTagEditorMessage.STREAM_CODEC, (msg, ctx) -> DogTagEditorMessage.handler(msg)); + registrar.playToClient(VehiclesDataMessage.TYPE, VehiclesDataMessage.STREAM_CODEC, (msg, ctx) -> VehiclesDataMessage.handler(msg)); registrar.playToServer(LaserShootMessage.TYPE, LaserShootMessage.STREAM_CODEC, LaserShootMessage::handler); registrar.playToServer(ShootMessage.TYPE, ShootMessage.STREAM_CODEC, ShootMessage::handler); diff --git a/src/main/java/com/atsuishio/superbwarfare/network/message/receive/VehiclesDataMessage.java b/src/main/java/com/atsuishio/superbwarfare/network/message/receive/VehiclesDataMessage.java new file mode 100644 index 000000000..8598713f2 --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/network/message/receive/VehiclesDataMessage.java @@ -0,0 +1,53 @@ +package com.atsuishio.superbwarfare.network.message.receive; + +import com.atsuishio.superbwarfare.Mod; +import com.atsuishio.superbwarfare.data.vehicle.DefaultVehicleData; +import com.atsuishio.superbwarfare.data.vehicle.VehicleDataTool; +import com.atsuishio.superbwarfare.tools.BufferSerializer; +import com.google.gson.Gson; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +public record VehiclesDataMessage(List data) implements CustomPacketPayload { + public static final Type TYPE = new Type<>(Mod.loc("set_vehicles_data")); + + + public static final StreamCodec STREAM_CODEC = StreamCodec.ofMember( + (obj, buf) -> { + buf.writeVarInt(obj.data.size()); + for (var data : obj.data) { + buf.writeBytes(BufferSerializer.serialize(data).getSource()); + } + }, + (buf) -> { + var size = buf.readVarInt(); + var list = new ArrayList(); + for (var i = 0; i < size; i++) { + list.add(BufferSerializer.deserialize(buf, new DefaultVehicleData())); + } + return new VehiclesDataMessage(list); + } + ); + + public static VehiclesDataMessage create() { + return new VehiclesDataMessage(VehicleDataTool.vehicleData.values().stream().toList()); + } + + public static void handler(final VehiclesDataMessage message) { + VehicleDataTool.vehicleData.clear(); + + for (var entry : message.data) { + VehicleDataTool.vehicleData.put(entry.id, entry); + } + } + + @Override + public @NotNull Type type() { + return TYPE; + } +} diff --git a/src/main/java/com/atsuishio/superbwarfare/tools/BufferSerializer.java b/src/main/java/com/atsuishio/superbwarfare/tools/BufferSerializer.java new file mode 100644 index 000000000..61c4eb105 --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/tools/BufferSerializer.java @@ -0,0 +1,139 @@ +package com.atsuishio.superbwarfare.tools; + +import com.atsuishio.superbwarfare.Mod; +import com.atsuishio.superbwarfare.annotation.ServerOnly; +import io.netty.buffer.Unpooled; +import net.minecraft.network.FriendlyByteBuf; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + + +// 屎 +// TODO 优化这一坨(至少得支持数组和嵌套对象序列化) +public class BufferSerializer { + public static List sortedFields(Class clazz) { + return Arrays.stream(clazz.getDeclaredFields()) + .filter(f -> !f.isAnnotationPresent(ServerOnly.class) && !f.getType().isAssignableFrom(Annotation.class)) + .sorted(Comparator.comparing(Field::getName)) + .toList(); + } + + public static List sortedFields(Object object) { + return sortedFields(object.getClass()); + } + + public static List fieldValuesList(Object object) { + var fields = new ArrayList<>(); + + for (var field : sortedFields(object)) { + try { + field.setAccessible(true); + Object value = field.get(object); + fields.add(value); + } catch (IllegalAccessException e) { + Mod.LOGGER.error("BufferSerializer read error: {}", e.getMessage()); + } + } + return fields; + } + + public static FriendlyByteBuf serialize(Object object) { + var buffer = new FriendlyByteBuf(Unpooled.buffer()); + var fields = fieldValuesList(object); + + fields.forEach(value -> { + switch (value) { + case Byte b -> buffer.writeByte(b); + case Integer i -> buffer.writeVarInt(i); + case Long l -> buffer.writeLong(l); + case Float f -> buffer.writeFloat(f); + case Double d -> buffer.writeDouble(d); + case String s -> buffer.writeUtf(s); + case Boolean b -> buffer.writeBoolean(b); +// case List l -> { +// buffer.writeVarInt(l.size()); +// l.forEach(o -> serialize(o, buffer)); +// } + + default -> serialize(value); + } + }); + + return buffer; + } + + public static T deserialize(FriendlyByteBuf buffer, T object) { + sortedFields(object).forEach(field -> { + if (field.getType().isAssignableFrom(Byte.class) || field.getType().getName().equals("byte")) { + setField(object, field, buffer.readByte()); + } else if (field.getType().isAssignableFrom(Integer.class) || field.getType().getName().equals("int")) { + setField(object, field, buffer.readVarInt()); + } else if (field.getType().isAssignableFrom(Long.class) || field.getType().getName().equals("long")) { + setField(object, field, buffer.readLong()); + } else if (field.getType().isAssignableFrom(Float.class) || field.getType().getName().equals("float")) { + setField(object, field, buffer.readFloat()); + } else if (field.getType().isAssignableFrom(Double.class) || field.getType().getName().equals("double")) { + setField(object, field, buffer.readDouble()); + } else if (field.getType().isAssignableFrom(String.class)) { + setField(object, field, buffer.readUtf()); + } else if (field.getType().isAssignableFrom(Boolean.class) || field.getType().getName().equals("boolean")) { + setField(object, field, buffer.readBoolean()); +// } else if (field.getType().isAssignableFrom(List.class)) { +// var size = buffer.readVarInt(); +// var list = new ArrayList<>(); +// for (int i = 0; i < size; i++) { +// list.add(readFieldByClass(object, field.getGenericType().getClass(), buffer)); +// } +// setField(object, field, list); + } else { + throw new IllegalArgumentException("Non-primary Object not supported"); +// setField(object, field, deserialize(buffer, getField(object, field))); + } + }); + + return object; + } + + public static Object readFieldByClass(Object object, Class clazz, FriendlyByteBuf buffer) { + if (clazz.isAssignableFrom(Byte.class) || clazz.getName().equals("byte")) { + return buffer.readByte(); + } else if (clazz.isAssignableFrom(Integer.class) || clazz.getName().equals("int")) { + return buffer.readVarInt(); + } else if (clazz.isAssignableFrom(Long.class) || clazz.getName().equals("long")) { + return buffer.readLong(); + } else if (clazz.isAssignableFrom(Float.class) || clazz.getName().equals("float")) { + return buffer.readFloat(); + } else if (clazz.isAssignableFrom(Double.class) || clazz.getName().equals("double")) { + return buffer.readDouble(); + } else if (clazz.isAssignableFrom(String.class)) { + return buffer.readUtf(); + } else if (clazz.isAssignableFrom(Boolean.class) || clazz.getName().equals("boolean")) { + return buffer.readByte(); +// } else if (clazz.isAssignableFrom(List.class)) { +// var size = buffer.readVarInt(); +// var list = new ArrayList<>(); +// for (int i = 0; i < size; i++) { +// clazz.getDeclaredConstructors()[0].newInstance() +// list.add(deserialize(field)); +// } +// setField(object, field, list); + } else { + throw new IllegalArgumentException("Non-primary Object not supported"); +// deserialize(buffer, getField(object, field)); + } + } + + public static void setField(Object object, Field field, Object value) { + try { + field.setAccessible(true); + field.set(object, value); + } catch (IllegalAccessException e) { + Mod.LOGGER.error("BufferSerializer write error: {}", e.getMessage()); + } + } +}