diff --git a/src/api/java/appeng/api/config/SecurityPermissions.java b/src/api/java/appeng/api/config/SecurityPermissions.java index e81a23507..8fe52875d 100644 --- a/src/api/java/appeng/api/config/SecurityPermissions.java +++ b/src/api/java/appeng/api/config/SecurityPermissions.java @@ -26,6 +26,8 @@ package appeng.api.config; import net.minecraft.text.Text; import net.minecraft.text.TranslatableText; +import java.util.Locale; + /** * Represent the security systems basic permissions, these are not for * anti-griefing, they are part of the mod as a gameplay feature. @@ -61,7 +63,7 @@ public enum SecurityPermissions { */ SECURITY; - private final String translationKey = "gui.appliedenergistics2.security." + this.name().toLowerCase(); + private final String translationKey = "gui.appliedenergistics2.security." + this.name().toLowerCase(Locale.ROOT); public String getTranslatedName() { return this.translationKey + ".name"; diff --git a/src/api/java/appeng/api/implementations/TransitionResult.java b/src/api/java/appeng/api/implementations/TransitionResult.java deleted file mode 100644 index 209524a49..000000000 --- a/src/api/java/appeng/api/implementations/TransitionResult.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2013 AlgorithmX2 - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package appeng.api.implementations; - -/** - * Defines the result of performing a transition from the world into a storage - * cell, if its possible, and what the energy usage is. - */ -public class TransitionResult { - public final boolean success; - public final double energyUsage; - - public TransitionResult(final boolean success, final double power) { - this.success = success; - this.energyUsage = power; - } -} diff --git a/src/api/java/appeng/api/implementations/items/ISpatialStorageCell.java b/src/api/java/appeng/api/implementations/items/ISpatialStorageCell.java index caf9d5128..f2bf77893 100644 --- a/src/api/java/appeng/api/implementations/items/ISpatialStorageCell.java +++ b/src/api/java/appeng/api/implementations/items/ISpatialStorageCell.java @@ -30,7 +30,6 @@ import net.minecraft.util.registry.RegistryKey; import net.minecraft.world.World; import net.minecraft.world.dimension.DimensionType; -import appeng.api.implementations.TransitionResult; import appeng.api.util.WorldCoord; /** @@ -53,13 +52,13 @@ public interface ISpatialStorageCell { int getMaxStoredDim(ItemStack is); /** - * get the currently stored Dimension id. + * get the currently stored spatial storage plot id. * * @param is spatial storage cell * - * @return dimension id or -1 + * @return plot id or -1 */ - RegistryKey getStoredDimension(ItemStack is); + int getAllocatedPlotId(ItemStack is); /** * Perform a spatial swap with the contents of the cell, and the world. @@ -70,7 +69,7 @@ public interface ISpatialStorageCell { * @param max max coord * @param playerId owner of current grid or -1 * - * @return result of transition + * @return success of transition */ - TransitionResult doSpatialTransition(ItemStack is, ServerWorld w, WorldCoord min, WorldCoord max, int playerId); + boolean doSpatialTransition(ItemStack is, ServerWorld w, WorldCoord min, WorldCoord max, int playerId); } \ No newline at end of file diff --git a/src/api/java/appeng/api/storage/ISpatialDimension.java b/src/api/java/appeng/api/storage/ISpatialDimension.java deleted file mode 100644 index a96352e2e..000000000 --- a/src/api/java/appeng/api/storage/ISpatialDimension.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * This file is part of Applied Energistics 2. - * Copyright (c) 2013 - 2017, AlgorithmX2, All rights reserved. - * - * Applied Energistics 2 is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Applied Energistics 2 is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Applied Energistics 2. If not, see . - */ - -package appeng.api.storage; - -import java.util.List; - -import javax.annotation.Nullable; - -import net.minecraft.server.world.ServerWorld; -import net.minecraft.text.Text; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.registry.RegistryKey; -import net.minecraft.world.World; -import net.minecraft.world.dimension.DimensionType; - -public interface ISpatialDimension { - ServerWorld getWorld(RegistryKey cellDim); - - @Nullable - RegistryKey createNewCellDimension(BlockPos capacity); - - void deleteCellDimension(RegistryKey cellDim); - - boolean isCellDimension(RegistryKey cellDim); - - BlockPos getCellDimensionOrigin(RegistryKey cellDim); - - BlockPos getCellDimensionSize(RegistryKey cellDim); - - /** - * Adds a user-facing tooltip that describes this dimension so that a player can - * keep storage cells apart. - */ - void addCellDimensionTooltip(RegistryKey cellDim, List lines); - -} diff --git a/src/main/java/appeng/block/networking/WirelessBlock.java b/src/main/java/appeng/block/networking/WirelessBlock.java index 0631b4c6c..919ee3b0b 100644 --- a/src/main/java/appeng/block/networking/WirelessBlock.java +++ b/src/main/java/appeng/block/networking/WirelessBlock.java @@ -18,6 +18,8 @@ package appeng.block.networking; +import java.util.Locale; + import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.block.ShapeContext; @@ -52,7 +54,7 @@ public class WirelessBlock extends AEBaseTileBlock { @Override public String asString() { - return this.name().toLowerCase(); + return this.name().toLowerCase(Locale.ROOT); } } diff --git a/src/main/java/appeng/client/ActionKey.java b/src/main/java/appeng/client/ActionKey.java index fab4b96a6..49d43288c 100644 --- a/src/main/java/appeng/client/ActionKey.java +++ b/src/main/java/appeng/client/ActionKey.java @@ -1,6 +1,8 @@ package appeng.client; +import java.util.Locale; + import org.lwjgl.glfw.GLFW; public enum ActionKey { @@ -13,7 +15,7 @@ public enum ActionKey { } public String getTranslationKey() { - return "key." + this.name().toLowerCase() + ".desc"; + return "key." + this.name().toLowerCase(Locale.ROOT) + ".desc"; } public int getDefaultKey() { diff --git a/src/main/java/appeng/client/gui/implementations/NumberEntryWidget.java b/src/main/java/appeng/client/gui/implementations/NumberEntryWidget.java index 457c8af80..3840ddae2 100644 --- a/src/main/java/appeng/client/gui/implementations/NumberEntryWidget.java +++ b/src/main/java/appeng/client/gui/implementations/NumberEntryWidget.java @@ -1,5 +1,7 @@ package appeng.client.gui.implementations; +import java.util.ArrayList; +import java.util.List; import java.util.function.Consumer; import java.util.function.LongConsumer; @@ -7,13 +9,13 @@ import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.Element; import net.minecraft.client.gui.widget.ButtonWidget; import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.text.LiteralText; import appeng.client.gui.AEBaseScreen; import appeng.client.gui.NumberEntryType; import appeng.client.gui.widgets.ITickingWidget; import appeng.client.gui.widgets.NumberBox; import appeng.core.AEConfig; +import net.minecraft.text.Text; /** * A utility widget that consists of a text-field to enter a number with @@ -21,6 +23,9 @@ import appeng.core.AEConfig; */ public class NumberEntryWidget implements ITickingWidget { + private static final Text PLUS = Text.method_30163("+"); + private static final Text MINUS = Text.method_30163("+"); + private final AEBaseScreen parent; private final int x; @@ -28,14 +33,7 @@ public class NumberEntryWidget implements ITickingWidget { private final NumberBox level; private final NumberEntryType type; - private ButtonWidget plus1; - private ButtonWidget plus10; - private ButtonWidget plus100; - private ButtonWidget plus1000; - private ButtonWidget minus1; - private ButtonWidget minus10; - private ButtonWidget minus100; - private ButtonWidget minus1000; + private List buttons; public NumberEntryWidget(AEBaseScreen parent, int x, int y, int width, int height, NumberEntryType type, LongConsumer changeListener) { @@ -57,14 +55,7 @@ public class NumberEntryWidget implements ITickingWidget { public void setActive(boolean active) { this.level.active = active; - this.plus1.active = active; - this.plus10.active = active; - this.plus100.active = active; - this.plus1000.active = active; - this.minus1.active = active; - this.minus10.active = active; - this.minus100.active = active; - this.minus1000.active = active; + this.buttons.forEach(b -> b.active = active); } public void setTextFieldBounds(int x, int y, int width) { @@ -87,25 +78,23 @@ public class NumberEntryWidget implements ITickingWidget { int left = parent.getX() + x; int top = parent.getY() + y; - addButton.accept(this.plus1 = new ButtonWidget(left, top, 22, 20, new LiteralText("+" + a), btn -> addQty(a))); - addButton.accept( - this.plus10 = new ButtonWidget(left + 28, top, 28, 20, new LiteralText("+" + b), btn -> addQty(b))); - addButton.accept( - this.plus100 = new ButtonWidget(left + 62, top, 32, 20, new LiteralText("+" + c), btn -> addQty(c))); - addButton.accept( - this.plus1000 = new ButtonWidget(left + 100, top, 38, 20, new LiteralText("+" + d), btn -> addQty(d))); + List buttons = new ArrayList<>(8); + + buttons.add(new ButtonWidget(left, top, 22, 20, makeLabel(PLUS, a), btn -> addQty(a))); + buttons.add(new ButtonWidget(left + 28, top, 28, 20, makeLabel(PLUS, b), btn -> addQty(b))); + buttons.add(new ButtonWidget(left + 62, top, 32, 20, makeLabel(PLUS, c), btn -> addQty(c))); + buttons.add(new ButtonWidget(left + 100, top, 38, 20, makeLabel(PLUS, d), btn -> addQty(d))); // Placing this here will give a sensible tab order addChildren.accept(this.level); - addButton.accept( - this.minus1 = new ButtonWidget(left, top + 42, 22, 20, new LiteralText("-" + a), btn -> addQty(-a))); - addButton.accept(this.minus10 = new ButtonWidget(left + 28, top + 42, 28, 20, new LiteralText("-" + b), - btn -> addQty(-b))); - addButton.accept(this.minus100 = new ButtonWidget(left + 62, top + 42, 32, 20, new LiteralText("-" + c), - btn -> addQty(-c))); - addButton.accept(this.minus1000 = new ButtonWidget(left + 100, top + 42, 38, 20, new LiteralText("-" + d), - btn -> addQty(-d))); + buttons.add(new ButtonWidget(left, top + 42, 22, 20, makeLabel(MINUS, a), btn -> addQty(-a))); + buttons.add(new ButtonWidget(left + 28, top + 42, 28, 20, makeLabel(MINUS, b), btn -> addQty(-b))); + buttons.add(new ButtonWidget(left + 62, top + 42, 32, 20, makeLabel(MINUS, c), btn -> addQty(-c))); + buttons.add(new ButtonWidget(left + 100, top + 42, 38, 20, makeLabel(MINUS, d), btn -> addQty(-d))); + + this.buttons = buttons; + this.buttons.forEach(addButton); } private void addQty(final long i) { @@ -135,4 +124,8 @@ public class NumberEntryWidget implements ITickingWidget { this.level.tick(); } + private Text makeLabel(Text prefix, int amount) { + return prefix.copy().append(String.valueOf(amount)); + } + } diff --git a/src/main/java/appeng/client/render/FacingToRotation.java b/src/main/java/appeng/client/render/FacingToRotation.java index e0c72bfe3..aa5e96140 100644 --- a/src/main/java/appeng/client/render/FacingToRotation.java +++ b/src/main/java/appeng/client/render/FacingToRotation.java @@ -18,6 +18,8 @@ package appeng.client.render; +import java.util.Locale; + import net.minecraft.client.util.math.MatrixStack; import net.minecraft.client.util.math.Vector3f; import net.minecraft.client.util.math.Vector4f; @@ -112,6 +114,6 @@ public enum FacingToRotation implements StringIdentifiable { @Override public String asString() { - return name().toLowerCase(); + return name().toLowerCase(Locale.ROOT); } } diff --git a/src/main/java/appeng/client/render/cablebus/CableBuilder.java b/src/main/java/appeng/client/render/cablebus/CableBuilder.java index 008902be3..386031651 100644 --- a/src/main/java/appeng/client/render/cablebus/CableBuilder.java +++ b/src/main/java/appeng/client/render/cablebus/CableBuilder.java @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.EnumMap; import java.util.EnumSet; import java.util.List; +import java.util.Locale; import java.util.function.Function; import net.fabricmc.api.EnvType; @@ -104,7 +105,7 @@ class CableBuilder { } return new SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEX, - new Identifier(AppEng.MOD_ID, textureFolder + color.name().toLowerCase())); + new Identifier(AppEng.MOD_ID, textureFolder + color.name().toLowerCase(Locale.ROOT))); } /** diff --git a/src/main/java/appeng/client/render/cablebus/CableCoreType.java b/src/main/java/appeng/client/render/cablebus/CableCoreType.java index 6f3caac80..01928e150 100644 --- a/src/main/java/appeng/client/render/cablebus/CableCoreType.java +++ b/src/main/java/appeng/client/render/cablebus/CableCoreType.java @@ -19,6 +19,7 @@ package appeng.client.render.cablebus; import java.util.EnumMap; +import java.util.Locale; import java.util.Map; import com.google.common.collect.ImmutableMap; @@ -74,7 +75,7 @@ public enum CableCoreType { public SpriteIdentifier getTexture(AEColor color) { return new SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEX, - new Identifier(AppEng.MOD_ID, this.textureFolder + "/" + color.name().toLowerCase())); + new Identifier(AppEng.MOD_ID, this.textureFolder + "/" + color.name().toLowerCase(Locale.ROOT))); } } diff --git a/src/main/java/appeng/client/render/effects/EnergyParticleData.java b/src/main/java/appeng/client/render/effects/EnergyParticleData.java index 192f731c5..f77d37c3a 100644 --- a/src/main/java/appeng/client/render/effects/EnergyParticleData.java +++ b/src/main/java/appeng/client/render/effects/EnergyParticleData.java @@ -49,7 +49,7 @@ public class EnergyParticleData implements ParticleEffect { reader.expect(' '); boolean forItem = reader.readBoolean(); reader.expect(' '); - AEPartLocation direction = AEPartLocation.valueOf(reader.readString().toUpperCase()); + AEPartLocation direction = AEPartLocation.valueOf(reader.readString().toUpperCase(Locale.ROOT)); return new EnergyParticleData(forItem, direction); } @@ -74,7 +74,8 @@ public class EnergyParticleData implements ParticleEffect { @Override public String asString() { - return String.format(Locale.ROOT, "%s %s", forItem ? "true" : "false", direction.name().toLowerCase()); + return String.format(Locale.ROOT, "%s %s", forItem ? "true" : "false", + direction.name().toLowerCase(Locale.ROOT)); } } diff --git a/src/main/java/appeng/client/render/spatial/SpatialPylonModel.java b/src/main/java/appeng/client/render/spatial/SpatialPylonModel.java index 98f274353..95580ee0b 100644 --- a/src/main/java/appeng/client/render/spatial/SpatialPylonModel.java +++ b/src/main/java/appeng/client/render/spatial/SpatialPylonModel.java @@ -20,6 +20,7 @@ package appeng.client.render.spatial; import java.util.Arrays; import java.util.EnumMap; +import java.util.Locale; import java.util.Map; import java.util.function.Function; import java.util.stream.Stream; @@ -59,7 +60,7 @@ public class SpatialPylonModel implements BasicUnbakedModel { private static SpriteIdentifier getTexturePath(SpatialPylonTextureType type) { return new SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEX, - new Identifier(AppEng.MOD_ID, "block/spatial_pylon/" + type.name().toLowerCase())); + new Identifier(AppEng.MOD_ID, "block/spatial_pylon/" + type.name().toLowerCase(Locale.ROOT))); } } diff --git a/src/main/java/appeng/core/AppEngBase.java b/src/main/java/appeng/core/AppEngBase.java index 3b20a6e62..adcc416fe 100644 --- a/src/main/java/appeng/core/AppEngBase.java +++ b/src/main/java/appeng/core/AppEngBase.java @@ -1,56 +1,5 @@ package appeng.core; -import java.util.OptionalLong; -import java.util.function.Consumer; - -import javax.annotation.Nullable; - -import net.earthcomputer.libstructure.LibStructure; -import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback; -import net.fabricmc.fabric.api.screenhandler.v1.ScreenHandlerRegistry; -import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.block.BlockState; -import net.minecraft.block.entity.BannerBlockEntity; -import net.minecraft.block.entity.BeaconBlockEntity; -import net.minecraft.block.entity.BrewingStandBlockEntity; -import net.minecraft.block.entity.ChestBlockEntity; -import net.minecraft.block.entity.CommandBlockBlockEntity; -import net.minecraft.block.entity.ComparatorBlockEntity; -import net.minecraft.block.entity.DaylightDetectorBlockEntity; -import net.minecraft.block.entity.DispenserBlockEntity; -import net.minecraft.block.entity.DropperBlockEntity; -import net.minecraft.block.entity.EnchantingTableBlockEntity; -import net.minecraft.block.entity.EndPortalBlockEntity; -import net.minecraft.block.entity.EnderChestBlockEntity; -import net.minecraft.block.entity.FurnaceBlockEntity; -import net.minecraft.block.entity.HopperBlockEntity; -import net.minecraft.block.entity.MobSpawnerBlockEntity; -import net.minecraft.block.entity.PistonBlockEntity; -import net.minecraft.block.entity.ShulkerBoxBlockEntity; -import net.minecraft.block.entity.SignBlockEntity; -import net.minecraft.block.entity.SkullBlockEntity; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.entity.player.PlayerInventory; -import net.minecraft.item.ItemStack; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.screen.ScreenHandlerType; -import net.minecraft.tag.BlockTags; -import net.minecraft.util.Identifier; -import net.minecraft.util.registry.BuiltinRegistries; -import net.minecraft.util.registry.Registry; -import net.minecraft.world.World; -import net.minecraft.world.biome.Biome; -import net.minecraft.world.dimension.DimensionType; -import net.minecraft.world.gen.GenerationStep; -import net.minecraft.world.gen.chunk.StructureConfig; -import net.minecraft.world.gen.decorator.Decorator; -import net.minecraft.world.gen.decorator.NopeDecoratorConfig; -import net.minecraft.world.gen.decorator.RangeDecoratorConfig; -import net.minecraft.world.gen.feature.DefaultFeatureConfig; -import net.minecraft.world.gen.feature.Feature; -import net.minecraft.world.gen.feature.FeatureConfig; -import net.minecraft.world.gen.feature.OreFeatureConfig; - import appeng.api.config.Upgrades; import appeng.api.definitions.IBlocks; import appeng.api.definitions.IItems; @@ -124,7 +73,6 @@ import appeng.fluids.container.FluidLevelEmitterContainer; import appeng.fluids.container.FluidStorageBusContainer; import appeng.fluids.container.FluidTerminalContainer; import appeng.fluids.registries.BasicFluidCellGuiHandler; -import appeng.hooks.RegisterDimensionTypeCallback; import appeng.hooks.ToolItemHook; import appeng.items.parts.FacadeItem; import appeng.items.tools.NetworkToolItem; @@ -145,14 +93,61 @@ import appeng.recipes.handlers.InscriberRecipe; import appeng.recipes.handlers.InscriberRecipeSerializer; import appeng.recipes.handlers.QuartzKnifeRecipeSerializer; import appeng.server.AECommand; -import appeng.spatial.SpatialDimensionManager; -import appeng.spatial.StorageCellBiome; -import appeng.spatial.StorageChunkGenerator; +import appeng.spatial.SpatialStorageBiome; +import appeng.spatial.SpatialStorageChunkGenerator; +import appeng.spatial.SpatialStorageDimensionIds; import appeng.tile.AEBaseBlockEntity; import appeng.worldgen.BiomeModifier; import appeng.worldgen.ChargedQuartzOreConfig; import appeng.worldgen.ChargedQuartzOreFeature; import appeng.worldgen.meteorite.MeteoriteStructure; +import net.earthcomputer.libstructure.LibStructure; +import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback; +import net.fabricmc.fabric.api.screenhandler.v1.ScreenHandlerRegistry; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BannerBlockEntity; +import net.minecraft.block.entity.BeaconBlockEntity; +import net.minecraft.block.entity.BrewingStandBlockEntity; +import net.minecraft.block.entity.ChestBlockEntity; +import net.minecraft.block.entity.CommandBlockBlockEntity; +import net.minecraft.block.entity.ComparatorBlockEntity; +import net.minecraft.block.entity.DaylightDetectorBlockEntity; +import net.minecraft.block.entity.DispenserBlockEntity; +import net.minecraft.block.entity.DropperBlockEntity; +import net.minecraft.block.entity.EnchantingTableBlockEntity; +import net.minecraft.block.entity.EndPortalBlockEntity; +import net.minecraft.block.entity.EnderChestBlockEntity; +import net.minecraft.block.entity.FurnaceBlockEntity; +import net.minecraft.block.entity.HopperBlockEntity; +import net.minecraft.block.entity.MobSpawnerBlockEntity; +import net.minecraft.block.entity.PistonBlockEntity; +import net.minecraft.block.entity.ShulkerBoxBlockEntity; +import net.minecraft.block.entity.SignBlockEntity; +import net.minecraft.block.entity.SkullBlockEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.screen.ScreenHandlerType; +import net.minecraft.util.Identifier; +import net.minecraft.util.registry.BuiltinRegistries; +import net.minecraft.util.registry.Registry; +import net.minecraft.world.World; +import net.minecraft.world.biome.Biome; +import net.minecraft.world.dimension.DimensionType; +import net.minecraft.world.gen.GenerationStep; +import net.minecraft.world.gen.chunk.StructureConfig; +import net.minecraft.world.gen.decorator.Decorator; +import net.minecraft.world.gen.decorator.NopeDecoratorConfig; +import net.minecraft.world.gen.decorator.RangeDecoratorConfig; +import net.minecraft.world.gen.feature.DefaultFeatureConfig; +import net.minecraft.world.gen.feature.Feature; +import net.minecraft.world.gen.feature.FeatureConfig; +import net.minecraft.world.gen.feature.OreFeatureConfig; + +import javax.annotation.Nullable; +import java.util.function.Consumer; public abstract class AppEngBase implements AppEng { @@ -614,16 +609,8 @@ public abstract class AppEngBase implements AppEng { } private void registerDimension() { - Registry.register(BuiltinRegistries.BIOME, AppEng.makeId("storage"), StorageCellBiome.INSTANCE); - Registry.register(Registry.CHUNK_GENERATOR, AppEng.makeId("storage"), StorageChunkGenerator.CODEC); - - RegisterDimensionTypeCallback.EVENT.register(registryTracker -> { - DimensionType dimensionType = new DimensionType(OptionalLong.of(12000), false, false, false, false, false, - false, true, false, false, 256, BlockTags.INFINIBURN_OVERWORLD.getId(), 1.0f); - - Registry.register(registryTracker.getDimensionTypes(), - SpatialDimensionManager.STORAGE_DIMENSION_TYPE.getValue(), dimensionType); - }); + Registry.register(BuiltinRegistries.BIOME, SpatialStorageDimensionIds.BIOME_ID, SpatialStorageBiome.INSTANCE); + Registry.register(Registry.CHUNK_GENERATOR, SpatialStorageDimensionIds.CHUNK_GENERATOR_ID, SpatialStorageChunkGenerator.CODEC); } } diff --git a/src/main/java/appeng/entity/ChargedQuartzEntity.java b/src/main/java/appeng/entity/ChargedQuartzEntity.java index 4f849c0fd..4c76a9008 100644 --- a/src/main/java/appeng/entity/ChargedQuartzEntity.java +++ b/src/main/java/appeng/entity/ChargedQuartzEntity.java @@ -19,6 +19,7 @@ package appeng.entity; import java.util.List; +import java.util.Random; import net.minecraft.block.BlockState; import net.minecraft.block.Material; @@ -42,6 +43,8 @@ import appeng.util.Platform; public final class ChargedQuartzEntity extends AEBaseItemEntity { + private static final Random RANDOM = new Random(); + public static EntityType TYPE; private int delay = 0; @@ -135,8 +138,15 @@ public final class ChargedQuartzEntity extends AEBaseItemEntity { } materials.fluixCrystal().maybeStack(2).ifPresent(is -> { - final ItemEntity entity = new ItemEntity(this.world, this.getX(), this.getY(), this.getZ(), is); + final double x = Math.floor(this.getX()) + .25d + RANDOM.nextDouble() * .5; + final double y = Math.floor(this.getY()) + .25d + RANDOM.nextDouble() * .5; + final double z = Math.floor(this.getZ()) + .25d + RANDOM.nextDouble() * .5; + final double xSpeed = RANDOM.nextDouble() * .25 - 0.125; + final double ySpeed = RANDOM.nextDouble() * .25 - 0.125; + final double zSpeed = RANDOM.nextDouble() * .25 - 0.125; + final ItemEntity entity = new ItemEntity(this.world, x, y, z, is); + entity.setVelocity(xSpeed, ySpeed, zSpeed); this.world.spawnEntity(entity); }); diff --git a/src/main/java/appeng/hooks/DynamicDimensions.java b/src/main/java/appeng/hooks/DynamicDimensions.java deleted file mode 100644 index bf52d90df..000000000 --- a/src/main/java/appeng/hooks/DynamicDimensions.java +++ /dev/null @@ -1,14 +0,0 @@ -package appeng.hooks; - -import net.minecraft.server.world.ServerWorld; -import net.minecraft.util.registry.RegistryKey; -import net.minecraft.world.World; -import net.minecraft.world.dimension.DimensionType; -import net.minecraft.world.gen.chunk.ChunkGenerator; - -public interface DynamicDimensions { - - ServerWorld addWorld(RegistryKey worldId, RegistryKey dimensionTypeId, - ChunkGenerator chunkGenerator); - -} diff --git a/src/main/java/appeng/hooks/RegisterDimensionTypeCallback.java b/src/main/java/appeng/hooks/RegisterDimensionTypeCallback.java deleted file mode 100644 index 127044cc9..000000000 --- a/src/main/java/appeng/hooks/RegisterDimensionTypeCallback.java +++ /dev/null @@ -1,18 +0,0 @@ -package appeng.hooks; - -import net.fabricmc.fabric.api.event.Event; -import net.fabricmc.fabric.api.event.EventFactory; -import net.minecraft.util.registry.DynamicRegistryManager; - -public interface RegisterDimensionTypeCallback { - - Event EVENT = EventFactory.createArrayBacked(RegisterDimensionTypeCallback.class, - (listeners) -> (registryTracker) -> { - for (RegisterDimensionTypeCallback listener : listeners) { - listener.addDimensionTypes(registryTracker); - } - }); - - void addDimensionTypes(DynamicRegistryManager registryTracker); - -} diff --git a/src/main/java/appeng/hooks/UnlitQuadHooks.java b/src/main/java/appeng/hooks/UnlitQuadHooks.java new file mode 100644 index 000000000..a5835728d --- /dev/null +++ b/src/main/java/appeng/hooks/UnlitQuadHooks.java @@ -0,0 +1,130 @@ +package appeng.hooks; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import appeng.core.AppEng; +import appeng.mixins.unlitquad.BakedQuadAccessor; +import net.minecraft.client.render.LightmapTextureManager; +import net.minecraft.client.render.VertexFormat; +import net.minecraft.client.render.VertexFormatElement; +import net.minecraft.client.render.VertexFormats; +import net.minecraft.client.render.model.BakedQuad; +import net.minecraft.client.render.model.json.ModelElementFace; +import net.minecraft.client.render.model.json.ModelElementTexture; +import net.minecraft.client.texture.Sprite; +import net.minecraft.util.Identifier; +import net.minecraft.util.JsonHelper; +import net.minecraft.util.math.Direction; + +/** + * Implementation details of allowing quads to be defined as "unlit" in JSON + * models by specifying an "unlit" boolean property on the face. + */ +public class UnlitQuadHooks { + + /** + * Vertex format used for blocks. + */ + private static final VertexFormat VERTEX_FORMAT = VertexFormats.POSITION_COLOR_TEXTURE_LIGHT_NORMAL; + + /** + * Offset into the vertex data (which is represented as integers). + */ + private static final int LIGHT_OFFSET = getLightOffset(); + + // Lightmap texture coordinate with full intensity light leading to no drop in + // brightness + private static final int UNLIT_LIGHT_UV = LightmapTextureManager.pack(15, 15); + + /** + * Thread-Local flag to indicate that an enhanced Applied Energistics model is + * currently being deserialized. + */ + private static final ThreadLocal ENABLE_UNLIT_EXTENSIONS = new ThreadLocal<>(); + + /** + * Notify the unlit model system that a specific model is about to be + * deserialized by {@link net.minecraft.client.render.model.ModelLoader}. + */ + public static void beginDeserializingModel(Identifier location) { + String namespace = location.getNamespace(); + if (namespace.equals(AppEng.MOD_ID)) { + ENABLE_UNLIT_EXTENSIONS.set(true); + } + } + + /** + * Notify the unlit model system that deserialization of a model has ended. + */ + public static void endDeserializingModel() { + ENABLE_UNLIT_EXTENSIONS.set(false); + } + + public static boolean isUnlitExtensionEnabled() { + Boolean b = ENABLE_UNLIT_EXTENSIONS.get(); + return b != null && b; + } + + public static ModelElementFace enhanceModelElementFace(ModelElementFace modelElement, JsonElement jsonElement) { + JsonObject jsonObject = jsonElement.getAsJsonObject(); + if (JsonHelper.getBoolean(jsonObject, "unlit", false)) { + return new UnlitModelElementFace(modelElement.cullFace, modelElement.tintIndex, modelElement.textureId, + modelElement.textureData); + } + return modelElement; + } + + /** + * Creates a new quad from the given quad and pre-bakes it to not be affected by + * lighting (neither diffuse lighting nor the prebaked lightmap). This works on + * the assumption that Vanilla will not modify a quad's lightmap data if it's + * not zero. + */ + public static BakedQuad makeUnlit(BakedQuad quad) { + int[] vertexData = quad.getVertexData().clone(); + int stride = VERTEX_FORMAT.getVertexSizeInteger(); + // Set the pre-baked texture coords for the lightmap. + // Vanilla will not overwrite them if they are non-zero + for (int i = 0; i < 4; i++) { + vertexData[stride * i + LIGHT_OFFSET] = UNLIT_LIGHT_UV; + } + Sprite sprite = ((BakedQuadAccessor) quad).getSprite(); + // Copy the quad to disable diffuse lighting + return new BakedQuad(vertexData, quad.getColorIndex(), quad.getFace(), sprite, false /* diffuse lighting */); + } + + /** + * This subclass is used as a marker to indicate this face deserialized from + * JSON is supposed to be unlit, which translates to processing by + * {@link #makeUnlit(BakedQuad)}. + */ + public static class UnlitModelElementFace extends ModelElementFace { + public UnlitModelElementFace(Direction cullFaceIn, int tintIndexIn, String textureIn, ModelElementTexture blockFaceUVIn) { + super(cullFaceIn, tintIndexIn, textureIn, blockFaceUVIn); + } + } + + /** + * Find the index in the BakedQuad vertex data (for vertex 0) where the lightmap + * coordinates are. Assumes the BLOCK vertex format. + */ + private static int getLightOffset() { + int offset = 0; + for (VertexFormatElement element : VERTEX_FORMAT.getElements()) { + // LIGHT_ELEMENT is the lightmap vertex element + if (element == VertexFormats.LIGHT_ELEMENT) { + if (element.getFormat() != VertexFormatElement.Format.SHORT) { + throw new UnsupportedOperationException("Expected light map format to be of type SHORT"); + } + if (offset % 4 != 0) { + throw new UnsupportedOperationException("Expected light map offset to be 4-byte aligned"); + } + return offset / 4; + } + offset += element.getSize(); + } + throw new UnsupportedOperationException("Failed to find the lightmap index in the block vertex format"); + } + +} diff --git a/src/main/java/appeng/integration/modules/waila/BaseWailaDataProvider.java b/src/main/java/appeng/integration/modules/waila/BaseWailaDataProvider.java index 5a339191e..ccf83b78f 100644 --- a/src/main/java/appeng/integration/modules/waila/BaseWailaDataProvider.java +++ b/src/main/java/appeng/integration/modules/waila/BaseWailaDataProvider.java @@ -47,7 +47,7 @@ public abstract class BaseWailaDataProvider implements IComponentProvider, IServ } @Override - public void appendServerData(CompoundTag compoundNBT, ServerPlayerEntity serverPlayerEntity, World world, + public void appendServerData(CompoundTag CompoundTag, ServerPlayerEntity serverPlayerEntity, World world, BlockEntity tileEntity) { } diff --git a/src/main/java/appeng/items/storage/SpatialStorageCellItem.java b/src/main/java/appeng/items/storage/SpatialStorageCellItem.java index b59ec9afa..ac3417421 100644 --- a/src/main/java/appeng/items/storage/SpatialStorageCellItem.java +++ b/src/main/java/appeng/items/storage/SpatialStorageCellItem.java @@ -18,35 +18,39 @@ package appeng.items.storage; +import java.time.Instant; import java.util.List; +import java.util.Locale; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.minecraft.client.item.TooltipContext; -import net.minecraft.item.ItemStack; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.server.world.ServerWorld; -import net.minecraft.text.LiteralText; -import net.minecraft.text.Text; -import net.minecraft.util.Formatting; -import net.minecraft.util.Identifier; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.registry.Registry; -import net.minecraft.util.registry.RegistryKey; -import net.minecraft.world.World; - -import appeng.api.implementations.TransitionResult; import appeng.api.implementations.items.ISpatialStorageCell; -import appeng.api.storage.ISpatialDimension; import appeng.api.util.WorldCoord; import appeng.core.AELog; import appeng.core.localization.GuiText; import appeng.items.AEBaseItem; -import appeng.spatial.SpatialDimensionManager; -import appeng.spatial.StorageHelper; +import appeng.spatial.SpatialStorageHelper; +import appeng.spatial.SpatialStoragePlot; +import appeng.spatial.SpatialStoragePlotManager; +import appeng.spatial.TransitionInfo; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.util.NbtType; +import net.minecraft.client.item.TooltipContext; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; public class SpatialStorageCellItem extends AEBaseItem implements ISpatialStorageCell { - private static final String TAG_DIMENSION_ID = "dimension_id"; + private static final String TAG_PLOT_ID = "plot_id"; + + /** + * This is only stored in the itemstack to display in the tooltip on the + * client-side. + */ + private static final String TAG_PLOT_SIZE = "plot_size"; private final int maxRegion; @@ -58,19 +62,23 @@ public class SpatialStorageCellItem extends AEBaseItem implements ISpatialStorag @Environment(EnvType.CLIENT) @Override public void appendTooltip(final ItemStack stack, final World world, final List lines, - final TooltipContext advancedTooltips) { - final RegistryKey worldId = this.getStoredDimension(stack); - if (worldId == null) { + final TooltipContext advancedTooltips) { + int plotId = this.getAllocatedPlotId(stack); + if (plotId == -1) { lines.add(GuiText.Unformatted.text().copy().formatted(Formatting.ITALIC)); lines.add(GuiText.SpatialCapacity.text(maxRegion, maxRegion, maxRegion)); - } else { - SpatialDimensionManager.INSTANCE.addCellDimensionTooltip(worldId, lines); + return; } - if (advancedTooltips.isAdvanced()) { - if (worldId != null && worldId.getValue() != null) { - lines.add(new LiteralText("Dimension: " + worldId.getValue())); - } + // Add a serial number to allows players to keep different cells apart + // Try to make this a little more flavorful. + String serialNumber = String.format(Locale.ROOT, "SP-%04d", plotId); + lines.add(GuiText.SerialNumber.text(serialNumber)); + + CompoundTag tag = stack.getTag(); + if (tag != null && tag.contains(TAG_PLOT_SIZE, NbtType.LONG)) { + BlockPos size = BlockPos.fromLong(tag.getLong(TAG_PLOT_SIZE)); + lines.add(GuiText.StoredSize.text(size.getX(), size.getY(), size.getZ())); } } @@ -85,70 +93,80 @@ public class SpatialStorageCellItem extends AEBaseItem implements ISpatialStorag } @Override - public RegistryKey getStoredDimension(final ItemStack is) { + public int getAllocatedPlotId(final ItemStack is) { final CompoundTag c = is.getTag(); - if (c != null && c.contains(TAG_DIMENSION_ID)) { + if (c != null && c.contains(TAG_PLOT_ID)) { try { - Identifier worldId = new Identifier(c.getString(TAG_DIMENSION_ID)); - return RegistryKey.of(Registry.DIMENSION, worldId); + int plotId = c.getInt(TAG_PLOT_ID); + if (SpatialStoragePlotManager.INSTANCE.getPlot(plotId) == null) { + return -1; + } + return plotId; } catch (Exception e) { - AELog.warn("Failed to retrieve storage cell dimension.", e); + AELog.warn("Failed to retrieve spatial storage dimension: %s", e); } } - return null; + return -1; } @Override - public TransitionResult doSpatialTransition(final ItemStack is, final ServerWorld w, final WorldCoord min, - final WorldCoord max, int playerId) { + public boolean doSpatialTransition(final ItemStack is, final ServerWorld w, final WorldCoord min, + final WorldCoord max, int playerId) { final int targetX = max.x - min.x - 1; final int targetY = max.y - min.y - 1; final int targetZ = max.z - min.z - 1; final int maxSize = this.getMaxStoredDim(is); + if (targetX > maxSize && targetY > maxSize && targetZ > maxSize) { + AELog.info( + "Failing spatial transition because the transfer area (%dx%dx%d) exceeds the cell capacity (%s).", + targetX, targetY, targetZ, maxSize); + return false; + } final BlockPos targetSize = new BlockPos(targetX, targetY, targetZ); - ISpatialDimension manager = SpatialDimensionManager.INSTANCE; + SpatialStoragePlotManager manager = SpatialStoragePlotManager.INSTANCE; - RegistryKey worldId = this.getStoredDimension(is); - if (worldId == null || manager.getWorld(worldId) == null) { - worldId = manager.createNewCellDimension(targetSize); + SpatialStoragePlot plot = SpatialStoragePlotManager.INSTANCE.getPlot(this.getAllocatedPlotId(is)); + if (plot != null) { + // Check that the existing plot has the right size + if (!plot.getSize().equals(targetSize)) { + AELog.info( + "Failing spatial transition because the transfer area (%dx%dx%d) does not match the spatial storage plot's size (%s).", + targetX, targetY, targetZ, plot.getSize()); + return false; + } + } else { + // Otherwise allocate a new one + plot = manager.allocatePlot(targetSize, playerId); } - if (worldId == null) { - // Failed to create the dimension - return new TransitionResult(false, 0); - } + // Store some information about this transition in the plot + TransitionInfo info = new TransitionInfo(w.getRegistryKey().getValue(), min.getBlockPos(), + max.getBlockPos(), Instant.now()); + manager.setLastTransition(plot.getId(), info); try { - if (manager.isCellDimension(worldId)) { - ServerWorld cellWorld = manager.getWorld(worldId); + ServerWorld cellWorld = manager.getWorld(); - BlockPos scale = manager.getCellDimensionSize(worldId); + BlockPos offset = plot.getOrigin(); - if (scale.equals(targetSize)) { - if (targetX <= maxSize && targetY <= maxSize && targetZ <= maxSize) { - BlockPos offset = manager.getCellDimensionOrigin(worldId); + this.setStoredDimension(is, plot.getId(), plot.getSize()); + SpatialStorageHelper.getInstance().swapRegions(w, min.x + 1, min.y + 1, min.z + 1, cellWorld, offset.getX(), + offset.getY(), offset.getZ(), targetX - 1, targetY - 1, targetZ - 1); - this.setStoredDimension(is, worldId); - StorageHelper.getInstance().swapRegions(w, min.x + 1, min.y + 1, min.z + 1, cellWorld, - offset.getX(), offset.getY(), offset.getZ(), targetX - 1, targetY - 1, targetZ - 1); - - return new TransitionResult(true, 0); - } - } - } - return new TransitionResult(false, 0); + return true; } finally { // clean up newly created dimensions that failed transfer - if (manager.isCellDimension(worldId) && this.getStoredDimension(is) == null) { - manager.deleteCellDimension(worldId); + if (this.getAllocatedPlotId(is) == -1) { + manager.freePlot(plot.getId(), true); } } } - private void setStoredDimension(final ItemStack is, RegistryKey worldId) { + public void setStoredDimension(final ItemStack is, int plotId, BlockPos size) { final CompoundTag c = is.getOrCreateTag(); - c.putString(TAG_DIMENSION_ID, worldId.getValue().toString()); + c.putInt(TAG_PLOT_ID, plotId); + c.putLong(TAG_PLOT_SIZE, size.asLong()); } } diff --git a/src/main/java/appeng/mixins/DimensionTypeMixin.java b/src/main/java/appeng/mixins/DimensionTypeMixin.java deleted file mode 100644 index 8b384fd63..000000000 --- a/src/main/java/appeng/mixins/DimensionTypeMixin.java +++ /dev/null @@ -1,22 +0,0 @@ -package appeng.mixins; - -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -import net.minecraft.util.registry.DynamicRegistryManager; -import net.minecraft.world.dimension.DimensionType; - -import appeng.hooks.RegisterDimensionTypeCallback; - -@Mixin(DimensionType.class) -public class DimensionTypeMixin { - - @Inject(method = "addRegistryDefaults", at = @At("TAIL")) - private static void addRegistryDefaults(DynamicRegistryManager.Impl registryTracker, - CallbackInfoReturnable cir) { - RegisterDimensionTypeCallback.EVENT.invoker().addDimensionTypes(registryTracker); - } - -} diff --git a/src/main/java/appeng/mixins/MinecraftServerMixin.java b/src/main/java/appeng/mixins/MinecraftServerMixin.java deleted file mode 100644 index e93eb63b8..000000000 --- a/src/main/java/appeng/mixins/MinecraftServerMixin.java +++ /dev/null @@ -1,106 +0,0 @@ -package appeng.mixins; - -import java.util.Map; -import java.util.concurrent.Executor; - -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; - -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; - -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.WorldGenerationProgressListener; -import net.minecraft.server.world.ServerWorld; -import net.minecraft.util.registry.DynamicRegistryManager; -import net.minecraft.util.registry.Registry; -import net.minecraft.util.registry.RegistryKey; -import net.minecraft.world.SaveProperties; -import net.minecraft.world.World; -import net.minecraft.world.border.WorldBorder; -import net.minecraft.world.border.WorldBorderListener; -import net.minecraft.world.dimension.DimensionOptions; -import net.minecraft.world.dimension.DimensionType; -import net.minecraft.world.gen.GeneratorOptions; -import net.minecraft.world.gen.chunk.ChunkGenerator; -import net.minecraft.world.level.ServerWorldProperties; -import net.minecraft.world.level.UnmodifiableLevelProperties; -import net.minecraft.world.level.storage.LevelStorage; - -import appeng.hooks.DynamicDimensions; - -@Mixin(MinecraftServer.class) -public class MinecraftServerMixin implements DynamicDimensions { - - @Shadow - private DynamicRegistryManager.Impl registryManager; - - @Shadow - private Map, ServerWorld> worlds; - - @Shadow - private SaveProperties saveProperties; - - @Shadow - private Executor workerExecutor; - - @Shadow - private LevelStorage.Session session; - - public ServerWorld addWorld(RegistryKey worldId, RegistryKey dimensionTypeId, - ChunkGenerator chunkGenerator) { - Preconditions.checkArgument(!worlds.containsKey(worldId), "World with id %s already exists.", - worldId.getValue()); - - // Each worlds must have a corresponding dimension options, otherwise they will - // not be re-created on load - GeneratorOptions generatorOptions = this.saveProperties.getGeneratorOptions(); - Preconditions.checkArgument(!generatorOptions.getDimensionMap().containsId(worldId.getValue()), - "Dimension config with id %s already exists.", worldId.getValue()); - - DimensionType dimensionType = registryManager.getDimensionTypes().get(dimensionTypeId); - Preconditions.checkArgument(dimensionType != null, "dimensionTypeId %s is not registered", dimensionTypeId); - - // Create dimension options with the given world-id and chunk generator - DimensionOptions dimensionOptions = new DimensionOptions( - () -> registryManager.getDimensionTypes().get(dimensionTypeId), chunkGenerator); - - ServerWorldProperties serverWorldProperties = this.saveProperties.getMainWorldProperties(); - - // This sucks a little, but during startup, the server will use the same - // listener for every world, - // but it is ultimately only stored in the chunk storage. So we retrieve it from - // the overworld to - // mirror the original code as much as possible - ServerWorld overworld = worlds.get(ServerWorld.OVERWORLD); - Preconditions.checkState(overworld != null, "Overworld does not exist!"); - WorldGenerationProgressListener worldGenerationProgressListener = ((ThreadedAnvilChunkStorageAccessor) overworld - .getChunkManager().threadedAnvilChunkStorage).getWorldGenerationProgressListener(); - - // Inherit seed + WorldBorder from overworld - long seed = overworld.getSeed(); - WorldBorder worldBorder = overworld.getWorldBorder(); - - MinecraftServer self = (MinecraftServer) (Object) this; - UnmodifiableLevelProperties unmodifiableLevelProperties = new UnmodifiableLevelProperties(this.saveProperties, - serverWorldProperties); - ServerWorld world = new ServerWorld(self, this.workerExecutor, this.session, unmodifiableLevelProperties, - worldId, dimensionTypeId, dimensionType, worldGenerationProgressListener, chunkGenerator, false, seed, - ImmutableList.of(), false); - worldBorder.addListener(new WorldBorderListener.WorldBorderSyncer(world.getWorldBorder())); - this.worlds.put(worldId, world); - - // Adding it here will cause the world to be re-created on server restart - RegistryKey dimOptKey = RegistryKey.of(Registry.DIMENSION_OPTIONS, worldId.getValue()); - generatorOptions.getDimensionMap().add(dimOptKey, dimensionOptions); - // FIXME FABRIC: might not be needed anymore if dimensions will save regardless - // FIXME FABRIC generatorOptions.getDimensionMap().markLoaded(dimOptKey); // - // Otherwise it wont be saved - // Ensure the save properties are saved, or the world will potentially be lost - // on restart - this.session.method_27426(this.registryManager, this.saveProperties, self.getPlayerManager().getUserData()); - - return world; - } - -} diff --git a/src/main/java/appeng/mixins/spatial/DimensionOptionMixin.java b/src/main/java/appeng/mixins/spatial/DimensionOptionMixin.java new file mode 100644 index 000000000..41f7f0a07 --- /dev/null +++ b/src/main/java/appeng/mixins/spatial/DimensionOptionMixin.java @@ -0,0 +1,37 @@ +package appeng.mixins.spatial; + +import java.util.List; +import java.util.Map; + +import net.minecraft.util.registry.RegistryKey; +import net.minecraft.world.dimension.DimensionOptions; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyVariable; + +import appeng.spatial.SpatialStorageDimensionIds; + +/** + * This Mixin tries to hide the screen that warns users about experimental + * features being used, but only if AE2's dimension is the only added dimension. + */ +@Mixin(DimensionOptions.class) +public class DimensionOptionMixin { + + /** + * This injects right after the method makes a local copy of all present + * dimensions, and modifies the list by removing our mod-provided dimension from + * it. + *

+ * This means Vanilla will perform it's check whether it should display an + * experimental warning without considering our dimension. + */ + @ModifyVariable(method = "method_29567", at = @At(value = "INVOKE_ASSIGN", ordinal = 0, target = "Lcom/google/common/collect/Lists;newArrayList(Ljava/lang/Iterable;)Ljava/util/ArrayList;", remap = false), allow = 1) + private static List, DimensionOptions>> overrideExperimentalCheck( + List, DimensionOptions>> dimensions) { + // this only removes our dimension from the check + dimensions.removeIf(e -> e.getKey() == SpatialStorageDimensionIds.DIMENSION_ID); + return dimensions; + } + +} \ No newline at end of file diff --git a/src/main/java/appeng/mixins/spatial/DimensionTypeMixin.java b/src/main/java/appeng/mixins/spatial/DimensionTypeMixin.java new file mode 100644 index 000000000..43aba7f7b --- /dev/null +++ b/src/main/java/appeng/mixins/spatial/DimensionTypeMixin.java @@ -0,0 +1,32 @@ +package appeng.mixins.spatial; + +import appeng.spatial.SpatialStorageDimensionIds; +import net.minecraft.tag.BlockTags; +import net.minecraft.util.registry.DynamicRegistryManager; +import net.minecraft.util.registry.Registry; +import net.minecraft.world.dimension.DimensionType; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.OptionalLong; + +/** + * Adds the storage cell world dimension type as a built-in dimension type. This + * can be registered as a JSON file as well, but doing so will trigger an + * experimental feature warning when the world is being loaded. + */ +@Mixin(DimensionType.class) +public class DimensionTypeMixin { + + @Inject(method = "addRegistryDefaults", at = @At("TAIL")) + private static void addRegistryDefaults(DynamicRegistryManager.Impl registryTracker, CallbackInfoReturnable cir) { + DimensionType dimensionType = new DimensionType(OptionalLong.of(12000), false, false, false, false, false, + false, true, false, false, 256, BlockTags.INFINIBURN_OVERWORLD.getId(), 1.0f); + + Registry.register(registryTracker.getDimensionTypes(), + SpatialStorageDimensionIds.DIMENSION_TYPE_ID.getValue(), dimensionType); + } + +} diff --git a/src/main/java/appeng/mixins/SkyPropertiesMixin.java b/src/main/java/appeng/mixins/spatial/SkyPropertiesMixin.java similarity index 71% rename from src/main/java/appeng/mixins/SkyPropertiesMixin.java rename to src/main/java/appeng/mixins/spatial/SkyPropertiesMixin.java index b74da5665..8074d481c 100644 --- a/src/main/java/appeng/mixins/SkyPropertiesMixin.java +++ b/src/main/java/appeng/mixins/spatial/SkyPropertiesMixin.java @@ -1,4 +1,4 @@ -package appeng.mixins; +package appeng.mixins.spatial; import java.util.Optional; @@ -11,8 +11,8 @@ import net.minecraft.client.render.SkyProperties; import net.minecraft.util.registry.RegistryKey; import net.minecraft.world.dimension.DimensionType; -import appeng.spatial.SpatialDimensionManager; -import appeng.spatial.StorageSkyProperties; +import appeng.spatial.SpatialStorageDimensionIds; +import appeng.spatial.SpatialStorageSkyProperties; @Mixin(SkyProperties.class) public class SkyPropertiesMixin { @@ -20,8 +20,8 @@ public class SkyPropertiesMixin { @Inject(method = "byDimensionType", at = @At("HEAD"), cancellable = true) private static void byDimensionType(Optional> optional, CallbackInfoReturnable ci) { - if (optional.orElse(null) == SpatialDimensionManager.STORAGE_DIMENSION_TYPE) { - ci.setReturnValue(StorageSkyProperties.INSTANCE); + if (optional.orElse(null) == SpatialStorageDimensionIds.DIMENSION_TYPE_ID) { + ci.setReturnValue(SpatialStorageSkyProperties.INSTANCE); } } diff --git a/src/main/java/appeng/mixins/SkyRenderMixin.java b/src/main/java/appeng/mixins/spatial/SkyRenderMixin.java similarity index 83% rename from src/main/java/appeng/mixins/SkyRenderMixin.java rename to src/main/java/appeng/mixins/spatial/SkyRenderMixin.java index 01e34ed45..d6de03d4b 100644 --- a/src/main/java/appeng/mixins/SkyRenderMixin.java +++ b/src/main/java/appeng/mixins/spatial/SkyRenderMixin.java @@ -1,4 +1,4 @@ -package appeng.mixins; +package appeng.mixins.spatial; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -11,7 +11,7 @@ import net.minecraft.client.render.WorldRenderer; import net.minecraft.client.util.math.MatrixStack; import appeng.client.render.SpatialSkyRender; -import appeng.spatial.SpatialDimensionManager; +import appeng.spatial.SpatialStorageDimensionIds; @Mixin(WorldRenderer.class) public class SkyRenderMixin { @@ -22,7 +22,7 @@ public class SkyRenderMixin { @SuppressWarnings("ConstantConditions") @Inject(method = "renderSky", at = @At("HEAD"), cancellable = true) public void renderSky(MatrixStack matrices, float tickDelta, CallbackInfo ci) { - if (client.world.getDimensionRegistryKey() == SpatialDimensionManager.STORAGE_DIMENSION_TYPE) { + if (client.world.getRegistryKey() == SpatialStorageDimensionIds.WORLD_ID) { SpatialSkyRender.getInstance().render(matrices); ci.cancel(); } diff --git a/src/main/java/appeng/mixins/unlitquad/BakedQuadAccessor.java b/src/main/java/appeng/mixins/unlitquad/BakedQuadAccessor.java new file mode 100644 index 000000000..12ff54e1c --- /dev/null +++ b/src/main/java/appeng/mixins/unlitquad/BakedQuadAccessor.java @@ -0,0 +1,14 @@ +package appeng.mixins.unlitquad; + +import net.minecraft.client.render.model.BakedQuad; +import net.minecraft.client.texture.Sprite; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(BakedQuad.class) +public interface BakedQuadAccessor { + + @Accessor + Sprite getSprite(); + +} diff --git a/src/main/java/appeng/mixins/unlitquad/JsonUnbakedModelMixin.java b/src/main/java/appeng/mixins/unlitquad/JsonUnbakedModelMixin.java new file mode 100644 index 000000000..66a147c56 --- /dev/null +++ b/src/main/java/appeng/mixins/unlitquad/JsonUnbakedModelMixin.java @@ -0,0 +1,34 @@ +package appeng.mixins.unlitquad; + +import appeng.hooks.UnlitQuadHooks; +import net.minecraft.client.render.model.BakedQuad; +import net.minecraft.client.render.model.ModelBakeSettings; +import net.minecraft.client.render.model.json.JsonUnbakedModel; +import net.minecraft.client.render.model.json.ModelElement; +import net.minecraft.client.render.model.json.ModelElementFace; +import net.minecraft.client.texture.Sprite; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.Direction; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +/** + * This mixin hooks into conversion from {@link ModelElementFace} to + * {@link BakedQuad} to apply our unlit extensions if the block part face is an + * instance of our marker class + * {@link appeng.hooks.UnlitQuadHooks.UnlitModelElementFace}. + */ +@Mixin(JsonUnbakedModel.class) +public class JsonUnbakedModelMixin { + + @Inject(method = "createQuad", at = @At("RETURN"), cancellable = true, require = 1, allow = 1) + private static void onBakeFace(ModelElement element, ModelElementFace elementFace, Sprite sprite, Direction side, + ModelBakeSettings settings, Identifier id, CallbackInfoReturnable cri) { + if (elementFace instanceof UnlitQuadHooks.UnlitModelElementFace) { + cri.setReturnValue(UnlitQuadHooks.makeUnlit(cri.getReturnValue())); + } + } + +} diff --git a/src/main/java/appeng/mixins/unlitquad/ModelElementFaceDeserializerMixin.java b/src/main/java/appeng/mixins/unlitquad/ModelElementFaceDeserializerMixin.java new file mode 100644 index 000000000..9736e9567 --- /dev/null +++ b/src/main/java/appeng/mixins/unlitquad/ModelElementFaceDeserializerMixin.java @@ -0,0 +1,32 @@ +package appeng.mixins.unlitquad; + +import appeng.hooks.UnlitQuadHooks; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonElement; +import net.minecraft.client.render.model.json.ModelElementFace; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.lang.reflect.Type; + +/** + * This mixin will call the hook to deserialize the unlit property, but only if + * we are currently deserializing an AE2 model. + */ +@Mixin(ModelElementFace.Deserializer.class) +public class ModelElementFaceDeserializerMixin { + + @Inject(method = "deserialize", at = @At("RETURN"), cancellable = true, allow = 1, remap = false) + public void onDeserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext, + CallbackInfoReturnable cri) { + if (!UnlitQuadHooks.isUnlitExtensionEnabled()) { + return; // Not in a model that activated the deserializer + } + + ModelElementFace modelElement = cri.getReturnValue(); + cri.setReturnValue(UnlitQuadHooks.enhanceModelElementFace(modelElement, jsonElement)); + } + +} diff --git a/src/main/java/appeng/mixins/unlitquad/ModelLoaderMixin.java b/src/main/java/appeng/mixins/unlitquad/ModelLoaderMixin.java new file mode 100644 index 000000000..463d540bb --- /dev/null +++ b/src/main/java/appeng/mixins/unlitquad/ModelLoaderMixin.java @@ -0,0 +1,32 @@ +package appeng.mixins.unlitquad; + +import appeng.hooks.UnlitQuadHooks; +import net.minecraft.client.render.model.ModelLoader; +import net.minecraft.client.render.model.json.JsonUnbakedModel; +import net.minecraft.util.Identifier; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.io.IOException; + +/** + * The only job of this mixin is to only enable the unlit extensions if the + * model is whitelisted for it, which is decided in {@link UnlitQuadHooks}. + */ +@Mixin(ModelLoader.class) +public class ModelLoaderMixin { + + @Inject(method = "loadModelFromJson", at = @At("HEAD"), allow = 1) + protected void onBeginLoadModel(Identifier location, CallbackInfoReturnable cri) + throws IOException { + UnlitQuadHooks.beginDeserializingModel(location); + } + + @Inject(method = "loadModelFromJson", at = @At("RETURN")) + protected void onEndLoadModel(Identifier location, CallbackInfoReturnable cri) { + UnlitQuadHooks.endDeserializingModel(); + } + +} diff --git a/src/main/java/appeng/parts/automation/IdentityAnnihilationPlanePart.java b/src/main/java/appeng/parts/automation/IdentityAnnihilationPlanePart.java index d235d3129..5049c0454 100644 --- a/src/main/java/appeng/parts/automation/IdentityAnnihilationPlanePart.java +++ b/src/main/java/appeng/parts/automation/IdentityAnnihilationPlanePart.java @@ -32,10 +32,7 @@ import net.minecraft.item.ItemStack; import net.minecraft.server.world.ServerWorld; import net.minecraft.util.math.BlockPos; -import appeng.api.parts.IPart; -import appeng.api.parts.IPartHost; import appeng.api.parts.IPartModel; -import appeng.api.util.AEPartLocation; import appeng.items.parts.PartModels; public class IdentityAnnihilationPlanePart extends AnnihilationPlanePart { diff --git a/src/main/java/appeng/parts/p2p/FluidP2PTunnelPart.java b/src/main/java/appeng/parts/p2p/FluidP2PTunnelPart.java index 317585231..1f2da27e3 100644 --- a/src/main/java/appeng/parts/p2p/FluidP2PTunnelPart.java +++ b/src/main/java/appeng/parts/p2p/FluidP2PTunnelPart.java @@ -60,7 +60,8 @@ public class FluidP2PTunnelPart extends P2PTunnelPart { return MODELS.getModels(); } - public float getPowerDrainPerTick() { + @Override + protected float getPowerDrainPerTick() { return 2.0f; } diff --git a/src/main/java/appeng/parts/p2p/ItemP2PTunnelPart.java b/src/main/java/appeng/parts/p2p/ItemP2PTunnelPart.java index 894686b81..eb99dc0a5 100644 --- a/src/main/java/appeng/parts/p2p/ItemP2PTunnelPart.java +++ b/src/main/java/appeng/parts/p2p/ItemP2PTunnelPart.java @@ -66,6 +66,11 @@ public class ItemP2PTunnelPart extends P2PTunnelPart implemen super(is); } + @Override + protected float getPowerDrainPerTick() { + return POWER_DRAIN; + } + @Override public void onNeighborUpdate(BlockView w, BlockPos pos, BlockPos neighbor) { this.cachedInv = null; @@ -199,10 +204,6 @@ public class ItemP2PTunnelPart extends P2PTunnelPart implemen to.offer(getDestination(), CacheInfo.NOT_CACHABLE); } - public float getPowerDrainPerTick() { - return POWER_DRAIN; - } - @Override public IPartModel getStaticModels() { return MODELS.getModel(this.isPowered(), this.isActive()); diff --git a/src/main/java/appeng/parts/p2p/LightP2PTunnelPart.java b/src/main/java/appeng/parts/p2p/LightP2PTunnelPart.java index eb9dc80b2..120be8296 100644 --- a/src/main/java/appeng/parts/p2p/LightP2PTunnelPart.java +++ b/src/main/java/appeng/parts/p2p/LightP2PTunnelPart.java @@ -56,6 +56,10 @@ public class LightP2PTunnelPart extends P2PTunnelPart implem super(is); } + protected float getPowerDrainPerTick() { + return 0.5f; + } + @Override public void chanRender(final MENetworkChannelsChanged c) { this.onTunnelNetworkChange(); @@ -186,10 +190,6 @@ public class LightP2PTunnelPart extends P2PTunnelPart implem return this.doWork() ? TickRateModulation.URGENT : TickRateModulation.SLOWER; } - public float getPowerDrainPerTick() { - return 0.5f; - } - @Override public IPartModel getStaticModels() { return MODELS.getModel(this.isPowered(), this.isActive()); diff --git a/src/main/java/appeng/parts/p2p/MEP2PTunnelPart.java b/src/main/java/appeng/parts/p2p/MEP2PTunnelPart.java index f7ab90af5..25128b5d6 100644 --- a/src/main/java/appeng/parts/p2p/MEP2PTunnelPart.java +++ b/src/main/java/appeng/parts/p2p/MEP2PTunnelPart.java @@ -67,6 +67,11 @@ public class MEP2PTunnelPart extends P2PTunnelPart implements I this.outerProxy.setFlags(GridFlags.DENSE_CAPACITY, GridFlags.CANNOT_CARRY_COMPRESSED); } + @Override + protected float getPowerDrainPerTick() { + return 2.0f; + } + @Override public void readFromNBT(final CompoundTag extra) { super.readFromNBT(extra); diff --git a/src/main/java/appeng/parts/p2p/P2PTunnelPart.java b/src/main/java/appeng/parts/p2p/P2PTunnelPart.java index 733170477..6bcc49677 100644 --- a/src/main/java/appeng/parts/p2p/P2PTunnelPart.java +++ b/src/main/java/appeng/parts/p2p/P2PTunnelPart.java @@ -59,6 +59,11 @@ public abstract class P2PTunnelPart extends BasicStateP public P2PTunnelPart(final ItemStack is) { super(is); + this.getProxy().setIdlePowerUsage(this.getPowerDrainPerTick()); + } + + protected float getPowerDrainPerTick() { + return 1.0f; } public TunnelCollection getCollection(final Collection collection, diff --git a/src/main/java/appeng/parts/p2p/RedstoneP2PTunnelPart.java b/src/main/java/appeng/parts/p2p/RedstoneP2PTunnelPart.java index 2200fe1d5..2b4310a2f 100644 --- a/src/main/java/appeng/parts/p2p/RedstoneP2PTunnelPart.java +++ b/src/main/java/appeng/parts/p2p/RedstoneP2PTunnelPart.java @@ -55,6 +55,11 @@ public class RedstoneP2PTunnelPart extends P2PTunnelPart super(is); } + @Override + protected float getPowerDrainPerTick() { + return 0.5f; + } + @MENetworkEventSubscribe public void changeStateA(final MENetworkBootingStatusChange bs) { this.setNetworkReady(); @@ -123,10 +128,6 @@ public class RedstoneP2PTunnelPart extends P2PTunnelPart this.setNetworkReady(); } - public float getPowerDrainPerTick() { - return 0.5f; - } - @Override public void onNeighborUpdate(BlockView w, BlockPos pos, BlockPos neighbor) { if (!this.isOutput()) { diff --git a/src/main/java/appeng/server/AECommand.java b/src/main/java/appeng/server/AECommand.java index e4c18a54b..620f1bde6 100644 --- a/src/main/java/appeng/server/AECommand.java +++ b/src/main/java/appeng/server/AECommand.java @@ -20,6 +20,8 @@ package appeng.server; import static net.minecraft.server.command.CommandManager.literal; +import java.util.Locale; + import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.builder.LiteralArgumentBuilder; @@ -47,7 +49,7 @@ public final class AECommand { private void add(LiteralArgumentBuilder builder, Commands subCommand) { - LiteralArgumentBuilder subCommandBuilder = literal(subCommand.name().toLowerCase()) + LiteralArgumentBuilder subCommandBuilder = literal(subCommand.name().toLowerCase(Locale.ROOT)) .requires(src -> src.hasPermissionLevel(subCommand.level)); subCommand.command.addArguments(subCommandBuilder); subCommandBuilder.executes(ctx -> { diff --git a/src/main/java/appeng/server/Commands.java b/src/main/java/appeng/server/Commands.java index 8b5997d7c..a2bd40714 100644 --- a/src/main/java/appeng/server/Commands.java +++ b/src/main/java/appeng/server/Commands.java @@ -19,13 +19,15 @@ package appeng.server; import appeng.server.subcommands.ChunkLogger; +import appeng.server.subcommands.SpatialStorageCommand; import appeng.server.subcommands.Supporters; import appeng.server.subcommands.TestMeteoritesCommand; import appeng.server.subcommands.TestOreGenCommand; public enum Commands { Chunklogger(4, new ChunkLogger(), false), Supporters(0, new Supporters(), false), - TestOreGen(4, new TestOreGenCommand(), true), TestMeteorites(4, new TestMeteoritesCommand(), true); + TestOreGen(4, new TestOreGenCommand(), true), TestMeteorites(4, new TestMeteoritesCommand(), true), + Spatial(4, new SpatialStorageCommand(), false); public final int level; public final ISubCommand command; diff --git a/src/main/java/appeng/server/subcommands/SpatialStorageCommand.java b/src/main/java/appeng/server/subcommands/SpatialStorageCommand.java new file mode 100644 index 000000000..f0f0e3b9a --- /dev/null +++ b/src/main/java/appeng/server/subcommands/SpatialStorageCommand.java @@ -0,0 +1,327 @@ +package appeng.server.subcommands; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.UUID; +import java.util.function.UnaryOperator; + +import appeng.core.AppEng; +import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; + +import net.minecraft.command.CommandException; +import net.minecraft.item.ItemStack; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.ClickEvent; +import net.minecraft.text.HoverEvent; +import net.minecraft.text.LiteralText; +import net.minecraft.text.MutableText; +import net.minecraft.text.Style; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; + +import appeng.core.Api; +import appeng.core.worlddata.WorldData; +import appeng.items.storage.SpatialStorageCellItem; +import appeng.server.ISubCommand; +import appeng.spatial.SpatialStorageDimensionIds; +import appeng.spatial.SpatialStoragePlot; +import appeng.spatial.SpatialStoragePlotManager; +import appeng.spatial.TransitionInfo; + +import net.minecraft.server.command.CommandManager; +import static net.minecraft.server.command.CommandManager.literal; + +/** + * This admin command allows management of spatial storage plots. + */ +public class SpatialStorageCommand implements ISubCommand { + + @Override + public void addArguments(LiteralArgumentBuilder builder) { + // Shows info about a given plot or about the plot the player is currently in + builder.then(literal("info").executes(ctx -> { + showPlotInfo(ctx.getSource(), getCurrentPlot(ctx.getSource())); + return 1; + }).then(CommandManager.argument("plotId", IntegerArgumentType.integer(1)).executes(ctx -> { + int plotId = IntegerArgumentType.getInteger(ctx, "plotId"); + showPlotInfo(ctx.getSource(), getPlot(plotId)); + return 1; + }))); + + // Teleport into the plot + builder.then(literal("tp").then(CommandManager.argument("plotId", IntegerArgumentType.integer(1)).executes(ctx -> { + int plotId = IntegerArgumentType.getInteger(ctx, "plotId"); + teleportToPlot(ctx.getSource(), plotId); + return 1; + }))); + + // Teleport from the current plot back to the source of its content, or do the + // same for a given plot id + builder.then(literal("tpback").executes(ctx -> { + teleportBack(ctx.getSource()); + return 1; + }).then(CommandManager.argument("plotId", IntegerArgumentType.integer(1)).executes(ctx -> { + int plotId = IntegerArgumentType.getInteger(ctx, "plotId"); + teleportBack(ctx.getSource(), getPlot(plotId)); + return 1; + }))); + + // Creates a storage cell for the given plot id and gives it to the player + builder.then( + literal("givecell").then(CommandManager.argument("plotId", IntegerArgumentType.integer(1)).executes(ctx -> { + int plotId = IntegerArgumentType.getInteger(ctx, "plotId"); + giveCell(ctx.getSource(), plotId); + return 1; + }))); + } + + /** + * If the player is currently within a spatial storage plot, teleports them back + * to the last source of transition. + */ + private void teleportBack(ServerCommandSource source) { + + if (source.getWorld().getRegistryKey() != SpatialStorageDimensionIds.WORLD_ID) { + throw new CommandException(Text.method_30163("Must be within the spatial storage world.")); + } + + BlockPos playerPos = new BlockPos(source.getPosition()); + int x = playerPos.getX(); + int z = playerPos.getZ(); + + // This is slow, but for an admin-command it's acceptable + + for (SpatialStoragePlot plot : SpatialStoragePlotManager.INSTANCE.getPlots()) { + BlockPos origin = plot.getOrigin(); + BlockPos size = plot.getSize(); + if (x >= origin.getX() && x <= origin.getX() + size.getX() && z >= origin.getZ() + && z <= origin.getZ() + size.getZ()) { + teleportBack(source, plot); + return; + } + } + + throw new CommandException(Text.method_30163("Couldn't find a plot for the current position.")); + + } + + /** + * Teleports back from the given plot. + */ + private void teleportBack(ServerCommandSource source, SpatialStoragePlot plot) { + TransitionInfo lastTransition = plot.getLastTransition(); + if (lastTransition == null) { + throw new CommandException( + Text.method_30163("This plot doesn't have a last known transition.")); + } + + String command = getTeleportCommand(lastTransition.getWorldId(), lastTransition.getMin().add(0, 1, 0)); + runCommandFor(source, command); + } + + /** + * Shows detailed information about a spatial storage plot. + */ + private static void showPlotInfo(ServerCommandSource source, SpatialStoragePlot plot) { + + sendKeyValuePair(source, "Plot ID", String.valueOf(plot.getId())); + // Show the owner of the spatial storage plot + int playerId = plot.getOwner(); + if (playerId != -1) { + UUID profileId = WorldData.instance().playerData().getProfileId(playerId); + + if (profileId == null) { + sendKeyValuePair(source, "Owner", "Unknown AE2 player (" + playerId + ")"); + } else { + MinecraftServer server = AppEng.instance().getServer(); + ServerPlayerEntity player = server.getPlayerManager().getPlayer(profileId); + if (player == null) { + sendKeyValuePair(source, "Owner", "Unknown Minecraft profile (" + profileId + ")"); + } else { + sendKeyValuePair(source, "Owner", player.getDisplayName()); + } + } + } else { + sendKeyValuePair(source, "Owner", "Unknown"); + } + + sendKeyValuePair(source, "Size", formatBlockPos(plot.getSize(), "x")); + + // Show the plot's origin and make it clickable to teleport directly to it + String teleportToPlotCommand = getTeleportCommand(SpatialStorageDimensionIds.WORLD_ID.getValue(), + plot.getOrigin()); + sendKeyValuePair(source, "Origin", new LiteralText(formatBlockPos(plot.getOrigin(), ",")) + .styled(makeCommandLink(teleportToPlotCommand, "Teleport into plot"))); + + sendKeyValuePair(source, "Region file:", plot.getRegionFilename()); + + // Show information about what was last transfered into the plot (with a + // clickable link to the source) + TransitionInfo lastTransition = plot.getLastTransition(); + if (lastTransition != null) { + source.sendFeedback(new LiteralText("Last Transition:").formatted(Formatting.UNDERLINE, + Formatting.BOLD), true); + + String sourceWorldId = lastTransition.getWorldId().toString(); + MutableText sourceLink = new LiteralText( + sourceWorldId + " - " + formatBlockPos(lastTransition.getMin(), ",") + " to " + + formatBlockPos(lastTransition.getMax(), ",")); + String tpCommand = getTeleportCommand(lastTransition.getWorldId(), lastTransition.getMin().add(0, 1, 0)); + sourceLink.styled(makeCommandLink(tpCommand, "Click to teleport")); + + sendKeyValuePair(source, "Source", sourceLink); + sendKeyValuePair(source, "When", lastTransition.getTimestamp().toString()); + } else { + source.sendFeedback(Text.method_30163("Last Transition unknown"), true); + } + + } + + private static void teleportToPlot(ServerCommandSource source, int plotId) { + SpatialStoragePlot plot = getPlot(plotId); + + String teleportCommand = getTeleportCommand(SpatialStorageDimensionIds.WORLD_ID.getValue(), + plot.getOrigin()); + + runCommandFor(source, teleportCommand); + } + + private void giveCell(ServerCommandSource source, int plotId) throws CommandSyntaxException { + ServerPlayerEntity player = source.getPlayer(); + + SpatialStoragePlot plot = getPlot(plotId); + + ItemStack cell; + int longestSide = getLongestSide(plot.getSize()); + if (longestSide <= 2) { + cell = Api.instance().definitions().items().spatialCell2().stack(1); + } else if (longestSide <= 16) { + cell = Api.instance().definitions().items().spatialCell16().stack(1); + } else { + cell = Api.instance().definitions().items().spatialCell128().stack(1); + } + + if (!(cell.getItem() instanceof SpatialStorageCellItem)) { + throw new CommandException( + Text.method_30163("Storage cell items don't implement the storage cell interface!")); + } + + SpatialStorageCellItem spatialCellItem = (SpatialStorageCellItem) cell.getItem(); + spatialCellItem.setStoredDimension(cell, plotId, plot.getSize()); + + player.giveItemStack(cell); + } + + private static int getLongestSide(BlockPos size) { + return Math.max(size.getX(), Math.max(size.getY(), size.getZ())); + } + + @Override + public void call(MinecraftServer srv, CommandContext ctx, ServerCommandSource sender) { + + List plots = new ArrayList<>(SpatialStoragePlotManager.INSTANCE.getPlots()); + + // Prints the least recently used plots + plots.sort(Comparator.comparing((SpatialStoragePlot plot) -> { + TransitionInfo lastTransition = plot.getLastTransition(); + if (lastTransition != null) { + return lastTransition.getTimestamp(); + } else { + return Instant.MIN; + } + }).reversed()); + + for (int i = 0; i < Math.min(5, plots.size()); i++) { + SpatialStoragePlot plot = plots.get(i); + String size = formatBlockPos(plot.getSize(), "x"); + BlockPos originPos = plot.getOrigin(); + String origin = formatBlockPos(originPos, ","); + + Text infoLink = new LiteralText("Plot #" + plot.getId()) + .styled(makeCommandLink("/ae2 spatial info " + plot.getId(), "Click to show details")); + Text tpLink = new LiteralText("Origin: " + origin) + .styled(makeCommandLink("/ae2 spatial tp " + plot.getId(), "Click to teleport into plot")); + + Text message = new LiteralText("").append(infoLink) + .append(" Size: " + size + " ").append(tpLink); + + sender.sendFeedback(message, true); + } + + } + + private static String formatBlockPos(BlockPos size, String separator) { + return size.getX() + separator + size.getY() + separator + size.getZ(); + } + + private static UnaryOperator