diff --git a/.github/workflows/build-and-test.yaml b/.github/workflows/build-and-test.yaml new file mode 100644 index 000000000..f3c7b7f7e --- /dev/null +++ b/.github/workflows/build-and-test.yaml @@ -0,0 +1,13 @@ +name: Build and Test + +on: + workflow_dispatch: + push: + pull_request_target: + types: + - opened + - synchronize + +jobs: + build-and-test: + uses: IntelligenceModding/actions/.github/workflows/build-and-test.yaml@master \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index febbe555a..ca4076697 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [1.19.2-0.7.35r] - 2024-04-12 + +### Fixed +- [#559] Fixed the creation of empty nbt tags when inserting items into the ME System - @Thanks to michele-grifa! +- [#577,#570] Fixed that the RS bridge does not show `isCraftable` in `getItem` or `getPattern` - Thanks to @tomprince! +- [#570] Improved performance of ME Bridge's iterating functions by making the functions not quadratic - Thanks to @tomprince! +- [#560] Fixed stacking problems when using `getItems` from the inventory manager +- [#551] Fixed that RS Bridge's `isItemCrafting` is false when the item is not in the system +- [#536] Invalidate the energy detectors in and output energy providers to prevent the energy detector from stopping to transfer energy +- [#575] Added support for more disk cells and portable cells for the ME Bridge - Thanks to @iTrooz + +### Added +- [#571] Added `selectionMode` to the output of ME Bridge's `getCraftingCPUs` function - Thanks to @tomprince! +- [#564] Added optional parameter to `getPlayerPos` to specify the amount of decimal places to retrieve the position of - Thanks to @minecraf7771 + ## [1.19.2-0.7.34r] - 2024-02-13 ### Fixed diff --git a/build.gradle b/build.gradle index 0c0f6b888..201d7912d 100644 --- a/build.gradle +++ b/build.gradle @@ -2,38 +2,24 @@ import net.darkhax.curseforgegradle.TaskPublishCurseForge import java.text.SimpleDateFormat -buildscript { - repositories { - maven { - name = "Intelligence Minecraft" - url = "https://mvn.intelligence-modding.de/Minecraft" - } - mavenCentral() - gradlePluginPortal() - } - dependencies { - classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '6.0+', changing: true - classpath 'org.parchmentmc:librarian:1.+' - } -} - plugins { id "maven-publish" id 'net.darkhax.curseforgegradle' version '1.1.16' id 'org.jetbrains.changelog' version '1.2.1' id "com.modrinth.minotaur" version "2.+" + id "org.jetbrains.kotlin.jvm" version "1.6.10" + id 'net.minecraftforge.gradle' version '[6.0.18,6.2)' + id 'org.parchmentmc.librarian.forgegradle' version '1.+' + id 'org.spongepowered.mixin' version '0.7.+' id "com.github.breadmoirai.github-release" version "2.5.2" id 'checkstyle' id 'java' } -apply plugin: 'net.minecraftforge.gradle' -apply plugin: 'org.parchmentmc.librarian.forgegradle' - java.toolchain.languageVersion = JavaLanguageVersion.of(17) wrapper { - gradleVersion = '8.1.1' + gradleVersion = '8.4' distributionType = Wrapper.DistributionType.ALL } @@ -187,45 +173,59 @@ repositories { maven { name = "Blamejared maven botania patchouli" url = 'https://maven.blamejared.com' + content { + includeGroup("vazkii.botania") + includeGroup("vazkii.patchouli") + } } maven { name = "Squiddev maven cct" url = 'https://squiddev.cc/maven/' content { includeGroup("org.squiddev") + includeGroup("cc.tweaked") includeModule("org.squiddev", "Cobalt") } } maven { name = "Theillusivec4 maven curios" url = "https://maven.theillusivec4.top/" + content { + includeGroup("top.theillusivec4.curios") + } } maven { name = "LDT Team minecolonies" url = 'https://ldtteam.jfrog.io/ldtteam/modding' + content { + includeGroup("com.ldtteam") + } } maven { name = "Modmaven Jei" url = 'https://modmaven.dev/' - } - maven { - name = "Create maven" - url = "https://maven.tterrag.com/" content { - includeGroup "com.simibubi.create" - includeGroup "com.jozufozu.flywheel" + includeGroup("mezz.jei") + includeGroup("appeng") + includeGroup("mekanism") } } maven { - name = "SirEdvin's private repository" - url = "https://repo.repsy.io/mvn/siredvin/default" + name = "Create maven" + url = "https://maven.tterrag.com/" content { - includeGroup "site.siredvin.ttoolkit" + includeGroup("com.simibubi.create") + includeGroup("com.jozufozu.flywheel") + includeGroup("com.tterrag.registrate") } } maven { name = "Shedaniel cloth" url = "https://maven.shedaniel.me/" + content { + includeGroup("dev.architectury") + includeGroup("me.shedaniel.cloth") + } } maven { url = uri("https://maven.pkg.github.com/refinedmods/refinedstorage") @@ -233,10 +233,16 @@ repositories { username = "anything" password = "\u0067hp_oGjcDFCn8jeTzIj4Ke9pLoEVtpnZMP4VQgaX" } + content { + includeModule("com.refinedmods", "refinedstorage") + } } maven { name = 'Kotlin for Forge' url = 'https://thedarkcolour.github.io/KotlinForForge/' + content { + includeModule("thedarkcolour", "kotlinforforge") + } } maven { url = "https://cursemaven.com" @@ -244,10 +250,6 @@ repositories { includeGroup "curse.maven" } } - maven { - name = "Intelligence repository" - url = "https://mvn.intelligence-modding.de/Intelligence" - } } configurations { @@ -349,6 +351,10 @@ dependencies { // Testing stuff // JEI implementation fg.deobf("mezz.jei:jei-${jei_version}") + + // Create Crafts & Additions + compileOnly fg.deobf("curse.maven:createaddition-439890:5099757") + // runtimeOnly fg.deobf("curse.maven:createaddition-439890:5099757") } @@ -472,6 +478,8 @@ tasks.register('publishCurseForge', TaskPublishCurseForge, { task -> addRequirement('cc-tweaked') + addModLoader("forge", "neoforge") + releaseType = "${release_type}" } }) @@ -505,7 +513,7 @@ modrinth { versionType = release_type uploadFile = jar gameVersions = [minecraft_version] - loaders = ["forge"] + loaders = ["forge", "neoforge"] dependencies { required.project "cc-tweaked" } @@ -579,4 +587,4 @@ publishing { } } } -} \ No newline at end of file +} diff --git a/gradle.properties b/gradle.properties index 405301743..d03e53a5b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,7 +7,7 @@ mod_id=advancedperipherals minecraft_version=1.19.2 forge_version=43.3.8 loader_version=43 -mod_version=0.7.35r +mod_version=0.7.36r release_type=release mappings_channel=parchment mappings_version=2022.11.27-1.19.2 @@ -26,7 +26,8 @@ appliedenergistics_version=12.9.5 patchouli_version=1.19.2-77 refinedstorage_version=1.11.6 botania_version=1.19.2-440-FORGE -create_version=0.5.1.b-30 +create_version=0.5.1.f-46 +createca_version=5099757 mekanism_version=1.19.2-10.3.9.13 ae2things_version=4367610 powah_version=4183078 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 8707e8b50..309b4e18d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 000000000..6b39aa618 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,20 @@ +pluginManagement { + repositories { + gradlePluginPortal() + maven { + name = 'MinecraftForge' + url = 'https://maven.minecraftforge.net/' + content { + includeGroup("net.minecraftforge") + includeGroup("net.minecraftforge.gradle") + includeGroup("org.spongepowered.mixin") + includeGroup("org.spongepowered") + } + } + maven { url = 'https://maven.parchmentmc.org' } + } +} + +plugins { + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0' +} \ No newline at end of file diff --git a/src/main/java/de/srendi/advancedperipherals/common/addons/appliedenergistics/AppEngApi.java b/src/main/java/de/srendi/advancedperipherals/common/addons/appliedenergistics/AppEngApi.java index 37cf6faf2..10173f316 100644 --- a/src/main/java/de/srendi/advancedperipherals/common/addons/appliedenergistics/AppEngApi.java +++ b/src/main/java/de/srendi/advancedperipherals/common/addons/appliedenergistics/AppEngApi.java @@ -10,9 +10,10 @@ import appeng.api.storage.AEKeyFilter; import appeng.api.storage.IStorageProvider; import appeng.api.storage.MEStorage; +import appeng.api.storage.cells.IBasicCellItem; import appeng.blockentity.storage.DriveBlockEntity; -import appeng.items.storage.BasicStorageCell; import appeng.parts.storagebus.StorageBusPart; +import com.the9grounds.aeadditions.item.storage.StorageCell; import com.the9grounds.aeadditions.item.storage.SuperStorageCell; import dan200.computercraft.shared.util.NBTUtil; import de.srendi.advancedperipherals.AdvancedPeripherals; @@ -84,35 +85,33 @@ public static Pair findAEFluidFromFilter(MEStorage monitor, @N return null; } - public static List listStacks(MEStorage monitor, ICraftingService service, int flag) { + public static List listStacks(MEStorage monitor, ICraftingService service) { List items = new ArrayList<>(); KeyCounter keyCounter = monitor.getAvailableStacks(); for (Object2LongMap.Entry aeKey : keyCounter) { if (aeKey.getKey() instanceof AEItemKey itemKey) { - if (flag == 1 && aeKey.getLongValue() < 0) { - continue; - } else if (flag == 2 && !service.isCraftable(itemKey)) { - service.getCraftables(AEKeyFilter.none()).forEach(aeKey1 -> { - Map itemObject = getObjectFromStack(Pair.of((long) 0, aeKey1), service); - if (keyCounter.get(aeKey1) == 0 && !items.contains(itemObject)) - items.add(itemObject); - }); - continue; - } - items.add(getObjectFromStack(Pair.of(aeKey.getLongValue(), itemKey), service)); } } return items; } - public static List listFluids(MEStorage monitor, ICraftingService service, int flag) { + public static List listCraftableStacks(MEStorage monitor, ICraftingService service) { + List items = new ArrayList<>(); + KeyCounter keyCounter = monitor.getAvailableStacks(); + Set craftables = service.getCraftables(AEKeyFilter.none()); + for (AEKey aeKey : craftables) { + if (aeKey instanceof AEItemKey) { + items.add(getObjectFromStack(Pair.of(keyCounter.get(aeKey), aeKey), service)); + } + } + return items; + } + + public static List listFluids(MEStorage monitor, ICraftingService service) { List items = new ArrayList<>(); for (Object2LongMap.Entry aeKey : monitor.getAvailableStacks()) { if (aeKey.getKey() instanceof AEFluidKey itemKey) { - if ((flag == 1 && aeKey.getLongValue() < 0) || (flag == 2 && !service.isCraftable(itemKey))) - continue; - items.add(getObjectFromStack(Pair.of(aeKey.getLongValue(), itemKey), service)); } } @@ -129,6 +128,18 @@ public static List listGases(MEStorage monitor, ICraftingService service return items; } + public static List listCraftableFluids(MEStorage monitor, ICraftingService service) { + List items = new ArrayList<>(); + KeyCounter keyCounter = monitor.getAvailableStacks(); + Set craftables = service.getCraftables(AEKeyFilter.none()); + for (AEKey aeKey : craftables) { + if (aeKey instanceof AEFluidKey) { + items.add(getObjectFromStack(Pair.of(keyCounter.get(aeKey), aeKey), service)); + } + } + return items; + } + public static Map getObjectFromStack(Pair stack, @Nullable ICraftingService service) { if (stack.getRight() == null) return Collections.emptyMap(); @@ -192,6 +203,8 @@ public static Map getObjectFromCPU(ICraftingCPU cpu) { map.put("isBusy", isBusy); map.put("craftingJob", cpu.getJobStatus() != null ? getObjectFromJob(cpu.getJobStatus()) : null); map.put("name", cpu.getName() != null ? cpu.getName().getString() : "Unnamed"); + map.put("selectionMode", cpu.getSelectionMode().toString()); + return map; } @@ -298,11 +311,11 @@ public static boolean isFluidCrafting(MEStorage monitor, ICraftingService grid, public static long getTotalItemStorage(IGridNode node) { long total = 0; - Iterator iterator = node.getGrid().getMachineNodes(DriveBlockEntity.class).iterator(); + // note: do not query DriveBlockEntity.class specifically here, because it will avoid subclasses, e.g. the ME Extended Drive from ExtendedAE + Iterator iterator = node.getGrid().getNodes().iterator(); while (iterator.hasNext()) { - DriveBlockEntity entity = (DriveBlockEntity) iterator.next().getService(IStorageProvider.class); - if (entity == null) + if (!(iterator.next().getService(IStorageProvider.class) instanceof DriveBlockEntity entity)) continue; InternalInventory inventory = entity.getInternalInventory(); @@ -313,7 +326,7 @@ public static long getTotalItemStorage(IGridNode node) { if (stack.isEmpty()) continue; - if (stack.getItem() instanceof BasicStorageCell cell) { + if (stack.getItem() instanceof IBasicCellItem cell) { if (cell.getKeyType().getClass().isAssignableFrom(AEKeyType.items().getClass())) { total += cell.getBytes(null); } @@ -323,6 +336,10 @@ public static long getTotalItemStorage(IGridNode node) { } } else if (APAddons.aeAdditionsLoaded && (stack.getItem() instanceof SuperStorageCell superStorageCell)) { total += superStorageCell.getKiloBytes() * 1024; + } else if (APAddons.aeAdditionsLoaded && (stack.getItem() instanceof StorageCell storageCell)) { + if (storageCell.getKeyType() != AEKeyType.items()) + continue; + total += storageCell.getKiloBytes() * 1024; } } } @@ -350,11 +367,10 @@ public static long getTotalItemStorage(IGridNode node) { public static long getTotalFluidStorage(IGridNode node) { long total = 0; - Iterator iterator = node.getGrid().getMachineNodes(DriveBlockEntity.class).iterator(); + Iterator iterator = node.getGrid().getNodes().iterator(); while (iterator.hasNext()) { - DriveBlockEntity entity = (DriveBlockEntity) iterator.next().getService(IStorageProvider.class); - if (entity == null) + if (!(iterator.next().getService(IStorageProvider.class) instanceof DriveBlockEntity entity)) continue; InternalInventory inventory = entity.getInternalInventory(); @@ -365,12 +381,16 @@ public static long getTotalFluidStorage(IGridNode node) { if (stack.isEmpty()) continue; - if (stack.getItem() instanceof BasicStorageCell cell) { + if (stack.getItem() instanceof IBasicCellItem cell) { if (cell.getKeyType().getClass().isAssignableFrom(AEKeyType.fluids().getClass())) { total += cell.getBytes(null); } } else if (APAddons.aeAdditionsLoaded && stack.getItem() instanceof SuperStorageCell superStorageCell) { total += superStorageCell.getKiloBytes() * 1024; + } else if (APAddons.aeAdditionsLoaded && (stack.getItem() instanceof StorageCell storageCell)) { + if (storageCell.getKeyType() != AEKeyType.fluids()) + continue; + total += storageCell.getKiloBytes() * 1024; } } } @@ -398,11 +418,10 @@ public static long getTotalFluidStorage(IGridNode node) { public static long getUsedItemStorage(IGridNode node) { long used = 0; - Iterator iterator = node.getGrid().getMachineNodes(DriveBlockEntity.class).iterator(); + Iterator iterator = node.getGrid().getNodes().iterator(); while (iterator.hasNext()) { - DriveBlockEntity entity = (DriveBlockEntity) iterator.next().getService(IStorageProvider.class); - if (entity == null) + if (!(iterator.next().getService(IStorageProvider.class) instanceof DriveBlockEntity entity)) continue; InternalInventory inventory = entity.getInternalInventory(); @@ -413,7 +432,7 @@ public static long getUsedItemStorage(IGridNode node) { if (stack.isEmpty()) continue; - if (stack.getItem() instanceof BasicStorageCell cell) { + if (stack.getItem() instanceof IBasicCellItem cell) { int bytesPerType = cell.getBytesPerType(null); if (cell.getKeyType().getClass().isAssignableFrom(AEKeyType.items().getClass())) { @@ -436,6 +455,14 @@ public static long getUsedItemStorage(IGridNode node) { continue; long numItemsInCell = stack.getTag().getLong("ic"); + used += numItemsInCell; + } else if (APAddons.aeAdditionsLoaded && stack.getItem() instanceof StorageCell storageCell) { + if (storageCell.getKeyType() != AEKeyType.items()) + continue; + if (stack.getTag() == null) + continue; + long numItemsInCell = stack.getTag().getLong("ic"); + used += numItemsInCell; } } @@ -460,11 +487,10 @@ public static long getUsedItemStorage(IGridNode node) { public static long getUsedFluidStorage(IGridNode node) { long used = 0; - Iterator iterator = node.getGrid().getMachineNodes(DriveBlockEntity.class).iterator(); + Iterator iterator = node.getGrid().getNodes().iterator(); while (iterator.hasNext()) { - DriveBlockEntity entity = (DriveBlockEntity) iterator.next().getService(IStorageProvider.class); - if (entity == null) + if (!(iterator.next().getService(IStorageProvider.class) instanceof DriveBlockEntity entity)) continue; InternalInventory inventory = entity.getInternalInventory(); @@ -472,7 +498,7 @@ public static long getUsedFluidStorage(IGridNode node) { for (int i = 0; i < inventory.size(); i++) { ItemStack stack = inventory.getStackInSlot(i); - if (stack.getItem() instanceof BasicStorageCell cell) { + if (stack.getItem() instanceof IBasicCellItem cell) { int bytesPerType = cell.getBytesPerType(null); if (cell.getKeyType().getClass().isAssignableFrom(AEKeyType.fluids().getClass())) { @@ -483,7 +509,15 @@ public static long getUsedFluidStorage(IGridNode node) { used += ((int) Math.ceil(((double) numBucketsInCell) / 8)) + ((long) bytesPerType * numOfType); } - } else if (APAddons.aeAdditionsLoaded && stack.getItem() instanceof SuperStorageCell superStorageCell) { + } else if (APAddons.aeAdditionsLoaded && stack.getItem() instanceof SuperStorageCell) { + if (stack.getTag() == null) + continue; + long numItemsInCell = stack.getTag().getLong("ic"); + + used += numItemsInCell; + } else if (APAddons.aeAdditionsLoaded && stack.getItem() instanceof StorageCell storageCell) { + if (storageCell.getKeyType() != AEKeyType.fluids()) + continue; if (stack.getTag() == null) continue; long numItemsInCell = stack.getTag().getLong("ic"); @@ -536,7 +570,7 @@ public static List listCells(IGridNode node) { if (stack.isEmpty()) continue; - if (stack.getItem() instanceof BasicStorageCell cell) { + if (stack.getItem() instanceof IBasicCellItem cell) { items.add(getObjectFromCell(cell, stack)); } else if (APAddons.aeThingsLoaded && stack.getItem() instanceof DISKDrive disk) { items.add(getObjectFromDisk(disk, stack)); @@ -549,7 +583,7 @@ public static List listCells(IGridNode node) { return items; } - private static Map getObjectFromCell(BasicStorageCell cell, ItemStack stack) { + private static Map getObjectFromCell(IBasicCellItem cell, ItemStack stack) { Map map = new HashMap<>(); map.put("item", ItemUtil.getRegistryKey(stack.getItem()).toString()); diff --git a/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/peripheral/ChunkyPeripheral.java b/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/peripheral/ChunkyPeripheral.java index b244e3eb1..94829b12f 100644 --- a/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/peripheral/ChunkyPeripheral.java +++ b/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/peripheral/ChunkyPeripheral.java @@ -20,7 +20,7 @@ public class ChunkyPeripheral extends BasePeripheral { public static final String PERIPHERAL_TYPE = "chunky"; private static final String UUID_TAG = "uuid"; - private @Nullable ChunkPos loadedCentralChunk; + private @Nullable ChunkPos loadedCentralChunk = null; public ChunkyPeripheral(ITurtleAccess turtle, TurtleSide side) { super(PERIPHERAL_TYPE, new TurtlePeripheralOwner(turtle, side)); @@ -45,36 +45,51 @@ public boolean isEnabled() { } public void updateChunkState() { + // TODO: should find someway to update after turtle moved or while moving, but not every tick ServerLevel level = (ServerLevel) getLevel(); ChunkManager manager = ChunkManager.get(level); ChunkPos currentChunk = getChunkPos(); - if (loadedCentralChunk == null || !loadedCentralChunk.equals(currentChunk)) { - setLoadedChunk(currentChunk, manager, level); - } else { - manager.touch(getUUID()); - } + setLoadedChunk(currentChunk, manager, level); + manager.touch(getUUID()); } protected void setLoadedChunk(@Nullable ChunkPos newChunk, ChunkManager manager, ServerLevel level) { if (loadedCentralChunk != null) { - manager.removeForceChunk(level, getUUID(), loadedCentralChunk); - //Should not be used - //level.setChunkForced(loadedChunk.x, loadedChunk.z, false); + if (loadedCentralChunk.equals(newChunk)) { + return; + } + manager.removeForceChunk(level, getUUID()); + // Should not be used + // level.setChunkForced(loadedChunk.x, loadedChunk.z, false); loadedCentralChunk = null; } if (newChunk != null) { loadedCentralChunk = newChunk; manager.addForceChunk(level, getUUID(), loadedCentralChunk); - //Should not be used - //level.setChunkForced(newChunk.x, newChunk.z, true); + // Should not be used + // level.setChunkForced(newChunk.x, newChunk.z, true); } } @Override - public void detach(@NotNull IComputerAccess computer) { - super.detach(computer); + public void attach(IComputerAccess computer) { + super.attach(computer); ServerLevel level = (ServerLevel) owner.getLevel(); ChunkManager manager = ChunkManager.get(Objects.requireNonNull(level)); - setLoadedChunk(null, manager, level); + ChunkPos currentChunk = getChunkPos(); + setLoadedChunk(currentChunk, manager, level); + } + + @Override + public void detach(@NotNull IComputerAccess computer) { + super.detach(computer); + // Should not remove the loaded chunk when detaching, + // because CC:T will detach all peripherals before server stopped. + // So the chunk record will never be saved if we removed the chunk record when detaching. + // The records will be automatically removed by the ChunkManager if they have not been touched a while ago. + + // ServerLevel level = (ServerLevel) owner.getLevel(); + // ChunkManager manager = ChunkManager.get(Objects.requireNonNull(level)); + // setLoadedChunk(null, manager, level); } } diff --git a/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/peripheral/InventoryManagerPeripheral.java b/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/peripheral/InventoryManagerPeripheral.java index 62ce49b65..66abb49fb 100644 --- a/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/peripheral/InventoryManagerPeripheral.java +++ b/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/peripheral/InventoryManagerPeripheral.java @@ -25,7 +25,9 @@ import net.minecraftforge.items.wrapper.PlayerOffhandInvWrapper; import org.jetbrains.annotations.NotNull; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; public class InventoryManagerPeripheral extends BasePeripheral> { @@ -103,9 +105,10 @@ public final List getItems() throws LuaException { List items = new ArrayList<>(); int i = 0; //Used to let users easily sort the items by the slots. Also, a better way for the user to see where an item actually is for (ItemStack stack : getOwnerPlayer().getInventory().items) { - if (!stack.isEmpty()) { - items.add(LuaConverter.stackToObjectWithSlot(stack, i)); - } + ItemStack copiedStack = stack.copy(); + if (!copiedStack.isEmpty()) + items.add(LuaConverter.stackToObjectWithSlot(copiedStack, i)); + i++; } return items; @@ -124,7 +127,7 @@ public final MethodResult getItemsChest(String target) throws LuaException { List items = new ArrayList<>(); for (int slot = 0; slot < inventoryTo.getSlots(); slot++) { if (!inventoryTo.getStackInSlot(slot).isEmpty()) { - items.add(LuaConverter.stackToObjectWithSlot(inventoryTo.getStackInSlot(slot), slot)); + items.add(LuaConverter.stackToObjectWithSlot(inventoryTo.getStackInSlot(slot).copy(), slot)); } } return MethodResult.of(items); @@ -134,8 +137,9 @@ public final MethodResult getItemsChest(String target) throws LuaException { public final List getArmor() throws LuaException { List items = new ArrayList<>(); for (ItemStack stack : getOwnerPlayer().getInventory().armor) { - if (!stack.isEmpty()) { - items.add(LuaConverter.stackToObjectWithSlot(stack, ArmorSlot.getSlotForItem(stack))); + ItemStack copiedStack = stack.copy(); + if (!copiedStack.isEmpty()) { + items.add(LuaConverter.stackToObjectWithSlot(copiedStack, ArmorSlot.getSlotForItem(copiedStack))); } } return items; diff --git a/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/peripheral/MeBridgePeripheral.java b/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/peripheral/MeBridgePeripheral.java index 98146c756..731e451f4 100644 --- a/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/peripheral/MeBridgePeripheral.java +++ b/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/peripheral/MeBridgePeripheral.java @@ -428,7 +428,7 @@ public final MethodResult listItems() { if (!isConnected()) return notConnected(); - return MethodResult.of(AppEngApi.listStacks(AppEngApi.getMonitor(node), getCraftingService(), 0)); + return MethodResult.of(AppEngApi.listStacks(AppEngApi.getMonitor(node), getCraftingService())); } @LuaFunction(mainThread = true) @@ -436,7 +436,7 @@ public final MethodResult listCraftableItems() { if (!isConnected()) return notConnected(); - return MethodResult.of(AppEngApi.listStacks(AppEngApi.getMonitor(node), getCraftingService(), 2)); + return MethodResult.of(AppEngApi.listCraftableStacks(AppEngApi.getMonitor(node), getCraftingService())); } @LuaFunction(mainThread = true) @@ -444,7 +444,7 @@ public final MethodResult listFluid() { if (!isConnected()) return notConnected(); - return MethodResult.of(AppEngApi.listFluids(AppEngApi.getMonitor(node), getCraftingService(), 0)); + return MethodResult.of(AppEngApi.listFluids(AppEngApi.getMonitor(node), getCraftingService())); } @LuaFunction(mainThread = true) @@ -460,7 +460,7 @@ public final MethodResult listCraftableFluid() { if (!isConnected()) return notConnected(); - return MethodResult.of(AppEngApi.listFluids(AppEngApi.getMonitor(node), getCraftingService(), 2)); + return MethodResult.of(AppEngApi.listCraftableFluids(AppEngApi.getMonitor(node), getCraftingService())); } @LuaFunction(mainThread = true) diff --git a/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/peripheral/PlayerDetectorPeripheral.java b/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/peripheral/PlayerDetectorPeripheral.java index aa59496f5..6e8b90fa3 100644 --- a/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/peripheral/PlayerDetectorPeripheral.java +++ b/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/peripheral/PlayerDetectorPeripheral.java @@ -1,5 +1,6 @@ package de.srendi.advancedperipherals.common.addons.computercraft.peripheral; +import dan200.computercraft.api.lua.IArguments; import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaFunction; import dan200.computercraft.api.lua.MethodResult; @@ -193,7 +194,7 @@ public final boolean isPlayerInRange(int range, String username) { } @LuaFunction(value = {"getPlayerPos", "getPlayer"}, mainThread = true) - public final Map getPlayerPos(String username) throws LuaException { + public final Map getPlayerPos(IArguments arguments) throws LuaException { if (!APConfig.PERIPHERALS_CONFIG.playerSpy.get()) throw new LuaException("This function is disabled in the config. Activate it or ask an admin if he can activate it."); ResourceKey dimension = getLevel().dimension(); @@ -202,7 +203,7 @@ public final Map getPlayerPos(String username) throws LuaExcepti for (ServerPlayer player : getPlayers()) { if (!isAllowedMultiDimensional() && player.getLevel().dimension() != dimension) continue; - if (player.getName().getString().equals(username)) { + if (player.getName().getString().equals(arguments.getString(0))) { if (MAX_RANGE == -1 || CoordUtil.isInRange(getPos(), getLevel(), player, MAX_RANGE, MAX_RANGE)) existingPlayer = player; break; @@ -247,16 +248,21 @@ public final Map getPlayerPos(String username) throws LuaExcepti } } - info.put("x", Math.floor(x)); - info.put("y", Math.floor(y)); - info.put("z", Math.floor(z)); + int decimals = Math.min(arguments.optInt(1, 0), 4); + + final double unit = Math.pow(10, decimals); + info.put("x", Math.floor(x * unit) / unit); + info.put("y", Math.floor(y * unit) / unit); + info.put("z", Math.floor(z * unit) / unit); if (APConfig.PERIPHERALS_CONFIG.morePlayerInformation.get()) { info.put("yaw", existingPlayer.yRotO); info.put("pitch", existingPlayer.xRotO); info.put("dimension", existingPlayer.getLevel().dimension().location().toString()); info.put("eyeHeight", existingPlayer.getEyeHeight()); info.put("health", existingPlayer.getHealth()); - info.put("maxHeatlh", existingPlayer.getMaxHealth()); + // TODO: remove the next line in next major version + info.put("maxHeatlh", existingPlayer.getMaxHealth()); // keep this for backward compatibility + info.put("maxHealth", existingPlayer.getMaxHealth()); info.put("airSupply", existingPlayer.getAirSupply()); info.put("respawnPosition", LuaConverter.posToObject(existingPlayer.getRespawnPosition())); info.put("respawnDimension", existingPlayer.getRespawnDimension().location().toString()); diff --git a/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/peripheral/RsBridgePeripheral.java b/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/peripheral/RsBridgePeripheral.java index 59a33eac5..a45b81086 100644 --- a/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/peripheral/RsBridgePeripheral.java +++ b/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/peripheral/RsBridgePeripheral.java @@ -354,7 +354,7 @@ public final MethodResult isItemCrafting(IArguments arguments) throws LuaExcepti if (filter.rightPresent()) return MethodResult.of(null, filter.getRight()); - ItemStack stack = RefinedStorage.findStackFromFilter(getNetwork(), null, filter.getLeft()); + ItemStack stack = RefinedStorage.findStackFromFilter(getNetwork(), getNetwork().getCraftingManager(), filter.getLeft()); if (stack == null) return MethodResult.of(null, "NOT_CRAFTABLE"); diff --git a/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/turtles/TurtleChunkyUpgrade.java b/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/turtles/TurtleChunkyUpgrade.java index aec241d37..bed242638 100644 --- a/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/turtles/TurtleChunkyUpgrade.java +++ b/src/main/java/de/srendi/advancedperipherals/common/addons/computercraft/turtles/TurtleChunkyUpgrade.java @@ -12,6 +12,7 @@ import org.jetbrains.annotations.NotNull; public class TurtleChunkyUpgrade extends PeripheralTurtleUpgrade { + private int updateTick = 0; public TurtleChunkyUpgrade(ResourceLocation id, ItemStack stack) { super(id, stack); @@ -19,7 +20,7 @@ public TurtleChunkyUpgrade(ResourceLocation id, ItemStack stack) { @Override public ModelResourceLocation getLeftModel() { - return null; //Null, the turtle uses the chunk controller item model. See BaseTurtle.java + return null; // Null, the turtle uses the chunk controller item model. See BaseTurtle.java } @Override @@ -34,10 +35,16 @@ protected ChunkyPeripheral buildPeripheral(@NotNull ITurtleAccess turtle, @NotNu @Override public void update(@NotNull ITurtleAccess turtle, @NotNull TurtleSide side) { - //Add a chunk to the Chunk Manager every 10 ticks, if it's not already forced. - //The turtle can move, so we need to do that. + // Add a chunk to the Chunk Manager every 10 ticks, if it's not already forced. + // The turtle can move, so we need to do that. super.update(turtle, side); if (APConfig.PERIPHERALS_CONFIG.enableChunkyTurtle.get()) { + // TODO: turtle will stop work when crossing chunks if update every 10 ticks + // updateTick++; + // if (updateTick < 10) { + // return; + // } + // updateTick = 0; IPeripheral peripheral = turtle.getPeripheral(side); if (peripheral instanceof ChunkyPeripheral chunkyPeripheral) { chunkyPeripheral.updateChunkState(); diff --git a/src/main/java/de/srendi/advancedperipherals/common/blocks/base/APBlockEntityBlock.java b/src/main/java/de/srendi/advancedperipherals/common/blocks/base/APBlockEntityBlock.java index 29d818c6c..abbc0c374 100644 --- a/src/main/java/de/srendi/advancedperipherals/common/blocks/base/APBlockEntityBlock.java +++ b/src/main/java/de/srendi/advancedperipherals/common/blocks/base/APBlockEntityBlock.java @@ -1,6 +1,8 @@ package de.srendi.advancedperipherals.common.blocks.base; +import de.srendi.advancedperipherals.common.blocks.blockentities.EnergyDetectorEntity; import net.minecraft.core.BlockPos; +import net.minecraft.world.level.LevelReader; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; @@ -28,4 +30,14 @@ public BlockEntity newBlockEntity(@NotNull BlockPos pos, @NotNull BlockState sta return tileEntity != null ? tileEntity.get().create(pos, state) : null; } + @Override + public void onNeighborChange(BlockState state, LevelReader level, BlockPos pos, BlockPos neighbor) { + super.onNeighborChange(state, level, pos, neighbor); + + BlockEntity blockEntity = level.getBlockEntity(pos); + + if(blockEntity instanceof EnergyDetectorEntity energyDetector) + energyDetector.invalidateStorages(); + + } } diff --git a/src/main/java/de/srendi/advancedperipherals/common/blocks/blockentities/EnergyDetectorEntity.java b/src/main/java/de/srendi/advancedperipherals/common/blocks/blockentities/EnergyDetectorEntity.java index b22c92121..f8db556aa 100644 --- a/src/main/java/de/srendi/advancedperipherals/common/blocks/blockentities/EnergyDetectorEntity.java +++ b/src/main/java/de/srendi/advancedperipherals/common/blocks/blockentities/EnergyDetectorEntity.java @@ -83,6 +83,10 @@ public void deserializeNBT(CompoundTag nbt) { super.deserializeNBT(nbt); } + public void invalidateStorages() { + outReceivingStorage = Optional.empty(); + } + // returns the cached output storage of the receiving block or refetches it if it has been invalidated @NotNull public Optional getOutputStorage() { diff --git a/src/main/java/de/srendi/advancedperipherals/common/commands/APCommands.java b/src/main/java/de/srendi/advancedperipherals/common/commands/APCommands.java index cd606f38e..06f8a9aa8 100644 --- a/src/main/java/de/srendi/advancedperipherals/common/commands/APCommands.java +++ b/src/main/java/de/srendi/advancedperipherals/common/commands/APCommands.java @@ -1,8 +1,19 @@ package de.srendi.advancedperipherals.common.commands; import com.mojang.brigadier.exceptions.CommandSyntaxException; + +import dan200.computercraft.core.computer.ComputerSide; +import dan200.computercraft.core.computer.Environment; +import dan200.computercraft.shared.command.UserLevel; +import dan200.computercraft.shared.command.text.ChatHelpers; +import dan200.computercraft.shared.command.text.TableBuilder; +import dan200.computercraft.shared.computer.core.ServerComputer; +import dan200.computercraft.shared.computer.core.ServerContext; + import de.srendi.advancedperipherals.AdvancedPeripherals; +import de.srendi.advancedperipherals.common.addons.computercraft.peripheral.ChunkyPeripheral; import de.srendi.advancedperipherals.common.util.inventory.ItemUtil; + import net.minecraft.ChatFormatting; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; @@ -17,10 +28,25 @@ @Mod.EventBusSubscriber(modid = AdvancedPeripherals.MOD_ID) public class APCommands { + public static final String ROOT_LITERAL = "advancedperipherals"; + public static final String FORCELOAD_LITERAL = "forceload"; + static final String FORCELOAD_HELP = + "/" + ROOT_LITERAL + " " + FORCELOAD_LITERAL + " help" + " - show this help message\n" + + "/" + ROOT_LITERAL + " " + FORCELOAD_LITERAL + " dump" + " - show all chunky turtles\n"; @SubscribeEvent public static void register(RegisterCommandsEvent event) { - event.getDispatcher().register(Commands.literal("advancedperipherals").then(Commands.literal("getHashItem").executes(context -> getHashItem(context.getSource())))); + event.getDispatcher().register(Commands.literal(ROOT_LITERAL) + .then(Commands.literal("getHashItem").executes(context -> getHashItem(context.getSource()))) + .then(Commands.literal(FORCELOAD_LITERAL) + .executes(context -> forceloadHelp(context.getSource())) + .then(Commands.literal("help") + .executes(context -> forceloadHelp(context.getSource()))) + .then(Commands.literal("dump") + .requires(UserLevel.OWNER_OP) + .executes(context -> forceloadDump(context.getSource()))) + ) + ); } private static int getHashItem(CommandSourceStack source) throws CommandSyntaxException { @@ -42,4 +68,50 @@ private static int getHashItem(CommandSourceStack source) throws CommandSyntaxEx .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal("Copy"))))), true); return 1; } + + private static int forceloadHelp(CommandSourceStack source) throws CommandSyntaxException { + source.sendSuccess(Component.literal(FORCELOAD_HELP), true); + return 1; + } + + private static int forceloadDump(CommandSourceStack source) throws CommandSyntaxException { + TableBuilder table = new TableBuilder("ChunkyTurtles", "Computer", "Position"); + + ServerComputer[] computers = ServerContext.get(source.getServer()).registry().getComputers().stream().filter((computer) -> { + Environment env = computer.getComputer().getEnvironment(); + for (ComputerSide side : ComputerSide.values()) { + if (env.getPeripheral(side) instanceof ChunkyPeripheral) { + return true; + } + } + return false; + }).sorted((a, b) -> a.getID() - b.getID()).toArray(size -> new ServerComputer[size]); + + for (ServerComputer computer : computers) { + table.row( + makeComputerDumpCommand(computer), + makeComputerPosCommand(computer) + ); + } + + table.display(source); + return computers.length; + } + + + private static Component makeComputerDumpCommand(ServerComputer computer) { + return ChatHelpers.link( + Component.literal("#" + computer.getID()), + "/computercraft dump " + computer.getInstanceID(), + Component.translatable("commands.computercraft.dump.action") + ); + } + + private static Component makeComputerPosCommand(ServerComputer computer) { + return ChatHelpers.link( + ChatHelpers.position(computer.getPosition()), + "/computercraft tp " + computer.getInstanceID(), + Component.translatable("commands.computercraft.tp.action") + ); + } } diff --git a/src/main/java/de/srendi/advancedperipherals/common/util/ChunkManager.java b/src/main/java/de/srendi/advancedperipherals/common/util/ChunkManager.java index dbd324dc8..3b338284d 100644 --- a/src/main/java/de/srendi/advancedperipherals/common/util/ChunkManager.java +++ b/src/main/java/de/srendi/advancedperipherals/common/util/ChunkManager.java @@ -2,14 +2,16 @@ import de.srendi.advancedperipherals.AdvancedPeripherals; import de.srendi.advancedperipherals.common.configuration.APConfig; +import net.minecraft.core.Registry; import net.minecraft.nbt.CompoundTag; +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.saveddata.SavedData; import net.minecraftforge.common.world.ForgeChunkManager; import net.minecraftforge.event.TickEvent; import net.minecraftforge.event.server.ServerStartedEvent; -import net.minecraftforge.event.server.ServerStoppingEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.server.ServerLifecycleHooks; @@ -40,17 +42,13 @@ public ChunkManager() { public static ChunkManager load(@NotNull CompoundTag data) { ChunkManager manager = new ChunkManager(); CompoundTag forcedData = data.getCompound(FORCED_CHUNKS_TAG); + AdvancedPeripherals.debug("Loading chunk manager from NBT " + data, Level.WARN); for (String key : forcedData.getAllKeys()) { manager.forcedChunks.put(UUID.fromString(key), LoadChunkRecord.deserialize(forcedData.getCompound(key))); } return manager; } - @SubscribeEvent - public static void beforeServerStopped(ServerStoppingEvent event) { - ChunkManager.get(event.getServer().overworld()).stop(); - } - @SubscribeEvent public static void afterServerStarted(ServerStartedEvent event) { ChunkManager.get(event.getServer().overworld()).init(); @@ -60,54 +58,71 @@ public static void afterServerStarted(ServerStartedEvent event) { public static void serverTick(TickEvent.ServerTickEvent event) { if (event.phase == TickEvent.Phase.END) { tickCounter++; - if (tickCounter % (APConfig.PERIPHERALS_CONFIG.chunkLoadValidTime.get() / 2) == 0) { + // run cleanup per chunkLoadValidTime / 10 + final int checkIntervalInTick = APConfig.PERIPHERALS_CONFIG.chunkLoadValidTime.get() * 20 / 10; + if (tickCounter >= checkIntervalInTick) { + tickCounter = 0; ChunkManager.get(ServerLifecycleHooks.getCurrentServer().overworld()).cleanup(); } } } + private static boolean forceChunk(UUID owner, ServerLevel level, ChunkPos pos) { + AdvancedPeripherals.debug("Forcing chunk " + pos, Level.WARN); + return ForgeChunkManager.forceChunk(level, AdvancedPeripherals.MOD_ID, owner, pos.x, pos.z, true, true); + } + + private static boolean unforceChunk(UUID owner, ServerLevel level, ChunkPos pos) { + AdvancedPeripherals.debug("Unforcing chunk " + pos, Level.WARN); + return ForgeChunkManager.forceChunk(level, AdvancedPeripherals.MOD_ID, owner, pos.x, pos.z, false, true); + } + public synchronized boolean addForceChunk(ServerLevel level, UUID owner, ChunkPos pos) { - AdvancedPeripherals.debug("Trying to load forced chunk cluster" + pos, Level.WARN); - if (forcedChunks.containsKey(owner)) return true; - forcedChunks.put(owner, new LoadChunkRecord(level.dimension().location().toString(), pos)); - setDirty(); - int chunkRadius = APConfig.PERIPHERALS_CONFIG.chunkyTurtleRadius.get(); - if (chunkRadius == 0) { - return ForgeChunkManager.forceChunk(level, AdvancedPeripherals.MOD_ID, owner, pos.x, pos.z, true, true); + AdvancedPeripherals.debug("Trying to load forced chunk cluster " + pos, Level.WARN); + LoadChunkRecord oldRecord = forcedChunks.get(owner); + if (oldRecord != null) { + ServerLevel oldLevel = getServerLevel(oldRecord.getDimensionName()); + if (oldLevel == level && pos.equals(oldRecord.getPos())) { + return true; + } + unforceChunkRecord(owner, oldRecord, oldLevel); } + final int chunkRadius = APConfig.PERIPHERALS_CONFIG.chunkyTurtleRadius.get(); + forcedChunks.put(owner, new LoadChunkRecord(level.dimension().location().toString(), pos, chunkRadius)); + setDirty(); boolean result = true; - for (int x = chunkRadius * -1; x < chunkRadius; x++) { - for (int z = chunkRadius * -1; z < chunkRadius; z++) { - result &= ForgeChunkManager.forceChunk(level, AdvancedPeripherals.MOD_ID, owner, pos.x + x, pos.z + z, true, true); + for (int x = -chunkRadius; x <= chunkRadius; x++) { + for (int z = -chunkRadius; z <= chunkRadius; z++) { + result &= forceChunk(owner, level, new ChunkPos(pos.x + x, pos.z + z)); } } return result; } public synchronized void touch(UUID owner) { - if (forcedChunks.containsKey(owner)) - forcedChunks.get(owner).touch(); + LoadChunkRecord forcedChunk = forcedChunks.get(owner); + if (forcedChunk != null) { + forcedChunk.touch(); + } } + // This method is kept for backward compatibility + // use removeForceChunk without the position argument instead + // TODO: remove in next major version + @Deprecated(forRemoval = true, since = "1.19.2-0.7.36") public synchronized boolean removeForceChunk(ServerLevel level, UUID owner, ChunkPos pos) { - AdvancedPeripherals.debug("Trying to unload forced chunk cluster" + pos, Level.WARN); - if (!forcedChunks.containsKey(owner)) - return true; + return removeForceChunk(level, owner); + } + + public synchronized boolean removeForceChunk(ServerLevel level, UUID owner) { + AdvancedPeripherals.debug("Attempting to unload forced chunk cluster " + owner, Level.WARN); LoadChunkRecord chunkRecord = forcedChunks.get(owner); + if (chunkRecord == null) + return true; String dimensionName = level.dimension().location().toString(); if (!chunkRecord.getDimensionName().equals(dimensionName)) throw new IllegalArgumentException(String.format("Incorrect dimension! Should be %s instead of %s", chunkRecord.getDimensionName(), dimensionName)); - boolean result = true; - int chunkRadius = APConfig.PERIPHERALS_CONFIG.chunkyTurtleRadius.get(); - if (chunkRadius == 0) { - result = ForgeChunkManager.forceChunk(level, AdvancedPeripherals.MOD_ID, owner, pos.x, pos.z, false, true); - } else { - for (int x = chunkRadius * -1; x < chunkRadius; x++) { - for (int z = chunkRadius * -1; z < chunkRadius; z++) { - result &= ForgeChunkManager.forceChunk(level, AdvancedPeripherals.MOD_ID, owner, pos.x + x, pos.z + z, true, true); - } - } - } + boolean result = unforceChunkRecord(owner, chunkRecord, level); if (result) { forcedChunks.remove(owner); setDirty(); @@ -115,85 +130,142 @@ public synchronized boolean removeForceChunk(ServerLevel level, UUID owner, Chun return result; } - public synchronized void init() { - if (!initialized) { - AdvancedPeripherals.debug("Schedule chunk manager init", Level.WARN); - int chunkRadius = APConfig.PERIPHERALS_CONFIG.chunkyTurtleRadius.get(); - ServerLifecycleHooks.getCurrentServer().getAllLevels().forEach(level -> { - String dimensionName = level.dimension().location().toString(); - forcedChunks.entrySet().stream().filter(entry -> entry.getValue().getDimensionName().equals(dimensionName)).forEach(entry -> { - if (chunkRadius == 0) { - ForgeChunkManager.forceChunk(level, AdvancedPeripherals.MOD_ID, entry.getKey(), entry.getValue().getPos().x, entry.getValue().getPos().z, true, true); - } - for (int x = chunkRadius * -1; x < chunkRadius; x++) { - for (int z = chunkRadius * -1; z < chunkRadius; z++) { - ForgeChunkManager.forceChunk(level, AdvancedPeripherals.MOD_ID, entry.getKey(), entry.getValue().getPos().x + x, entry.getValue().getPos().z + z, true, true); - } - } - }); - }); - initialized = true; + private synchronized boolean unforceChunkRecord(UUID owner, LoadChunkRecord chunkRecord, ServerLevel level) { + boolean result = true; + final ChunkPos pos = chunkRecord.getPos(); + final int chunkRadius = chunkRecord.getRadius(); + AdvancedPeripherals.debug(String.format("Trying to unload forced chunk cluster %s at %s with radius %d", owner, pos, chunkRadius), Level.WARN); + for (int x = -chunkRadius; x <= chunkRadius; x++) { + for (int z = -chunkRadius; z <= chunkRadius; z++) { + result &= unforceChunk(owner, level, new ChunkPos(pos.x + x, pos.z + z)); + } } + return result; } - public synchronized void stop() { + public synchronized void init() { if (initialized) { - AdvancedPeripherals.debug("Schedule chunk manager stop", Level.WARN); - int chunkRadius = APConfig.PERIPHERALS_CONFIG.chunkyTurtleRadius.get(); - ServerLifecycleHooks.getCurrentServer().getAllLevels().forEach(level -> { - String dimensionName = level.dimension().location().toString(); - forcedChunks.entrySet().stream().filter(entry -> entry.getValue().getDimensionName().equals(dimensionName)).forEach(entry -> { - if (chunkRadius == 0) { - ForgeChunkManager.forceChunk(level, AdvancedPeripherals.MOD_ID, entry.getKey(), entry.getValue().getPos().x, entry.getValue().getPos().z, false, true); + return; + } + initialized = true; + + AdvancedPeripherals.debug(String.format("Schedule chunk manager init, forcedChunks = %d", forcedChunks.size()), Level.WARN); + final int chunkRadius = APConfig.PERIPHERALS_CONFIG.chunkyTurtleRadius.get(); + final Map levels = getServerLevels(); + forcedChunks.forEach((uuid, value) -> { + String dimensionName = value.getDimensionName(); + ServerLevel level = levels.get(dimensionName); + if (level == null) { + AdvancedPeripherals.debug("Skipped not exists dimension " + dimensionName, Level.ERROR); + return; + } + final ChunkPos pos = value.getPos(); + final int loadedRadius = value.getRadius(); + AdvancedPeripherals.debug(String.format("Recorded chunk in %s at %s with radius %d", dimensionName, pos, loadedRadius), Level.INFO); + if (loadedRadius == chunkRadius) { + return; + } + if (loadedRadius == -1) { + // if it's coming from old version, just force all + for (int x = -chunkRadius; x <= chunkRadius; x++) { + for (int z = -chunkRadius; z <= chunkRadius; z++) { + forceChunk(uuid, level, new ChunkPos(pos.x + x, pos.z + z)); } - for (int x = chunkRadius * -1; x < chunkRadius; x++) { - for (int z = chunkRadius * -1; z < chunkRadius; z++) { - ForgeChunkManager.forceChunk(level, AdvancedPeripherals.MOD_ID, entry.getKey(), entry.getValue().getPos().x + x, entry.getValue().getPos().z + z, false, true); + } + } else if (loadedRadius > chunkRadius) { + // clean overflowed load radius + for (int x = -loadedRadius; x <= loadedRadius; x++) { + for (int z = -loadedRadius; z <= loadedRadius; z++) { + if (Math.abs(x) > chunkRadius || Math.abs(z) > chunkRadius) { + unforceChunk(uuid, level, new ChunkPos(pos.x + x, pos.z + z)); } } - }); - }); - initialized = false; - } + } + } else if (loadedRadius < chunkRadius) { + // otherwise, only do the changed part to reduce startup time (in case we have a lot chunky turtle) + for (int x = -chunkRadius; x <= chunkRadius; x++) { + for (int z = -chunkRadius; z <= chunkRadius; z++) { + if (Math.abs(x) > loadedRadius || Math.abs(z) > loadedRadius) { + forceChunk(uuid, level, new ChunkPos(pos.x + x, pos.z + z)); + } + } + } + } + value.setRadius(chunkRadius); + setDirty(); + }); } public synchronized void cleanup() { AdvancedPeripherals.debug("Schedule chunk manager cleanup", Level.WARN); - ServerLifecycleHooks.getCurrentServer().getAllLevels().forEach(level -> { - String dimensionName = level.dimension().location().toString(); - List purgeList = new ArrayList<>(); - forcedChunks.entrySet().stream().filter(entry -> entry.getValue().getDimensionName().equals(dimensionName) && !entry.getValue().isValid()).forEach(entry -> purgeList.add(entry.getKey())); - purgeList.forEach(uuid -> { - AdvancedPeripherals.debug(String.format("Purge forced chunk for %s", uuid), Level.WARN); - removeForceChunk(level, uuid, forcedChunks.get(uuid).getPos()); - }); - }); + final Map levels = getServerLevels(); + final Iterator> iterator = forcedChunks.entrySet().iterator(); + while (iterator.hasNext()) { + final Map.Entry entry = iterator.next(); + UUID uuid = entry.getKey(); + LoadChunkRecord chunkRecord = entry.getValue(); + String dimensionName = chunkRecord.getDimensionName(); + ServerLevel level = levels.get(dimensionName); + if (level == null) { + continue; + } + if (chunkRecord.isValid()) { + continue; + } + AdvancedPeripherals.debug(String.format("Purge forced chunk for %s", uuid), Level.WARN); + unforceChunkRecord(uuid, chunkRecord, level); + iterator.remove(); + setDirty(); + } } @Override - public @NotNull CompoundTag save(@NotNull CompoundTag data) { + public synchronized @NotNull CompoundTag save(@NotNull CompoundTag data) { + AdvancedPeripherals.debug("Schedule chunk manager save, forcedChunks = " + forcedChunks.size(), Level.WARN); CompoundTag forcedChunksTag = new CompoundTag(); forcedChunks.forEach((key, value) -> forcedChunksTag.put(key.toString(), value.serialize())); + // !!! DO NOT forget to put forcedChunksTag into data !!! + // It will not magically be saved 😅 + data.put(FORCED_CHUNKS_TAG, forcedChunksTag); return data; } + private static Map getServerLevels() { + Map levels = new HashMap<>(); + ServerLifecycleHooks.getCurrentServer().getAllLevels().forEach(level -> { + String dimensionName = level.dimension().location().toString(); + levels.put(dimensionName, level); + }); + return levels; + } + + private static ServerLevel getServerLevel(String name) { + ResourceKey key = ResourceKey.create(Registry.DIMENSION_REGISTRY, new ResourceLocation(name)); + return ServerLifecycleHooks.getCurrentServer().getLevel(key); + } + private static class LoadChunkRecord { private static final String POS_TAG = "pos"; private static final String DIMENSION_NAME_TAG = "dimensionName"; + private static final String RADIUS_TAG = "radius"; private final @NotNull String dimensionName; private final @NotNull ChunkPos pos; + private int radius; private long lastTouch; - LoadChunkRecord(@NotNull String dimensionName, @NotNull ChunkPos pos) { + LoadChunkRecord(@NotNull String dimensionName, @NotNull ChunkPos pos, int radius) { this.dimensionName = dimensionName; this.pos = pos; + this.radius = radius; this.lastTouch = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC); } public static LoadChunkRecord deserialize(@NotNull CompoundTag tag) { - return new LoadChunkRecord(tag.getString(DIMENSION_NAME_TAG), NBTUtil.chunkPosFromNBT(tag.getCompound(POS_TAG))); + Set keys = tag.getAllKeys(); + int radius = keys.contains(RADIUS_TAG) ? tag.getInt(RADIUS_TAG) : -1; + return new LoadChunkRecord(tag.getString(DIMENSION_NAME_TAG), NBTUtil.chunkPosFromNBT(tag.getCompound(POS_TAG)), radius); } public @NotNull ChunkPos getPos() { @@ -204,6 +276,14 @@ public static LoadChunkRecord deserialize(@NotNull CompoundTag tag) { return dimensionName; } + public int getRadius() { + return radius; + } + + public void setRadius(int radius) { + this.radius = radius; + } + public void touch() { lastTouch = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC); } @@ -217,6 +297,7 @@ public boolean isValid() { CompoundTag tag = new CompoundTag(); tag.putString(DIMENSION_NAME_TAG, dimensionName); tag.put(POS_TAG, NBTUtil.toNBT(pos)); + tag.putInt(RADIUS_TAG, radius); return tag; } } diff --git a/src/main/java/de/srendi/advancedperipherals/common/util/CoordUtil.java b/src/main/java/de/srendi/advancedperipherals/common/util/CoordUtil.java index 19f66b417..a21f2ab8d 100644 --- a/src/main/java/de/srendi/advancedperipherals/common/util/CoordUtil.java +++ b/src/main/java/de/srendi/advancedperipherals/common/util/CoordUtil.java @@ -100,26 +100,24 @@ public static Direction getDirection(FrontAndTop orientation, String computerSid } if (front.getAxis() == Direction.Axis.Y) { - switch (side) { - case FRONT: return front; - case BACK: return front.getOpposite(); - case TOP: return top; - case BOTTOM: return top.getOpposite(); - case RIGHT: return top.getClockWise(); - case LEFT: return top.getCounterClockWise(); - } + return switch (side) { + case FRONT -> front; + case BACK -> front.getOpposite(); + case TOP -> top; + case BOTTOM -> top.getOpposite(); + case RIGHT -> top.getClockWise(); + case LEFT -> top.getCounterClockWise(); + }; } else { - switch (side) { - case FRONT: return front; - case BACK: return front.getOpposite(); - case TOP: return Direction.UP; - case BOTTOM: return Direction.DOWN; - case RIGHT: return front.getCounterClockWise(); - case LEFT: return front.getClockWise(); - } + return switch (side) { + case FRONT -> front; + case BACK -> front.getOpposite(); + case TOP -> Direction.UP; + case BOTTOM -> Direction.DOWN; + case RIGHT -> front.getCounterClockWise(); + case LEFT -> front.getClockWise(); + }; } - - throw new LuaException(computerSide + " is not a expected side"); } } diff --git a/src/main/java/de/srendi/advancedperipherals/common/util/ServerWorker.java b/src/main/java/de/srendi/advancedperipherals/common/util/ServerWorker.java index 558fee12c..884cbd5a2 100644 --- a/src/main/java/de/srendi/advancedperipherals/common/util/ServerWorker.java +++ b/src/main/java/de/srendi/advancedperipherals/common/util/ServerWorker.java @@ -5,13 +5,13 @@ import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; -import java.util.ArrayDeque; import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; @Mod.EventBusSubscriber(modid = AdvancedPeripherals.MOD_ID) public class ServerWorker { - private static final Queue callQueue = new ArrayDeque<>(); + private static final Queue callQueue = new ConcurrentLinkedQueue<>(); public static void add(final Runnable call) { callQueue.add(call);