From 99f70815bc5f489fede134215684057466f20338 Mon Sep 17 00:00:00 2001 From: Uko Kokņevičs Date: Fri, 12 Jan 2024 19:12:14 +0100 Subject: Make EMC be synced from server to client --- README.md | 7 - TODO.md | 4 + src/main/java/lv/enes/mc/eris_alchemy/Emc.java | 252 ++++++++++++--------- .../java/lv/enes/mc/eris_alchemy/ErisAlchemy.java | 5 + .../enes/mc/eris_alchemy/ErisAlchemyRegistry.java | 3 + .../block/entity/EnergyCondenserEntity.java | 9 +- .../mc/eris_alchemy/client/ErisAlchemyClient.java | 5 +- .../lv/enes/mc/eris_alchemy/utils/BufUtils.java | 20 ++ .../lv/enes/mc/eris_alchemy/utils/PlayerUtils.java | 22 ++ 9 files changed, 209 insertions(+), 118 deletions(-) delete mode 100644 README.md create mode 100644 TODO.md create mode 100644 src/main/java/lv/enes/mc/eris_alchemy/utils/BufUtils.java create mode 100644 src/main/java/lv/enes/mc/eris_alchemy/utils/PlayerUtils.java diff --git a/README.md b/README.md deleted file mode 100644 index 34c9b35..0000000 --- a/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Eris Alchemy - -## TODO - -- custom model for guide book - -When upgrading minecraft `grep` for `UPGRADE`, update content and bracketed version. \ No newline at end of file diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..97ca272 --- /dev/null +++ b/TODO.md @@ -0,0 +1,4 @@ +# TODO # + +- custom model for guide book +- proper EmcLoader with separate prepare and apply stages \ No newline at end of file diff --git a/src/main/java/lv/enes/mc/eris_alchemy/Emc.java b/src/main/java/lv/enes/mc/eris_alchemy/Emc.java index fdc7c0a..c12b143 100644 --- a/src/main/java/lv/enes/mc/eris_alchemy/Emc.java +++ b/src/main/java/lv/enes/mc/eris_alchemy/Emc.java @@ -1,11 +1,18 @@ package lv.enes.mc.eris_alchemy; -import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; +import lv.enes.mc.eris_alchemy.ErisAlchemyRegistry.NetworkingConstants; +import lv.enes.mc.eris_alchemy.utils.BufUtils; import lv.enes.mc.eris_alchemy.utils.ItemUtils; +import lv.enes.mc.eris_alchemy.utils.PlayerUtils; +import net.minecraft.client.Minecraft; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.Registries; +import net.minecraft.network.FriendlyByteBuf; import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; import net.minecraft.tags.TagKey; import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.Item; @@ -13,86 +20,37 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.crafting.Ingredient; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; +import org.quiltmc.loader.api.minecraft.ClientOnly; +import org.quiltmc.qsl.networking.api.PacketByteBufs; +import org.quiltmc.qsl.networking.api.ServerPlayNetworking; +import org.quiltmc.qsl.networking.api.client.ClientPlayNetworking; import java.text.DecimalFormat; import java.util.*; import java.util.stream.Stream; -public class Emc { +public final class Emc { + private Emc() {} + private static final Map ITEM_VALUES = new HashMap<>(); private static final Map, OptionalDouble> ITEM_TAG_VALUES = new HashMap<>(); private static final Map, OptionalDouble> BLOCK_TAG_VALUES = new HashMap<>(); private static final List FAKE_RECIPES = new ArrayList<>(); - private static final Map instances = Collections.synchronizedMap(new WeakHashMap<>()); + private static final Map VALUES = Collections.synchronizedMap(new HashMap<>()); - private static final DecimalFormat formatter = new DecimalFormat("0"); + private static final DecimalFormat FORMATTER = new DecimalFormat("0"); static { - formatter.setMaximumFractionDigits(1); + FORMATTER.setMaximumFractionDigits(1); } - public static Emc getInstance(@Nonnull Level world) { - if (instances.containsKey(world)) { - return instances.get(world); - } - - var instance = new Emc(world); - instances.put(world, instance); - return instance; - } + private static ServerLevel overworld = null; public static String formatEmc(double value) { - return formatter.format(value); + return FORMATTER.format(value); } - public static void reloadData( - Map itemValues, - Map itemTagValues, - Map blockTagValues, - List fakeRecipes - ) { - ITEM_VALUES.clear(); - ITEM_VALUES.putAll(itemValues); - - ITEM_TAG_VALUES.clear(); - itemTagValues.forEach((id, value) -> ITEM_TAG_VALUES.put(TagKey.create(Registries.ITEM, id), value)); - - BLOCK_TAG_VALUES.clear(); - blockTagValues.forEach((id, value) -> BLOCK_TAG_VALUES.put(TagKey.create(Registries.BLOCK, id), value)); - - FAKE_RECIPES.clear(); - FAKE_RECIPES.addAll(fakeRecipes); - - instances.clear(); - } - - private final Map data; - - private Emc(@Nonnull Level world) { - data = Collections.synchronizedMap(new HashMap<>(ITEM_VALUES)); - ITEM_TAG_VALUES.forEach( - (tag, emcValue) -> BuiltInRegistries.ITEM - .getTagOrEmpty(tag) - .forEach(holder -> data.putIfAbsent(ItemUtils.getId(holder), emcValue)) - ); - BLOCK_TAG_VALUES.forEach( - (tag, emcValue) -> BuiltInRegistries.BLOCK - .getTagOrEmpty(tag) - .forEach(holder -> data.putIfAbsent(ItemUtils.getId(holder), emcValue)) - ); - - ErisAlchemy.LOGGER.info("Calculating EMC values..."); - var recipes = getRecipes(world); - var configured = new HashSet<>(data.keySet()); - BuiltInRegistries.ITEM.keySet().forEach(item -> { - configEmc(recipes, configured, item); - if (!data.containsKey(item)) { - ErisAlchemy.LOGGER.warn("No EMC value for '{}' known", item); - } - }); - } - - public OptionalDouble get(ItemStack stack) { + public static OptionalDouble get(ItemStack stack) { if (stack.isEmpty()) { return OptionalDouble.empty(); } @@ -118,51 +76,66 @@ public class Emc { .findFirst(); } - public OptionalDouble get(ResourceLocation itemId) { - return data.getOrDefault(itemId, OptionalDouble.empty()); + public static OptionalDouble get(ResourceLocation itemId) { + return VALUES.getOrDefault(itemId, OptionalDouble.empty()); } - private List getRecipes(@Nullable Level world) { - Stream recipes = FAKE_RECIPES.stream(); + @ClientOnly + public static void initClient(Minecraft ignoredClient) { + ClientPlayNetworking.registerGlobalReceiver( + NetworkingConstants.UPDATE_EMCS, + (client1, handler, buf, responseSender) -> { + var map = buf.readMap(FriendlyByteBuf::readResourceLocation, BufUtils::readOptionalDouble); + VALUES.clear(); + VALUES.putAll(map); + } + ); + } - if (world != null) { - recipes = Stream.concat( - recipes, - world.getRecipeManager() - .getRecipes() - .stream() - .map(recipe -> new SimplifiedRecipe(recipe, world.registryAccess())) - ); - } + public static void initServer(MinecraftServer server) { + overworld = server.overworld(); + reinit(); + warnOfMissingValues(); + } - return recipes.toList(); + public static void reloadData( + Map itemValues, + Map itemTagValues, + Map blockTagValues, + List fakeRecipes + ) { + ITEM_VALUES.clear(); + ITEM_VALUES.putAll(itemValues); + + ITEM_TAG_VALUES.clear(); + itemTagValues.forEach((id, value) -> ITEM_TAG_VALUES.put(TagKey.create(Registries.ITEM, id), value)); + + BLOCK_TAG_VALUES.clear(); + blockTagValues.forEach((id, value) -> BLOCK_TAG_VALUES.put(TagKey.create(Registries.BLOCK, id), value)); + + FAKE_RECIPES.clear(); + FAKE_RECIPES.addAll(fakeRecipes); + + reinit(); + warnOfMissingValues(); } - private OptionalDouble configEmc( + private static OptionalDouble calculateEmcForIngredient( List recipes, Set configured, - ResourceLocation itemId + Ingredient ingredient ) { - var res = get(itemId); - if (res.isPresent() || configured.contains(itemId)) { - return res; - } - - configured.add(itemId); - var item = BuiltInRegistries.ITEM.get(itemId); - res = recipes.stream() - .filter(recipe -> recipe.output().is(item)) - .map(recipe -> calculateEmcForRecipe(recipes, configured, recipe)) - .flatMapToDouble(OptionalDouble::stream) + return Arrays.stream(ingredient.getItems()) + .map(stack -> configEmc(recipes, configured, ItemUtils.getId(stack.getItem())).stream() + .map(x -> x * stack.getCount()) + .findFirst()) + .filter(OptionalDouble::isPresent) + .mapToDouble(OptionalDouble::getAsDouble) + .filter(x -> x > 0) .average(); - res.ifPresentOrElse( - emc -> data.put(itemId, OptionalDouble.of(emc)), - () -> configured.remove(itemId) - ); - return res; } - private OptionalDouble calculateEmcForRecipe( + private static OptionalDouble calculateEmcForRecipe( List recipes, Set configured, SimplifiedRecipe recipe @@ -197,18 +170,87 @@ public class Emc { } } - private OptionalDouble calculateEmcForIngredient( + private static OptionalDouble configEmc( List recipes, Set configured, - Ingredient ingredient + ResourceLocation itemId ) { - return Arrays.stream(ingredient.getItems()) - .map(stack -> configEmc(recipes, configured, ItemUtils.getId(stack.getItem())).stream() - .map(x -> x * stack.getCount()) - .findFirst()) - .filter(OptionalDouble::isPresent) - .mapToDouble(OptionalDouble::getAsDouble) - .filter(x -> x > 0) + var res = get(itemId); + if (res.isPresent() || configured.contains(itemId)) { + return res; + } + + configured.add(itemId); + var item = BuiltInRegistries.ITEM.get(itemId); + res = recipes.stream() + .filter(recipe -> recipe.output().is(item)) + .map(recipe -> calculateEmcForRecipe(recipes, configured, recipe)) + .flatMapToDouble(OptionalDouble::stream) .average(); + res.ifPresentOrElse( + emc -> VALUES.put(itemId, OptionalDouble.of(emc)), + () -> configured.remove(itemId) + ); + return res; + } + + private static List getRecipes(@Nullable Level world) { + Stream recipes = FAKE_RECIPES.stream(); + + if (world != null) { + recipes = Stream.concat( + recipes, + world.getRecipeManager() + .getRecipes() + .stream() + .map(recipe -> new SimplifiedRecipe(recipe, world.registryAccess())) + ); + } + + return recipes.toList(); + } + + private static void reinit() { + VALUES.clear(); + VALUES.putAll(ITEM_VALUES); + ITEM_TAG_VALUES.forEach( + (tag, emcValue) -> BuiltInRegistries.ITEM + .getTagOrEmpty(tag) + .forEach(holder -> VALUES.putIfAbsent(ItemUtils.getId(holder), emcValue)) + ); + BLOCK_TAG_VALUES.forEach( + (tag, emcValue) -> BuiltInRegistries.BLOCK + .getTagOrEmpty(tag) + .forEach(holder -> VALUES.putIfAbsent(ItemUtils.getId(holder), emcValue)) + ); + + ErisAlchemy.LOGGER.info("Calculating EMC values from recipes..."); + var recipes = getRecipes(overworld); + var configured = new HashSet<>(VALUES.keySet()); + BuiltInRegistries.ITEM.keySet().forEach(item -> configEmc(recipes, configured, item)); + + sync(); + } + + private static void sync() { + syncTo(PlayerUtils.all()); + } + + private static void syncTo(Collection player) { + var buf = PacketByteBufs.create(); + buf.writeMap(VALUES, FriendlyByteBuf::writeResourceLocation, BufUtils::writeOptionalDouble); + ServerPlayNetworking.send(player, NetworkingConstants.UPDATE_EMCS, buf); + } + + private static void warnOfMissingValues() { + if (overworld == null) { + return; + } + + BuiltInRegistries.ITEM + .keySet() + .stream() + .filter(item -> !VALUES.containsKey(item)) + .forEach(item -> ErisAlchemy.LOGGER.warn("No EMC value for '{}' known", item)); } } diff --git a/src/main/java/lv/enes/mc/eris_alchemy/ErisAlchemy.java b/src/main/java/lv/enes/mc/eris_alchemy/ErisAlchemy.java index bb84e18..1d363c2 100644 --- a/src/main/java/lv/enes/mc/eris_alchemy/ErisAlchemy.java +++ b/src/main/java/lv/enes/mc/eris_alchemy/ErisAlchemy.java @@ -2,6 +2,7 @@ package lv.enes.mc.eris_alchemy; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import lv.enes.mc.eris_alchemy.utils.PlayerUtils; import net.fabricmc.fabric.api.itemgroup.v1.FabricItemGroup; import net.minecraft.core.Registry; import net.minecraft.core.registries.BuiltInRegistries; @@ -12,6 +13,7 @@ import net.minecraft.world.item.CreativeModeTab; import net.minecraft.world.item.ItemStack; import org.quiltmc.loader.api.ModContainer; import org.quiltmc.qsl.base.api.entrypoint.ModInitializer; +import org.quiltmc.qsl.lifecycle.api.event.ServerLifecycleEvents; import org.quiltmc.qsl.resource.loader.api.ResourceLoader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,6 +44,9 @@ public class ErisAlchemy implements ModInitializer { ResourceLoader.get(PackType.SERVER_DATA).registerReloader(EmcLoader.INSTANCE); + ServerLifecycleEvents.READY.register(Emc::initServer); + PlayerUtils.init(); + Registry.register(BuiltInRegistries.CREATIVE_MODE_TAB, new ResourceLocation(ID, "item_group"), ITEM_GROUP); ErisAlchemyRegistry.BlockEntities.consume( diff --git a/src/main/java/lv/enes/mc/eris_alchemy/ErisAlchemyRegistry.java b/src/main/java/lv/enes/mc/eris_alchemy/ErisAlchemyRegistry.java index 893e60d..e0f6e31 100644 --- a/src/main/java/lv/enes/mc/eris_alchemy/ErisAlchemyRegistry.java +++ b/src/main/java/lv/enes/mc/eris_alchemy/ErisAlchemyRegistry.java @@ -179,6 +179,9 @@ public final class ErisAlchemyRegistry { public static final class NetworkingConstants { private NetworkingConstants() {} + public static final ResourceLocation UPDATE_EMCS + = new ResourceLocation(ErisAlchemy.ID, "update_emcs"); + public static final ResourceLocation UPDATE_SYNCED_VALUE = new ResourceLocation(ErisAlchemy.ID, "update_synced_value"); } diff --git a/src/main/java/lv/enes/mc/eris_alchemy/block/entity/EnergyCondenserEntity.java b/src/main/java/lv/enes/mc/eris_alchemy/block/entity/EnergyCondenserEntity.java index a264722..432ba6f 100644 --- a/src/main/java/lv/enes/mc/eris_alchemy/block/entity/EnergyCondenserEntity.java +++ b/src/main/java/lv/enes/mc/eris_alchemy/block/entity/EnergyCondenserEntity.java @@ -81,8 +81,8 @@ public class EnergyCondenserEntity extends ChestLikeEntity implements ExtendedSc @Override public void tick(Level world, BlockPos pos, BlockState state) { super.tick(world, pos, state); - Emc.getInstance(world).get(items.get(0)).ifPresent(cost -> { - tryConsumeEmc(world, cost); + Emc.get(items.get(0)).ifPresent(cost -> { + tryConsumeEmc(cost); tryCloneTemplate(cost); }); @@ -123,17 +123,16 @@ public class EnergyCondenserEntity extends ChestLikeEntity implements ExtendedSc ); } - private void tryConsumeEmc(Level world, double cost) { + private void tryConsumeEmc(double cost) { if (cost <= getStoredEmc()) { return; } - var emc = Emc.getInstance(world); var template = items.get(0); var sacrifice = items.stream() .skip(1) // skip the template .filter(stack -> !ItemStack.isSameItemSameTags(template, stack)) - .flatMap(stack -> emc.get(stack) + .flatMap(stack -> Emc.get(stack) .stream() .mapToObj(emcValue -> new StackEmcPair(stack, emcValue))) .findFirst(); diff --git a/src/main/java/lv/enes/mc/eris_alchemy/client/ErisAlchemyClient.java b/src/main/java/lv/enes/mc/eris_alchemy/client/ErisAlchemyClient.java index f4de045..3e73c9e 100644 --- a/src/main/java/lv/enes/mc/eris_alchemy/client/ErisAlchemyClient.java +++ b/src/main/java/lv/enes/mc/eris_alchemy/client/ErisAlchemyClient.java @@ -7,6 +7,7 @@ import net.minecraft.client.renderer.blockentity.BlockEntityRenderers; import net.minecraft.network.chat.Component; import org.quiltmc.loader.api.ModContainer; import org.quiltmc.qsl.base.api.entrypoint.client.ClientModInitializer; +import org.quiltmc.qsl.lifecycle.api.client.event.ClientLifecycleEvents; import org.quiltmc.qsl.tooltip.api.client.ItemTooltipCallback; @SuppressWarnings("unused") @@ -17,12 +18,14 @@ public class ErisAlchemyClient implements ClientModInitializer { ErisAlchemyClientRegistry.ItemRenderers.consume(BuiltinItemRendererRegistry.INSTANCE::register); ErisAlchemyClientRegistry.MenuScreens.consume(MenuScreens::register); + ClientLifecycleEvents.READY.register(Emc::initClient); + ItemTooltipCallback.EVENT.register((stack, player, context, tooltip) -> { if (player == null) { return; } - var emc = Emc.getInstance(player.level()).get(stack); + var emc = Emc.get(stack); emc.ifPresent(value -> tooltip.add(Component.literal("EMC %s".formatted(Emc.formatEmc(value))))); }); } diff --git a/src/main/java/lv/enes/mc/eris_alchemy/utils/BufUtils.java b/src/main/java/lv/enes/mc/eris_alchemy/utils/BufUtils.java new file mode 100644 index 0000000..9d8fe82 --- /dev/null +++ b/src/main/java/lv/enes/mc/eris_alchemy/utils/BufUtils.java @@ -0,0 +1,20 @@ +package lv.enes.mc.eris_alchemy.utils; + +import io.netty.buffer.ByteBuf; +import net.minecraft.network.FriendlyByteBuf; + +import java.util.OptionalDouble; + +public final class BufUtils { + private BufUtils() {} + + public static OptionalDouble readOptionalDouble(FriendlyByteBuf buf) { + return buf.readOptional(ByteBuf::readDouble).stream().mapToDouble(x -> x).findFirst(); + } + + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + public static void writeOptionalDouble(FriendlyByteBuf buf, OptionalDouble value) { + var convert = value.stream().boxed().findFirst(); + buf.writeOptional(convert, ByteBuf::writeDoubleLE); + } +} diff --git a/src/main/java/lv/enes/mc/eris_alchemy/utils/PlayerUtils.java b/src/main/java/lv/enes/mc/eris_alchemy/utils/PlayerUtils.java new file mode 100644 index 0000000..6320d6f --- /dev/null +++ b/src/main/java/lv/enes/mc/eris_alchemy/utils/PlayerUtils.java @@ -0,0 +1,22 @@ +package lv.enes.mc.eris_alchemy.utils; + +import net.minecraft.server.level.ServerPlayer; +import org.quiltmc.qsl.networking.api.ServerPlayConnectionEvents; + +import java.util.HashSet; +import java.util.Set; + +public final class PlayerUtils { + private PlayerUtils() {} + + private static final Set ALL_PLAYERS = new HashSet<>(); + + public static Set all() { + return ALL_PLAYERS; + } + + public static void init() { + ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> ALL_PLAYERS.add(handler.getPlayer())); + ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> ALL_PLAYERS.remove(handler.getPlayer())); + } +} -- cgit v1.2.3