diff --git a/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java b/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java index 70f2e1e14..73ea97eba 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java +++ b/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java @@ -15,8 +15,10 @@ import org.apache.logging.log4j.Logger; import ru.dbotthepony.mc.otm.capability.AndroidCapability; import ru.dbotthepony.mc.otm.capability.AndroidCapabilityPlayer; import ru.dbotthepony.mc.otm.capability.MatteryCapability; +import ru.dbotthepony.mc.otm.capability.drive.DrivePool; import ru.dbotthepony.mc.otm.client.AndroidGui; import ru.dbotthepony.mc.otm.client.EventHandler; +import ru.dbotthepony.mc.otm.item.ItemPortableCondensationDrive; import ru.dbotthepony.mc.otm.matter.MatterGrid; import ru.dbotthepony.mc.otm.matter.MatterRegistry; import ru.dbotthepony.mc.otm.network.MatteryNetworking; @@ -58,6 +60,9 @@ public class OverdriveThatMatters { FMLJavaModLoadingContext.get().getModEventBus().register(Registry.AndroidResearch.class); FMLJavaModLoadingContext.get().getModEventBus().register(Registry.Stats.class); + MinecraftForge.EVENT_BUS.register(DrivePool.class); + MinecraftForge.EVENT_BUS.register(ItemPortableCondensationDrive.class); + FMLJavaModLoadingContext.get().getModEventBus().addListener(AndroidCapability::registerEffects); // LOGGER.info("Registered event handlers"); diff --git a/src/main/java/ru/dbotthepony/mc/otm/Registry.java b/src/main/java/ru/dbotthepony/mc/otm/Registry.java index 5d05cc08a..4db08fb1d 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/Registry.java +++ b/src/main/java/ru/dbotthepony/mc/otm/Registry.java @@ -29,10 +29,7 @@ import ru.dbotthepony.mc.otm.android.feature.AndroidNanobotsRegeneration; import ru.dbotthepony.mc.otm.block.*; import ru.dbotthepony.mc.otm.block.entity.*; import ru.dbotthepony.mc.otm.android.AndroidFeatureType; -import ru.dbotthepony.mc.otm.item.ItemBattery; -import ru.dbotthepony.mc.otm.item.ItemMatterCapacitor; -import ru.dbotthepony.mc.otm.item.ItemPatternStorage; -import ru.dbotthepony.mc.otm.item.ItemPill; +import ru.dbotthepony.mc.otm.item.*; import ru.dbotthepony.mc.otm.menu.*; import ru.dbotthepony.mc.otm.screen.*; @@ -113,6 +110,8 @@ public class Registry { public static final ResourceLocation NUTRIENT_PASTE = new ResourceLocation(OverdriveThatMatters.MOD_ID, "nutrient_paste"); + public static final ResourceLocation PORTABLE_CONDENSATION_DRIVE = new ResourceLocation(OverdriveThatMatters.MOD_ID, "portable_condensation_drive"); + // android features and research public static final ResourceLocation AIR_BAGS = new ResourceLocation(OverdriveThatMatters.MOD_ID, "air_bags"); @@ -234,6 +233,8 @@ public class Registry { public static final ItemPatternStorage PATTERN_DRIVE_NORMAL = new ItemPatternStorage(4); public static final ItemPatternStorage PATTERN_DRIVE_CREATIVE = new ItemPatternStorage(); + public static final ItemPortableCondensationDrive PORTABLE_CONDENSATION_DRIVE = new ItemPortableCondensationDrive(); + public static final Item NUTRIENT_PASTE = new Item(new Item.Properties().stacksTo(64).food(new FoodProperties.Builder().meat().nutrition(8).saturationMod(0.9F).build()).tab(OverdriveThatMatters.CREATIVE_TAB)); static { @@ -262,6 +263,7 @@ public class Registry { PATTERN_DRIVE_NORMAL.setRegistryName(Names.PATTERN_DRIVE_NORMAL); PATTERN_DRIVE_CREATIVE.setRegistryName(Names.PATTERN_DRIVE_CREATIVE); + PORTABLE_CONDENSATION_DRIVE.setRegistryName(Names.PORTABLE_CONDENSATION_DRIVE); NUTRIENT_PASTE.setRegistryName(Names.NUTRIENT_PASTE); } @@ -293,6 +295,7 @@ public class Registry { event.getRegistry().register(PATTERN_DRIVE_NORMAL); event.getRegistry().register(PATTERN_DRIVE_CREATIVE); + event.getRegistry().register(PORTABLE_CONDENSATION_DRIVE); event.getRegistry().register(NUTRIENT_PASTE); diff --git a/src/main/java/ru/dbotthepony/mc/otm/capability/MatteryCapability.java b/src/main/java/ru/dbotthepony/mc/otm/capability/MatteryCapability.java index 620ea8f73..b90d216fa 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/capability/MatteryCapability.java +++ b/src/main/java/ru/dbotthepony/mc/otm/capability/MatteryCapability.java @@ -4,6 +4,7 @@ import net.minecraftforge.common.capabilities.Capability; import net.minecraftforge.common.capabilities.CapabilityInject; import net.minecraftforge.common.capabilities.CapabilityManager; import net.minecraftforge.energy.IEnergyStorage; +import ru.dbotthepony.mc.otm.capability.drive.IMatteryDrive; import java.math.BigDecimal; import java.math.MathContext; @@ -28,6 +29,9 @@ public class MatteryCapability { @CapabilityInject(IMatterTaskProvider.class) public static Capability TASK = null; + @CapabilityInject(IMatteryDrive.class) + public static Capability DRIVE = null; + public static void register() { CapabilityManager.INSTANCE.register(IAndroidCapability.class); CapabilityManager.INSTANCE.register(IMatteryEnergyStorage.class); @@ -35,6 +39,7 @@ public class MatteryCapability { CapabilityManager.INSTANCE.register(IPatternStorage.class); CapabilityManager.INSTANCE.register(IMatterGridCell.class); CapabilityManager.INSTANCE.register(IMatterTaskProvider.class); + CapabilityManager.INSTANCE.register(IMatteryDrive.class); } public static final MathContext ROUND_RULES = new MathContext(32, RoundingMode.HALF_DOWN); diff --git a/src/main/java/ru/dbotthepony/mc/otm/capability/drive/DrivePool.java b/src/main/java/ru/dbotthepony/mc/otm/capability/drive/DrivePool.java new file mode 100644 index 000000000..042234838 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/capability/drive/DrivePool.java @@ -0,0 +1,267 @@ +package ru.dbotthepony.mc.otm.capability.drive; + +import net.minecraft.CrashReport; +import net.minecraft.ReportedException; +import net.minecraft.Util; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtIo; +import net.minecraft.server.MinecraftServer; +import net.minecraftforge.event.TickEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fmlserverevents.FMLServerStartedEvent; +import net.minecraftforge.fmlserverevents.FMLServerStoppingEvent; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import ru.dbotthepony.mc.otm.OverdriveThatMatters; + +import javax.annotation.Nullable; +import java.io.File; +import java.lang.reflect.Array; +import java.security.Provider; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.locks.LockSupport; +import java.util.function.Function; +import java.util.function.Supplier; + + +/** + * Let me answer everyone's questions about: + * + * Why? + * + * There are several reasons: + * 0. This data can get very large very quickly, even when playing singleplayer (and much quicker and bigger when hosting a dedicated server) + * 1. This data can not be stored inside ItemStack.ForgeCaps due to it's size + * 2. This data can not be stored inside unshared (server only) nbt tag because, again, mods prone to use and interact with + * it wrong, causing loss of stored data or mods exposing full content of a drive inside their own tag (which cause very real "NBT size too large" + * network kicks, often locking players out of server + * 3. net.minecraft.world.level.saveddata.SaveData is for storing everything inside one dat file, which + * is performance tanking, because we have to write *entire* NBT on each save, not the data of Drive which got changed + */ +public class DrivePool { + private static final Logger LOGGER = LogManager.getLogger(); + + public static class Entry { + public final UUID id; + public final IMatteryDrive drive; + public long last_access = System.currentTimeMillis(); + public long soft_sync_on = last_access + 8_000; // soft sync in 4 seconds + public long hard_sync_on = last_access + 30_000; // hard sync in 4 seconds + + Entry(UUID id, IMatteryDrive drive) { + this.id = id; + this.drive = drive; + } + + @Nullable + public CompoundTag sync(boolean force) { + if (!drive.isDirty()) { + soft_sync_on = System.currentTimeMillis() + 8_000; + hard_sync_on = System.currentTimeMillis() + 30_000; + next_save = Math.min(next_save, soft_sync_on); + return null; + } + + if (force || hard_sync_on <= System.currentTimeMillis() || soft_sync_on <= System.currentTimeMillis()) { + var tag = drive.serializeNBT(); + drive.markClean(); + soft_sync_on = System.currentTimeMillis() + 8_000; + hard_sync_on = System.currentTimeMillis() + 30_000; + next_save = Math.min(next_save, soft_sync_on); + last_access = System.currentTimeMillis(); + return tag; + } + + return null; + } + + public IMatteryDrive access() { + last_access = System.currentTimeMillis(); + soft_sync_on = last_access + 8_000; + next_save = Math.min(hard_sync_on, Math.min(next_save, soft_sync_on)); + return drive; + } + } + + private static HashMap pool = new HashMap<>(); + private static Thread thread; + private static boolean stopping = false; + private static ReportedException exception; + public static final String DATA_PATH = "data/otm_drives"; + + private static long next_save = Long.MAX_VALUE; + + public static T get(UUID id, Function factory_stored, Supplier factory_empty) { + var get = pool.get(id); + + if (get != null) { + return (T) get.access(); + } + + var file = new File(base_directory, id.toString() + ".dat"); + + if (file.exists()) { + try { + var factorize = factory_stored.apply(NbtIo.readCompressed(file)); + pool.put(id, new Entry(id, factorize)); + return factorize; + } catch(Throwable error) { + var report = new CrashReport("Reading OTM Drive from disk", error); + var category = report.addCategory("Corrupt drive"); + category.setDetail("UUID", id); + throw new ReportedException(report); + } + } + + var factorize = factory_empty.get(); + pool.put(id, new Entry(id, factorize)); + + return factorize; + } + + public static void put(UUID id, IMatteryDrive drive) { + pool.put(id, new Entry(id, drive)); + } + + private record BacklogLine(UUID file, CompoundTag tag, IMatteryDrive drive) { + @Override + public boolean equals(Object obj) { + return obj instanceof BacklogLine line && line.file.equals(file); + } + } + + private static List backlog = new ArrayList<>(); + private static File base_directory; + + private static void thread(MinecraftServer server) { + while (true) { + LockSupport.park(); + + if (stopping) { + return; + } + + try { + sync(); + } catch (ReportedException error) { + exception = error; + return; + } + } + } + + @SubscribeEvent + public static void onServerPostTick(TickEvent.ServerTickEvent event) { + if (event.phase == TickEvent.Phase.END) { + if (exception != null) { + throw exception; + } + + //if (next_save < System.currentTimeMillis()) { + writeBacklog(true); + //} + } + } + + @SubscribeEvent + public static void serverStartEvent(FMLServerStartedEvent event) { + if (thread != null && thread.isAlive()) { + LOGGER.error("Attempting to start another DrivePool I/O thread when already running one!"); + return; + } + + pool.clear(); + + final var server = event.getServer(); + base_directory = new File(server.storageSource.getWorldDir().toFile(), DATA_PATH); + base_directory.mkdirs(); + + thread = new Thread(null, () -> DrivePool.thread(server), "Overdrive That Matters DrivePool I/O"); + thread.start(); + } + + @SubscribeEvent + public static void serverStopEvent(FMLServerStoppingEvent event) { + if (thread != null && thread.isAlive()) { + stopping = true; + LockSupport.unpark(thread); + } + + writeBacklog(true); + sync(); + + pool.clear(); + } + + public static void writeBacklog(boolean force) { + var sync_required = false; + ArrayList remove_keys = null; + + for (var entry : pool.entrySet()) { + var tag = entry.getValue().sync(force); + + if (tag != null) { + LOGGER.info("Syncing {}", entry.getKey()); + + var index = backlog.indexOf(entry.getKey()); + + if (index != -1) { + backlog.set(index, new BacklogLine(entry.getKey(), tag, entry.getValue().drive)); + } else { + backlog.add(new BacklogLine(entry.getKey(), tag, entry.getValue().drive)); + } + + sync_required = true; + } else if (entry.getValue().last_access < System.currentTimeMillis() - 600_000) { + // no access or changes in 600 seconds - unload + if (remove_keys == null) + remove_keys = new ArrayList<>(); + + remove_keys.add(entry.getKey()); + } + } + + if (remove_keys != null) { + for (var key : remove_keys) { + pool.remove(key); + } + } + + if (sync_required) { + LockSupport.unpark(thread); + } + + if (pool.size() == 0) { + next_save = Long.MAX_VALUE; + } + } + + public static void sync() { + var copy = backlog; + backlog = new ArrayList<>(); + + for (var entry : copy) { + try { + var file = new File(base_directory, entry.file.toString() + ".dat"); + LOGGER.info("Writing {}", entry.file); + NbtIo.writeCompressed(entry.tag, file); + } catch(Throwable error) { + var report = new CrashReport("Syncing OTM Drives state to disk", error); + var category = report.addCategory("Corrupt drive"); + category.setDetail("UUID", entry.file.toString()); + category.setDetail("Stored item count", entry.drive.getStoredCount()); + category.setDetail("Capacity", entry.drive.getCapacity()); + + if (entry.drive instanceof MatteryDrive drive) { + category.setDetail("Amount of different stacks", drive.different_stacks); + category.setDetail("Max amount of different stacks", drive.max_different_stacks); + } + + throw new ReportedException(report); + } + } + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/capability/drive/IMatteryDrive.java b/src/main/java/ru/dbotthepony/mc/otm/capability/drive/IMatteryDrive.java new file mode 100644 index 000000000..a0a77822c --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/capability/drive/IMatteryDrive.java @@ -0,0 +1,50 @@ +package ru.dbotthepony.mc.otm.capability.drive; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.common.util.INBTSerializable; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; +import java.util.UUID; +import java.util.function.Predicate; + +public interface IMatteryDrive { + List getItems(); + + boolean isDirty(); + void markDirty(); + void markClean(); + + /** + * @param item + * @return all items belonging to passed class + */ + @Nonnull + List findItems(@Nonnull Item item); + + /** + * @param stack + * @return all items that match this itemstack + */ + @Nonnull + List findItems(@Nonnull ItemStack stack); + + @Nullable + StoredStack getItem(@Nonnull UUID id); + + int getStoredCount(); + int getCapacity(); + + @Nonnull + ItemStack insertItem(@Nonnull ItemStack item, boolean simulate); + + @Nonnull + ItemStack extractItem(@Nonnull UUID id, int amount, boolean simulate); + + // not extending INBTSerializable to avoid serializing it as forgecaps + CompoundTag serializeNBT(); + void deserializeNBT(CompoundTag nbt); +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/capability/drive/MatteryDrive.java b/src/main/java/ru/dbotthepony/mc/otm/capability/drive/MatteryDrive.java new file mode 100644 index 000000000..1867e3e8b --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/capability/drive/MatteryDrive.java @@ -0,0 +1,277 @@ +package ru.dbotthepony.mc.otm.capability.drive; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.Tag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraftforge.registries.RegistryManager; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.*; + +public class MatteryDrive implements IMatteryDrive { + protected final HashMap> items = new HashMap<>(); + protected final HashMap items_uuid = new HashMap<>(); + + protected boolean dirty = false; + protected int different_stacks = 0; + protected int stored = 0; + protected int max_different_stacks; + protected int capacity; + + public MatteryDrive(int capacity, int max_different_stacks) { + this.capacity = capacity; + this.max_different_stacks = max_different_stacks; + } + + public MatteryDrive(int capacity) { + this(capacity, 0xFFFF); + } + + @Override + public List getItems() { + int amount = 0; + + for (var list : items.values()) { + amount += list.size(); + } + + var output = new ArrayList(amount); + int i = 0; + + for (var list : items.values()) { + for (var stack : list) { + output.set(i, stack); + i++; + } + } + + return output; + } + + @Override + public boolean isDirty() { + return dirty; + } + + @Override + public void markDirty() { + dirty = true; + } + + @Override + public void markClean() { + dirty = false; + } + + @Override + public int getStoredCount() { + return stored; + } + + @Override + public int getCapacity() { + return capacity; + } + + @Override + @Nonnull + public List findItems(@Nonnull Item item) { + var list = items.get(item); + + if (list != null) + return List.copyOf(list); + + return List.of(); + } + + @Override + @Nonnull + public List findItems(@Nonnull ItemStack stack) { + var list = items.get(stack.getItem()); + + if (list == null) + return List.of(); + + var amount = 0; + + for (var _stack : list) { + if (ItemStack.tagMatches(_stack.stack(), stack)) { + amount++; + } + } + + var build_list = new ArrayList(amount); + + for (var _stack : list) { + if (ItemStack.tagMatches(_stack.stack(), stack)) { + build_list.add(_stack); + } + } + + return build_list; + } + + @Nullable + @Override + public StoredStack getItem(@Nonnull UUID id) { + return items_uuid.get(id); + } + + @Nonnull + @Override + public ItemStack insertItem(@Nonnull ItemStack item, boolean simulate) { + int max_insert = Math.min(getCapacity() - getStoredCount(), item.getCount()); + + if (max_insert <= 0) + return item; + + var listing = items.computeIfAbsent(item.getItem(), (key) -> new ArrayList<>()); + + for (var state : listing) { + if (ItemStack.tagMatches(state.stack(), item)) { + if (!simulate) { + state.stack().grow(max_insert); + stored += max_insert; + markDirty(); + } + + var copy_item = item.copy(); + copy_item.shrink(max_insert); + return copy_item; + } + } + + if (different_stacks >= max_different_stacks) { + return item; + } + + if (!simulate) { + different_stacks++; + var copy = item.copy(); + copy.setCount(max_insert); + var state = new StoredStack(copy, UUID.randomUUID()); + listing.add(state); + items_uuid.put(state.id(), state); + markDirty(); + } + + var copy_item = item.copy(); + copy_item.shrink(max_insert); + return copy_item; + } + + @Nonnull + @Override + public ItemStack extractItem(@Nonnull UUID id, int amount, boolean simulate) { + var get = items_uuid.get(id); + + if (get == null) + return ItemStack.EMPTY; + + var extract = Math.min(amount, get.stack().getCount()); + + if (extract <= 0) + return ItemStack.EMPTY; + + if (!simulate) { + if (extract == get.stack().getCount()) { + var listing = items.get(get.stack().getItem()); + listing.remove(get); + } + + get.stack().shrink(extract); + markDirty(); + } + + var copy = get.stack().copy(); + copy.setCount(extract); + return copy; + } + + @Override + public CompoundTag serializeNBT() { + final var compound = new CompoundTag(); + + compound.putInt("capacity", capacity); + compound.putInt("max_different_stacks", max_different_stacks); + + var list_items = new ListTag(); + compound.put("items", list_items); + + for (var entry : items.entrySet()) { + var item = Objects.requireNonNull(entry.getKey().getRegistryName(), "Missing registry name for stored Item").toString(); + var entry_compound = new CompoundTag(); + + entry_compound.putString("id", item); + var stack_list = new ListTag(); + entry_compound.put("list", stack_list); + + for (var stack : entry.getValue()) { + var stack_nbt = new CompoundTag(); + stack_list.add(stack_nbt); + + stack_nbt.putInt("count", stack.stack().getCount()); + stack_nbt.putLongArray("uuid", new long[] { stack.id().getMostSignificantBits(), stack.id().getLeastSignificantBits() }); + + if (stack.stack().getTag() != null) { + stack_nbt.put("data", stack.stack().getTag()); + } + } + + list_items.add(entry_compound); + } + + return compound; + } + + @Override + public void deserializeNBT(CompoundTag nbt) { + items.clear(); + items_uuid.clear(); + stored = 0; + different_stacks = 0; + + capacity = nbt.getInt("capacity"); + max_different_stacks = nbt.getInt("max_different_stacks"); + + var list_items = nbt.getList("items", Tag.TAG_COMPOUND); + var registry = RegistryManager.ACTIVE.getRegistry(Item.class); + + for (var _entry : list_items) { + if (_entry instanceof CompoundTag entry) { + var item = registry.getValue(new ResourceLocation(entry.getString("id"))); + + if (item != null && item != Items.AIR) { + var map_list = items.computeIfAbsent(item, (key) -> new ArrayList<>()); + + var stack_list = entry.getList("list", Tag.TAG_COMPOUND); + + for (var _stack : stack_list) { + if (_stack instanceof CompoundTag stack) { + var count = stack.getInt("count"); + var _uuid = stack.getLongArray("uuid"); + var data = stack.get("data"); + + var itemstack = new ItemStack(item, count); + + if (data != null) { + itemstack.setTag((CompoundTag) data); + } + + stored += count; + different_stacks += 1; + var state = new StoredStack(itemstack, new UUID(_uuid[0], _uuid[1])); + map_list.add(state); + items_uuid.put(state.id(), state); + } + } + } + } + } + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/capability/drive/StoredStack.java b/src/main/java/ru/dbotthepony/mc/otm/capability/drive/StoredStack.java new file mode 100644 index 000000000..901c68237 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/capability/drive/StoredStack.java @@ -0,0 +1,8 @@ +package ru.dbotthepony.mc.otm.capability.drive; + +import net.minecraft.world.item.ItemStack; + +import java.util.UUID; + +public record StoredStack(ItemStack stack, UUID id) { +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/item/ItemPortableCondensationDrive.java b/src/main/java/ru/dbotthepony/mc/otm/item/ItemPortableCondensationDrive.java new file mode 100644 index 000000000..1ace58cfe --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/item/ItemPortableCondensationDrive.java @@ -0,0 +1,95 @@ +package ru.dbotthepony.mc.otm.item; + +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.capabilities.ICapabilityProvider; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.event.entity.player.EntityItemPickupEvent; +import net.minecraftforge.event.entity.player.PlayerEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import ru.dbotthepony.mc.otm.OverdriveThatMatters; +import ru.dbotthepony.mc.otm.capability.MatteryCapability; +import ru.dbotthepony.mc.otm.capability.drive.DrivePool; +import ru.dbotthepony.mc.otm.capability.drive.IMatteryDrive; +import ru.dbotthepony.mc.otm.capability.drive.MatteryDrive; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.UUID; + +public class ItemPortableCondensationDrive extends Item { + public ItemPortableCondensationDrive() { + super(new Properties().stacksTo(1).tab(OverdriveThatMatters.CREATIVE_TAB)); + } + + @SubscribeEvent + public static void onPickupEvent(EntityItemPickupEvent event) { + for (var stack : event.getPlayer().getInventory().items) { + if (stack.getItem() instanceof ItemPortableCondensationDrive) { + var cap = stack.getCapability(MatteryCapability.DRIVE).resolve().get(); + + var remaining = cap.insertItem(event.getItem().getItem(), false); + + if (remaining.getCount() == event.getItem().getItem().getCount()) { + continue; + } + + event.setCanceled(true); + + if (remaining.getCount() > 0) { + event.getItem().getItem().setCount(remaining.getCount()); + } else { + event.getItem().discard(); + return; + } + } + } + } + + private static class DriveCapability implements ICapabilityProvider { + private UUID uuid; + private final ItemStack stack; + private final LazyOptional resolver = LazyOptional.of(() -> { + return DrivePool.get(uuid, (tag) -> { + var drive = new MatteryDrive(4000); + drive.deserializeNBT(tag); + return drive; + }, () -> new MatteryDrive(4000)); + }); + + DriveCapability(ItemStack stack) { + this.stack = stack; + } + + @Nonnull + @Override + public LazyOptional getCapability(@Nonnull Capability cap, @Nullable Direction side) { + if (cap == MatteryCapability.DRIVE) { + if (uuid == null) { + var get_value = stack.getOrCreateTag().getLongArray("uuid"); + + if (get_value.length == 2) { + uuid = new UUID(get_value[0], get_value[1]); + } else { + uuid = UUID.randomUUID(); + stack.getOrCreateTag().putLongArray("uuid", new long[] { uuid.getMostSignificantBits(), uuid.getLeastSignificantBits() }); + } + } + + return resolver.cast(); + } + + return LazyOptional.empty(); + } + } + + @Nullable + @Override + public ICapabilityProvider initCapabilities(ItemStack stack, @Nullable CompoundTag nbt) { + return new DriveCapability(stack); + } +} diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index d57cc8b56..f769c32e1 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -1,5 +1,6 @@ # Make Screen.title be public and easily modifiable public-f net.minecraft.client.gui.screens.Screen f_96539_ # title +public net.minecraft.server.MinecraftServer f_129744_ # storageSource # for accessing and setting from MatteryScreen class # protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97724_ # lastQuickMoved