diff --git a/gradle.properties b/gradle.properties index d7cd7bd..85f9805 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,9 +17,9 @@ parchment_mappings_version=2024.11.17 mod_id=pmweatherapi mod_name=PMWeatherAPI mod_license=GNU GPL 3.0 -mod_version=0.15.3.2 +mod_version=0.15.3.3-rc1 mod_group_id=net.nullved -mod_authors=NullVed +mod_authors=nullved mod_description=An API for interfacing with ProtoManly's Weather Mod # Dependencies diff --git a/src/main/java/net/nullved/pmweatherapi/PMWeatherAPI.java b/src/main/java/net/nullved/pmweatherapi/PMWeatherAPI.java index 290e612..acd7c1d 100644 --- a/src/main/java/net/nullved/pmweatherapi/PMWeatherAPI.java +++ b/src/main/java/net/nullved/pmweatherapi/PMWeatherAPI.java @@ -12,13 +12,28 @@ import net.neoforged.neoforge.client.gui.ConfigurationScreen; import net.neoforged.neoforge.client.gui.IConfigScreenFactory; import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; +import net.nullved.pmweatherapi.client.data.PMWClientStorages; +import net.nullved.pmweatherapi.client.metar.MetarClientStorage; +import net.nullved.pmweatherapi.client.radar.RadarClientStorage; +import net.nullved.pmweatherapi.client.radar.WSRClientStorage; import net.nullved.pmweatherapi.client.render.IDOverlay; import net.nullved.pmweatherapi.client.render.RadarOverlays; import net.nullved.pmweatherapi.config.PMWClientConfig; +import net.nullved.pmweatherapi.data.PMWStorages; +import net.nullved.pmweatherapi.example.ExampleOverlay; +import net.nullved.pmweatherapi.metar.MetarServerStorage; +import net.nullved.pmweatherapi.metar.MetarStorage; +import net.nullved.pmweatherapi.metar.MetarStorageData; import net.nullved.pmweatherapi.network.PMWNetworking; import net.nullved.pmweatherapi.radar.RadarMode; +import net.nullved.pmweatherapi.radar.storage.*; +import net.nullved.pmweatherapi.storage.data.BlockPosData; +import net.nullved.pmweatherapi.storage.data.StorageDataManager; +import org.checkerframework.checker.units.qual.C; import org.slf4j.Logger; +import java.awt.*; + @Mod(PMWeatherAPI.MODID) public class PMWeatherAPI { public static final String MODID = "pmweatherapi"; @@ -40,9 +55,14 @@ public PMWeatherAPI(IEventBus modEventBus, ModContainer modContainer) { } private void commonSetup(FMLCommonSetupEvent event) { -// if (!ModList.get().isLoaded("pmweather")) { -// throw new RuntimeException("ProtoManly's Weather not detected!"); -// } + StorageDataManager.register(BlockPosData.ID, BlockPosData::deserializeFromNBT); + StorageDataManager.register(RadarStorageData.ID, RadarStorageData::deserializeFromNBT); + StorageDataManager.register(MetarStorageData.ID, MetarStorageData::deserializeFromNBT); + StorageDataManager.register(WSRStorageData.ID, WSRStorageData::deserializeFromNBT); + + PMWStorages.registerStorage(RadarStorage.ID, RadarServerStorage.class, RadarServerStorage::new); + PMWStorages.registerStorage(MetarStorage.ID, MetarServerStorage.class, MetarServerStorage::new); + PMWStorages.registerStorage(WSRStorage.ID, WSRServerStorage.class, WSRServerStorage::new); } private void registerPayloads(RegisterPayloadHandlersEvent event) { @@ -50,10 +70,15 @@ private void registerPayloads(RegisterPayloadHandlersEvent event) { } private void clientSetup(FMLClientSetupEvent event) { + //RadarMode.removeBaseRendering(true); + + PMWClientStorages.registerStorage(RadarStorage.ID, RadarClientStorage.class, RadarClientStorage::new); + PMWClientStorages.registerStorage(MetarStorage.ID, MetarClientStorage.class, MetarClientStorage::new); + PMWClientStorages.registerStorage(WSRStorage.ID, WSRClientStorage.class, WSRClientStorage::new); + RadarOverlays.registerOverlay(() -> IDOverlay.INSTANCE); //RadarOverlays.registerOverlay(() -> ExampleOverlay.INSTANCE); } - public static ResourceLocation rl(String path) { return ResourceLocation.fromNamespaceAndPath(MODID, path); } diff --git a/src/main/java/net/nullved/pmweatherapi/client/data/IClientStorage.java b/src/main/java/net/nullved/pmweatherapi/client/data/IClientStorage.java new file mode 100644 index 0000000..0adad50 --- /dev/null +++ b/src/main/java/net/nullved/pmweatherapi/client/data/IClientStorage.java @@ -0,0 +1,107 @@ +package net.nullved.pmweatherapi.client.data; + +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.*; +import net.nullved.pmweatherapi.PMWeatherAPI; +import net.nullved.pmweatherapi.client.radar.RadarClientStorage; +import net.nullved.pmweatherapi.network.S2CStoragePacket; +import net.nullved.pmweatherapi.storage.IStorage; +import net.nullved.pmweatherapi.storage.data.IStorageData; +import net.nullved.pmweatherapi.storage.data.StorageData; + +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * The interface defining the client-side implementation of a Storage such as {@link RadarClientStorage} + *

+ * Every time the client changes dimension, a new {@link IClientStorage} is created for each storage. + * @since 0.15.3.3 + */ +public interface IClientStorage extends IStorage { + ClientLevel getLevel(); + + /** + * Syncs all data from a {@link S2CStoragePacket} with operation {@code overwrite} into this storage's memory + * @param tag The {@link CompoundTag} of the data + * @since 0.15.3.3 + */ + default void syncAll(CompoundTag tag) { + clean(); + syncAdd(tag); + } + + /** + * Syncs data from a {@link S2CStoragePacket} with operation {@code add} into this storage's memory + * @param tag The {@link CompoundTag} of the data + * @since 0.15.3.3 + */ + default void syncAdd(CompoundTag tag) { + int version = tag.getInt("version"); + if (tag.contains("list") && tag.getBoolean("list")) { + // list format + ListTag list = tag.getList("data", ListTag.TAG_COMPOUND); + add(list.stream().map((t -> { + try { + return (D) StorageData.deserializeFromNBT((CompoundTag) t, version); + } catch (ClassCastException e) { + PMWeatherAPI.LOGGER.info("Invalid data entry in NBT: {}", e.getMessage()); + return null; + } + })).filter(Objects::nonNull).collect(Collectors.toSet())); + } else { + try { + add((D) StorageData.deserializeFromNBT(tag.getCompound("data"), version)); + } catch (ClassCastException e) { + PMWeatherAPI.LOGGER.info("Invalid data entry in NBT: {}", e.getMessage()); + } + } + } + + /** + * Syncs data from a {@link S2CStoragePacket} with operation {@code remove} into this storage's memory + * @param tag The {@link CompoundTag} of the data + * @since 0.15.3.3 + */ + default void syncRemove(CompoundTag tag) { + int version = tag.getInt("version"); + if (!tag.contains("format")) { + // data + if (tag.contains("list") && tag.getBoolean("list")) { + // list format + ListTag list = tag.getList("data", ListTag.TAG_COMPOUND); + removeByData(list.stream().map((t -> { + try { + return (D) StorageData.deserializeFromNBT((CompoundTag) t, version); + } catch (ClassCastException e) { + PMWeatherAPI.LOGGER.info("Invalid data entry in NBT: {}", e.getMessage()); + return null; + } + })).filter(Objects::nonNull).collect(Collectors.toSet())); + } else { + // not list format + try { + remove((D) StorageData.deserializeFromNBT(tag.getCompound("data"), version)); + } catch (ClassCastException e) { + PMWeatherAPI.LOGGER.info("Invalid data entry in NBT: {}", e.getMessage()); + } + } + } else if (tag.getString("format").equals("blockpos")) { + if (tag.contains("list") && tag.getBoolean("list")) { + // list format + ListTag list = tag.getList("data", ListTag.TAG_INT_ARRAY); + removeByPos(list.stream().map(t -> { + if (t instanceof IntArrayTag iat) { + return new BlockPos(iat.get(0).getAsInt(), iat.get(1).getAsInt(), iat.get(2).getAsInt()); + } else return null; + }).collect(Collectors.toSet())); + } else { + // not list format + remove(NbtUtils.readBlockPos(tag, "data").orElseThrow()); + } + } else { + PMWeatherAPI.LOGGER.info("Invalid data format for packet: '{}'!", tag.getString("format")); + } + } +} diff --git a/src/main/java/net/nullved/pmweatherapi/client/data/PMWClientStorages.java b/src/main/java/net/nullved/pmweatherapi/client/data/PMWClientStorages.java index 28f3378..f2ad8dd 100644 --- a/src/main/java/net/nullved/pmweatherapi/client/data/PMWClientStorages.java +++ b/src/main/java/net/nullved/pmweatherapi/client/data/PMWClientStorages.java @@ -1,17 +1,32 @@ package net.nullved.pmweatherapi.client.data; import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.core.BlockPos; -import net.minecraft.world.level.Level; +import net.minecraft.resources.ResourceLocation; import net.neoforged.api.distmarker.Dist; import net.neoforged.api.distmarker.OnlyIn; import net.nullved.pmweatherapi.PMWeatherAPI; +import net.nullved.pmweatherapi.client.metar.MetarClientStorage; import net.nullved.pmweatherapi.client.radar.RadarClientStorage; +import net.nullved.pmweatherapi.client.radar.WSRClientStorage; +import net.nullved.pmweatherapi.client.storage.ClientStorageInstance; +import net.nullved.pmweatherapi.metar.MetarStorage; +import net.nullved.pmweatherapi.metar.MetarStorageData; import net.nullved.pmweatherapi.radar.RadarMode; +import net.nullved.pmweatherapi.radar.storage.RadarStorage; +import net.nullved.pmweatherapi.radar.storage.WSRStorage; +import net.nullved.pmweatherapi.radar.storage.WSRStorageData; +import net.nullved.pmweatherapi.storage.data.IStorageData; +import net.nullved.pmweatherapi.storage.data.StorageData; +import net.nullved.pmweatherapi.radar.storage.RadarStorageData; import java.awt.*; +import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.Optional; +import java.util.function.Function; /** * A class holding the specific storage instances for the client @@ -25,44 +40,122 @@ public class PMWClientStorages { */ public static Map>> RADAR_MODE_COLORS = new HashMap<>(); - private static Level lastLevel; - private static RadarClientStorage radar; + public static final Map> STORAGE_INSTANCES = new HashMap<>(); + + private static ClientLevel lastLevel; /** - * Resets this client's internal radar storage + * Gets the {@link ClientStorageInstance} of the {@link RadarClientStorage} + * @return The {@link ClientStorageInstance} * @since 0.14.15.3 */ - public static void resetRadars() { - radar = null; + public static ClientStorageInstance radars() { + return get(RadarStorage.ID, RadarClientStorage.class).orElseThrow(); } /** - * Gets the radar storage for the dimension this client is currently in - * @return A {@link RadarClientStorage} - * @since 0.14.15.3 + * Gets the {@link ClientStorageInstance} of the {@link MetarClientStorage} + * @return The {@link ClientStorageInstance} + * @since 0.15.3.3 */ - public static RadarClientStorage getRadars() { - try { - Level level = Minecraft.getInstance().level; - if (radar == null || level != lastLevel) { - init(level); - } - } catch (Exception e) { - PMWeatherAPI.LOGGER.error(e.getMessage(), e); - } + public static ClientStorageInstance metars() { + return get(MetarStorage.ID, MetarClientStorage.class).orElseThrow(); + } - return radar; + /** + * Gets the {@link ClientStorageInstance} of the {@link WSRClientStorage} + * @return The {@link ClientStorageInstance} + * @since 0.15.3.3 + */ + public static ClientStorageInstance wsrs() { + return get(WSRStorage.ID, WSRClientStorage.class).orElseThrow(); } /** - * Initializes this client's storages to be the storages for the given level - * @param level The {@link Level} to initialize storages for - * @since 0.14.15.3 + * Get a {@link ClientStorageInstance} for a given {@link ResourceLocation} ID + * @param location The ID of the storage + * @return A {@link ClientStorageInstance} + * @since 0.15.3.3 */ - public static void init(Level level) { - lastLevel = level; - if (level != null) { - radar = new RadarClientStorage(level.dimension()); + public static ClientStorageInstance get(ResourceLocation location) { + if (!STORAGE_INSTANCES.containsKey(location)) { + PMWeatherAPI.LOGGER.error("No storage instance found for location {}", location); + } + + ClientLevel curLevel = Minecraft.getInstance().level; + if (curLevel != null && curLevel != lastLevel) { + loadDimension(curLevel); } + + ClientStorageInstance csi = STORAGE_INSTANCES.get(location); + if (csi.get() == null) { + csi.load(curLevel); + } + + return csi; + } + + /** + * Overwrite a {@link ClientStorageInstance} + * @param location The ID {@link ResourceLocation} + * @param instance The new {@link ClientStorageInstance} + * @since 0.15.3.3 + */ + public static void set(ResourceLocation location, ClientStorageInstance instance) { + STORAGE_INSTANCES.put(location, instance); + } + + /** + * Casts the {@link ClientStorageInstance} to the specified {@link IClientStorage} class after retrieval + * @param location The ID {@link ResourceLocation} + * @param clazz The {@link Class} of an {@link IClientStorage} to cast to + * @return The casted {@link ClientStorageInstance} + * @param The {@link IStorageData} of the {@link IClientStorage} + * @param The {@link IClientStorage} + * @since 0.15.3.3 + */ + public static > Optional> get(ResourceLocation location, Class clazz) { + return get(location).cast(clazz); + } + + /** + * Gets all {@link ClientStorageInstance}s + * @return A {@link Collection} of all {@link ClientStorageInstance}s + * @since 0.15.3.3 + */ + public static Collection> getAll() { + return STORAGE_INSTANCES.values(); + } + + /** + * Resets all data for all {@link ClientStorageInstance}s + * @since 0.15.3.3 + */ + public static void resetAll() { + getAll().forEach(ClientStorageInstance::clear); + } + + /** + * Loads a new {@link ClientLevel} for all {@link ClientStorageInstance}s + * @param clientLevel The new {@link ClientLevel} to load + * @since 0.15.3.3 + */ + public static void loadDimension(ClientLevel clientLevel) { + lastLevel = clientLevel; + STORAGE_INSTANCES.forEach((location, instance) -> instance.load(clientLevel)); + } + + /** + * Register a new {@link IClientStorage} + * @param id The {@link ResourceLocation} to save this {@link IClientStorage} as + * @param clazz The {@link Class} of the {@link IClientStorage} + * @param creator A function creating another {@link IClientStorage} for the given {@link ClientLevel} + * @param The {@link IStorageData} of the {@link IClientStorage} + * @param The {@link IClientStorage} + * @since 0.15.3.3 + */ + public static > void registerStorage(ResourceLocation id, Class clazz, Function creator) { + ClientStorageInstance instance = new ClientStorageInstance<>(id, clazz, creator); + STORAGE_INSTANCES.put(id, instance); } } diff --git a/src/main/java/net/nullved/pmweatherapi/client/event/PMWClientEvents.java b/src/main/java/net/nullved/pmweatherapi/client/event/PMWClientEvents.java new file mode 100644 index 0000000..20bb949 --- /dev/null +++ b/src/main/java/net/nullved/pmweatherapi/client/event/PMWClientEvents.java @@ -0,0 +1,30 @@ +package net.nullved.pmweatherapi.client.event; + +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.world.level.LevelAccessor; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.event.level.LevelEvent; +import net.nullved.pmweatherapi.PMWeatherAPI; +import net.nullved.pmweatherapi.client.data.PMWClientStorages; + +@EventBusSubscriber(modid = PMWeatherAPI.MODID, value = Dist.CLIENT) +public class PMWClientEvents { + @SubscribeEvent + public static void onLevelLoadEvent(LevelEvent.Load event) { + LevelAccessor level = event.getLevel(); + if (level.isClientSide() && level instanceof ClientLevel clevel) { + PMWeatherAPI.LOGGER.info("Loaded client storages for dimension {}", clevel.dimension().location()); + PMWClientStorages.loadDimension(clevel); + } + } + + @SubscribeEvent + public static void onLevelUnloadEvent(LevelEvent.Unload event) { + LevelAccessor level = event.getLevel(); + if (level.isClientSide() && level instanceof ClientLevel clevel) { + PMWeatherAPI.LOGGER.info("Unloaded client storages for dimension {}", clevel.dimension().location()); + } + } +} diff --git a/src/main/java/net/nullved/pmweatherapi/client/metar/MetarClientStorage.java b/src/main/java/net/nullved/pmweatherapi/client/metar/MetarClientStorage.java new file mode 100644 index 0000000..c856022 --- /dev/null +++ b/src/main/java/net/nullved/pmweatherapi/client/metar/MetarClientStorage.java @@ -0,0 +1,41 @@ +package net.nullved.pmweatherapi.client.metar; + +import dev.protomanly.pmweather.block.MetarBlock; +import dev.protomanly.pmweather.block.RadarBlock; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.nullved.pmweatherapi.client.data.IClientStorage; +import net.nullved.pmweatherapi.client.data.PMWClientStorages; +import net.nullved.pmweatherapi.metar.MetarStorage; +import net.nullved.pmweatherapi.metar.MetarStorageData; +import net.nullved.pmweatherapi.radar.storage.RadarStorage; +import net.nullved.pmweatherapi.radar.storage.RadarStorageData; + +/** + * A {@link IClientStorage} implementation for {@link MetarBlock}s + *

+ * You should not create a {@link MetarClientStorage}, instead, use {@link PMWClientStorages#metars()} + * @since 0.15.3.3 + */ +public class MetarClientStorage extends MetarStorage implements IClientStorage { + /** + * DO NOT CALL THIS CONSTRUCTOR!!! + *
+ * Get a radar storage from {@link PMWClientStorages#metars()} + * @param clientLevel The {@link ClientLevel} to create this storage for + * @since 0.15.3.3 + */ + public MetarClientStorage(ClientLevel clientLevel) { + super(clientLevel.dimension()); + } + + /** + * Gets the level associated with this {@link MetarClientStorage} + * @return The {@link Minecraft} {@link ClientLevel} + * @since 0.15.3.3 + */ + @Override + public ClientLevel getLevel() { + return Minecraft.getInstance().level; + } +} \ No newline at end of file diff --git a/src/main/java/net/nullved/pmweatherapi/client/radar/RadarClientStorage.java b/src/main/java/net/nullved/pmweatherapi/client/radar/RadarClientStorage.java index b06f7d8..9136a23 100644 --- a/src/main/java/net/nullved/pmweatherapi/client/radar/RadarClientStorage.java +++ b/src/main/java/net/nullved/pmweatherapi/client/radar/RadarClientStorage.java @@ -1,84 +1,38 @@ package net.nullved.pmweatherapi.client.radar; +import dev.protomanly.pmweather.block.RadarBlock; import net.minecraft.client.Minecraft; -import net.minecraft.core.BlockPos; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.IntArrayTag; -import net.minecraft.nbt.ListTag; -import net.minecraft.nbt.NbtUtils; -import net.minecraft.resources.ResourceKey; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.level.Level; +import net.minecraft.client.multiplayer.ClientLevel; +import net.nullved.pmweatherapi.client.data.IClientStorage; import net.nullved.pmweatherapi.client.data.PMWClientStorages; -import net.nullved.pmweatherapi.network.S2CRadarsPacket; -import net.nullved.pmweatherapi.radar.RadarStorage; - -import java.util.stream.Collectors; +import net.nullved.pmweatherapi.radar.storage.RadarStorage; +import net.nullved.pmweatherapi.radar.storage.RadarStorageData; /** - * A Radar Storage for the client side. - * Every time the client changes dimension, a new {@code RadarClientStorage} will be created - *
- * You should not create a {@code RadarClientStorage} directly, instead, use {@link PMWClientStorages#getRadars()} - * @since 0.14.15.3 + * A {@link IClientStorage} implementation for {@link RadarBlock}s + *

+ * You should not create a {@link RadarClientStorage}, instead, use {@link PMWClientStorages#radars()} + * @since 0.15.3.3 */ -public class RadarClientStorage extends RadarStorage { +public class RadarClientStorage extends RadarStorage implements IClientStorage { /** * DO NOT CALL THIS CONSTRUCTOR!!! *
- * Get a radar storage from {@link PMWClientStorages#getRadars()} - * @param dimension The dimension to create this storage for - * @since 0.14.15.3 + * Get a radar storage from {@link PMWClientStorages#radars()} + * @param clientLevel The {@link ClientLevel} to create this storage for + * @since 0.15.3.3 */ - public RadarClientStorage(ResourceKey dimension) { - super(dimension); + public RadarClientStorage(ClientLevel clientLevel) { + super(clientLevel.dimension()); } /** - * Gets the level associated with this {@code RadarClientStorage} - * @return The {@link Minecraft} instance {@link Level} - * @since 0.14.15.3 + * Gets the level associated with this {@link RadarClientStorage} + * @return The {@link Minecraft} {@link ClientLevel} + * @since 0.15.3.3 */ @Override - public Level getLevel() { + public ClientLevel getLevel() { return Minecraft.getInstance().level; } - - /** - * Syncs data from a {@link S2CRadarsPacket} with operation {@code add} into this storage's memory - * @since 0.14.15.3 - */ - public void syncAdd(CompoundTag tag) { - if (tag.contains("list") && tag.getBoolean("list")) { - // list format - ListTag list = tag.getList("data", ListTag.TAG_INT_ARRAY); - addRadars(list.stream().map(t -> { - if (t instanceof IntArrayTag iat) { - return new BlockPos(iat.get(0).getAsInt(), iat.get(1).getAsInt(), iat.get(2).getAsInt()); - } else return null; - }).collect(Collectors.toSet())); - } else { - // not list format - addRadar(NbtUtils.readBlockPos(tag, "data").orElseThrow()); - } - } - - /** - * Syncs data from a {@link S2CRadarsPacket} with operation {@code remove} into this storage's memory - * @since 0.14.15.3 - */ - public void syncRemove(CompoundTag tag) { - if (tag.contains("list") && tag.getBoolean("list")) { - // list format - ListTag list = tag.getList("data", ListTag.TAG_INT_ARRAY); - removeRadars(list.stream().map(t -> { - if (t instanceof IntArrayTag iat) { - return new BlockPos(iat.get(0).getAsInt(), iat.get(1).getAsInt(), iat.get(2).getAsInt()); - } else return null; - }).collect(Collectors.toSet())); - } else { - // not list format - removeRadar(NbtUtils.readBlockPos(tag, "data").orElseThrow()); - } - } -} +} \ No newline at end of file diff --git a/src/main/java/net/nullved/pmweatherapi/client/radar/WSRClientStorage.java b/src/main/java/net/nullved/pmweatherapi/client/radar/WSRClientStorage.java new file mode 100644 index 0000000..c0a6107 --- /dev/null +++ b/src/main/java/net/nullved/pmweatherapi/client/radar/WSRClientStorage.java @@ -0,0 +1,41 @@ +package net.nullved.pmweatherapi.client.radar; + +import dev.protomanly.pmweather.block.RadarBlock; +import dev.protomanly.pmweather.multiblock.wsr88d.WSR88DCore; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.nullved.pmweatherapi.client.data.IClientStorage; +import net.nullved.pmweatherapi.client.data.PMWClientStorages; +import net.nullved.pmweatherapi.radar.storage.RadarStorage; +import net.nullved.pmweatherapi.radar.storage.RadarStorageData; +import net.nullved.pmweatherapi.radar.storage.WSRStorage; +import net.nullved.pmweatherapi.radar.storage.WSRStorageData; + +/** + * A {@link IClientStorage} implementation for {@link WSR88DCore}s + *

+ * You should not create a {@link WSRClientStorage}, instead, use {@link PMWClientStorages#wsrs()} + * @since 0.15.3.3 + */ +public class WSRClientStorage extends WSRStorage implements IClientStorage { + /** + * DO NOT CALL THIS CONSTRUCTOR!!! + *
+ * Get a radar storage from {@link PMWClientStorages#wsrs()} + * @param clientLevel The {@link ClientLevel} to create this storage for + * @since 0.15.3.3 + */ + public WSRClientStorage(ClientLevel clientLevel) { + super(clientLevel.dimension()); + } + + /** + * Gets the level associated with this {@link WSRClientStorage} + * @return The {@link Minecraft} {@link ClientLevel} + * @since 0.15.3.3 + */ + @Override + public ClientLevel getLevel() { + return Minecraft.getInstance().level; + } +} \ No newline at end of file diff --git a/src/main/java/net/nullved/pmweatherapi/client/render/IRadarOverlay.java b/src/main/java/net/nullved/pmweatherapi/client/render/IRadarOverlay.java index ec2415b..812234b 100644 --- a/src/main/java/net/nullved/pmweatherapi/client/render/IRadarOverlay.java +++ b/src/main/java/net/nullved/pmweatherapi/client/render/IRadarOverlay.java @@ -2,16 +2,21 @@ import com.mojang.blaze3d.vertex.BufferBuilder; import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.Tesselator; +import com.mojang.blaze3d.vertex.VertexConsumer; import com.mojang.math.Axis; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Font; import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.block.entity.BlockEntity; import net.nullved.pmweatherapi.data.PMWExtras; import net.nullved.pmweatherapi.radar.RadarMode; +import org.joml.Matrix3f; +import org.joml.Matrix4f; /** * An interface defining a radar overlay @@ -29,17 +34,107 @@ public interface IRadarOverlay { */ void render(boolean canRender, RenderData renderData, BufferBuilder bufferBuilder, Object... args); + /** + * Get the {@link RadarMode} of the radar + * @param renderData The {@link RenderData} + * @return The radar's {@link RadarMode} + * @since 0.14.16.2 + */ default RadarMode getRadarMode(RenderData renderData) { return renderData.blockEntity().getBlockState().getValue(PMWExtras.RADAR_MODE); } + /** + * Render a texture at the given {@link ResourceLocation} + * @param texture The {@link ResourceLocation} of the texture + * @param renderData The {@link RenderData} + * @param poseStack The {@link PoseStack} to render with + * @param color The color + * @since 0.15.3.3 + */ + default void renderTexture(ResourceLocation texture, RenderData renderData, PoseStack poseStack, int color) { + PoseStack.Pose pose = poseStack.last(); + VertexConsumer consumer = renderData.multiBufferSource().getBuffer(RenderType.entityCutout(texture)); + + consumer.addVertex(pose, -0.5f, -0.5f, 0.0f) + .setColor(color) + .setUv(0.0F, 0.0F) + .setOverlay(renderData.combinedOverlayIn()) + .setLight(0xF000F0) + .setNormal(pose, 0.0f, 0.0f, 1.0f); + + consumer.addVertex(pose, 0.5f, -0.5f, 0.0f) + .setColor(color) + .setUv(1.0F, 0.0F) + .setOverlay(renderData.combinedOverlayIn()) + .setLight(0xF000F0) + .setNormal(pose, 0.0f, 0.0f, 1.0f); + + consumer.addVertex(pose, 0.5f, 0.5f, 0.0f) + .setColor(color) + .setUv(1.0F, 1.0F) + .setOverlay(renderData.combinedOverlayIn()) + .setLight(0xF000F0) + .setNormal(pose, 0.0f, 0.0f, 1.0f); + + consumer.addVertex(pose, -0.5f, 0.5f, 0.0f) + .setColor(color) + .setUv(0.0F, 1.0F) + .setOverlay(renderData.combinedOverlayIn()) + .setLight(0xF000F0) + .setNormal(pose, 0.0f, 0.0f, 1.0f); + } + + /** + * Render a texture at the given {@link ResourceLocation} + * @param texture The {@link ResourceLocation} of the texture + * @param renderData The {@link RenderData} + * @param poseStack The {@link PoseStack} to render with + * @since 0.15.3.3 + */ + default void renderTexture(ResourceLocation texture, RenderData renderData, PoseStack poseStack) { + renderTexture(texture, renderData, poseStack, 0xFFFFFFFF); + } + + /** + * Render a texture at the given {@link ResourceLocation} + * @param texture The {@link ResourceLocation} of the texture + * @param renderData The {@link RenderData} + * @param color The color + * @since 0.15.3.3 + */ + default void renderTexture(ResourceLocation texture, RenderData renderData, int color) { + renderTexture(texture, renderData, renderData.poseStack(), color); + } + + /** + * Render a texture at the given {@link ResourceLocation} + * @param texture The {@link ResourceLocation} of the texture + * @param renderData The {@link RenderData} + * @since 0.15.3.3 + */ + default void renderTexture(ResourceLocation texture, RenderData renderData) { + renderTexture(texture, renderData, renderData.poseStack(), 0xFFFFFFFF); + } + + /** + * Render the text given in the given {@link Component} + * @param component The {@link Component} to render + * @param renderData The {@link RenderData} + * @param poseStack The {@link PoseStack} to render with + * @since 0.14.16.2 + */ default void renderText(Component component, RenderData renderData, PoseStack poseStack) { Font font = Minecraft.getInstance().font; - poseStack.mulPose(Axis.XN.rotationDegrees(-90)); font.drawInBatch(component, 0, 0, 0xFFFFFF, false, poseStack.last().pose(), renderData.multiBufferSource(), Font.DisplayMode.NORMAL, 0xFFFFFF, 0xFFFFFF); - poseStack.mulPose(Axis.XN.rotationDegrees(90)); } + /** + * Render the text given in the given {@link Component} + * @param component The {@link Component} to render + * @param renderData The {@link RenderData} + * @since 0.14.16.2 + */ default void renderText(Component component, RenderData renderData) { this.renderText(component, renderData, renderData.poseStack()); } diff --git a/src/main/java/net/nullved/pmweatherapi/client/storage/ClientStorageInstance.java b/src/main/java/net/nullved/pmweatherapi/client/storage/ClientStorageInstance.java new file mode 100644 index 0000000..16c9513 --- /dev/null +++ b/src/main/java/net/nullved/pmweatherapi/client/storage/ClientStorageInstance.java @@ -0,0 +1,71 @@ +package net.nullved.pmweatherapi.client.storage; + +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.resources.ResourceLocation; +import net.nullved.pmweatherapi.PMWeatherAPI; +import net.nullved.pmweatherapi.client.data.IClientStorage; +import net.nullved.pmweatherapi.client.data.PMWClientStorages; +import net.nullved.pmweatherapi.storage.StorageInstance; +import net.nullved.pmweatherapi.storage.data.IStorageData; +import net.nullved.pmweatherapi.storage.data.StorageData; + +import java.util.Optional; +import java.util.function.Function; + +/** + * A client version of {@link StorageInstance}s, but only holds one {@link IClientStorage} + *

+ * You should not create {@link ClientStorageInstance}s yourself. + * Instead, get them from {@link PMWClientStorages#get} + * + * @param The {@link IStorageData} of the {@link IClientStorage} + * @param The {@link IClientStorage} + * @since 0.15.3.3 + */ +public class ClientStorageInstance> { + private final ResourceLocation id; + private final Class clazz; + private final Function creator; + private C storage; + + public ClientStorageInstance(ResourceLocation id, Class clazz, Function creator) { + this.id = id; + this.clazz = clazz; + this.creator = creator; + } + + public ResourceLocation id() { + return id; + } + + public C get() { + return storage; + } + + public void set(C storage) { + this.storage = storage; + } + + public > Optional> cast(Class oclazz) { + if (oclazz.isAssignableFrom(clazz)) { + @SuppressWarnings("unchecked") + ClientStorageInstance casted = new ClientStorageInstance<>(id(), oclazz, cl -> (O) creator.apply(cl)); + + try { + casted.set((O) storage); + return Optional.of(casted); + } catch (ClassCastException e) { + PMWeatherAPI.LOGGER.error("Could not cast {} to {}", clazz.getSimpleName(), oclazz.getSimpleName()); + return Optional.empty(); + } + } else return Optional.empty(); + } + + public void load(ClientLevel level) { + storage = creator.apply(level); + } + + public void clear() { + storage = null; + } +} diff --git a/src/main/java/net/nullved/pmweatherapi/command/NearbyRadarsCommand.java b/src/main/java/net/nullved/pmweatherapi/command/NearbyRadarsCommand.java deleted file mode 100644 index aba36a8..0000000 --- a/src/main/java/net/nullved/pmweatherapi/command/NearbyRadarsCommand.java +++ /dev/null @@ -1,61 +0,0 @@ -package net.nullved.pmweatherapi.command; - -import com.mojang.brigadier.Command; -import com.mojang.brigadier.CommandDispatcher; -import com.mojang.brigadier.arguments.IntegerArgumentType; -import com.mojang.brigadier.context.CommandContext; -import net.minecraft.ChatFormatting; -import net.minecraft.commands.CommandSourceStack; -import net.minecraft.commands.Commands; -import net.minecraft.core.BlockPos; -import net.minecraft.network.chat.Component; -import net.minecraft.world.entity.player.Player; -import net.nullved.pmweatherapi.PMWeatherAPI; -import net.nullved.pmweatherapi.radar.NearbyRadars; -import net.nullved.pmweatherapi.util.PMWUtils; - -import java.util.Set; - -public class NearbyRadarsCommand { - public static void register(CommandDispatcher dispatcher) { - dispatcher.register( - Commands.literal("nearbyradars") - .then(Commands.argument("radius", IntegerArgumentType.integer(1, 2048)) - .executes(NearbyRadarsCommand::nearbyRadars)) - .executes(NearbyRadarsCommand::nearbyRadars) - ); - } - - private static int nearbyRadars(CommandContext ctx) { - PMWeatherAPI.LOGGER.info("Checking for nearby radars..."); - - if (!ctx.getSource().isPlayer()) { - ctx.getSource().sendSystemMessage(Component.translatable("commands.pmweatherapi.non_player")); - return Command.SINGLE_SUCCESS; - } - - Player plr = ctx.getSource().getPlayer(); - int radius; - try { - radius = ctx.getArgument("radius", Integer.class); - } catch (Exception e) { - radius = 512; - } - - long startTimeMillis = System.currentTimeMillis(); - Set blocks = NearbyRadars.get(plr.level()).radarsNearBlock(plr.blockPosition(), radius); - long elapsedTime = System.currentTimeMillis() - startTimeMillis; - - StringBuilder sb = new StringBuilder("Found ").append(blocks.size()).append(" radars in ").append(elapsedTime / 1000.0F).append("s"); - for (BlockPos blockPos : blocks) { - sb.append("\nPos: ").append(blockPos.toShortString()); - } - - if (PMWUtils.isRadarAdjacent(plr.level(), plr.blockPosition())) { - sb.append("\nYou are next to a radar!"); - } - - plr.sendSystemMessage(Component.literal(sb.toString()).withColor(ChatFormatting.GOLD.getColor())); - return Command.SINGLE_SUCCESS; - } -} diff --git a/src/main/java/net/nullved/pmweatherapi/command/StoragesCommand.java b/src/main/java/net/nullved/pmweatherapi/command/StoragesCommand.java new file mode 100644 index 0000000..b37d257 --- /dev/null +++ b/src/main/java/net/nullved/pmweatherapi/command/StoragesCommand.java @@ -0,0 +1,158 @@ +package net.nullved.pmweatherapi.command; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.context.CommandContext; +import net.minecraft.ChatFormatting; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.ResourceLocationArgument; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.ChunkPos; +import net.nullved.pmweatherapi.PMWeatherAPI; +import net.nullved.pmweatherapi.client.data.IClientStorage; +import net.nullved.pmweatherapi.client.data.PMWClientStorages; +import net.nullved.pmweatherapi.data.PMWStorages; +import net.nullved.pmweatherapi.storage.IServerStorage; +import net.nullved.pmweatherapi.storage.IStorage; +import net.nullved.pmweatherapi.storage.data.StorageData; +import net.nullved.pmweatherapi.util.PMWUtils; + +import java.util.Set; +import java.util.function.BiFunction; + +public class StoragesCommand { + public static void register(CommandDispatcher dispatcher) { + dispatcher.register( + Commands.literal("storages") + .then(Commands.argument("storage", ResourceLocationArgument.id()) + .suggests((ctx, builder) -> { + PMWStorages.getAll().forEach(si -> builder.suggest(si.id().toString())); + return builder.buildFuture(); + }) + .then(Commands.literal("client") + .then(Commands.literal("all") + .then(Commands.argument("radius", IntegerArgumentType.integer(1, 2048)) + .executes(StoragesCommand::clientAll)) + .executes(StoragesCommand::clientAll)) + .then(Commands.literal("adjacentChunks") + .executes(StoragesCommand::clientAdjacentChunks)) + .executes(StoragesCommand::clientAll)) + .then(Commands.literal("all") + .then(Commands.argument("radius", IntegerArgumentType.integer(1, 2048)) + .executes(StoragesCommand::serverAll)) + .executes(StoragesCommand::serverAll)) + .then(Commands.literal("adjacentChunks") + .executes(StoragesCommand::serverAdjacentChunks)) + .executes(StoragesCommand::serverAll) + ) + ); + } + + private static int clientAll(CommandContext ctx) { + BiFunction, Player, Set> func; + try { + final int radius = ctx.getArgument("radius", Integer.class); + func = (stg, plr) -> stg.getAllWithinRange(plr.blockPosition(), radius); + } catch (Exception e) { + func = (stg, plr) -> stg.getAll(); + } + + return execClient(ctx, func); + } + + private static int serverAll(CommandContext ctx) { + BiFunction, Player, Set> func; + try { + final int radius = ctx.getArgument("radius", Integer.class); + func = (stg, plr) -> stg.getAllWithinRange(plr.blockPosition(), radius); + } catch (Exception e) { + func = (stg, plr) -> stg.getAll(); + } + + return exec(ctx, func); + } + + private static int clientAdjacentChunks(CommandContext ctx) { + return execClient(ctx, (stg, plr) -> stg.getInAdjacentChunks(new ChunkPos(plr.blockPosition()))); + } + + private static int serverAdjacentChunks(CommandContext ctx) { + return exec(ctx, (stg, plr) -> stg.getInAdjacentChunks(new ChunkPos(plr.blockPosition()))); + } + + private static int exec(CommandContext ctx, BiFunction, Player, Set> blocksFunction) { + PMWeatherAPI.LOGGER.info("Checking for nearby storages..."); + + Player plr = ctx.getSource().getPlayer(); + ResourceLocation storage = ResourceLocationArgument.getId(ctx, "storage"); + if (!ctx.getSource().isPlayer()) { + ctx.getSource().sendSystemMessage(Component.translatable("commands.pmweatherapi.non_player")); + return Command.SINGLE_SUCCESS; + } + + long startTimeMillis = System.currentTimeMillis(); + IServerStorage stg = (IServerStorage) PMWStorages.get(storage).get(plr.level().dimension()); + + Set blocks = Set.of(); + if (stg != null) { + blocks = blocksFunction.apply(stg, plr); + } + long elapsedTime = System.currentTimeMillis() - startTimeMillis; + + StringBuilder sb = new StringBuilder("Found ").append(blocks.size()).append(" server block positions in ").append(elapsedTime / 1000.0F).append("s"); + for (D data : blocks) { + sb.append("\nPos: ").append(data.getPos().toShortString()); + + if (PMWUtils.isRadarCornerAdjacent(plr.level(), data.getPos())) { + sb.append(" (RA)"); + } + } + + if (PMWUtils.isRadarCornerAdjacent(plr.level(), plr.blockPosition())) { + sb.append("\nYou are next to a radar!"); + } + + plr.sendSystemMessage(Component.literal(sb.toString()).withColor(ChatFormatting.GOLD.getColor())); + return Command.SINGLE_SUCCESS; + } + + private static int execClient(CommandContext ctx, BiFunction, Player, Set> blocksFunction) { + PMWeatherAPI.LOGGER.info("Checking for nearby storages..."); + + Player plr = ctx.getSource().getPlayer(); + ResourceLocation storage = ResourceLocationArgument.getId(ctx, "storage"); + if (!ctx.getSource().isPlayer()) { + ctx.getSource().sendSystemMessage(Component.translatable("commands.pmweatherapi.non_player")); + return Command.SINGLE_SUCCESS; + } + + long startTimeMillis = System.currentTimeMillis(); + IClientStorage stg = (IClientStorage) PMWClientStorages.get(storage).get(); + + Set blocks = Set.of(); + if (stg != null) { + blocks = blocksFunction.apply(stg, plr); + } + long elapsedTime = System.currentTimeMillis() - startTimeMillis; + + StringBuilder sb = new StringBuilder("Found ").append(blocks.size()).append(" client block positions in ").append(elapsedTime / 1000.0F).append("s"); + for (D data : blocks) { + sb.append("\nPos: ").append(data.getPos().toShortString()); + + if (PMWUtils.isRadarCornerAdjacent(plr.level(), data.getPos())) { + sb.append(" (RA)"); + } + } + + if (PMWUtils.isRadarCornerAdjacent(plr.level(), plr.blockPosition())) { + sb.append("\nYou are next to a radar!"); + } + + plr.sendSystemMessage(Component.literal(sb.toString()).withColor(ChatFormatting.GOLD.getColor())); + return Command.SINGLE_SUCCESS; + } +} diff --git a/src/main/java/net/nullved/pmweatherapi/data/PMWSavedData.java b/src/main/java/net/nullved/pmweatherapi/data/PMWSavedData.java index 2c1daa3..d9ff6fe 100644 --- a/src/main/java/net/nullved/pmweatherapi/data/PMWSavedData.java +++ b/src/main/java/net/nullved/pmweatherapi/data/PMWSavedData.java @@ -3,12 +3,14 @@ import net.minecraft.core.HolderLookup; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.level.saveddata.SavedData; -import net.nullved.pmweatherapi.radar.RadarStorage; +import net.nullved.pmweatherapi.radar.storage.RadarStorage; /** * The {@link SavedData} for PMWeatherAPI * @since 0.14.15.3 + * @deprecated Since 0.15.3.3 | For removal in 0.16.0.0 | Using new Storages system */ +@Deprecated(since = "0.15.3.3", forRemoval = true) public class PMWSavedData extends SavedData { private CompoundTag tag; private RadarStorage radarStorage; diff --git a/src/main/java/net/nullved/pmweatherapi/data/PMWStorageSavedData.java b/src/main/java/net/nullved/pmweatherapi/data/PMWStorageSavedData.java new file mode 100644 index 0000000..fc63946 --- /dev/null +++ b/src/main/java/net/nullved/pmweatherapi/data/PMWStorageSavedData.java @@ -0,0 +1,66 @@ +package net.nullved.pmweatherapi.data; + +import net.minecraft.core.HolderLookup; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.saveddata.SavedData; +import net.nullved.pmweatherapi.storage.IStorage; + +/** + * A {@link SavedData} instance for {@link IStorage}s + * + * @since 0.15.3.3 + */ +public class PMWStorageSavedData extends SavedData { + private final CompoundTag tag; + private IStorage storage; + + /** + * Gets the factory for loading the {@link SavedData} + * @return A {@link SavedData.Factory} for {@link PMWStorageSavedData} + * @since 0.15.3.3 + */ + public static SavedData.Factory factory() { + return new SavedData.Factory<>(PMWStorageSavedData::new, PMWStorageSavedData::load, null); + } + + public PMWStorageSavedData() { + this.tag = new CompoundTag(); + } + + public PMWStorageSavedData(CompoundTag tag) { + this.tag = tag; + } + + private static PMWStorageSavedData load(CompoundTag compoundTag, HolderLookup.Provider registries) { + return new PMWStorageSavedData(compoundTag); + } + + @Override + public CompoundTag save(CompoundTag compoundTag, HolderLookup.Provider provider) { + return storage.save(compoundTag); + } + + /** + * Sets the {@link IStorage} associated with this {@link Level} or dimension + * @param storage The {@link IStorage} + * @since 0.15.3.3 + */ + public void setStorage(IStorage storage) { + this.storage = storage; + } + + /** + * Gets the {@link SavedData} from the {@link Level} + * @return A data {@link CompoundTag} + * @since 0.15.3.3 + */ + public CompoundTag getTag() { + return tag; + } + + @Override + public boolean isDirty() { + return true; + } +} diff --git a/src/main/java/net/nullved/pmweatherapi/data/PMWStorages.java b/src/main/java/net/nullved/pmweatherapi/data/PMWStorages.java index 33817b6..402a04c 100644 --- a/src/main/java/net/nullved/pmweatherapi/data/PMWStorages.java +++ b/src/main/java/net/nullved/pmweatherapi/data/PMWStorages.java @@ -1,39 +1,151 @@ package net.nullved.pmweatherapi.data; import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.Level; -import net.nullved.pmweatherapi.radar.RadarMode; -import net.nullved.pmweatherapi.radar.RadarServerStorage; -import net.nullved.pmweatherapi.radar.RadarStorage; -import net.nullved.pmweatherapi.util.StringProperty; +import net.nullved.pmweatherapi.PMWeatherAPI; +import net.nullved.pmweatherapi.metar.MetarServerStorage; +import net.nullved.pmweatherapi.metar.MetarStorage; +import net.nullved.pmweatherapi.metar.MetarStorageData; +import net.nullved.pmweatherapi.radar.storage.*; +import net.nullved.pmweatherapi.storage.IServerStorage; +import net.nullved.pmweatherapi.storage.StorageInstance; +import net.nullved.pmweatherapi.storage.data.IStorageData; +import net.nullved.pmweatherapi.storage.data.StorageData; +import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.Optional; +import java.util.function.Function; /** * A class holding maps of dimensions to different storages * @since 0.14.15.3 */ public class PMWStorages { - public static final Map, RadarServerStorage> RADARS = new HashMap<>(); + public static final Map> STORAGE_INSTANCES = new HashMap<>(); /** - * Gets a {@link RadarServerStorage} for the given dimension - * @param dim The dimension to get the radar storage for + * Gets the map of {@link RadarServerStorage}s * @return The associated dimension's {@link RadarServerStorage} * @since 0.14.15.3 */ - public static RadarServerStorage getRadar(ResourceKey dim) { - return RADARS.get(dim); + public static StorageInstance radars() { + return get(RadarStorage.ID, RadarServerStorage.class).orElseThrow(); } /** - * Gets a {@link RadarServerStorage} for the given dimension - * @param dim The dimension to get the radar storage for - * @return The associated dimension's {@link RadarServerStorage} - * @since 0.14.15.3 + * Gets the map of {@link MetarServerStorage}s + * @return The associated dimension's {@link MetarServerStorage} + * @since 0.15.3.3 + */ + public static StorageInstance metars() { + return get(MetarStorage.ID, MetarServerStorage.class).orElseThrow(); + } + + /** + * Gets the map of {@link WSRServerStorage}s + * @return The associated dimension's {@link WSRServerStorage} + * @since 0.15.3.3 + */ + public static StorageInstance wsrs() { + return get(WSRStorage.ID, WSRServerStorage.class).orElseThrow(); + } + + /** + * Get a {@link StorageInstance} for a given {@link ResourceLocation} ID + * @param location The ID of the storage + * @return A {@link StorageInstance} + * @since 0.15.3.3 + */ + public static StorageInstance get(ResourceLocation location) { + if (!STORAGE_INSTANCES.containsKey(location)) { + PMWeatherAPI.LOGGER.error("No storage instance found for location {}", location); + } + return STORAGE_INSTANCES.get(location); + } + + /** + * Overwrite a {@link StorageInstance} + * @param location The ID {@link ResourceLocation} + * @param instance The new {@link StorageInstance} + * @since 0.15.3.3 + */ + public static void set(ResourceLocation location, StorageInstance instance) { + STORAGE_INSTANCES.put(location, instance); + } + + /** + * Casts the {@link StorageInstance} to the specified {@link IServerStorage} class after retrieval + * @param location The ID {@link ResourceLocation} + * @param clazz The {@link Class} of an {@link IServerStorage} to cast to + * @return The casted {@link StorageInstance} + * @param The {@link IStorageData} of the {@link IServerStorage} + * @param The {@link IServerStorage} + * @since 0.15.3.3 + */ + public static > Optional> get(ResourceLocation location, Class clazz) { + return STORAGE_INSTANCES.get(location).cast(clazz); + } + + /** + * Resets all data for all {@link StorageInstance}s + * @since 0.15.3.3 + */ + public static void resetAll() { + getAll().forEach(StorageInstance::clear); + } + + /** + * Gets all {@link StorageInstance}s + * @return A {@link Collection} of all {@link StorageInstance}s + * @since 0.15.3.3 + */ + public static Collection> getAll() { + return STORAGE_INSTANCES.values(); + } + + /** + * Gets all {@link IServerStorage}s for a given dimension + * @param dimension The {@link ResourceKey} of the dimension to get + * @return A {@link Collection} of {@link IServerStorage} for the given dimension + * @since 0.15.3.3 + */ + public static Collection> getForDimension(ResourceKey dimension) { + return getAll().stream().map(si -> si.get(dimension)).toList(); + } + + /** + * Loads a new {@link ServerLevel} for all {@link StorageInstance}s + * @param serverLevel The new {@link ServerLevel} to load + * @since 0.15.3.3 + */ + public static void loadDimension(ServerLevel serverLevel) { + STORAGE_INSTANCES.forEach((rl, si) -> si.load(serverLevel)); + } + + /** + * Removes a dimension from all {@link StorageInstance}s + * @param dimension The {@link ResourceKey} of the dimension to remove + * @since 0.15.3.3 + */ + public static void removeDimension(ResourceKey dimension) { + STORAGE_INSTANCES.forEach((rl, si) -> si.remove(dimension)); + } + + /** + * Register a new {@link IServerStorage} + * @param id The {@link ResourceLocation} to save this {@link IServerStorage} as + * @param clazz The {@link Class} of the {@link IServerStorage} + * @param creator A function creating another {@link IServerStorage} for the given {@link ServerLevel} + * @param The {@link IStorageData} of the {@link IServerStorage} + * @param The {@link IServerStorage} + * @since 0.15.3.3 */ - public static RadarServerStorage getRadar(Level dim) { - return getRadar(dim.dimension()); + public static > void registerStorage(ResourceLocation id, Class clazz, Function creator) { + StorageInstance instance = new StorageInstance<>(id, clazz, creator); + STORAGE_INSTANCES.put(id, instance); } } diff --git a/src/main/java/net/nullved/pmweatherapi/event/PMWEvents.java b/src/main/java/net/nullved/pmweatherapi/event/PMWEvents.java index 3d51687..40a2891 100644 --- a/src/main/java/net/nullved/pmweatherapi/event/PMWEvents.java +++ b/src/main/java/net/nullved/pmweatherapi/event/PMWEvents.java @@ -1,63 +1,126 @@ package net.nullved.pmweatherapi.event; -import dev.protomanly.pmweather.block.RadarBlock; +import dev.protomanly.pmweather.event.GameBusEvents; +import dev.protomanly.pmweather.weather.Sounding; +import dev.protomanly.pmweather.weather.ThermodynamicEngine; +import dev.protomanly.pmweather.weather.WeatherHandler; +import dev.protomanly.pmweather.weather.WindEngine; import net.minecraft.core.BlockPos; import net.minecraft.resources.ResourceKey; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.phys.Vec3; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.neoforge.event.RegisterCommandsEvent; import net.neoforged.neoforge.event.entity.player.PlayerEvent; -import net.neoforged.neoforge.event.level.ChunkWatchEvent; import net.neoforged.neoforge.event.level.LevelEvent; +import net.neoforged.neoforge.event.tick.ServerTickEvent; import net.nullved.pmweatherapi.PMWeatherAPI; -import net.nullved.pmweatherapi.command.NearbyRadarsCommand; +import net.nullved.pmweatherapi.command.StoragesCommand; import net.nullved.pmweatherapi.data.PMWStorages; -import net.nullved.pmweatherapi.radar.RadarServerStorage; +import net.nullved.pmweatherapi.metar.MetarStorageData; +import net.nullved.pmweatherapi.storage.IServerStorage; +import net.nullved.pmweatherapi.storage.ISyncServerStorage; -import java.util.*; +import java.util.ArrayList; +import java.util.List; @EventBusSubscriber(bus = EventBusSubscriber.Bus.GAME, modid = PMWeatherAPI.MODID) public class PMWEvents { + private static int ticks = 0; + @SubscribeEvent public static void onPlayerJoin(PlayerEvent.PlayerLoggedInEvent event) { - PMWStorages.getRadar(event.getEntity().level()).syncAllToPlayer(event.getEntity()); + ResourceKey dimension = event.getEntity().level().dimension(); + + PMWStorages.getAll().forEach(si -> { + IServerStorage storage = si.get(dimension); + if (storage != null) { + if (storage instanceof ISyncServerStorage isss) { + PMWeatherAPI.LOGGER.debug("Syncing stoage {}", isss.getId().toString()); + isss.syncAllToPlayer(event.getEntity()); + } + } + }); + + PMWeatherAPI.LOGGER.info("Synced all sync-storages to joined player {}", event.getEntity().getDisplayName().getString()); } @SubscribeEvent - public static void onChunkLoadEvent(ChunkWatchEvent.Sent event) { - RadarServerStorage radarStorage = PMWStorages.getRadar(event.getLevel()); - if (radarStorage.shouldRecalculate(event.getChunk().getPos())) { - List radars = new ArrayList<>(); - LevelAccessor level = event.getLevel(); - Set blockEntities = event.getChunk().getBlockEntitiesPos(); - - for (BlockPos pos : blockEntities) { - if (level.getBlockState(pos).getBlock() instanceof RadarBlock) radars.add(pos); - } + public static void onPlayerChangeDimension(PlayerEvent.PlayerChangedDimensionEvent event) { + PMWStorages.getForDimension(event.getTo()).forEach(iss -> { + if (iss instanceof ISyncServerStorage isss) isss.syncAllToPlayer(event.getEntity()); + }); + } + +// @SubscribeEvent +// public static void onChunkSentEvent(ChunkWatchEvent.Sent event) { +// RadarServerStorage radarStorage = PMWStorages.radars().getOrCreate(event.getLevel()); +// if (radarStorage.shouldRecalculate(event.getChunk().getPos())) { +// List radars = new ArrayList<>(); +// LevelAccessor level = event.getLevel(); +// Set blockEntities = event.getChunk().getBlockEntitiesPos(); +// +// for (BlockPos pos : blockEntities) { +// if (level.getBlockState(pos).getBlock() instanceof RadarBlock) radars.add(new RadarStorageData(pos)); +// } +// +// radarStorage.addAndSync(radars); +// } +// } + + @SubscribeEvent + public static void onTick(ServerTickEvent.Pre event) { + ticks += 1; + if (ticks % 1200 == 0) { + ticks = 0; + + PMWeatherAPI.LOGGER.info("Saving metar data!"); + PMWStorages.metars().entrySet().forEach(entry -> { + WeatherHandler weatherHandler = GameBusEvents.MANAGERS.get(entry.getKey()); + + List updated = new ArrayList<>(); + entry.getValue().getAll().forEach(msd -> { + BlockPos pos = msd.getPos(); + Level level = weatherHandler.getWorld(); + + Vec3 wind = WindEngine.getWind(pos, level); + int windAngle = Math.floorMod((int)Math.toDegrees(Math.atan2(wind.x, -wind.z)), 360); + double windspeed = wind.length(); + ThermodynamicEngine.AtmosphericDataPoint sfc = ThermodynamicEngine.samplePoint(weatherHandler, pos.getCenter(), level, null, 0); + float temp = sfc.temperature(); + float dew = sfc.dewpoint(); + float riskV = 0.0F; + for(int i = 0; i < 24000; i += 200) { + Sounding sounding = new Sounding(weatherHandler, pos.getCenter(), level, 250, 16000, i); + float r = sounding.getRisk(i); + if (r > riskV) { + riskV = r; + } + } - radarStorage.addRadars(radars); - radarStorage.syncAdd(radars); + MetarStorageData newMsd = new MetarStorageData(pos, temp, dew, (float) windAngle, (float) windspeed, riskV); + updated.add(newMsd); + }); + entry.getValue().addAndSync(updated); + }); } } @SubscribeEvent public static void onRegisterCommandsEvent(RegisterCommandsEvent event) { PMWeatherAPI.LOGGER.info("Registering PMWeatherAPI Commands"); - NearbyRadarsCommand.register(event.getDispatcher()); + StoragesCommand.register(event.getDispatcher()); } @SubscribeEvent public static void onLevelLoadEvent(LevelEvent.Load event) { LevelAccessor level = event.getLevel(); if (!level.isClientSide() && level instanceof ServerLevel slevel) { - ResourceKey dimension = slevel.dimension(); - RadarServerStorage radars = new RadarServerStorage(slevel); - radars.read(); - PMWStorages.RADARS.put(dimension, radars); - PMWeatherAPI.LOGGER.info("Loaded radars for dimension {}", slevel.dimension().location()); + PMWStorages.loadDimension(slevel); + PMWeatherAPI.LOGGER.info("Loaded storages for dimension {}", slevel.dimension().location()); } } @@ -65,7 +128,8 @@ public static void onLevelLoadEvent(LevelEvent.Load event) { public static void onLevelUnloadEvent(LevelEvent.Unload event) { LevelAccessor level = event.getLevel(); if (!level.isClientSide() && level instanceof ServerLevel slevel) { - PMWStorages.RADARS.remove(slevel.dimension()); + PMWStorages.removeDimension(slevel.dimension()); + PMWeatherAPI.LOGGER.info("Unloaded storages for dimension {}", slevel.dimension().location()); } } } diff --git a/src/main/java/net/nullved/pmweatherapi/example/ExampleOverlay.java b/src/main/java/net/nullved/pmweatherapi/example/ExampleOverlay.java index 36a70e4..ac65ca5 100644 --- a/src/main/java/net/nullved/pmweatherapi/example/ExampleOverlay.java +++ b/src/main/java/net/nullved/pmweatherapi/example/ExampleOverlay.java @@ -1,7 +1,10 @@ package net.nullved.pmweatherapi.example; import com.mojang.blaze3d.vertex.BufferBuilder; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.math.Axis; import dev.protomanly.pmweather.block.RadarBlock; +import dev.protomanly.pmweather.block.entity.RadarBlockEntity; import dev.protomanly.pmweather.config.ClientConfig; import net.minecraft.core.BlockPos; import net.minecraft.world.level.block.entity.BlockEntity; @@ -14,6 +17,7 @@ import net.nullved.pmweatherapi.radar.NearbyRadars; import net.nullved.pmweatherapi.radar.RadarMode; import net.nullved.pmweatherapi.storm.NearbyStorms; +import net.nullved.pmweatherapi.util.ColorMaps; import org.joml.Vector3f; /** @@ -30,28 +34,37 @@ public void render(boolean canRender, RenderData renderData, BufferBuilder buffe BlockEntity blockEntity = renderData.blockEntity(); BlockPos pos = blockEntity.getBlockPos(); RadarMode mode = getRadarMode(renderData); + if (mode == RadarMode.REFLECTIVITY) { NearbyRadars.client().forRadarNearBlock(pos, 2048, - p -> renderMarker(bufferBuilder, p.offset(-pos.getX(), -pos.getY(), -pos.getZ()).getCenter())); + p -> renderMarker(bufferBuilder, renderData, p.offset(-pos.getX(), -pos.getY(), -pos.getZ()).getCenter(), 0xFF880000)); } else if (mode == RadarMode.VELOCITY) { NearbyStorms.client().forStormNearBlock(pos, 2048, - s -> renderMarker(bufferBuilder, s.position.add(-pos.getX(), -pos.getY(), -pos.getZ()))); + s -> renderMarker(bufferBuilder, renderData, s.position.add(-pos.getX(), -pos.getY(), -pos.getZ()), 0xFF008800)); } } @Override public String getModID() { - return PMWeatherAPI.MODID + "_test"; + return "example"; } - private static void renderMarker(BufferBuilder bufferBuilder, Vec3 relative) { + private void renderMarker(BufferBuilder bufferBuilder, RenderData renderData, Vec3 relative, int color) { float resolution = ClientConfig.radarResolution; Vector3f radarPos = relative.add(0.5, 0.5, 0.5).toVector3f().mul(3 / (2 * resolution)).div(2048, 0, 2048).div(1.0F / resolution, 0.0F, 1.0F / resolution); Vector3f topLeft = (new Vector3f(-1.0F, 0.0F, -1.0F)).mul(0.015F).add(radarPos.x, 0.005F, radarPos.z); Vector3f bottomLeft = (new Vector3f(-1.0F, 0.0F, 1.0F)).mul(0.015F).add(radarPos.x, 0.005F, radarPos.z); Vector3f bottomRight = (new Vector3f(1.0F, 0.0F, 1.0F)).mul(0.015F).add(radarPos.x, 0.005F, radarPos.z); Vector3f topRight = (new Vector3f(1.0F, 0.0F, -1.0F)).mul(0.015F).add(radarPos.x, 0.005F, radarPos.z); - int color = 0xFFAAAAAA; + + PoseStack pose = renderData.poseStack(); + pose.pushPose(); + pose.translate(0.5F, 1.05F, 0.5F); + pose.translate(radarPos.x, 0.005F, radarPos.z); + pose.scale(0.05F, 0.05F, 0.05F); + pose.mulPose(Axis.XP.rotationDegrees(180)); + + pose.popPose(); bufferBuilder.addVertex(topLeft).setColor(color).addVertex(bottomLeft).setColor(color).addVertex(bottomRight).setColor(color).addVertex(topRight).setColor(color); } } diff --git a/src/main/java/net/nullved/pmweatherapi/metar/MetarServerStorage.java b/src/main/java/net/nullved/pmweatherapi/metar/MetarServerStorage.java new file mode 100644 index 0000000..82d5274 --- /dev/null +++ b/src/main/java/net/nullved/pmweatherapi/metar/MetarServerStorage.java @@ -0,0 +1,40 @@ +package net.nullved.pmweatherapi.metar; + +import dev.protomanly.pmweather.block.MetarBlock; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerLevel; +import net.nullved.pmweatherapi.client.data.IClientStorage; +import net.nullved.pmweatherapi.client.metar.MetarClientStorage; +import net.nullved.pmweatherapi.data.PMWStorages; +import net.nullved.pmweatherapi.network.S2CMetarPacket; +import net.nullved.pmweatherapi.network.S2CStoragePacket; +import net.nullved.pmweatherapi.storage.IServerStorage; +import net.nullved.pmweatherapi.storage.ISyncServerStorage; + +/** + * {@link IServerStorage} for {@link MetarBlock}s + *

+ * You should not create a {@link MetarServerStorage}, instead, use {@link PMWStorages#metars()} + * + * @since 0.15.3.3 + * @see MetarStorage + * @see MetarClientStorage + */ +public class MetarServerStorage extends MetarStorage implements ISyncServerStorage { + private final ServerLevel level; + + public MetarServerStorage(ServerLevel level) { + super(level.dimension()); + this.level = level; + } + + @Override + public ServerLevel getLevel() { + return level; + } + + @Override + public S2CStoragePacket> packet(CompoundTag tag) { + return new S2CMetarPacket(tag); + } +} diff --git a/src/main/java/net/nullved/pmweatherapi/metar/MetarStorage.java b/src/main/java/net/nullved/pmweatherapi/metar/MetarStorage.java new file mode 100644 index 0000000..7f9f3c8 --- /dev/null +++ b/src/main/java/net/nullved/pmweatherapi/metar/MetarStorage.java @@ -0,0 +1,43 @@ +package net.nullved.pmweatherapi.metar; + +import dev.protomanly.pmweather.block.MetarBlock; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.Level; +import net.nullved.pmweatherapi.PMWeatherAPI; +import net.nullved.pmweatherapi.client.metar.MetarClientStorage; +import net.nullved.pmweatherapi.storage.PMWStorage; + +/** + * {@link PMWStorage} for {@link MetarBlock}s + * + * @since 0.15.3.3 + * @see PMWStorage + * @see MetarServerStorage + * @see MetarClientStorage + */ +public abstract class MetarStorage extends PMWStorage { + public static final int VERSION = 1; + public static final ResourceLocation ID = PMWeatherAPI.rl("metars"); + + public MetarStorage(ResourceKey dimension) { + super(dimension); + } + + public abstract Level getLevel(); + + @Override + public ResourceLocation getId() { + return ID; + } + + @Override + public ResourceLocation getExpectedDataType() { + return MetarStorageData.ID; + } + + @Override + public int version() { + return VERSION; + } +} diff --git a/src/main/java/net/nullved/pmweatherapi/metar/MetarStorageData.java b/src/main/java/net/nullved/pmweatherapi/metar/MetarStorageData.java new file mode 100644 index 0000000..9a31d03 --- /dev/null +++ b/src/main/java/net/nullved/pmweatherapi/metar/MetarStorageData.java @@ -0,0 +1,80 @@ +package net.nullved.pmweatherapi.metar; + +import dev.protomanly.pmweather.block.MetarBlock; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; +import net.nullved.pmweatherapi.PMWeatherAPI; +import net.nullved.pmweatherapi.storage.data.StorageData; + +/** + * {@link StorageData} for {@link MetarBlock}s. + * Includes position, temp, dewpoint, wind, and risk data + * + * @since 0.15.3.3 + * @see StorageData + */ +public class MetarStorageData extends StorageData { + public static final ResourceLocation ID = PMWeatherAPI.rl("metar"); + private float temp, dew, windAngle, windspeed, risk; + + public MetarStorageData(BlockPos pos, float temp, float dew, float windAngle, float windspeed, float risk) { + super(pos); + this.temp = temp; + this.dew = dew; + this.windAngle = windAngle; + this.windspeed = windspeed; + this.risk = risk; + } + + public float getTemp() { + return temp; + } + + public float getDew() { + return dew; + } + + public float getWindAngle() { + return windAngle; + } + + public float getWindspeed() { + return windspeed; + } + + public float getRisk() { + return risk; + } + + @Override + public ResourceLocation getId() { + return ID; + } + + @Override + public CompoundTag serializeToNBT() { + CompoundTag tag = super.serializeToNBT(); + tag.putFloat("temp", temp); + tag.putFloat("dew", dew); + tag.putFloat("wind_angle", windAngle); + tag.putFloat("windspeed", windspeed); + tag.putFloat("risk", risk); + return tag; + } + + public static MetarStorageData deserializeFromNBT(CompoundTag tag, int version) { + BlockPos bp = deserializeBlockPos(tag); + + if (bp == null) { + throw new IllegalArgumentException("Could not read BlockPos in MetarStorageData!"); + } + + float temp = tag.getFloat("temp"); + float dew = tag.getFloat("dew"); + float windAngle = tag.getFloat("wind_angle"); + float windspeed = tag.getFloat("windspeed"); + float risk = tag.getFloat("risk"); + return new MetarStorageData(bp, temp, dew, windAngle, windspeed, risk); + } +} diff --git a/src/main/java/net/nullved/pmweatherapi/mixin/BlockBehaviourMixin.java b/src/main/java/net/nullved/pmweatherapi/mixin/BlockBehaviourMixin.java index dec4c09..558edc5 100644 --- a/src/main/java/net/nullved/pmweatherapi/mixin/BlockBehaviourMixin.java +++ b/src/main/java/net/nullved/pmweatherapi/mixin/BlockBehaviourMixin.java @@ -1,12 +1,27 @@ package net.nullved.pmweatherapi.mixin; +import dev.protomanly.pmweather.block.MetarBlock; import dev.protomanly.pmweather.block.RadarBlock; +import dev.protomanly.pmweather.block.entity.RadarBlockEntity; +import dev.protomanly.pmweather.event.GameBusEvents; +import dev.protomanly.pmweather.multiblock.wsr88d.WSR88DCore; +import dev.protomanly.pmweather.weather.Sounding; +import dev.protomanly.pmweather.weather.ThermodynamicEngine; +import dev.protomanly.pmweather.weather.WeatherHandler; +import dev.protomanly.pmweather.weather.WindEngine; import net.minecraft.core.BlockPos; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.state.BlockBehaviour; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.Vec3; +import net.nullved.pmweatherapi.data.PMWExtras; import net.nullved.pmweatherapi.data.PMWStorages; -import net.nullved.pmweatherapi.radar.RadarServerStorage; +import net.nullved.pmweatherapi.metar.MetarServerStorage; +import net.nullved.pmweatherapi.metar.MetarStorageData; +import net.nullved.pmweatherapi.radar.RadarMode; +import net.nullved.pmweatherapi.radar.storage.RadarServerStorage; +import net.nullved.pmweatherapi.radar.storage.RadarStorageData; +import net.nullved.pmweatherapi.radar.storage.WSRStorageData; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -17,18 +32,43 @@ public class BlockBehaviourMixin { @Inject(method = "onPlace", at = @At("HEAD")) private static void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean movedByPiston, CallbackInfo ci) { if (state.getBlock() instanceof RadarBlock) { - RadarServerStorage radarStorage = PMWStorages.getRadar(level); - radarStorage.addRadar(pos); - radarStorage.syncAdd(pos); + RadarServerStorage radarStorage = PMWStorages.radars().get(level.dimension()); + radarStorage.addAndSync(new RadarStorageData(pos, state.getValue(PMWExtras.RADAR_MODE))); + } else if (state.getBlock() instanceof MetarBlock) { + // Get Metar data + WeatherHandler weatherHandler = GameBusEvents.MANAGERS.get(level.dimension()); + Vec3 wind = WindEngine.getWind(pos, level); + int windAngle = Math.floorMod((int)Math.toDegrees(Math.atan2(wind.x, -wind.z)), 360); + double windspeed = wind.length(); + ThermodynamicEngine.AtmosphericDataPoint sfc = ThermodynamicEngine.samplePoint(weatherHandler, pos.getCenter(), level, null, 0); + float temp = sfc.temperature(); + float dew = sfc.dewpoint(); + float riskV = 0.0F; + for(int i = 0; i < 24000; i += 200) { + Sounding sounding = new Sounding(weatherHandler, pos.getCenter(), level, 250, 16000, i); + float r = sounding.getRisk(i); + if (r > riskV) { + riskV = r; + } + } + + MetarServerStorage metarStorage = PMWStorages.metars().get(level.dimension()); + metarStorage.addAndSync(new MetarStorageData(pos, temp, dew, (float) windAngle, (float) windspeed, riskV)); + } else if (state.getBlock() instanceof WSR88DCore wsr) { + PMWStorages.wsrs().get(level.dimension()).addAndSync(new WSRStorageData(pos, wsr.isComplete(state))); } } @Inject(method = "onRemove", at = @At("HEAD")) private static void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean movedByPiston, CallbackInfo ci) { if (state.getBlock() instanceof RadarBlock) { - RadarServerStorage radarStorage = PMWStorages.getRadar(level); - radarStorage.removeRadar(pos); - radarStorage.syncRemove(pos); + RadarServerStorage radarStorage = PMWStorages.radars().get(level.dimension()); + radarStorage.removeAndSync(pos); + } else if (state.getBlock() instanceof MetarBlock) { + MetarServerStorage metarStorage = PMWStorages.metars().get(level.dimension()); + metarStorage.removeAndSync(pos); + }else if (state.getBlock() instanceof WSR88DCore wsr) { + PMWStorages.wsrs().get(level.dimension()).removeAndSync(pos); } } } diff --git a/src/main/java/net/nullved/pmweatherapi/mixin/PacketNBTFromClientMixin.java b/src/main/java/net/nullved/pmweatherapi/mixin/PacketNBTFromClientMixin.java deleted file mode 100644 index ba62f7b..0000000 --- a/src/main/java/net/nullved/pmweatherapi/mixin/PacketNBTFromClientMixin.java +++ /dev/null @@ -1,23 +0,0 @@ -package net.nullved.pmweatherapi.mixin; - -import dev.protomanly.pmweather.networking.PacketNBTFromClient; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.NbtUtils; -import net.minecraft.world.entity.player.Player; -import net.nullved.pmweatherapi.data.PMWStorages; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(PacketNBTFromClient.class) -public class PacketNBTFromClientMixin { - @Shadow @Final private CompoundTag compoundTag; - - @Inject(method = "handle", at = @At(value = "INVOKE", target = "Ldev/protomanly/pmweather/block/entity/RadarBlockEntity;playerRequestsSync(Lnet/minecraft/server/level/ServerPlayer;Lnet/minecraft/core/BlockPos;)V")) - private void onHandle(Player player, CallbackInfo ci) { - PMWStorages.getRadar(player.level()).addRadar(NbtUtils.readBlockPos(this.compoundTag, "blockPos").get()); - } -} diff --git a/src/main/java/net/nullved/pmweatherapi/mixin/RadarBlockMixin.java b/src/main/java/net/nullved/pmweatherapi/mixin/RadarBlockMixin.java index 873547d..49108eb 100644 --- a/src/main/java/net/nullved/pmweatherapi/mixin/RadarBlockMixin.java +++ b/src/main/java/net/nullved/pmweatherapi/mixin/RadarBlockMixin.java @@ -14,7 +14,9 @@ import net.minecraft.world.level.block.state.properties.EnumProperty; import net.minecraft.world.phys.BlockHitResult; import net.nullved.pmweatherapi.data.PMWExtras; +import net.nullved.pmweatherapi.data.PMWStorages; import net.nullved.pmweatherapi.radar.RadarMode; +import net.nullved.pmweatherapi.radar.storage.RadarStorageData; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; @@ -49,6 +51,7 @@ private InteractionResult useWithoutItem(BlockState state, Level level, BlockPos if (!level.isClientSide()) { RadarMode currentMode = state.getValue(PMWExtras.RADAR_MODE); RadarMode newMode = currentMode.cycle(); + PMWStorages.radars().get(level.dimension()).addAndSync(new RadarStorageData(pos, newMode)); level.setBlockAndUpdate(pos, state.setValue(PMWExtras.RADAR_MODE, newMode)); } diff --git a/src/main/java/net/nullved/pmweatherapi/mixin/RadarRendererMixin.java b/src/main/java/net/nullved/pmweatherapi/mixin/RadarRendererMixin.java index 67acf73..b1943f1 100644 --- a/src/main/java/net/nullved/pmweatherapi/mixin/RadarRendererMixin.java +++ b/src/main/java/net/nullved/pmweatherapi/mixin/RadarRendererMixin.java @@ -568,17 +568,19 @@ private void render(BlockEntity blockEntity, float partialTicks, PoseStack poseS color = dbg; } - r = (float)color.getRed() / 255.0F; - g = (float)color.getGreen() / 255.0F; - b = (float)color.getBlue() / 255.0F; - a = (float)color.getAlpha() / 255.0F * 0.75F + 0.25F; - - Vector3f topLeft = (new Vector3f(-1.0F, 0.0F, -1.0F)).mul(size / 4.0F).add(pixelPos); - Vector3f bottomLeft = (new Vector3f(-1.0F, 0.0F, 1.0F)).mul(size / 4.0F).add(pixelPos); - Vector3f bottomRight = (new Vector3f(1.0F, 0.0F, 1.0F)).mul(size / 4.0F).add(pixelPos); - Vector3f topRight = (new Vector3f(1.0F, 0.0F, -1.0F)).mul(size / 4.0F).add(pixelPos); - - bufferBuilder.addVertex(topLeft).setColor(r, g, b, a).addVertex(bottomLeft).setColor(r, g, b, a).addVertex(bottomRight).setColor(r, g, b, a).addVertex(topRight).setColor(r, g, b, a); + if (!RadarMode.isBaseRenderingDisabled()) { + r = (float) color.getRed() / 255.0F; + g = (float) color.getGreen() / 255.0F; + b = (float) color.getBlue() / 255.0F; + a = (float) color.getAlpha() / 255.0F * 0.75F + 0.25F; + + Vector3f topLeft = (new Vector3f(-1.0F, 0.0F, -1.0F)).mul(size / 4.0F).add(pixelPos); + Vector3f bottomLeft = (new Vector3f(-1.0F, 0.0F, 1.0F)).mul(size / 4.0F).add(pixelPos); + Vector3f bottomRight = (new Vector3f(1.0F, 0.0F, 1.0F)).mul(size / 4.0F).add(pixelPos); + Vector3f topRight = (new Vector3f(1.0F, 0.0F, -1.0F)).mul(size / 4.0F).add(pixelPos); + + bufferBuilder.addVertex(topLeft).setColor(r, g, b, a).addVertex(bottomLeft).setColor(r, g, b, a).addVertex(bottomRight).setColor(r, g, b, a).addVertex(topRight).setColor(r, g, b, a); + } } } diff --git a/src/main/java/net/nullved/pmweatherapi/mixin/WSR88DCoreMixin.java b/src/main/java/net/nullved/pmweatherapi/mixin/WSR88DCoreMixin.java new file mode 100644 index 0000000..f26f8d0 --- /dev/null +++ b/src/main/java/net/nullved/pmweatherapi/mixin/WSR88DCoreMixin.java @@ -0,0 +1,20 @@ +package net.nullved.pmweatherapi.mixin; + +import dev.protomanly.pmweather.multiblock.wsr88d.WSR88DCore; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import net.nullved.pmweatherapi.data.PMWStorages; +import net.nullved.pmweatherapi.radar.storage.WSRStorageData; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(WSR88DCore.class) +public class WSR88DCoreMixin { + @Inject(method = "completionChanged", at = @At("HEAD")) + public void completionChanged(boolean completed, Level level, BlockState blockState, BlockPos pos, CallbackInfo ci) { + PMWStorages.wsrs().get(level.dimension()).addAndSync(new WSRStorageData(pos, completed)); + } +} diff --git a/src/main/java/net/nullved/pmweatherapi/network/PMWNetworking.java b/src/main/java/net/nullved/pmweatherapi/network/PMWNetworking.java index 74c5670..867461b 100644 --- a/src/main/java/net/nullved/pmweatherapi/network/PMWNetworking.java +++ b/src/main/java/net/nullved/pmweatherapi/network/PMWNetworking.java @@ -1,34 +1,33 @@ package net.nullved.pmweatherapi.network; import net.minecraft.nbt.CompoundTag; -import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.player.Player; import net.neoforged.neoforge.network.PacketDistributor; import net.neoforged.neoforge.network.handling.IPayloadHandler; import net.neoforged.neoforge.network.registration.PayloadRegistrar; -import net.nullved.pmweatherapi.PMWeatherAPI; +import net.nullved.pmweatherapi.radar.storage.RadarServerStorage; import java.util.function.BiConsumer; +import java.util.function.Function; /** * The base networking class for PMWeatherAPI * @since 0.14.15.3 */ public class PMWNetworking { - public static final ResourceLocation RADARS_ID = PMWeatherAPI.rl("radars"); - /** * Registers all of PMWeatherAPI's packets. Should not be called * @param args The arguments to pass * @since 0.14.15.3 */ public static void register(Object... args) { - registerClientboundPacket(S2CRadarsPacket.TYPE, S2CRadarsPacket.STREAM_CODEC, S2CRadarsPacket::handle, args); + registerClientboundPacket(S2CRadarPacket.TYPE, S2CRadarPacket.STREAM_CODEC, S2CRadarPacket::handle, args); + registerClientboundPacket(S2CMetarPacket.TYPE, S2CMetarPacket.STREAM_CODEC, S2CMetarPacket::handle, args); + registerClientboundPacket(S2CWSRPacket.TYPE, S2CWSRPacket.STREAM_CODEC, S2CWSRPacket::handle, args); } /** @@ -55,27 +54,27 @@ public static void registerServerboundPacket(Cus * @param A packet extending {@link CustomPacketPayload} * @since 0.14.15.3 */ - public static void registerClientboundPacket(CustomPacketPayload.Type type, StreamCodec codec, BiConsumer handler, Object... args) { + public static void registerClientboundPacket(CustomPacketPayload.Type type, StreamCodec codec, BiConsumer handler, Object... args) { PayloadRegistrar registrar = (PayloadRegistrar) args[0]; IPayloadHandler clientHandler = (pkt, ctx) -> ctx.enqueueWork(() -> handler.accept(pkt, ctx.player())); registrar.playToClient(type, codec, clientHandler); } /** - * Sends all radars to all clients - * @param tag The tag to send, generated by the {@link net.nullved.pmweatherapi.radar.RadarServerStorage} - * @since 0.14.15.3 + * Sends all clients a {@link S2CStoragePacket}. The type of packet is dependent on the caller of this method + * @param tag The tag to send, generated by the {@link RadarServerStorage} + * @since 0.15.3.3 */ - public static void serverSendRadarsToAll(CompoundTag tag) { - PacketDistributor.sendToAllPlayers(new S2CRadarsPacket(tag)); + public static void serverSendStorageToAll(CompoundTag tag, Function> pkt) { + PacketDistributor.sendToAllPlayers(pkt.apply(tag)); } /** - * Sends all radars to a specific player - * @param tag The tag to send, generated by the {@link net.nullved.pmweatherapi.radar.RadarServerStorage} - * @since 0.14.15.3 + * Sends a {@link S2CStoragePacket} to a specific player. The type of packet is dependent on the caller of this method + * @param tag The tag to send with the packet + * @since 0.15.3.3 */ - public static void serverSendRadarsToPlayer(CompoundTag tag, Player player) { - PacketDistributor.sendToPlayer((ServerPlayer) player, new S2CRadarsPacket(tag)); + public static void serverSendStorageToPlayer(CompoundTag tag, Function> pkt, Player player) { + PacketDistributor.sendToPlayer((ServerPlayer) player, pkt.apply(tag)); } } diff --git a/src/main/java/net/nullved/pmweatherapi/network/S2CMetarPacket.java b/src/main/java/net/nullved/pmweatherapi/network/S2CMetarPacket.java new file mode 100644 index 0000000..00a4d73 --- /dev/null +++ b/src/main/java/net/nullved/pmweatherapi/network/S2CMetarPacket.java @@ -0,0 +1,44 @@ +package net.nullved.pmweatherapi.network; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.nullved.pmweatherapi.PMWeatherAPI; +import net.nullved.pmweatherapi.client.data.PMWClientStorages; +import net.nullved.pmweatherapi.client.metar.MetarClientStorage; +import net.nullved.pmweatherapi.client.radar.RadarClientStorage; + +/** + * The packet that syncs metars from the server to the client, using the Storages system + * @since 0.15.3.3 + */ +public class S2CMetarPacket extends S2CStoragePacket { + public static final Type TYPE = new Type<>(PMWeatherAPI.rl("s2c_metar")); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite(ByteBufCodecs.COMPOUND_TAG, S2CMetarPacket::tag, S2CMetarPacket::new); + + /** + * Creates a new {@link S2CMetarPacket} + * @param tag The {@link CompoundTag} to send with the packet + * @since 0.15.3.3 + */ + public S2CMetarPacket(CompoundTag tag) { + super(tag); + } + + /** + * Gets the {@link MetarClientStorage} that is receiving data + * @return The {@link MetarClientStorage} + * @since 0.15.3.3 + */ + @Override + public MetarClientStorage getStorage() { + return PMWClientStorages.metars().get(); + } + + @Override + public Type type() { + return TYPE; + } +} diff --git a/src/main/java/net/nullved/pmweatherapi/network/S2CRadarPacket.java b/src/main/java/net/nullved/pmweatherapi/network/S2CRadarPacket.java new file mode 100644 index 0000000..e65ddb6 --- /dev/null +++ b/src/main/java/net/nullved/pmweatherapi/network/S2CRadarPacket.java @@ -0,0 +1,43 @@ +package net.nullved.pmweatherapi.network; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.nullved.pmweatherapi.PMWeatherAPI; +import net.nullved.pmweatherapi.client.data.PMWClientStorages; +import net.nullved.pmweatherapi.client.radar.RadarClientStorage; + +/** + * The packet that syncs radars from the server to the client, using the Storages system + * @since 0.15.3.3 + */ +public class S2CRadarPacket extends S2CStoragePacket { + public static final CustomPacketPayload.Type TYPE = new Type<>(PMWeatherAPI.rl("s2c_radar")); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite(ByteBufCodecs.COMPOUND_TAG, S2CRadarPacket::tag, S2CRadarPacket::new); + + /** + * Creates a new {@link S2CRadarPacket} + * @param tag The {@link CompoundTag} to send with the packet + * @since 0.15.3.3 + */ + public S2CRadarPacket(CompoundTag tag) { + super(tag); + } + + /** + * Gets the {@link RadarClientStorage} that is receiving data + * @return The {@link RadarClientStorage} + * @since 0.15.3.3 + */ + @Override + public RadarClientStorage getStorage() { + return PMWClientStorages.radars().get(); + } + + @Override + public Type type() { + return TYPE; + } +} diff --git a/src/main/java/net/nullved/pmweatherapi/network/S2CRadarsPacket.java b/src/main/java/net/nullved/pmweatherapi/network/S2CRadarsPacket.java deleted file mode 100644 index d0a4d34..0000000 --- a/src/main/java/net/nullved/pmweatherapi/network/S2CRadarsPacket.java +++ /dev/null @@ -1,72 +0,0 @@ -package net.nullved.pmweatherapi.network; - -import net.minecraft.nbt.CompoundTag; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.network.RegistryFriendlyByteBuf; -import net.minecraft.network.codec.ByteBufCodecs; -import net.minecraft.network.codec.StreamCodec; -import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -import net.minecraft.world.entity.player.Player; -import net.nullved.pmweatherapi.PMWeatherAPI; -import net.nullved.pmweatherapi.client.radar.RadarClientStorage; -import net.nullved.pmweatherapi.client.data.PMWClientStorages; - -/** - * A packet for sending radar information from Server -> Client (S2C) - * @param tag The {@link CompoundTag} to send - * @since 0.14.15.3 - */ -public record S2CRadarsPacket(CompoundTag tag) implements CustomPacketPayload { - public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(PMWeatherAPI.rl("s2c_radars")); - public static final StreamCodec STREAM_CODEC = StreamCodec.composite(ByteBufCodecs.COMPOUND_TAG, S2CRadarsPacket::tag, S2CRadarsPacket::new); - - /** - * Creates a new {@link S2CRadarsPacket} from a {@link RegistryFriendlyByteBuf} - * @param buf The {@link RegistryFriendlyByteBuf} to read a {@link CompoundTag} from - * @since 0.14.15.3 - */ - public S2CRadarsPacket(RegistryFriendlyByteBuf buf) { - this(buf.readNbt()); - } - - /** - * Writes the data into the given {@link FriendlyByteBuf} - * @param buf The {@link FriendlyByteBuf} to write into - * @since 0.14.15.3 - */ - public void write(FriendlyByteBuf buf) { - buf.writeNbt(tag); - } - - /** - * Handles the packet on the CLIENT SIDE - * @param player The player the packet was sent to - * @since 0.14.15.3 - */ - public void handle(Player player) { - try { - String operation = tag.getString("operation"); - RadarClientStorage radarStorage = PMWClientStorages.getRadars(); - - if (operation.equals("add")) { - radarStorage.syncAdd(tag); - } else if (operation.equals("remove")) { - radarStorage.syncRemove(tag); - } else { - PMWeatherAPI.LOGGER.error("Unknown S2CRadarsPacket operation: {}", operation); - } - } catch (Exception e) { - PMWeatherAPI.LOGGER.error("An error occurred when trying to write packet", e); - } - } - - /** - * Gets the packet type - * @return The packet type - * @since 0.14.15.3 - */ - @Override - public Type type() { - return TYPE; - } -} diff --git a/src/main/java/net/nullved/pmweatherapi/network/S2CStoragePacket.java b/src/main/java/net/nullved/pmweatherapi/network/S2CStoragePacket.java new file mode 100644 index 0000000..2ddc217 --- /dev/null +++ b/src/main/java/net/nullved/pmweatherapi/network/S2CStoragePacket.java @@ -0,0 +1,81 @@ +package net.nullved.pmweatherapi.network; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.world.entity.player.Player; +import net.nullved.pmweatherapi.PMWeatherAPI; +import net.nullved.pmweatherapi.client.data.IClientStorage; + +/** + * A base packet for the Storages system that syncs data from the Server -> Client (S2C) + * @param The {@link IClientStorage} that will be synced to + * @since 0.15.3.3 + */ +public abstract class S2CStoragePacket> implements CustomPacketPayload { + public CompoundTag tag; + + /** + * Gets the {@link IClientStorage} that will be synced to. + * @return The storage to be synced. + * @since 0.15.3.3 + */ + public abstract C getStorage(); + + public S2CStoragePacket(CompoundTag tag) { + this.tag = tag; + } + + /** + * Creates a new {@link S2CStoragePacket} from a {@link RegistryFriendlyByteBuf} + * @param buf The {@link RegistryFriendlyByteBuf} to read a {@link CompoundTag} from + * @since 0.15.3.3 + */ + public S2CStoragePacket(RegistryFriendlyByteBuf buf) { + this(buf.readNbt()); + } + + /** + * Gets the {@link CompoundTag} send with the packet + * @return The packet data + * @since 0.15.3.3 + */ + public CompoundTag tag() { + return tag; + } + + /** + * Writes the data into the given {@link FriendlyByteBuf} + * @param buf The {@link FriendlyByteBuf} to write into + * @since 0.15.3.3 + */ + public void write(FriendlyByteBuf buf) { + tag.putInt("version", getStorage().version()); + buf.writeNbt(tag); + } + + /** + * Handles the packet on the CLIENT SIDE + * @param player The player the packet was sent to + * @since 0.15.3.3 + */ + public void handle(Player player) { + try { + String operation = tag.getString("operation"); + C storage = getStorage(); + + if (operation.equals("overwrite")) { + storage.syncAll(tag); + } else if (operation.equals("add")) { + storage.syncAdd(tag); + } else if (operation.equals("remove")) { + storage.syncRemove(tag); + } else { + PMWeatherAPI.LOGGER.error("Unknown S2CRadarsPacket operation: {}", operation); + } + } catch (Exception e) { + PMWeatherAPI.LOGGER.error("An error occurred when trying to write packet", e); + } + } +} diff --git a/src/main/java/net/nullved/pmweatherapi/network/S2CWSRPacket.java b/src/main/java/net/nullved/pmweatherapi/network/S2CWSRPacket.java new file mode 100644 index 0000000..24591fd --- /dev/null +++ b/src/main/java/net/nullved/pmweatherapi/network/S2CWSRPacket.java @@ -0,0 +1,44 @@ +package net.nullved.pmweatherapi.network; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.nullved.pmweatherapi.PMWeatherAPI; +import net.nullved.pmweatherapi.client.data.PMWClientStorages; +import net.nullved.pmweatherapi.client.metar.MetarClientStorage; +import net.nullved.pmweatherapi.client.radar.WSRClientStorage; + +/** + * The packet that syncs wsrs from the server to the client, using the Storages system + * @since 0.15.3.3 + */ +public class S2CWSRPacket extends S2CStoragePacket { + public static final Type TYPE = new Type<>(PMWeatherAPI.rl("s2c_wsr")); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite(ByteBufCodecs.COMPOUND_TAG, S2CWSRPacket::tag, S2CWSRPacket::new); + + /** + * Creates a new {@link S2CWSRPacket} + * @param tag The {@link CompoundTag} to send with the packet + * @since 0.15.3.3 + */ + public S2CWSRPacket(CompoundTag tag) { + super(tag); + } + + /** + * Gets the {@link WSRClientStorage} that is receiving data + * @return The {@link WSRClientStorage} + * @since 0.15.3.3 + */ + @Override + public WSRClientStorage getStorage() { + return PMWClientStorages.wsrs().get(); + } + + @Override + public Type type() { + return TYPE; + } +} diff --git a/src/main/java/net/nullved/pmweatherapi/radar/NearbyRadars.java b/src/main/java/net/nullved/pmweatherapi/radar/NearbyRadars.java index 91886c1..16aacad 100644 --- a/src/main/java/net/nullved/pmweatherapi/radar/NearbyRadars.java +++ b/src/main/java/net/nullved/pmweatherapi/radar/NearbyRadars.java @@ -10,6 +10,9 @@ import net.neoforged.api.distmarker.OnlyIn; import net.nullved.pmweatherapi.client.data.PMWClientStorages; import net.nullved.pmweatherapi.data.PMWStorages; +import net.nullved.pmweatherapi.radar.storage.RadarStorage; +import net.nullved.pmweatherapi.radar.storage.RadarStorageData; +import net.nullved.pmweatherapi.storage.data.BlockPosData; import java.util.HashMap; import java.util.HashSet; @@ -40,7 +43,7 @@ private NearbyRadars(RadarStorage storage) { */ @OnlyIn(Dist.CLIENT) public static NearbyRadars client() { - return new NearbyRadars(PMWClientStorages.getRadars()); + return new NearbyRadars(PMWClientStorages.radars().get()); } /** @@ -50,7 +53,7 @@ public static NearbyRadars client() { * @since 0.14.15.3 */ public static NearbyRadars get(ResourceKey dim) { - return DIMENSION_MAP.computeIfAbsent(dim, d -> new NearbyRadars(PMWStorages.getRadar(d))); + return DIMENSION_MAP.computeIfAbsent(dim, d -> new NearbyRadars(PMWStorages.radars().get(d))); } /** @@ -73,8 +76,8 @@ public static NearbyRadars get(Level level) { public Set radarsNearBlock(BlockPos pos, double radius) { Set radarList = new HashSet<>(); - for (BlockPos radar: storage.getAllRadars()) { - if (radar.distToCenterSqr(pos.getX(), pos.getY(), pos.getZ()) <= radius * radius) radarList.add(radar); + for (RadarStorageData radar: storage.getAll()) { + if (Math.abs(radar.getPos().distToCenterSqr(pos.getX(), pos.getY(), pos.getZ())) <= radius * radius) radarList.add(radar.getPos()); } radarList.remove(pos); @@ -92,8 +95,8 @@ public Set radarsNearBlock(BlockPos pos, double radius) { public Set radarsNearChunk(ChunkPos pos, double radius) { Set radarList = new HashSet<>(); - for (BlockPos radar: storage.getAllRadars()) { - if (radar.distToCenterSqr(pos.getMiddleBlockX(), radar.getY(), pos.getMiddleBlockZ()) <= radius * radius) radarList.add(radar); + for (RadarStorageData radar: storage.getAll()) { + if (Math.abs(radar.getPos().distToCenterSqr(pos.getMiddleBlockX(), radar.getPos().getY(), pos.getMiddleBlockZ())) <= radius * radius) radarList.add(radar.getPos()); } return radarList; @@ -109,8 +112,8 @@ public Set radarsNearChunk(ChunkPos pos, double radius) { public Set radarsNearPlayer(Player player, double radius) { Set radarList = new HashSet<>(); - for (BlockPos radar: storage.getAllRadars()) { - if (radar.distToCenterSqr(player.getX(), player.getY(), player.getZ()) <= radius * radius) radarList.add(radar); + for (RadarStorageData radar: storage.getAll()) { + if (Math.abs(radar.getPos().distToCenterSqr(player.getX(), player.getY(), player.getZ())) <= radius * radius) radarList.add(radar.getPos()); } return radarList; diff --git a/src/main/java/net/nullved/pmweatherapi/radar/RadarMode.java b/src/main/java/net/nullved/pmweatherapi/radar/RadarMode.java index af90357..17f9516 100644 --- a/src/main/java/net/nullved/pmweatherapi/radar/RadarMode.java +++ b/src/main/java/net/nullved/pmweatherapi/radar/RadarMode.java @@ -42,6 +42,7 @@ */ public class RadarMode implements StringRepresentable, Comparable { private static final LinkedHashMap MODES = new LinkedHashMap<>(); + private static boolean disableBaseRendering = false; /** * A "Null" Radar Mode mimicking Minecraft's missing texture. @@ -57,7 +58,7 @@ public class RadarMode implements StringRepresentable, Comparable { * A Radar Mode that is a copy of PMWeather's Reflectivity * @since 0.14.15.6 */ - public static final RadarMode REFLECTIVITY = create(PMWeather.getPath("reflectivity"), prd -> { + public static final RadarMode REFLECTIVITY = create(PMWeather.getPath("reflectivity"), prd -> { Holder biome = ((RadarBlockEntity) prd.renderData().blockEntity()).getNearestBiome(new BlockPos((int) prd.worldPos().x, (int) prd.worldPos().y, (int) prd.worldPos().z)); if (biome != null) return ColorMaps.REFLECTIVITY.getWithBiome(prd.rdbz(), biome, prd.worldPos()); else return ColorMaps.REFLECTIVITY.get(prd.rdbz()); @@ -101,6 +102,24 @@ private RadarMode(ResourceLocation id, Function colorFun this.dotColor = dotColor; } + /** + * Disables all rendering of pixels from any radar mode + * @param disable Whether to disable rendering or not + * @since 0.15.3.3 + */ + public static void disableBaseRendering(boolean disable) { + disableBaseRendering = disable; + } + + /** + * Returns whether base rendering is disabled or not + * @return Base rendering disable state + * @since 0.15.3.3 + */ + public static boolean isBaseRenderingDisabled() { + return disableBaseRendering; + } + /** * Create a new {@link RadarMode} * @param id The {@link ResourceLocation} of this radar mode diff --git a/src/main/java/net/nullved/pmweatherapi/radar/RadarServerStorage.java b/src/main/java/net/nullved/pmweatherapi/radar/RadarServerStorage.java deleted file mode 100644 index 58d486a..0000000 --- a/src/main/java/net/nullved/pmweatherapi/radar/RadarServerStorage.java +++ /dev/null @@ -1,131 +0,0 @@ -package net.nullved.pmweatherapi.radar; - -import net.minecraft.core.BlockPos; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.ListTag; -import net.minecraft.nbt.NbtUtils; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.entity.player.Player; -import net.nullved.pmweatherapi.network.PMWNetworking; -import net.nullved.pmweatherapi.data.PMWStorages; - -import java.util.Collection; - -/** - * A Radar Storage for the server side. - * There is a {@code RadarServerStorage} for each dimension of a save - *
- * You should not create a {@code RadarServerStorage}, instead, use {@link PMWStorages#getRadar}. - * @since 0.14.15.3 - */ -public class RadarServerStorage extends RadarStorage { - private final ServerLevel level; - - /** - * DO NOT CALL THIS CONSTRUCTOR!!! - *
- * Get a radar storage from {@link PMWStorages#getRadar} - * @param level The level to create this storage for - * @since 0.14.15.3 - */ - public RadarServerStorage(ServerLevel level) { - super(level.dimension()); - this.level = level; - } - - /** - * Gets the level associated with this {@code RadarServerStorage} - * @return A {@link ServerLevel} - * @since 0.14.15.3 - */ - @Override - public ServerLevel getLevel() { - return level; - } - - /** - * Syncs all radars to the given player - * @param player The {@link Player} to sync all radars to - * @since 0.14.15.3 - */ - public void syncAllToPlayer(Player player) { - CompoundTag tag = new CompoundTag(); - tag.putString("operation", "add"); - tag.putBoolean("list", true); - - ListTag list = new ListTag(); - for (BlockPos pos: getAllRadars()) { - list.add(NbtUtils.writeBlockPos(pos)); - } - - tag.put("data", list); - - PMWNetworking.serverSendRadarsToPlayer(tag, player); - } - - /** - * Syncs a new radar to all clients - * @param pos The {@link BlockPos} of the new radar - * @since 0.14.15.3 - */ - public void syncAdd(BlockPos pos) { - CompoundTag tag = new CompoundTag(); - tag.putString("operation", "add"); - tag.put("data", NbtUtils.writeBlockPos(pos)); - - PMWNetworking.serverSendRadarsToAll(tag); - } - - /** - * Syncs multiple new radars to all clients - * @param posList A {@link Collection} of {@link BlockPos} to sync - * @since 0.14.15.3 - */ - public void syncAdd(Collection posList) { - CompoundTag tag = new CompoundTag(); - tag.putString("operation", "add"); - tag.putBoolean("list", true); - - ListTag list = new ListTag(); - for (BlockPos pos: posList) { - list.add(NbtUtils.writeBlockPos(pos)); - } - - tag.put("data", list); - - PMWNetworking.serverSendRadarsToAll(tag); - } - - /** - * Syncs a radar removal to all clients - * @param pos The {@link BlockPos} of the radar to remove - * @since 0.14.15.3 - */ - public void syncRemove(BlockPos pos) { - CompoundTag tag = new CompoundTag(); - tag.putString("operation", "remove"); - tag.put("data", NbtUtils.writeBlockPos(pos)); - - PMWNetworking.serverSendRadarsToAll(tag); - } - - /** - * Syncs multiple radar removals to all clients - * @param posList A {@link Collection} of {@link BlockPos} to sync - * @since 0.14.15.3 - */ - public void syncRemove(Collection posList) { - CompoundTag tag = new CompoundTag(); - tag.putString("operation", "remove"); - tag.putBoolean("list", true); - - ListTag list = new ListTag(); - for (BlockPos pos: posList) { - list.add(NbtUtils.writeBlockPos(pos)); - } - - tag.put("data", list); - - PMWNetworking.serverSendRadarsToAll(tag); - } -} diff --git a/src/main/java/net/nullved/pmweatherapi/radar/RadarStorage.java b/src/main/java/net/nullved/pmweatherapi/radar/RadarStorage.java deleted file mode 100644 index 8a439e6..0000000 --- a/src/main/java/net/nullved/pmweatherapi/radar/RadarStorage.java +++ /dev/null @@ -1,240 +0,0 @@ -package net.nullved.pmweatherapi.radar; - - -import net.minecraft.core.BlockPos; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.ListTag; -import net.minecraft.nbt.NbtUtils; -import net.minecraft.resources.ResourceKey; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.level.ChunkPos; -import net.minecraft.world.level.Level; -import net.nullved.pmweatherapi.PMWeatherAPI; -import net.nullved.pmweatherapi.data.PMWSavedData; - -import java.util.*; -import java.util.stream.Collectors; - -/** - * Saves all the radars to a file to be saved and loaded from - */ -public abstract class RadarStorage { - private Map> radars = new HashMap<>(); - private Map checkTimes = new HashMap<>(); - private ResourceKey dimension; - - public abstract Level getLevel(); - - public RadarStorage(ResourceKey dimension) { - this.dimension = dimension; - } - - public Set getAllRadars() { - return radars.values().stream().flatMap(Collection::stream).collect(Collectors.toSet()); - } - - public Set getRadarsInChunk(ChunkPos pos) { - return radars.getOrDefault(pos, Set.of()); - } - - public boolean shouldRecalculate(ChunkPos pos) { - if (!checkTimes.containsKey(pos)) { - checkTimes.put(pos, System.currentTimeMillis()); - return true; - } - - return checkTimes.get(pos) - System.currentTimeMillis() > 30000L; - } - - public void addRadar(BlockPos pos) { - ChunkPos chunkPos = new ChunkPos(pos); - Set set = radars.computeIfAbsent(chunkPos, c -> new HashSet<>()); - set.add(pos); - radars.put(chunkPos, set); - } - - public void addRadars(Collection pos) { - pos.forEach(this::addRadar); - } - - public void removeRadar(BlockPos pos) { - ChunkPos chunkPos = new ChunkPos(pos); - Set set = radars.computeIfAbsent(chunkPos, c -> new HashSet<>()); - set.remove(pos); - radars.put(chunkPos, set); - } - - public void removeRadars(Collection pos) { - pos.forEach(this::removeRadar); - } - - public CompoundTag save(CompoundTag tag) { - PMWeatherAPI.LOGGER.info("Saving radars to level..."); - tag.putInt("version", 1); - tag.putLong("saveTime", System.currentTimeMillis()); - - for (Map.Entry> entry : radars.entrySet()) { - ListTag blockList = new ListTag(); - entry.getValue().forEach(blockPos -> blockList.add(NbtUtils.writeBlockPos(blockPos))); - tag.put(String.valueOf(entry.getKey().toLong()), blockList); - } - - PMWeatherAPI.LOGGER.info("Saved radars to level"); - return tag; - } - - public void read() { - PMWSavedData savedData = ((ServerLevel) this.getLevel()).getDataStorage().computeIfAbsent(PMWSavedData.factory(), "pmweatherapi_radars"); - savedData.setRadarStorage(this); - PMWeatherAPI.LOGGER.info("Reading radars from level..."); - CompoundTag data = savedData.getTag(); - Set chunks = data.getAllKeys(); - chunks.removeAll(Set.of("version", "saveTime")); - - for (String chunk: chunks) { - Set radars = new HashSet<>(); - - ListTag blockList = (ListTag) data.get(chunk); - for (int i = 0; i < blockList.size(); i++) { - int[] bp = blockList.getIntArray(i); - radars.add(new BlockPos(bp[0], bp[1], bp[2])); - } - - this.radars.put(new ChunkPos(Long.parseLong(chunk)), radars); - } - } - -// private static final String DATA_FOLDER = "data/pmweatherapi"; -// private static final String DATA_FILE = "radars.json"; -// private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); -// -// /** -// * Reads all radars from a save or server -// *
-// * For each dimension ({@link ResourceKey}), there is a {@link Map} mapping a {@link ChunkPos} to all the saved radar's {@link BlockPos}'s -// * @return All saved radars across all dimensions -// */ -// public static Map, Map>> readAllRadars() { -// Radars.clear(); -// Map, Map>> dimensionToRadar = new HashMap<>(); -// -// try { -// Path worldDir = getWorldSaveDirectory(); -// Path modDir = worldDir.resolve(DATA_FOLDER); -// Path file = modDir.resolve(DATA_FILE); -// -// if (!file.toFile().exists()) { -// Radars.clear(); -// PMWeatherAPI.LOGGER.warn("No radars file for this save!"); -// return dimensionToRadar; -// } -// -// JsonObject root; -// try (FileReader fr = new FileReader(file.toFile())) { -// root = JsonParser.parseReader(fr).getAsJsonObject(); -// } catch (IOException e) { -// PMWeatherAPI.LOGGER.warn("Failed to read radars.json! Error:", e); -// return dimensionToRadar; -// } -// -// int version = root.has("version") ? root.get("version").getAsInt() : 1; -// if (version > 1) { -// PMWeatherAPI.LOGGER.info("Radars file has newer version {} than supported (1), attemping load regardless", version); -// } -// -// root.entrySet() -// .stream() -// .filter(e -> !e.getKey().equals("version") && !e.getKey().equals("saveTime")) -// .forEach(e -> { -// ResourceKey dim = ResourceKey.create(Registries.DIMENSION, ResourceLocation.parse(e.getKey())); -// Map> radarMap = readRadarsForDimension(new HashMap<>(), e.getValue()); -// dimensionToRadar.put(dim, radarMap); -// }); -// PMWeatherAPI.LOGGER.info("Loaded saved radar data!"); -// } catch (Exception e) { -// PMWeatherAPI.LOGGER.warn("Unable to load saved radar data! Error:", e); -// } -// -// return dimensionToRadar; -// } -// -// /** -// * Saves all radars to data/pmweatherapi/radars.json -// */ -// public static void saveAllRadars() { -// try { -// Path worldDir = getWorldSaveDirectory(); -// Path modDir = worldDir.resolve(DATA_FOLDER); -// Files.createDirectories(modDir); -// Path file = modDir.resolve(DATA_FILE); -// -// JsonObject root = new JsonObject(); -// root.addProperty("version", 1); -// root.addProperty("saveTime", System.currentTimeMillis()); -// -// for (Map.Entry, Radars> radars: Radars.getAllDimensions().entrySet()) { -// root.add(radars.getKey().location().toString(), saveRadarsForDimension(radars.getKey(), radars.getValue())); -// } -// -// Path tmp = modDir.resolve(DATA_FILE + ".tmp"); -// -// try (FileWriter writer = new FileWriter(tmp.toFile())) { -// GSON.toJson(root, writer); -// writer.flush(); -// } -// -// Files.move(tmp, file, StandardCopyOption.REPLACE_EXISTING); -// PMWeatherAPI.LOGGER.info("Saved radars for all dimensions to {}/{}", DATA_FOLDER, DATA_FILE); -// } catch (IOException e) { -// PMWeatherAPI.LOGGER.warn("Failed to save radars.json! Error:", e); -// } -// } -// -// protected static JsonObject saveRadarsForDimension(ResourceKey dimension, Radars radars) { -// JsonObject root = new JsonObject(); -// Map> radarMap = radars.getRadarMap(); -// -// for (Map.Entry> entry: radarMap.entrySet()) { -// if (entry.getValue().isEmpty()) continue; -// JsonArray array = new JsonArray(); -// -// for (BlockPos pos: entry.getValue()) { -// JsonObject obj = new JsonObject(); -// obj.addProperty("x", pos.getX()); -// obj.addProperty("y", pos.getY()); -// obj.addProperty("z", pos.getZ()); -// array.add(obj); -// } -// -// root.add(String.valueOf(entry.getKey().toLong()), array); -// } -// -// return root; -// } -// -// protected static Map> readRadarsForDimension(Map> radarMap, JsonElement json) { -// if (json.isJsonObject()) { -// JsonObject root = json.getAsJsonObject(); -// -// for (Map.Entry entry: root.entrySet()) { -// JsonArray array = entry.getValue().getAsJsonArray(); -// if (array.isEmpty()) continue; -// -// ChunkPos chunkPos = new ChunkPos(Long.parseLong(entry.getKey())); -// Set radars = new HashSet<>(); -// -// for (JsonElement e: array) { -// JsonObject obj = e.getAsJsonObject(); -// int x = obj.get("x").getAsInt(); -// int y = obj.get("y").getAsInt(); -// int z = obj.get("z").getAsInt(); -// radars.add(new BlockPos(x, y, z)); -// } -// -// radarMap.put(chunkPos, radars); -// } -// } -// -// return radarMap; -// } -} diff --git a/src/main/java/net/nullved/pmweatherapi/radar/storage/RadarServerStorage.java b/src/main/java/net/nullved/pmweatherapi/radar/storage/RadarServerStorage.java new file mode 100644 index 0000000..f94cfae --- /dev/null +++ b/src/main/java/net/nullved/pmweatherapi/radar/storage/RadarServerStorage.java @@ -0,0 +1,49 @@ +package net.nullved.pmweatherapi.radar.storage; + +import dev.protomanly.pmweather.block.RadarBlock; +import dev.protomanly.pmweather.multiblock.wsr88d.WSR88DCore; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerLevel; +import net.nullved.pmweatherapi.client.data.IClientStorage; +import net.nullved.pmweatherapi.client.radar.RadarClientStorage; +import net.nullved.pmweatherapi.client.radar.WSRClientStorage; +import net.nullved.pmweatherapi.data.PMWStorages; +import net.nullved.pmweatherapi.network.S2CRadarPacket; +import net.nullved.pmweatherapi.network.S2CStoragePacket; +import net.nullved.pmweatherapi.storage.IServerStorage; +import net.nullved.pmweatherapi.storage.ISyncServerStorage; + +/** + * {@link IServerStorage} for {@link RadarBlock}s + *

+ * You should not create a {@link RadarServerStorage}, instead, use {@link PMWStorages#radars()} + * + * @since 0.15.3.3 + * @see RadarStorage + * @see RadarClientStorage + */ +public class RadarServerStorage extends RadarStorage implements ISyncServerStorage { + private final ServerLevel level; + + /** + * DO NOT CALL THIS CONSTRUCTOR!!! + *
+ * Get a radar storage from {@link PMWStorages#radars()} + * @param level The level to create this storage for + * @since 0.15.3.3 + */ + public RadarServerStorage(ServerLevel level) { + super(level.dimension()); + this.level = level; + } + + @Override + public ServerLevel getLevel() { + return level; + } + + @Override + public S2CStoragePacket> packet(CompoundTag tag) { + return new S2CRadarPacket(tag); + } +} diff --git a/src/main/java/net/nullved/pmweatherapi/radar/storage/RadarStorage.java b/src/main/java/net/nullved/pmweatherapi/radar/storage/RadarStorage.java new file mode 100644 index 0000000..f06fe02 --- /dev/null +++ b/src/main/java/net/nullved/pmweatherapi/radar/storage/RadarStorage.java @@ -0,0 +1,45 @@ +package net.nullved.pmweatherapi.radar.storage; + + +import dev.protomanly.pmweather.block.RadarBlock; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.Level; +import net.nullved.pmweatherapi.PMWeatherAPI; +import net.nullved.pmweatherapi.client.radar.RadarClientStorage; +import net.nullved.pmweatherapi.storage.IStorage; +import net.nullved.pmweatherapi.storage.PMWStorage; + +/** + * {@link PMWStorage} for {@link RadarBlock}s + * + * @since 0.15.3.3 + * @see PMWStorage + * @see RadarServerStorage + * @see RadarClientStorage + */ +public abstract class RadarStorage extends PMWStorage { + public static final int VERSION = 2; + public static final ResourceLocation ID = PMWeatherAPI.rl("radars"); + + public RadarStorage(ResourceKey dimension) { + super(dimension); + } + + public abstract Level getLevel(); + + @Override + public ResourceLocation getId() { + return ID; + } + + @Override + public ResourceLocation getExpectedDataType() { + return RadarStorageData.ID; + } + + @Override + public int version() { + return VERSION; + } +} diff --git a/src/main/java/net/nullved/pmweatherapi/radar/storage/RadarStorageData.java b/src/main/java/net/nullved/pmweatherapi/radar/storage/RadarStorageData.java new file mode 100644 index 0000000..de46a96 --- /dev/null +++ b/src/main/java/net/nullved/pmweatherapi/radar/storage/RadarStorageData.java @@ -0,0 +1,50 @@ +package net.nullved.pmweatherapi.radar.storage; + +import dev.protomanly.pmweather.block.RadarBlock; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtUtils; +import net.minecraft.resources.ResourceLocation; +import net.nullved.pmweatherapi.PMWeatherAPI; +import net.nullved.pmweatherapi.radar.RadarMode; +import net.nullved.pmweatherapi.storage.data.StorageData; + +/** + * {@link StorageData} for {@link RadarBlock}s. + * Includes position and radar mode data + * + * @since 0.15.3.3 + * @see StorageData + */ +public class RadarStorageData extends StorageData { + public static final ResourceLocation ID = PMWeatherAPI.rl("radar"); + private RadarMode radarMode; + + public RadarStorageData(BlockPos pos, RadarMode radarMode) { + super(pos); + this.radarMode = radarMode; + } + + @Override + public ResourceLocation getId() { + return ID; + } + + @Override + public CompoundTag serializeToNBT() { + CompoundTag tag = super.serializeToNBT(); + tag.putString("radar_mode", radarMode.getSerializedName()); + return tag; + } + + public static RadarStorageData deserializeFromNBT(CompoundTag tag, int version) { + BlockPos bp = deserializeBlockPos(tag); + + if (bp != null) { + RadarMode mode = RadarMode.get(tag.getString("radar_mode")); + return new RadarStorageData(bp, mode); + } else { + return new RadarStorageData(NbtUtils.readBlockPos(tag, "").orElseThrow(() -> new IllegalArgumentException("Could not read BlockPos in RadarStorageData!")), RadarMode.get(tag.getString("radar_mode"))); + } + } +} diff --git a/src/main/java/net/nullved/pmweatherapi/radar/storage/WSRServerStorage.java b/src/main/java/net/nullved/pmweatherapi/radar/storage/WSRServerStorage.java new file mode 100644 index 0000000..31b453d --- /dev/null +++ b/src/main/java/net/nullved/pmweatherapi/radar/storage/WSRServerStorage.java @@ -0,0 +1,48 @@ +package net.nullved.pmweatherapi.radar.storage; + +import dev.protomanly.pmweather.multiblock.wsr88d.WSR88DCore; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerLevel; +import net.nullved.pmweatherapi.client.data.IClientStorage; +import net.nullved.pmweatherapi.client.radar.WSRClientStorage; +import net.nullved.pmweatherapi.data.PMWStorages; +import net.nullved.pmweatherapi.network.S2CRadarPacket; +import net.nullved.pmweatherapi.network.S2CStoragePacket; +import net.nullved.pmweatherapi.network.S2CWSRPacket; +import net.nullved.pmweatherapi.storage.IServerStorage; +import net.nullved.pmweatherapi.storage.ISyncServerStorage; + +/** + * {@link IServerStorage} for {@link WSR88DCore}s + *

+ * You should not create a {@link WSRServerStorage}, instead, use {@link PMWStorages#wsrs()} + * + * @since 0.15.3.3 + * @see WSRStorage + * @see WSRClientStorage + */ +public class WSRServerStorage extends WSRStorage implements ISyncServerStorage { + private final ServerLevel level; + + /** + * DO NOT CALL THIS CONSTRUCTOR!!! + *
+ * Get a radar storage from {@link PMWStorages#wsrs()} + * @param level The level to create this storage for + * @since 0.15.3.3 + */ + public WSRServerStorage(ServerLevel level) { + super(level.dimension()); + this.level = level; + } + + @Override + public ServerLevel getLevel() { + return level; + } + + @Override + public S2CStoragePacket> packet(CompoundTag tag) { + return new S2CWSRPacket(tag); + } +} diff --git a/src/main/java/net/nullved/pmweatherapi/radar/storage/WSRStorage.java b/src/main/java/net/nullved/pmweatherapi/radar/storage/WSRStorage.java new file mode 100644 index 0000000..b714554 --- /dev/null +++ b/src/main/java/net/nullved/pmweatherapi/radar/storage/WSRStorage.java @@ -0,0 +1,41 @@ +package net.nullved.pmweatherapi.radar.storage; + +import dev.protomanly.pmweather.multiblock.wsr88d.WSR88DCore; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.Level; +import net.nullved.pmweatherapi.PMWeatherAPI; +import net.nullved.pmweatherapi.client.radar.WSRClientStorage; +import net.nullved.pmweatherapi.storage.PMWStorage; + +/** + * {@link PMWStorage} for {@link WSR88DCore}s + * + * @since 0.15.3.3 + * @see PMWStorage + * @see WSRServerStorage + * @see WSRClientStorage + */ +public abstract class WSRStorage extends PMWStorage { + public static final int VERSION = 1; + public static final ResourceLocation ID = PMWeatherAPI.rl("wsrs"); + + public WSRStorage(ResourceKey dimension) { + super(dimension); + } + + @Override + public ResourceLocation getId() { + return ID; + } + + @Override + public ResourceLocation getExpectedDataType() { + return WSRStorageData.ID; + } + + @Override + public int version() { + return VERSION; + } +} diff --git a/src/main/java/net/nullved/pmweatherapi/radar/storage/WSRStorageData.java b/src/main/java/net/nullved/pmweatherapi/radar/storage/WSRStorageData.java new file mode 100644 index 0000000..4185fd8 --- /dev/null +++ b/src/main/java/net/nullved/pmweatherapi/radar/storage/WSRStorageData.java @@ -0,0 +1,50 @@ +package net.nullved.pmweatherapi.radar.storage; + +import dev.protomanly.pmweather.multiblock.wsr88d.WSR88DCore; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; +import net.nullved.pmweatherapi.PMWeatherAPI; +import net.nullved.pmweatherapi.storage.data.StorageData; + +/** + * {@link StorageData} for {@link WSR88DCore}s. + * Includes position and completion data + * + * @since 0.15.3.3 + * @see StorageData + */ +public class WSRStorageData extends StorageData { + public static final ResourceLocation ID = PMWeatherAPI.rl("wsr"); + private final boolean completed; + + public WSRStorageData(BlockPos pos, boolean completed) { + super(pos); + this.completed = completed; + } + + public boolean isCompleted() { + return completed; + } + + @Override + public ResourceLocation getId() { + return ID; + } + + @Override + public CompoundTag serializeToNBT() { + CompoundTag tag = super.serializeToNBT(); + tag.putBoolean("completed", completed); + return tag; + } + + public static WSRStorageData deserializeFromNBT(CompoundTag tag, int version) { + BlockPos bp = deserializeBlockPos(tag); + if (bp != null) { + boolean completed = tag.getBoolean("completed"); + return new WSRStorageData(bp, completed); + } + else throw new IllegalArgumentException("Could not read BlockPos in WSRStorageData!"); + } +} diff --git a/src/main/java/net/nullved/pmweatherapi/storage/IServerStorage.java b/src/main/java/net/nullved/pmweatherapi/storage/IServerStorage.java new file mode 100644 index 0000000..aeb89d6 --- /dev/null +++ b/src/main/java/net/nullved/pmweatherapi/storage/IServerStorage.java @@ -0,0 +1,170 @@ +package net.nullved.pmweatherapi.storage; + +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.NbtUtils; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.player.Player; +import net.nullved.pmweatherapi.client.data.IClientStorage; +import net.nullved.pmweatherapi.data.PMWStorages; +import net.nullved.pmweatherapi.network.PMWNetworking; +import net.nullved.pmweatherapi.network.S2CStoragePacket; +import net.nullved.pmweatherapi.storage.data.IStorageData; +import net.nullved.pmweatherapi.storage.data.StorageData; + +import java.util.Collection; + +/** + * The Server Storage interface. + * There is a {@link IServerStorage} for each dimension of a save + *
+ * You should only create a {@link IServerStorage} for a level once, instead, use {@link PMWStorages} if built-in or a custom storage handler. + * @since 0.15.3.3 + */ +public interface IServerStorage extends IStorage { + /** + * Gets the level associated with this {@link IServerStorage} + * @return A {@link ServerLevel} + * @since 0.15.3.3 + */ + ServerLevel getLevel(); + + /** + * Generates a {@link S2CStoragePacket} to be sent to the client + * @param tag The {@link CompoundTag} to be sent + * @return A {@link S2CStoragePacket} instance + * @since 0.15.3.3 + */ + S2CStoragePacket> packet(CompoundTag tag); + + /** + * Syncs all {@link IStorageData} to all players + * @since 0.15.3.3 + */ + default void syncAllToAll() { + CompoundTag tag = new CompoundTag(); + tag.putString("operation", "overwrite"); + tag.putBoolean("list", true); + + ListTag list = new ListTag(); + getAll().forEach(data -> list.add(data.serializeToNBT())); + + tag.put("data", list); + + PMWNetworking.serverSendStorageToAll(tag, this::packet); + } + + /** + * Syncs all {@link IStorageData} from the storage to the given player + * @param player The {@link Player} to sync all data to + * @since 0.15.3.3 + */ + default void syncAllToPlayer(Player player) { + CompoundTag tag = new CompoundTag(); + tag.putString("operation", "overwrite"); + tag.putBoolean("list", true); + + ListTag list = new ListTag(); + getAll().forEach(data -> list.add(data.serializeToNBT())); + + tag.put("data", list); + + PMWNetworking.serverSendStorageToPlayer(tag, this::packet, player); + } + + /** + * Syncs new {@link IStorageData} to all clients + * @param data The new {@link IStorageData} + * @since 0.15.3.3 + */ + default void syncAdd(D data) { + CompoundTag tag = new CompoundTag(); + tag.putString("operation", "add"); + tag.put("data", data.serializeToNBT()); + + PMWNetworking.serverSendStorageToAll(tag, this::packet); + } + + /** + * Syncs multiple new {@link IStorageData} to all clients + * @param datum A {@link Collection} of {@link IStorageData} to sync + * @since 0.15.3.3 + */ + default void syncAdd(Collection datum) { + CompoundTag tag = new CompoundTag(); + tag.putString("operation", "add"); + tag.putBoolean("list", true); + + ListTag list = new ListTag(); + datum.forEach(data -> list.add(data.serializeToNBT())); + + tag.put("data", list); + + PMWNetworking.serverSendStorageToAll(tag, this::packet); + } + + /** + * Syncs a {@link BlockPos} removal to all clients + * @param pos The {@link BlockPos} of the radar to remove + * @since 0.15.3.3 + */ + default void syncRemove(BlockPos pos) { + CompoundTag tag = new CompoundTag(); + tag.putString("operation", "remove"); + tag.putString("format", "blockpos"); + tag.put("data", NbtUtils.writeBlockPos(pos)); + + PMWNetworking.serverSendStorageToAll(tag, this::packet); + } + + /** + * Syncs multiple {@link BlockPos} removals to all clients + * @param posList A {@link Collection} of {@link BlockPos} to sync + * @since 0.15.3.3 + */ + default void syncRemoveByPos(Collection posList) { + CompoundTag tag = new CompoundTag(); + tag.putString("operation", "remove"); + tag.putString("format", "blockpos"); + tag.putBoolean("list", true); + + ListTag list = new ListTag(); + posList.forEach(pos -> list.remove(NbtUtils.writeBlockPos(pos))); + + tag.put("data", list); + + PMWNetworking.serverSendStorageToAll(tag, this::packet); + } + + /** + * Syncs a {@link IStorageData} removal to all clients + * @param data The {@link IStorageData} of the radar to remove + * @since 0.15.3.3 + */ + default void syncRemove(D data) { + CompoundTag tag = new CompoundTag(); + tag.putString("operation", "remove"); + tag.put("data", data.serializeToNBT()); + + PMWNetworking.serverSendStorageToAll(tag, this::packet); + } + + /** + * Syncs multiple {@link IStorageData} removals to all clients + * @param datum A {@link Collection} of {@link IStorageData} to sync + * @since 0.15.3.3 + */ + default void syncRemoveByData(Collection datum) { + CompoundTag tag = new CompoundTag(); + tag.putString("operation", "remove"); + tag.putBoolean("list", true); + + ListTag list = new ListTag(); + datum.forEach(data -> list.remove(data.serializeToNBT())); + + tag.put("data", list); + + PMWNetworking.serverSendStorageToAll(tag, this::packet); + } +} diff --git a/src/main/java/net/nullved/pmweatherapi/storage/IStorage.java b/src/main/java/net/nullved/pmweatherapi/storage/IStorage.java new file mode 100644 index 0000000..ebbc32f --- /dev/null +++ b/src/main/java/net/nullved/pmweatherapi/storage/IStorage.java @@ -0,0 +1,55 @@ +package net.nullved.pmweatherapi.storage; + +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.nullved.pmweatherapi.client.data.IClientStorage; +import net.nullved.pmweatherapi.radar.storage.RadarStorage; +import net.nullved.pmweatherapi.storage.data.IStorageData; +import net.nullved.pmweatherapi.storage.data.StorageData; + +import java.util.Collection; +import java.util.Set; + +/** + * The interface defining a Storage such as {@link RadarStorage} + *

+ * On the server-side, there is a {@link IServerStorage} for each dimension of a world. + * On the client-side, there is one {@link IClientStorage} for each player on a world. + *

+ * To add or remove {@link BlockPos}, use {@link #add} and {@link #remove}. + * To get all {@link BlockPos} use {@link #getAll()} or {@link #getInChunk(ChunkPos)} + *

+ * Optionally, Storages can be numerically versioned, however, you must write you own version mismatch handler + *

+ * For method definitions, see {@link PMWStorage} + * + * @see PMWStorage + * @since 0.15.3.3 + */ +public interface IStorage { + Level getLevel(); + ResourceLocation getId(); + int version(); + + void clean(); + + Set getAll(); + Set getAllWithinRange(BlockPos base, double radius); + Set getInChunk(ChunkPos pos); + Set getInAdjacentChunks(ChunkPos pos); + + boolean shouldRecalculate(ChunkPos pos); + + void add(D data); + void add(Collection datum); + void remove(D data); + void removeByData(Collection datum); + void remove(BlockPos pos); + void removeByPos(Collection pos); + + CompoundTag save(CompoundTag tag); + void read(); +} diff --git a/src/main/java/net/nullved/pmweatherapi/storage/ISyncServerStorage.java b/src/main/java/net/nullved/pmweatherapi/storage/ISyncServerStorage.java new file mode 100644 index 0000000..0cbbb90 --- /dev/null +++ b/src/main/java/net/nullved/pmweatherapi/storage/ISyncServerStorage.java @@ -0,0 +1,69 @@ +package net.nullved.pmweatherapi.storage; + +import net.minecraft.core.BlockPos; +import net.nullved.pmweatherapi.storage.data.IStorageData; +import net.nullved.pmweatherapi.storage.data.StorageData; + +import java.util.Collection; + +public interface ISyncServerStorage extends IServerStorage { + /** + * Shorthand for calling {@link #add(IStorageData)} and {@link #syncAdd(IStorageData)} + * @param data The {@link IStorageData} to add and sync + * @since 0.15.3.3 + */ + default void addAndSync(D data) { + this.add(data); + this.syncAdd(data); + } + + /** + * Shorthand for calling {@link #add(Collection)} and {@link #syncAdd(Collection)} + * @param datum The {@link Collection} of {@link IStorageData} to add and sync + * @since 0.15.3.3 + */ + default void addAndSync(Collection datum) { + this.add(datum); + this.syncAdd(datum); + } + + /** + * Shorthand for calling {@link #remove(BlockPos)} and {@link #remove(BlockPos)} + * @param pos The {@link BlockPos} to remove and sync + * @since 0.15.3.3 + */ + default void removeAndSync(BlockPos pos) { + this.remove(pos); + this.syncRemove(pos); + } + + /** + * Shorthand for calling {@link #removeByPos(Collection)} and {@link #syncRemoveByPos(Collection)} + * @param pos The {@link Collection} of {@link BlockPos} to remove and sync + * @since 0.15.3.3 + */ + default void removeAndSyncByPos(Collection pos) { + this.removeByPos(pos); + this.syncRemoveByPos(pos); + } + + /** + * Shorthand for calling {@link #remove(IStorageData)} and {@link #syncRemove(IStorageData)} + * @param data The {@link IStorageData} to remove and sync + * @since 0.15.3.3 + */ + default void removeAndSync(D data) { + this.remove(data); + this.syncRemove(data); + } + + /** + * Shorthand for calling {@link #removeByData(Collection)} and {@link #syncRemoveByData(Collection)} + * @param datum The {@link Collection} of {@link IStorageData} to remove and sync + * @since 0.15.3.3 + */ + default void removeAndSyncByData(Collection datum) { + this.removeByData(datum); + this.syncRemoveByData(datum); + } +} diff --git a/src/main/java/net/nullved/pmweatherapi/storage/PMWStorage.java b/src/main/java/net/nullved/pmweatherapi/storage/PMWStorage.java new file mode 100644 index 0000000..6a2062f --- /dev/null +++ b/src/main/java/net/nullved/pmweatherapi/storage/PMWStorage.java @@ -0,0 +1,340 @@ +package net.nullved.pmweatherapi.storage; + +import dev.protomanly.pmweather.block.RadarBlock; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.NbtUtils; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.neoforged.neoforge.event.level.ChunkWatchEvent; +import net.nullved.pmweatherapi.PMWeatherAPI; +import net.nullved.pmweatherapi.client.data.IClientStorage; +import net.nullved.pmweatherapi.client.data.PMWClientStorages; +import net.nullved.pmweatherapi.client.radar.RadarClientStorage; +import net.nullved.pmweatherapi.data.PMWStorageSavedData; +import net.nullved.pmweatherapi.data.PMWStorages; +import net.nullved.pmweatherapi.event.PMWEvents; +import net.nullved.pmweatherapi.radar.storage.RadarServerStorage; +import net.nullved.pmweatherapi.radar.storage.RadarStorage; +import net.nullved.pmweatherapi.storage.data.IStorageData; +import net.nullved.pmweatherapi.storage.data.StorageData; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + + +/** + * A basic {@link IStorage} implementation that should cover most, if not all, use-cases. + *

+ * A "Storage" saves and maintains a list of {@link IStorageData} on the {@link Level} that can be reloaded on world load. + * It does this by separating each {@link IStorageData} by chunk (more specifically, by {@link ChunkPos}) + *

+ * Any {@link IStorageData} can be saved, regardless of type, however, both server and clients must expect the same data structure. + * For example, {@link RadarStorage} is meant to store the positions of {@link RadarBlock}s in the world + *

+ * {@link PMWStorage} does not handle syncing radars from the server to the client, instead, + * implement {@link ISyncServerStorage} on a Server Storage and {@link IClientStorage} on a Client Storage. + * To sync using your own method, implement {@link IServerStorage} on the Server Storage instead. + *

+ * For your storage to be saved, you must first register it using {@link PMWStorages#registerStorage(ResourceLocation, Class, Function)} on both sides, + * and {@link PMWClientStorages#registerStorage(ResourceLocation, Class, Function)} on the client-side only + *

+ * For a full implementation example, see {@link RadarStorage}, {@link RadarServerStorage}, and {@link RadarClientStorage} + * + * @see IStorage + * @see IServerStorage + * @see IClientStorage + * @since 0.15.3.3 + */ +public abstract class PMWStorage implements IStorage { + + /** + * A {@link Set} of {@link IStorageData} split up by {@link ChunkPos} + * @since 0.15.3.3 + */ + private final Map> data = new HashMap<>(); + /** + * The times each {@link ChunkPos} was last checked + * @since 0.15.3.3 + */ + private final Map checkTimes = new HashMap<>(); + /** + * The dimension to store {@link BlockPos} for + * @since 0.15.3.3 + */ + private final ResourceKey dimension; + + @Override + public void clean() { + data.clear(); + checkTimes.clear(); + } + + public abstract ResourceLocation getExpectedDataType(); + + /** + * Gets the level associated with this {@link IStorage}. + * For the client side, it returns the {@link ClientLevel}. + * For the server side, it returns a {@link ServerLevel}. + * + * @return A {@link Level} instance + * @since 0.15.3.3 + */ + public abstract Level getLevel(); + + /** + * The {@link ResourceLocation} ID of this {@link IStorage}. + * Used primarily for saving to the file at {@code data/_.dat}. + * + * @return A {@link ResourceLocation} + * @since 0.15.3.3 + */ + public abstract ResourceLocation getId(); + + /** + * The version of this {@link IStorage}. + * To disable version data from being saved, return {@code -1} + * + * @return The version of the saved data + * @since 0.15.3.3 + */ + public abstract int version(); + + /** + * The base constructor + * + * @param dimension The dimension of the {@link IStorage} + * @since 0.15.3.3 + */ + public PMWStorage(ResourceKey dimension) { + this.dimension = dimension; + } + + /** + * Gets a {@link Set} of every {@link IStorageData} saved in this {@link IStorage}, regardless of {@link ChunkPos} + * + * @return Every saved {@link IStorageData} + * @since 0.15.3.3 + */ + public Set getAll() { + return data.values().stream().flatMap(Collection::stream).collect(Collectors.toSet()); + } + + @Override + public Set getAllWithinRange(BlockPos base, double radius) { + int chunks = (int) Math.ceil(radius / 16.0F) + 1; + ChunkPos cpos = new ChunkPos(base); + + HashSet set = new HashSet<>(); + for (int x = -chunks; x <= chunks; x++) { + for (int z = -chunks; z <= chunks; z++) { + for (D candidate: getInChunk(new ChunkPos(cpos.x + x, cpos.z + z))) { + if (Math.abs(base.distToCenterSqr(candidate.getPos().getX(), candidate.getPos().getY(), candidate.getPos().getZ())) <= radius * radius) set.add(candidate); + } + } + } + + return set; + } + + /** + * Gets the {@link Set} of {@link IStorageData} for this {@link ChunkPos} + * + * @param pos The {@link ChunkPos} to search + * @return A {@link Set} of the {@link IStorageData} in this chunk + * @since 0.15.3.3 + */ + public Set getInChunk(ChunkPos pos) { + return data.getOrDefault(pos, Set.of()); + } + + @Override + public Set getInAdjacentChunks(ChunkPos pos) { + Set set = new HashSet<>(); + for (int x = -1; x <= 1; x++) { + for (int z = -1; z <= 1; z++) { + set.addAll(getInChunk(new ChunkPos(pos.x + x, pos.z + z))); + } + } + return set; + } + + /** + * Determines if the data for the given {@link ChunkPos} is older than 30 seconds or does not exist. + * Intended to be used while listening to a {@link ChunkWatchEvent.Sent} event (See {@link PMWEvents}) + * + * @param pos The {@link ChunkPos} to check + * @return Whether the data should be recalculated or not + * @since 0.15.3.3 + */ + public boolean shouldRecalculate(ChunkPos pos) { + if (!checkTimes.containsKey(pos)) { + checkTimes.put(pos, System.currentTimeMillis()); + return true; + } + + return checkTimes.get(pos) - System.currentTimeMillis() > 30000L; + } + + /** + * Adds a single {@link IStorageData} to the {@link IStorage} + * + * @param addData The new {@link IStorageData} + * @since 0.15.3.3 + */ + public void add(D addData) { + ChunkPos chunkPos = new ChunkPos(addData.getPos()); + Set set = data.computeIfAbsent(chunkPos, c -> new HashSet<>()); + + Set exist = set.stream().map(IStorageData::getPos).filter(c -> c.equals(addData.getPos())).collect(Collectors.toSet()); + if (!exist.isEmpty()) { + removeByPos(exist); + } + + set.add(addData); + data.put(chunkPos, set); + } + + /** + * Adds multiple new {@link IStorageData} to the {@link IStorage} + * + * @param datum A {@link Collection} of new {@link IStorageData} + * @since 0.15.3.3 + */ + public void add(Collection datum) { + datum.forEach(this::add); + } + + /** + * Removes a single {@link BlockPos} from the {@link IStorage} + * + * @param pos The {@link BlockPos} to remove + * @since 0.15.3.3 + */ + public void remove(BlockPos pos) { + ChunkPos chunkPos = new ChunkPos(pos); + Set set = data.computeIfAbsent(chunkPos, c -> new HashSet<>()); + + Set exist = set.stream().filter(c -> c.getPos().equals(pos)).collect(Collectors.toSet()); + if (!exist.isEmpty()) { + exist.forEach(set::remove); + } + + data.put(chunkPos, set); + } + + /** + * Removes multiple {@link BlockPos} from the {@link IStorage} + * + * @param pos A {@link Collection} of {@link BlockPos} to remove + * @since 0.15.3.3 + */ + public void removeByPos(Collection pos) { + pos.forEach(this::remove); + } + + /** + * Removes a single {@link IStorageData} from the {@link IStorage} + * + * @param removedData The {@link IStorageData} to remove + * @since 0.15.3.3 + */ + public void remove(D removedData) { + ChunkPos chunkPos = new ChunkPos(removedData.getPos()); + Set set = data.computeIfAbsent(chunkPos, c -> new HashSet<>()); + set.remove(removedData); + data.put(chunkPos, set); + } + + /** + * Removes multiple {@link IStorageData} from the {@link IStorage} + * + * @param datum A {@link Collection} of {@link IStorageData} to remove + * @since 0.15.3.3 + */ + public void removeByData(Collection datum) { + datum.forEach(this::remove); + } + + /** + * Saves the data of this {@link IStorage} to a {@link CompoundTag} + * + * @param tag The pre-existing {@link CompoundTag} + * @return A {@link CompoundTag} with storage data + * @since 0.15.3.3 + */ + public CompoundTag save(CompoundTag tag) { + PMWeatherAPI.LOGGER.info("Saving storage {} to level...", getId()); + if (version() != -1) tag.putInt("version", version()); + tag.putLong("saveTime", System.currentTimeMillis()); + + final ResourceLocation[] type = {null}; + for (Map.Entry> entry : data.entrySet()) { + ListTag list = new ListTag(); + entry.getValue().forEach(storageData -> { + CompoundTag ctag = storageData.serializeToNBT(); + if (type[0] == null) { + type[0] = ResourceLocation.parse(ctag.getString("type")); + } + ctag.remove("type"); + list.add(ctag); + }); + tag.put(String.valueOf(entry.getKey().toLong()), list); + } + + if (type[0] != null) tag.putString("type", type[0].toString()); + + PMWeatherAPI.LOGGER.info("Saved storage {} to level", getId()); + return tag; + } + + /** + * Reads the saved data from the {@link Level} and initializes this {@link IStorage} with the data + * @since 0.15.3.3 + */ + public void read() { + PMWStorageSavedData savedData = ((ServerLevel) this.getLevel()).getDataStorage().computeIfAbsent(PMWStorageSavedData.factory(), getId().toString().replace(":", "_")); + savedData.setStorage(this); + PMWeatherAPI.LOGGER.info("Reading storage {} from level...", getId()); + CompoundTag data = savedData.getTag(); + String type = this.getExpectedDataType().toString(); + int version = data.getInt("version"); + Set chunks = data.getAllKeys(); + chunks.removeAll(Set.of("version", "saveTime", "type")); + + for (String chunk : chunks) { + Set blocks = new HashSet<>(); + ListTag list = (ListTag) data.get(chunk); + for (int i = 0; i < list.size(); i++) { + try { + if (!list.get(i).getType().equals(CompoundTag.TYPE)) { + // not a compound + CompoundTag ctag = new CompoundTag(); + ctag.put("blockpos", list.get(i)); + + if (NbtUtils.readBlockPos(ctag, "blockpos").isPresent()) { + blocks.add(StorageData.deserializeFromNBT(ctag, version)); + } else { + PMWeatherAPI.LOGGER.error("Could not deserialize tag {}! No type data and not a blockpos!", NbtUtils.toPrettyComponent(ctag.get("blockpos"))); + } + } else { + CompoundTag ctag = list.getCompound(i); + if (!type.isEmpty()) ctag.putString("type", type); + blocks.add(StorageData.deserializeFromNBT(ctag, version)); + } + } catch (ClassCastException e) { + PMWeatherAPI.LOGGER.warn("Invalid data entry in storage {} at chunk {}: {}", getId(), chunk, e.getMessage()); + } + } + + this.data.put(new ChunkPos(Long.parseLong(chunk)), blocks); + } + } +} \ No newline at end of file diff --git a/src/main/java/net/nullved/pmweatherapi/storage/StorageInstance.java b/src/main/java/net/nullved/pmweatherapi/storage/StorageInstance.java new file mode 100644 index 0000000..a8f6ed4 --- /dev/null +++ b/src/main/java/net/nullved/pmweatherapi/storage/StorageInstance.java @@ -0,0 +1,110 @@ +package net.nullved.pmweatherapi.storage; + +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.Level; +import net.nullved.pmweatherapi.PMWeatherAPI; +import net.nullved.pmweatherapi.data.PMWStorages; +import net.nullved.pmweatherapi.storage.data.IStorageData; +import net.nullved.pmweatherapi.storage.data.StorageData; + +import java.util.*; +import java.util.function.Function; + +/** + * A Storage Instance for a given {@link IServerStorage} type. + *

+ * A Storage Instance holds all of the {@link IServerStorage} instances for each dimension. + *

+ * To get the {@link IServerStorage} for a specific dimension, use {@link #get(ResourceKey)}. + * If you are unsure the {@link IServerStorage} exists and have a {@link ServerLevel}, use {@link #getOrCreate(ServerLevel)}. + *

+ * To load a new dimension, pass the {@link ServerLevel} into {@link #load(ServerLevel)}. + * To remove a dimension, call {@link #remove(ResourceKey)} + *

+ * You should not create {@link StorageInstance}s yourself. + * Instead, get them from {@link PMWStorages#get} + * + * @param The {@link IStorageData} of the {@link IServerStorage} + * @param The {@link IServerStorage} + * @since 0.15.3.3 + */ +public class StorageInstance> { + private final ResourceLocation id; + private final Class clazz; + private final Function creator; + private final HashMap, S> map = new HashMap<>(); + + public StorageInstance(ResourceLocation id, Class clazz, Function creator) { + this.id = id; + this.clazz = clazz; + this.creator = creator; + } + + public ResourceLocation id() { + return id; + } + + public HashMap, S> getBackingMap() { + return map; + } + + public void clear() { + this.map.clear(); + } + + public Set> keySet() { + return map.keySet(); + } + + public Collection values() { + return map.values(); + } + + public Set, S>> entrySet() { + return map.entrySet(); + } + + public S get(ResourceKey dimension) { + return map.get(dimension); + } + + public S getOrCreate(ServerLevel level) { + return map.computeIfAbsent(level.dimension(), dim -> { + S storage = creator.apply(level); + storage.read(); + return storage; + }); + } + + public > Optional> cast(Class oclazz) { + if (oclazz.isAssignableFrom(clazz)) { + @SuppressWarnings("unchecked") + StorageInstance casted = new StorageInstance<>(id(), oclazz, sl -> (O) creator.apply(sl)); + HashMap, O> backingMap = casted.getBackingMap(); + + try { + for (Map.Entry, S> entry : this.map.entrySet()) { + backingMap.put(entry.getKey(), oclazz.cast(entry.getValue())); + } + + return Optional.of(casted); + } catch (ClassCastException e) { + PMWeatherAPI.LOGGER.error("Could not cast {} to {}", clazz.getSimpleName(), oclazz.getSimpleName()); + return Optional.empty(); + } + } else return Optional.empty(); + } + + public void load(ServerLevel level) { + S storage = creator.apply(level); + storage.read(); + map.put(level.dimension(), storage); + } + + public void remove(ResourceKey dimension) { + S storage = map.get(dimension); + map.remove(dimension); + } +} diff --git a/src/main/java/net/nullved/pmweatherapi/storage/data/BlockPosData.java b/src/main/java/net/nullved/pmweatherapi/storage/data/BlockPosData.java new file mode 100644 index 0000000..698036a --- /dev/null +++ b/src/main/java/net/nullved/pmweatherapi/storage/data/BlockPosData.java @@ -0,0 +1,29 @@ +package net.nullved.pmweatherapi.storage.data; + +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; +import net.nullved.pmweatherapi.PMWeatherAPI; + +/** + * A wrapper around {@link StorageData} to give it an ID + * + * @see StorageData + * @since 0.15.3.3 + */ +public class BlockPosData extends StorageData { + public static final ResourceLocation ID = PMWeatherAPI.rl("blockpos"); + + public BlockPosData(BlockPos pos) { + super(pos); + } + + @Override + public ResourceLocation getId() { + return ID; + } + + public static BlockPosData deserializeFromNBT(CompoundTag tag, int version) { + return new BlockPosData(deserializeBlockPos(tag)); + } +} diff --git a/src/main/java/net/nullved/pmweatherapi/storage/data/IStorageData.java b/src/main/java/net/nullved/pmweatherapi/storage/data/IStorageData.java new file mode 100644 index 0000000..79ec60c --- /dev/null +++ b/src/main/java/net/nullved/pmweatherapi/storage/data/IStorageData.java @@ -0,0 +1,20 @@ +package net.nullved.pmweatherapi.storage.data; + +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; +import net.nullved.pmweatherapi.radar.storage.RadarStorageData; + +/** + * The interface defining Storage Data such as {@link RadarStorageData} + *

+ * For method definitions, see {@link StorageData} + * + * @see StorageData + * @since 0.15.3.3 + */ +public interface IStorageData { + ResourceLocation getId(); + BlockPos getPos(); + CompoundTag serializeToNBT(); +} diff --git a/src/main/java/net/nullved/pmweatherapi/storage/data/StorageData.java b/src/main/java/net/nullved/pmweatherapi/storage/data/StorageData.java new file mode 100644 index 0000000..5b1f8e8 --- /dev/null +++ b/src/main/java/net/nullved/pmweatherapi/storage/data/StorageData.java @@ -0,0 +1,93 @@ +package net.nullved.pmweatherapi.storage.data; + +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtUtils; +import net.minecraft.resources.ResourceLocation; +import net.nullved.pmweatherapi.PMWeatherAPI; +import net.nullved.pmweatherapi.client.data.IClientStorage; +import net.nullved.pmweatherapi.metar.MetarStorageData; +import net.nullved.pmweatherapi.radar.storage.RadarStorageData; +import net.nullved.pmweatherapi.radar.storage.WSRStorageData; +import net.nullved.pmweatherapi.storage.IServerStorage; +import net.nullved.pmweatherapi.storage.IStorage; + +import java.util.Optional; +import java.util.function.BiFunction; + +/** + * A basic {@link IStorageData} implementation that stores and handles a {@link BlockPos} by default. + *

+ * By extending this class, you can add additional data that will be saved as part of the "Storages" system. + *

+ * To register your data for use, you must first register it with {@link StorageDataManager#register(ResourceLocation, BiFunction)} + *

+ * When serializing, you should get the {@link CompoundTag} from calling {@link StorageData#serializeToNBT()}. + * This makes sure that you have both the type and blockpos saved. + *

+ * When deserializing, you can call {@link #deserializeBlockPos(CompoundTag)} to automatically read the saved {@link BlockPos} + *

+ * For some example implementations, view {@link RadarStorageData}, {@link MetarStorageData}, and {@link WSRStorageData} + * + * @since 0.15.3.3 + * @see IStorageData + * @see StorageDataManager + * @see IStorage + * @see IServerStorage + * @see IClientStorage + */ +public abstract class StorageData implements IStorageData { + protected final BlockPos pos; + + public StorageData(BlockPos pos) { + this.pos = pos; + } + + /** + * Get the position saved in this data + * @return A {@link BlockPos} + * @since 0.15.3.3 + */ + @Override + public BlockPos getPos() { + return pos; + } + + /** + * Serialize this storage data to NBT + * @return A {@link CompoundTag} of the serialized data + * @since 0.15.3.3 + */ + @Override + public CompoundTag serializeToNBT() { + CompoundTag tag = new CompoundTag(); + tag.putString("type", getId().toString()); + tag.put("blockpos", NbtUtils.writeBlockPos(pos)); + return tag; + } + + /** + * Deserialize a {@link BlockPos} from a {@link CompoundTag} + * @param tag The {@link CompoundTag} to get the {@link BlockPos} from + * @return The found {@link BlockPos}, or {@code null} + * @since 0.15.3.3 + */ + public static BlockPos deserializeBlockPos(CompoundTag tag) { + Optional bp = NbtUtils.readBlockPos(tag, "blockpos"); + if (bp.isPresent()) return bp.get(); + else PMWeatherAPI.LOGGER.warn("Could not deserialize BlockPos! Tag: {}", NbtUtils.toPrettyComponent(tag)); + return null; + } + + /** + * Shorthand for {@link StorageDataManager#get(CompoundTag, int)} + * @param tag The {@link CompoundTag} + * @param version The version of the data + * @return An {@link IStorageData} instance + * @param The type of {@link IStorageData} + * @since 0.15.3.3 + */ + public static D deserializeFromNBT(CompoundTag tag, int version) { + return StorageDataManager.get(tag, version); + } +} diff --git a/src/main/java/net/nullved/pmweatherapi/storage/data/StorageDataManager.java b/src/main/java/net/nullved/pmweatherapi/storage/data/StorageDataManager.java new file mode 100644 index 0000000..66edb93 --- /dev/null +++ b/src/main/java/net/nullved/pmweatherapi/storage/data/StorageDataManager.java @@ -0,0 +1,42 @@ +package net.nullved.pmweatherapi.storage.data; + +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtUtils; +import net.minecraft.resources.ResourceLocation; +import net.nullved.pmweatherapi.PMWeatherAPI; + +import java.util.HashMap; +import java.util.Optional; +import java.util.function.BiFunction; + +/** + * A manager for {@link IStorageData}. + *

+ * To register a {@link IStorageData}, you must pass a {@link ResourceLocation} of the id, and the deserialization function + * + * @since 0.15.3.3 + * @see IStorageData + * @see StorageData + */ +public class StorageDataManager { + public static final HashMap> map = new HashMap<>(); + + public static void register(ResourceLocation id, BiFunction deserializer) { + map.put(id, deserializer); + } + + public static D get(CompoundTag tag, int version) { + try { + //PMWeatherAPI.LOGGER.info("Getting storage data for type {}", tag.getString("type")); + if (tag.getString("type").isEmpty()) { + Optional bp = NbtUtils.readBlockPos(tag, "blockpos"); + if (bp.isPresent()) return (D) new BlockPosData(bp.get()); + else throw new IllegalArgumentException("No type given and does not meet BlockPos criteria"); + } else return (D) map.get(ResourceLocation.parse(tag.getString("type"))).apply(tag, version); + } catch (Exception e) { + PMWeatherAPI.LOGGER.error("Could not deserialize tag {} of type {}: {}", NbtUtils.toPrettyComponent(tag), tag.get("type"), e.getMessage()); + return null; + } + } +} diff --git a/src/main/java/net/nullved/pmweatherapi/util/ColorMap.java b/src/main/java/net/nullved/pmweatherapi/util/ColorMap.java index 4b9bc58..47c0df4 100644 --- a/src/main/java/net/nullved/pmweatherapi/util/ColorMap.java +++ b/src/main/java/net/nullved/pmweatherapi/util/ColorMap.java @@ -107,8 +107,8 @@ public Color getWithBiome(float val, Holder biome, Vec3 worldPos) { if (rn.contains("ocean") || rn.contains("river")) startColor = new Color(biome.value().getWaterColor()); else if (rn.contains("beach") || rn.contains("desert")) { if (rn.contains("badlands")) startColor = new Color(214, 111, 42); - else startColor = new Color(biome.value().getGrassColor(worldPos.x, worldPos.z)); - } else startColor = new Color(227, 198, 150); + else startColor = new Color(227, 198, 150); + } else startColor = new Color(biome.value().getGrassColor(worldPos.x, worldPos.z)); if (val < firstThreshold) { return lerp(Math.clamp(val / (firstThreshold - min), 0.0F, 1.0F), startColor, segments.firstEntry().getValue().to); diff --git a/src/main/java/net/nullved/pmweatherapi/util/PMWUtils.java b/src/main/java/net/nullved/pmweatherapi/util/PMWUtils.java index a287e83..b2353af 100644 --- a/src/main/java/net/nullved/pmweatherapi/util/PMWUtils.java +++ b/src/main/java/net/nullved/pmweatherapi/util/PMWUtils.java @@ -2,10 +2,69 @@ import net.minecraft.core.BlockPos; import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.Level; import net.nullved.pmweatherapi.radar.NearbyRadars; +import net.nullved.pmweatherapi.storage.IStorage; +import net.nullved.pmweatherapi.storage.data.StorageData; + +import java.util.HashSet; +import java.util.Set; +import java.util.function.Function; public class PMWUtils { + /** + * Checks if two {@link BlockPos} are corner-adjacent + * @param a A {@link BlockPos} + * @param b Another {@link BlockPos} + * @return {@code true} if they are corner-adjacent + */ + public static boolean isCornerAdjacent(BlockPos a, BlockPos b) { + int dx = Math.abs(a.getX() - b.getX()); + int dy = Math.abs(a.getY() - b.getY()); + int dz = Math.abs(a.getZ() - b.getZ()); + + return (dx <= 1 && dy <= 1 && dz <= 1) && (dx + dy + dz > 0); + } + + /** + * Performs a {@link Function} for each {@link BlockPos} around a {@link BlockPos} + * @param pos The base {@link BlockPos} + * @param test The test + * @return A {@link Set} of {@link BlockPos} that passed the test + */ + public static Set testAround(BlockPos pos, Function test) { + HashSet set = new HashSet<>(); + + for (int x = -1; x <= 1; x++) { + for (int y = -1; y <= 1; y++) { + for (int z = -1; z <= 1; z++) { + if (x == 0 && y == 0 && z == 0) continue; + if (test.apply(pos.offset(x, y, z))) set.add(pos.offset(x, y, z)); + } + } + } + + return set; + } + + /** + * Gets all {@link BlockPos} in a {@link IStorage} that are corner-adjacent to the base {@link BlockPos} + * @param storage The {@link IStorage} to check in + * @param pos The base {@link BlockPos} + * @return A {@link Set} of {@link BlockPos} that are in the {@link IStorage} around the base {@link BlockPos} + * @since 0.15.3.3 + */ + public static Set storageCornerAdjacent(IStorage storage, BlockPos pos) { + HashSet set = new HashSet<>(); + + for (D data: storage.getInAdjacentChunks(new ChunkPos(pos))) { + if (isCornerAdjacent(data.getPos(), pos)) set.add(data); + } + + return set; + } + /** * Determines if a radar is next to the given {@link BlockPos} * @param dim The dimension to search in @@ -27,4 +86,33 @@ public static boolean isRadarAdjacent(ResourceKey dim, BlockPos pos) { public static boolean isRadarAdjacent(Level level, BlockPos pos) { return isRadarAdjacent(level.dimension(), pos); } + + /** + * Determines if a radar is within 1 block, including diagonally, to the given {@link BlockPos} + * @param dim The dimension to search in + * @param pos The {@link BlockPos} to look by + * @return {@code true} if there is a radar adjacent to this block, {@code false} otherwise + * @since 0.15.3.3 + */ + public static boolean isRadarCornerAdjacent(ResourceKey dim, BlockPos pos) { + Set nearby = NearbyRadars.get(dim).radarsNearBlock(pos, 3); + + boolean adj = false; + for (BlockPos nearbyPos : nearby) { + adj |= isCornerAdjacent(nearbyPos, pos); + } + + return adj; + } + + /** + * Determines if a radar is within 1 block, including diagonally, to the given {@link BlockPos}. + * @param level The {@link Level} to search in + * @param pos The {@link BlockPos} to look by + * @return {@code true} if there is a radar adjacent to this block, {@code false} otherwise + * @since 0.15.3.3 + */ + public static boolean isRadarCornerAdjacent(Level level, BlockPos pos) { + return isRadarCornerAdjacent(level.dimension(), pos); + } } diff --git a/src/main/resources/pmweatherapi.mixins.json b/src/main/resources/pmweatherapi.mixins.json index f96d20e..35538f4 100644 --- a/src/main/resources/pmweatherapi.mixins.json +++ b/src/main/resources/pmweatherapi.mixins.json @@ -6,9 +6,9 @@ "refmap": "pmweatherapi.refmap.json", "mixins": [ "BlockBehaviourMixin", - "PacketNBTFromClientMixin", "RadarBlockMixin", - "StormMixin" + "StormMixin", + "WSR88DCoreMixin" ], "client": [ "RadarRendererMixin",