diff --git a/gradle.properties b/gradle.properties index f80f2cd54..b16343f8a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ org.gradle.daemon=false # Project mcVersion=1.12.2 forgeVersion=14.23.5.2860 -modVersion=2.1.10 +modVersion=2.2.1 archiveBase=AdvancedRocketry startGitRev=8e676bd diff --git a/src/main/java/zmaster587/advancedRocketry/AdvancedRocketry.java b/src/main/java/zmaster587/advancedRocketry/AdvancedRocketry.java index 77d37f955..ffb6b7127 100644 --- a/src/main/java/zmaster587/advancedRocketry/AdvancedRocketry.java +++ b/src/main/java/zmaster587/advancedRocketry/AdvancedRocketry.java @@ -628,7 +628,7 @@ public void registerBlocks(RegistryEvent.Register evt) { AdvancedRocketryBlocks.blockForceFieldProjector = new BlockForceFieldProjector(Material.IRON).setUnlocalizedName("forceFieldProjector").setCreativeTab(tabAdvRocketry).setHardness(3f); AdvancedRocketryBlocks.blockForceField = new BlockForceField(Material.BARRIER).setBlockUnbreakable().setResistance(6000000.0F).setUnlocalizedName("forceField"); AdvancedRocketryBlocks.blockVacuumLaser = new BlockFullyRotatable(Material.IRON).setUnlocalizedName("vacuumLaser").setCreativeTab(tabAdvRocketry).setHardness(4f); - AdvancedRocketryBlocks.blockPump = new BlockTile(TilePump.class, GuiHandler.guiId.MODULAR.ordinal()).setUnlocalizedName("pump").setCreativeTab(tabAdvRocketry).setHardness(3f); + AdvancedRocketryBlocks.blockPump = new BlockPump(TilePump.class, GuiHandler.guiId.MODULAR.ordinal()).setUnlocalizedName("pump").setCreativeTab(tabAdvRocketry).setHardness(3f); AdvancedRocketryBlocks.blockSuitWorkStation = new BlockSuitWorkstation(TileSuitWorkStation.class, GuiHandler.guiId.MODULAR.ordinal()).setUnlocalizedName("suitWorkStation").setCreativeTab(tabAdvRocketry).setHardness(3f); AdvancedRocketryBlocks.blockPressureTank = new BlockPressurizedFluidTank(Material.IRON).setUnlocalizedName("pressurizedTank").setCreativeTab(tabAdvRocketry).setHardness(3f); AdvancedRocketryBlocks.blockSolarGenerator = new BlockSolarGenerator(TileSolarPanel.class, GuiHandler.guiId.MODULAR.ordinal()).setCreativeTab(tabAdvRocketry).setHardness(3f).setUnlocalizedName("solarGenerator"); diff --git a/src/main/java/zmaster587/advancedRocketry/api/ARConfiguration.java b/src/main/java/zmaster587/advancedRocketry/api/ARConfiguration.java index 617d27f1f..9051d7528 100644 --- a/src/main/java/zmaster587/advancedRocketry/api/ARConfiguration.java +++ b/src/main/java/zmaster587/advancedRocketry/api/ARConfiguration.java @@ -165,6 +165,10 @@ public class ARConfiguration { @ConfigProperty(needsSync = true) public double gasCollectionMult; @ConfigProperty(needsSync = true) + public double gasHarvestAmountMultiplier; + @ConfigProperty(needsSync = true) + public boolean gasHarvestInfinite; + @ConfigProperty(needsSync = true) public double terraformSpeed; @ConfigProperty public boolean terraformRequiresFluid; @@ -412,6 +416,16 @@ public static void loadPreInit() { arConfig.gasCollectionMult = config.get(MISSION, "gasMissionMultiplier", 1.0, "Multiplier for the amount of time gas collection missions take").getDouble(); harvestableGasses = config.getStringList("harvestableGasses", MISSION, new String[]{}, "list of fluid names that can be harvested as Gas from any gas giant"); spawnableGasses = config.getStringList("spawnableGasses", MISSION, new String[]{"hydrogen;125;1600;1.0", "helium;125;1600;0.9", "helium3;175;1600;0.2", "oxygen;0;124;1.0", "nitrogen;0;124;1.0", "ammonia;0;124;0.75", "methane;0;124;0.25"}, "list of fluid names that can be spawned as a gas giant. Format is fluid;minGravity;maxGravity;chance"); + arConfig.gasHarvestAmountMultiplier = config.get( + MISSION, "gasHarvestAmountMultiplier", 1.0, + "Per-mission harvest cap = 64,000 mB × multiplier. Actual yield = min(cap, total free tank space at launch). Ignored if gasHarvestInfinite=true." + ).getDouble(); + + arConfig.gasHarvestInfinite = config.get( + MISSION, "gasHarvestInfinite", false, + "Fill all attached tanks up to their total free space at launch, capped at 2,147,483,647 mB per mission." + ).getBoolean(); + //Energy Production arConfig.solarGeneratorMult = config.get(ENERGY, "solarGeneratorMultiplier", 1, "Amount of power per tick the solar generator should produce").getInt(); diff --git a/src/main/java/zmaster587/advancedRocketry/api/DataStorage.java b/src/main/java/zmaster587/advancedRocketry/api/DataStorage.java index 5cb5dc963..c99849801 100644 --- a/src/main/java/zmaster587/advancedRocketry/api/DataStorage.java +++ b/src/main/java/zmaster587/advancedRocketry/api/DataStorage.java @@ -20,16 +20,25 @@ public DataStorage(DataType data) { } public boolean setData(int data, DataType dataType) { - if (this.dataType == DataStorage.DataType.UNDEFINED) + // If empty/typeless, allow adopting the provided type (unless locked) + if (this.dataType == DataStorage.DataType.UNDEFINED && !this.locked) { this.dataType = dataType; + } if (dataType == DataStorage.DataType.UNDEFINED || dataType == this.dataType) { this.data = Math.min(data, maxData); + + // If we just became empty and are not locked, clear type + if (!this.locked && this.data == 0) { + this.dataType = DataType.UNDEFINED; + } return true; } return false; } + + public int getData() { return data; } @@ -78,20 +87,46 @@ public boolean isLocked() { * @param dataType type to add * @return data amount added */ - public int addData(int data, DataType dataType, boolean commit) { - if ((!this.locked && (dataType == DataStorage.DataType.UNDEFINED)) || dataType == this.dataType || this.dataType == DataStorage.DataType.UNDEFINED) { + public int addData(int data, DataType incomingType, boolean commit) { + // Snapshot + final boolean empty = (this.data == 0); + DataType current = this.dataType; + + // Compute effective type without mutating state on simulation + DataType effective = current; + if (!this.locked && empty) { + effective = (incomingType != DataType.UNDEFINED) ? incomingType : DataType.UNDEFINED; + } + + // Accept if: unlocked+UNDEFINED (wildcard), same type, or typeless + boolean accepts = + (!this.locked && incomingType == DataType.UNDEFINED) || + (incomingType == effective) || + (!this.locked && effective == DataType.UNDEFINED); + + + if (!accepts) return 0; - if (this.dataType == DataStorage.DataType.UNDEFINED) - this.dataType = dataType; + int amountToAdd = Math.min(data, this.maxData - this.data); + if (amountToAdd <= 0) return 0; - int amountToAdd = Math.min(data, this.maxData - this.data); - if (commit) - this.data += amountToAdd; - return amountToAdd; + if (commit) { + // Finalize adoption only on real add + if (!this.locked && this.data == 0) { + this.dataType = (incomingType != DataType.UNDEFINED) ? incomingType : DataType.UNDEFINED; + } + this.data += amountToAdd; + + // If still UNDEFINED but we added concrete data (edge case), solidify + if (this.dataType == DataType.UNDEFINED && incomingType != DataType.UNDEFINED) { + this.dataType = incomingType; + } } - return 0; + return amountToAdd; } + + /** * @param data max amount of data to remove * @return amount of data removed @@ -122,12 +157,15 @@ public void readFromNBT(NBTTagCompound nbt) { dataType = DataType.UNDEFINED; } - - ///TODO: dev compat if (nbt.hasKey("locked")) locked = nbt.getBoolean("locked"); else locked = false; + + // >>> ADD: heal stale type on empty <<< + if (!locked && data == 0) { + dataType = DataType.UNDEFINED; + } } public enum DataType { diff --git a/src/main/java/zmaster587/advancedRocketry/api/RocketEvent.java b/src/main/java/zmaster587/advancedRocketry/api/RocketEvent.java index e4c3644fc..a93b79b26 100644 --- a/src/main/java/zmaster587/advancedRocketry/api/RocketEvent.java +++ b/src/main/java/zmaster587/advancedRocketry/api/RocketEvent.java @@ -35,6 +35,13 @@ public RocketPreLaunchEvent(Entity entity) { super(entity); } } + public static class RocketAbortEvent extends RocketEvent { + public final String reason; // optional, for GUIs/logs + public RocketAbortEvent(Entity entity, String reason) { + super(entity); + this.reason = reason; + } + } /** * Fired before the rocket is finished teleporting to the destination world on the Minecraft Forge EVENT_BUS diff --git a/src/main/java/zmaster587/advancedRocketry/api/StatsRocket.java b/src/main/java/zmaster587/advancedRocketry/api/StatsRocket.java index 427f01ee4..eb292e2b0 100644 --- a/src/main/java/zmaster587/advancedRocketry/api/StatsRocket.java +++ b/src/main/java/zmaster587/advancedRocketry/api/StatsRocket.java @@ -145,6 +145,10 @@ public float getWeight() { Fluid f = FluidRegistry.getFluid(getOxidizerFluid()); fluidWeight += WeightEngine.INSTANCE.getWeight(f, getFuelAmount(FuelType.LIQUID_OXIDIZER)); } + if (FluidRegistry.isFluidRegistered(getWorkingFluid())) { + Fluid f = FluidRegistry.getFluid(getWorkingFluid()); + fluidWeight += WeightEngine.INSTANCE.getWeight(f, getFuelAmount(FuelType.NUCLEAR_WORKING_FLUID)); + } } return weight + fluidWeight; } @@ -554,6 +558,7 @@ public void reset() { weight = 0; fuelFluid = "null"; oxidizerFluid = "null"; + workingFluid = "null"; drillingPower = 0f; for (FuelType type : FuelType.values()) { @@ -720,12 +725,12 @@ public void readFromNBT(NBTTagCompound nbt) { this.fuelRateNuclearWorkingFluid = stats.getInteger("fuelRateNuclearWorkingFluid"); this.fuelRateWarp = stats.getInteger("fuelRateWarp"); - this.fuelBaseRateMonopropellant = stats.getInteger("fuelBaseRateMonopropellant"); - this.fuelBaseRateBipropellant = stats.getInteger("fuelBaseRateBipropellant"); - this.fuelBaseRateOxidizer = stats.getInteger("fuelBaseRateOxidizer"); + this.fuelBaseRateMonopropellant = (int)stats.getFloat("fuelBaseRateMonopropellant"); + this.fuelBaseRateBipropellant = (int)stats.getFloat("fuelBaseRateBipropellant"); + this.fuelBaseRateOxidizer = (int)stats.getFloat("fuelBaseRateOxidizer"); this.fuelBaseRateImpulse = stats.getInteger("fuelBaseRateImpulse"); this.fuelBaseRateIon = stats.getInteger("fuelBaseRateIon"); - this.fuelBaseRateNuclearWorkingFluid = stats.getInteger("fuelBaseRateNuclearWorkingFluid"); + this.fuelBaseRateNuclearWorkingFluid = (int)stats.getFloat("fuelBaseRateNuclearWorkingFluid"); this.fuelBaseRateWarp = stats.getInteger("fuelBaseRateWarp"); @@ -766,4 +771,4 @@ else if (obj instanceof NBTTagInt) } } } -} \ No newline at end of file +} diff --git a/src/main/java/zmaster587/advancedRocketry/block/BlockBipropellantRocketMotor.java b/src/main/java/zmaster587/advancedRocketry/block/BlockBipropellantRocketMotor.java index 74d3d4a6b..e9342d566 100644 --- a/src/main/java/zmaster587/advancedRocketry/block/BlockBipropellantRocketMotor.java +++ b/src/main/java/zmaster587/advancedRocketry/block/BlockBipropellantRocketMotor.java @@ -2,6 +2,9 @@ import net.minecraft.block.material.Material; import net.minecraft.block.state.IBlockState; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.resources.I18n; +import net.minecraft.client.util.ITooltipFlag; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.item.EntityItem; import net.minecraft.entity.player.EntityPlayer; @@ -11,14 +14,20 @@ import net.minecraft.util.EnumFacing; import net.minecraft.util.NonNullList; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.text.TextFormatting; import net.minecraft.world.IBlockAccess; import net.minecraft.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; import zmaster587.advancedRocketry.api.ARConfiguration; import zmaster587.advancedRocketry.api.IRocketEngine; +import zmaster587.advancedRocketry.client.TooltipInjector; import zmaster587.advancedRocketry.tile.TileBrokenPart; import zmaster587.advancedRocketry.util.IBrokenPartBlock; import zmaster587.libVulpes.block.BlockFullyRotatable; +import java.util.List; + import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -39,6 +48,23 @@ public int getThrust(World world, BlockPos pos) { return 10; } + @Override + public IBlockState getActualState(@Nonnull IBlockState state, IBlockAccess world, BlockPos pos) { + if (world.getBlockState(pos.up()).getBlock() instanceof BlockFuelTank) + return state.withProperty(FACING, EnumFacing.DOWN); + if (world.getBlockState(pos.down()).getBlock() instanceof BlockFuelTank) + return state.withProperty(FACING, EnumFacing.UP); + if (world.getBlockState(pos.east()).getBlock() instanceof BlockFuelTank) + return state.withProperty(FACING, EnumFacing.EAST); + if (world.getBlockState(pos.west()).getBlock() instanceof BlockFuelTank) + return state.withProperty(FACING, EnumFacing.WEST); + if (world.getBlockState(pos.south()).getBlock() instanceof BlockFuelTank) + return state.withProperty(FACING, EnumFacing.SOUTH); + if (world.getBlockState(pos.north()).getBlock() instanceof BlockFuelTank) + return state.withProperty(FACING, EnumFacing.NORTH); + return super.getActualState(state, world, pos); + } + @Override public int getFuelConsumptionRate(World world, int x, int y, int z) { return 1; @@ -102,6 +128,13 @@ public TileEntity createTileEntity(final World worldIn, final IBlockState state) return new TileBrokenPart(10, (float) ARConfiguration.getCurrentConfig().increaseWearIntensityProb); } + @SideOnly(Side.CLIENT) + @Override + public void addInformation(ItemStack stack, @Nullable World world, List tooltip, ITooltipFlag flag) { + int insertAt = TooltipInjector.computeInsertIndex(tooltip, flag.isAdvanced()); + TooltipInjector.renderShiftAlt(stack, tooltip, "tooltip.advancedrocketry.bipropmotor", insertAt); + } + @Override public ItemStack getDropItem(final IBlockState state, final World world, final @Nullable TileBrokenPart te) { ItemStack drop = new ItemStack(this.getItemDropped(state, world.rand, 0)); diff --git a/src/main/java/zmaster587/advancedRocketry/block/BlockDoor2.java b/src/main/java/zmaster587/advancedRocketry/block/BlockDoor2.java index e6012eb75..cd569c4cf 100644 --- a/src/main/java/zmaster587/advancedRocketry/block/BlockDoor2.java +++ b/src/main/java/zmaster587/advancedRocketry/block/BlockDoor2.java @@ -3,6 +3,7 @@ import net.minecraft.block.BlockDoor; import net.minecraft.block.material.Material; import net.minecraft.block.state.IBlockState; +import net.minecraft.client.util.ITooltipFlag; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.Items; import net.minecraft.item.Item; @@ -14,9 +15,13 @@ import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; import zmaster587.advancedRocketry.api.AdvancedRocketryItems; +import zmaster587.advancedRocketry.client.TooltipInjector; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import javax.annotation.ParametersAreNullableByDefault; + +import java.util.List; import java.util.Random; public class BlockDoor2 extends BlockDoor { @@ -32,7 +37,6 @@ public ItemStack getItem(World worldIn, BlockPos pos, IBlockState state) { return new ItemStack(AdvancedRocketryItems.itemSmallAirlockDoor); } - @Override @Nonnull public Item getItemDropped(IBlockState state, Random rand, int fortune) { @@ -45,5 +49,5 @@ public boolean onBlockActivated(World worldIn, BlockPos pos, IBlockState state, EntityPlayer playerIn, EnumHand hand, EnumFacing side, float hitX, float hitY, float hitZ) { return false; - } + } } diff --git a/src/main/java/zmaster587/advancedRocketry/block/BlockForceFieldProjector.java b/src/main/java/zmaster587/advancedRocketry/block/BlockForceFieldProjector.java index 59958642e..19405aa88 100644 --- a/src/main/java/zmaster587/advancedRocketry/block/BlockForceFieldProjector.java +++ b/src/main/java/zmaster587/advancedRocketry/block/BlockForceFieldProjector.java @@ -1,10 +1,18 @@ package zmaster587.advancedRocketry.block; +import java.util.List; +import javax.annotation.Nullable; + import net.minecraft.block.material.Material; import net.minecraft.block.state.IBlockState; +import net.minecraft.client.util.ITooltipFlag; +import net.minecraft.item.ItemStack; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import zmaster587.advancedRocketry.client.TooltipInjector; import zmaster587.advancedRocketry.tile.TileForceFieldProjector; import zmaster587.libVulpes.block.BlockFullyRotatable; @@ -33,5 +41,10 @@ public void breakBlock(World worldIn, BlockPos pos, IBlockState state) { public TileEntity createTileEntity(World world, IBlockState state) { return new TileForceFieldProjector(); } - + @SideOnly(Side.CLIENT) + @Override + public void addInformation(ItemStack stack, @Nullable World world, List tooltip, ITooltipFlag flag) { + int insertAt = TooltipInjector.computeInsertIndex(tooltip, flag.isAdvanced()); + TooltipInjector.renderShiftAlt(stack, tooltip, "tooltip.advancedrocketry.forcefieldprojector", insertAt); + } } diff --git a/src/main/java/zmaster587/advancedRocketry/block/BlockIntake.java b/src/main/java/zmaster587/advancedRocketry/block/BlockIntake.java index 706f2f883..af0b222e0 100644 --- a/src/main/java/zmaster587/advancedRocketry/block/BlockIntake.java +++ b/src/main/java/zmaster587/advancedRocketry/block/BlockIntake.java @@ -1,19 +1,35 @@ package zmaster587.advancedRocketry.block; +import java.util.List; + +import javax.annotation.Nullable; + import net.minecraft.block.Block; import net.minecraft.block.material.Material; import net.minecraft.block.state.IBlockState; +import net.minecraft.client.util.ITooltipFlag; +import net.minecraft.item.ItemStack; +import net.minecraft.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; import zmaster587.advancedRocketry.api.IIntake; +import zmaster587.advancedRocketry.client.TooltipInjector; public class BlockIntake extends Block implements IIntake { public BlockIntake(Material material) { super(material); } + + @SideOnly(Side.CLIENT) + @Override + public void addInformation(ItemStack stack, @Nullable World world, List tooltip, ITooltipFlag flag) { + int insertAt = TooltipInjector.computeInsertIndex(tooltip, flag.isAdvanced()); + TooltipInjector.renderShiftAlt(stack, tooltip, "tooltip.advancedrocketry.intake", insertAt); + } @Override public int getIntakeAmt(IBlockState state) { - return 10; + return 1; } - } diff --git a/src/main/java/zmaster587/advancedRocketry/block/BlockLandingPad.java b/src/main/java/zmaster587/advancedRocketry/block/BlockLandingPad.java index 4eb73f072..d559f1835 100644 --- a/src/main/java/zmaster587/advancedRocketry/block/BlockLandingPad.java +++ b/src/main/java/zmaster587/advancedRocketry/block/BlockLandingPad.java @@ -1,14 +1,26 @@ package zmaster587.advancedRocketry.block; +import java.util.List; + +import javax.annotation.Nullable; + import net.minecraft.block.Block; import net.minecraft.block.material.Material; import net.minecraft.block.state.IBlockState; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.resources.I18n; +import net.minecraft.client.util.ITooltipFlag; import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumHand; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.text.TextFormatting; import net.minecraft.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import zmaster587.advancedRocketry.client.TooltipInjector; import zmaster587.advancedRocketry.tile.station.TileLandingPad; import zmaster587.libVulpes.LibVulpes; import zmaster587.libVulpes.inventory.GuiHandler; @@ -48,6 +60,13 @@ public boolean onBlockActivated(World world, BlockPos pos, return true; } + @SideOnly(Side.CLIENT) + @Override + public void addInformation(ItemStack stack, @Nullable World world, List tooltip, ITooltipFlag flag) { + int insertAt = TooltipInjector.computeInsertIndex(tooltip, flag.isAdvanced()); + TooltipInjector.renderShiftAlt(stack, tooltip, "tooltip.advancedrocketry.landingpad", insertAt); + } + @Override public void breakBlock(World world, BlockPos pos, IBlockState state) { TileEntity tile = world.getTileEntity(pos); diff --git a/src/main/java/zmaster587/advancedRocketry/block/BlockLens.java b/src/main/java/zmaster587/advancedRocketry/block/BlockLens.java index 6807ad386..4e71391ad 100644 --- a/src/main/java/zmaster587/advancedRocketry/block/BlockLens.java +++ b/src/main/java/zmaster587/advancedRocketry/block/BlockLens.java @@ -1,8 +1,18 @@ package zmaster587.advancedRocketry.block; +import java.util.List; + +import javax.annotation.Nullable; + import net.minecraft.block.BlockGlass; import net.minecraft.block.SoundType; import net.minecraft.block.material.Material; +import net.minecraft.client.util.ITooltipFlag; +import net.minecraft.item.ItemStack; +import net.minecraft.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import zmaster587.advancedRocketry.client.TooltipInjector; /** * Yes, this class may seem useless, but setSoundType can't be run in the registry, only by a subclass of Block. @@ -12,4 +22,11 @@ public BlockLens() { super(Material.GLASS, true); setSoundType(SoundType.GLASS); } + + @SideOnly(Side.CLIENT) + @Override + public void addInformation(ItemStack stack, @Nullable World world, List tooltip, ITooltipFlag flag) { + int insertAt = TooltipInjector.computeInsertIndex(tooltip, flag.isAdvanced()); + TooltipInjector.renderShiftAlt(stack, tooltip, "tooltip.advancedrocketry.lens", insertAt); + } } diff --git a/src/main/java/zmaster587/advancedRocketry/block/BlockLinkedHorizontalTexture.java b/src/main/java/zmaster587/advancedRocketry/block/BlockLinkedHorizontalTexture.java index 7e9153b17..aee2caf69 100644 --- a/src/main/java/zmaster587/advancedRocketry/block/BlockLinkedHorizontalTexture.java +++ b/src/main/java/zmaster587/advancedRocketry/block/BlockLinkedHorizontalTexture.java @@ -1,13 +1,25 @@ package zmaster587.advancedRocketry.block; +import java.util.List; + +import javax.annotation.Nullable; + import net.minecraft.block.Block; import net.minecraft.block.material.Material; import net.minecraft.block.properties.PropertyEnum; import net.minecraft.block.state.BlockStateContainer; import net.minecraft.block.state.IBlockState; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.resources.I18n; +import net.minecraft.client.util.ITooltipFlag; +import net.minecraft.item.ItemStack; import net.minecraft.util.IStringSerializable; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.text.TextFormatting; import net.minecraft.world.IBlockAccess; +import net.minecraft.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; public class BlockLinkedHorizontalTexture extends Block { @@ -84,4 +96,43 @@ public String getName() { return suffix; } } -} \ No newline at end of file + + @SideOnly(Side.CLIENT) + @Override + public void addInformation(ItemStack stack, @Nullable World world, List tooltip, ITooltipFlag flag) { + tooltip.add(TextFormatting.GRAY + I18n.format("tooltip.advancedrocketry.launchpad")); + + final boolean shift = GuiScreen.isShiftKeyDown(); + final boolean alt = isAltDown(); + + if (alt) { + // Advanced details + tooltip.add(TextFormatting.DARK_GRAY + I18n.format("tooltip.advancedrocketry.launchpad.alt.1")); + tooltip.add(TextFormatting.DARK_GRAY + I18n.format("tooltip.advancedrocketry.launchpad.alt.2")); + } else if (shift) { + // More info + tooltip.add(TextFormatting.GRAY + I18n.format("tooltip.advancedrocketry.launchpad.shift.1")); + tooltip.add(TextFormatting.GRAY + I18n.format("tooltip.advancedrocketry.launchpad.shift.2")); + if (I18n.hasKey("tooltip.advancedrocketry.hold_alt")) + tooltip.add(TextFormatting.DARK_GRAY.toString() + TextFormatting.ITALIC + I18n.format("tooltip.advancedrocketry.hold_alt")); + } else { + // Hints + if (I18n.hasKey("tooltip.advancedrocketry.hold_shift")) + tooltip.add(TextFormatting.DARK_GRAY.toString() + TextFormatting.ITALIC + I18n.format("tooltip.advancedrocketry.hold_shift")); + if (I18n.hasKey("tooltip.advancedrocketry.hold_alt")) + tooltip.add(TextFormatting.DARK_GRAY.toString() + TextFormatting.ITALIC + I18n.format("tooltip.advancedrocketry.hold_alt")); + } + } + + @SideOnly(Side.CLIENT) + private static boolean isAltDown() { + try { + // Works on Forge 1.12.x; LWJGL fallback for safety + return GuiScreen.isAltKeyDown() + || org.lwjgl.input.Keyboard.isKeyDown(org.lwjgl.input.Keyboard.KEY_LMENU) + || org.lwjgl.input.Keyboard.isKeyDown(org.lwjgl.input.Keyboard.KEY_RMENU); + } catch (Throwable t) { + return false; + } + } +} diff --git a/src/main/java/zmaster587/advancedRocketry/block/BlockMiningDrill.java b/src/main/java/zmaster587/advancedRocketry/block/BlockMiningDrill.java index 432bf6dac..13b4fc1db 100644 --- a/src/main/java/zmaster587/advancedRocketry/block/BlockMiningDrill.java +++ b/src/main/java/zmaster587/advancedRocketry/block/BlockMiningDrill.java @@ -1,10 +1,19 @@ package zmaster587.advancedRocketry.block; +import java.util.List; + +import javax.annotation.Nullable; + import net.minecraft.block.material.Material; import net.minecraft.block.state.IBlockState; +import net.minecraft.client.util.ITooltipFlag; +import net.minecraft.item.ItemStack; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; import zmaster587.advancedRocketry.api.IMiningDrill; +import zmaster587.advancedRocketry.client.TooltipInjector; import zmaster587.libVulpes.block.BlockFullyRotatable; public class BlockMiningDrill extends BlockFullyRotatable implements IMiningDrill { @@ -24,9 +33,15 @@ public float getMiningSpeed(World world, BlockPos pos) { return world.isAirBlock(pos.add(0, 1, 0)) && world.isAirBlock(pos.add(0, 2, 0)) ? 0.02f : 0.01f; } + @SideOnly(Side.CLIENT) + @Override + public void addInformation(ItemStack stack, @Nullable World world, List tooltip, ITooltipFlag flag) { + int insertAt = TooltipInjector.computeInsertIndex(tooltip, flag.isAdvanced()); + TooltipInjector.renderShiftAlt(stack, tooltip, "tooltip.advancedrocketry.drill", insertAt); + } + @Override public int powerConsumption() { return 0; } - } diff --git a/src/main/java/zmaster587/advancedRocketry/block/BlockNuclearCore.java b/src/main/java/zmaster587/advancedRocketry/block/BlockNuclearCore.java index 7cdc3d25b..1664ad679 100644 --- a/src/main/java/zmaster587/advancedRocketry/block/BlockNuclearCore.java +++ b/src/main/java/zmaster587/advancedRocketry/block/BlockNuclearCore.java @@ -2,10 +2,21 @@ import net.minecraft.block.Block; import net.minecraft.block.material.Material; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.resources.I18n; +import net.minecraft.client.util.ITooltipFlag; +import net.minecraft.item.ItemStack; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.text.TextFormatting; import net.minecraft.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; import zmaster587.advancedRocketry.api.ARConfiguration; import zmaster587.advancedRocketry.api.IRocketNuclearCore; +import zmaster587.advancedRocketry.client.TooltipInjector; + +import javax.annotation.Nullable; +import java.util.List; public class BlockNuclearCore extends Block implements IRocketNuclearCore { @@ -18,5 +29,10 @@ public int getMaxThrust(World world, BlockPos pos) { return (int) (1000 * ARConfiguration.getCurrentConfig().nuclearCoreThrustRatio); } - + @SideOnly(Side.CLIENT) + @Override + public void addInformation(ItemStack stack, @Nullable World world, List tooltip, ITooltipFlag flag) { + int insertAt = TooltipInjector.computeInsertIndex(tooltip, flag.isAdvanced()); + TooltipInjector.renderShiftAlt(stack, tooltip, "tooltip.advancedrocketry.nuclearcore", insertAt); + } } diff --git a/src/main/java/zmaster587/advancedRocketry/block/BlockNuclearRocketMotor.java b/src/main/java/zmaster587/advancedRocketry/block/BlockNuclearRocketMotor.java index ed84d34d5..604f1321b 100644 --- a/src/main/java/zmaster587/advancedRocketry/block/BlockNuclearRocketMotor.java +++ b/src/main/java/zmaster587/advancedRocketry/block/BlockNuclearRocketMotor.java @@ -2,12 +2,26 @@ import net.minecraft.block.material.Material; import net.minecraft.block.state.IBlockState; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.resources.I18n; +import net.minecraft.client.util.ITooltipFlag; +import net.minecraft.item.ItemStack; import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.EnumFacing; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.text.TextFormatting; +import net.minecraft.world.IBlockAccess; import net.minecraft.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; import zmaster587.advancedRocketry.api.ARConfiguration; +import zmaster587.advancedRocketry.api.IRocketNuclearCore; +import zmaster587.advancedRocketry.client.TooltipInjector; import zmaster587.advancedRocketry.tile.TileBrokenPart; +import java.util.List; + +import javax.annotation.Nonnull; import javax.annotation.Nullable; public class BlockNuclearRocketMotor extends BlockRocketMotor { @@ -20,7 +34,23 @@ public BlockNuclearRocketMotor(Material mat) { public int getThrust(World world, BlockPos pos) { return 35; } - + @Override + public IBlockState getActualState(@Nonnull IBlockState state, IBlockAccess world, BlockPos pos) { + // Prefer nuclear core adjacency over tanks + if (world.getBlockState(pos.up()).getBlock() instanceof IRocketNuclearCore) + return state.withProperty(FACING, EnumFacing.DOWN); + if (world.getBlockState(pos.down()).getBlock() instanceof IRocketNuclearCore) + return state.withProperty(FACING, EnumFacing.UP); + if (world.getBlockState(pos.east()).getBlock() instanceof IRocketNuclearCore) + return state.withProperty(FACING, EnumFacing.EAST); + if (world.getBlockState(pos.west()).getBlock() instanceof IRocketNuclearCore) + return state.withProperty(FACING, EnumFacing.WEST); + if (world.getBlockState(pos.south()).getBlock() instanceof IRocketNuclearCore) + return state.withProperty(FACING, EnumFacing.SOUTH); + if (world.getBlockState(pos.north()).getBlock() instanceof IRocketNuclearCore) + return state.withProperty(FACING, EnumFacing.NORTH); + return state; + } @Override public int getFuelConsumptionRate(World world, int x, int y, int z) { return 1; @@ -31,4 +61,11 @@ public int getFuelConsumptionRate(World world, int x, int y, int z) { public TileEntity createTileEntity(final World worldIn, final IBlockState state) { return new TileBrokenPart(10, 4 * (float) ARConfiguration.getCurrentConfig().increaseWearIntensityProb); } + + @SideOnly(Side.CLIENT) + @Override + public void addInformation(ItemStack stack, @Nullable World world, List tooltip, ITooltipFlag flag) { + int insertAt = TooltipInjector.computeInsertIndex(tooltip, flag.isAdvanced()); + TooltipInjector.renderShiftAlt(stack, tooltip, "tooltip.advancedrocketry.nuclearmotor", insertAt); + } } diff --git a/src/main/java/zmaster587/advancedRocketry/block/BlockPressurizedFluidTank.java b/src/main/java/zmaster587/advancedRocketry/block/BlockPressurizedFluidTank.java index cd6dffcfa..53cc745a2 100644 --- a/src/main/java/zmaster587/advancedRocketry/block/BlockPressurizedFluidTank.java +++ b/src/main/java/zmaster587/advancedRocketry/block/BlockPressurizedFluidTank.java @@ -15,7 +15,6 @@ import net.minecraft.world.IBlockAccess; import net.minecraft.world.World; import net.minecraftforge.fluids.FluidUtil; -import net.minecraftforge.fluids.capability.CapabilityFluidHandler; import net.minecraftforge.fluids.capability.IFluidHandler; import zmaster587.advancedRocketry.api.ARConfiguration; import zmaster587.advancedRocketry.api.AdvancedRocketryBlocks; @@ -48,66 +47,136 @@ public boolean hasTileEntity(IBlockState state) { } @Override - public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumHand hand, EnumFacing side, float hitX, float hitY, float hitZ) { - TileEntity tile = world.getTileEntity(pos); - - //Do some fancy fluid stuff - if (FluidUtils.containsFluid(player.getHeldItem(hand))) { - FluidUtil.interactWithFluidHandler(player, hand, ((TileFluidHatch) tile).getFluidTank()); - } else if (!world.isRemote) - player.openGui(LibVulpes.instance, guiId.MODULAR.ordinal(), world, pos.getX(), pos.getY(), pos.getZ()); + public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumHand hand, + EnumFacing side, float hitX, float hitY, float hitZ) { + TileEntity te = world.getTileEntity(pos); + if (!(te instanceof TileFluidTank)) return false; + + // Client: consume the click (let server do the actual transfer) + if (world.isRemote) return true; + + // Try to interact via the tile's FLUID CAPABILITY (column-aware path), + // NOT the raw internal tank. + IFluidHandler handler = te.getCapability( + net.minecraftforge.fluids.capability.CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, + side + ); + if (handler == null) { + handler = te.getCapability( + net.minecraftforge.fluids.capability.CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, + null + ); + } + + boolean acted = false; + if (handler != null) { + // Server-side inventory mutation + acted = net.minecraftforge.fluids.FluidUtil.interactWithFluidHandler(player, hand, handler); + if (acted) { + TileFluidTank tank = (TileFluidTank) te; + // Persist + sync + tank.markDirty(); + tank.onAdjacentBlockUpdated(EnumFacing.DOWN); + tank.onAdjacentBlockUpdated(EnumFacing.UP); + } + } + + // If we didn't perform a fluid interaction, open the GUI + if (!acted) { + player.openGui(zmaster587.libVulpes.LibVulpes.instance, + zmaster587.libVulpes.inventory.GuiHandler.guiId.MODULAR.ordinal(), + world, pos.getX(), pos.getY(), pos.getZ()); + } return true; } + + + @Override + public void onBlockAdded(World world, BlockPos pos, IBlockState state) { + super.onBlockAdded(world, pos, state); + if (world.isRemote) return; + TileEntity teAbove = world.getTileEntity(pos.up()); + if (teAbove instanceof TileFluidTank) { + ((TileFluidTank) teAbove).onAdjacentBlockUpdated(EnumFacing.DOWN); + } + } + + @Override @ParametersAreNullableByDefault public TileEntity createTileEntity(World world, IBlockState state) { - return new TileFluidTank((int) (64000 * ARConfiguration.getCurrentConfig().blockTankCapacity)); + long computed = Math.round(64000d * ARConfiguration.getCurrentConfig().blockTankCapacity); + int capMb = (int) Math.min(Integer.MAX_VALUE, Math.max(0L, computed)); + return new TileFluidTank(capMb); } @Override @Nonnull @ParametersAreNullableByDefault - public List getDrops(IBlockAccess world, BlockPos pos, - IBlockState state, int fortune) { - return new LinkedList<>(); - } + public List getDrops(IBlockAccess world, BlockPos pos, IBlockState state, int fortune) { + List drops = new LinkedList<>(); + TileEntity te = world.getTileEntity(pos); - @Override - @ParametersAreNonnullByDefault - public void harvestBlock(World world, EntityPlayer player, BlockPos pos, IBlockState state, @Nullable TileEntity te, @Nonnull ItemStack stack) { + ItemStack out = new ItemStack(AdvancedRocketryBlocks.blockPressureTank); if (te instanceof TileFluidTank) { - IFluidHandler fluid = te.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, EnumFacing.DOWN); - + net.minecraftforge.fluids.FluidStack own = ((TileFluidTank) te).getOwnContentsCopy(); + if (own != null && own.amount > 0) { + ((ItemBlockFluidTank) out.getItem()).fill(out, own); + } + } - ItemStack itemstack = new ItemStack(AdvancedRocketryBlocks.blockPressureTank); + drops.add(out); + return drops; + } - ((ItemBlockFluidTank) itemstack.getItem()).fill(itemstack, fluid.drain(Integer.MAX_VALUE, false)); - EntityItem entityitem; + @Override + public boolean removedByPlayer(IBlockState state, World world, BlockPos pos, EntityPlayer player, boolean willHarvest) { + if (!world.isRemote) { + TileEntity te = world.getTileEntity(pos); + if (te instanceof TileFluidTank) { + ((TileFluidTank) te).setRemoving(true); + } + } + // Let vanilla handle removal; we’ll control drops in harvestBlock + return super.removedByPlayer(state, world, pos, player, willHarvest); + } - int j1 = world.rand.nextInt(21) + 10; - float f = world.rand.nextFloat() * 0.8F + 0.1F; - float f1 = world.rand.nextFloat() * 0.8F + 0.1F; - float f2 = world.rand.nextFloat() * 0.8F + 0.1F; + @Override + public void harvestBlock(World world, EntityPlayer player, BlockPos pos, IBlockState state, + @Nullable TileEntity te, @Nonnull ItemStack tool) { + if (world.isRemote) return; + + // Creative: no drop, just remove + if (player.capabilities.isCreativeMode) { + world.setBlockToAir(pos); + return; + } - itemstack.setCount(1); - entityitem = new EntityItem(world, (float) pos.getX() + f, (float) pos.getY() + f1, (float) pos.getZ() + f2, new ItemStack(itemstack.getItem(), 1, 0)); - float f3 = 0.05F; - entityitem.motionX = (float) world.rand.nextGaussian() * f3; - entityitem.motionY = (float) world.rand.nextGaussian() * f3 + 0.2F; - entityitem.motionZ = (float) world.rand.nextGaussian() * f3; + // Build ONE drop item from the authoritative server tile we received + ItemStack drop = new ItemStack(AdvancedRocketryBlocks.blockPressureTank); + if (te instanceof TileFluidTank) { + // Make sure the tile knows it's in teardown; block cross-tile moves + ((TileFluidTank) te).setRemoving(true); - if (itemstack.hasTagCompound()) { - entityitem.getItem().setTagCompound(itemstack.getTagCompound().copy()); + net.minecraftforge.fluids.FluidStack own = ((TileFluidTank) te).getOwnContentsCopy(); + if (own != null && own.amount > 0) { + ((ItemBlockFluidTank) drop.getItem()).fill(drop, own); } - world.spawnEntity(entityitem); } - super.harvestBlock(world, player, pos, state, te, stack); + EntityItem ei = new EntityItem(world, + pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, drop); + world.spawnEntity(ei); + + world.setBlockToAir(pos); } + + + @Override @ParametersAreNonnullByDefault public boolean shouldSideBeRendered(IBlockState blockState, @@ -133,12 +202,39 @@ public boolean isFullCube(IBlockState state) { return false; } + private void notifyTankOfNeighborChange(World world, BlockPos pos, BlockPos fromPos) { + // Only act for strictly adjacent vertical neighbors (no diagonals, no sides) + int dx = fromPos.getX() - pos.getX(); + int dy = fromPos.getY() - pos.getY(); + int dz = fromPos.getZ() - pos.getZ(); + + // Must be exactly one block away on Y, and same X/Z + if (dx != 0 || dz != 0) return; + if (dy != 1 && dy != -1) return; + + TileEntity te = world.getTileEntity(pos); + if (!(te instanceof zmaster587.advancedRocketry.tile.TileFluidTank)) return; + + EnumFacing dir = (dy == 1) ? EnumFacing.UP : EnumFacing.DOWN; + ((zmaster587.advancedRocketry.tile.TileFluidTank) te).onAdjacentBlockUpdated(dir); + } + + + + // Reliable for block state changes (place/break) + @Override + public void neighborChanged(IBlockState state, World world, BlockPos pos, Block blockIn, BlockPos fromPos) { + super.neighborChanged(state, world, pos, blockIn, fromPos); + if (!world.isRemote) notifyTankOfNeighborChange(world, pos, fromPos); + } + + // TE-only neighbor updates (no block state change) @Override - public void onNeighborChange(IBlockAccess world, BlockPos pos, - BlockPos neighbor) { - TileEntity tile = world.getTileEntity(pos); - if (tile instanceof TileFluidTank) - ((TileFluidTank) tile).onAdjacentBlockUpdated(EnumFacing.getFacingFromVector(neighbor.getX() - pos.getX(), neighbor.getY() - pos.getY(), neighbor.getZ() - pos.getZ())); + public void onNeighborChange(IBlockAccess world, BlockPos pos, BlockPos neighbor) { + if (world instanceof World) { + World w = (World) world; + if (!w.isRemote) notifyTankOfNeighborChange(w, pos, neighbor); + } } @Override diff --git a/src/main/java/zmaster587/advancedRocketry/block/BlockPump.java b/src/main/java/zmaster587/advancedRocketry/block/BlockPump.java new file mode 100644 index 000000000..94a888dc9 --- /dev/null +++ b/src/main/java/zmaster587/advancedRocketry/block/BlockPump.java @@ -0,0 +1,28 @@ +package zmaster587.advancedRocketry.block; + +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraftforge.fluids.FluidUtil; +import zmaster587.libVulpes.block.BlockTile; + +public class BlockPump extends BlockTile { + + public BlockPump(Class tileClass, int guiId) { + super(tileClass, guiId); + } + + @Override + public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, + EntityPlayer player, EnumHand hand, + EnumFacing side, float hitX, float hitY, float hitZ) { + // Try container <-> TE interaction first (handles buckets both directions). + if (FluidUtil.interactWithFluidHandler(player, hand, world, pos, side)) { + return true; + } + return super.onBlockActivated(world, pos, state, player, hand, side, hitX, hitY, hitZ); + } +} diff --git a/src/main/java/zmaster587/advancedRocketry/block/BlockRedstoneEmitter.java b/src/main/java/zmaster587/advancedRocketry/block/BlockRedstoneEmitter.java index 99720b44b..dc4a78830 100644 --- a/src/main/java/zmaster587/advancedRocketry/block/BlockRedstoneEmitter.java +++ b/src/main/java/zmaster587/advancedRocketry/block/BlockRedstoneEmitter.java @@ -5,20 +5,28 @@ import net.minecraft.block.properties.PropertyBool; import net.minecraft.block.state.BlockStateContainer; import net.minecraft.block.state.IBlockState; +import net.minecraft.client.util.ITooltipFlag; import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumHand; import net.minecraft.util.math.BlockPos; import net.minecraft.world.IBlockAccess; import net.minecraft.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import zmaster587.advancedRocketry.client.TooltipInjector; import zmaster587.advancedRocketry.tile.atmosphere.TileAtmosphereDetector; import zmaster587.libVulpes.LibVulpes; import zmaster587.libVulpes.inventory.GuiHandler; +import java.util.List; + import javax.annotation.Nonnull; +import javax.annotation.Nullable; import javax.annotation.ParametersAreNullableByDefault; - +// Atmosphere Detector that emits redstone signal when a specific atmosphere is detected public class BlockRedstoneEmitter extends Block { public static final PropertyBool POWERED = PropertyBool.create("powered"); @@ -86,9 +94,15 @@ public int getWeakPower(IBlockState blockState, IBlockAccess blockAccess, return blockState.getValue(POWERED) ? 15 : 0; } + @SideOnly(Side.CLIENT) + @Override + public void addInformation(ItemStack stack, @Nullable World world, List tooltip, ITooltipFlag flag) { + int insertAt = TooltipInjector.computeInsertIndex(tooltip, flag.isAdvanced()); + TooltipInjector.renderShiftAlt(stack, tooltip, "tooltip.advancedrocketry.atmosphereDetector", insertAt); + } + @Override public boolean canProvidePower(IBlockState state) { return true; } - } diff --git a/src/main/java/zmaster587/advancedRocketry/block/BlockRocketMotor.java b/src/main/java/zmaster587/advancedRocketry/block/BlockRocketMotor.java index 088ac702e..cba1024bc 100644 --- a/src/main/java/zmaster587/advancedRocketry/block/BlockRocketMotor.java +++ b/src/main/java/zmaster587/advancedRocketry/block/BlockRocketMotor.java @@ -2,6 +2,9 @@ import net.minecraft.block.material.Material; import net.minecraft.block.state.IBlockState; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.resources.I18n; +import net.minecraft.client.util.ITooltipFlag; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.item.EntityItem; import net.minecraft.entity.player.EntityPlayer; @@ -11,14 +14,20 @@ import net.minecraft.util.EnumFacing; import net.minecraft.util.NonNullList; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.text.TextFormatting; import net.minecraft.world.IBlockAccess; import net.minecraft.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; import zmaster587.advancedRocketry.api.ARConfiguration; import zmaster587.advancedRocketry.api.IRocketEngine; +import zmaster587.advancedRocketry.client.TooltipInjector; import zmaster587.advancedRocketry.tile.TileBrokenPart; import zmaster587.advancedRocketry.util.IBrokenPartBlock; import zmaster587.libVulpes.block.BlockFullyRotatable; +import java.util.List; + import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -137,4 +146,11 @@ public ItemStack getDropItem(final IBlockState state, final World world, final @ } return drop; } + + @SideOnly(Side.CLIENT) + @Override + public void addInformation(ItemStack stack, @Nullable World world, List tooltip, ITooltipFlag flag) { + int insertAt = TooltipInjector.computeInsertIndex(tooltip, flag.isAdvanced()); + TooltipInjector.renderShiftAlt(stack, tooltip, "tooltip.advancedrocketry.monopropmotor", insertAt); + } } diff --git a/src/main/java/zmaster587/advancedRocketry/block/BlockSeal.java b/src/main/java/zmaster587/advancedRocketry/block/BlockSeal.java index 894c1028a..a34786b89 100644 --- a/src/main/java/zmaster587/advancedRocketry/block/BlockSeal.java +++ b/src/main/java/zmaster587/advancedRocketry/block/BlockSeal.java @@ -3,13 +3,18 @@ import net.minecraft.block.Block; import net.minecraft.block.material.Material; import net.minecraft.block.state.IBlockState; +import net.minecraft.client.util.ITooltipFlag; +import net.minecraft.item.ItemStack; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.EnumFacing; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; import zmaster587.advancedRocketry.api.AreaBlob; import zmaster587.advancedRocketry.api.util.IBlobHandler; import zmaster587.advancedRocketry.atmosphere.AtmosphereHandler; +import zmaster587.advancedRocketry.client.TooltipInjector; import zmaster587.advancedRocketry.tile.atmosphere.TileSeal; import zmaster587.libVulpes.util.HashedBlockPosition; @@ -18,6 +23,7 @@ import javax.annotation.ParametersAreNonnullByDefault; import java.util.HashMap; import java.util.LinkedList; +import java.util.List; public class BlockSeal extends Block { @@ -196,4 +202,11 @@ public int getTraceDistance() { return -1; } } + + @SideOnly(Side.CLIENT) + @Override + public void addInformation(ItemStack stack, @Nullable World world, List tooltip, ITooltipFlag flag) { + int insertAt = TooltipInjector.computeInsertIndex(tooltip, flag.isAdvanced()); + TooltipInjector.renderShiftAlt(stack, tooltip, "tooltip.advancedrocketry.pipeseal", insertAt); + } } diff --git a/src/main/java/zmaster587/advancedRocketry/block/BlockSeat.java b/src/main/java/zmaster587/advancedRocketry/block/BlockSeat.java index ea2676456..71d3d565c 100644 --- a/src/main/java/zmaster587/advancedRocketry/block/BlockSeat.java +++ b/src/main/java/zmaster587/advancedRocketry/block/BlockSeat.java @@ -3,8 +3,10 @@ import net.minecraft.block.Block; import net.minecraft.block.material.Material; import net.minecraft.block.state.IBlockState; +import net.minecraft.client.util.ITooltipFlag; import net.minecraft.entity.Entity; import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; import net.minecraft.util.BlockRenderLayer; import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumHand; @@ -13,6 +15,9 @@ import net.minecraft.world.Explosion; import net.minecraft.world.IBlockAccess; import net.minecraft.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import zmaster587.advancedRocketry.client.TooltipInjector; import zmaster587.advancedRocketry.entity.EntityDummy; import javax.annotation.Nonnull; @@ -107,4 +112,11 @@ public boolean onBlockActivated(World world, BlockPos pos, return true; } + + @SideOnly(Side.CLIENT) + @Override + public void addInformation(ItemStack stack, @Nullable World world, List tooltip, ITooltipFlag flag) { + int insertAt = TooltipInjector.computeInsertIndex(tooltip, flag.isAdvanced()); + TooltipInjector.renderShiftAlt(stack, tooltip, "tooltip.advancedrocketry.seat", insertAt); + } } diff --git a/src/main/java/zmaster587/advancedRocketry/block/BlockSolarGenerator.java b/src/main/java/zmaster587/advancedRocketry/block/BlockSolarGenerator.java index 32e58c13f..23f15865d 100644 --- a/src/main/java/zmaster587/advancedRocketry/block/BlockSolarGenerator.java +++ b/src/main/java/zmaster587/advancedRocketry/block/BlockSolarGenerator.java @@ -1,7 +1,17 @@ package zmaster587.advancedRocketry.block; +import java.util.List; + +import javax.annotation.Nullable; + import net.minecraft.block.state.IBlockState; +import net.minecraft.client.util.ITooltipFlag; +import net.minecraft.item.ItemStack; import net.minecraft.tileentity.TileEntity; +import net.minecraft.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import zmaster587.advancedRocketry.client.TooltipInjector; import zmaster587.libVulpes.block.BlockTile; /** @@ -22,4 +32,10 @@ public boolean isBlockNormalCube(IBlockState state) { public boolean isOpaqueCube(IBlockState state) { return true; } + @SideOnly(Side.CLIENT) + @Override + public void addInformation(ItemStack stack, @Nullable World world, List tooltip, ITooltipFlag flag) { + int insertAt = TooltipInjector.computeInsertIndex(tooltip, flag.isAdvanced()); + TooltipInjector.renderShiftAlt(stack, tooltip, "tooltip.advancedrocketry.solargenerator", insertAt); + } } diff --git a/src/main/java/zmaster587/advancedRocketry/block/BlockStationModuleDockingPort.java b/src/main/java/zmaster587/advancedRocketry/block/BlockStationModuleDockingPort.java index 7cec3efe0..168a9d20e 100644 --- a/src/main/java/zmaster587/advancedRocketry/block/BlockStationModuleDockingPort.java +++ b/src/main/java/zmaster587/advancedRocketry/block/BlockStationModuleDockingPort.java @@ -2,6 +2,7 @@ import net.minecraft.block.material.Material; import net.minecraft.block.state.IBlockState; +import net.minecraft.client.util.ITooltipFlag; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemStack; @@ -10,13 +11,19 @@ import net.minecraft.util.EnumHand; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import zmaster587.advancedRocketry.client.TooltipInjector; import zmaster587.advancedRocketry.tile.station.TileDockingPort; import zmaster587.advancedRocketry.tile.station.TileLandingPad; import zmaster587.libVulpes.LibVulpes; import zmaster587.libVulpes.block.BlockFullyRotatable; import zmaster587.libVulpes.inventory.GuiHandler; +import java.util.List; + import javax.annotation.Nonnull; +import javax.annotation.Nullable; import javax.annotation.ParametersAreNonnullByDefault; import javax.annotation.ParametersAreNullableByDefault; @@ -56,6 +63,13 @@ public void onBlockPlacedBy(World world, BlockPos pos, IBlockState state, } } + @SideOnly(Side.CLIENT) + @Override + public void addInformation(ItemStack stack, @Nullable World world, List tooltip, ITooltipFlag flag) { + int insertAt = TooltipInjector.computeInsertIndex(tooltip, flag.isAdvanced()); + TooltipInjector.renderShiftAlt(stack, tooltip, "tooltip.advancedrocketry.dockingport", insertAt); + } + @Override @ParametersAreNonnullByDefault public void breakBlock(World world, BlockPos pos, IBlockState state) { diff --git a/src/main/java/zmaster587/advancedRocketry/block/BlockSuitWorkstation.java b/src/main/java/zmaster587/advancedRocketry/block/BlockSuitWorkstation.java index f51998463..cea44cc89 100644 --- a/src/main/java/zmaster587/advancedRocketry/block/BlockSuitWorkstation.java +++ b/src/main/java/zmaster587/advancedRocketry/block/BlockSuitWorkstation.java @@ -1,12 +1,20 @@ package zmaster587.advancedRocketry.block; +import java.util.List; + +import javax.annotation.Nullable; + import net.minecraft.block.state.IBlockState; +import net.minecraft.client.util.ITooltipFlag; import net.minecraft.entity.item.EntityItem; import net.minecraft.inventory.IInventory; import net.minecraft.item.ItemStack; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import zmaster587.advancedRocketry.client.TooltipInjector; import zmaster587.libVulpes.block.BlockTile; public class BlockSuitWorkstation extends BlockTile { @@ -54,4 +62,11 @@ public void breakBlock(World world, BlockPos pos, IBlockState state) { world.removeTileEntity(pos); } + + @SideOnly(Side.CLIENT) + @Override + public void addInformation(ItemStack stack, @Nullable World world, List tooltip, ITooltipFlag flag) { + int insertAt = TooltipInjector.computeInsertIndex(tooltip, flag.isAdvanced()); + TooltipInjector.renderShiftAlt(stack, tooltip, "tooltip.advancedrocketry.suitworkingstation", insertAt); + } } diff --git a/src/main/java/zmaster587/advancedRocketry/block/BlockThermiteTorch.java b/src/main/java/zmaster587/advancedRocketry/block/BlockThermiteTorch.java index 3c5aedd3a..266895ca5 100644 --- a/src/main/java/zmaster587/advancedRocketry/block/BlockThermiteTorch.java +++ b/src/main/java/zmaster587/advancedRocketry/block/BlockThermiteTorch.java @@ -1,7 +1,23 @@ package zmaster587.advancedRocketry.block; +import java.util.List; + +import javax.annotation.Nullable; + import net.minecraft.block.BlockTorch; +import net.minecraft.client.util.ITooltipFlag; +import net.minecraft.item.ItemStack; +import net.minecraft.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import zmaster587.advancedRocketry.client.TooltipInjector; public class BlockThermiteTorch extends BlockTorch { + @SideOnly(Side.CLIENT) + @Override + public void addInformation(ItemStack stack, @Nullable World world, List tooltip, ITooltipFlag flag) { + int insertAt = TooltipInjector.computeInsertIndex(tooltip, flag.isAdvanced()); + TooltipInjector.renderShiftAlt(stack, tooltip, "tooltip.advancedrocketry.thermitetorch", insertAt); + } } diff --git a/src/main/java/zmaster587/advancedRocketry/block/BlockTileNeighborUpdate.java b/src/main/java/zmaster587/advancedRocketry/block/BlockTileNeighborUpdate.java index a39b6b5c3..4ee00b0d3 100644 --- a/src/main/java/zmaster587/advancedRocketry/block/BlockTileNeighborUpdate.java +++ b/src/main/java/zmaster587/advancedRocketry/block/BlockTileNeighborUpdate.java @@ -15,6 +15,21 @@ public BlockTileNeighborUpdate(Class tileClass, int guiId) super(tileClass, guiId); } + // redstone power uses neighbor change to update redstone power + @Override + public void neighborChanged(net.minecraft.block.state.IBlockState state, + net.minecraft.world.World world, + net.minecraft.util.math.BlockPos pos, + net.minecraft.block.Block blockIn, + net.minecraft.util.math.BlockPos fromPos) { + super.neighborChanged(state, world, pos, blockIn, fromPos); + TileEntity te = world.getTileEntity(pos); + if (te instanceof zmaster587.libVulpes.util.IAdjBlockUpdate) { + ((zmaster587.libVulpes.util.IAdjBlockUpdate) te).onAdjacentBlockUpdated(); + } + } + + @Override public void onNeighborChange(IBlockAccess world, BlockPos pos, BlockPos neighbor) { super.onNeighborChange(world, pos, neighbor); diff --git a/src/main/java/zmaster587/advancedRocketry/block/BlockTileRedstoneEmitter.java b/src/main/java/zmaster587/advancedRocketry/block/BlockTileRedstoneEmitter.java index fefcbe482..8d0604a0d 100644 --- a/src/main/java/zmaster587/advancedRocketry/block/BlockTileRedstoneEmitter.java +++ b/src/main/java/zmaster587/advancedRocketry/block/BlockTileRedstoneEmitter.java @@ -1,24 +1,45 @@ package zmaster587.advancedRocketry.block; +import javax.annotation.Nullable; + import net.minecraft.block.state.IBlockState; +import net.minecraft.client.util.ITooltipFlag; +import net.minecraft.item.ItemStack; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.EnumFacing; import net.minecraft.util.math.BlockPos; import net.minecraft.world.IBlockAccess; import net.minecraft.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import zmaster587.advancedRocketry.client.TooltipInjector; import zmaster587.libVulpes.block.BlockTile; +import javax.annotation.Nullable; +import java.util.List; + +// Fueling Station block public class BlockTileRedstoneEmitter extends BlockTile { - public BlockTileRedstoneEmitter(Class tileClass, - int guiId) { + public BlockTileRedstoneEmitter(Class tileClass, int guiId) { super(tileClass, guiId); } @Override - public int getWeakPower(IBlockState blockState, IBlockAccess blockAccess, - BlockPos pos, EnumFacing side) { - return blockState.getValue(STATE) ? 15 : 0; + public int getWeakPower(IBlockState state, IBlockAccess world, BlockPos pos, EnumFacing side) { + return state.getValue(STATE) ? 15 : 0; + } + + @Override + public int getStrongPower(IBlockState state, IBlockAccess world, BlockPos pos, EnumFacing side) { + return getWeakPower(state, world, pos, side); + } + + @SideOnly(Side.CLIENT) + @Override + public void addInformation(ItemStack stack, @Nullable World world, List tooltip, ITooltipFlag flag) { + int insertAt = TooltipInjector.computeInsertIndex(tooltip, flag.isAdvanced()); + TooltipInjector.renderShiftAlt(stack, tooltip, "tooltip.advancedrocketry.fuelingstation", insertAt); } @Override @@ -26,11 +47,23 @@ public boolean canProvidePower(IBlockState state) { return true; } - public void setRedstoneState(World world, IBlockState state, BlockPos pos, boolean newState) { - if (world.getBlockState(pos).getBlock() != this) - return; + public void setRedstoneState(World world, IBlockState _ignored, BlockPos pos, boolean newState) { + // Server-only to avoid client mutations + if (world.isRemote) return; + + // skip if chunk isn't loaded + if (!world.isBlockLoaded(pos)) return; + + // Read the current state from the world to avoid acting on a stale IBlockState + IBlockState curState = world.getBlockState(pos); + if (curState.getBlock() != this) return; + + boolean current = curState.getValue(STATE); + if (current == newState) return; // no-op if unchanged + + IBlockState updated = curState.withProperty(STATE, newState); - world.setBlockState(pos, state.withProperty(STATE, newState)); - world.notifyBlockUpdate(pos, state, state, 3); + // 3 = neighbors notified (1) + clients updated (2) + world.setBlockState(pos, updated, 3); } } diff --git a/src/main/java/zmaster587/advancedRocketry/block/BlockTileTerraformer.java b/src/main/java/zmaster587/advancedRocketry/block/BlockTileTerraformer.java index 824464b51..e4967cba4 100644 --- a/src/main/java/zmaster587/advancedRocketry/block/BlockTileTerraformer.java +++ b/src/main/java/zmaster587/advancedRocketry/block/BlockTileTerraformer.java @@ -8,6 +8,7 @@ import net.minecraft.block.properties.PropertyBool; import net.minecraft.block.state.BlockStateContainer; import net.minecraft.block.state.IBlockState; +import net.minecraft.client.util.ITooltipFlag; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.item.EntityItem; import net.minecraft.entity.player.EntityPlayer; @@ -21,8 +22,12 @@ import net.minecraft.util.math.BlockPos; import net.minecraft.world.IBlockAccess; import net.minecraft.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + import org.lwjgl.Sys; import scala.tools.nsc.doc.base.comment.EntityLink; +import zmaster587.advancedRocketry.client.TooltipInjector; import zmaster587.advancedRocketry.dimension.DimensionManager; import zmaster587.advancedRocketry.dimension.DimensionProperties; import zmaster587.advancedRocketry.tile.satellite.TileTerraformingTerminal; @@ -31,7 +36,9 @@ import zmaster587.libVulpes.block.RotatableBlock; import zmaster587.libVulpes.util.IAdjBlockUpdate; +import java.util.List; import javax.annotation.Nonnull; +import javax.annotation.Nullable; public class BlockTileTerraformer extends RotatableBlock { protected Class tileClass; @@ -163,4 +170,10 @@ public void breakBlock(World world, BlockPos pos, IBlockState state) { super.breakBlock(world, pos, state); } + @SideOnly(Side.CLIENT) + @Override + public void addInformation(ItemStack stack, @Nullable World world, List tooltip, ITooltipFlag flag) { + int insertAt = TooltipInjector.computeInsertIndex(tooltip, flag.isAdvanced()); + TooltipInjector.renderShiftAlt(stack, tooltip, "tooltip.advancedrocketry.terraformer", insertAt); + } } diff --git a/src/main/java/zmaster587/advancedRocketry/block/BlockTransciever.java b/src/main/java/zmaster587/advancedRocketry/block/BlockTransciever.java index 38b59d4b7..9eb0d0f61 100644 --- a/src/main/java/zmaster587/advancedRocketry/block/BlockTransciever.java +++ b/src/main/java/zmaster587/advancedRocketry/block/BlockTransciever.java @@ -1,38 +1,154 @@ package zmaster587.advancedRocketry.block; +import net.minecraft.block.state.BlockStateContainer; import net.minecraft.block.state.IBlockState; +import net.minecraft.client.util.ITooltipFlag; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.item.ItemStack; import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.BlockRenderLayer; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; import net.minecraft.world.IBlockAccess; +import net.minecraft.world.World; +import net.minecraft.block.properties.PropertyDirection; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; import zmaster587.libVulpes.block.BlockTile; +import zmaster587.advancedRocketry.client.TooltipInjector; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; public class BlockTransciever extends BlockTile { - private static final AxisAlignedBB[] bb = {new AxisAlignedBB(.25, .25, .75, .75, .75, 1), - new AxisAlignedBB(.25, .25, 0, .75, .75, 0.25), - new AxisAlignedBB(.75, .25, .25, 1, .75, .75), - new AxisAlignedBB(0, .25, .25, 0.25, .75, .75)}; + public static final PropertyDirection FACING = PropertyDirection.create("facing"); + + private static final AxisAlignedBB AABB_N = new AxisAlignedBB(.25, .25, 0.00, .75, .75, .25); + private static final AxisAlignedBB AABB_S = new AxisAlignedBB(.25, .25, .75, .75, .75, 1.00); + private static final AxisAlignedBB AABB_W = new AxisAlignedBB(0.00, .25, .25, .25, .75, .75); + private static final AxisAlignedBB AABB_E = new AxisAlignedBB(.75, .25, .25, 1.00, .75, .75); + private static final AxisAlignedBB AABB_U = new AxisAlignedBB(.25, .75, .25, .75, 1.00, .75); + private static final AxisAlignedBB AABB_D = new AxisAlignedBB(.25, 0.00, .25, .75, .25, .75); public BlockTransciever(Class tileClass, int guiId) { super(tileClass, guiId); + this.setDefaultState( + this.blockState.getBaseState() + .withProperty(STATE, Boolean.FALSE) + .withProperty(FACING, EnumFacing.NORTH) + ); + this.setHardness(3f); + this.setResistance(10f); + this.setLightOpacity(0); } @Override - public AxisAlignedBB getBoundingBox(IBlockState state, IBlockAccess source, - BlockPos pos) { + public AxisAlignedBB getBoundingBox(IBlockState state, IBlockAccess source, BlockPos pos) { + EnumFacing f = state.getValue(FACING).getOpposite(); + switch (f) { + case NORTH: return AABB_N; + case SOUTH: return AABB_S; + case WEST: return AABB_W; + case EAST: return AABB_E; + case UP: return AABB_U; + case DOWN: return AABB_D; + default: return FULL_BLOCK_AABB; + } + } + @Override public boolean isFullCube(IBlockState state) { return false; } + @Override public boolean isNormalCube(IBlockState state, IBlockAccess world, BlockPos pos) { return false; } + @Override public boolean isOpaqueCube(IBlockState state) { return false; } - return bb[state.getValue(FACING).ordinal() - 2]; + @SideOnly(Side.CLIENT) + @Override + @Nonnull + public BlockRenderLayer getBlockLayer() { + return BlockRenderLayer.CUTOUT_MIPPED; } @Override - public boolean isFullCube(IBlockState state) { - return false; + protected BlockStateContainer createBlockState() { + return new BlockStateContainer(this, FACING, STATE); + } + + @Override + public int getMetaFromState(IBlockState state) { + int face = state.getValue(FACING).getIndex(); // 0..5 + boolean on = state.getValue(STATE); + return face | (on ? 8 : 0); + } + + @Override + public IBlockState getStateFromMeta(int meta) { + EnumFacing f = EnumFacing.getFront(meta & 7); + boolean on = (meta & 8) != 0; + return this.getDefaultState().withProperty(FACING, f).withProperty(STATE, on); + } + + @Override + public IBlockState getActualState(IBlockState state, IBlockAccess world, BlockPos pos) { + return state; } @Override - public boolean isNormalCube(IBlockState state, IBlockAccess world, BlockPos pos) { + @Nonnull + public IBlockState getStateForPlacement( + World world, BlockPos pos, EnumFacing clickedFace, + float hitX, float hitY, float hitZ, int meta, + EntityLivingBase placer, EnumHand hand) { + return this.getDefaultState() + .withProperty(FACING, clickedFace) + .withProperty(STATE, Boolean.FALSE); + } + + @Override + public void onBlockPlacedBy(World world, BlockPos pos, IBlockState state, + EntityLivingBase placer, @Nonnull ItemStack stack) { + // keep orientation from getStateForPlacement + } + + @Override + public boolean rotateBlock(World world, BlockPos pos, EnumFacing axis) { + IBlockState s = world.getBlockState(pos); + EnumFacing cur = s.getValue(FACING); + EnumFacing next; + + switch (axis) { + case UP: + case DOWN: + next = (cur == EnumFacing.UP || cur == EnumFacing.DOWN) ? cur : cur.rotateY(); + break; + case NORTH: + case SOUTH: + case EAST: + case WEST: + next = (cur == EnumFacing.UP || cur == EnumFacing.DOWN) ? cur : cur.rotateY(); + break; + default: + next = cur; + } + + if (next != cur) { + world.setBlockState(pos, s.withProperty(FACING, next), 2); + return true; + } return false; } + + @SideOnly(Side.CLIENT) + @Override + public void addInformation(ItemStack stack, @Nullable World world, List tooltip, ITooltipFlag flag) { + int insertAt = TooltipInjector.computeInsertIndex(tooltip, flag.isAdvanced()); + TooltipInjector.renderShiftAlt(stack, tooltip, "tooltip.advancedrocketry.transceiver", insertAt); + } + + public static EnumFacing getFront(IBlockState state) { + if (state == null || !state.getPropertyKeys().contains(FACING)) return EnumFacing.NORTH; + return state.getValue(FACING); + } } diff --git a/src/main/java/zmaster587/advancedRocketry/block/BlockWarpController.java b/src/main/java/zmaster587/advancedRocketry/block/BlockWarpController.java index 6d02996fb..016e09722 100644 --- a/src/main/java/zmaster587/advancedRocketry/block/BlockWarpController.java +++ b/src/main/java/zmaster587/advancedRocketry/block/BlockWarpController.java @@ -1,17 +1,23 @@ package zmaster587.advancedRocketry.block; import net.minecraft.block.state.IBlockState; +import net.minecraft.client.util.ITooltipFlag; import net.minecraft.entity.EntityLivingBase; import net.minecraft.item.ItemStack; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; import zmaster587.advancedRocketry.api.stations.ISpaceObject; +import zmaster587.advancedRocketry.client.TooltipInjector; import zmaster587.advancedRocketry.stations.SpaceObjectManager; import zmaster587.advancedRocketry.stations.SpaceStationObject; import zmaster587.libVulpes.block.BlockTile; +import java.util.List; import javax.annotation.Nonnull; +import javax.annotation.Nullable; public class BlockWarpController extends BlockTile { @@ -30,4 +36,12 @@ public void onBlockPlacedBy(World world, BlockPos pos, IBlockState state, ((SpaceStationObject) spaceObject).setForwardDirection(getFront(state).getOpposite()); } } + + @SideOnly(Side.CLIENT) + @Override + public void addInformation(ItemStack stack, @Nullable World world, List tooltip, ITooltipFlag flag) { + int insertAt = TooltipInjector.computeInsertIndex(tooltip, flag.isAdvanced()); + TooltipInjector.renderShiftAlt(stack, tooltip, "tooltip.advancedrocketry.warpcontroller", insertAt); + } + } diff --git a/src/main/java/zmaster587/advancedRocketry/block/multiblock/BlockARHatch.java b/src/main/java/zmaster587/advancedRocketry/block/multiblock/BlockARHatch.java index d45817de4..8fd68b7e7 100644 --- a/src/main/java/zmaster587/advancedRocketry/block/multiblock/BlockARHatch.java +++ b/src/main/java/zmaster587/advancedRocketry/block/multiblock/BlockARHatch.java @@ -35,17 +35,24 @@ public void getSubBlocks(CreativeTabs tab, } @Override - public boolean shouldSideBeRendered(IBlockState blockState, - IBlockAccess blockAccess, BlockPos pos, EnumFacing direction) { + public boolean shouldSideBeRendered(IBlockState blockState, IBlockAccess blockAccess, BlockPos pos, EnumFacing direction) { + int variant = blockState.getValue(VARIANT); + // Always render sides for guidancecomputeraccesshatch variants (6 and 14) + if (variant == 6 || variant == 14) { + return true; + } - boolean isPointer = blockAccess.getTileEntity(pos.offset(direction.getOpposite())) instanceof TilePointer; - if (blockState.getValue(VARIANT) == 8) + // Keep + if (variant == 8) return false; - if (isPointer || blockState.getValue(VARIANT) < 2) + + boolean isPointer = blockAccess.getTileEntity(pos.offset(direction.getOpposite())) instanceof TilePointer; + if (isPointer || variant < 2) return super.shouldSideBeRendered(blockState, blockAccess, pos, direction); - return true; + + return true; } @Override diff --git a/src/main/java/zmaster587/advancedRocketry/client/TooltipInjector.java b/src/main/java/zmaster587/advancedRocketry/client/TooltipInjector.java new file mode 100644 index 000000000..b2fa67284 --- /dev/null +++ b/src/main/java/zmaster587/advancedRocketry/client/TooltipInjector.java @@ -0,0 +1,522 @@ +package zmaster587.advancedRocketry.client; + +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.resources.I18n; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.text.TextFormatting; +import net.minecraftforge.client.event.ModelRegistryEvent; +import net.minecraftforge.event.entity.player.ItemTooltipEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import org.lwjgl.input.Keyboard; +import zmaster587.advancedRocketry.api.Constants; +import zmaster587.advancedRocketry.api.ARConfiguration; + +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.FluidRegistry; +import zmaster587.advancedRocketry.api.fuel.FuelRegistry; +import zmaster587.advancedRocketry.api.fuel.FuelRegistry.FuelType; + +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Mod.EventBusSubscriber(modid = Constants.modId, value = Side.CLIENT) +public final class TooltipInjector { + + private TooltipInjector() {} + + /** Maps exact registry IDs -> base tooltip lang key */ + private static final Map KEY_BY_ID = new HashMap<>(); + /** Fallback: maps the unlocalized-name tail -> base tooltip lang key */ + private static final Map KEY_BY_SUFFIX = new HashMap<>(); + /** Optional: dynamic args for formatted lines (usually used in .alt.2) */ + @FunctionalInterface interface ArgProvider { Object[] get(ItemStack s); } + private static final Map ARGS_BY_BASEKEY = new HashMap<>(); + /** For items that need per-stack (e.g., meta) keys */ + private static final Map> KEY_RESOLVER_BY_ID = new HashMap<>(); + + + static { + // ---- CO2 Scrubber ---- + KEY_BY_ID.put("advancedrocketry:scrubber", "tooltip.advancedrocketry.scrubber"); + KEY_BY_SUFFIX.put("scrubber", "tooltip.advancedrocketry.scrubber"); + + // ---- Oxygen Vent ---- + KEY_BY_ID.put("advancedrocketry:oxygenvent", "tooltip.advancedrocketry.oxygenvent"); + KEY_BY_SUFFIX.put("oxygenVent", "tooltip.advancedrocketry.oxygenvent"); + + ARGS_BY_BASEKEY.put("tooltip.advancedrocketry.oxygenvent", + s -> new Object[] { ARConfiguration.getCurrentConfig().oxygenVentSize }); + + // ---- Carbon Scrubber Cartridge ---- + KEY_BY_ID.put("advancedrocketry:carbonscrubbercartridge", "tooltip.advancedrocketry.scrubbercart"); + KEY_BY_SUFFIX.put("carbonScrubberCartridge", "tooltip.advancedrocketry.scrubbercart"); + + // ---- Structure Tower ---- + KEY_BY_ID.put("advancedrocketry:structuretower", "tooltip.advancedrocketry.structuretower"); + KEY_BY_SUFFIX.put("structuretower", "tooltip.advancedrocketry.structuretower"); + + // --- ItemUpgrade (meta-based) 6 Space Suit Components--- + KEY_RESOLVER_BY_ID.put("advancedrocketry:itemupgrade", + s -> "tooltip.advancedrocketry.itemupgrade." + s.getItemDamage()); + KEY_RESOLVER_BY_ID.put("advancedrocketry:item_upgrade", + s -> "tooltip.advancedrocketry.itemupgrade." + s.getItemDamage()); + + // ---- Guidance Computer ---- + KEY_BY_ID.put("advancedrocketry:guidancecomputer", "tooltip.advancedrocketry.guidancecomputer"); + KEY_BY_ID.put("advancedrocketry:guidance_computer", "tooltip.advancedrocketry.guidancecomputer"); // just in case + KEY_BY_SUFFIX.put("guidanceComputer", "tooltip.advancedrocketry.guidancecomputer"); + + // ---- Service Monitor ---- + KEY_BY_ID.put("advancedrocketry:servicemonitor", "tooltip.advancedrocketry.servicemonitor"); + KEY_BY_SUFFIX.put("servicemonitor", "tooltip.advancedrocketry.servicemonitor"); + + // ---- Service Station ---- + KEY_BY_ID.put("advancedrocketry:servicestation", "tooltip.advancedrocketry.servicestation"); + KEY_BY_ID.put("advancedrocketry:serviceStation", "tooltip.advancedrocketry.servicestation"); + KEY_BY_SUFFIX.put("serviceStation", "tooltip.advancedrocketry.servicestation"); + + // ---- Gas Charge Pad ---- + KEY_BY_ID.put("advancedrocketry:oxygenCharger", "tooltip.advancedrocketry.oxygencharger"); + KEY_BY_SUFFIX.put("oxygenCharger", "tooltip.advancedrocketry.oxygencharger"); + + // --- Station Controllers + KEY_BY_ID.put("advancedrocketry:orientationcontroller", "tooltip.advancedrocketry.orientationctrl"); + KEY_BY_SUFFIX.put("orientationcontroller", "tooltip.advancedrocketry.orientationctrl"); + KEY_BY_SUFFIX.put("orientationController", "tooltip.advancedrocketry.orientationctrl"); + + KEY_BY_SUFFIX.put("gravityController", "tooltip.advancedrocketry.gravityctrl"); + KEY_BY_ID.put("advancedrocketry:gravitycontroller", "tooltip.advancedrocketry.gravityctrl"); + KEY_BY_SUFFIX.put("gravitycontroller", "tooltip.advancedrocketry.gravityctrl"); + + KEY_BY_ID.put("advancedrocketry:altitudecontroller", "tooltip.advancedrocketry.altitudectrl"); + KEY_BY_SUFFIX.put("altitudeController", "tooltip.advancedrocketry.altitudectrl"); + KEY_BY_SUFFIX.put("altitudecontroller", "tooltip.advancedrocketry.altitudectrl"); + + // ---- Small Airlock Door (ItemDoor tied to blockAirLock) ---- + KEY_BY_ID.put("advancedrocketry:smallairlockdoor", "tooltip.advancedrocketry.smallairlock"); + // Fallback by unloc tail (you set setUnlocalizedName("smallAirlock")) + KEY_BY_SUFFIX.put("smallAirlock", "tooltip.advancedrocketry.smallairlock"); + + // Planet Selectors + KEY_BY_ID.put("advancedrocketry:planetselector", "tooltip.advancedrocketry.planetselector"); + KEY_BY_SUFFIX.put("planetSelector", "tooltip.advancedrocketry.planetselector"); + + KEY_BY_ID.put("advancedrocketry:planetholoselector", "tooltip.advancedrocketry.planetholoselector"); + KEY_BY_SUFFIX.put("planetHoloSelector", "tooltip.advancedrocketry.planetholoselector"); + + // ---- Station Light ---- + KEY_BY_ID.put("advancedrocketry:circlelight", "tooltip.advancedrocketry.circlelight"); + KEY_BY_SUFFIX.put("circleLight", "tooltip.advancedrocketry.circlelight"); + + // ---- Monitoring Station ---- + KEY_BY_ID.put("advancedrocketry:monitoringstation", "tooltip.advancedrocketry.monitoringstation"); + KEY_BY_ID.put("advancedrocketry:monitoring_station", "tooltip.advancedrocketry.monitoringstation"); + KEY_BY_SUFFIX.put("monitoringstation", "tooltip.advancedrocketry.monitoringstation"); + + // ---- Satellite Builder ---- + KEY_BY_ID.put("advancedrocketry:satellitebuilder", "tooltip.advancedrocketry.satellitebuilder"); + KEY_BY_SUFFIX.put("satelliteBuilder", "tooltip.advancedrocketry.satellitebuilder"); + + // ---- Satellite Control Terminal ---- + KEY_BY_ID.put("advancedrocketry:satellitemonitor", "tooltip.advancedrocketry.satellitemonitor"); + KEY_BY_ID.put("advancedrocketry:satellite_monitor", "tooltip.advancedrocketry.satellitemonitor"); + KEY_BY_SUFFIX.put("satelliteMonitor", "tooltip.advancedrocketry.satellitemonitor"); + + // --- Satellite Primary Function (metas 0..6) + KEY_RESOLVER_BY_ID.put("advancedrocketry:satelliteprimaryfunction", s -> { + switch (s.getMetadata() & 7) { + case 0: return "tooltip.advancedrocketry.satfunc.optical"; + case 1: return "tooltip.advancedrocketry.satfunc.composition"; + case 2: return "tooltip.advancedrocketry.satfunc.mass"; + case 3: return "tooltip.advancedrocketry.satfunc.microwave"; + case 4: return "tooltip.advancedrocketry.satfunc.oremapping"; + case 5: return "tooltip.advancedrocketry.satfunc.biomechanger"; + case 6: return "tooltip.advancedrocketry.satfunc.weather"; + default: return null; + } + }); + // (optional camelCase fallback if you ever see it) + KEY_RESOLVER_BY_ID.put("advancedrocketry:satellitePrimaryFunction", + KEY_RESOLVER_BY_ID.get("advancedrocketry:satelliteprimaryfunction")); + + // --- Satellite Power Source (metas 0..1) + KEY_RESOLVER_BY_ID.put("advancedrocketry:satellitepowersource", s -> { + switch (s.getMetadata() & 1) { + case 0: return "tooltip.advancedrocketry.satpower.0"; // Basic solar + case 1: return "tooltip.advancedrocketry.satpower.1"; // Advanced solar + default: return null; + } + }); + KEY_RESOLVER_BY_ID.put("advancedrocketry:satellitePowerSource", + KEY_RESOLVER_BY_ID.get("advancedrocketry:satellitepowersource")); + + // ---- LibVulpes Battery (meta 0..1) ---- + KEY_RESOLVER_BY_ID.put("libvulpes:battery", s -> "tooltip.libvulpes.battery." + (s.getMetadata() & 1)); + + // ---- ID Chips / Chips ---- + KEY_BY_ID.put("advancedrocketry:satelliteidchip", "tooltip.advancedrocketry.satidchip"); + KEY_BY_SUFFIX.put("satelliteIdChip", "tooltip.advancedrocketry.satidchip"); + + KEY_BY_ID.put("advancedrocketry:planetidchip", "tooltip.advancedrocketry.planetidchip"); + KEY_BY_SUFFIX.put("planetIdChip", "tooltip.advancedrocketry.planetidchip"); + + KEY_BY_ID.put("advancedrocketry:stationchip", "tooltip.advancedrocketry.stationchip"); + KEY_BY_SUFFIX.put("stationChip", "tooltip.advancedrocketry.stationchip"); + // registry sometimes: spaceStationChip + KEY_BY_ID.put("advancedrocketry:spacestationchip", "tooltip.advancedrocketry.stationchip"); + KEY_BY_SUFFIX.put("spaceStationChip", "tooltip.advancedrocketry.stationchip"); + + KEY_BY_ID.put("advancedrocketry:elevatorchip", "tooltip.advancedrocketry.elevatorchip"); + KEY_BY_SUFFIX.put("elevatorChip", "tooltip.advancedrocketry.elevatorchip"); + + KEY_BY_ID.put("advancedrocketry:asteroidchip", "tooltip.advancedrocketry.asteroidchip"); + KEY_BY_SUFFIX.put("asteroidChip", "tooltip.advancedrocketry.asteroidchip"); + + // ---- Energy multiblocks ---- + + // Black Hole Generator + KEY_BY_ID.put("advancedrocketry:blackholegenerator", "tooltip.advancedrocketry.blackholegen"); + KEY_BY_SUFFIX.put("blackholegenerator", "tooltip.advancedrocketry.blackholegen"); + + // Microwave Receiver (note: source uses the misspelling "Reciever"; cover both) + KEY_BY_ID.put("advancedrocketry:microwavereciever", "tooltip.advancedrocketry.microwavereceiver"); + KEY_BY_ID.put("advancedrocketry:microwaveReciever", "tooltip.advancedrocketry.microwavereceiver"); + KEY_BY_SUFFIX.put("microwavereciever", "tooltip.advancedrocketry.microwavereceiver"); + KEY_BY_SUFFIX.put("microwaveReciever", "tooltip.advancedrocketry.microwavereceiver"); + // (optional safety if it ever gets corrected) + KEY_BY_ID.put("advancedrocketry:microwavereceiver", "tooltip.advancedrocketry.microwavereceiver"); + KEY_BY_SUFFIX.put("microwaveReceiver", "tooltip.advancedrocketry.microwavereceiver"); + + // ---- Solar Panel (part of multiblock) ---- + KEY_BY_ID.put("advancedrocketry:solarpanel", "tooltip.advancedrocketry.solarpanel"); + KEY_BY_ID.put("advancedrocketry:solarPanel", "tooltip.advancedrocketry.solarpanel"); // just in case + KEY_BY_SUFFIX.put("solarpanel", "tooltip.advancedrocketry.solarpanel"); + KEY_BY_SUFFIX.put("solarPanel", "tooltip.advancedrocketry.solarpanel"); + + // Solar Array + KEY_BY_ID.put("advancedrocketry:solararray", "tooltip.advancedrocketry.solararray"); + KEY_BY_SUFFIX.put("solararray", "tooltip.advancedrocketry.solararray"); + // Solar Array Panel + KEY_BY_ID.put("advancedrocketry:solararraypanel", "tooltip.advancedrocketry.solararraypanel"); + KEY_BY_SUFFIX.put("solararraypanel", "tooltip.advancedrocketry.solararraypanel"); + + // ---- BlockARHatch (registered as advancedrocketry:loader), meta 0..6 ---- + KEY_RESOLVER_BY_ID.put("advancedrocketry:loader", s -> { + final int v = s.getMetadata() & 7; // strip redstone/state bit + switch (v) { + case 0: return "tooltip.advancedrocketry.hatch.databus"; + case 1: return "tooltip.advancedrocketry.hatch.satellite"; + case 2: return "tooltip.advancedrocketry.hatch.item_unloader"; + case 3: return "tooltip.advancedrocketry.hatch.item_loader"; + case 4: return "tooltip.advancedrocketry.hatch.fluid_unloader"; + case 5: return "tooltip.advancedrocketry.hatch.fluid_loader"; + case 6: return "tooltip.advancedrocketry.hatch.gca"; + default: return null; + } + }); + + // ---- Processing / Machines / Multiblocks---- + KEY_BY_ID.put("advancedrocketry:electricarcfurnace", "tooltip.advancedrocketry.arcfurnace"); + KEY_BY_SUFFIX.put("electricArcFurnace", "tooltip.advancedrocketry.arcfurnace"); + + KEY_BY_ID.put("advancedrocketry:rollingmachine", "tooltip.advancedrocketry.rollingmachine"); + KEY_BY_SUFFIX.put("rollingMachine", "tooltip.advancedrocketry.rollingmachine"); + + KEY_BY_ID.put("advancedrocketry:lathe", "tooltip.advancedrocketry.lathe"); + KEY_BY_SUFFIX.put("lathe", "tooltip.advancedrocketry.lathe"); + + KEY_BY_ID.put("advancedrocketry:crystallizer", "tooltip.advancedrocketry.crystallizer"); + KEY_BY_SUFFIX.put("Crystallizer", "tooltip.advancedrocketry.crystallizer"); // note capital C + + KEY_BY_ID.put("advancedrocketry:cuttingmachine", "tooltip.advancedrocketry.cuttingmachine"); + KEY_BY_SUFFIX.put("cuttingMachine", "tooltip.advancedrocketry.cuttingmachine"); + + KEY_BY_ID.put("advancedrocketry:precisionassemblingmachine", "tooltip.advancedrocketry.precisionassembler"); + KEY_BY_SUFFIX.put("precisionAssemblingMachine", "tooltip.advancedrocketry.precisionassembler"); + + KEY_BY_ID.put("advancedrocketry:electrolyser", "tooltip.advancedrocketry.electrolyser"); + KEY_BY_SUFFIX.put("electrolyser", "tooltip.advancedrocketry.electrolyser"); + + KEY_BY_ID.put("advancedrocketry:chemreactor", "tooltip.advancedrocketry.chemreactor"); + KEY_BY_SUFFIX.put("chemreactor", "tooltip.advancedrocketry.chemreactor"); + + KEY_BY_ID.put("advancedrocketry:precisionlaseretcher","tooltip.advancedrocketry.precisionlaseretcher"); + KEY_BY_SUFFIX.put("precisionlaseretcher", "tooltip.advancedrocketry.precisionlaseretcher"); + + KEY_BY_ID.put("advancedrocketry:observatory", "tooltip.advancedrocketry.observatory"); + KEY_BY_SUFFIX.put("observatory", "tooltip.advancedrocketry.observatory"); + + KEY_BY_ID.put("advancedrocketry:planetanalyser", "tooltip.advancedrocketry.planetanalyser"); + KEY_BY_SUFFIX.put("planetanalyser", "tooltip.advancedrocketry.planetanalyser"); + + KEY_BY_ID.put("advancedrocketry:centrifuge", "tooltip.advancedrocketry.centrifuge"); + KEY_BY_SUFFIX.put("centrifuge", "tooltip.advancedrocketry.centrifuge"); + + // ---- Aux / Huge ---- + KEY_BY_ID.put("advancedrocketry:warpcore", "tooltip.advancedrocketry.warpcore"); + KEY_BY_SUFFIX.put("warpCore", "tooltip.advancedrocketry.warpcore"); + + KEY_BY_ID.put("advancedrocketry:beacon", "tooltip.advancedrocketry.beacon"); + KEY_BY_SUFFIX.put("beacon", "tooltip.advancedrocketry.beacon"); + + KEY_BY_ID.put("advancedrocketry:biomescanner", "tooltip.advancedrocketry.biomescan"); + KEY_BY_SUFFIX.put("biomeScanner", "tooltip.advancedrocketry.biomescan"); + + KEY_BY_ID.put("advancedrocketry:railgun", "tooltip.advancedrocketry.railgun"); + KEY_BY_SUFFIX.put("railgun", "tooltip.advancedrocketry.railgun"); + + KEY_BY_ID.put("advancedrocketry:spaceelevatorcontroller", "tooltip.advancedrocketry.spaceelevatorctrl"); + KEY_BY_SUFFIX.put("spaceElevatorController", "tooltip.advancedrocketry.spaceelevatorctrl"); + + // ---- Building / components ---- + KEY_BY_ID.put("advancedrocketry:concrete", "tooltip.advancedrocketry.concrete"); + KEY_BY_SUFFIX.put("concrete", "tooltip.advancedrocketry.concrete"); + + KEY_BY_ID.put("advancedrocketry:blastbrick", "tooltip.advancedrocketry.blastbrick"); + KEY_BY_ID.put("advancedrocketry:blastBrick", "tooltip.advancedrocketry.blastbrick"); + KEY_BY_SUFFIX.put("blastbrick", "tooltip.advancedrocketry.blastbrick"); + KEY_BY_SUFFIX.put("blastBrick", "tooltip.advancedrocketry.blastbrick"); + + // Machines / misc + KEY_BY_ID.put("advancedrocketry:qcrucible", "tooltip.advancedrocketry.qcrucible"); + KEY_BY_SUFFIX.put("qcrucible", "tooltip.advancedrocketry.qcrucible"); + + KEY_BY_ID.put("advancedrocketry:sawblade", "tooltip.advancedrocketry.sawblade"); + KEY_BY_ID.put("advancedrocketry:sawBlade", "tooltip.advancedrocketry.sawblade"); + KEY_BY_SUFFIX.put("sawblade", "tooltip.advancedrocketry.sawblade"); + KEY_BY_SUFFIX.put("sawBlade", "tooltip.advancedrocketry.sawblade"); + + // Atmosphere Terraformer + KEY_BY_ID.put("advancedrocketry:terraformer", "tooltip.advancedrocketry.atmosterraformer"); + KEY_BY_SUFFIX.put("terraformer", "tooltip.advancedrocketry.atmosterraformer"); + + // Area Gravity Controller + KEY_BY_ID.put("advancedrocketry:gravitymachine", "tooltip.advancedrocketry.gravitymachine"); + KEY_BY_SUFFIX.put("gravityMachine", "tooltip.advancedrocketry.gravitymachine"); + KEY_BY_SUFFIX.put("gravitymachine", "tooltip.advancedrocketry.gravitymachine"); + + // Orbital Laser Drill (space laser) + KEY_BY_ID.put("advancedrocketry:spacelaser", "tooltip.advancedrocketry.spacelaser"); + KEY_BY_SUFFIX.put("spaceLaser", "tooltip.advancedrocketry.spacelaser"); + KEY_BY_SUFFIX.put("spacelaser", "tooltip.advancedrocketry.spacelaser"); + + // ---- Vacuum Laser ---- + KEY_BY_ID.put("advancedrocketry:vacuumlaser", "tooltip.advancedrocketry.vacuumlaser"); + KEY_BY_SUFFIX.put("vacuumLaser", "tooltip.advancedrocketry.vacuumlaser"); + KEY_BY_SUFFIX.put("vacuumlaser", "tooltip.advancedrocketry.vacuumlaser"); + + // ---- Pump ---- + KEY_BY_ID.put("advancedrocketry:pump", "tooltip.advancedrocketry.pump"); + KEY_BY_SUFFIX.put("pump", "tooltip.advancedrocketry.pump"); + + // ---- Assemblers ---- + // Rocket Assembler + KEY_BY_ID.put("advancedrocketry:rocketbuilder", "tooltip.advancedrocketry.rocketassembler"); + KEY_BY_SUFFIX.put("rocketbuilder", "tooltip.advancedrocketry.rocketassembler"); // safety + KEY_BY_SUFFIX.put("rocketAssembler", "tooltip.advancedrocketry.rocketassembler"); + + // Space Station Assembler + KEY_BY_ID.put("advancedrocketry:stationbuilder", "tooltip.advancedrocketry.stationassembler"); + KEY_BY_SUFFIX.put("stationbuilder", "tooltip.advancedrocketry.stationassembler"); + KEY_BY_SUFFIX.put("stationAssembler", "tooltip.advancedrocketry.stationassembler"); + + // Deployable Rocket Assembler (unmanned vehicle) + KEY_BY_ID.put("advancedrocketry:deployablerocketbuilder", "tooltip.advancedrocketry.deployablerocketassembler"); + KEY_BY_SUFFIX.put("deployablerocketbuilder", "tooltip.advancedrocketry.deployablerocketassembler"); + KEY_BY_SUFFIX.put("deployableRocketAssembler", "tooltip.advancedrocketry.deployablerocketassembler"); + + // ---- Fuel Tanks ---- + // Monopropellant Fuel Tank + KEY_BY_ID.put("advancedrocketry:fueltank", "tooltip.advancedrocketry.fueltank"); + KEY_BY_ID.put("advancedrocketry:fuelTank", "tooltip.advancedrocketry.fueltank"); + KEY_BY_SUFFIX.put("fuelTank", "tooltip.advancedrocketry.fueltank"); + + // Bipropellant Fuel Tank + KEY_BY_ID.put("advancedrocketry:bipropellantfueltank", "tooltip.advancedrocketry.bipropfueltank"); + KEY_BY_SUFFIX.put("bipropellantfueltank", "tooltip.advancedrocketry.bipropfueltank"); + + // Oxidizer Fuel Tank + KEY_BY_ID.put("advancedrocketry:oxidizerfueltank", "tooltip.advancedrocketry.oxidizerfueltank"); + KEY_BY_SUFFIX.put("oxidizerfueltank", "tooltip.advancedrocketry.oxidizerfueltank"); + + // Nuclear Fuel Tank + KEY_BY_ID.put("advancedrocketry:nuclearfueltank", "tooltip.advancedrocketry.nuclearfueltank"); + KEY_BY_SUFFIX.put("nuclearfueltank", "tooltip.advancedrocketry.nuclearfueltank"); + + // Monoprop tank + ARGS_BY_BASEKEY.put("tooltip.advancedrocketry.fueltank", + s -> new Object[]{ listFluidsFor(FuelType.LIQUID_MONOPROPELLANT, 6) }); + // Biprop fuel tank + ARGS_BY_BASEKEY.put("tooltip.advancedrocketry.bipropfueltank", + s -> new Object[]{ listFluidsFor(FuelType.LIQUID_BIPROPELLANT, 6) }); + // Oxidizer tank + ARGS_BY_BASEKEY.put("tooltip.advancedrocketry.oxidizerfueltank", + s -> new Object[]{ listFluidsFor(FuelType.LIQUID_OXIDIZER, 6) }); + // Nuclear working fluid tank + ARGS_BY_BASEKEY.put("tooltip.advancedrocketry.nuclearfueltank", + s -> new Object[]{ listFluidsFor(FuelType.NUCLEAR_WORKING_FLUID, 6) }); + + // Example for adding more items later (no code changes beyond these lines): + // KEY_BY_ID.put("advancedrocketry:carbonscrubbercartridge", "tooltip.advancedrocketry.scrubbercart"); + // KEY_BY_SUFFIX.put("carbonScrubberCartridge", "tooltip.advancedrocketry.scrubbercart"); + // ARGS_BY_BASEKEY.put("tooltip.advancedrocketry.scrubbercart", s -> new Object[]{ Math.max(0, s.getMaxDamage() - s.getItemDamage()) }); + } + + @SubscribeEvent + public static void onModels(ModelRegistryEvent e) { /* no-op */ } + + @SubscribeEvent + public static void onTooltip(ItemTooltipEvent e) { + final ItemStack stack = e.getItemStack(); + if (stack.isEmpty()) return; + + final List tooltip = e.getToolTip(); + + // Insert before the advanced "modid:item" line when advanced tooltips are on + final int insertAt = (e.getFlags().isAdvanced() && tooltip.size() > 1) + ? tooltip.size() - 1 + : tooltip.size(); + + final Item item = stack.getItem(); + @Nullable final ResourceLocation id = item.getRegistryName(); + String baseKey = null; + + if (id != null) { + java.util.function.Function res = KEY_RESOLVER_BY_ID.get(id.toString()); + if (res != null) { + baseKey = res.apply(stack); // e.g., tooltip.advancedrocketry.itemupgrade.3 + } + if (baseKey == null) { + baseKey = KEY_BY_ID.get(id.toString()); + } + } + + // Fallback: tail of unlocalized name (1.12 style) + if (baseKey == null) { + final String transKey = item.getUnlocalizedName(stack); + final int dot = transKey.lastIndexOf('.'); + if (dot > 0) { + final String tail = transKey.substring(dot + 1); + baseKey = KEY_BY_SUFFIX.get(tail); + } + } + + if (baseKey != null) { + renderShiftAlt(stack, tooltip, baseKey, insertAt); + } + } + + // ----- Generic renderer for base/shift/alt blocks ----- + @SideOnly(Side.CLIENT) + public static void renderShiftAlt(ItemStack s, List t, String baseKey, int idx) { + final ArgProvider ap = ARGS_BY_BASEKEY.get(baseKey); + final boolean hasShift = I18n.hasKey(baseKey + ".shift.1"); + final boolean hasAlt = I18n.hasKey(baseKey + ".alt.1") || ap != null; + + // Base block: base, base.1, base.2, ... + for (int i = 0; i <= 8; i++) { + final String k = (i == 0) ? baseKey : (baseKey + "." + i); + if (!I18n.hasKey(k)) { + if (i == 0) continue; // no bare base line; try .1 anyway + break; // stop when sequence ends + } + t.add(idx++, TextFormatting.GRAY + (ap != null ? I18n.format(k, ap.get(s)) : I18n.format(k))); + } + + // Shift block (shift.1..N) + if (hasShift) { + if (GuiScreen.isShiftKeyDown()) { + for (int i = 1; i <= 8; i++) { + final String k = baseKey + ".shift." + i; + if (!I18n.hasKey(k)) break; + t.add(idx++, TextFormatting.GRAY + (ap != null ? I18n.format(k, ap.get(s)) : I18n.format(k))); + } + } else if (I18n.hasKey("tooltip.advancedrocketry.hold_shift")) { + t.add(idx++, TextFormatting.DARK_GRAY.toString() + TextFormatting.ITALIC + + I18n.format("tooltip.advancedrocketry.hold_shift")); + } + } + + // Alt block (alt.1..N) + if (hasAlt) { + if (isAltDown()) { + for (int i = 1; i <= 8; i++) { + final String k = baseKey + ".alt." + i; + if (!I18n.hasKey(k)) break; + t.add(idx++, TextFormatting.GRAY + (ap != null ? I18n.format(k, ap.get(s)) : I18n.format(k))); + } + } else if (I18n.hasKey("tooltip.advancedrocketry.hold_alt")) { + t.add(idx++, TextFormatting.DARK_GRAY.toString() + TextFormatting.ITALIC + + I18n.format("tooltip.advancedrocketry.hold_alt")); + } + } + } + + + + // ----- Helpers ----- + + @SideOnly(Side.CLIENT) + private static String listFluidsFor(FuelType type, int max) { + java.util.List names = new java.util.ArrayList<>(); + for (net.minecraftforge.fluids.Fluid f : FluidRegistry.getRegisteredFluids().values()) { + try { + if (FuelRegistry.instance.isFuel(type, f)) { + // Localized name (1 bucket) + names.add(new FluidStack(f, 1000).getLocalizedName()); + if (names.size() >= max) break; + } + } catch (Throwable t) { + // be defensive against any odd registry states + } + } + if (names.isEmpty()) return I18n.hasKey("tooltip.advancedrocketry.none") ? I18n.format("tooltip.advancedrocketry.none") : "None"; + // if there are more than max, add an ellipsis + int total = 0; + for (net.minecraftforge.fluids.Fluid f : FluidRegistry.getRegisteredFluids().values()) + if (FuelRegistry.instance.isFuel(type, f)) total++; + String s = String.join(", ", names); + return (total > names.size()) ? (s + ", …") : s; + } + + private static int addIfPresentGray(List t, String key, int idx) { + if (I18n.hasKey(key)) { + t.add(idx, TextFormatting.GRAY + I18n.format(key)); + return idx + 1; + } + return idx; + } + + private static int addIfPresentDarkGray(List t, String key, int idx) { + if (I18n.hasKey(key)) { + t.add(idx, TextFormatting.DARK_GRAY + I18n.format(key)); + return idx + 1; + } + return idx; + } + + private static int addIfPresentDarkGrayFmt(List t, String key, int idx, Object... args) { + if (I18n.hasKey(key)) { + t.add(idx, TextFormatting.DARK_GRAY + I18n.format(key, args)); + return idx + 1; + } + return idx; + } + + @SideOnly(Side.CLIENT) + public static int computeInsertIndex(List tooltip, boolean advanced) { + return (advanced && tooltip.size() > 1) ? tooltip.size() - 1 : tooltip.size(); + } + + @SideOnly(Side.CLIENT) + public static boolean isAltDown() { + return Keyboard.isKeyDown(Keyboard.KEY_LMENU) || Keyboard.isKeyDown(Keyboard.KEY_RMENU); + } +} diff --git a/src/main/java/zmaster587/advancedRocketry/client/render/planet/RenderAsteroidSky.java b/src/main/java/zmaster587/advancedRocketry/client/render/planet/RenderAsteroidSky.java index 75bac2b36..ba7c44ef2 100644 --- a/src/main/java/zmaster587/advancedRocketry/client/render/planet/RenderAsteroidSky.java +++ b/src/main/java/zmaster587/advancedRocketry/client/render/planet/RenderAsteroidSky.java @@ -5,7 +5,6 @@ import net.minecraft.client.renderer.*; import net.minecraft.client.renderer.GlStateManager.DestFactor; import net.minecraft.client.renderer.GlStateManager.SourceFactor; -import net.minecraft.client.renderer.entity.Render; import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.util.EnumFacing; import net.minecraft.util.ResourceLocation; @@ -19,54 +18,70 @@ import zmaster587.advancedRocketry.api.dimension.solar.StellarBody; import zmaster587.advancedRocketry.dimension.DimensionManager; import zmaster587.advancedRocketry.dimension.DimensionProperties; -import zmaster587.advancedRocketry.event.RocketEventHandler; import zmaster587.advancedRocketry.inventory.TextureResources; import zmaster587.advancedRocketry.stations.SpaceObjectManager; import zmaster587.advancedRocketry.stations.SpaceStationObject; import zmaster587.advancedRocketry.util.AstronomicalBodyHelper; import zmaster587.libVulpes.util.Vector3F; -import java.util.LinkedList; +import java.util.ArrayList; import java.util.List; import java.util.Random; public class RenderAsteroidSky extends IRenderHandler { - + // === Textures === public static final ResourceLocation asteroid1 = new ResourceLocation("advancedRocketry:textures/planets/asteroid_a.png"); public static final ResourceLocation asteroid2 = new ResourceLocation("advancedRocketry:textures/planets/asteroid_b.png"); public static final ResourceLocation asteroid3 = new ResourceLocation("advancedRocketry:textures/planets/asteroid_c.png"); - ResourceLocation currentlyBoundTex = null; - float celestialAngle; - Vector3F axis; - Minecraft mc = Minecraft.getMinecraft(); - private int starGLCallList; - private int glSkyList; - private int glSkyList2; - private int glSkyList3; - - //Mostly vanilla code - //TODO: make usable on other planets + + // Per-frame texture bind cache (local to this renderer) + private ResourceLocation boundTex = null; + + // Runtime / state + private float celestialAngle; + private final Vector3F axis; + private final Minecraft mc = Minecraft.getMinecraft(); + + // Display lists + private final int starGLCallList; + private final int glSkyList; + private final int glSkyList2; + private final int glSkyList3; + + // Reused scratch buffers to reduce GC + private final List childrenBuf = new ArrayList<>(8); + private final float[] shadowColorTmp = new float[3]; + + // Helpers for ring/black-hole math + private static float xrotangle = 0; // for ring rotation (kept exactly as before) + private static final float[] skycolor = {0,0,0}; // for black hole rendering (same usage as before) + private static double currentplanetphi = 0; // ring/disk angle (same) + + // === ctor === public RenderAsteroidSky() { axis = new Vector3F<>(1f, 0f, 0f); + // Build display lists once (same seeds/geometry as original) this.starGLCallList = GLAllocation.generateDisplayLists(4); + GL11.glPushMatrix(); GL11.glNewList(this.starGLCallList, GL11.GL_COMPILE); - this.renderStars(); + this.renderStars(); // stars list GL11.glEndList(); GL11.glPopMatrix(); + BufferBuilder buffer = Tessellator.getInstance().getBuffer(); + + // Sky dome slice 1 this.glSkyList = this.starGLCallList + 1; GL11.glNewList(this.glSkyList, GL11.GL_COMPILE); byte b2 = 64; int i = 256 / b2 + 2; float f = 16.0F; - int j; - int k; - for (j = -b2 * i; j <= b2 * i; j += b2) { - for (k = -b2 * i; k <= b2 * i; k += b2) { + for (int j = -b2 * i; j <= b2 * i; j += b2) { + for (int k = -b2 * i; k <= b2 * i; k += b2) { buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION); buffer.pos(j, f, k).endVertex(); buffer.pos(j + b2, f, k).endVertex(); @@ -75,35 +90,43 @@ public RenderAsteroidSky() { Tessellator.getInstance().draw(); } } - GL11.glEndList(); + + // Sky dome slice 2 this.glSkyList2 = this.starGLCallList + 2; GL11.glNewList(this.glSkyList2, GL11.GL_COMPILE); f = -16.0F; buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION); - - for (j = -b2 * i; j <= b2 * i; j += b2) { - for (k = -b2 * i; k <= b2 * i; k += b2) { + for (int j = -b2 * i; j <= b2 * i; j += b2) { + for (int k = -b2 * i; k <= b2 * i; k += b2) { buffer.pos(j, f, k).endVertex(); buffer.pos(j + b2, f, k).endVertex(); buffer.pos(j + b2, f, k + b2).endVertex(); buffer.pos(j, f, k + b2).endVertex(); } } - Tessellator.getInstance().draw(); GL11.glEndList(); + // Asteroids list this.glSkyList3 = this.starGLCallList + 3; GL11.glPushMatrix(); GL11.glNewList(this.glSkyList3, GL11.GL_COMPILE); - renderAsteroids(); + renderAsteroids(); // geometry baked with fixed seed matching original GL11.glEndList(); GL11.glPopMatrix(); } + // Efficient texture binder (skip redundant binds per frame) + private void bind(ResourceLocation tex) { + if (tex != null && tex != boundTex) { + mc.renderEngine.bindTexture(tex); + boundTex = tex; + } + } + private void renderAsteroids() { - Random random = new Random(10843L); + Random random = new Random(10843L); // same seed => identical layout to original BufferBuilder buffer = Tessellator.getInstance().getBuffer(); buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR); @@ -132,10 +155,9 @@ private void renderAsteroids() { double d15 = Math.sin(d14); double d16 = Math.cos(d14); - float r, g, b; - r = random.nextFloat() * 0.05f + .95f; - g = random.nextFloat() * 0.1f + .9f; - b = random.nextFloat() * 0.1f + .9f; + float r = random.nextFloat() * 0.05f + .95f; + float g = random.nextFloat() * 0.1f + .9f; + float b = random.nextFloat() * 0.1f + .9f; for (int j = 0; j < 4; ++j) { double d17 = 0.0D; @@ -151,13 +173,11 @@ private void renderAsteroids() { } } } - Tessellator.getInstance().draw(); - //buffer.finishDrawing(); } private void renderStars() { - Random random = new Random(10842L); + Random random = new Random(10842L); // same seed => identical layout to original BufferBuilder buffer = Tessellator.getInstance().getBuffer(); buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION); @@ -200,50 +220,38 @@ private void renderStars() { } } } - Tessellator.getInstance().draw(); - //buffer.finishDrawing(); } - private static float xrotangle = 0; // used for ring rotation because I don't want to bother changing the definitions of methods. - private static float[] skycolor = {0,0,0}; // used for black hole rendering - same reason as above - private static double currentplanetphi = 0; // used for calculating ring/disk angle - @Override public void render(float partialTicks, WorldClient world, Minecraft mc) { + // per-frame texture bind cache reset + boundTex = null; - - //TODO: properly handle this + // === Gather properties (unchanged logic) === float atmosphere; int solarOrbitalDistance, planetOrbitalDistance = 0; double myPhi = 0, myTheta = 0, myPrevOrbitalTheta = 0, myRotationalPhi = 0; - boolean hasAtmosphere = false, isMoon; - float[] shadowColorMultiplier = {0f, 0f, 0f}; - float[] parentAtmColor = new float[]{1f, 1f, 1f}; + boolean isMoon; + // shadowColorTmp reused; values set below float[] parentRingColor = new float[]{1f, 1f, 1f}; float[] ringColor = new float[]{1f, 1f, 1f}; float sunSize = 1.0f; - float starSeparation = 0f; boolean isWarp = false; - boolean isGasGiant = false; boolean hasRings = false; - boolean parentPlanetHasDecorator = true; boolean parentHasRings = false; boolean parentHasATM = false; DimensionProperties parentProperties = null; DimensionProperties properties; EnumFacing travelDirection = null; - ResourceLocation parentPlanetIcon = null; List children; StellarBody primaryStar; celestialAngle = mc.world.getCelestialAngle(partialTicks); Vec3d sunColor; - if (mc.world.provider instanceof IPlanetaryProvider) { IPlanetaryProvider planetaryProvider = (IPlanetaryProvider) mc.world.provider; - properties = (DimensionProperties) planetaryProvider.getDimensionProperties(mc.player.getPosition()); atmosphere = planetaryProvider.getAtmosphereDensityFromHeight(mc.getRenderViewEntity().posY, mc.player.getPosition()); @@ -259,15 +267,16 @@ public void render(float partialTicks, WorldClient world, Minecraft mc) { hasRings = properties.hasRings(); ringColor = properties.ringColor; - children = new LinkedList<>(); + childrenBuf.clear(); for (Integer i : properties.getChildPlanets()) { - children.add(DimensionManager.getInstance().getDimensionProperties(i)); + childrenBuf.add(DimensionManager.getInstance().getDimensionProperties(i)); } + children = childrenBuf; solarOrbitalDistance = properties.getSolarOrbitalDistance(); - - if (isMoon = properties.isMoon()) { + isMoon = properties.isMoon(); + if (isMoon) { parentProperties = properties.getParentProperties(); planetOrbitalDistance = properties.getParentOrbitalDistance(); parentHasRings = parentProperties.hasRings; @@ -277,22 +286,22 @@ public void render(float partialTicks, WorldClient world, Minecraft mc) { sunColor = planetaryProvider.getSunColor(mc.player.getPosition()); primaryStar = properties.getStar(); if (primaryStar != null) { - sunSize = properties.getStar().getSize(); - } else + sunSize = primaryStar.getSize(); + } else { primaryStar = DimensionManager.getInstance().getStar(0); + } + if (world.provider.getDimension() == ARConfiguration.getCurrentConfig().spaceDimId) { isWarp = properties.getParentPlanet() == SpaceObjectManager.WARPDIMID; if (isWarp) { SpaceStationObject station = (SpaceStationObject) SpaceObjectManager.getSpaceManager().getSpaceStationFromBlockCoords(mc.player.getPosition()); - travelDirection = station.getForwardDirection(); + if (station != null) travelDirection = station.getForwardDirection(); } } - } - else if (DimensionManager.getInstance().isDimensionCreated(mc.world.provider.getDimension())) { - + } else if (DimensionManager.getInstance().isDimensionCreated(mc.world.provider.getDimension())) { properties = DimensionManager.getInstance().getDimensionProperties(mc.world.provider.getDimension()); - atmosphere = properties.getAtmosphereDensityAtHeight(mc.getRenderViewEntity().posY);//planetaryProvider.getAtmosphereDensityFromHeight(mc.getRenderViewEntity().posY, mc.player.getPosition()); + atmosphere = properties.getAtmosphereDensityAtHeight(mc.getRenderViewEntity().posY); EnumFacing dir = getRotationAxis(properties, mc.player.getPosition()); axis.x = (float) dir.getFrontOffsetX(); axis.y = (float) dir.getFrontOffsetY(); @@ -305,15 +314,16 @@ else if (DimensionManager.getInstance().isDimensionCreated(mc.world.provider.get hasRings = properties.hasRings(); ringColor = properties.ringColor; - children = new LinkedList<>(); + childrenBuf.clear(); for (Integer i : properties.getChildPlanets()) { - children.add(DimensionManager.getInstance().getDimensionProperties(i)); + childrenBuf.add(DimensionManager.getInstance().getDimensionProperties(i)); } + children = childrenBuf; solarOrbitalDistance = properties.getSolarOrbitalDistance(); - - if (isMoon = properties.isMoon()) { + isMoon = properties.isMoon(); + if (isMoon) { parentProperties = properties.getParentProperties(); planetOrbitalDistance = properties.getParentOrbitalDistance(); parentHasRings = parentProperties.hasRings; @@ -322,22 +332,25 @@ else if (DimensionManager.getInstance().isDimensionCreated(mc.world.provider.get } float[] sunColorFloat = properties.getSunColor(); - sunColor = new Vec3d(sunColorFloat[0], sunColorFloat[1], sunColorFloat[2]);//planetaryProvider.getSunColor(mc.player.getPosition()); + sunColor = new Vec3d(sunColorFloat[0], sunColorFloat[1], sunColorFloat[2]); primaryStar = properties.getStar(); if (primaryStar != null) { - sunSize = properties.getStar().getSize(); - } else + sunSize = primaryStar.getSize(); + } else { primaryStar = DimensionManager.getInstance().getStar(0); + } + if (world.provider.getDimension() == ARConfiguration.getCurrentConfig().spaceDimId) { isWarp = properties.getParentPlanet() == SpaceObjectManager.WARPDIMID; if (isWarp) { SpaceStationObject station = (SpaceStationObject) SpaceObjectManager.getSpaceManager().getSpaceStationFromBlockCoords(mc.player.getPosition()); - travelDirection = station.getForwardDirection(); + if (station != null) travelDirection = station.getForwardDirection(); } } - } - else { - children = new LinkedList<>(); + } else { + // No planet provider and dimension not registered: fall back to overworld props (exactly as before) + childrenBuf.clear(); + children = childrenBuf; isMoon = false; atmosphere = DimensionManager.overworldProperties.getAtmosphereDensityAtHeight(mc.getRenderViewEntity().posY); solarOrbitalDistance = DimensionManager.overworldProperties.orbitalDist; @@ -348,6 +361,7 @@ else if (DimensionManager.getInstance().isDimensionCreated(mc.world.provider.get currentplanetphi = myPhi; + // === Sky color & base dome === GlStateManager.disableTexture2D(); Vec3d vec3 = Minecraft.getMinecraft().world.getSkyColor(this.mc.getRenderViewEntity(), partialTicks); float f1 = (float) vec3.x; @@ -359,23 +373,17 @@ else if (DimensionManager.getInstance().isDimensionCreated(mc.world.provider.get float f4 = (f1 * 30.0F + f2 * 59.0F + f3 * 11.0F) / 100.0F; float f5 = (f1 * 30.0F + f2 * 70.0F) / 100.0F; f6 = (f1 * 30.0F + f3 * 70.0F) / 100.0F; - f1 = f4; - f2 = f5; - f3 = f6; + f1 = f4; f2 = f5; f3 = f6; } - //Simulate atmospheric thickness, vaugely - //This is done like this to prevent problems with superbright atmospheres on low-atmosphere planets - //Plus you couldn't see stars during the day anyway + // Atmospheric brightness shaping (unchanged) int atmosphereInt = properties.getAtmosphereDensity(); -// System.out.println("before:"+f1+":"+f2+":"+f3); f1 = atmosphereInt < 1 ? 0 : (float) Math.pow(f1, Math.sqrt(Math.max(atmosphere, 0.0001))); f2 = atmosphereInt < 1 ? 0 : (float) Math.pow(f2, Math.sqrt(Math.max(atmosphere, 0.0001))); f3 = atmosphereInt < 1 ? 0 : (float) Math.pow(f3, Math.sqrt(Math.max(atmosphere, 0.0001))); - - f1*=Math.min(1,atmosphere); - f2*=Math.min(1,atmosphere); - f3*=Math.min(1,atmosphere); + f1 *= Math.min(1, atmosphere); + f2 *= Math.min(1, atmosphere); + f3 *= Math.min(1, atmosphere); skycolor[0] = f1; skycolor[1] = f2; @@ -391,12 +399,10 @@ else if (DimensionManager.getInstance().isDimensionCreated(mc.world.provider.get GL11.glCallList(this.glSkyList); GlStateManager.disableFog(); GlStateManager.disableAlpha(); - RenderHelper.disableStandardItemLighting(); + net.minecraft.client.renderer.RenderHelper.disableStandardItemLighting(); + float[] afloat = mc.world.provider.calcSunriseSunsetColors(celestialAngle, partialTicks); - float f7; - float f8; - float f9; - float f10; + float f7, f8, f9, f10; if (afloat != null) { GlStateManager.disableTexture2D(); @@ -406,7 +412,6 @@ else if (DimensionManager.getInstance().isDimensionCreated(mc.world.provider.get GL11.glRotatef(MathHelper.sin(mc.world.getCelestialAngleRadians(partialTicks)) < 0.0F ? 180.0F : 0.0F, 0.0F, 0.0F, 1.0F); GL11.glRotated(90.0F - myRotationalPhi, 0.0F, 0.0F, 1.0F); - //Sim atmospheric thickness f6 = afloat[0]; f7 = afloat[1]; f8 = afloat[2]; @@ -416,9 +421,7 @@ else if (DimensionManager.getInstance().isDimensionCreated(mc.world.provider.get f9 = (f6 * 30.0F + f7 * 59.0F + f8 * 11.0F) / 100.0F; f10 = (f6 * 30.0F + f7 * 70.0F) / 100.0F; f11 = (f6 * 30.0F + f8 * 70.0F) / 100.0F; - f6 = f9; - f7 = f10; - f8 = f11; + f6 = f9; f7 = f10; f8 = f11; } buffer.begin(GL11.GL_TRIANGLE_FAN, DefaultVertexFormats.POSITION_COLOR); @@ -427,50 +430,49 @@ else if (DimensionManager.getInstance().isDimensionCreated(mc.world.provider.get for (int j = 0; j <= b0; ++j) { f11 = (float) j * (float) Math.PI * 2.0F / (float) b0; - float f12 = MathHelper.sin(f11); - float f13 = MathHelper.cos(f11); - buffer.pos(f12 * 120.0F, f13 * 120.0F, -f13 * 40.0F * afloat[3]).color(afloat[0], afloat[1], afloat[2], 0.0F).endVertex(); + float sx = MathHelper.sin(f11); + float cx = MathHelper.cos(f11); + buffer.pos(sx * 120.0F, cx * 120.0F, -cx * 40.0F * afloat[3]).color(afloat[0], afloat[1], afloat[2], 0.0F).endVertex(); } - Tessellator.getInstance().draw(); GL11.glPopMatrix(); GlStateManager.shadeModel(GL11.GL_FLAT); } - shadowColorMultiplier = new float[]{f1, f2, f3}; + + // shadow color multiplier (reused array) + shadowColorTmp[0] = f1; + shadowColorTmp[1] = f2; + shadowColorTmp[2] = f3; GlStateManager.enableTexture2D(); GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE); GL11.glPushMatrix(); + // rain alpha handling + if (atmosphere > 0) f6 = 1.0F - (mc.world.getRainStrength(partialTicks) * (atmosphere / 100f)); + else f6 = 1f; - if (atmosphere > 0) - f6 = 1.0F - (mc.world.getRainStrength(partialTicks) * (atmosphere / 100f)); - else - f6 = 1f; - - f7 = 0.0F; - f8 = 0.0F; - f9 = 0.0F; + f7 = 0.0F; f8 = 0.0F; f9 = 0.0F; GlStateManager.color(1.0F, 1.0F, 1.0F, f6); GL11.glTranslatef(f7, f8, f9); GL11.glRotatef(-90.0F, 0.0F, 1.0F, 0.0F); - float multiplier = (2 - atmosphere) / 2f;//atmosphere > 1 ? (2-atmosphere) : 1f; - if (mc.world.isRainingAt(mc.player.getPosition().add(0, 199, 0))) + float multiplier = (2 - atmosphere) / 2f; + if (mc.world.isRainingAt(mc.player.getPosition().add(0, 199, 0))) { multiplier *= 1 - mc.world.getRainStrength(partialTicks); + } GL11.glRotatef((float) myRotationalPhi, 0f, 1f, 0f); - //Draw Rings + // Rings (unchanged visuals) if (hasRings) { GL11.glPushMatrix(); GL11.glRotatef(90f, 0f, 1f, 0f); f10 = 100; double ringDist = 0; - //mc.renderEngine.bindTexture(DimensionProperties.planetRings); - mc.renderEngine.bindTexture(DimensionProperties.planetRingsNew); + bind(DimensionProperties.planetRingsNew); GL11.glRotated(70, 1, 0, 0); GL11.glTranslated(0, -10, 0); @@ -484,148 +486,120 @@ else if (DimensionManager.getInstance().isDimensionCreated(mc.world.provider.get Tessellator.getInstance().draw(); GL11.glPopMatrix(); - /* - GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA); - GL11.glPushMatrix(); - - GL11.glRotatef(90f, 0f, 1f, 0f); - GL11.glRotated(70, 1, 0, 0); - GL11.glRotatef(isWarp ? 0 : celestialAngle * 360.0F, 0, 1, 0); - GL11.glTranslated(0, -10, 0); - - mc.renderEngine.bindTexture(DimensionProperties.planetRingShadow); - GlStateManager.color(0f, 0f, 0f, multiplier); - buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); - buffer.pos(f10, ringDist, -f10).tex(1.0D, 0.0D).endVertex(); - buffer.pos(-f10, ringDist, -f10).tex(0.0D, 0.0D).endVertex(); - buffer.pos(-f10, ringDist, f10).tex(0.0D, 1.0D).endVertex(); - buffer.pos(f10, ringDist, f10).tex(1.0D, 1.0D).endVertex(); - Tessellator.getInstance().draw(); - GL11.glPopMatrix(); - */ - + // (Shadowed ring quad code left as in original) GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE); } GlStateManager.disableTexture2D(); - //This determines whether stars should come out regardless of thickness of atmosphere, as that is factored in later - // - it checks if the colors of the sky are so close to black that you'd see stars, or if the atmosphere is zero and so no one gives a damn - float f18 = mc.world.getStarBrightness(partialTicks) * f6;//((atmosphere == 0 || (f1 < 0.09 && f2 < 0.09 && f3 < 0.09)) ? 1 : 0);// - (atmosphere > 1 ? atmosphere - 1 : 0); + // Stars + float f18 = mc.world.getStarBrightness(partialTicks) * f6; + float starAlpha = 1 - ((1 - f18) * atmosphere); - float starAlpha = 1-((1-f18)*atmosphere); - //System.out.println(starAlpha+":"+f18+":"+atmosphere); + GlStateManager.disableDepth(); // stars always on top of sky Keep? makes asteroids glow :D - //if (f18 > 0.0F) { - if (true){ - GlStateManager.color(1, 1, 1, 1); - GL11.glPushMatrix(); - if (isWarp) { - for (int i = -3; i < 5; i++) { - GL11.glPushMatrix(); - double magnitude = i * -100 + (((System.currentTimeMillis()) + 50) % 2000) / 20f; - GL11.glTranslated(-travelDirection.getFrontOffsetZ() * magnitude, 0, travelDirection.getFrontOffsetX() * magnitude); - GL11.glCallList(this.starGLCallList); - GL11.glPopMatrix(); - } - //GL11.glTranslated(((System.currentTimeMillis()/10) + 50) % 100, 0, 0); - - } else { - GL11.glColor4f(1,1,1,starAlpha); + GlStateManager.color(1, 1, 1, 1); + GL11.glPushMatrix(); + if (isWarp && travelDirection != null) { + for (int n = -3; n < 5; n++) { + GL11.glPushMatrix(); + double magnitude = n * -100 + (((System.currentTimeMillis()) + 50) % 2000) / 20f; + GL11.glTranslated(-travelDirection.getFrontOffsetZ() * magnitude, 0, travelDirection.getFrontOffsetX() * magnitude); GL11.glCallList(this.starGLCallList); - //Extra stars for low ATM - if (atmosphere < 0.5) { - GL11.glColor4f(1,1,1,starAlpha/2); - GL11.glPushMatrix(); - GL11.glRotatef(-90, 0, 1, 0); - GL11.glCallList(this.starGLCallList); - GL11.glPopMatrix(); - } - if (atmosphere < 0.25) { - GL11.glColor4f(1,1,1,starAlpha/4); - GL11.glPushMatrix(); - GL11.glRotatef(90, 0, 1, 0); - GL11.glCallList(this.starGLCallList); - GL11.glPopMatrix(); - } - GlStateManager.color(1, 1, 1, 1); + GL11.glPopMatrix(); + } + } else { + GL11.glColor4f(1, 1, 1, starAlpha); + GL11.glCallList(this.starGLCallList); + if (atmosphere < 0.5f) { + GL11.glColor4f(1, 1, 1, starAlpha / 2f); + GL11.glPushMatrix(); + GL11.glRotatef(-90, 0, 1, 0); + GL11.glCallList(this.starGLCallList); + GL11.glPopMatrix(); } - GL11.glPopMatrix(); + if (atmosphere < 0.25f) { + GL11.glColor4f(1, 1, 1, starAlpha / 4f); + GL11.glPushMatrix(); + GL11.glRotatef(90, 0, 1, 0); + GL11.glCallList(this.starGLCallList); + GL11.glPopMatrix(); + } + GlStateManager.color(1, 1, 1, 1); } + GL11.glPopMatrix(); + GlStateManager.enableTexture2D(); + GlStateManager.enableDepth(); // keep? - mc.renderEngine.bindTexture(TextureResources.locationSunPng); + // Sun & sub-stars + bind(TextureResources.locationSunPng); - //--------------------------- Draw the suns -------------------- if (!isWarp) { if (parentProperties == null || !parentProperties.isStar()) { - xrotangle = ((float) (properties.getSolarTheta() * 180f / Math.PI) % 360f); // for black hole disk - //System.out.println(xrotangle+":"+properties.getSolarTheta()); + xrotangle = ((float) (properties.getSolarTheta() * 180f / Math.PI) % 360f); // used in black hole path drawStarAndSubStars(buffer, primaryStar, properties, solarOrbitalDistance, sunSize, sunColor, multiplier); xrotangle = 0; } } + // Moons/parent planets (unchanged logic) + if (DimensionProperties.AtmosphereTypes.SUPERHIGHPRESSURE.denserThan( + DimensionProperties.AtmosphereTypes.getAtmosphereTypeFromValue((int) (100 * atmosphere)))) { - //For these parts only render if the atmosphere is below a certain threshold (SHP atmosphere) - if (DimensionProperties.AtmosphereTypes.SUPERHIGHPRESSURE.denserThan(DimensionProperties.AtmosphereTypes.getAtmosphereTypeFromValue((int) (100 * atmosphere)))) { - //Render the parent planet - if (isMoon) { + if (isMoon && parentProperties != null) { GL11.glPushMatrix(); - //Do a whole lotta math to figure out where the parent planet is supposed to be - //That 0.3054325f is there because we need to do adjustments for some ^$%^$% reason and it's consistently off by 17.5 degrees - float planetPositionTheta = AstronomicalBodyHelper.getParentPlanetThetaFromMoon(properties.rotationalPeriod, properties.orbitalDist, parentProperties.gravitationalMultiplier, myTheta, properties.baseOrbitTheta); + float planetPositionTheta = AstronomicalBodyHelper.getParentPlanetThetaFromMoon( + properties.rotationalPeriod, properties.orbitalDist, parentProperties.gravitationalMultiplier, + myTheta, properties.baseOrbitTheta); GL11.glRotatef((float) myPhi, 0f, 0f, 1f); GL11.glRotatef(planetPositionTheta, 1f, 0f, 0f); - float phiAngle = (float) ((myPhi) * Math.PI / 180f); - - //Close enough approximation, I missed something but seems to off by no more than 30* - //Nobody will look + float phiAngle = (float) (myPhi * Math.PI / 180f); double x = MathHelper.sin(phiAngle) * MathHelper.cos((float) myTheta); double y = -MathHelper.sin((float) myTheta); double rotation = -Math.PI / 2f + Math.atan2(x, y) - (myTheta - Math.PI) * MathHelper.sin(phiAngle); if (parentHasRings) { - //Semihacky rotation stuff to keep rings synced to a different rotation than planet in the sky xrotangle = -planetPositionTheta + ((float) (myTheta * 180f / Math.PI) % 360f); - //System.out.println("r:"+xrotangle); } - shadowColorMultiplier = new float[]{f1, f2, f3}; + shadowColorTmp[0] = f1; + shadowColorTmp[1] = f2; + shadowColorTmp[2] = f3; - //System.out.println("draw moon (renderplanet"); - renderPlanet(buffer, parentProperties, planetOrbitalDistance, multiplier, rotation, false, parentHasRings, (float) Math.pow(parentProperties.getGravitationalMultiplier(), 0.4), shadowColorMultiplier, 1); + renderPlanet(buffer, parentProperties, planetOrbitalDistance, multiplier, rotation, false, parentHasRings, + (float) Math.pow(parentProperties.getGravitationalMultiplier(), 0.4), shadowColorTmp, 1); xrotangle = 0; GL11.glPopMatrix(); } - //This needs to exist specifically for init purposes - //The overworld literally breaks without it - shadowColorMultiplier[0] = 1.000001f * shadowColorMultiplier[0]; + // init quirk kept as-is + shadowColorTmp[0] = 1.000001f * shadowColorTmp[0]; for (DimensionProperties moons : children) { GL11.glPushMatrix(); float planetPositionTheta = (float) ((partialTicks * moons.orbitTheta + ((1 - partialTicks) * moons.prevOrbitalTheta)) * 180F / Math.PI); - float flippedPlanetPositionTheta = 360 - planetPositionTheta; GL11.glRotatef((float) moons.orbitalPhi, 0f, 0f, 1f); GL11.glRotated(planetPositionTheta, 1f, 0f, 0f); - //Close enough approximation, I missed something but seems to off by no more than 30* - //Nobody will look - float phiAngle = (float) ((moons.orbitalPhi) * Math.PI / 180f); + float phiAngle = (float) (moons.orbitalPhi * Math.PI / 180f); double x = -MathHelper.sin(phiAngle) * MathHelper.cos((float) moons.orbitTheta); double y = MathHelper.sin((float) moons.orbitTheta); double rotation = (-Math.PI / 2f + Math.atan2(x, y) - (moons.orbitTheta - Math.PI) * MathHelper.sin(phiAngle)) + Math.PI; - shadowColorMultiplier = new float[]{f1, f2, f3}; - renderPlanet(buffer, moons, moons.getParentOrbitalDistance(), multiplier, rotation, moons.hasAtmosphere(), moons.hasRings, (float) Math.pow(moons.gravitationalMultiplier, 0.4), shadowColorMultiplier, 1); + shadowColorTmp[0] = f1; + shadowColorTmp[1] = f2; + shadowColorTmp[2] = f3; + + renderPlanet(buffer, moons, moons.getParentOrbitalDistance(), multiplier, rotation, moons.hasAtmosphere(), + moons.hasRings, (float) Math.pow(moons.gravitationalMultiplier, 0.4), shadowColorTmp, 1); GL11.glPopMatrix(); } } @@ -635,35 +609,48 @@ else if (DimensionManager.getInstance().isDimensionCreated(mc.world.provider.get GlStateManager.disableBlend(); GlStateManager.enableAlpha(); - GL11.glPopMatrix(); + GL11.glPopMatrix(); // matching the big push before rings/stars/sun + // === Asteroid billboards === GlStateManager.enableTexture2D(); - - mc.renderEngine.bindTexture(asteroid1); GlStateManager.color(1, 1, 1); + GlStateManager.depthMask(false); + GlStateManager.enableBlend(); // additive star style keeps them in "sky" + GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE); + + GlStateManager.disableDepth(); // keep? + + bind(asteroid1); GL11.glCallList(this.glSkyList3); GL11.glPushMatrix(); GL11.glRotatef(90, 0.2f, 0.8f, 0); - mc.renderEngine.bindTexture(asteroid2); + bind(asteroid2); GL11.glCallList(this.glSkyList3); GL11.glRotatef(90, 0.2f, 0.8f, 0); - mc.renderEngine.bindTexture(asteroid3); + bind(asteroid3); GL11.glCallList(this.glSkyList3); GL11.glPopMatrix(); - GL11.glDepthMask(true); + GlStateManager.enableDepth(); // keep? - - //RocketEventHandler.onPostWorldRender(partialTicks); - //Fix player/items going transparent + // === PROPER GL STATE RESET === + // Keep depth mask on, but DO NOT clear depth here + GlStateManager.depthMask(true); + GlStateManager.disableBlend(); OpenGlHelper.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, 0, 0); + GlStateManager.shadeModel(GL11.GL_FLAT); + GlStateManager.enableTexture2D(); + GlStateManager.enableAlpha(); + GlStateManager.alphaFunc(GL11.GL_GREATER, 0.1f); + GlStateManager.color(1f, 1f, 1f, 1f); + GlStateManager.enableCull(); } + protected void drawStarAndSubStars(BufferBuilder buffer, StellarBody sun, DimensionProperties properties, int solarOrbitalDistance, float sunSize, Vec3d sunColor, float multiplier) { drawStar(buffer, sun, properties, solarOrbitalDistance, sunSize, sunColor, multiplier); List subStars = sun.getSubStars(); - if (subStars != null && !subStars.isEmpty()) { GL11.glPushMatrix(); float phaseInc = 360f / subStars.size(); @@ -674,12 +661,14 @@ protected void drawStarAndSubStars(BufferBuilder buffer, StellarBody sun, Dimens GL11.glRotatef(subStar.getStarSeparation() * AstronomicalBodyHelper.getBodySizeMultiplier(solarOrbitalDistance), 1, 0, 0); float[] color = subStar.getColor(); - drawStar(buffer, subStar, properties, solarOrbitalDistance, subStar.getSize(), new Vec3d(color[0], color[1], color[2]), multiplier); + drawStar(buffer, subStar, properties, solarOrbitalDistance, subStar.getSize(), + new Vec3d(color[0], color[1], color[2]), multiplier); GL11.glPopMatrix(); } GL11.glPopMatrix(); } } + protected ResourceLocation getTextureForPlanet(DimensionProperties properties) { return properties.getPlanetIcon(); } @@ -692,7 +681,6 @@ protected EnumFacing getRotationAxis(DimensionProperties properties, BlockPos po return EnumFacing.EAST; } - protected void renderPlanet(BufferBuilder buffer, DimensionProperties properties, float planetOrbitalDistance, float alphaMultiplier, double shadowAngle, boolean hasAtmosphere, boolean hasRing, float gravitationalMultiplier, float[] shadowColorMultiplier, float alphaMultiplier2) { renderPlanet2(buffer, properties, 20f * AstronomicalBodyHelper.getBodySizeMultiplier(planetOrbitalDistance) * gravitationalMultiplier, alphaMultiplier, shadowAngle, hasRing, shadowColorMultiplier, alphaMultiplier2); } @@ -704,9 +692,15 @@ protected void renderPlanet2(BufferBuilder buffer, DimensionProperties propertie boolean gasGiant = properties.isGasGiant(); float[] skyColor = properties.skyColor; float[] ringColor = properties.ringColor; - RenderPlanetarySky. renderPlanetPubHelper(buffer, icon, 0, 0, -20, size * 0.2f, alphaMultiplier, shadowAngle, hasAtmosphere, skyColor, ringColor, gasGiant, hasRing, properties.ringAngle, hasDecorators, shadowColorMultiplier, alphaMultiplier2); - } + // Keep external call identical + RenderPlanetarySky.renderPlanetPubHelper( + buffer, icon, 0, 0, -20, + size * 0.2f, alphaMultiplier, shadowAngle, + hasAtmosphere, skyColor, ringColor, gasGiant, hasRing, properties.ringAngle, + hasDecorators, shadowColorMultiplier, alphaMultiplier2 + ); + } protected Vector3F getRotateAxis() { return axis; @@ -718,12 +712,12 @@ public void renderSphere(double x, double y, double z, float radius, int slices, bufferBuilder.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); - for(int i = 0; i < slices; i++) { - for(int j = 0; j < stacks; j++) { - double firstLong = 2 * Math.PI * (i / (double)slices); - double secondLong = 2 * Math.PI * ((i + 1) / (double)slices); - double firstLat = Math.PI * (j / (double)stacks) - Math.PI / 2; - double secondLat = Math.PI * ((j + 1) / (double)stacks) - Math.PI / 2; + for (int i = 0; i < slices; i++) { + for (int j = 0; j < stacks; j++) { + double firstLong = 2 * Math.PI * (i / (double) slices); + double secondLong = 2 * Math.PI * ((i + 1) / (double) slices); + double firstLat = Math.PI * (j / (double) stacks) - Math.PI / 2; + double secondLat = Math.PI * ((j + 1) / (double) stacks) - Math.PI / 2; bufferBuilder.pos(x + radius * Math.cos(firstLat) * Math.cos(firstLong), y + radius * Math.sin(firstLat), z + radius * Math.cos(firstLat) * Math.sin(firstLong)).tex(0.0D, 0.0D).endVertex(); bufferBuilder.pos(x + radius * Math.cos(secondLat) * Math.cos(firstLong), y + radius * Math.sin(secondLat), z + radius * Math.cos(secondLat) * Math.sin(firstLong)).tex(1.0D, 0.0D).endVertex(); @@ -731,9 +725,9 @@ public void renderSphere(double x, double y, double z, float radius, int slices, bufferBuilder.pos(x + radius * Math.cos(firstLat) * Math.cos(secondLong), y + radius * Math.sin(firstLat), z + radius * Math.cos(firstLat) * Math.sin(secondLong)).tex(0.0D, 1.0D).endVertex(); } } - tessellator.draw(); } + protected void drawStar(BufferBuilder buffer, StellarBody sun, DimensionProperties properties, int solarOrbitalDistance, float sunSize, Vec3d sunColor, float multiplier) { if (sun != null && sun.isBlackHole()) { GlStateManager.enableAlpha(); @@ -742,86 +736,38 @@ protected void drawStar(BufferBuilder buffer, StellarBody sun, DimensionProperti GL11.glPushMatrix(); GL11.glTranslatef(0, 30, 0); - GL11.glDisable(GL11.GL_BLEND); GlStateManager.depthMask(true); + // Black hole sphere GL11.glPushMatrix(); GL11.glTranslatef(0, 100, 0); f10 = sunSize * 2f * AstronomicalBodyHelper.getBodySizeMultiplier(solarOrbitalDistance); - - mc.renderEngine.bindTexture(TextureResources.locationWhitePng); + bind(TextureResources.locationWhitePng); GlStateManager.disableCull(); - GlStateManager.color(skycolor[0], skycolor[1], skycolor[2]); // Set the color - renderSphere(0, 0, 0, f10, 16, 16); // Draw the sphere + GlStateManager.color(skycolor[0], skycolor[1], skycolor[2]); + renderSphere(0, 0, 0, f10, 16, 16); GlStateManager.enableCull(); GL11.glEnable(GL11.GL_BLEND); GL11.glDepthMask(false); GL11.glPopMatrix(); -/* - GL11.glPushMatrix(); - mc.renderEngine.bindTexture(TextureResources.locationBlackHole); - GL11.glTranslatef(0, 100, 0); - f10 = sunSize * 2f * AstronomicalBodyHelper.getBodySizeMultiplier(solarOrbitalDistance); - //float scale = 1 ; - //GL11.glRotatef(phase, 0, 1, 0); - //GL11.glScaled(scale, scale, scale); - GlStateManager.color((float) 1, (float) .5, (float) .4, 1f); - - buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); - //multiplier = 2; - buffer.pos(-f10, 0.0D, -f10).tex(0.0D, 0.0D).endVertex(); - buffer.pos(f10, 0.0D, -f10).tex(1.0D, 0.0D).endVertex(); - buffer.pos(f10, 0.0D, f10).tex(1.0D, 1.0D).endVertex(); - buffer.pos(-f10, 0.0D, f10).tex(0.0D, 1.0D).endVertex(); - Tessellator.getInstance().draw(); - GL11.glPopMatrix(); - - - GL11.glEnable(GL11.GL_BLEND); - GL11.glDepthMask(false); - - GL11.glPushMatrix(); - mc.renderEngine.bindTexture(TextureResources.locationBlackHoleBorder); - GL11.glTranslatef( 0, 99.8F, 0); - //GL11.glRotatef(phase, 0, 1, 0); - float scale = 1.1F; - GL11.glScaled(scale, scale, scale); - GlStateManager.color((float) 1, (float) .5, (float) .4, 1f); - buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); - //multiplier = 2; - buffer.pos(-f10, 0.0D, -f10).tex(0.0D, 0.0D).endVertex(); - buffer.pos(f10, 0.0D, -f10).tex(1.0D, 0.0D).endVertex(); - buffer.pos(f10, 0.0D, f10).tex(1.0D, 1.0D).endVertex(); - buffer.pos(-f10, 0.0D, f10).tex(0.0D, 1.0D).endVertex(); - Tessellator.getInstance().draw(); - GL11.glPopMatrix(); -*/ float diskangle = sun.diskAngle; - float m = -xrotangle; - while (m > 360) - m-=360; - while (m < 0) - m+=360; - //Render accretion disk - mc.renderEngine.bindTexture(TextureResources.locationAccretionDiskDense); - GlStateManager.depthMask(false); + while (m > 360) m -= 360; + while (m < 0) m += 360; - float speedMult = 5; + // Dense inner disk - ORIGINAL ROTATIONS + bind(TextureResources.locationAccretionDiskDense); + GlStateManager.depthMask(false); GlStateManager.disableCull(); - GL11.glPushMatrix(); GL11.glTranslatef(0, 100, 0); GL11.glRotatef(90, 0f, 1f, 0f); - //GL11.glRotatef(m, 1f, 0f, 0f); - //GL11.glRotatef(diskangle, 0, 0, 1); - //GL11.glRotatef(90, 1, 0, 0); - GL11.glRotatef((System.currentTimeMillis() % (int) (360 * 360 * speedMult)) / (360f * speedMult), 0, 1, 0); - - GlStateManager.color((float) 1, (float) .7, (float) .55, 1f); + // Original rotation with speedMult = 5 + GL11.glRotatef((System.currentTimeMillis() % (int) (360 * 360 * 5)) / (360f * 5), 0, 1, 0); + GlStateManager.color(1f, .7f, .55f, 1f); buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); f10 = sunSize * 6.5f * AstronomicalBodyHelper.getBodySizeMultiplier(solarOrbitalDistance); buffer.pos(-f10, 0.0D, -f10).tex(0.0D, 0.0D).endVertex(); @@ -831,23 +777,23 @@ protected void drawStar(BufferBuilder buffer, StellarBody sun, DimensionProperti Tessellator.getInstance().draw(); GL11.glPopMatrix(); - - mc.renderEngine.bindTexture(TextureResources.locationAccretionDisk); - + // Outer translucent disks - COMPLEX ORIGINAL LOGIC + bind(TextureResources.locationAccretionDisk); for (int i = 0; i < 3; i++) { - speedMult = ((0) * 1.01f + 1)/0.1F; + float speedMult = 10.0f; // ORIGINAL CALCULATION: ((0) * 1.01f + 1)/0.1F + + // First layer - 100.01f GL11.glPushMatrix(); GL11.glTranslatef(0, 100.01f, 0); + // RESTORE ALL ORIGINAL ROTATIONS: GL11.glRotatef((float) currentplanetphi, 0f, 1f, 0f); GL11.glRotatef(m, 1f, 0f, 0f); GL11.glRotatef(diskangle, 0, 0, 1); GL11.glRotatef((System.currentTimeMillis() % (int) (speedMult * 36000)) / (100f * speedMult), 0, 1, 0); - - // make every disks angle slightly different - GL11.glRotatef(120*i, 0, 1, 0); + GL11.glRotatef(120 * i, 0, 1, 0); GL11.glRotatef(0.5f, 1, 0, 0); - GlStateManager.color((float) 1, (float) .5, (float) .4, 0.3f); + GlStateManager.color(1f, .5f, .4f, 0.3f); buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); f10 = sunSize * 40f * AstronomicalBodyHelper.getBodySizeMultiplier(solarOrbitalDistance); buffer.pos(-f10, 0.0D, -f10).tex(0.0D, 0.0D).endVertex(); @@ -857,22 +803,19 @@ protected void drawStar(BufferBuilder buffer, StellarBody sun, DimensionProperti Tessellator.getInstance().draw(); GL11.glPopMatrix(); - + // Second layer - 100f GL11.glPushMatrix(); - GL11.glTranslatef(0, 100f, 0); GL11.glRotatef((float) currentplanetphi, 0f, 1f, 0f); GL11.glRotatef(m, 1f, 0f, 0f); GL11.glRotatef(diskangle, 0, 0, 1); GL11.glRotatef((System.currentTimeMillis() % (int) (speedMult * 360 * 50)) / (50f * speedMult), 0, 1, 0); - // make every disks angle slightly different - GL11.glRotatef(120*i, 0, 1, 0); + GL11.glRotatef(120 * i, 0, 1, 0); GL11.glRotatef(0.5f, 1, 0, 0); - GlStateManager.color((float) 0.8, (float) .7, (float) .4, 0.3f); + GlStateManager.color(0.8f, .7f, .4f, 0.3f); buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); f10 = sunSize * 30f * AstronomicalBodyHelper.getBodySizeMultiplier(solarOrbitalDistance); - //multiplier = 2; buffer.pos(-f10, 0.0D, -f10).tex(0.0D, 0.0D).endVertex(); buffer.pos(f10, 0.0D, -f10).tex(1.0D, 0.0D).endVertex(); buffer.pos(f10, 0.0D, f10).tex(1.0D, 1.0D).endVertex(); @@ -880,47 +823,41 @@ protected void drawStar(BufferBuilder buffer, StellarBody sun, DimensionProperti Tessellator.getInstance().draw(); GL11.glPopMatrix(); + // Third layer - 99.99f GL11.glPushMatrix(); - GL11.glTranslatef(0, 99.99f, 0); GL11.glRotatef((float) currentplanetphi, 0f, 1f, 0f); GL11.glRotatef(m, 1f, 0f, 0f); GL11.glRotatef(diskangle, 0, 0, 1); GL11.glRotatef((System.currentTimeMillis() % (int) (speedMult * 360 * 25)) / (25f * speedMult), 0, 1, 0); - // make every disks angle slightly different - GL11.glRotatef(120*i, 0, 1, 0); + GL11.glRotatef(120 * i, 0, 1, 0); GL11.glRotatef(0.5f, 1, 0, 0); - GlStateManager.color((float) 0.2, (float) .4, (float) 1, 0.3f); + GlStateManager.color(0.2f, .4f, 1f, 0.3f); buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); f10 = sunSize * 15f * AstronomicalBodyHelper.getBodySizeMultiplier(solarOrbitalDistance); - //multiplier = 2; buffer.pos(-f10, 0.0D, -f10).tex(0.0D, 0.0D).endVertex(); buffer.pos(f10, 0.0D, -f10).tex(1.0D, 0.0D).endVertex(); buffer.pos(f10, 0.0D, f10).tex(1.0D, 1.0D).endVertex(); buffer.pos(-f10, 0.0D, f10).tex(0.0D, 1.0D).endVertex(); Tessellator.getInstance().draw(); GL11.glPopMatrix(); - - - - } + // ORIGINAL DEPTH MANAGEMENT GlStateManager.depthMask(true); GL11.glClear(GL11.GL_DEPTH_BUFFER_BIT); GlStateManager.depthMask(false); + GL11.glPopMatrix(); GlStateManager.enableCull(); - - + //GlStateManager.depthMask(true); // keep ? } else { - mc.renderEngine.bindTexture(TextureResources.locationSunPng); - //Set sun color and distance + // Regular star (quad) path + bind(TextureResources.locationSunPng); GlStateManager.color((float) sunColor.x, (float) sunColor.y, (float) sunColor.z, Math.min((multiplier) * 2f, 1f)); buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); float f10 = sunSize * 15f * AstronomicalBodyHelper.getBodySizeMultiplier(solarOrbitalDistance); - //multiplier = 2; buffer.pos(-f10, 120.0D, -f10).tex(0.0D, 0.0D).endVertex(); buffer.pos(f10, 120.0D, -f10).tex(1.0D, 0.0D).endVertex(); buffer.pos(f10, 120.0D, f10).tex(1.0D, 1.0D).endVertex(); diff --git a/src/main/java/zmaster587/advancedRocketry/entity/EntityRocket.java b/src/main/java/zmaster587/advancedRocketry/entity/EntityRocket.java index 96a725916..4945bc0b7 100644 --- a/src/main/java/zmaster587/advancedRocketry/entity/EntityRocket.java +++ b/src/main/java/zmaster587/advancedRocketry/entity/EntityRocket.java @@ -25,6 +25,7 @@ import net.minecraft.util.SoundCategory; import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; import net.minecraft.util.text.TextComponentString; @@ -33,6 +34,9 @@ import net.minecraft.world.WorldServer; import net.minecraft.world.chunk.Chunk; import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.common.ForgeChunkManager; +import net.minecraftforge.common.ForgeChunkManager.Ticket; +import net.minecraftforge.common.ForgeChunkManager.Type; import net.minecraftforge.fluids.FluidRegistry; import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.FluidTank; @@ -90,6 +94,8 @@ import javax.annotation.Nullable; import java.util.*; + + public class EntityRocket extends EntityRocketBase implements INetworkEntity, IModularInventory, IProgressBar, IButtonInventory, ISelectionNotify, IPlanetDefiner { // set to 2 seconds because keyboard event is not sent to server @@ -124,6 +130,8 @@ public class EntityRocket extends EntityRocketBase implements INetworkEntity, IM protected ModulePlanetSelector container; boolean acceptedPacket = false; SpacePosition spacePosition; + //true if we have posted the landed event after loading from nbt + private transient boolean postedLandedAfterLoad = false; //true if the rocket is on decent private boolean isInOrbit; //True if the rocket isn't on the ground @@ -140,6 +148,18 @@ public class EntityRocket extends EntityRocketBase implements INetworkEntity, IM private int rcs_mode_counter = 0; //Used to most of the logic, determining if in RCS mode or not private boolean rcs_mode = false; + + // Preload ticket for destination chunks on launch event should be enough time to get a warm dimension + private Ticket destPreloadTicket = null; + private int destPreloadDim = Integer.MIN_VALUE; + private long destPreloadExpire = Long.MIN_VALUE; // world time when we auto-release + + // Only show an oxidizer bar when the rocket actually provides oxidizer capacity. + public boolean shouldShowOxBar() { + return getFuelCapacity(FuelRegistry.FuelType.LIQUID_OXIDIZER) > 0; + } + + public EntityRocket(World p_i1582_1_) { super(p_i1582_1_); @@ -209,6 +229,50 @@ private static IBlockState getDamagedBlock(IBlockState blockState) { return null; } + private void preloadDestinationChunks(int dimId, double x, double z, int radiusChunks, int holdSeconds) { + if (world.isRemote) return; + + // Clean any previous + releaseDestinationPreload(); + + MinecraftServer server = this.getServer(); + if (server == null) return; + + WorldServer target = server.getWorld(dimId); + if (target == null) return; // dimension not available + + // Request a NORMAL ticket in the DESTINATION world (not bound to this entity) + destPreloadTicket = ForgeChunkManager.requestTicket(AdvancedRocketry.instance, target, Type.NORMAL); + if (destPreloadTicket == null) { + AdvancedRocketry.logger.warn("[EntityRocket] Could not acquire destination preload ticket for dim {}", dimId); + return; + } + + int cx = ((int)Math.floor(x)) >> 4; + int cz = ((int)Math.floor(z)) >> 4; + for (int dx = -radiusChunks; dx <= radiusChunks; dx++) { + for (int dz = -radiusChunks; dz <= radiusChunks; dz++) { + ForgeChunkManager.forceChunk(destPreloadTicket, new ChunkPos(cx + dx, cz + dz)); + } + } + + destPreloadDim = dimId; + // use *server* time base; holdSeconds should be enough to cover ascent (~6s) + destPreloadExpire = world.getTotalWorldTime() + holdSeconds * 20L; + AdvancedRocketry.logger.debug("[EntityRocket] Preloaded 3x3 chunks at dim {} around {},{} for ~{}s", + dimId, (cx<<4), (cz<<4), holdSeconds); + } + + private void releaseDestinationPreload() { + if (destPreloadTicket != null) { + ForgeChunkManager.releaseTicket(destPreloadTicket); + destPreloadTicket = null; + destPreloadDim = Integer.MIN_VALUE; + destPreloadExpire = Long.MIN_VALUE; + } + } + + public void toggleRCS() { if (DimensionManager.getInstance().getDimensionProperties(this.world.provider.getDimension()).isAsteroid()) { rcs_mode = !rcs_mode; @@ -365,6 +429,20 @@ else if (!isInFlight()) private void setError(String error) { this.errorStr = error; this.lastErrorTime = this.world.getTotalWorldTime(); + + if (!world.isRemote) { + // notify riders only + for (Entity e : this.getPassengers()) { + if (e instanceof EntityPlayerMP) { + ((EntityPlayerMP) e).sendMessage(new TextComponentString(error)); + } + } + // post an event the monitor already consumes + MinecraftForge.EVENT_BUS.post(new RocketEvent.RocketAbortEvent(this, error)); + + // stop countdown if it was running + this.dataManager.set(LAUNCH_COUNTER, -1); + } } @Override @@ -670,6 +748,45 @@ protected boolean canFitPassenger(Entity passenger) { return this.getPassengers().size() < stats.getNumPassengerSeats(); } + + // Check if we have enough fuel to reach orbit from our current position + private boolean hasMissionFuelFor(int destDimId) { + if (!ARConfiguration.getCurrentConfig().rocketRequireFuel) return true; + + final FuelRegistry.FuelType main = getRocketFuelType(); + if (main == null) return false; // no usable tanks + + if (isInOrbit()) return true; // already at orbit + + if (stats.getThrust() <= stats.getWeight()) return false; + + final DimensionProperties src = DimensionManager.getInstance() + .getDimensionProperties(this.world.provider.getDimension()); + final float gSrc = Math.max(0.01f, src.getGravitationalMultiplier()); + final double a = Math.max(0.0001d, stats.getAcceleration(gSrc)); + final double h = Math.max(0.0, stats.orbitHeight - this.posY); + + long nTicks = (long)Math.ceil(Math.sqrt(2.0 * h / a)); + nTicks += 2L; // small safety buffer + if (nTicks <= 0) nTicks = 1; + + int mainRate = Math.max(1, getFuelConsumptionRate(main)); + long mainNeeded = nTicks * (long)mainRate; + long mainHave = getFuelAmount(main); + if (mainHave < mainNeeded) return false; + + if (main == FuelRegistry.FuelType.LIQUID_BIPROPELLANT) { + int oxRate = Math.max(1, getFuelConsumptionRate(FuelRegistry.FuelType.LIQUID_OXIDIZER)); + long oxNeeded = nTicks * (long)oxRate; + long oxHave = getFuelAmount(FuelRegistry.FuelType.LIQUID_OXIDIZER); + if (oxHave < oxNeeded) return false; + } + + // Descent currently does not burn fuel in your code path. + return true; + } + + /** * @param fluidStack the stack to check whether the rocket can fit * @return boolean on whether said fluid stack can fit into the rocket's internal fuel point storage @@ -690,6 +807,11 @@ public boolean canRocketFitFluid(FluidStack fluidStack) { if (stats.getOxidizerFluid().equals("null") && isCorrectFluid) stats.setOxidizerFluid(fluidStack.getFluid().getName()); return isCorrectFluid; + } else if (FuelRegistry.instance.isFuel(FuelType.NUCLEAR_WORKING_FLUID, fluidStack.getFluid())) { + boolean isCorrectFluid = stats.getWorkingFluid().equals("null") || fluidStack.getFluid() == FluidRegistry.getFluid(stats.getWorkingFluid()); + if (stats.getWorkingFluid().equals("null") && isCorrectFluid) + stats.setWorkingFluid(fluidStack.getFluid().getName()); + return isCorrectFluid; } return false; } @@ -968,7 +1090,15 @@ public void onUpdate() { super.onUpdate(); long deltaTime = world.getTotalWorldTime() - lastWorldTickTicked; lastWorldTickTicked = world.getTotalWorldTime(); - + if (!world.isRemote && !postedLandedAfterLoad && this.ticksExisted >= 5) { + // Consider "landed" = entity exists, NOT in flight, NOT in orbit + if (!isInFlight() && !isInOrbit()) { + net.minecraftforge.common.MinecraftForge.EVENT_BUS.post( + new zmaster587.advancedRocketry.api.RocketEvent.RocketLandedEvent(this) + ); + postedLandedAfterLoad = true; + } + } if (world.isRemote) { double ct = 50; @@ -1084,6 +1214,11 @@ else if (!getRCS() && rcs_mode_counter > 0) { entity.fallDistance = 0; } this.fallDistance = 0; + + // Auto-release destination preload after timeout + if (destPreloadTicket != null && world.getTotalWorldTime() >= destPreloadExpire) { + releaseDestinationPreload(); + } } // When flying around in space @@ -1288,6 +1423,7 @@ else if (distanceSq > this.spacePosition.world.getRenderSizePlanetView() * this. this.motionY = 0; this.setInFlight(false); this.setInOrbit(false); + releaseDestinationPreload(); } @@ -1837,21 +1973,52 @@ public void launch() { if (this.stats.getWeight() >= this.stats.getThrust()) { - allowLaunch = false; + setError(LibVulpes.proxy.getLocalizedString("error.rocket.tooHeavy")); + return; // hard stop; no silent fall-through } //Check to see what place we should be going to //This is bad but it works and is mostly intelligible so it's here for now stats.orbitHeight = (storage.getGuidanceComputer() == null) ? getEntryHeight(this.world.provider.getDimension()) : storage.getGuidanceComputer().getLaunchSequence(this.world.provider.getDimension(), this.getPosition()); - - + + // Enough fuel for the mission? + if (!hasMissionFuelFor(destinationDimId)) { + setError(LibVulpes.proxy.getLocalizedString("error.rocket.notEnoughMissionFuel")); + return; + } + //TODO: Clean this logic a bit? - if (allowLaunch || !stats.hasSeat() || ((DimensionManager.getInstance().isDimensionCreated(destinationDimId)) || destinationDimId == ARConfiguration.getCurrentConfig().spaceDimId || destinationDimId == 0)) { //Abort if destination is invalid + if (allowLaunch || !stats.hasSeat() || ((DimensionManager.getInstance().isDimensionCreated(destinationDimId)) || destinationDimId == ARConfiguration.getCurrentConfig().spaceDimId || destinationDimId == 0)) { setInFlight(true); Iterator connectedTiles = connectedInfrastructure.iterator(); MinecraftForge.EVENT_BUS.post(new RocketLaunchEvent(this)); + // ---- PRELOAD DESTINATION 3x3 (server only) ---- + if (!world.isRemote) { + boolean willTeleportAtAscent = + !(ARConfiguration.getCurrentConfig().experimentalSpaceFlight && storage.getGuidanceComputer().isEmpty()); + + // Only preload when we know we’ll teleport off this world soon + if (willTeleportAtAscent) { + int dimId = destinationDimId; + + boolean canLoad = + DimensionManager.getInstance().isDimensionCreated(dimId) || + dimId == ARConfiguration.getCurrentConfig().spaceDimId; + + if (canLoad) { + Vector3F destVec = (storage != null) ? storage.getDestinationCoordinates(dimId, true) : null; + double dx = (destVec != null) ? destVec.x : this.posX; + double dz = (destVec != null) ? destVec.z : this.posZ; + + preloadDestinationChunks(dimId, dx, dz, /*radiusChunks*/ 1, /*holdSeconds*/ 60); + } + } + } + // ----------------------------------------------- + + //Disconnect things linked to the rocket on liftoff while (connectedTiles.hasNext()) { @@ -1911,6 +2078,7 @@ public void deconstructRocket() { @Override public void setDead() { super.setDead(); + releaseDestinationPreload(); if (storage != null && storage.world.displayListIndex != -1) GLAllocation.deleteDisplayLists(storage.world.displayListIndex); @@ -2256,6 +2424,7 @@ else if (id == PacketType.RECIEVENBT.ordinal()) { this.turningDownforWhat = nbt.getBoolean("down"); } else if (id == PacketType.ABORTLAUNCH.ordinal()) { this.dataManager.set(LAUNCH_COUNTER, -1); + releaseDestinationPreload(); } else if (id == PacketType.SENDSPACEPOS.ordinal()) { this.spacePosition.readFromNBT(nbt); } else if (id >= STATION_LOC_OFFSET + BUTTON_ID_OFFSET) { @@ -2386,6 +2555,15 @@ public List getModules(int ID, EntityPlayer player) { //Fuel modules.add(new ModuleProgress(192, 7, 0, new ProgressBarImage(2, 173, 12, 71, 17, 6, 3, 69, 1, 1, EnumFacing.UP, TextureResources.rocketHud), this)); + // Conditional oxidizer bar + if (shouldShowOxBar()) { + // Add a second, distinct bar for oxidizer (reuse the monitoring station’s UVs) + modules.add(new ModuleProgress( + 198, 7, 6, // position offset to avoid overlap; ID=6 matches monitoring station semantics + new ProgressBarImage(2, 173, 12, 71, 17, 75, 3, 69, 1, 1, EnumFacing.UP, TextureResources.rocketHud), + this + )); + } //Add buttons @@ -2460,14 +2638,23 @@ public float getNormallizedProgress(int id) { case LIQUID_BIPROPELLANT: case LIQUID_MONOPROPELLANT: case NUCLEAR_WORKING_FLUID: - return getFuelAmount(fuelType) / (float) getFuelCapacity(fuelType); + int amt = getFuelAmount(fuelType); + int cap = getFuelCapacity(fuelType); + return (cap > 0) ? (amt / (float) cap) : 0f; } } + // oxidizer bar matches monitoring station’s ID=6 semantics + if (id == 6) { + int oxAmt = getFuelAmount(FuelType.LIQUID_OXIDIZER); + int oxCap = getFuelCapacity(FuelType.LIQUID_OXIDIZER); + return (oxCap > 0) ? (oxAmt / (float) oxCap) : 0f; + } - return 0; + return 0f; } + public double getRelativeHeightFraction() { return (posY - getTopBlock(getPosition()).getY()) / (getEntryHeight(dimension) - getTopBlock(getPosition()).getY()); } @@ -2483,12 +2670,25 @@ public void setProgress(int id, int progress) { @Override public int getProgress(int id) { + if (id == 0) { + FuelType ft = getRocketFuelType(); + return (ft != null) ? getFuelAmount(ft) : 0; + } else if (id == 6) { + return getFuelAmount(FuelType.LIQUID_OXIDIZER); + } return 0; } @Override public int getTotalProgress(int id) { - return 0; + if (id == 0) { + FuelType ft = getRocketFuelType(); + return (ft != null) ? getFuelCapacity(ft) : 1; // never 0 + } else if (id == 6) { + int cap = getFuelCapacity(FuelType.LIQUID_OXIDIZER); + return (cap > 0) ? cap : 1; // never 0 + } + return 1; } @Override diff --git a/src/main/java/zmaster587/advancedRocketry/entity/EntityStationDeployedRocket.java b/src/main/java/zmaster587/advancedRocketry/entity/EntityStationDeployedRocket.java index 228d593d5..831401cb6 100644 --- a/src/main/java/zmaster587/advancedRocketry/entity/EntityStationDeployedRocket.java +++ b/src/main/java/zmaster587/advancedRocketry/entity/EntityStationDeployedRocket.java @@ -26,14 +26,13 @@ import zmaster587.advancedRocketry.api.RocketEvent.RocketLaunchEvent; import zmaster587.advancedRocketry.api.RocketEvent.RocketPreLaunchEvent; import zmaster587.advancedRocketry.api.StatsRocket; -import zmaster587.advancedRocketry.api.atmosphere.AtmosphereRegister; +import zmaster587.advancedRocketry.api.fuel.FuelRegistry; import zmaster587.advancedRocketry.api.stations.ISpaceObject; import zmaster587.advancedRocketry.client.SoundRocketEngine; import zmaster587.advancedRocketry.dimension.DimensionManager; import zmaster587.advancedRocketry.dimension.DimensionProperties; import zmaster587.advancedRocketry.mission.MissionGasCollection; import zmaster587.advancedRocketry.network.PacketSatellite; -import zmaster587.advancedRocketry.network.PacketSatellitesUpdate; import zmaster587.advancedRocketry.stations.SpaceObjectManager; import zmaster587.advancedRocketry.util.AudioRegistry; import zmaster587.advancedRocketry.util.StorageChunk; @@ -62,7 +61,10 @@ public class EntityStationDeployedRocket extends EntityRocket { private ModuleText atmText; private short gasId; private Ticket ticket; - + private long plannedHarvestMb = 0L; // planned total mB to attempt this mission + private transient boolean postedLandedAfterLoad = false; + private transient boolean postedDeorbit = false; + public EntityStationDeployedRocket(World world) { super(world); launchDirection = EnumFacing.DOWN; @@ -121,8 +123,21 @@ public void launch() { setInFlight(true); return; } - if (getFuelAmount(getRocketFuelType()) < getFuelCapacity(getRocketFuelType())) - return; + + if (storage != null) { + storage.recalculateStats(this.stats); // keeps everything else in sync + } + + + FuelRegistry.FuelType rt = getRocketFuelType(); + if (rt != null && ARConfiguration.getCurrentConfig().rocketRequireFuel) { + if (getFuelAmount(rt) < getFuelCapacity(rt)) return; + + if (rt == FuelRegistry.FuelType.LIQUID_BIPROPELLANT) { + if (getFuelAmount(FuelRegistry.FuelType.LIQUID_OXIDIZER) + < getFuelCapacity(FuelRegistry.FuelType.LIQUID_OXIDIZER)) return; + } + } ISpaceObject spaceObj; if (world.provider.getDimension() == ARConfiguration.getCurrentConfig().spaceDimId && @@ -152,7 +167,15 @@ public void launch() { @Override public void onUpdate() { lastWorldTickTicked = world.getTotalWorldTime(); - + if (!world.isRemote && !postedLandedAfterLoad && this.ticksExisted >= 5) { + // Consider "landed" = entity exists, NOT in flight, NOT in orbit + if (!isInFlight() && !isInOrbit()) { + net.minecraftforge.common.MinecraftForge.EVENT_BUS.post( + new zmaster587.advancedRocketry.api.RocketEvent.RocketLandedEvent(this) + ); + postedLandedAfterLoad = true; + } + } if (this.ticksExisted == 20) { //problems with loading on other world then where the infrastructure was set? for (HashedBlockPosition temp : new LinkedList<>(infrastructureCoords)) { @@ -226,6 +249,13 @@ public void onUpdate() { //Returning if (isInOrbit()) { //For unmanned rockets + // Post deorbit once, as we start the return phase + if (!world.isRemote && !postedDeorbit) { + net.minecraftforge.common.MinecraftForge.EVENT_BUS.post( + new zmaster587.advancedRocketry.api.RocketEvent.RocketDeOrbitingEvent(this) + ); + postedDeorbit = true; + } EnumFacing dir; isCoasting = Math.abs(this.posX - actualLaunchLocation.x) < 0.01 && Math.abs(this.posZ - actualLaunchLocation.z) < 0.01; @@ -281,7 +311,12 @@ public void onUpdate() { motionX += acc * forwardDirection.getFrontOffsetX(); motionY += acc * forwardDirection.getFrontOffsetY(); motionZ += acc * forwardDirection.getFrontOffsetZ(); - setFuelAmount(getRocketFuelType(), getFuelAmount(getRocketFuelType()) - 1); + + // server-side fuel consumption for thrust ticks + if (!world.isRemote && burningFuel) { + // only consume if we actually need to (respect config + biprop pairing) + tryConsumeAscentFuel(); + } } if (!world.isRemote && this.getDistance(actualLaunchLocation.x, actualLaunchLocation.y, actualLaunchLocation.z) > 128) { @@ -375,13 +410,15 @@ else if (gasId > props.getHarvestableGasses().size() - 1) /** * Called when the rocket reaches orbit */ + @Override public void onOrbitReached() { - //make it 30 minutes with one drill - - if (world.isRemote)System.out.println("this code should not run on client side!"); + if (world.isRemote) return; // client should not run any of this + // Emit the “reached orbit” event directly so monitors update. + net.minecraftforge.common.MinecraftForge.EVENT_BUS.post( + new zmaster587.advancedRocketry.api.RocketEvent.RocketReachesOrbitEvent(this) + ); + if (this.isDead) return; - if (this.isDead) - return; //Check again to make sure we are around a gas giant ISpaceObject spaceObj; @@ -402,14 +439,64 @@ public void onOrbitReached() { return; } - //one intake with a 1 bucket tank should take 100 seconds - float intakePower = (Integer) stats.getStatTag("intakePower"); + // --- Plan harvest & cap duration by what we can actually get --- + final net.minecraftforge.fluids.Fluid targetFluid = + properties.getHarvestableGasses().get(gasId); + + // (1) config harvest cap (mB) + final boolean infinite = ARConfiguration.getCurrentConfig().gasHarvestInfinite; + final double mult = Math.max(0.0, ARConfiguration.getCurrentConfig().gasHarvestAmountMultiplier); + final long base64k = 64_000L; + final int harvestCapMb = infinite + ? Integer.MAX_VALUE + : (int) Math.min(Integer.MAX_VALUE, Math.round(base64k * mult)); + + // (2) free capacity for this gas across all rocket tanks (simulate) + int freeMb = 0; + for (TileEntity tile : this.storage.getFluidTiles()) { + net.minecraftforge.fluids.capability.IFluidHandler h = + tile.getCapability(net.minecraftforge.fluids.capability.CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, null); + if (h == null) continue; + int couldTake = h.fill(new net.minecraftforge.fluids.FluidStack(targetFluid, Integer.MAX_VALUE), false); + if (couldTake > 0) { + freeMb = (int) Math.min((long) Integer.MAX_VALUE, (long) freeMb + (long) couldTake); + } + } + + // (3) final planned harvest for this mission + this.plannedHarvestMb = Math.max(0, Math.min(harvestCapMb, freeMb)); + + // (4) duration = min( baseCurveTime(capForTiming), ceil(plannedHarvest / rate) ) + // Keep your curve and denominator 25 + final int liquidCapacity = safeTagInt(stats, "liquidCapacity"); + final int intake = safeTagInt(stats, "intakePower"); + final long rate = DENOM_PER_INTAKE * (long) Math.max(1, intake); // mB/s + + final long durationSeconds; + if (intake <= 0 || this.plannedHarvestMb <= 0) { + durationSeconds = 180L; // safety default + } else { + // IMPORTANT: cap the capacity used by the curve to the harvest cap, + // so durations match the table when harvest is smaller than tank size. + final int capForTiming = infinite ? liquidCapacity : Math.min(liquidCapacity, harvestCapMb); + + double effCapMb = computeEffectiveCapacityMb(capForTiming); + long baseSeconds = (long) Math.floor(effCapMb / (double) rate); + long capSeconds = (long) Math.ceil((double) this.plannedHarvestMb / (double) rate); + + durationSeconds = Math.max(1L, Math.min(baseSeconds, capSeconds)); + } + final long durationTicks = Math.max(1L, durationSeconds * 20L); + + + MissionGasCollection miningMission = + new MissionGasCollection(durationTicks, this, connectedInfrastructure, targetFluid); - MissionGasCollection miningMission = new MissionGasCollection(intakePower == 0 ? 360 : (long) (2 * ((int) stats.getStatTag("liquidCapacity") / intakePower)), this, connectedInfrastructure, properties.getHarvestableGasses().get(gasId)); miningMission.setDimensionId(properties.getId()); properties.addSatellite(miningMission); + // broadcast if (!world.isRemote) { PacketHandler.sendToAll(new PacketSatellite(miningMission)); } @@ -495,8 +582,99 @@ public void writeMissionPersistentNBT(NBTTagCompound nbt) { nbt.setShort("gas", gasId); + nbt.setLong("plannedHarvestMb", Math.max(0L, this.plannedHarvestMb)); + } + + // handle possible bad data gracefully + private static int safeTagInt(StatsRocket s, String key) { + Object v = s.getStatTag(key); + return (v instanceof Number) ? Math.max(0, ((Number) v).intValue()) : 0; + } + + // --- Nonlinear gas mission timing (alpha = 0.2) --- + // effectiveCapacity = BASE_CAP * (liquidCapacity / BASE_CAP)^ALPHA + // baseSeconds = floor( effectiveCapacity / (DENOM_PER_INTAKE * intakePower) ) + // finalSeconds = min(baseSeconds, ceil(plannedHarvestMb / (DENOM_PER_INTAKE * intakePower))) + private static final long BASE_CAP = 64_000L; // 64,000 mB (64 buckets) + private static final double ALPHA = 0.2d; // gentle sublinear scaling + private static final long DENOM_PER_INTAKE = 25L; // you picked "25 * intakePower" + + // Returns the effective capacity (mB) from your nonlinear curve. + private static double computeEffectiveCapacityMb(int liquidCapacity) { + double ratio = Math.max(1.0d, ((double) liquidCapacity) / (double) BASE_CAP); + return (double) BASE_CAP * Math.pow(ratio, ALPHA); + } + + private static long computeMissionDurationSeconds(int liquidCapacity, int intakePower) { + // default fallback if bad data + if (intakePower <= 0) return 180L; // 3 minutes safety default + + // scale in double to avoid precision loss, clamp ratio >= 1 to avoid shrinking below base + double ratio = Math.max(1.0d, ((double) liquidCapacity) / (double) BASE_CAP); + double effectiveCapacity = (double) BASE_CAP * Math.pow(ratio, ALPHA); + + long denom = DENOM_PER_INTAKE * (long) Math.max(1, intakePower); + long secs = (long) Math.floor(effectiveCapacity / (double) denom); + + return Math.max(1L, secs); // never zero + } + + // Consume ascent fuel exactly like the parent rocket does. + // Returns true if fuel was consumed this tick (or fuel is not required by config). + private boolean tryConsumeAscentFuel() { + if (!ARConfiguration.getCurrentConfig().rocketRequireFuel) + return true; + + final FuelRegistry.FuelType rt = getRocketFuelType(); + if (rt == null) + return false; + + // current amounts + int main = getFuelAmount(rt); + final int mainRate = Math.max(1, getFuelConsumptionRate(rt)); // defensive + + if (rt == FuelRegistry.FuelType.LIQUID_BIPROPELLANT) { + int ox = getFuelAmount(FuelRegistry.FuelType.LIQUID_OXIDIZER); + final int oxRate = Math.max(1, getFuelConsumptionRate(FuelRegistry.FuelType.LIQUID_OXIDIZER)); + + // both-or-nothing + if (main >= mainRate && ox >= oxRate) { + setFuelAmount(rt, main - mainRate); + setFuelAmount(FuelRegistry.FuelType.LIQUID_OXIDIZER, ox - oxRate); + } else { + return false; // not enough of one stream + } + + // normalize + clear fluid names when empty + setFuelAmount(rt, Math.max(0, getFuelAmount(rt))); + setFuelAmount(FuelRegistry.FuelType.LIQUID_OXIDIZER, Math.max(0, getFuelAmount(FuelRegistry.FuelType.LIQUID_OXIDIZER))); + + if (getFuelAmount(rt) == 0) { + stats.setFuelFluid("null"); + stats.setWorkingFluid("null"); + } + if (getFuelAmount(FuelRegistry.FuelType.LIQUID_OXIDIZER) == 0) { + stats.setOxidizerFluid("null"); + } + return true; + } else { + if (main >= mainRate) { + setFuelAmount(rt, main - mainRate); + } else { + return false; + } + + // normalize + clear when empty + setFuelAmount(rt, Math.max(0, getFuelAmount(rt))); + if (getFuelAmount(rt) == 0) { + stats.setFuelFluid("null"); + stats.setWorkingFluid("null"); + } + return true; + } } + @Override public void readMissionPersistentNBT(NBTTagCompound nbt) { super.readMissionPersistentNBT(nbt); diff --git a/src/main/java/zmaster587/advancedRocketry/integration/jei/ARPlugin.java b/src/main/java/zmaster587/advancedRocketry/integration/jei/ARPlugin.java index 22a596f1d..addaa0c5d 100644 --- a/src/main/java/zmaster587/advancedRocketry/integration/jei/ARPlugin.java +++ b/src/main/java/zmaster587/advancedRocketry/integration/jei/ARPlugin.java @@ -17,12 +17,18 @@ import zmaster587.advancedRocketry.integration.jei.chemicalReactor.ChemicalReactorCategory; import zmaster587.advancedRocketry.integration.jei.chemicalReactor.ChemicalReactorRecipeHandler; import zmaster587.advancedRocketry.integration.jei.chemicalReactor.ChemicalReactorRecipeMaker; +import zmaster587.advancedRocketry.integration.jei.co2scrubber.Co2ScrubberCategory; +import zmaster587.advancedRocketry.integration.jei.co2scrubber.Co2ScrubberRecipeHandler; +import zmaster587.advancedRocketry.integration.jei.co2scrubber.Co2ScrubberRecipeMaker; import zmaster587.advancedRocketry.integration.jei.crystallizer.CrystallizerCategory; import zmaster587.advancedRocketry.integration.jei.crystallizer.CrystallizerRecipeHandler; import zmaster587.advancedRocketry.integration.jei.crystallizer.CrystallizerRecipeMaker; import zmaster587.advancedRocketry.integration.jei.electrolyser.ElectrolyzerCategory; import zmaster587.advancedRocketry.integration.jei.electrolyser.ElectrolyzerRecipeHandler; import zmaster587.advancedRocketry.integration.jei.electrolyser.ElectrolyzerRecipeMaker; +import zmaster587.advancedRocketry.integration.jei.fuelingStation.FuelingStationCategory; +import zmaster587.advancedRocketry.integration.jei.fuelingStation.FuelingStationRecipeHandler; +import zmaster587.advancedRocketry.integration.jei.fuelingStation.FuelingStationRecipeMaker; import zmaster587.advancedRocketry.integration.jei.lathe.LatheCategory; import zmaster587.advancedRocketry.integration.jei.lathe.LatheRecipeHandler; import zmaster587.advancedRocketry.integration.jei.lathe.LatheRecipeMaker; @@ -41,7 +47,16 @@ import zmaster587.advancedRocketry.integration.jei.sawmill.SawMillCategory; import zmaster587.advancedRocketry.integration.jei.sawmill.SawMillRecipeHandler; import zmaster587.advancedRocketry.integration.jei.sawmill.SawMillRecipeMaker; +import zmaster587.advancedRocketry.integration.jei.satelliteBuilder.SatelliteBuilderCategory; +import zmaster587.advancedRocketry.integration.jei.satelliteBuilder.SatelliteBuilderRecipeHandler; +import zmaster587.advancedRocketry.integration.jei.satelliteBuilder.SatelliteBuilderRecipeMaker; +import zmaster587.advancedRocketry.integration.jei.stationAssembler.StationAssemblerCategory; +import zmaster587.advancedRocketry.integration.jei.stationAssembler.StationAssemblerRecipeHandler; +import zmaster587.advancedRocketry.integration.jei.stationAssembler.StationAssemblerRecipeMaker; +import zmaster587.advancedRocketry.tile.TileStationAssembler; +import zmaster587.advancedRocketry.tile.infrastructure.TileFuelingStation; import zmaster587.advancedRocketry.tile.multiblock.machine.*; +import zmaster587.advancedRocketry.tile.satellite.TileSatelliteBuilder; import zmaster587.libVulpes.inventory.GuiModular; import javax.annotation.Nonnull; @@ -61,6 +76,10 @@ public class ARPlugin implements IModPlugin { public static final String platePresser = "zmaster587.AR.platePresser"; public static final String centrifugeUUID = "zmaster587.AR.centrifuge"; public static final String precisionLaserEngraverUUID = "zmaster587.AR.precisionlaseretcher"; + public static final String satelliteBuilderUUID = "zmaster587.AR.satelliteBuilder"; + public static final String fuelingStationUUID = "zmaster587.AR.fuelingStation"; + public static final String co2ScrubberUUID = "zmaster587.AR.co2scrubber"; + public static final String stationAssemblerUUID = "zmaster587.AR.stationAssembler"; public static IJeiHelpers jeiHelpers; //AR machines can reload recipes. We still need this for JEI to be up-to-date @@ -74,17 +93,23 @@ public void registerCategories(IRecipeCategoryRegistration registry) { jeiHelpers = registry.getJeiHelpers(); IGuiHelper guiHelper = jeiHelpers.getGuiHelper(); - registry.addRecipeCategories(new RollingMachineCategory(guiHelper), - new LatheCategory(guiHelper), - new PrecisionAssemblerCategory(guiHelper), - new SawMillCategory(guiHelper), - new ChemicalReactorCategory(guiHelper), - new CrystallizerCategory(guiHelper), - new ElectrolyzerCategory(guiHelper), - new ArcFurnaceCategory(guiHelper), - new PlatePressCategory(guiHelper), - new CentrifugeCategory(guiHelper), - new PrecisionLaserEtcherCategory(guiHelper)); + registry.addRecipeCategories( + new RollingMachineCategory(guiHelper), + new LatheCategory(guiHelper), + new PrecisionAssemblerCategory(guiHelper), + new SawMillCategory(guiHelper), + new ChemicalReactorCategory(guiHelper), + new CrystallizerCategory(guiHelper), + new ElectrolyzerCategory(guiHelper), + new ArcFurnaceCategory(guiHelper), + new PlatePressCategory(guiHelper), + new CentrifugeCategory(guiHelper), + new PrecisionLaserEtcherCategory(guiHelper), + new SatelliteBuilderCategory(guiHelper), + new FuelingStationCategory(guiHelper), + new Co2ScrubberCategory(guiHelper), + new StationAssemblerCategory(guiHelper) + ); } @Override @@ -128,7 +153,12 @@ public Object getIngredientUnderMouse(GuiModular guiContainer, new ArcFurnaceRecipeHandler(), new PlatePressRecipeHandler(), new CentrifugeRecipeHandler(), - new PrecisionLaserEtcherRecipeHandler()); + new PrecisionLaserEtcherRecipeHandler(), + new SatelliteBuilderRecipeHandler(), + new FuelingStationRecipeHandler(), + new Co2ScrubberRecipeHandler(), + new StationAssemblerRecipeHandler() + ); registry.addRecipes(RollingMachineRecipeMaker.getMachineRecipes(jeiHelpers, TileRollingMachine.class), rollingMachineUUID); registry.addRecipes(LatheRecipeMaker.getMachineRecipes(jeiHelpers, TileLathe.class), latheUUID); @@ -141,7 +171,10 @@ public Object getIngredientUnderMouse(GuiModular guiContainer, registry.addRecipes(ChemicalReactorRecipeMaker.getMachineRecipes(jeiHelpers, TileChemicalReactor.class), chemicalReactorUUID); registry.addRecipes(CentrifugeRecipeMaker.getMachineRecipes(jeiHelpers, TileCentrifuge.class), centrifugeUUID); registry.addRecipes(PrecisionLaserEtcherRecipeMaker.getMachineRecipes(jeiHelpers, TilePrecisionLaserEtcher.class), precisionLaserEngraverUUID); - + registry.addRecipes(SatelliteBuilderRecipeMaker.getMachineRecipes(jeiHelpers, TileSatelliteBuilder.class), satelliteBuilderUUID); + registry.addRecipes(FuelingStationRecipeMaker.getMachineRecipes(jeiHelpers, TileFuelingStation.class), fuelingStationUUID); + registry.addRecipes(Co2ScrubberRecipeMaker.getRecipes(jeiHelpers), co2ScrubberUUID); + registry.addRecipes(StationAssemblerRecipeMaker.getMachineRecipes(jeiHelpers, TileStationAssembler.class),stationAssemblerUUID); registry.addRecipeCatalyst(new ItemStack(AdvancedRocketryBlocks.blockRollingMachine), rollingMachineUUID); registry.addRecipeCatalyst(new ItemStack(AdvancedRocketryBlocks.blockLathe), latheUUID); @@ -154,5 +187,20 @@ public Object getIngredientUnderMouse(GuiModular guiContainer, registry.addRecipeCatalyst(new ItemStack(AdvancedRocketryBlocks.blockPlatePress), platePresser); registry.addRecipeCatalyst(new ItemStack(AdvancedRocketryBlocks.blockCentrifuge), centrifugeUUID); registry.addRecipeCatalyst(new ItemStack(AdvancedRocketryBlocks.blockPrecisionLaserEngraver), precisionLaserEngraverUUID); + registry.addRecipeCatalyst(new ItemStack(AdvancedRocketryBlocks.blockSatelliteBuilder), satelliteBuilderUUID); + // Station Assembler catalyst + registry.addRecipeCatalyst(new ItemStack(AdvancedRocketryBlocks.blockStationBuilder), stationAssemblerUUID); + registry.addRecipeCatalyst(new ItemStack(AdvancedRocketryItems.itemSpaceStationChip), stationAssemblerUUID); + // Co2 Scrubber catalysts + registry.addRecipeCatalyst(new ItemStack(AdvancedRocketryBlocks.blockCO2Scrubber), co2ScrubberUUID); + registry.addRecipeCatalyst(new ItemStack(AdvancedRocketryBlocks.blockOxygenVent), co2ScrubberUUID); + + // One tab: Fueling Station + Tank-type catalysts (mono / biprop fuel / oxidizer / working fluid) + registry.addRecipeCatalyst(new ItemStack(AdvancedRocketryBlocks.blockFuelingStation), fuelingStationUUID); + registry.addRecipeCatalyst(new ItemStack(AdvancedRocketryBlocks.blockFuelTank), fuelingStationUUID); // mono + registry.addRecipeCatalyst(new ItemStack(AdvancedRocketryBlocks.blockBipropellantFuelTank), fuelingStationUUID); // biprop fuel + registry.addRecipeCatalyst(new ItemStack(AdvancedRocketryBlocks.blockOxidizerFuelTank), fuelingStationUUID); // oxidizer + registry.addRecipeCatalyst(new ItemStack(AdvancedRocketryBlocks.blockNuclearFuelTank), fuelingStationUUID); // working fluid + } } diff --git a/src/main/java/zmaster587/advancedRocketry/integration/jei/co2scrubber/Co2ScrubberCategory.java b/src/main/java/zmaster587/advancedRocketry/integration/jei/co2scrubber/Co2ScrubberCategory.java new file mode 100644 index 000000000..298b201bc --- /dev/null +++ b/src/main/java/zmaster587/advancedRocketry/integration/jei/co2scrubber/Co2ScrubberCategory.java @@ -0,0 +1,50 @@ +package zmaster587.advancedRocketry.integration.jei.co2scrubber; + +import mezz.jei.api.IGuiHelper; +import mezz.jei.api.gui.IDrawable; +import mezz.jei.api.gui.IGuiItemStackGroup; +import mezz.jei.api.ingredients.IIngredients; +import mezz.jei.api.recipe.IRecipeCategory; +import mezz.jei.api.gui.IRecipeLayout; +import net.minecraft.client.Minecraft; +import net.minecraft.item.ItemStack; +import zmaster587.advancedRocketry.api.AdvancedRocketryBlocks; +import zmaster587.advancedRocketry.integration.jei.ARPlugin; + +public class Co2ScrubberCategory implements IRecipeCategory { + + private final IDrawable bg; + private final IDrawable icon; + private final IDrawable slot; + + public Co2ScrubberCategory(IGuiHelper gui) { + this.bg = gui.createBlankDrawable(150, 40); + this.icon = gui.createDrawableIngredient(new ItemStack(AdvancedRocketryBlocks.blockCO2Scrubber)); + this.slot = gui.getSlotDrawable(); + } + + @Override public String getUid() { return ARPlugin.co2ScrubberUUID; } + @Override public String getTitle() { return new ItemStack(AdvancedRocketryBlocks.blockCO2Scrubber).getDisplayName(); } + @Override public String getModName() { return "Advanced Rocketry"; } + @Override public IDrawable getBackground(){ return bg; } + @Override public IDrawable getIcon() { return icon; } + + @Override + public void setRecipe(IRecipeLayout layout, Co2ScrubberWrapper wrapper, IIngredients ing) { + IGuiItemStackGroup items = layout.getItemStacks(); + + // One input slot (cartridge), left side + items.init(0, true, 20, 11); + items.set(0, ing.getInputs(mezz.jei.api.ingredients.VanillaTypes.ITEM).get(0)); + + // Oxygen Vent ghost on the right + items.init(1, false, 120, 11); + items.set(1, new ItemStack(AdvancedRocketryBlocks.blockOxygenVent)); + } + + @Override + public void drawExtras(Minecraft mc) { + // Draw the slot frame behind the cartridge + slot.draw(mc, 20, 11); + } +} diff --git a/src/main/java/zmaster587/advancedRocketry/integration/jei/co2scrubber/Co2ScrubberRecipeHandler.java b/src/main/java/zmaster587/advancedRocketry/integration/jei/co2scrubber/Co2ScrubberRecipeHandler.java new file mode 100644 index 000000000..5ebc0a039 --- /dev/null +++ b/src/main/java/zmaster587/advancedRocketry/integration/jei/co2scrubber/Co2ScrubberRecipeHandler.java @@ -0,0 +1,28 @@ +package zmaster587.advancedRocketry.integration.jei.co2scrubber; + +import mezz.jei.api.recipe.IRecipeHandler; +import mezz.jei.api.recipe.IRecipeWrapper; +import zmaster587.advancedRocketry.integration.jei.ARPlugin; + +public class Co2ScrubberRecipeHandler implements IRecipeHandler { + + @Override + public Class getRecipeClass() { + return Co2ScrubberWrapper.class; + } + + @Override + public String getRecipeCategoryUid(Co2ScrubberWrapper recipe) { + return ARPlugin.co2ScrubberUUID; + } + + @Override + public IRecipeWrapper getRecipeWrapper(Co2ScrubberWrapper recipe) { + return recipe; + } + + @Override + public boolean isRecipeValid(Co2ScrubberWrapper recipe) { + return recipe != null && !recipe.getCartridgeStack().isEmpty(); + } +} diff --git a/src/main/java/zmaster587/advancedRocketry/integration/jei/co2scrubber/Co2ScrubberRecipeMaker.java b/src/main/java/zmaster587/advancedRocketry/integration/jei/co2scrubber/Co2ScrubberRecipeMaker.java new file mode 100644 index 000000000..a8354d898 --- /dev/null +++ b/src/main/java/zmaster587/advancedRocketry/integration/jei/co2scrubber/Co2ScrubberRecipeMaker.java @@ -0,0 +1,19 @@ +package zmaster587.advancedRocketry.integration.jei.co2scrubber; + +import mezz.jei.api.IJeiHelpers; +import net.minecraft.item.ItemStack; +import zmaster587.advancedRocketry.api.AdvancedRocketryItems; + +import java.util.ArrayList; +import java.util.List; + +public class Co2ScrubberRecipeMaker { + public static List getRecipes(IJeiHelpers helpers) { + List list = new ArrayList<>(); + + ItemStack cart = new ItemStack(AdvancedRocketryItems.itemCarbonScrubberCartridge, 1, net.minecraftforge.oredict.OreDictionary.WILDCARD_VALUE); + list.add(new Co2ScrubberWrapper(cart)); + + return list; + } +} diff --git a/src/main/java/zmaster587/advancedRocketry/integration/jei/co2scrubber/Co2ScrubberWrapper.java b/src/main/java/zmaster587/advancedRocketry/integration/jei/co2scrubber/Co2ScrubberWrapper.java new file mode 100644 index 000000000..cbddae11f --- /dev/null +++ b/src/main/java/zmaster587/advancedRocketry/integration/jei/co2scrubber/Co2ScrubberWrapper.java @@ -0,0 +1,34 @@ +package zmaster587.advancedRocketry.integration.jei.co2scrubber; + +import mezz.jei.api.ingredients.IIngredients; +import mezz.jei.api.recipe.BlankRecipeWrapper; +import net.minecraft.item.ItemStack; +import java.util.Collections; + +public class Co2ScrubberWrapper extends BlankRecipeWrapper { + private final ItemStack cartridge; + + public Co2ScrubberWrapper(ItemStack cartridge) { + this.cartridge = cartridge; + } + + @Override + public void getIngredients(IIngredients ing) { + // INPUTS: the cartridge (as a list-of-lists) + ing.setInputLists(mezz.jei.api.ingredients.VanillaTypes.ITEM, + java.util.Collections.singletonList( + java.util.Collections.singletonList(cartridge))); + + // OUTPUTS: expose BOTH blocks + the cartridge + java.util.List outs = new java.util.ArrayList<>(3); + outs.add(new net.minecraft.item.ItemStack(zmaster587.advancedRocketry.api.AdvancedRocketryBlocks.blockCO2Scrubber)); + outs.add(new net.minecraft.item.ItemStack(zmaster587.advancedRocketry.api.AdvancedRocketryBlocks.blockOxygenVent)); + outs.add(cartridge); + ing.setOutputs(mezz.jei.api.ingredients.VanillaTypes.ITEM, outs); + } + + // Used by the recipe handler's isRecipeValid + public ItemStack getCartridgeStack() { + return cartridge; + } +} diff --git a/src/main/java/zmaster587/advancedRocketry/integration/jei/fuelingStation/FuelingStationCategory.java b/src/main/java/zmaster587/advancedRocketry/integration/jei/fuelingStation/FuelingStationCategory.java new file mode 100644 index 000000000..240586afe --- /dev/null +++ b/src/main/java/zmaster587/advancedRocketry/integration/jei/fuelingStation/FuelingStationCategory.java @@ -0,0 +1,97 @@ +package zmaster587.advancedRocketry.integration.jei.fuelingStation; + +import mezz.jei.api.IGuiHelper; +import mezz.jei.api.gui.*; +import mezz.jei.api.ingredients.IIngredients; +import mezz.jei.api.recipe.IRecipeCategory; +import net.minecraft.client.Minecraft; +import net.minecraft.item.ItemStack; +import zmaster587.advancedRocketry.api.AdvancedRocketryBlocks; +import zmaster587.advancedRocketry.integration.jei.ARPlugin; +import zmaster587.libVulpes.gui.CommonResources; + +public class FuelingStationCategory implements IRecipeCategory { + + private final IDrawable background; + private final IDrawable icon; + private final IDrawable tankFrame; // 14x54 bezel from generic background + private final IDrawable slotFrame; // JEI’s standard slot look + + // --- compact but accurate: 150 x 56 so 4 recipes fit on one JEI page --- + public FuelingStationCategory(IGuiHelper gui) { + this.background = gui.createBlankDrawable(150, 56); + this.icon = gui.createDrawableIngredient(new ItemStack(AdvancedRocketryBlocks.blockFuelingStation)); + + // exact bezel the in-game ModuleLiquidIndicator draws: u=176,v=58,w=14,h=54 + this.tankFrame = gui.createDrawable(CommonResources.genericBackground, 176, 58, 14, 54); + + // vanilla-looking slot border + this.slotFrame = gui.getSlotDrawable(); + } + + @Override public String getUid() { return ARPlugin.fuelingStationUUID; } + @Override public String getTitle() { return new ItemStack(AdvancedRocketryBlocks.blockFuelingStation).getDisplayName(); } + @Override public String getModName() { return "Advanced Rocketry"; } + @Override public IDrawable getBackground(){ return background; } + @Override public IDrawable getIcon() { return icon; } + + @Override + public void setRecipe(IRecipeLayout layout, FuelingStationWrapper wrapper, IIngredients ing) { + // Fluid gauge (inside the real bezel) + IGuiFluidStackGroup fluids = layout.getFluidStacks(); + fluids.init(0, true, 28, 3, 12, 52, 1000, false, null); + fluids.set(0, wrapper.getFluid()); + + IGuiItemStackGroup items = layout.getItemStacks(); + + // ITEM inputs come as two lists: [ [bucket?], [role tank] ] + java.util.List> itemInputs = + ing.getInputs(mezz.jei.api.ingredients.VanillaTypes.ITEM); + + // Slot 0: bucket INPUT (if present) + items.init(0, true, 45, 6); + if (!itemInputs.isEmpty() && !itemInputs.get(0).isEmpty() + && itemInputs.get(0).get(0).getItem() == wrapper.getFilledContainer().getItem()) { + items.set(0, itemInputs.get(0)); + } else { + items.set(0, java.util.Collections.emptyList()); + } + items.addTooltipCallback((slotIndex, input, stack, tooltip) -> { + if (slotIndex != 0 || stack == null || stack.isEmpty()) return; + + // Only decorate the bucket input slot + tooltip.add(""); + tooltip.add(net.minecraft.util.text.TextFormatting.YELLOW + + zmaster587.libVulpes.LibVulpes.proxy.getLocalizedString( + "jei.ar.fuel.role." + wrapper.getRole().langKey() + )); + }); + + // Slot 1: ROLE TANK + items.init(1, true, 120, 6); + // The role tank will be the other input list + if (itemInputs.size() >= 2) { + items.set(1, itemInputs.get(1)); + } else if (!wrapper.getRoleTankStack().isEmpty()) { + // fallback if bucket missing → the only input is the role tank + items.set(1, java.util.Collections.singletonList(wrapper.getRoleTankStack())); + } + fluids.addTooltipCallback((slotIndex, input, fluid, tooltip) -> { + if (slotIndex != 0 || fluid == null) return; + + // Blank spacer then role + usage + tooltip.add(""); + tooltip.add(net.minecraft.util.text.TextFormatting.YELLOW + + zmaster587.libVulpes.LibVulpes.proxy.getLocalizedString( + "jei.ar.fuel.role." + wrapper.getRole().langKey() + )); + }); + } + + @Override + public void drawExtras(Minecraft mc) { + tankFrame.draw(mc, 27, 2); + slotFrame.draw(mc, 45, 6); + } + +} diff --git a/src/main/java/zmaster587/advancedRocketry/integration/jei/fuelingStation/FuelingStationRecipeHandler.java b/src/main/java/zmaster587/advancedRocketry/integration/jei/fuelingStation/FuelingStationRecipeHandler.java new file mode 100644 index 000000000..efc87ce5d --- /dev/null +++ b/src/main/java/zmaster587/advancedRocketry/integration/jei/fuelingStation/FuelingStationRecipeHandler.java @@ -0,0 +1,12 @@ +package zmaster587.advancedRocketry.integration.jei.fuelingStation; + +import mezz.jei.api.recipe.IRecipeHandler; +import mezz.jei.api.recipe.IRecipeWrapper; +import zmaster587.advancedRocketry.integration.jei.ARPlugin; + +public class FuelingStationRecipeHandler implements IRecipeHandler { + @Override public Class getRecipeClass() { return FuelingStationWrapper.class; } + @Override public String getRecipeCategoryUid(FuelingStationWrapper r) { return ARPlugin.fuelingStationUUID; } + @Override public IRecipeWrapper getRecipeWrapper(FuelingStationWrapper r) { return r; } + @Override public boolean isRecipeValid(FuelingStationWrapper r) { return r != null; } +} diff --git a/src/main/java/zmaster587/advancedRocketry/integration/jei/fuelingStation/FuelingStationRecipeMaker.java b/src/main/java/zmaster587/advancedRocketry/integration/jei/fuelingStation/FuelingStationRecipeMaker.java new file mode 100644 index 000000000..ab6af1c14 --- /dev/null +++ b/src/main/java/zmaster587/advancedRocketry/integration/jei/fuelingStation/FuelingStationRecipeMaker.java @@ -0,0 +1,41 @@ +package zmaster587.advancedRocketry.integration.jei.fuelingStation; + +import mezz.jei.api.IJeiHelpers; +import net.minecraftforge.fluids.Fluid; +import net.minecraftforge.fluids.FluidStack; +import zmaster587.advancedRocketry.api.fuel.FuelRegistry; +import zmaster587.advancedRocketry.api.fuel.FuelRegistry.FuelType; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class FuelingStationRecipeMaker { + + public static List getRecipes(IJeiHelpers helpers) { + List out = new ArrayList<>(); + + add(out, FuelType.LIQUID_MONOPROPELLANT, FuelingStationWrapper.Role.MONO); + add(out, FuelType.LIQUID_BIPROPELLANT, FuelingStationWrapper.Role.BIPROP_FUEL); + add(out, FuelType.LIQUID_OXIDIZER, FuelingStationWrapper.Role.OXIDIZER); + add(out, FuelType.NUCLEAR_WORKING_FLUID, FuelingStationWrapper.Role.WORKING_FLUID); + + return out; + } + + private static void add(List list, + FuelType type, + FuelingStationWrapper.Role role) { + // Avoid name clash with AR’s FuelRegistry by fully-qualifying Forge’s registry here. + for (Map.Entry e : net.minecraftforge.fluids.FluidRegistry.getRegisteredFluids().entrySet()) { + Fluid f = e.getValue(); + if (f != null && FuelRegistry.instance.isFuel(type, f)) { + list.add(new FuelingStationWrapper(new FluidStack(f, 1000), role)); + } + } + } + + public static List getMachineRecipes(IJeiHelpers helpers, Class ignored) { + return getRecipes(helpers); + } +} diff --git a/src/main/java/zmaster587/advancedRocketry/integration/jei/fuelingStation/FuelingStationWrapper.java b/src/main/java/zmaster587/advancedRocketry/integration/jei/fuelingStation/FuelingStationWrapper.java new file mode 100644 index 000000000..2c5811be2 --- /dev/null +++ b/src/main/java/zmaster587/advancedRocketry/integration/jei/fuelingStation/FuelingStationWrapper.java @@ -0,0 +1,95 @@ +package zmaster587.advancedRocketry.integration.jei.fuelingStation; + +import mezz.jei.api.ingredients.IIngredients; +import mezz.jei.api.recipe.IRecipeWrapper; +import net.minecraft.init.Items; +import net.minecraft.item.ItemStack; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.FluidUtil; +import zmaster587.advancedRocketry.api.AdvancedRocketryBlocks; + +import java.util.Collections; + +public class FuelingStationWrapper implements IRecipeWrapper { + + public enum Role { + MONO("monopropellant"), BIPROP_FUEL("biprop_fuel"), + OXIDIZER("oxidizer"), WORKING_FLUID("working_fluid"); + private final String key; Role(String k){ this.key = k; } + public String langKey(){ return key; } + } + + private final FluidStack fluid; + private final Role role; + + public FuelingStationWrapper(FluidStack fluid, Role role) { + this.fluid = fluid; + this.role = role; + } + + public Role getRole() { return role; } + public FluidStack getFluid() { return fluid; } + + @Override + public void getIngredients(IIngredients ing) { + // Fluid input (internal tank) + ing.setInputs(mezz.jei.api.ingredients.VanillaTypes.FLUID, + java.util.Collections.singletonList(fluid)); + + // ITEM inputs in order: + // 0: [filled bucket?] + // 1: [role tank] + // 2: [fueling station] <-- hidden, just for discoverability via U/R on the block + java.util.List> itemInputs = new java.util.ArrayList<>(3); + + // 0) filled bucket (if present) + ItemStack filled = getFilledContainer(); + if (!filled.isEmpty()) { + itemInputs.add(java.util.Collections.singletonList(filled)); + } + + // 1) role tank (always try to include) + ItemStack roleTank = getRoleTankStack(); + if (!roleTank.isEmpty()) { + itemInputs.add(java.util.Collections.singletonList(roleTank)); + } + + // 2) fueling station (hidden ingredient so U/R on the block opens this tab) + ItemStack station = new ItemStack(zmaster587.advancedRocketry.api.AdvancedRocketryBlocks.blockFuelingStation); + itemInputs.add(java.util.Collections.singletonList(station)); + + // commit item inputs + ing.setInputLists(mezz.jei.api.ingredients.VanillaTypes.ITEM, itemInputs); + + // Outputs: keep role tank (if present) and ALSO the station (so R on the block opens this tab) + java.util.List outs = new java.util.ArrayList<>(2); + if (!roleTank.isEmpty()) outs.add(roleTank); + outs.add(station); + ing.setOutputs(mezz.jei.api.ingredients.VanillaTypes.ITEM, outs); + } + + + public ItemStack getRoleTankStack() { + switch (role) { + case MONO: + return AdvancedRocketryBlocks.blockFuelTank != null ? new ItemStack(AdvancedRocketryBlocks.blockFuelTank) : ItemStack.EMPTY; + case BIPROP_FUEL: + return AdvancedRocketryBlocks.blockBipropellantFuelTank != null ? new ItemStack(AdvancedRocketryBlocks.blockBipropellantFuelTank) : ItemStack.EMPTY; + case OXIDIZER: + return AdvancedRocketryBlocks.blockOxidizerFuelTank != null ? new ItemStack(AdvancedRocketryBlocks.blockOxidizerFuelTank) : ItemStack.EMPTY; + case WORKING_FLUID: + return AdvancedRocketryBlocks.blockNuclearFuelTank != null ? new ItemStack(AdvancedRocketryBlocks.blockNuclearFuelTank) : ItemStack.EMPTY; + default: + return ItemStack.EMPTY; + } + } + + public ItemStack getFilledContainer() { + ItemStack is = net.minecraftforge.fluids.FluidUtil.getFilledBucket(fluid); + return is == null ? ItemStack.EMPTY : is; + } + + static ItemStack fuelStationDisplayStack() { + return new ItemStack(AdvancedRocketryBlocks.blockFuelingStation); + } +} diff --git a/src/main/java/zmaster587/advancedRocketry/integration/jei/satelliteBuilder/SatelliteBuilderCategory.java b/src/main/java/zmaster587/advancedRocketry/integration/jei/satelliteBuilder/SatelliteBuilderCategory.java new file mode 100644 index 000000000..0a303e02f --- /dev/null +++ b/src/main/java/zmaster587/advancedRocketry/integration/jei/satelliteBuilder/SatelliteBuilderCategory.java @@ -0,0 +1,152 @@ +package zmaster587.advancedRocketry.integration.jei.satelliteBuilder; + +import mezz.jei.api.IGuiHelper; +import mezz.jei.api.gui.IDrawable; +import mezz.jei.api.gui.IRecipeLayout; +import mezz.jei.api.ingredients.IIngredients; +import mezz.jei.api.recipe.IRecipeCategory; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.Gui; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ResourceLocation; +import zmaster587.advancedRocketry.integration.jei.ARPlugin; +import zmaster587.advancedRocketry.inventory.TextureResources; +import zmaster587.libVulpes.LibVulpes; + +import java.util.List; + +public class SatelliteBuilderCategory implements IRecipeCategory { + + private final IDrawable background; + private final IDrawable slotFunctionComponent; + private final IDrawable slotPowerComponent; + private final IDrawable slotIO; + private final IDrawable slotSatellite; + private final IDrawable slotIdChip; + private final IDrawable progressBar; + private final IDrawable vanillaSlot; + private final String uid; + + public SatelliteBuilderCategory(IGuiHelper guiHelper) { + // Use a blank or minimal background (176x90 is enough for the elements) + this.background = guiHelper.createBlankDrawable(176, 90); + + // Slot frames/icons from TextureResources + this.slotFunctionComponent = guiHelper.createDrawable( + new ResourceLocation("libvulpes:textures/gui/maingui.png"), + 212, 18, 18, 18); // functionComponent + + this.slotPowerComponent = guiHelper.createDrawable( + new ResourceLocation("libvulpes:textures/gui/maingui.png"), + 230, 18, 18, 18); // powercomponent + + this.slotIO = guiHelper.createDrawable( + new ResourceLocation("libvulpes:textures/gui/maingui.png"), + 212, 0, 18, 18); // ioSlot + + this.slotSatellite = guiHelper.createDrawable( + new ResourceLocation("advancedrocketry:textures/gui/progressBars/progressBars.png"), + 220, 238, 18, 18); + + this.slotIdChip = guiHelper.createDrawable( + new ResourceLocation("libvulpes:textures/gui/maingui.png"), + 230, 0, 18, 18); // idChip + + this.vanillaSlot = guiHelper.getSlotDrawable(); + + this.progressBar = guiHelper.createDrawable( + new ResourceLocation("advancedrocketry:textures/gui/progressBars/progressBars.png"), + 217, 0, 17, 17); // progressBar + + this.uid = ARPlugin.satelliteBuilderUUID; + } + + @Override + public IDrawable getBackground() { + return background; + } + + @Override + public String getTitle() { + return LibVulpes.proxy.getLocalizedString("tile.satelliteBuilder.name"); + } + + @Override + public String getModName() { + return "Advanced Rocketry"; + } + + @Override + public String getUid() { + return uid; + } + + public Class getRecipeClass() { + return SatelliteBuilderWrapper.class; + } + + + @Override + public void setRecipe(IRecipeLayout recipeLayout, SatelliteBuilderWrapper wrapper, IIngredients ingredients) { + // Place JEI slots at the same coordinates as the modules + // Slot indices: see TileSatelliteBuilder for mapping + // 0: function, 1-6: IO, 7: Output, 8: Chip, 9: Chip, 10: Chip copy 11: chassis + // Function slot 0 + recipeLayout.getItemStacks().init(0, true, 152, 10); + + // Power slots 1-2-3 + recipeLayout.getItemStacks().init(1, true, 116, 30); + recipeLayout.getItemStacks().init(2, true, 134, 30); + recipeLayout.getItemStacks().init(3, true, 152, 30); + recipeLayout.getItemStacks().init(4, true, 116, 50); + recipeLayout.getItemStacks().init(5, true, 134, 50); + recipeLayout.getItemStacks().init(6, true, 152, 50); + + // Output slot 7 + recipeLayout.getItemStacks().init(7, false, 58, 36); + + // ID chip slot 8 + recipeLayout.getItemStacks().init(8, true, 58, 16); + + // Chip copy slot 9 + recipeLayout.getItemStacks().init(9, true, 82, 16); + + // holdingslot slot 10 not used by players + //recipeLayout.getItemStacks().init(10, false, 58, 36); + + // Chassis slot 11 + recipeLayout.getItemStacks().init(11, true, 38, 16); + + recipeLayout.getItemStacks().set(ingredients); + + // Add tooltip cosmetics + wrapper.registerTooltipCallbacks(recipeLayout.getItemStacks()); + } + + @Override + public void drawExtras(Minecraft minecraft) { + FontRenderer fr = minecraft.fontRenderer; + // Draw slot frames and icons at the correct positions + slotFunctionComponent.draw(minecraft, 152, 10); // slot 0 + slotPowerComponent.draw(minecraft, 116, 30); // slot 1 + slotPowerComponent.draw(minecraft, 134, 30); // slot 2 + slotPowerComponent.draw(minecraft, 152, 30); // slot 3 + slotIO.draw(minecraft, 116, 50); // slot 4 + slotIO.draw(minecraft, 134, 50); // slot 5 + slotIO.draw(minecraft, 152, 50); // slot 6 + vanillaSlot.draw(minecraft, 58, 36); // Output slot (slot 7) + slotIdChip.draw(minecraft, 58, 16); // slot 8 + slotIdChip.draw(minecraft, 82, 16); // slot 9 + slotSatellite.draw(minecraft, 38, 16); // Chassis slot (slot 11) + + // Progress bar + progressBar.draw(minecraft, 75, 36); + + } + @Override + public java.util.List getTooltipStrings(int mouseX, int mouseY) { + return java.util.Collections.emptyList(); + } + +} diff --git a/src/main/java/zmaster587/advancedRocketry/integration/jei/satelliteBuilder/SatelliteBuilderRecipeHandler.java b/src/main/java/zmaster587/advancedRocketry/integration/jei/satelliteBuilder/SatelliteBuilderRecipeHandler.java new file mode 100644 index 000000000..adaa2e443 --- /dev/null +++ b/src/main/java/zmaster587/advancedRocketry/integration/jei/satelliteBuilder/SatelliteBuilderRecipeHandler.java @@ -0,0 +1,28 @@ +package zmaster587.advancedRocketry.integration.jei.satelliteBuilder; + +import mezz.jei.api.recipe.IRecipeHandler; +import mezz.jei.api.recipe.IRecipeWrapper; +import zmaster587.advancedRocketry.integration.jei.ARPlugin; + +public class SatelliteBuilderRecipeHandler implements IRecipeHandler { + + @Override + public Class getRecipeClass() { + return SatelliteBuilderWrapper.class; + } + + @Override + public String getRecipeCategoryUid(SatelliteBuilderWrapper recipe) { + return ARPlugin.satelliteBuilderUUID; + } + + @Override + public IRecipeWrapper getRecipeWrapper(SatelliteBuilderWrapper recipe) { + return recipe; + } + + @Override + public boolean isRecipeValid(SatelliteBuilderWrapper recipe) { + return recipe != null; + } +} diff --git a/src/main/java/zmaster587/advancedRocketry/integration/jei/satelliteBuilder/SatelliteBuilderRecipeMaker.java b/src/main/java/zmaster587/advancedRocketry/integration/jei/satelliteBuilder/SatelliteBuilderRecipeMaker.java new file mode 100644 index 000000000..2827a502f --- /dev/null +++ b/src/main/java/zmaster587/advancedRocketry/integration/jei/satelliteBuilder/SatelliteBuilderRecipeMaker.java @@ -0,0 +1,168 @@ +package zmaster587.advancedRocketry.integration.jei.satelliteBuilder; + +import mezz.jei.api.gui.ITooltipCallback; +import mezz.jei.api.IJeiHelpers; +import net.minecraft.init.Items; +import net.minecraft.item.ItemStack; +import net.minecraftforge.fluids.FluidStack; +import zmaster587.advancedRocketry.api.AdvancedRocketryItems; +import zmaster587.advancedRocketry.integration.jei.satelliteBuilder.SatelliteBuilderWrapper; +import zmaster587.libVulpes.api.LibVulpesItems; +import zmaster587.libVulpes.interfaces.IRecipe; + + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class SatelliteBuilderRecipeMaker { + + public static List getMachineRecipes(IJeiHelpers helpers, Class clazz) { + List recipes = new ArrayList<>(); + + // --- Satellite Assembly Example --- + // Slot mapping: + // 0: Function component (core module) + // 1-6: Module components (power, IO, etc) + // 7: Output slot + // 8: ID chip slot (input) + // 9: Chip copy slot (input) + // 10: Holding slot (ghostslot, not used by player) + // 11: Chassis slot + + + // slot 0 + List coreModules = new ArrayList<>(); + for (int i = 0; i < 7; i++) { + coreModules.add(new ItemStack(AdvancedRocketryItems.itemSatellitePrimaryFunction, 1, i)); + } // slot 0 + + // slots 1-6: power gen, battery, data units + List moduleVariants = Arrays.asList( + new ItemStack(AdvancedRocketryItems.itemSatellitePowerSource, 1, 0), + new ItemStack(AdvancedRocketryItems.itemSatellitePowerSource, 1, 1), + new ItemStack(LibVulpesItems.itemBattery, 1, 0), + new ItemStack(LibVulpesItems.itemBattery, 1, 1), + new ItemStack(AdvancedRocketryItems.itemDataUnit, 1, 0) + ); + + + + // Slot 8: controllers mapped 1:1 to primariry core modules (metas 0-6) + List satelliteControllers = Arrays.asList( + new ItemStack(AdvancedRocketryItems.itemSatelliteIdChip), // 0: Optical + new ItemStack(AdvancedRocketryItems.itemSatelliteIdChip), // 1: Composition + new ItemStack(AdvancedRocketryItems.itemSatelliteIdChip), // 2: Mass Scanner + new ItemStack(AdvancedRocketryItems.itemSatelliteIdChip), // 3: Microwave Energy + new ItemStack(AdvancedRocketryItems.itemOreScanner), // 4: Ore Mapping + new ItemStack(AdvancedRocketryItems.itemBiomeChanger), // 5: Biome Changer (remote) + new ItemStack(AdvancedRocketryItems.itemWeatherController)// 6: Weather Controller (remote) + ); + + + List output = Collections.singletonList(new ItemStack(AdvancedRocketryItems.itemSatellite)); // slot 7 (output) + List chassis = Collections.singletonList(new ItemStack(AdvancedRocketryItems.itemSatellite)); // slot 11 (empty chassis) + + List> inputs = new ArrayList<>(); + inputs.add(coreModules); // slot 0: Function component + inputs.add(moduleVariants); // slot 1: Module component + inputs.add(moduleVariants); // slot 2: Module component + inputs.add(moduleVariants); // slot 3: Module component + inputs.add(moduleVariants); // slot 4: Module component + inputs.add(moduleVariants); // slot 5: Module component + inputs.add(moduleVariants); // slot 6: Module component + //inputs.add(Collections.emptyList()); // slot 7: Output slot (not used as input) + inputs.add(satelliteControllers); // slot 8: ID chip slot + inputs.add(Collections.emptyList()); // slot 9: Chip copy slot (not used in this recipe) + //inputs.add(Collections.emptyList()); // slot 10: Holding slot (ghostslot, not used) + inputs.add(chassis); // slot 11: Chassis slot + + // Anonymous IRecipe implementation + IRecipe assemblyRecipe = new IRecipe() { + @Override + public List getOutput() { return output; } // slot 7 + @Override + public List getFluidOutputs() { return Collections.emptyList(); } + @Override + public List> getIngredients() { return inputs; } + @Override + public List getFluidIngredients() { return Collections.emptyList(); } + @Override + public int getTime() { return 200; } + @Override + public int getPower() { return 0; } + @Override + public String getOreDictString(int var1) { return ""; } + }; + + recipes.add(new SatelliteBuilderWrapper(assemblyRecipe, false)); + + // --- Chip Copy Example --- + // Slot mapping for chip copy: + // 8: Source chip (input) + // 9: Blank chip (input) + // 7: Output slot (copied chip) + + List sourceChips = Arrays.asList( + new ItemStack(AdvancedRocketryItems.itemSatelliteIdChip), + new ItemStack(AdvancedRocketryItems.itemPlanetIdChip), + new ItemStack(AdvancedRocketryItems.itemSpaceStationChip), + new ItemStack(AdvancedRocketryItems.itemOreScanner), + new ItemStack(AdvancedRocketryItems.itemBiomeChanger), + new ItemStack(AdvancedRocketryItems.itemWeatherController), + new ItemStack(AdvancedRocketryItems.itemSpaceElevatorChip) + ); + + // Mirror the source chips for blank chips + List blankChips = sourceChips; + + // The output cycling in mapped order: + List copiedOutputVariants = Arrays.asList( + new ItemStack(AdvancedRocketryItems.itemSatelliteIdChip), + new ItemStack(AdvancedRocketryItems.itemPlanetIdChip), + new ItemStack(AdvancedRocketryItems.itemSpaceStationChip), + new ItemStack(AdvancedRocketryItems.itemOreScanner), + new ItemStack(AdvancedRocketryItems.itemBiomeChanger), + new ItemStack(AdvancedRocketryItems.itemWeatherController), + new ItemStack(AdvancedRocketryItems.itemSpaceElevatorChip) + ); + + List> chipCopyInputs = new ArrayList<>(); + chipCopyInputs.add(Collections.emptyList()); // slot 0: Function component (not used) + chipCopyInputs.add(Collections.emptyList()); // slot 1: Power component (not used) + chipCopyInputs.add(Collections.emptyList()); // slot 2: Power component (not used) + chipCopyInputs.add(Collections.emptyList()); // slot 3: Power component (not used) + chipCopyInputs.add(Collections.emptyList()); // slot 4: IO component (not used) + chipCopyInputs.add(Collections.emptyList()); // slot 5: IO component (not used) + chipCopyInputs.add(Collections.emptyList()); // slot 6: IO component (not used) + //chipCopyInputs.add(Collections.emptyList()); // slot 7: Output slot + chipCopyInputs.add(sourceChips); // slot 8: Source chip + chipCopyInputs.add(blankChips); // slot 9: Blank chip + //chipCopyInputs.add(Collections.emptyList()); // slot 10: Holding slot (not used) + chipCopyInputs.add(Collections.emptyList()); // slot 11: Chassis slot (not used) + + IRecipe chipCopyRecipe = new IRecipe() { + @Override public List getOutput() { + // Could return first as a fallback; the wrapper will override with setOutputLists + return java.util.Collections.singletonList(copiedOutputVariants.get(0)); + } + @Override + public List getFluidOutputs() { return Collections.emptyList(); } + @Override + public List> getIngredients() { return chipCopyInputs; } + @Override + public List getFluidIngredients() { return Collections.emptyList(); } + @Override + public int getTime() { return 200; } + @Override + public int getPower() { return 0; } + @Override + public String getOreDictString(int var1) { return ""; } + }; + + recipes.add(new SatelliteBuilderWrapper(chipCopyRecipe, true, copiedOutputVariants)); + + return recipes; + } +} diff --git a/src/main/java/zmaster587/advancedRocketry/integration/jei/satelliteBuilder/SatelliteBuilderWrapper.java b/src/main/java/zmaster587/advancedRocketry/integration/jei/satelliteBuilder/SatelliteBuilderWrapper.java new file mode 100644 index 000000000..4c6943055 --- /dev/null +++ b/src/main/java/zmaster587/advancedRocketry/integration/jei/satelliteBuilder/SatelliteBuilderWrapper.java @@ -0,0 +1,147 @@ +package zmaster587.advancedRocketry.integration.jei.satelliteBuilder; + +import mezz.jei.api.gui.IGuiItemStackGroup; +import mezz.jei.api.gui.ITooltipCallback; +import mezz.jei.api.ingredients.IIngredients; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.resources.I18n; +import net.minecraft.item.ItemStack; +import net.minecraft.util.text.TextFormatting; +import zmaster587.advancedRocketry.api.AdvancedRocketryItems; +import zmaster587.advancedRocketry.integration.jei.MachineRecipe; +import zmaster587.libVulpes.LibVulpes; +import zmaster587.libVulpes.interfaces.IRecipe; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class SatelliteBuilderWrapper extends MachineRecipe { + public final boolean isCopyRecipe; + private final IRecipe baseRecipe; // keep a handle + private final List outputVariants; // for JEI cycling (copy recipe) + private static Set COPY_STRIP_STRINGS; + + public SatelliteBuilderWrapper(IRecipe rec, boolean isCopyRecipe) { + this(rec, isCopyRecipe, null); + } + + public SatelliteBuilderWrapper(IRecipe rec, boolean isCopyRecipe, List outputVariants) { + super(rec); + this.isCopyRecipe = isCopyRecipe; + this.baseRecipe = rec; + this.outputVariants = outputVariants; + } + + @Override + public void getIngredients(mezz.jei.api.ingredients.IIngredients ingredients) { + super.getIngredients(ingredients); // inputs already mapped + + if (isCopyRecipe) { + // ONE output slot cycling MANY variants + List variants = (outputVariants != null && !outputVariants.isEmpty()) + ? outputVariants + : baseRecipe.getOutput(); // fallback if you didn’t pass variants + + if (variants != null && !variants.isEmpty()) { + ingredients.setOutputLists(ItemStack.class, + java.util.Collections.singletonList(variants)); + } + } else { + // Assembly: single concrete output (first item) + List outs = baseRecipe.getOutput(); + if (outs != null && !outs.isEmpty()) { + ingredients.setOutput(ItemStack.class, outs.get(0)); + } + } + } + + + + public java.util.List getOutputVariants() { return outputVariants; } + + public void registerTooltipCallbacks(IGuiItemStackGroup stacks) { + final boolean isCopy = this.isCopyRecipe; + + stacks.addTooltipCallback(new ITooltipCallback() { + @Override + public void onTooltip(int slotIndex, boolean input, ItemStack stack, List tooltip) { + if (stack.isEmpty()) return; + + if (!isCopy) { + // === ASSEMBLY RECIPE === (only touch output slot) + if (slotIndex != 7) return; + + if (stack.getItem() == AdvancedRocketryItems.itemSatellite) { + // Remove "empty chassis" (whatever the localization) + String libEmpty = LibVulpes.proxy.getLocalizedString("msg.itemsatellite.empty"); + tooltip.removeIf(line -> { + String stripped = net.minecraft.util.text.TextFormatting.getTextWithoutFormattingCodes(line); + if (stripped == null) return false; + String s = stripped.trim(); + return s.equalsIgnoreCase(libEmpty) || s.equalsIgnoreCase("unprogrammed"); + }); + + // Add clean preview label if not already present + String label = I18n.format("jei.sb.satellitepreview"); + addIfMissing(tooltip, label); + } + return; + } + + // === CHIP-COPY RECIPE === + if (slotIndex == 7 || slotIndex == 8) { + ensureCopyStripStringsBuilt(); + + // Strip any "unprogrammed"/blank-style lines (locale + color safe) + tooltip.removeIf(line -> { + String stripped = net.minecraft.util.text.TextFormatting.getTextWithoutFormattingCodes(line); + return stripped != null && COPY_STRIP_STRINGS.contains(stripped.trim().toLowerCase()); + }); + + // Add concise labels + final String key = (slotIndex == 7) ? "jei.sb.copy.output" : "jei.sb.copy.source"; + String label = I18n.format(key); + addIfMissing(tooltip, label); + } + } + }); + } + + private static void addIfMissing(List tooltip, String label) { + for (String l : tooltip) { + String stripped = net.minecraft.util.text.TextFormatting.getTextWithoutFormattingCodes(l); + if (stripped != null && stripped.equalsIgnoreCase(label)) return; + } + tooltip.add(label); + } + + private static void ensureCopyStripStringsBuilt() { + if (COPY_STRIP_STRINGS != null) return; + COPY_STRIP_STRINGS = new HashSet<>(); + String[] keys = { + "msg.itemchip.unprogrammed", + "msg.satelliteidchip.unprogrammed", + "msg.planetidchip.unprogrammed", + "msg.stationchip.unprogrammed", + "msg.orescanner.unprogrammed", + "msg.itemsatellite.empty" + }; + for (String k : keys) { + String v1 = I18n.format(k); + if (v1 != null) COPY_STRIP_STRINGS.add(v1.trim().toLowerCase()); + String v2 = LibVulpes.proxy.getLocalizedString(k); + if (v2 != null) COPY_STRIP_STRINGS.add(v2.trim().toLowerCase()); + } + COPY_STRIP_STRINGS.add("unprogrammed"); + } + + @Override + public void drawInfo(Minecraft minecraft, int recipeWidth, int recipeHeight, int mouseX, int mouseY) { + FontRenderer fr = minecraft.fontRenderer; + String text = I18n.format(isCopyRecipe ? "jei.sb.copychiphint" : "jei.sb.assemblyhint"); + int tw = fr.getStringWidth(text); + fr.drawString(text, (recipeWidth - tw) / 2, recipeHeight - fr.FONT_HEIGHT - 4, 0x000000); + } +} diff --git a/src/main/java/zmaster587/advancedRocketry/integration/jei/stationAssembler/StationAssemblerCategory.java b/src/main/java/zmaster587/advancedRocketry/integration/jei/stationAssembler/StationAssemblerCategory.java new file mode 100644 index 000000000..0b3bfcff8 --- /dev/null +++ b/src/main/java/zmaster587/advancedRocketry/integration/jei/stationAssembler/StationAssemblerCategory.java @@ -0,0 +1,156 @@ +package zmaster587.advancedRocketry.integration.jei.stationAssembler; + +import mezz.jei.api.IGuiHelper; +import mezz.jei.api.gui.IGuiItemStackGroup; +import mezz.jei.api.gui.IRecipeLayout; +import mezz.jei.api.gui.IDrawable; +import mezz.jei.api.gui.IDrawableAnimated; +import mezz.jei.api.gui.IDrawableAnimated.StartDirection; +import mezz.jei.api.gui.IDrawableStatic; +import mezz.jei.api.ingredients.IIngredients; +import mezz.jei.api.recipe.IRecipeCategory; +import net.minecraft.client.Minecraft; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ResourceLocation; +import zmaster587.advancedRocketry.integration.jei.ARPlugin; +import zmaster587.libVulpes.LibVulpes; + +/** + * Compact layout, mirroring the simple two-input / two-output flow: + * Inputs: [Satellite Loader (meta 1)] [Station Chip] + * Outputs: [Packed Station] [Station Chip (when new station)] + */ +public class StationAssemblerCategory implements IRecipeCategory { + + private final IDrawable background; + private final IDrawable icon; + private final IDrawable slotFrame; + private static final int BG_W = 180; + private static final int BG_H = 90; + + private static final ResourceLocation ROCKET_BUILDER_PNG = + new ResourceLocation("advancedrocketry", "textures/gui/rocketBuilder.png"); + + private static final int PB_BACK_U = 76, PB_BACK_V = 93, PB_BACK_W = 8, PB_BACK_H = 52; + private static final int PB_FILL_U = 176, PB_FILL_V = 15, PB_FILL_W = 2, PB_FILL_H = 38; + private static final int PB_INSET_X = 3, PB_INSET_Y = 2; + private static final int ANIM_MS = 100; + private final IDrawable backBar; // background frame (8x52) + private final IDrawableStatic fillStatic; // fill slice (2x38) + private final IDrawableAnimated fillAnim; // animated fill (bottom→top) + private int _x0, _x1, _y0, _y1; + private final int barX; + private final int barY; + + public StationAssemblerCategory(IGuiHelper gui) { + this.background = gui.createBlankDrawable(BG_W, BG_H); + this.icon = gui.createDrawableIngredient( + new net.minecraft.item.ItemStack(zmaster587.advancedRocketry.api.AdvancedRocketryBlocks.blockStationBuilder)); + this.slotFrame = gui.getSlotDrawable(); + + // build drawables from the exact atlas slices + this.backBar = gui.createDrawable(ROCKET_BUILDER_PNG, PB_BACK_U, PB_BACK_V, PB_BACK_W, PB_BACK_H); + this.fillStatic = gui.createDrawable(ROCKET_BUILDER_PNG, PB_FILL_U, PB_FILL_V, PB_FILL_W, PB_FILL_H); + this.fillAnim = gui.createAnimatedDrawable(fillStatic, ANIM_MS, StartDirection.BOTTOM, /*inverted*/ false); + + // position: right edge, centered Y + this.barX = BG_W - PB_BACK_W; + this.barY = (BG_H - PB_BACK_H) / 2; + } + + @Override public String getUid() { return ARPlugin.stationAssemblerUUID; } + @Override + public String getTitle() { + return new net.minecraft.item.ItemStack( + zmaster587.advancedRocketry.api.AdvancedRocketryBlocks.blockStationBuilder + ).getDisplayName(); + } + @Override public String getModName() { return "Advanced Rocketry"; } + @Override public IDrawable getBackground(){ return background; } + @Override public IDrawable getIcon() { return icon; } + + // keep your BG_W, BG_H, progress bar fields as-is... + + @Override + public void setRecipe(IRecipeLayout layout, StationAssemblerWrapper wrapper, IIngredients ing) { + IGuiItemStackGroup items = layout.getItemStacks(); + + // compact columns; roomy rows + final int SLOT = 18; + final int COL_GAP = 2; // close together horizontally + final int ROW_GAP = 24; // more empty space vertically + + final int widthNeeded = SLOT * 2 + COL_GAP; // 38 + final int heightNeeded = SLOT * 2 + ROW_GAP; // 60 + final int left = (BG_W - widthNeeded) / 2; + final int top = (BG_H - heightNeeded) / 2; + + final int x0 = left; + final int x1 = left + SLOT + COL_GAP; + final int y0 = top; + final int y1 = top + SLOT + ROW_GAP; + + // row 1: [ bay | empty chip ] + items.init(0, true, x0, y0); // bay (Satellite Loader meta 1) + items.init(1, true, x1, y0); // empty chip + + // row 2: [ packed item | programmed chip ] + items.init(2, false, x0, y1); // packed station + items.init(3, false, x1, y1); // programmed chip + + // bind ingredients + java.util.List> inLists = + ing.getInputs(mezz.jei.api.ingredients.VanillaTypes.ITEM); + java.util.List> outLists = + ing.getOutputs(mezz.jei.api.ingredients.VanillaTypes.ITEM); + + if (inLists.size() >= 1) items.set(0, inLists.get(0)); + if (inLists.size() >= 2) items.set(1, inLists.get(1)); + if (outLists.size() >= 1) items.set(2, outLists.get(0)); + if (outLists.size() >= 2) items.set(3, outLists.get(1)); + + // programmed chip: strip original tooltip, show only our hint + items.addTooltipCallback((slot, input, stack, tooltip) -> { + if (slot != 3 || stack == null || stack.isEmpty() + || !(stack.getItem() instanceof zmaster587.advancedRocketry.item.ItemStationChip)) return; + + // Only when chip is unprogrammed + if (zmaster587.advancedRocketry.item.ItemStationChip.getUUID(stack) == 0) { + // Vanilla "unprogrammed" text (with and without gray formatting) + final String vanilla = zmaster587.libVulpes.LibVulpes.proxy.getLocalizedString("msg.unprogrammed"); + final String vanillaGray = net.minecraft.util.text.TextFormatting.GRAY + vanilla; + + // Strip just that line (handle formatting/no-format) + tooltip.removeIf(line -> + line.equals(vanillaGray) || + line.equals(vanilla) || + net.minecraft.util.text.TextFormatting.getTextWithoutFormattingCodes(line).equals(vanilla) + ); + + // Insert our JEI-specific hint + tooltip.add(net.minecraft.util.text.TextFormatting.GRAY + + zmaster587.libVulpes.LibVulpes.proxy.getLocalizedString( + "jei.ar.stationAssembler.newStationChipHint" + ) + ); + } + }); + + + // keep for drawing slot frames + this._x0 = x0; this._x1 = x1; this._y0 = y0; this._y1 = y1; + } + + @Override + public void drawExtras(Minecraft mc) { + // progress bar (exact sprite, right-aligned, centered Y) + backBar.draw(mc, barX, barY); + fillAnim.draw(mc, barX + PB_INSET_X, barY + PB_INSET_Y); + + // draw slot frames at the centered positions + slotFrame.draw(mc, _x0, _y0); + slotFrame.draw(mc, _x1, _y0); + slotFrame.draw(mc, _x0, _y1); + slotFrame.draw(mc, _x1, _y1); + } +} diff --git a/src/main/java/zmaster587/advancedRocketry/integration/jei/stationAssembler/StationAssemblerRecipeHandler.java b/src/main/java/zmaster587/advancedRocketry/integration/jei/stationAssembler/StationAssemblerRecipeHandler.java new file mode 100644 index 000000000..8ff1af524 --- /dev/null +++ b/src/main/java/zmaster587/advancedRocketry/integration/jei/stationAssembler/StationAssemblerRecipeHandler.java @@ -0,0 +1,12 @@ +package zmaster587.advancedRocketry.integration.jei.stationAssembler; + +import mezz.jei.api.recipe.IRecipeHandler; +import mezz.jei.api.recipe.IRecipeWrapper; +import zmaster587.advancedRocketry.integration.jei.ARPlugin; + +public class StationAssemblerRecipeHandler implements IRecipeHandler { + @Override public Class getRecipeClass() { return StationAssemblerWrapper.class; } + @Override public String getRecipeCategoryUid(StationAssemblerWrapper r) { return ARPlugin.stationAssemblerUUID; } + @Override public IRecipeWrapper getRecipeWrapper(StationAssemblerWrapper r) { return r; } + @Override public boolean isRecipeValid(StationAssemblerWrapper r) { return r != null; } +} diff --git a/src/main/java/zmaster587/advancedRocketry/integration/jei/stationAssembler/StationAssemblerRecipeMaker.java b/src/main/java/zmaster587/advancedRocketry/integration/jei/stationAssembler/StationAssemblerRecipeMaker.java new file mode 100644 index 000000000..2209d379c --- /dev/null +++ b/src/main/java/zmaster587/advancedRocketry/integration/jei/stationAssembler/StationAssemblerRecipeMaker.java @@ -0,0 +1,17 @@ +package zmaster587.advancedRocketry.integration.jei.stationAssembler; + +import mezz.jei.api.IJeiHelpers; +import java.util.Collections; +import java.util.List; + +/** Single illustrative entry: the Station Assembler has no craft-list; it’s a process gate. */ +public class StationAssemblerRecipeMaker { + public static List getRecipes(IJeiHelpers helpers) { + return java.util.Collections.singletonList(new StationAssemblerWrapper()); + } + + // Keep this to match existing pattern in your makers + public static List getMachineRecipes(IJeiHelpers helpers, Class ignored) { + return getRecipes(helpers); + } +} diff --git a/src/main/java/zmaster587/advancedRocketry/integration/jei/stationAssembler/StationAssemblerWrapper.java b/src/main/java/zmaster587/advancedRocketry/integration/jei/stationAssembler/StationAssemblerWrapper.java new file mode 100644 index 000000000..496a26307 --- /dev/null +++ b/src/main/java/zmaster587/advancedRocketry/integration/jei/stationAssembler/StationAssemblerWrapper.java @@ -0,0 +1,68 @@ +package zmaster587.advancedRocketry.integration.jei.stationAssembler; + +import mezz.jei.api.ingredients.IIngredients; +import mezz.jei.api.recipe.IRecipeWrapper; +import net.minecraft.item.ItemStack; +import zmaster587.advancedRocketry.api.AdvancedRocketryBlocks; +import zmaster587.advancedRocketry.api.AdvancedRocketryItems; + +/** + * JEI wrapper for the Station Assembler flow shown in TileStationAssembler. + * Inputs (by code): + * - slot 0: AdvancedRocketryBlocks.blockLoader (meta 1) -> Satellite Loading Hatch + * - slot 1: AdvancedRocketryItems.itemSpaceStationChip -> Station Chip (can be blank or programmed) + * Outputs (by code): + * - slot 2: AdvancedRocketryItems.itemSpaceStation -> Packed station (ItemPackedStructure) + * - slot 3: AdvancedRocketryItems.itemSpaceStationChip -> New chip ONLY when making a brand-new station + * + * We present both outputs (JEI is illustrative, not conditional). + */ +public class StationAssemblerWrapper implements IRecipeWrapper { + + private final ItemStack inputHatch; + private final ItemStack inputChip; + private final ItemStack outStation; + private final ItemStack outChipMaybe; + + public StationAssemblerWrapper() { + // Input 0: blockLoader with meta 1 + this.inputHatch = new ItemStack(AdvancedRocketryBlocks.blockLoader, 1, 1); + + // Input 1: station chip item (no NBT required for JEI showcase) + this.inputChip = new ItemStack(AdvancedRocketryItems.itemSpaceStationChip); + + // Output 2: station item + this.outStation = new ItemStack(AdvancedRocketryItems.itemSpaceStation); + + // Output 3: station chip (appears when creating a new station) + this.outChipMaybe = new ItemStack(AdvancedRocketryItems.itemSpaceStationChip); + } + + @Override + public void getIngredients(IIngredients ing) { + // --- Inputs (your two visible inputs) --- + java.util.List> inputs = new java.util.ArrayList<>(3); + inputs.add(java.util.Collections.singletonList(inputHatch)); // bay (loader meta 1) + inputs.add(java.util.Collections.singletonList(inputChip)); // empty chip + + // --- Hidden machine block for discoverability (so R/U on block opens this page) --- + ItemStack stationBlock = new ItemStack( + zmaster587.advancedRocketry.api.AdvancedRocketryBlocks.blockStationBuilder + ); + inputs.add(java.util.Collections.singletonList(stationBlock)); + + ing.setInputLists(mezz.jei.api.ingredients.VanillaTypes.ITEM, inputs); + + // --- Outputs (show both possible results of "Build") --- + java.util.List outs = new java.util.ArrayList<>(3); + outs.add(outStation); + if (outChipMaybe != null && !outChipMaybe.isEmpty()) { + outs.add(outChipMaybe); + } + + // Also include the block as an output so R on the block finds this page too + outs.add(stationBlock); + + ing.setOutputs(mezz.jei.api.ingredients.VanillaTypes.ITEM, outs); + } +} diff --git a/src/main/java/zmaster587/advancedRocketry/inventory/modules/ModuleContainerPanYOnlyWithScrollCache.java b/src/main/java/zmaster587/advancedRocketry/inventory/modules/ModuleContainerPanYOnlyWithScrollCache.java new file mode 100644 index 000000000..5e5265475 --- /dev/null +++ b/src/main/java/zmaster587/advancedRocketry/inventory/modules/ModuleContainerPanYOnlyWithScrollCache.java @@ -0,0 +1,281 @@ +package zmaster587.advancedRocketry.inventory.modules; + +import zmaster587.libVulpes.inventory.modules.ModuleContainerPanYOnly; +import zmaster587.libVulpes.inventory.modules.ModuleBase; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.gui.inventory.GuiContainer; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.client.event.GuiScreenEvent; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicInteger; + +public class ModuleContainerPanYOnlyWithScrollCache extends ModuleContainerPanYOnly { + + // ===== Single-slot global cache (latest write wins; no memory growth) ===== + private static final int NO_SCROLL = Integer.MIN_VALUE; + private static final AtomicInteger GLOBAL_SCROLL = new AtomicInteger(NO_SCROLL); + + // ===== Track live instances so the event handler can find "this" without editing GuiModular ===== + @SideOnly(Side.CLIENT) + private static final CopyOnWriteArrayList> LIVE = + new CopyOnWriteArrayList<>(); + + @SideOnly(Side.CLIENT) + private WeakReference lastGui = new WeakReference<>(null); + + @SideOnly(Side.CLIENT) + private static volatile boolean EVENT_REGISTERED = false; + + // >>> Store GUI origin captured during render to avoid touching protected xSize/ySize + @SideOnly(Side.CLIENT) + private volatile int lastGuiLeft = 0, lastGuiTop = 0; + + // ===== Instance state ===== + private int lastSavedY = NO_SCROLL; + private boolean didRestore = false; + + // Debounce to avoid micro-stutter under heavy input + private long lastSaveNs = 0L; + private static final long SAVE_INTERVAL_NS = 30_000_000L; // 30 ms + + public ModuleContainerPanYOnlyWithScrollCache( + int offsetX, int offsetY, + List moduleList, List staticModules, + ResourceLocation backdrop, + int screenSizeX, int screenSizeY, + int paddingX, int paddingY, + int containerSizeX, int containerSizeY + ) { + super(offsetX, offsetY, moduleList, staticModules, backdrop, + screenSizeX, screenSizeY, paddingX, paddingY, + containerSizeX, containerSizeY); + + // Register this instance for event routing (client-side only) + if (Minecraft.getMinecraft() != null) { + LIVE.add(new WeakReference<>(this)); + maybeRegisterEventHandler(); + } + } + + // Ensure we don’t leak refs if the module gets disabled/detached + @Override + public void setEnabled(boolean state) { + if (!state && this.isEnabled()) { + saveScrollIfChangedForce(); // persist last position + } + super.setEnabled(state); + // Optional: prune dead refs occasionally + pruneDeadRefs(); + } + + // Clamp to base’s legal range [-containerSizeY, 0] + private int clampScroll(int y) { + if (y > 0) return 0; + int min = -this.containerSizeY; + return (y < min) ? min : y; + } + + // Debounced save after movement, skipping no-op writes globally and per-instance + private void saveScrollIfChanged() { + final int y = clampScroll(super.getScrollY()); + + // Per-instance no-op: if we already observed y, don't do anything. + if (y == lastSavedY) return; + + // Global no-op: if the global cache already holds y, skip the write, but + // update our lastSavedY so we don't keep re-checking. + final int global = GLOBAL_SCROLL.get(); + if (global == y) { + lastSavedY = y; + return; + } + + // Keep debounce to avoid bursts during fine-grained drags. + final long now = System.nanoTime(); + if (now - lastSaveNs < SAVE_INTERVAL_NS) { + lastSavedY = y; // remember the new y even if we didn't write globally yet + return; + } + + lastSaveNs = now; + lastSavedY = y; + + // Use lazySet for cheap release write, perfectly fine for a UI cache + GLOBAL_SCROLL.lazySet(y); + + // DEBUG + //System.out.println("[SCROLLER] save y=" + y); + } + + + // Force save (bypass debounce) on close or disable, still skipping no-op + private void saveScrollIfChangedForce() { + final int y = clampScroll(super.getScrollY()); + + // If both our last and the global already equal y, it's a no-op + if (y == lastSavedY && GLOBAL_SCROLL.get() == y) return; + + lastSavedY = y; + lastSaveNs = System.nanoTime(); + + GLOBAL_SCROLL.lazySet(y); + + // DEBUG + //System.out.println("[SCROLLER] force-save y=" + y); + } + + // Restore once when bounds are stable + @Override + @SideOnly(Side.CLIENT) + public void renderBackground(GuiContainer gui, int x, int y, int mouseX, int mouseY, FontRenderer font) { + // Remember the GUI we’re rendering in so the event handler can filter by current screen + this.lastGui = new WeakReference<>(gui); + + // >>> Capture guiLeft/guiTop from the parameters + this.lastGuiLeft = x; + this.lastGuiTop = y; + + if (!didRestore) { + int v = GLOBAL_SCROLL.get(); + if (v != NO_SCROLL) { + int clamped = clampScroll(v); + super.setOffset2(-clamped); // base uses -y + lastSavedY = clamped; + //System.out.println("[SCROLLER] restore y=" + clamped); // DEBUG + } + didRestore = true; + } + super.renderBackground(gui, x, y, mouseX, mouseY, font); + } + + + @Override + @SideOnly(Side.CLIENT) + public void renderForeground(int guiOffsetX, int guiOffsetY, int mouseX, int mouseY, float zLevel, + GuiContainer gui, FontRenderer font) { + super.renderForeground(guiOffsetX, guiOffsetY, mouseX, mouseY, zLevel, gui, font); + } + + // Single save point for any movement + @Override + protected void moveContainerInterior(int deltaY) { + super.moveContainerInterior(deltaY); + saveScrollIfChanged(); + } + + // Base onScroll calls moveContainerInterior; don’t double-save here + @Override + public void onScroll(int dwheel) { + super.onScroll(dwheel); + } + + @Override + @SideOnly(Side.CLIENT) + public void onMouseClickedAndDragged(int x, int y, int button, long timeSinceLastClick) { + super.onMouseClickedAndDragged(x, y, button, timeSinceLastClick); + } + + // Public clear (e.g., on new scan) + public static void clearScrollCache() { + GLOBAL_SCROLL.set(NO_SCROLL); + //System.out.println("[SCROLLER] clear cache"); // DEBUG + } + + // ===== Event routing (client-side only) ===== + + @SideOnly(Side.CLIENT) + private static void maybeRegisterEventHandler() { + if (!EVENT_REGISTERED) { + MinecraftForge.EVENT_BUS.register(new WheelRouter()); + EVENT_REGISTERED = true; + } + } + + @SideOnly(Side.CLIENT) + private static void pruneDeadRefs() { + for (WeakReference ref : LIVE) { + if (ref.get() == null) LIVE.remove(ref); + } + } + + @SideOnly(Side.CLIENT) + private boolean isMouseOverThis(int relX, int relY) { + // relX/relY are GUI-relative to (guiLeft, guiTop) + int localX = relX - this.offsetX; + int localY = relY - this.offsetY; + return localX >= 0 && localX < this.screenSizeX + && localY >= 0 && localY < this.screenSizeY; + } + + @SideOnly(Side.CLIENT) + private boolean isOnThisGui(GuiScreen current) { + GuiContainer g = lastGui.get(); + return g != null && g == current; + } + + @SideOnly(Side.CLIENT) + private static class WheelRouter { + private static int lastTickDispatched = -1; + private static int lastScreenId = 0; + private static int lastWheelSign = 0; // -1/+1 + + @SubscribeEvent + public void onMouseInputPre(GuiScreenEvent.MouseInputEvent.Pre evt) throws IOException { + GuiScreen screen = evt.getGui(); + if (!(screen instanceof GuiContainer)) return; + + int d = org.lwjgl.input.Mouse.getEventDWheel(); + if (d == 0) return; + + Minecraft mc = Minecraft.getMinecraft(); + int tick = (mc.ingameGUI != null) ? mc.ingameGUI.getUpdateCounter() : 0; + int screenId = System.identityHashCode(screen); + int sign = Integer.signum(d); + + // Coalesce: same screen + same tick + same direction => treat as duplicate + if (tick == lastTickDispatched && screenId == lastScreenId && sign == lastWheelSign) { + evt.setCanceled(true); + return; + } + + int scaledW = screen.width, scaledH = screen.height; + int mouseX = org.lwjgl.input.Mouse.getX() * scaledW / mc.displayWidth; + int mouseY = scaledH - org.lwjgl.input.Mouse.getY() * scaledH / mc.displayHeight - 1; + + boolean handled = false; + for (int i = LIVE.size() - 1; i >= 0; i--) { + WeakReference ref = LIVE.get(i); + ModuleContainerPanYOnlyWithScrollCache mod = ref.get(); + if (mod == null) { LIVE.remove(i); continue; } + if (!mod.getVisible() || !mod.isEnabled()) continue; + if (!mod.isOnThisGui(screen)) continue; + + int relX = mouseX - mod.lastGuiLeft; + int relY = mouseY - mod.lastGuiTop; + if (!mod.isMouseOverThis(relX, relY)) continue; + + mod.onScroll(d); // will call moveContainerInterior -> save + handled = true; + break; + } + + if (handled) { + lastTickDispatched = tick; + lastScreenId = screenId; + lastWheelSign = sign; + evt.setCanceled(true); + } + } + } +} diff --git a/src/main/java/zmaster587/advancedRocketry/inventory/modules/ModuleSatelliteTerminal.java b/src/main/java/zmaster587/advancedRocketry/inventory/modules/ModuleSatelliteTerminal.java new file mode 100644 index 000000000..c9ce27b44 --- /dev/null +++ b/src/main/java/zmaster587/advancedRocketry/inventory/modules/ModuleSatelliteTerminal.java @@ -0,0 +1,225 @@ +package zmaster587.advancedRocketry.inventory.modules; + +import java.util.Collections; +import java.util.List; + +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.inventory.GuiContainer; +import net.minecraft.inventory.Container; +import net.minecraft.inventory.IContainerListener; +import net.minecraft.inventory.IInventory; +import net.minecraft.inventory.Slot; +import net.minecraft.item.ItemStack; + +import zmaster587.advancedRocketry.tile.satellite.TileSatelliteTerminal; +import zmaster587.advancedRocketry.dimension.DimensionManager; +import zmaster587.advancedRocketry.util.PlanetaryTravelHelper; +import zmaster587.advancedRocketry.satellite.SatelliteData; +import zmaster587.advancedRocketry.api.satellite.SatelliteBase; +import zmaster587.advancedRocketry.item.ItemSatelliteIdentificationChip; + +import zmaster587.libVulpes.LibVulpes; +import zmaster587.libVulpes.inventory.modules.ModuleBase; +import zmaster587.libVulpes.inventory.modules.ModuleText; + +/** + * Per-viewer status module for the Satellite Control Center. + * Forces a sync every 0.5s (9 ticks) while the GUI is open. + * Sends 4 ints: 0=status, 1=ppt, 2=data, 3=maxdata. + */ +public class ModuleSatelliteTerminal extends ModuleBase { + + private final ModuleText text; + private final int color; + private final IInventory inv; // client: read chip name + private final TileSatelliteTerminal tile; // server: compute values + + private static final long PERIOD_TICKS = 9L; + // {status, ppt, data, max} + private final int[] vals = new int[4]; + + + // Force burst every 9 ticks + private long lastPushBucket = Long.MIN_VALUE; + private boolean burstPending = false; + + + // Add field to track current chip/satellite identity (server-side) + private long lastSatId = Long.MIN_VALUE; // or int; use -1 for "no sat" + + private static long getCurrentSatId(TileSatelliteTerminal t) { + zmaster587.advancedRocketry.api.satellite.SatelliteBase sat = t.getSatelliteFromSlot(0); + if (sat == null) return -1L; + return sat.getId(); // adjust if getId() is int; cast/convert as needed + } + + public ModuleSatelliteTerminal(int x, int y, int color) { + this(x, y, color, null, null); + } + + public ModuleSatelliteTerminal(int x, int y, int color, IInventory inv, TileSatelliteTerminal tile) { + super(x, y); + this.color = color; + this.inv = inv; + this.tile = tile; + this.text = new ModuleText(x, y, "", color); + this.text.setText(LibVulpes.proxy.getLocalizedString("msg.satctrlcenter.nolink")); + } + + @Override + public void renderForeground(int x, int y, int mouseX, int mouseY, float zLevel, + GuiContainer gui, FontRenderer font) { + } + + @Override + public void renderBackground(GuiContainer gui, int x, int y, int mouseX, int mouseY, + FontRenderer font) { + + text.renderBackground(gui, x, y, mouseX, mouseY, font); + } + + @Override + public List getSlots(Container container) { return Collections.emptyList(); } + + @Override public int numberOfChangesToSend() { return 4; } + + // Some libVulpes builds use needsUpdate; keep it mapped to our logic. + @Override + public boolean needsUpdate(int localId) { return isUpdateRequired(localId); } + + @Override + public void sendInitialChanges(Container container, IContainerListener listener, int moduleIndex) { + if (tile != null && !tile.getWorld().isRemote) { + int[] now = computeStatusFromTile(tile); + for (int i = 0; i < 4; i++) vals[i] = now[i]; + lastSatId = getCurrentSatId(tile); + long t = tile.getWorld().getTotalWorldTime(); + lastPushBucket = t / PERIOD_TICKS; + } + + for (int i = 0; i < 4; i++) { + listener.sendWindowProperty(container, moduleIndex + i, vals[i]); + } + burstPending = false; // reset + } + + @Override + public boolean isUpdateRequired(int relativeIdx) { + if (tile != null && !tile.getWorld().isRemote) { + final long t = tile.getWorld().getTotalWorldTime(); + final long bucket = t / PERIOD_TICKS; + + // Detect satellite/chip change + final long curSatId = getCurrentSatId(tile); + final boolean satChanged = (curSatId != lastSatId); + if (satChanged) lastSatId = curSatId; + + // Arm a new burst on bucket edge OR sat change + if (bucket != lastPushBucket || satChanged) { + lastPushBucket = bucket; + + // Compute all four, assign immediately so all lanes read the same snapshot + final int[] now = computeStatusFromTile(tile); + System.arraycopy(now, 0, vals, 0, 4); + + // One atomic send of all lanes this tick + burstPending = true; + + } + } + + // During a burst, ALL lanes return true so container sends 0..3 in one pass. + return burstPending; + } + + @Override + public void sendChanges(Container container, IContainerListener listener, + int variableId, int relativeIdx) { + // 'variableId' IS the global property id. Do NOT add relativeIdx. + listener.sendWindowProperty(container, variableId, vals[relativeIdx]); + + // Clear the burst only after the last lane goes out + if (relativeIdx == 3) { + burstPending = false; + } + } + + + + @Override + public void onChangeRecieved(int relativeIdx, int value) { + vals[relativeIdx] = value; + rebuildClientText(); + } + + // ---- Helpers ---- + + private static int[] computeStatusFromTile(TileSatelliteTerminal t) { + int status = 0, ppt = 0, data = 0, max = 0; + + SatelliteBase sat = t.getSatelliteFromSlot(0); + + // --- Case 1: No chip or invalid satellite --- + if (!(sat instanceof SatelliteData)) { + return new int[] { 0, 0, 0, 0 }; + } + + // --- Case 2: Valid satellite --- + boolean hasPower = t.getUniversalEnergyStored() >= t.getPowerPerOperation(); + int hereDim = DimensionManager.getEffectiveDimId(t.getWorld(), t.getPos()).getId(); + boolean inRange = PlanetaryTravelHelper.isTravelAnywhereInPlanetarySystem(sat.getDimensionId(), hereDim); + + if (!hasPower) { + status = 1; // Not enough power + } else if (!inRange) { + status = 2; // Out of range + } else { + status = 3; // OK and connected + + SatelliteData s = (SatelliteData) sat; + ppt = s.getPowerPerTick(); // Power generation rate + data = s.data.getData(); // Current data amount + max = s.data.getMaxData(); // Maximum storage + } + + // --- Always return all four fields --- + return new int[] { status, ppt, data, max }; + } + + + // Client: rebuild visible text; sat name read locally from chip + private void rebuildClientText() { + final int status = vals[0]; + final int ppt = vals[1]; + final int data = vals[2]; + final int max = vals[3]; + + String satName = null; + if (inv != null && inv.getSizeInventory() > 0) { + ItemStack stack0 = inv.getStackInSlot(0); + if (!stack0.isEmpty() && stack0.getItem() instanceof ItemSatelliteIdentificationChip) { + SatelliteBase sat = ItemSatelliteIdentificationChip.getSatellite(stack0); + if (sat != null) satName = sat.getName(); + } + } + + String msg; + if (status == 0) { + msg = LibVulpes.proxy.getLocalizedString("msg.satctrlcenter.nolink"); + } else if (status == 1) { + msg = LibVulpes.proxy.getLocalizedString("msg.notenoughpower"); + } else if (status == 2) { + msg = LibVulpes.proxy.getLocalizedString("msg.satctrlcenter.toofar"); + } else { + msg = LibVulpes.proxy.getLocalizedString("msg.satctrlcenter.info") + + "\nPower gen.: " + ppt + "\nData: " + data + "/" + max; + } + + if (satName != null && !satName.isEmpty()) { + msg = satName + "\n\n" + msg; + } + + text.setText(msg); + text.setColor(color); + } +} diff --git a/src/main/java/zmaster587/advancedRocketry/inventory/modules/ModuleWirelessBufferBar.java b/src/main/java/zmaster587/advancedRocketry/inventory/modules/ModuleWirelessBufferBar.java new file mode 100644 index 000000000..3ec7c6e66 --- /dev/null +++ b/src/main/java/zmaster587/advancedRocketry/inventory/modules/ModuleWirelessBufferBar.java @@ -0,0 +1,138 @@ +package zmaster587.advancedRocketry.inventory.modules; + +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.inventory.GuiContainer; +import net.minecraft.client.resources.I18n; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import zmaster587.advancedRocketry.api.DataStorage; +import zmaster587.advancedRocketry.api.DataStorage.DataType; +import zmaster587.libVulpes.gui.CommonResources; +import zmaster587.libVulpes.inventory.modules.ModuleBase; + +import java.util.LinkedList; +import java.util.List; + +/** + * Minimal, read-only data bar for the wireless transceiver's internal buffer. + * - No buttons + * - No slots + * - Server-authoritative: syncs amount, max, and type via tiny window properties. + */ +public class ModuleWirelessBufferBar extends ModuleBase { + + // Reuse same visuals as ModuleData so it looks consistent + static final int BAR_Y_SIZE = 38; + static final int BAR_X_SIZE = 6; + static final int TEX_OFFSET_X = 0; + static final int TEX_OFFSET_Y = 215; + + private final DataStorage data; // points to your uiBuffer + private int prevAmount = -1; + private int prevMax = -1; + private int prevTypeOrdinal = -1; + + public ModuleWirelessBufferBar(int offsetX, int offsetY, DataStorage data) { + super(offsetX, offsetY); + this.data = data; + this.sizeX = 10; // hitbox-ish; not used for layout + this.sizeY = BAR_Y_SIZE + 12; + } + + @Override + public int numberOfChangesToSend() { + // amount, max, type + return 3; + } + + @Override + public boolean needsUpdate(int localId) { + switch (localId) { + case 0: return data.getData() != prevAmount; + case 1: return data.getMaxData() != prevMax; + case 2: return data.getDataType().ordinal() != prevTypeOrdinal; + default: return false; + } + } + + @Override + protected void updatePreviousState(int localId) { + if (localId == 0) prevAmount = data.getData(); + else if (localId == 1) prevMax = data.getMaxData(); + else if (localId == 2) prevTypeOrdinal = data.getDataType().ordinal(); + } + + @Override + public void sendChanges(net.minecraft.inventory.Container container, + net.minecraft.inventory.IContainerListener crafter, + int variableId, int localId) { + int v; + if (localId == 0) v = data.getData(); + else if (localId == 1) v = data.getMaxData(); + else /* localId == 2 */ v = data.getDataType().ordinal(); + crafter.sendWindowProperty(container, variableId, v); + } + + @Override + public void onChangeRecieved(int slot, int value) { + if (slot == 0) { + // amount (type set below or left unchanged) + data.setData(value, DataType.UNDEFINED); + } else if (slot == 1) { + data.setMaxData(value); + } else if (slot == 2) { + DataType t = DataType.values()[Math.max(0, Math.min(DataType.values().length - 1, value))]; + data.setDataType(t); + } + } + + @SideOnly(Side.CLIENT) + @Override + public void renderForeground(int guiOffsetX, int guiOffsetY, int mouseX, int mouseY, float zLevel, + GuiContainer gui, FontRenderer font) { + int relX = mouseX - offsetX; + int relY = mouseY - offsetY; + if (relX >= 0 && relX < BAR_X_SIZE && relY >= 0 && relY < BAR_Y_SIZE) { + List tt = new LinkedList<>(); + // "Data" + tt.add(net.minecraft.client.resources.I18n.format( + "msg.tooltip.data") + " " + data.getData() + " / " + data.getMaxData()); + + // "Type: %s" with translated type + String typeName = net.minecraft.client.resources.I18n.format(data.getDataType().toString()); + tt.add(net.minecraft.client.resources.I18n.format("msg.wirelessTransciever.type", typeName)); + + + this.drawTooltip(gui, tt, mouseX, mouseY, zLevel, font); + } + } + + + @SideOnly(Side.CLIENT) + @Override + public void renderBackground(GuiContainer gui, int x, int y, int mouseX, int mouseY, FontRenderer font) { + // Bind the correct texture (same sheet as ModuleData) + gui.mc.getTextureManager().bindTexture(CommonResources.genericBackground); + + // Draw only the bar frame (8x40 at UV 176,18) + gui.drawTexturedModalRect(offsetX + x, offsetY + y, 176, 18, 8, 40); + + // Compute fill amount + int max = Math.max(1, data.getMaxData()); + float percent = Math.min(1f, Math.max(0f, data.getData() / (float) max)); + int filled = (int) (percent * BAR_Y_SIZE); + + // Draw the green fill (6 x filled) from UV (0, 215 + (BAR_Y_SIZE - filled)) + // Fill grows upward inside the frame + if (filled > 0) { + gui.drawTexturedModalRect( + offsetX + x + 1, + offsetY + y + 1 + (BAR_Y_SIZE - filled), + 0, + 215 + (BAR_Y_SIZE - filled), + BAR_X_SIZE, + filled + ); + } + } +} diff --git a/src/main/java/zmaster587/advancedRocketry/item/ItemAsteroidChip.java b/src/main/java/zmaster587/advancedRocketry/item/ItemAsteroidChip.java index 4ecc42e2f..2c2ee55ac 100644 --- a/src/main/java/zmaster587/advancedRocketry/item/ItemAsteroidChip.java +++ b/src/main/java/zmaster587/advancedRocketry/item/ItemAsteroidChip.java @@ -1,9 +1,13 @@ package zmaster587.advancedRocketry.item; import com.mojang.realmsclient.gui.ChatFormatting; + +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.resources.I18n; import net.minecraft.client.util.ITooltipFlag; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.text.TextFormatting; import net.minecraft.world.World; import zmaster587.libVulpes.LibVulpes; @@ -23,7 +27,14 @@ public boolean isDamageable() { return false; } - + public static String shortDisplayId(Long uuid, String type) { + long base = (uuid == null) ? 0L : uuid; + long th = (type == null) ? 0L : Integer.toUnsignedLong(type.hashCode()); + long disp = mix64(base ^ (th << 1)); + String hex = Long.toUnsignedString(disp, 16).toUpperCase(); + int N = 6; + return (hex.length() > N) ? hex.substring(hex.length() - N) : hex; + } /** * Removes any Information and reset the stack to a default state * @@ -66,25 +77,50 @@ public void setType(@Nonnull ItemStack stack, String type) { nbt.setString(astType, type); stack.setTagCompound(nbt); } + // SplitMix64 mixer: great diffusion, tiny cost + // Make Unique ID from UUID and type (looks random, but is deterministic) + // Only for tooltip display purposes. Actual NBT untouched. + private static long mix64(long z) { + z += 0x9E3779B97F4A7C15L; + z = (z ^ (z >>> 30)) * 0xBF58476D1CE4E5B9L; + z = (z ^ (z >>> 27)) * 0x94D049BB133111EBL; + return z ^ (z >>> 31); + } - @Override - public void addInformation(@Nonnull ItemStack stack, World player, List list, ITooltipFlag bool) { + // Deterministic display id from UUID and type (no world dependence) + private static long makeDisplayId(Long uuid, String type) { + long base = (uuid == null) ? 0L : uuid; + long th = (type == null) ? 0L : Integer.toUnsignedLong(type.hashCode()); + return mix64(base ^ (th << 1)); // fold in type so same UUID/different types look different + } + @Override + public void addInformation(@Nonnull ItemStack stack, World world, List list, ITooltipFlag flag) { if (!stack.hasTagCompound()) { list.add(LibVulpes.proxy.getLocalizedString("msg.unprogrammed")); - } else { - if (stack.getItemDamage() == 0) { + return; + } + if (stack.getItemDamage() == 0) { + Long id = getUUID(stack); + String type = getType(stack); - list.add(LibVulpes.proxy.getLocalizedString("msg.asteroidChip.asteroid") + "-" + ChatFormatting.DARK_GREEN + getUUID(stack)); + if (type != null && !type.isEmpty()) { + list.add(LibVulpes.proxy.getLocalizedString("msg.asteroidChip.type") + ": " + + ChatFormatting.AQUA + type); + } - super.addInformation(stack, player, list, bool); + // Tooltip-only, random-looking but deterministic + final long disp = makeDisplayId(id, type); + final String hex = Long.toUnsignedString(disp, 16).toUpperCase(); - //list.add("Mass: " + unknown); - //list.add("Atmosphere Density: " + unknown); - //list.add("Distance From Star: " + unknown); + // Fixed-length visual tag + final int N = 6; + final String shortHex = (hex.length() > N) ? hex.substring(hex.length() - N) : hex; - } + list.add(LibVulpes.proxy.getLocalizedString("msg.asteroidChip.asteroid") + ": " + + ChatFormatting.DARK_GREEN + shortHex); + + super.addInformation(stack, world, list, flag); } } - } diff --git a/src/main/java/zmaster587/advancedRocketry/item/ItemAtmosphereAnalzer.java b/src/main/java/zmaster587/advancedRocketry/item/ItemAtmosphereAnalzer.java index e02ffccf4..06f01c537 100644 --- a/src/main/java/zmaster587/advancedRocketry/item/ItemAtmosphereAnalzer.java +++ b/src/main/java/zmaster587/advancedRocketry/item/ItemAtmosphereAnalzer.java @@ -6,6 +6,7 @@ import net.minecraft.client.renderer.BufferBuilder; import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.client.util.ITooltipFlag; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.inventory.EntityEquipmentSlot; @@ -26,6 +27,7 @@ import org.lwjgl.opengl.GL11; import zmaster587.advancedRocketry.atmosphere.AtmosphereHandler; import zmaster587.advancedRocketry.atmosphere.AtmosphereType; +import zmaster587.advancedRocketry.client.TooltipInjector; import zmaster587.advancedRocketry.dimension.DimensionManager; import zmaster587.advancedRocketry.event.RocketEventHandler; import zmaster587.advancedRocketry.inventory.TextureResources; @@ -107,6 +109,14 @@ public boolean isAllowedInSlot(@Nonnull ItemStack componentStack, EntityEquipmen return targetSlot == EntityEquipmentSlot.HEAD; } + @SideOnly(Side.CLIENT) + @Override + public void addInformation(ItemStack stack, @Nullable World world, List tooltip, ITooltipFlag flag) { + int insertAt = TooltipInjector.computeInsertIndex(tooltip, flag.isAdvanced()); + TooltipInjector.renderShiftAlt(stack, tooltip, "tooltip.advancedrocketry.atmanalyzer", insertAt); + } + + @Override @SideOnly(Side.CLIENT) public void renderScreen(@Nonnull ItemStack componentStack, List modules, @@ -149,5 +159,4 @@ public void renderScreen(@Nonnull ItemStack componentStack, List modu public ResourceIcon getComponentIcon(@Nonnull ItemStack armorStack) { return null; } - } diff --git a/src/main/java/zmaster587/advancedRocketry/item/ItemBeaconFinder.java b/src/main/java/zmaster587/advancedRocketry/item/ItemBeaconFinder.java index 3c385c1ad..27e263ec9 100644 --- a/src/main/java/zmaster587/advancedRocketry/item/ItemBeaconFinder.java +++ b/src/main/java/zmaster587/advancedRocketry/item/ItemBeaconFinder.java @@ -6,6 +6,7 @@ import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.client.util.ITooltipFlag; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.inventory.EntityEquipmentSlot; @@ -19,6 +20,8 @@ import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; import org.lwjgl.opengl.GL11; + +import zmaster587.advancedRocketry.client.TooltipInjector; import zmaster587.advancedRocketry.dimension.DimensionManager; import zmaster587.libVulpes.api.IArmorComponent; import zmaster587.libVulpes.client.ResourceIcon; @@ -27,6 +30,8 @@ import zmaster587.libVulpes.util.HashedBlockPosition; import javax.annotation.Nonnull; +import javax.annotation.Nullable; + import java.util.List; public class ItemBeaconFinder extends Item implements IArmorComponent { @@ -95,6 +100,13 @@ public void renderScreen(@Nonnull ItemStack componentStack, List modu } } + @SideOnly(Side.CLIENT) + @Override + public void addInformation(ItemStack stack, @Nullable World world, List tooltip, ITooltipFlag flag) { + int insertAt = TooltipInjector.computeInsertIndex(tooltip, flag.isAdvanced()); + TooltipInjector.renderShiftAlt(stack, tooltip, "tooltip.advancedrocketry.beaconfinder", insertAt); + } + @Override public ResourceIcon getComponentIcon(@Nonnull ItemStack armorStack) { return null; diff --git a/src/main/java/zmaster587/advancedRocketry/item/ItemBlockFluidTank.java b/src/main/java/zmaster587/advancedRocketry/item/ItemBlockFluidTank.java index 614a958ae..2f05015fd 100644 --- a/src/main/java/zmaster587/advancedRocketry/item/ItemBlockFluidTank.java +++ b/src/main/java/zmaster587/advancedRocketry/item/ItemBlockFluidTank.java @@ -2,6 +2,8 @@ import net.minecraft.block.Block; import net.minecraft.block.state.IBlockState; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.resources.I18n; import net.minecraft.client.util.ITooltipFlag; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemBlock; @@ -10,11 +12,15 @@ import net.minecraft.tileentity.TileEntity; import net.minecraft.util.EnumFacing; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.text.TextFormatting; import net.minecraft.world.World; import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.FluidTank; import net.minecraftforge.fluids.capability.CapabilityFluidHandler; import net.minecraftforge.fluids.capability.IFluidHandler; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import org.lwjgl.input.Keyboard; import zmaster587.advancedRocketry.api.ARConfiguration; import zmaster587.advancedRocketry.tile.TileFluidTank; @@ -29,48 +35,72 @@ public ItemBlockFluidTank(Block block) { super(block); } + /** Capacity of the item tank in mB, using the same base as the block (64_000 mB), + * preserving fractional multipliers and clamping to int range. */ + private static int getCapMb() { + // Math.round(double) -> long; keep it in long, then clamp to int range + long computed = Math.round(64000d * ARConfiguration.getCurrentConfig().blockTankCapacity); + return (int) Math.min(Integer.MAX_VALUE, Math.max(0L, computed)); + } + @Override + @SideOnly(Side.CLIENT) @ParametersAreNonnullByDefault - public void addInformation(@Nonnull ItemStack stack, @Nullable World world, List list, ITooltipFlag bool) { - super.addInformation(stack, world, list, bool); + public void addInformation(@Nonnull ItemStack stack, @Nullable World world, List list, ITooltipFlag flag) { + super.addInformation(stack, world, list, flag); - FluidStack fluidStack = getFluid(stack); + final int capMb = getCapMb(); + final FluidStack fs = getFluid(stack); - if (fluidStack == null) { - list.add("Empty"); - } else { - list.add(fluidStack.getLocalizedName() + ": " + fluidStack.amount/1000 + "/"+64* ARConfiguration.getCurrentConfig().blockTankCapacity+"b"); - } + final String fluidName = (fs != null && fs.getFluid() != null) ? fs.getLocalizedName() : "Empty"; + final int amount = (fs != null) ? fs.amount : 0; + + list.add("Fluid: " + fluidName); + list.add("Level: " + amount + "/" + capMb + " mB"); + + // --- SHIFT for more info --- + if (GuiScreen.isShiftKeyDown()) { + list.add(TextFormatting.GRAY + I18n.format("tooltip.advancedrocketry.fluidtank.shift.1")); + } else if (I18n.hasKey("tooltip.advancedrocketry.hold_shift")) { + list.add(TextFormatting.DARK_GRAY.toString() + TextFormatting.ITALIC + + I18n.format("tooltip.advancedrocketry.hold_shift")); + } } @Override @ParametersAreNonnullByDefault - public boolean placeBlockAt(@Nonnull ItemStack stack, EntityPlayer player, World world, BlockPos pos, EnumFacing side, float hitX, float hitY, float hitZ, IBlockState newState) { - super.placeBlockAt(stack, player, world, pos, side, hitX, hitY, hitZ, newState); - + public boolean placeBlockAt(@Nonnull ItemStack stack, EntityPlayer player, World world, BlockPos pos, + EnumFacing side, float hitX, float hitY, float hitZ, IBlockState newState) { + if (!super.placeBlockAt(stack, player, world, pos, side, hitX, hitY, hitZ, newState)) { + return false; + } TileEntity tile = world.getTileEntity(pos); - if (tile instanceof TileFluidTank) { IFluidHandler handler = tile.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, EnumFacing.DOWN); - ItemStack stack2 = stack.copy(); - stack2.setCount(1); - handler.fill(drain(stack2, Integer.MAX_VALUE), true); + if (handler != null) { + ItemStack one = stack.copy(); + one.setCount(1); + FluidStack drained = drain(one, Integer.MAX_VALUE); + if (drained != null && drained.amount > 0) { + handler.fill(drained, true); + } + } } - return true; } public void fill(@Nonnull ItemStack stack, FluidStack fluid) { - NBTTagCompound nbt; - FluidTank tank = new FluidTank((int) (640000* ARConfiguration.getCurrentConfig().blockTankCapacity)); + FluidTank tank = new FluidTank(getCapMb()); if (stack.hasTagCompound()) { nbt = stack.getTagCompound(); tank.readFromNBT(nbt); - } else + } else { nbt = new NBTTagCompound(); + } - tank.fill(fluid, true); + if (fluid != null) {tank.fill(fluid, true); + } tank.writeToNBT(nbt); stack.setTagCompound(nbt); @@ -78,29 +108,29 @@ public void fill(@Nonnull ItemStack stack, FluidStack fluid) { public FluidStack drain(@Nonnull ItemStack stack, int amt) { NBTTagCompound nbt; - FluidTank tank = new FluidTank((int) (640000* ARConfiguration.getCurrentConfig().blockTankCapacity)); + FluidTank tank = new FluidTank(getCapMb()); if (stack.hasTagCompound()) { nbt = stack.getTagCompound(); tank.readFromNBT(nbt); - } else + } else { nbt = new NBTTagCompound(); + } - FluidStack stack2 = tank.drain(amt, true); + FluidStack drained = tank.drain(amt, true); tank.writeToNBT(nbt); stack.setTagCompound(nbt); - return stack2; + return drained; } public FluidStack getFluid(@Nonnull ItemStack stack) { NBTTagCompound nbt; - FluidTank tank = new FluidTank((int) (640000* ARConfiguration.getCurrentConfig().blockTankCapacity)); + FluidTank tank = new FluidTank(getCapMb()); if (stack.hasTagCompound()) { nbt = stack.getTagCompound(); tank.readFromNBT(nbt); } - return tank.getFluid(); } } diff --git a/src/main/java/zmaster587/advancedRocketry/item/ItemData.java b/src/main/java/zmaster587/advancedRocketry/item/ItemData.java index 61ee7848f..4de0f0af9 100644 --- a/src/main/java/zmaster587/advancedRocketry/item/ItemData.java +++ b/src/main/java/zmaster587/advancedRocketry/item/ItemData.java @@ -1,9 +1,11 @@ package zmaster587.advancedRocketry.item; +import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.resources.I18n; import net.minecraft.client.util.ITooltipFlag; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.text.TextFormatting; import net.minecraft.world.World; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; @@ -11,6 +13,8 @@ import zmaster587.libVulpes.items.ItemIngredient; import javax.annotation.Nonnull; +import javax.annotation.Nullable; + import java.util.List; public class ItemData extends ItemIngredient { @@ -89,14 +93,29 @@ public void setData(@Nonnull ItemStack item, int amount, DataStorage.DataType da @Override @SideOnly(Side.CLIENT) - public void addInformation(@Nonnull ItemStack stack, World player, List list, ITooltipFlag bool) { - super.addInformation(stack, player, list, bool); + public void addInformation(@Nonnull ItemStack stack, @Nullable World world, List list, ITooltipFlag flag) { + super.addInformation(stack, world, list, flag); DataStorage data = getDataStorage(stack); - list.add(data.getData() + " / " + data.getMaxData() + " Data"); - list.add(I18n.format(data.getDataType().toString())); - + // Type: + list.add(TextFormatting.DARK_PURPLE + "Space Suit Component"); + String typeText = net.minecraft.client.resources.I18n.format(data.getDataType().toString()); + list.add(net.minecraft.util.text.TextFormatting.WHITE + "Type: " + typeText); + // Data: + list.add(net.minecraft.util.text.TextFormatting.WHITE + "Data stored: " + + net.minecraft.util.text.TextFormatting.GOLD + data.getData() + + net.minecraft.util.text.TextFormatting.WHITE + " / " + + net.minecraft.util.text.TextFormatting.GOLD + data.getMaxData()); + + // Hold Shift for more info + if (net.minecraft.client.gui.GuiScreen.isShiftKeyDown()) { + list.add(net.minecraft.util.text.TextFormatting.GRAY + + net.minecraft.client.resources.I18n.format("tooltip.advancedrocketry.itemdataunit.shift.1")); + } else if (net.minecraft.client.resources.I18n.hasKey("tooltip.advancedrocketry.hold_shift")) { + list.add(net.minecraft.util.text.TextFormatting.DARK_GRAY.toString() + + net.minecraft.util.text.TextFormatting.ITALIC + + net.minecraft.client.resources.I18n.format("tooltip.advancedrocketry.hold_shift")); + } } - } diff --git a/src/main/java/zmaster587/advancedRocketry/item/ItemJackHammer.java b/src/main/java/zmaster587/advancedRocketry/item/ItemJackHammer.java index 6aa3551b3..560f3554d 100644 --- a/src/main/java/zmaster587/advancedRocketry/item/ItemJackHammer.java +++ b/src/main/java/zmaster587/advancedRocketry/item/ItemJackHammer.java @@ -4,14 +4,22 @@ import net.minecraft.block.Block; import net.minecraft.block.material.Material; import net.minecraft.block.state.IBlockState; +import net.minecraft.client.util.ITooltipFlag; import net.minecraft.init.Blocks; import net.minecraft.item.ItemStack; import net.minecraft.item.ItemTool; +import net.minecraft.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; import net.minecraftforge.oredict.OreDictionary; import zmaster587.advancedRocketry.api.MaterialGeode; +import zmaster587.advancedRocketry.client.TooltipInjector; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import javax.annotation.ParametersAreNonnullByDefault; + +import java.util.List; import java.util.Set; public class ItemJackHammer extends ItemTool { @@ -40,4 +48,12 @@ public float getDestroySpeed(@Nonnull ItemStack stack, IBlockState state) { public boolean canHarvestBlock(IBlockState blockIn) { return true; } + + @SideOnly(Side.CLIENT) + @Override + public void addInformation(ItemStack stack, @Nullable World world, List tooltip, ITooltipFlag flag) { + int insertAt = TooltipInjector.computeInsertIndex(tooltip, flag.isAdvanced()); + TooltipInjector.renderShiftAlt(stack, tooltip, "tooltip.advancedrocketry.jackhammer", insertAt); + } + } diff --git a/src/main/java/zmaster587/advancedRocketry/item/ItemMultiData.java b/src/main/java/zmaster587/advancedRocketry/item/ItemMultiData.java index b0b72a2bf..e9f48657b 100644 --- a/src/main/java/zmaster587/advancedRocketry/item/ItemMultiData.java +++ b/src/main/java/zmaster587/advancedRocketry/item/ItemMultiData.java @@ -42,6 +42,14 @@ public int getData(@Nonnull ItemStack stack, DataStorage.DataType type) { public int getMaxData(@Nonnull ItemStack stack) { return getDataStorage(stack).getMaxData(); } + // Supported types for this item. Others will be ignored. + // FIX IF WE ADD MORE TYPES TO DataStorage.DataType + private static final java.util.EnumSet SUPPORTED_TYPES = + java.util.EnumSet.of( + DataStorage.DataType.COMPOSITION, + DataStorage.DataType.MASS, + DataStorage.DataType.DISTANCE + ); private MultiData getDataStorage(@Nonnull ItemStack item) { @@ -117,9 +125,9 @@ public void addInformation(@Nonnull ItemStack stack, World player, List MultiData data = getDataStorage(stack); - for (DataStorage.DataType type : DataStorage.DataType.values()) { - if (type != DataStorage.DataType.UNDEFINED) - list.add(data.getDataAmount(type) + " / " + data.getMaxData() + " " + I18n.format(type.toString(), new Object[0]) + " Data"); + for (DataStorage.DataType type : SUPPORTED_TYPES) { + final int amt = data.getDataAmount(type); + list.add(amt + " / " + data.getMaxData() + " " + I18n.format(type.toString()) + " Data"); } } } diff --git a/src/main/java/zmaster587/advancedRocketry/item/ItemPackedStructure.java b/src/main/java/zmaster587/advancedRocketry/item/ItemPackedStructure.java index 70e68cae1..3bb596dac 100644 --- a/src/main/java/zmaster587/advancedRocketry/item/ItemPackedStructure.java +++ b/src/main/java/zmaster587/advancedRocketry/item/ItemPackedStructure.java @@ -1,11 +1,19 @@ package zmaster587.advancedRocketry.item; +import net.minecraft.client.util.ITooltipFlag; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import zmaster587.advancedRocketry.client.TooltipInjector; import zmaster587.advancedRocketry.util.StorageChunk; +import java.util.List; + import javax.annotation.Nonnull; +import javax.annotation.Nullable; public class ItemPackedStructure extends Item { @@ -38,4 +46,11 @@ public StorageChunk getStructure(@Nonnull ItemStack stack) { } return null; } + + @SideOnly(Side.CLIENT) + @Override + public void addInformation(ItemStack stack, @Nullable World world, List tooltip, ITooltipFlag flag) { + int insertAt = TooltipInjector.computeInsertIndex(tooltip, flag.isAdvanced()); + TooltipInjector.renderShiftAlt(stack, tooltip, "tooltip.advancedrocketry.packedstructure", insertAt); + } } diff --git a/src/main/java/zmaster587/advancedRocketry/item/ItemSatellite.java b/src/main/java/zmaster587/advancedRocketry/item/ItemSatellite.java index 8510fc3f4..78fb03ce7 100644 --- a/src/main/java/zmaster587/advancedRocketry/item/ItemSatellite.java +++ b/src/main/java/zmaster587/advancedRocketry/item/ItemSatellite.java @@ -17,8 +17,45 @@ public class ItemSatellite extends ItemIdWithName { + private static final int CORE_SLOT = 0; + private static final int FIRST_MOD_SLOT = 1; + private static final int LAST_MOD_SLOT = 6; + + //Guarding inventory to ensure only valid items are placed in slots. + public static class SatelliteModuleInventory extends EmbeddedInventory { + public SatelliteModuleInventory() { super(7); } // slots 0-6 embedded from chassis + + @Override + public boolean isItemValidForSlot(int slot, @Nonnull ItemStack stack) { + if (stack.isEmpty()) return false; + + SatelliteProperties p = SatelliteRegistry.getSatelliteProperty(stack); + if (p == null) return false; + int f = p.getPropertyFlag(); + // only allow appropriate items in appropriate slots + if (slot == CORE_SLOT) { + return SatelliteProperties.Property.MAIN.isOfType(f); + } + + if (slot >= FIRST_MOD_SLOT && slot <= LAST_MOD_SLOT) { + return SatelliteProperties.Property.POWER_GEN.isOfType(f) || + SatelliteProperties.Property.BATTERY.isOfType(f) || + SatelliteProperties.Property.DATA.isOfType(f); + } + return false; + } + + + @Override + public void setInventorySlotContents(int index, ItemStack stack) { + if (!stack.isEmpty() && !isItemValidForSlot(index, stack)) return; + super.setInventorySlotContents(index, stack); + } + } + + public EmbeddedInventory readInvFromNBT(@Nonnull ItemStack stackIn) { - EmbeddedInventory inv = new EmbeddedInventory(7); + EmbeddedInventory inv = new SatelliteModuleInventory(); // slots 0-6 embedded from chassis, guarded by class above if (!stackIn.hasTagCompound() || !stackIn.getTagCompound().hasKey("inv")) return inv; @@ -59,47 +96,119 @@ public void setSatellite(@Nonnull ItemStack stack, SatelliteProperties propertie } - @Override - public void addInformation(@Nonnull ItemStack stack, World player, List list, ITooltipFlag bool) { - if (stack.getItem() instanceof ItemSatellite && SatelliteRegistry.getSatelliteProperties(stack) != null) { - SatelliteProperties properties = SatelliteRegistry.getSatelliteProperties(stack); + public void addInformation(@Nonnull ItemStack stack, World world, List list, ITooltipFlag flag) { + // Assembled = has properties AND a real ID (>0) + SatelliteProperties props = SatelliteRegistry.getSatelliteProperties(stack); + final boolean isAssembled = (props != null && props.getId() > 0); + if (isAssembled) { int dataStorage, powerGeneration, powerStorage; float weight; list.add(getName(stack)); - list.add("ID: " + properties.getId()); + list.add("ID: " + props.getId()); - if (SatelliteProperties.Property.BATTERY.isOfType(properties.getPropertyFlag())) { - if ((powerStorage = properties.getPowerStorage()) > 0) - list.add(LibVulpes.proxy.getLocalizedString("msg.itemsatellite.pwr") + powerStorage); - else - list.add(ChatFormatting.RED + LibVulpes.proxy.getLocalizedString("msg.itemsatellite.nopwr")); + if (SatelliteProperties.Property.BATTERY.isOfType(props.getPropertyFlag())) { + powerStorage = props.getPowerStorage(); + list.add((powerStorage > 0) + ? LibVulpes.proxy.getLocalizedString("msg.itemsatellite.pwr") + powerStorage + : ChatFormatting.RED + LibVulpes.proxy.getLocalizedString("msg.itemsatellite.nopwr")); } - if (SatelliteProperties.Property.POWER_GEN.isOfType(properties.getPropertyFlag())) { - if ((powerGeneration = properties.getPowerGeneration()) > 0) - list.add(LibVulpes.proxy.getLocalizedString("msg.itemsatellite.pwrgen") + powerGeneration); - else - list.add(ChatFormatting.RED + LibVulpes.proxy.getLocalizedString("msg.itemsatellite.nopwrgen")); + if (SatelliteProperties.Property.POWER_GEN.isOfType(props.getPropertyFlag())) { + powerGeneration = props.getPowerGeneration(); + list.add((powerGeneration > 0) + ? LibVulpes.proxy.getLocalizedString("msg.itemsatellite.pwrgen") + powerGeneration + : ChatFormatting.RED + LibVulpes.proxy.getLocalizedString("msg.itemsatellite.nopwrgen")); } - if (SatelliteProperties.Property.DATA.isOfType(properties.getPropertyFlag())) { - if ((dataStorage = properties.getMaxDataStorage()) > 0) - list.add(LibVulpes.proxy.getLocalizedString("msg.itemsatellite.data") + ZUtils.formatNumber(dataStorage)); - else - list.add(ChatFormatting.YELLOW + LibVulpes.proxy.getLocalizedString("msg.itemsatellite.nodata")); + if (SatelliteProperties.Property.DATA.isOfType(props.getPropertyFlag())) { + dataStorage = props.getMaxDataStorage(); + list.add((dataStorage > 0) + ? LibVulpes.proxy.getLocalizedString("msg.itemsatellite.data") + ZUtils.formatNumber(dataStorage) + : ChatFormatting.YELLOW + LibVulpes.proxy.getLocalizedString("msg.itemsatellite.nodata")); } - if ((weight = properties.getWeight()) > 0) - list.add(LibVulpes.proxy.getLocalizedString("msg.itemsatellite.weight") + weight); - else - list.add(ChatFormatting.YELLOW + LibVulpes.proxy.getLocalizedString("msg.itemsatellite.noweight")); + weight = props.getWeight(); + list.add((weight > 0f) + ? LibVulpes.proxy.getLocalizedString("msg.itemsatellite.weight") + weight + : ChatFormatting.YELLOW + LibVulpes.proxy.getLocalizedString("msg.itemsatellite.noweight")); + return; + } + + // --- Preview for unassembled chassis --- + EmbeddedInventory inv = readInvFromNBT(stack); - } else { + boolean hasParts = false; + for (int i = CORE_SLOT; i <= LAST_MOD_SLOT; i++) { + if (!inv.getStackInSlot(i).isEmpty()) { hasParts = true; break; } + } + if (!hasParts) { list.add(ChatFormatting.RED + LibVulpes.proxy.getLocalizedString("msg.itemsatellite.empty")); + return; + } + + int flags = 0; + int powerGen = 0, powerStor = 0, dataMax = 0; + float weight = 0f; + + // Core first: flags + preview type name (no weight from core) + ItemStack core = inv.getStackInSlot(CORE_SLOT); + if (!core.isEmpty()) { + SatelliteProperties cp = SatelliteRegistry.getSatelliteProperty(core); + if (cp != null) { + flags |= cp.getPropertyFlag(); + String satType = cp.getSatelliteType(); + SatelliteBase satBase = SatelliteRegistry.getNewSatellite(satType); + if (satBase != null) { + // Show same display name users will see after assembly + list.add(satBase.getName()); + } + } + } + + // Modules: stats + weight + for (int i = FIRST_MOD_SLOT; i <= LAST_MOD_SLOT; i++) { + ItemStack s = inv.getStackInSlot(i); + if (s.isEmpty()) continue; + + SatelliteProperties p = SatelliteRegistry.getSatelliteProperty(s); + if (p != null) { + flags |= p.getPropertyFlag(); + int f = p.getPropertyFlag(); + if (f == SatelliteProperties.Property.POWER_GEN.getFlag()) + powerGen += p.getPowerGeneration(); + else if (f == SatelliteProperties.Property.BATTERY.getFlag()) + powerStor += p.getPowerStorage(); + else if (f == SatelliteProperties.Property.DATA.getFlag()) + dataMax += p.getMaxDataStorage(); + } + weight += zmaster587.advancedRocketry.util.WeightEngine.INSTANCE.getWeight(s); } + // Match assembly semantics: base buffer is always present + powerStor += 720; + + // Always show power storage in preview (even if no battery modules are installed) + list.add(LibVulpes.proxy.getLocalizedString("msg.itemsatellite.pwr") + powerStor); + + if (SatelliteProperties.Property.POWER_GEN.isOfType(flags)) { + list.add((powerGen > 0) + ? LibVulpes.proxy.getLocalizedString("msg.itemsatellite.pwrgen") + powerGen + : ChatFormatting.RED + LibVulpes.proxy.getLocalizedString("msg.itemsatellite.nopwrgen")); + } + if (SatelliteProperties.Property.DATA.isOfType(flags)) { + list.add((dataMax > 0) + ? LibVulpes.proxy.getLocalizedString("msg.itemsatellite.data") + ZUtils.formatNumber(dataMax) + : ChatFormatting.YELLOW + LibVulpes.proxy.getLocalizedString("msg.itemsatellite.nodata")); + } + if (weight > 0f) { + list.add(LibVulpes.proxy.getLocalizedString("msg.itemsatellite.weight") + weight); + } + + // Footer LAST + list.add(ChatFormatting.RED + LibVulpes.proxy.getLocalizedString("msg.itemsatellite.unassembled")); } + } diff --git a/src/main/java/zmaster587/advancedRocketry/item/ItemSealDetector.java b/src/main/java/zmaster587/advancedRocketry/item/ItemSealDetector.java index b01a05aa2..cace938ce 100644 --- a/src/main/java/zmaster587/advancedRocketry/item/ItemSealDetector.java +++ b/src/main/java/zmaster587/advancedRocketry/item/ItemSealDetector.java @@ -1,7 +1,12 @@ package zmaster587.advancedRocketry.item; +import java.util.List; + +import javax.annotation.Nullable; + import net.minecraft.block.material.Material; import net.minecraft.block.state.IBlockState; +import net.minecraft.client.util.ITooltipFlag; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; @@ -13,6 +18,9 @@ import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.world.World; import net.minecraftforge.fluids.IFluidBlock; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import zmaster587.advancedRocketry.client.TooltipInjector; import zmaster587.advancedRocketry.util.SealableBlockHandler; /** @@ -20,7 +28,6 @@ * Created by Dark(DarkGuardsman, Robert) on 1/6/2016. */ public class ItemSealDetector extends Item { - //TODO make consume power? @Override public ActionResult onItemRightClick(World worldIn, EntityPlayer playerIn, EnumHand hand) { @@ -53,4 +60,10 @@ public EnumActionResult onItemUse(EntityPlayer player, return EnumActionResult.SUCCESS; } + @SideOnly(Side.CLIENT) + @Override + public void addInformation(ItemStack stack, @Nullable World world, List tooltip, ITooltipFlag flag) { + int insertAt = TooltipInjector.computeInsertIndex(tooltip, flag.isAdvanced()); + TooltipInjector.renderShiftAlt(stack, tooltip, "tooltip.advancedrocketry.sealdetector", insertAt); + } } diff --git a/src/main/java/zmaster587/advancedRocketry/item/ItemThermite.java b/src/main/java/zmaster587/advancedRocketry/item/ItemThermite.java index a17a52453..9b0368631 100644 --- a/src/main/java/zmaster587/advancedRocketry/item/ItemThermite.java +++ b/src/main/java/zmaster587/advancedRocketry/item/ItemThermite.java @@ -1,9 +1,17 @@ package zmaster587.advancedRocketry.item; +import net.minecraft.client.util.ITooltipFlag; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; +import net.minecraft.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import zmaster587.advancedRocketry.client.TooltipInjector; + +import java.util.List; import javax.annotation.Nonnull; +import javax.annotation.Nullable; public class ItemThermite extends Item { @@ -11,5 +19,11 @@ public class ItemThermite extends Item { public int getItemBurnTime(@Nonnull ItemStack itemStack) { return 6000; } - + @SideOnly(Side.CLIENT) + @Override + public void addInformation(ItemStack stack, @Nullable World world, List tooltip, ITooltipFlag flag) { + int insertAt = TooltipInjector.computeInsertIndex(tooltip, flag.isAdvanced()); + TooltipInjector.renderShiftAlt(stack, tooltip, "tooltip.advancedrocketry.thermite", insertAt); + } + } diff --git a/src/main/java/zmaster587/advancedRocketry/item/components/ItemJetpack.java b/src/main/java/zmaster587/advancedRocketry/item/components/ItemJetpack.java index fc219584b..1e41e822d 100644 --- a/src/main/java/zmaster587/advancedRocketry/item/components/ItemJetpack.java +++ b/src/main/java/zmaster587/advancedRocketry/item/components/ItemJetpack.java @@ -2,6 +2,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Gui; +import net.minecraft.client.util.ITooltipFlag; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.inventory.EntityEquipmentSlot; @@ -22,6 +23,7 @@ import zmaster587.advancedRocketry.api.ARConfiguration; import zmaster587.advancedRocketry.api.AdvancedRocketryFluids; import zmaster587.advancedRocketry.api.AdvancedRocketryItems; +import zmaster587.advancedRocketry.client.TooltipInjector; import zmaster587.advancedRocketry.event.RocketEventHandler; import zmaster587.advancedRocketry.inventory.TextureResources; import zmaster587.libVulpes.api.IArmorComponent; @@ -32,6 +34,8 @@ import zmaster587.libVulpes.util.InputSyncHandler; import javax.annotation.Nonnull; +import javax.annotation.Nullable; + import java.util.List; public class ItemJetpack extends Item implements IArmorComponent, IJetPack { @@ -345,4 +349,12 @@ private enum MODES { NORMAL, HOVER } + + @SideOnly(Side.CLIENT) + @Override + public void addInformation(ItemStack stack, @Nullable World world, List tooltip, ITooltipFlag flag) { + int insertAt = TooltipInjector.computeInsertIndex(tooltip, flag.isAdvanced()); + TooltipInjector.renderShiftAlt(stack, tooltip, "tooltip.advancedrocketry.jetpack", insertAt); + } + } diff --git a/src/main/java/zmaster587/advancedRocketry/item/components/ItemPressureTank.java b/src/main/java/zmaster587/advancedRocketry/item/components/ItemPressureTank.java index 0f727dc9c..163ace625 100644 --- a/src/main/java/zmaster587/advancedRocketry/item/components/ItemPressureTank.java +++ b/src/main/java/zmaster587/advancedRocketry/item/components/ItemPressureTank.java @@ -1,7 +1,9 @@ package zmaster587.advancedRocketry.item.components; import net.minecraft.client.gui.Gui; +import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.util.ITooltipFlag; +import net.minecraft.client.resources.I18n; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.inventory.EntityEquipmentSlot; @@ -9,12 +11,14 @@ import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.DamageSource; +import net.minecraft.util.text.TextFormatting; import net.minecraft.world.World; import net.minecraftforge.client.event.RenderGameOverlayEvent; import net.minecraftforge.common.capabilities.ICapabilityProvider; import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; +import org.lwjgl.input.Keyboard; import zmaster587.advancedRocketry.capability.TankCapabilityItemStack; import zmaster587.libVulpes.LibVulpes; import zmaster587.libVulpes.api.IArmorComponent; @@ -23,6 +27,8 @@ import zmaster587.libVulpes.util.FluidUtils; import javax.annotation.Nonnull; +import javax.annotation.Nullable; + import java.util.List; public class ItemPressureTank extends ItemIngredient implements IArmorComponent { @@ -34,26 +40,47 @@ public class ItemPressureTank extends ItemIngredient implements IArmorComponent public ItemPressureTank(int number, int capacity) { super(number); this.capacity = capacity; - this.maxStackSize = 1; + this.maxStackSize = 8; } - + + @SideOnly(Side.CLIENT) @Override - public void addInformation(@Nonnull ItemStack stack, World player, List list, ITooltipFlag bool) { - super.addInformation(stack, player, list, bool); - - FluidStack fluidStack = FluidUtils.getFluidForItem(stack); + public void addInformation(@Nonnull ItemStack stack, @Nullable World world, + List list, ITooltipFlag flag) { + super.addInformation(stack, world, list, flag); + + final int capMb = Math.max(0, getCapacity(stack)); + final net.minecraftforge.fluids.FluidStack fs = zmaster587.libVulpes.util.FluidUtils.getFluidForItem(stack); + + final String fluidName = (fs != null && fs.getFluid() != null) ? fs.getLocalizedName() : "Empty"; + final int amount = (fs != null) ? fs.amount : 0; + + // Match main tank style + list.add(TextFormatting.DARK_PURPLE + "Space Suit Component"); + list.add("Fluid: " + fluidName); + list.add("Level: " + amount + "/" + capMb + " mB"); + + // SHIFT block + if (GuiScreen.isShiftKeyDown()) { + list.add(TextFormatting.GRAY + I18n.format("tooltip.advancedrocketry.pressuretank.shift.1")); + } else if (I18n.hasKey("tooltip.advancedrocketry.hold_shift")) { + list.add(TextFormatting.DARK_GRAY.toString() + TextFormatting.ITALIC + + I18n.format("tooltip.advancedrocketry.hold_shift")); + } - if (fluidStack == null) { - list.add(LibVulpes.proxy.getLocalizedString("msg.empty")); - } else { - list.add(fluidStack.getLocalizedName() + ": " + fluidStack.amount); + // ALT block (independent of SHIFT) + if (Keyboard.isKeyDown(Keyboard.KEY_LMENU) || Keyboard.isKeyDown(Keyboard.KEY_RMENU)) { + list.add(TextFormatting.DARK_GRAY + I18n.format("tooltip.advancedrocketry.pressuretank.alt.1")); + list.add(TextFormatting.DARK_GRAY + I18n.format("tooltip.advancedrocketry.pressuretank.alt.2")); + } else if (I18n.hasKey("tooltip.advancedrocketry.hold_alt")) { + list.add(TextFormatting.DARK_GRAY.toString() + TextFormatting.ITALIC + + I18n.format("tooltip.advancedrocketry.hold_alt")); } } @Override public void onTick(World world, EntityPlayer player, @Nonnull ItemStack armorStack, IInventory inv, @Nonnull ItemStack componentStack) { - } @Override @@ -90,12 +117,11 @@ public boolean isAllowedInSlot(@Nonnull ItemStack stack, EntityEquipmentSlot slo @SideOnly(Side.CLIENT) public void renderScreen(@Nonnull ItemStack componentStack, List modules, RenderGameOverlayEvent event, Gui gui) { // TODO Auto-generated method stub - } + @Override public ICapabilityProvider initCapabilities(@Nonnull ItemStack stack, NBTTagCompound nbt) { return new TankCapabilityItemStack(stack, getCapacity(stack)); } - } diff --git a/src/main/java/zmaster587/advancedRocketry/mission/MissionGasCollection.java b/src/main/java/zmaster587/advancedRocketry/mission/MissionGasCollection.java index 6c94b3d21..6cb80d0cc 100644 --- a/src/main/java/zmaster587/advancedRocketry/mission/MissionGasCollection.java +++ b/src/main/java/zmaster587/advancedRocketry/mission/MissionGasCollection.java @@ -22,7 +22,6 @@ public class MissionGasCollection extends MissionResourceCollection { - private Fluid gasFluid; public MissionGasCollection() { @@ -42,14 +41,49 @@ public String getName() { @Override public void onMissionComplete() { + Object ipObj = rocketStats.getStatTag("intakePower"); + int ip = (ipObj instanceof Number) ? Math.max(0, ((Number) ipObj).intValue()) : 0; + + if (ip > 0 && gasFluid != null) { + final Fluid type = gasFluid; + + // Planned harvest written by the rocket at launch + final boolean hasPlanned = missionPersistantNBT.hasKey("plannedHarvestMb"); + final long planned = hasPlanned ? Math.max(0L, missionPersistantNBT.getLong("plannedHarvestMb")) : -1L; + + // Config + final boolean infinite = ARConfiguration.getCurrentConfig().gasHarvestInfinite; + final double mult = Math.max(0.0, ARConfiguration.getCurrentConfig().gasHarvestAmountMultiplier); + final long basePerMission = 64_000L; // mB + + long remaining; + if (hasPlanned) { + remaining = Math.min(Integer.MAX_VALUE, planned); + } else { + remaining = infinite + ? Integer.MAX_VALUE + : Math.min(Integer.MAX_VALUE, Math.round(basePerMission * mult)); + } + + - if ((int) rocketStats.getStatTag("intakePower") > 0 && gasFluid != null) { - Fluid type = gasFluid;//FluidRegistry.getFluid("hydrogen"); - //Fill gas tanks for (TileEntity tile : this.rocketStorage.getFluidTiles()) { - tile.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, null).fill(new FluidStack(type, 64000), true); + net.minecraftforge.fluids.capability.IFluidHandler handler = + tile.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, null); + if (handler == null) continue; + + if (remaining <= 0) break; + + int want = (int)Math.min(Integer.MAX_VALUE, remaining); + int couldTake = handler.fill(new FluidStack(type, want), false); // simulate + if (couldTake > 0) { + int filled = handler.fill(new FluidStack(type, couldTake), true); + remaining -= Math.max(0, filled); + } } } + + World world = DimensionManager.getWorld(launchDimension); if (world == null) { @@ -89,12 +123,19 @@ public void onMissionComplete() { @Override public void writeToNBT(NBTTagCompound nbt) { super.writeToNBT(nbt); - nbt.setString("gas", gasFluid.getName()); + if (gasFluid != null) { + nbt.setString("gas", gasFluid.getName()); + } } + public net.minecraftforge.fluids.Fluid getGasFluid() { + return gasFluid; + } + @Override public void readFromNBT(NBTTagCompound nbt) { super.readFromNBT(nbt); - gasFluid = FluidRegistry.getFluid(nbt.getString("gas")); + String name = nbt.getString("gas"); + gasFluid = name != null && !name.isEmpty() ? FluidRegistry.getFluid(name) : null; } } diff --git a/src/main/java/zmaster587/advancedRocketry/mission/MissionOreMining.java b/src/main/java/zmaster587/advancedRocketry/mission/MissionOreMining.java index 3e180f4fa..2f777071d 100644 --- a/src/main/java/zmaster587/advancedRocketry/mission/MissionOreMining.java +++ b/src/main/java/zmaster587/advancedRocketry/mission/MissionOreMining.java @@ -34,8 +34,38 @@ public MissionOreMining() { public MissionOreMining(long l, EntityRocket entityRocket, LinkedList connectedInfrastructure) { super(l, entityRocket, connectedInfrastructure); + + // Persist asteroid metadata for the monitor UI + try { + if (rocketStorage != null && rocketStorage.getGuidanceComputer() != null) { + ItemStack chip = rocketStorage.getGuidanceComputer().getStackInSlot(0); + if (!chip.isEmpty() && chip.getItem() instanceof ItemAsteroidChip) { + ItemAsteroidChip ac = (ItemAsteroidChip) chip.getItem(); + + String type = ac.getType(chip); + Long uuid = ac.getUUID(chip); + + if (type != null && !type.isEmpty()) + missionPersistantNBT.setString("asteroidType", type); + if (uuid != null) + missionPersistantNBT.setLong("asteroidUUID", uuid); + } + } + } catch (Throwable t) { + // leave fields unset; GUI will show defaults + } } + + public String getAsteroidTypeOrEmpty() { + return (missionPersistantNBT != null && missionPersistantNBT.hasKey("asteroidType")) + ? missionPersistantNBT.getString("asteroidType") : ""; + } + @javax.annotation.Nullable + public Long getAsteroidUUIDOrNull() { + return (missionPersistantNBT != null && missionPersistantNBT.hasKey("asteroidUUID")) + ? missionPersistantNBT.getLong("asteroidUUID") : null; + } @Override public void onMissionComplete() { diff --git a/src/main/java/zmaster587/advancedRocketry/mission/MissionResourceCollection.java b/src/main/java/zmaster587/advancedRocketry/mission/MissionResourceCollection.java index 18fe4e55c..8126abccf 100644 --- a/src/main/java/zmaster587/advancedRocketry/mission/MissionResourceCollection.java +++ b/src/main/java/zmaster587/advancedRocketry/mission/MissionResourceCollection.java @@ -37,6 +37,7 @@ public abstract class MissionResourceCollection extends SatelliteBase implements public MissionResourceCollection() { infrastructureCoords = new LinkedList<>(); + missionPersistantNBT = new NBTTagCompound(); } public MissionResourceCollection(long duration, EntityRocket entity, LinkedList infrastructureCoords) { @@ -48,6 +49,10 @@ public MissionResourceCollection(long duration, EntityRocket entity, LinkedList< startWorldTime = DimensionManager.getWorld(0).getTotalWorldTime(); this.duration = duration; + if (this.duration <= 0L) { + this.duration = 1L; // at least 1 tick + } + this.launchDimension = entity.world.provider.getDimension(); rocketStorage = entity.storage; rocketStats = entity.stats; @@ -62,8 +67,16 @@ public MissionResourceCollection(long duration, EntityRocket entity, LinkedList< this.infrastructureCoords.add(new HashedBlockPosition(((TileEntity) tile).getPos())); } + public long getPlannedHarvestMbOrDefault() { + if (missionPersistantNBT != null && missionPersistantNBT.hasKey("plannedHarvestMb")) { + return Math.max(0L, missionPersistantNBT.getLong("plannedHarvestMb")); + } + return -1L; // means "unknown/not provided" + } + @Override public double getProgress(World world) { + if (duration <= 0L) return 1.0d; return Math.max((AdvancedRocketry.proxy.getWorldTimeUniversal(0) - startWorldTime) / (double) duration, 0); } @@ -143,7 +156,8 @@ public void writeToNBT(NBTTagCompound nbt) { public void readFromNBT(NBTTagCompound nbt) { super.readFromNBT(nbt); - missionPersistantNBT = nbt.getCompoundTag("persist"); + missionPersistantNBT = nbt.hasKey("persist") ? nbt.getCompoundTag("persist") : new NBTTagCompound(); + rocketStats = new StatsRocket(); rocketStats.readFromNBT(nbt.getCompoundTag("rocketStats")); @@ -184,5 +198,4 @@ public void unlinkInfrastructure(IInfrastructure tile) { HashedBlockPosition pos = new HashedBlockPosition(((TileEntity) tile).getPos()); infrastructureCoords.remove(pos); } - } diff --git a/src/main/java/zmaster587/advancedRocketry/network/PacketStationUpdate.java b/src/main/java/zmaster587/advancedRocketry/network/PacketStationUpdate.java index 21eb199fd..28182d281 100644 --- a/src/main/java/zmaster587/advancedRocketry/network/PacketStationUpdate.java +++ b/src/main/java/zmaster587/advancedRocketry/network/PacketStationUpdate.java @@ -75,6 +75,7 @@ public void write(ByteBuf out) { Logger.getLogger("advancedRocketry").warning("Dimension " + stationNumber + " has thrown an exception trying to write NBT, deleting!"); DimensionManager.getInstance().deleteDimension(stationNumber); } + break; default: } } @@ -128,7 +129,8 @@ public void read(ByteBuf in) { @Override public void executeClient(EntityPlayer thePlayer) { spaceObject = SpaceObjectManager.getSpaceManager().getSpaceStation(stationNumber); - + if (spaceObject == null) return; + switch (type) { case DEST_ORBIT_UPDATE: spaceObject.setDestOrbitingBody(destOrbitingBody); @@ -140,14 +142,12 @@ public void executeClient(EntityPlayer thePlayer) { if (spaceObject instanceof SpaceStationObject) ((SpaceStationObject) spaceObject).setFuelAmount(fuel); break; - case ROTANGLE_UPDATE: - spaceObject.setRotation(rx, EnumFacing.EAST); - spaceObject.setRotation(ry, EnumFacing.UP); - spaceObject.setRotation(rz, EnumFacing.NORTH); - spaceObject.setDeltaRotation(drx, EnumFacing.EAST); - spaceObject.setDeltaRotation(dry, EnumFacing.UP); - spaceObject.setDeltaRotation(drz, EnumFacing.NORTH); + case ROTANGLE_UPDATE: { + ((SpaceStationObject) spaceObject).applyRemoteRotationState( + rx, ry, rz, drx, dry, drz + ); break; + } case SIGNAL_WHITE_BURST: PlanetEventHandler.runBurst(Minecraft.getMinecraft().world.getTotalWorldTime() + 20, 20); break; diff --git a/src/main/java/zmaster587/advancedRocketry/stations/SpaceStationObject.java b/src/main/java/zmaster587/advancedRocketry/stations/SpaceStationObject.java index 10340153f..fc6b65544 100644 --- a/src/main/java/zmaster587/advancedRocketry/stations/SpaceStationObject.java +++ b/src/main/java/zmaster587/advancedRocketry/stations/SpaceStationObject.java @@ -57,9 +57,10 @@ public class SpaceStationObject implements ISpaceObject, IPlanetDefiner { private boolean isAnchored = false; private double[] rotation; private double[] angularVelocity; - private long lastTimeModification = 0; + private final long[] lastTimeModification = new long[3]; // one per axis private DimensionProperties properties; + public SpaceStationObject() { properties = (DimensionProperties) zmaster587.advancedRocketry.dimension.DimensionManager.defaultSpaceDimensionProperties.clone(); orbitalDistance = 50.0f; @@ -75,8 +76,12 @@ public SpaceStationObject() { knownPlanetList = new HashSet<>(); angularVelocity = new double[3]; rotation = new double[3]; - } - + long now = getWorldTime(); + lastTimeModification[0] = now; + lastTimeModification[1] = now; + lastTimeModification[2] = now; + } + public Set getKnownPlanetList() { return knownPlanetList; } @@ -110,6 +115,17 @@ public void discoverPlanet(int pid) { PacketHandler.sendToAll(new PacketSpaceStationInfo(getId(), this)); } + public void applyRemoteRotationState(double rx, double ry, double rz, + double drx, double dry, double drz) { + rotation[0] = rx; rotation[1] = ry; rotation[2] = rz; + angularVelocity[0] = drx; angularVelocity[1] = dry; angularVelocity[2] = drz; + long now = getWorldTime(); + lastTimeModification[0] = now; + lastTimeModification[1] = now; + lastTimeModification[2] = now; + } + + /** * @return id of the space object (NOT the DIMID) */ @@ -209,8 +225,12 @@ public int getAltitude() { * @return rotation of the station in degrees */ public double getRotation(EnumFacing dir) { - - return (rotation[getIDFromDir(dir)] + getDeltaRotation(dir) * (getWorldTime() - lastTimeModification)) % (360D); + int idx = getIDFromDir(dir); + long dt = getWorldTime() - lastTimeModification[idx]; + double a = rotation[idx] + angularVelocity[idx] * dt; + // keep modulo stable + a = ((a % 360D) + 360D) % 360D; + return a; } /** @@ -241,8 +261,10 @@ else if (facing == EnumFacing.UP) /** * @param rotation rotation of the station in degrees */ - public void setRotation(double rotation, EnumFacing facing) { - this.rotation[getIDFromDir(facing)] = rotation; + public void setRotation(double rotDeg, EnumFacing facing) { + int idx = getIDFromDir(facing); + rotation[idx] = rotDeg; + lastTimeModification[idx] = getWorldTime(); } /** @@ -255,15 +277,17 @@ public double getDeltaRotation(EnumFacing facing) { /** * @param rotation anglarVelocity of the station in degrees per tick */ - public void setDeltaRotation(double rotation, EnumFacing facing) { + public void setDeltaRotation(double newVel, EnumFacing facing) { if (!isAnchored()) { - this.rotation[getIDFromDir(facing)] = getRotation(facing); - this.lastTimeModification = getWorldTime(); - - this.angularVelocity[getIDFromDir(facing)] = rotation; + int idx = getIDFromDir(facing); + // capture current integrated angle as the new snapshot + rotation[idx] = getRotation(facing); + lastTimeModification[idx] = getWorldTime(); + angularVelocity[idx] = newVel; } } + public double getMaxRotationalAcceleration() { return 0.02D; } diff --git a/src/main/java/zmaster587/advancedRocketry/tile/TileFluidTank.java b/src/main/java/zmaster587/advancedRocketry/tile/TileFluidTank.java index faddcb90f..c0843e49f 100644 --- a/src/main/java/zmaster587/advancedRocketry/tile/TileFluidTank.java +++ b/src/main/java/zmaster587/advancedRocketry/tile/TileFluidTank.java @@ -8,11 +8,13 @@ import net.minecraft.util.EnumFacing; import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.capability.IFluidHandler; +import net.minecraftforge.fluids.capability.IFluidTankProperties; import zmaster587.advancedRocketry.api.AdvancedRocketryBlocks; import zmaster587.advancedRocketry.world.util.WorldDummy; import zmaster587.libVulpes.tile.multiblock.hatch.TileFluidHatch; import javax.annotation.Nonnull; +import javax.annotation.Nullable; public class TileFluidTank extends TileFluidHatch { @@ -20,6 +22,11 @@ public class TileFluidTank extends TileFluidHatch { private long lastUpdateTime; private boolean fluidChanged; + private boolean removing = false; + private boolean inColumnOp = false; + + public void setRemoving(boolean removing) { this.removing = removing; } + public TileFluidTank() { super(); fluidChanged = false; @@ -30,15 +37,56 @@ public TileFluidTank(int i) { fluidChanged = false; } + // Single, reusable delegating handler (column-aware) + private final net.minecraftforge.fluids.capability.IFluidHandler selfHandler = + new net.minecraftforge.fluids.capability.IFluidHandler() { + @Override public int fill(FluidStack r, boolean doFill) { return TileFluidTank.this.fill(r, doFill); } + @Override public FluidStack drain(FluidStack r, boolean doDrain) { return TileFluidTank.this.drain(r, doDrain); } + @Override public FluidStack drain(int max, boolean doDrain) { return TileFluidTank.this.drain(max, doDrain); } + @Override public IFluidTankProperties[] getTankProperties() { return TileFluidTank.this.getTankProperties(); } + }; + + @Override + public boolean hasCapability(net.minecraftforge.common.capabilities.Capability cap, + @Nullable EnumFacing side) { + if (cap == net.minecraftforge.fluids.capability.CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) { + return true; // expose our own handler for fluids, all sides + } + return super.hasCapability(cap, side); // items and anything else come from the parent + } + + + @Override + @SuppressWarnings("unchecked") + public T getCapability(net.minecraftforge.common.capabilities.Capability cap, + @Nullable EnumFacing side) { + if (cap == net.minecraftforge.fluids.capability.CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) { + return (T) selfHandler; // never fall back to parent for fluids + } + return super.getCapability(cap, side); // items etc. still via TileFluidHatch (EmbeddedInventory) + } + + + + private void checkForUpdate() { - if (fluidChanged && world instanceof WorldDummy || world.getTotalWorldTime() - lastUpdateTime > MAX_UPDATE) { - this.markDirty(); + if (world == null) return; + if (fluidChanged && (world instanceof WorldDummy || world.getTotalWorldTime() - lastUpdateTime > MAX_UPDATE)) { + markDirty(); world.notifyBlockUpdate(pos, world.getBlockState(pos), world.getBlockState(pos), 2); lastUpdateTime = world.getTotalWorldTime(); fluidChanged = false; } } + private boolean enterColumnOp() { + if (inColumnOp) return false; + inColumnOp = true; + return true; + } + private void exitColumnOp() { inColumnOp = false; } + + @Override public SPacketUpdateTileEntity getUpdatePacket() { return new SPacketUpdateTileEntity(getPos(), getBlockMetadata(), getUpdateTag()); @@ -49,12 +97,24 @@ public void onDataPacket(NetworkManager net, SPacketUpdateTileEntity pkt) { readFromNBT(pkt.getNbtCompound()); } + @Override + public NBTTagCompound getUpdateTag() { + return writeToNBT(new NBTTagCompound()); + } + + @Override + public void handleUpdateTag(NBTTagCompound tag) { + readFromNBT(tag); + } + @Override public int fill(FluidStack resource, boolean doFill) { if (resource == null) return 0; + if (world == null || world.isRemote || removing) return 0; + TileFluidTank handler2 = this.getFluidTankInDirection(EnumFacing.UP); //Move up, check if we can fill there, do top down @@ -79,9 +139,10 @@ private int fillInternal2(FluidStack resource, boolean doFill) { if (resource2.amount > 0) amt += super.fill(resource2, doFill); - if (amt > 0 && doFill) + if (amt > 0 && doFill) { fluidChanged = true; - + markDirty(); + } checkForUpdate(); return amt; @@ -92,16 +153,28 @@ public String getModularInventoryName() { return AdvancedRocketryBlocks.blockPressureTank.getLocalizedName(); } + @Nullable + public FluidStack getOwnContentsCopy() { + FluidStack f = fluidTank.getFluid(); + return f == null ? null : f.copy(); + } + + @Override public FluidStack drain(int maxDrain, boolean doDrain) { + if (world == null || world.isRemote || removing) return null; + IFluidHandler handler = this.getFluidTankInDirection(EnumFacing.UP); FluidStack fStack = null; - if (handler != null && handler.getTankProperties()[0].getContents() != null && - fluidTank.getFluid() != null && fluidTank.getFluid().getFluid() == - handler.getTankProperties()[0].getContents().getFluid()) { - - fStack = handler.drain(maxDrain, doDrain); + if (handler != null) { + IFluidTankProperties[] props = handler.getTankProperties(); + FluidStack contents = (props != null && props.length > 0) ? props[0].getContents() : null; + if (contents != null && + fluidTank.getFluid() != null && + fluidTank.getFluid().getFluid() == contents.getFluid()) { + fStack = handler.drain(maxDrain, doDrain); + } } if (fStack != null) return fStack; @@ -110,6 +183,7 @@ public FluidStack drain(int maxDrain, boolean doDrain) { if (fStack2 != null && doDrain) { fluidChanged = true; + markDirty(); } checkForUpdate(); @@ -118,8 +192,8 @@ public FluidStack drain(int maxDrain, boolean doDrain) { } @Override - public FluidStack drain(FluidStack resource, - boolean doDrain) { + public FluidStack drain(FluidStack resource, boolean doDrain) { + if (world == null || world.isRemote || removing || resource == null) return null; if (this.fluidTank.getFluid() == null || resource.getFluid() != this.fluidTank.getFluid().getFluid()) return null; @@ -127,6 +201,7 @@ public FluidStack drain(FluidStack resource, } public TileFluidTank getFluidTankInDirection(EnumFacing direction) { + if (world == null) return null; TileEntity tile = world.getTileEntity(pos.offset(direction)); if (tile instanceof TileFluidTank) { @@ -161,30 +236,134 @@ protected boolean useBucket(int slot, @Nonnull ItemStack stack) { if (bucketUsed) { IFluidHandler handler = getFluidTankInDirection(EnumFacing.DOWN); if (handler != null) { - FluidStack othertank = handler.getTankProperties()[0].getContents(); - if (othertank == null || (othertank.amount < handler.getTankProperties()[0].getCapacity())) - fluidTank.drain(handler.fill(fluidTank.getFluid(), true), true); + IFluidTankProperties[] props = handler.getTankProperties(); + FluidStack contents = (props != null && props.length > 0) ? props[0].getContents() : null; + int capacity = (props != null && props.length > 0) ? props[0].getCapacity() : 0; + + // If the tank below is empty or has room, push fluid down + if (contents == null || (capacity > 0 && contents.amount < capacity)) { + FluidStack ours = fluidTank.getFluid(); + if (ours != null && ours.amount > 0) { + int canMove = handler.fill(new FluidStack(ours.getFluid(), ours.amount), false); + if (canMove > 0) { + FluidStack drained = fluidTank.drain(canMove, true); + int filled = handler.fill(drained, true); + if (filled < drained.amount) { + fluidTank.fill(new FluidStack(drained.getFluid(), drained.amount - filled), true); + } + fluidChanged = true; + markDirty(); + checkForUpdate(); + } + } + } } } return bucketUsed; } - public void onAdjacentBlockUpdated(EnumFacing dir) { - if (dir != EnumFacing.DOWN) - return; + @Override + public void invalidate() { + removing = true; + super.invalidate(); + } - TileFluidTank tank = getFluidTankInDirection(EnumFacing.UP); + @Override + public void onChunkUnload() { + removing = true; + super.onChunkUnload(); + } - if (tank != null && tank.getTankProperties()[0].getContents() != null) { - if (fluidTank.getFluid() == null) { - fluidTank.fill(tank.fluidTank.drain(fluidTank.getCapacity(), true), true); - } else if (tank.getTankProperties()[0].getContents().getFluid() == fluidTank.getFluid().getFluid()) { - fluidTank.fill(tank.drain(fluidTank.getCapacity() - fluidTank.getFluidAmount(), true), true); - tank.fluidTank.drain(fluidTank.getCapacity() - fluidTank.getFluidAmount(), true); + public void onAdjacentBlockUpdated(EnumFacing dir) { + if (world == null || world.isRemote || removing) return; + if (!enterColumnOp()) return; + try { + // If the block BELOW changed, push our fluid down into it and cascade. + if (dir == EnumFacing.DOWN) { + TileFluidTank down = getFluidTankInDirection(EnumFacing.DOWN); + if (down != null) { + FluidStack ours = fluidTank.getFluid(); + if (ours != null && ours.amount > 0) { + IFluidTankProperties[] props = down.getTankProperties(); + FluidStack below = (props != null && props.length > 0) ? props[0].getContents() : null; + boolean compatible = (below == null) || (below.getFluid() == ours.getFluid()); + + if (compatible) { + int room = down.fluidTank.getCapacity() - down.fluidTank.getFluidAmount(); + if (room > 0) { + int toMove = Math.min(room, ours.amount); + + // Use capability: simulate then commit + IFluidHandler downH = down.getCapability( + net.minecraftforge.fluids.capability.CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, + EnumFacing.UP + ); + if (downH != null) { + int canFill = downH.fill(new FluidStack(ours.getFluid(), toMove), false); + if (canFill > 0) { + FluidStack drained = fluidTank.drain(canFill, true); + int filled = downH.fill(drained, true); + if (filled < drained.amount) { + // Put any remainder back to avoid loss + fluidTank.fill(new FluidStack(drained.getFluid(), drained.amount - filled), true); + } + fluidChanged = true; + markDirty(); + checkForUpdate(); + down.markDirty(); + down.checkForUpdate(); + + // Cascade to the next tank down + down.onAdjacentBlockUpdated(EnumFacing.DOWN); + } + } + } + } + } + } + } + // If the block ABOVE changed, pull fluid from the tank above into THIS tank (column-aware). + else if (dir == EnumFacing.UP) { + TileFluidTank up = getFluidTankInDirection(EnumFacing.UP); + if (up != null) { + IFluidTankProperties[] props = up.getTankProperties(); + FluidStack above = (props != null && props.length > 0) ? props[0].getContents() : null; + + if (above != null) { + if (fluidTank.getFluid() == null) { + // We're empty: pull directly from the upper tank's internal store + FluidStack moved = up.drain(fluidTank.getCapacity(), true); + if (moved != null && moved.amount > 0) { + fluidTank.fill(moved, true); + fluidChanged = true; + markDirty(); + checkForUpdate(); + } + } else if (above.getFluid() == fluidTank.getFluid().getFluid()) { + // Same fluid: do a column-aware pull from the upper tank + int room = fluidTank.getCapacity() - fluidTank.getFluidAmount(); + if (room > 0) { + FluidStack moved = up.drain(room, true); + if (moved != null && moved.amount > 0) { + fluidTank.fill(moved, true); + fluidChanged = true; + markDirty(); + checkForUpdate(); + } + } + } + } + } } - this.markDirty(); + // Final safety: ensure any state change is persisted/sent + if (fluidChanged) { + this.markDirty(); + checkForUpdate(); + } + } finally { + exitColumnOp(); } } } diff --git a/src/main/java/zmaster587/advancedRocketry/tile/TilePump.java b/src/main/java/zmaster587/advancedRocketry/tile/TilePump.java index 59c57298f..3d487d76c 100644 --- a/src/main/java/zmaster587/advancedRocketry/tile/TilePump.java +++ b/src/main/java/zmaster587/advancedRocketry/tile/TilePump.java @@ -1,8 +1,11 @@ package zmaster587.advancedRocketry.tile; import net.minecraft.block.Block; +import net.minecraft.block.BlockLiquid; import net.minecraft.block.material.Material; +import net.minecraft.block.state.IBlockState; import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.nbt.NBTTagCompound; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.EnumFacing; import net.minecraft.util.math.BlockPos; @@ -11,6 +14,7 @@ import net.minecraftforge.fluids.Fluid; import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.FluidTank; +import net.minecraftforge.fluids.FluidRegistry; import net.minecraftforge.fluids.IFluidBlock; import net.minecraftforge.fluids.capability.CapabilityFluidHandler; import net.minecraftforge.fluids.capability.IFluidHandler; @@ -22,6 +26,9 @@ import zmaster587.libVulpes.network.PacketHandler; import zmaster587.libVulpes.tile.TileEntityRFConsumer; + + + import java.util.*; public class TilePump extends TileEntityRFConsumer implements IFluidHandler, IModularInventory { @@ -29,6 +36,7 @@ public class TilePump extends TileEntityRFConsumer implements IFluidHandler, IMo private final int RANGE = 64; private FluidTank tank; private List cache; + private Fluid lastFluidType = null; public TilePump() { super(1000); @@ -36,6 +44,15 @@ public TilePump() { cache = new LinkedList<>(); } + private static final int PUMP_INTERVAL_TICKS = 25; // ~1 Hz + private static final int EJECT_INTERVAL_TICKS = 20; // 1 Hz + private final IFluidHandler fluidCap = new FluidCapability(this); + + private boolean shouldRunThisTick(int interval) { + // world time is long; 0 mod interval fires once per interval + return interval <= 1 || (world.getWorldTime() % interval) == 0; + } + public int getPowerPerOperation() { return 100; } @@ -50,132 +67,242 @@ public boolean hasCapability(Capability capability, EnumFacing facing) { @Override public T getCapability(Capability capability, EnumFacing facing) { if (capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) { - return (T) new FluidCapability(this); + return CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY.cast(fluidCap); } return super.getCapability(capability, facing); } @Override public void update() { + if (world.isRemote) return; + + // Drop stale plan if accepted fluid changed + Fluid cur = tank.getFluid() == null ? null : tank.getFluid().getFluid(); + if (cur != lastFluidType) { + if (!cache.isEmpty()) cache.clear(); + lastFluidType = cur; + } + + if (isRedstoneDisabled()) { + // optional: cache.clear(); + return; + } + super.update(); - //Attempt fluid Eject - if (!world.isRemote && tank.getFluid() != null) { - for (EnumFacing direction : EnumFacing.values()) { - BlockPos newBlock = getPos().offset(direction); - TileEntity tile = world.getTileEntity(newBlock); - if (tile != null && tile.hasCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, direction.getOpposite())) { - IFluidHandler cap = tile.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, direction.getOpposite()); - FluidStack stack = tank.getFluid().copy(); - stack.amount = Math.min(tank.getFluid().amount, 1000); - //Perform the drain - cap.fill(tank.drain(cap.fill(stack, false), true), true); - - //Abort if we run out of fluid - if (tank.getFluid() == null) - break; + // Attempt fluid Eject (throttled; see section 3) + if (shouldRunThisTick(EJECT_INTERVAL_TICKS) && tank.getFluid() != null) { + final FluidStack src = tank.getFluid(); + final int toOffer = Math.min(src.amount, 1000); + if (toOffer > 0) { + for (EnumFacing dir : EnumFacing.values()) { + TileEntity te = world.getTileEntity(pos.offset(dir)); + if (te == null || !te.hasCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, dir.getOpposite())) + continue; + + IFluidHandler out = te.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, dir.getOpposite()); + if (out == null) continue; + + int simAccepted = out.fill(new FluidStack(src, toOffer), false); + if (simAccepted > 0) { + FluidStack drained = tank.drain(simAccepted, true); + if (drained != null && drained.amount > 0) { + out.fill(drained, true); + if (tank.getFluid() == null) break; // ran out + } + } } } } } - private int getFrequencyFromPower() { - float ratio = energy.getUniversalEnergyStored() / (float) energy.getMaxEnergyStored(); - if (ratio > 0.5) - return 1; - return 10; + private boolean isVanillaLiquid(BlockPos pos) { + IBlockState state = world.getBlockState(pos); + Material mat = state.getMaterial(); + return mat == Material.WATER || mat == Material.LAVA; + } + + private boolean isVanillaSource(BlockPos pos) { + IBlockState state = world.getBlockState(pos); + Block block = state.getBlock(); + if (block instanceof BlockLiquid) { + // 0 == source, 1..7 flowing (or up to 15, depending) + Integer lvl = state.getValue(BlockLiquid.LEVEL); + return lvl != null && lvl == 0; + } + return false; + } + + private Fluid getVanillaFluid(BlockPos pos) { + Material mat = world.getBlockState(pos).getMaterial(); + if (mat == Material.WATER) return FluidRegistry.WATER; + if (mat == Material.LAVA) return FluidRegistry.LAVA; + return null; } + + @Override public void performFunction() { - if (!world.isRemote) { - //Do we have room? if (tank.getCapacity() - 1000 < tank.getFluidAmount()) return; BlockPos nextPos = getNextBlockLocation(); - if (nextPos != null) { - if (canFitFluid(nextPos)) { - Block worldBlock = world.getBlockState(nextPos).getBlock(); - Material mat = world.getBlockState(nextPos).getMaterial(); - if (worldBlock instanceof IFluidBlock) { - FluidStack fStack = ((IFluidBlock) worldBlock).drain(world, nextPos, true); - - if (fStack != null) - tank.fill(fStack, true); - int colour = ((IFluidBlock) worldBlock).getFluid().getColor(); - if (mat == Material.LAVA) - colour = 0xFFbd3718; - - PacketHandler.sendToNearby(new PacketFluidParticle(nextPos, this.pos, 200, colour), world.provider.getDimension(), this.pos, 128); + if (nextPos != null && canFitFluid(nextPos)) { + IBlockState state = world.getBlockState(nextPos); + Block worldBlock = state.getBlock(); + Material mat = state.getMaterial(); + + if (worldBlock instanceof IFluidBlock) { + FluidStack fStack = ((IFluidBlock) worldBlock).drain(world, nextPos, true); + if (fStack != null) tank.fill(fStack, true); + + int colour = ((IFluidBlock) worldBlock).getFluid().getColor(); + if (mat == Material.LAVA) colour = 0xFFbd3718; + PacketHandler.sendToNearby(new PacketFluidParticle(nextPos, this.pos, 200, colour), world.provider.getDimension(), this.pos, 128); + } else if (isVanillaLiquid(nextPos) && isVanillaSource(nextPos)) { + Fluid f = getVanillaFluid(nextPos); + if (f != null) { + FluidStack stack = new FluidStack(f, 1000); + int filled = tank.fill(stack, true); + if (filled == 1000) { + world.setBlockToAir(nextPos); // remove the source + int colour = (mat == Material.LAVA) ? 0xFFbd3718 : 0xFF3F76E4; // MC-ish tint + PacketHandler.sendToNearby(new PacketFluidParticle(nextPos, this.pos, 200, colour), world.provider.getDimension(), this.pos, 128); + } } } } } } + private boolean canFitFluid(BlockPos pos) { Block worldBlock = world.getBlockState(pos).getBlock(); if (worldBlock instanceof IFluidBlock) { - // Can we put it into the tank? return tank.getFluid() == null || tank.getFluid().getFluid() == ((IFluidBlock) worldBlock).getFluid(); } + if (isVanillaLiquid(pos)) { + Fluid f = getVanillaFluid(pos); + return f != null && (tank.getFluid() == null || tank.getFluid().getFluid() == f); + } return false; } - private BlockPos getNextBlockLocation() { + private BlockPos getNextBlockLocation() { if (!cache.isEmpty()) return cache.remove(0); BlockPos currentPos = new MutableBlockPos(getPos().down()); - while (world.isAirBlock(currentPos)) currentPos = currentPos.down(); - // We found a fluid Block worldBlock = world.getBlockState(currentPos).getBlock(); - if (canFitFluid(currentPos)) - findFluidAtOrAbove(currentPos, ((IFluidBlock) worldBlock).getFluid()); + if (canFitFluid(currentPos)) { + Fluid target = null; + if (worldBlock instanceof IFluidBlock) { + target = ((IFluidBlock) worldBlock).getFluid(); + } else if (isVanillaLiquid(currentPos)) { + target = getVanillaFluid(currentPos); + } + findFluidAtOrAbove(currentPos, target); + } if (!cache.isEmpty()) return cache.remove(0); return null; } - private void findFluidAtOrAbove(BlockPos pos, Fluid fluid) { + + private void findFluidAtOrAbove(BlockPos pos, Fluid targetFluid) { Queue queue = new LinkedList<>(); Set visited = new HashSet<>(); queue.add(pos); while (!queue.isEmpty()) { - BlockPos nextElement = queue.poll(); - if (visited.contains(nextElement) || nextElement.getDistance(pos.getX(), nextElement.getY(), pos.getZ()) > RANGE) + BlockPos next = queue.poll(); + if (visited.contains(next) || next.getDistance(pos.getX(), pos.getY(), pos.getZ()) > RANGE) continue; - Block worldBlock = world.getBlockState(nextElement).getBlock(); - if (worldBlock instanceof IFluidBlock) { - if (fluid == null || ((IFluidBlock) worldBlock).getFluid() == fluid) { - //only add drainable fluids, allow chaining along flowing fluid tho - if (((IFluidBlock) worldBlock).canDrain(world, nextElement)) - cache.add(0, nextElement); - visited.add(nextElement); - queue.add(nextElement.west()); - queue.add(nextElement.east()); - queue.add(nextElement.north()); - queue.add(nextElement.south()); - queue.add(nextElement.up()); + IBlockState state = world.getBlockState(next); + Block block = state.getBlock(); + + // Case 1: IFluidBlock (existing behavior) + if (block instanceof IFluidBlock) { + IFluidBlock fb = (IFluidBlock) block; + Fluid f = fb.getFluid(); + if (targetFluid == null || f == targetFluid) { + if (fb.canDrain(world, next)) { + cache.add(0, next); // drainable + } + visited.add(next); + queue.add(next.west()); + queue.add(next.east()); + queue.add(next.north()); + queue.add(next.south()); + queue.add(next.up()); + } + continue; + } + + // Case 2: Vanilla BlockLiquid + if (isVanillaLiquid(next)) { + Fluid f = getVanillaFluid(next); + if (f != null && (targetFluid == null || f == targetFluid)) { + // Only sources are drainable; but we still traverse through flowing + if (isVanillaSource(next)) { + cache.add(0, next); + } + visited.add(next); + queue.add(next.west()); + queue.add(next.east()); + queue.add(next.north()); + queue.add(next.south()); + queue.add(next.up()); } } } } + private boolean isRedstoneDisabled() { + return world.isBlockPowered(pos); + } + @Override public boolean canPerformFunction() { - return tank.getFluidAmount() <= tank.getCapacity() && world.getWorldTime() % getFrequencyFromPower() == 0; + if (isRedstoneDisabled()) return false; + if (!shouldRunThisTick(PUMP_INTERVAL_TICKS)) return false; + + // must have at least 100 RF for one bucket operation + if (energy.getUniversalEnergyStored() < getPowerPerOperation()) return false; + + // must be able to accept a full bucket + if ((tank.getCapacity() - tank.getFluidAmount()) < 1000) return false; + + // must have a drainable source available; if not, try to populate cache now (cheap probe) + if (cache.isEmpty()) { + // very small, one-shot version of your getNextBlockLocation() to populate cache + BlockPos currentPos = new MutableBlockPos(getPos().down()); + while (world.isAirBlock(currentPos)) + currentPos = ((MutableBlockPos) currentPos).down(); + + if (canFitFluid(currentPos)) { + Fluid target = null; + Block worldBlock = world.getBlockState(currentPos).getBlock(); + if (worldBlock instanceof IFluidBlock) { + target = ((IFluidBlock) worldBlock).getFluid(); + } else if (isVanillaLiquid(currentPos)) { + target = getVanillaFluid(currentPos); + } + findFluidAtOrAbove(currentPos, target); + } + } + return !cache.isEmpty(); // only authorize (and thus spend 100 RF) if we have a source to drain } + @Override public IFluidTankProperties[] getTankProperties() { return tank.getTankProperties(); @@ -207,6 +334,34 @@ public String getModularInventoryName() { return "tile.pump.name"; } + @Override + public void onLoad() { + super.onLoad(); + lastFluidType = tank.getFluid() == null ? null : tank.getFluid().getFluid(); + } + + @Override + public void onChunkUnload() { + super.onChunkUnload(); + if (!cache.isEmpty()) cache.clear(); + } + + @Override + public void invalidate() { + super.invalidate(); + if (!cache.isEmpty()) cache.clear(); + } + + @Override public NBTTagCompound writeToNBT(NBTTagCompound nbt) { + super.writeToNBT(nbt); + nbt.setTag("tank", tank.writeToNBT(new NBTTagCompound())); + return nbt; + } + @Override public void readFromNBT(NBTTagCompound nbt) { + super.readFromNBT(nbt); + tank.readFromNBT(nbt.getCompoundTag("tank")); + } + @Override public boolean canInteractWithContainer(EntityPlayer entity) { return false; diff --git a/src/main/java/zmaster587/advancedRocketry/tile/TileRocketAssemblingMachine.java b/src/main/java/zmaster587/advancedRocketry/tile/TileRocketAssemblingMachine.java index aa77c094d..719945150 100644 --- a/src/main/java/zmaster587/advancedRocketry/tile/TileRocketAssemblingMachine.java +++ b/src/main/java/zmaster587/advancedRocketry/tile/TileRocketAssemblingMachine.java @@ -14,6 +14,7 @@ import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.MathHelper; +import net.minecraft.util.ITickable; import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.world.World; import net.minecraftforge.common.MinecraftForge; @@ -28,6 +29,7 @@ import zmaster587.advancedRocketry.entity.EntityRocket; import zmaster587.advancedRocketry.item.ItemPackedStructure; import zmaster587.advancedRocketry.network.PacketInvalidLocationNotify; +import zmaster587.advancedRocketry.tile.TileRocketAssemblingMachine.ErrorCodes; import zmaster587.advancedRocketry.tile.hatch.TileSatelliteHatch; import zmaster587.advancedRocketry.util.StorageChunk; import zmaster587.advancedRocketry.util.WeightEngine; @@ -58,7 +60,7 @@ * changed to complete the rocket structure * Also will be used to "build" the rocket components from the placed frames, control fuel flow etc **/ -public class TileRocketAssemblingMachine extends TileEntityRFConsumer implements IButtonInventory, INetworkMachine, IDataSync, IModularInventory, IProgressBar, ILinkableTile { +public class TileRocketAssemblingMachine extends TileEntityRFConsumer implements ITickable, IButtonInventory, INetworkMachine, IDataSync, IModularInventory, IProgressBar, ILinkableTile { protected static final ResourceLocation backdrop = new ResourceLocation("advancedrocketry", "textures/gui/rocketBuilder.png"); protected static final ProgressBarImage verticalProgressBar = new ProgressBarImage(76, 93, 8, 52, 176, 15, 2, 38, 3, 2, EnumFacing.UP, backdrop); @@ -81,6 +83,8 @@ public class TileRocketAssemblingMachine extends TileEntityRFConsumer implements private boolean building; //True is rocket is being built, false if only scanning or otherwise private int lastRocketID; private List blockPos; + private int relinkRetries = 0; // how many relinking tries left + private long nextRelinkAttempt = 0L; // world time for next try public TileRocketAssemblingMachine() { super(100000); @@ -91,25 +95,82 @@ public TileRocketAssemblingMachine() { stats = new StatsRocket(); building = false; prevProgress = 0; - MinecraftForge.EVENT_BUS.register(this); } + private boolean registeredBus = false; + + @Override + public void onLoad() { + if (!world.isRemote && !registeredBus) { + MinecraftForge.EVENT_BUS.register(this); + registeredBus = true; + } + if (!world.isRemote) { + relinkRetries = 15; // give it time + nextRelinkAttempt = world.getTotalWorldTime() + 20; + tryRelinkNow(); // best-effort first shot + } + if (world.isRemote) return; + + // Recompute pad bounds and relink infra to any rockets already on the pad + bbCache = getRocketPadBounds(world, pos); + if (bbCache != null) { + final AxisAlignedBB box = bbCache.grow(1.0E-4, 1.0E-4, 1.0E-4); + List rockets = world.getEntitiesWithinAABB(EntityRocketBase.class, box); + if (!rockets.isEmpty()) { + for (IInfrastructure infra : getConnectedInfrastructure()) { + for (EntityRocketBase r : rockets) { + if (infra instanceof zmaster587.advancedRocketry.tile.infrastructure.TileRocketMonitoringStation) { + ((zmaster587.advancedRocketry.tile.infrastructure.TileRocketMonitoringStation) infra) + .markRocketFromAssembler(r); + } + r.linkInfrastructure(infra); + } + } + } + } + } + @Override public void invalidate() { super.invalidate(); - MinecraftForge.EVENT_BUS.unregister(this); - for (HashedBlockPosition pos : blockPos) { - TileEntity tile = world.getTileEntity(pos.getBlockPos()); - - if (tile instanceof IMultiblock) - ((IMultiblock) tile).setIncomplete(); + unregisterFromBus(); + relinkRetries = 0; + nextRelinkAttempt = 0L; + // Notify linked multiblocks BEFORE clearing (server only) + if (world != null && !world.isRemote) { + for (HashedBlockPosition p : blockPos) { + TileEntity te = world.getTileEntity(p.getBlockPos()); + if (te instanceof IMultiblock) { + ((IMultiblock) te).setIncomplete(); + } + } } + + // Clear caches + bbCache = null; + stats.reset(); + blockPos.clear(); } @Override public void onChunkUnload() { super.onChunkUnload(); - MinecraftForge.EVENT_BUS.unregister(this); + unregisterFromBus(); + relinkRetries = 0; + nextRelinkAttempt = 0L; + // Clear caches + bbCache = null; + stats.reset(); + blockPos.clear(); + } + + + private void unregisterFromBus() { + if (registeredBus) { + MinecraftForge.EVENT_BUS.unregister(this); + registeredBus = false; + } } public ErrorCodes getStatus() { @@ -202,6 +263,8 @@ public int getPowerPerOperation() { @Override public void performFunction() { + + if (!isScanning()) return; if (progress >= (totalProgress * MAXSCANDELAY)) { if (!world.isRemote) { if (building) @@ -262,10 +325,10 @@ public AxisAlignedBB scanRocket(World world, BlockPos pos2, AxisAlignedBB bb) { double buffer = 0.0001; AxisAlignedBB bufferedBB = bbCache.grow(buffer, buffer, buffer); List rockets = world.getEntitiesWithinAABB(EntityRocket.class, bufferedBB); - if (rockets.size() == 1){ // only if axactly one rocket is here + if (rockets.size() == 1){ rockets.get(0).recalculateStats(); this.stats = rockets.get(0).stats; - status = ErrorCodes.ALREADY_ASSEMBLED; // to prevent assembly + status = ErrorCodes.ALREADY_ASSEMBLED; return null; } } @@ -423,17 +486,23 @@ public AxisAlignedBB scanRocket(World world, BlockPos pos2, AxisAlignedBB bb) { thrustNuclearTotalLimit = (nuclearWorkingFluidUse * thrustNuclearNozzleLimit) / nuclearWorkingFluidUseMax; } - //Set fuel stats - //Thrust depending on rocket type + // Set fuel stats + // Thrust depending on rocket type stats.setBaseFuelRate(FuelType.LIQUID_MONOPROPELLANT, monopropellantfuelUse); - stats.setBaseFuelRate(FuelType.LIQUID_BIPROPELLANT, bipropellantfuelUse); - stats.setBaseFuelRate(FuelType.LIQUID_OXIDIZER, bipropellantfuelUse); + stats.setBaseFuelRate(FuelType.LIQUID_BIPROPELLANT, bipropellantfuelUse); + stats.setBaseFuelRate(FuelType.LIQUID_OXIDIZER, bipropellantfuelUse); stats.setBaseFuelRate(FuelType.NUCLEAR_WORKING_FLUID, nuclearWorkingFluidUse); - //Fuel storage depending on rocket type - stats.setFuelCapacity(FuelType.LIQUID_MONOPROPELLANT, fuelCapacityMonopropellant); - stats.setFuelCapacity(FuelType.LIQUID_BIPROPELLANT, fuelCapacityBipropellant); - stats.setFuelCapacity(FuelType.LIQUID_OXIDIZER, fuelCapacityOxidizer); - stats.setFuelCapacity(FuelType.NUCLEAR_WORKING_FLUID, fuelCapacityNuclearWorkingFluid); + + stats.setFuelRate(FuelType.LIQUID_MONOPROPELLANT, monopropellantfuelUse); + stats.setFuelRate(FuelType.LIQUID_BIPROPELLANT, bipropellantfuelUse); + stats.setFuelRate(FuelType.LIQUID_OXIDIZER, bipropellantfuelUse); + stats.setFuelRate(FuelType.NUCLEAR_WORKING_FLUID, nuclearWorkingFluidUse); + + // Fuel storage depending on rocket type + stats.setFuelCapacity(FuelType.LIQUID_MONOPROPELLANT, fuelCapacityMonopropellant); + stats.setFuelCapacity(FuelType.LIQUID_BIPROPELLANT, fuelCapacityBipropellant); + stats.setFuelCapacity(FuelType.LIQUID_OXIDIZER, fuelCapacityOxidizer); + stats.setFuelCapacity(FuelType.NUCLEAR_WORKING_FLUID, fuelCapacityNuclearWorkingFluid); //Non-fuel stats stats.setWeight(weight); @@ -445,27 +514,63 @@ public AxisAlignedBB scanRocket(World world, BlockPos pos2, AxisAlignedBB bb) { int totalFuelUse = bipropellantfuelUse + nuclearWorkingFluidUse + monopropellantfuelUse; //System.out.println("rocket fuel use:"+totalFuelUse); + // Biprop requirement: if any bipropellant thrust exists, require both tanks + if (thrustBipropellant > 0) { + if (fuelCapacityBipropellant <= 0 || fuelCapacityOxidizer <= 0) { + status = ErrorCodes.NOFUEL; + return new AxisAlignedBB(actualMinX, actualMinY, actualMinZ, actualMaxX, actualMaxY, actualMaxZ); + } + } + //Set status - if (invalidBlock) + if (invalidBlock) { status = ErrorCodes.INVALIDBLOCK; - else if (((fuelCapacityBipropellant > 0 && totalFuel > fuelCapacityBipropellant) || (fuelCapacityMonopropellant > 0 && totalFuel > fuelCapacityMonopropellant) || (fuelCapacityNuclearWorkingFluid > 0 && totalFuel > fuelCapacityNuclearWorkingFluid)) + + } else if (((fuelCapacityBipropellant > 0 && totalFuel > fuelCapacityBipropellant) + || (fuelCapacityMonopropellant > 0 && totalFuel > fuelCapacityMonopropellant) + || (fuelCapacityNuclearWorkingFluid > 0 && totalFuel > fuelCapacityNuclearWorkingFluid)) || - ((thrustBipropellant > 0 && totalFuelUse > bipropellantfuelUse) || (thrustMonopropellant > 0 && totalFuelUse > monopropellantfuelUse) || (thrustNuclearTotalLimit > 0 && totalFuelUse > nuclearWorkingFluidUse))) + ((thrustBipropellant > 0 && totalFuelUse > bipropellantfuelUse) + || (thrustMonopropellant > 0 && totalFuelUse > monopropellantfuelUse) + || (thrustNuclearTotalLimit > 0 && totalFuelUse > nuclearWorkingFluidUse))) { status = ErrorCodes.COMBINEDTHRUST; - else if (!hasGuidance && !hasSatellite) + + } else if (!hasGuidance && !hasSatellite) { status = ErrorCodes.NOGUIDANCE; - else if (getThrust() <= getNeededThrust()) + + } else if (getThrust() <= getNeededThrust()) { status = ErrorCodes.NOENGINES; - else if (((thrustBipropellant > 0) && !hasEnoughFuel(FuelType.LIQUID_BIPROPELLANT)) || ((thrustMonopropellant > 0) && !hasEnoughFuel(FuelType.LIQUID_MONOPROPELLANT)) || ((thrustNuclearTotalLimit > 0) && !hasEnoughFuel(FuelType.NUCLEAR_WORKING_FLUID))) + + } else if (thrustBipropellant > 0 && (fuelCapacityBipropellant <= 0 || fuelCapacityOxidizer <= 0)) { + // Biprop engines require BOTH bipropellant AND oxidizer capacity + status = ErrorCodes.NOFUEL; + + } else if (((thrustBipropellant > 0) && !hasEnoughFuel(FuelType.LIQUID_BIPROPELLANT)) + || ((thrustMonopropellant > 0) && !hasEnoughFuel(FuelType.LIQUID_MONOPROPELLANT)) + || ((thrustNuclearTotalLimit > 0) && !hasEnoughFuel(FuelType.NUCLEAR_WORKING_FLUID))) { status = ErrorCodes.NOFUEL; - else + + } else { status = ErrorCodes.SUCCESS; + } } - - return new AxisAlignedBB(actualMinX, actualMinY, actualMinZ, actualMaxX, actualMaxY, actualMaxZ); + + // Normalize integer mins/maxes first + int minXi = Math.min(actualMinX, actualMaxX); + int minYi = Math.min(actualMinY, actualMaxY); + int minZi = Math.min(actualMinZ, actualMaxZ); + int maxXi = Math.max(actualMinX, actualMaxX); + int maxYi = Math.max(actualMaxY, actualMinY); + int maxZi = Math.max(actualMinZ, actualMaxZ); + + // use BlockPos ctor so the AABB is [min, max+1) in block space + return new AxisAlignedBB( + new BlockPos(minXi, minYi, minZi), + new BlockPos(maxXi, maxYi, maxZi) + ); } - private void removeReplaceableBlocks(AxisAlignedBB bb) { + protected void removeReplaceableBlocks(AxisAlignedBB bb) { for (int yCurr = (int) bb.minY; yCurr <= bb.maxY; yCurr++) { for (int xCurr = (int) bb.minX; xCurr <= bb.maxX; xCurr++) { for (int zCurr = (int) bb.minZ; zCurr <= bb.maxZ; zCurr++) { @@ -485,47 +590,79 @@ private void removeReplaceableBlocks(AxisAlignedBB bb) { } } + private static boolean isEmptyAABB(@Nullable AxisAlignedBB b) { + return b == null || b.maxX < b.minX || b.maxY < b.minY || b.maxZ < b.minZ; + } + + + private static AxisAlignedBB normalize(AxisAlignedBB b) { + double minX = Math.min(b.minX, b.maxX); + double minY = Math.min(b.minY, b.maxY); + double minZ = Math.min(b.minZ, b.maxZ); + double maxX = Math.max(b.minX, b.maxX); + double maxY = Math.max(b.minY, b.maxY); + double maxZ = Math.max(b.minZ, b.maxZ); + return new AxisAlignedBB(minX, minY, minZ, maxX, maxY, maxZ); + } + + public void assembleRocket() { + // server only + need a pad cache + if (world.isRemote || bbCache == null) return; - if (bbCache == null || world.isRemote) - return; - // Need to scan again b/c something may have changed - AxisAlignedBB rocketBB = scanRocket(world, pos, bbCache); + // Re-scan to get a tight non-air AABB and fresh stats/status + final AxisAlignedBB scanBB = scanRocket(world, pos, bbCache); + if (status != ErrorCodes.SUCCESS || scanBB == null) return; - if (status != ErrorCodes.SUCCESS) + // Normalize and defensively guard against degenerate boxes + final AxisAlignedBB rocketBB = normalize(scanBB); + if (isEmptyAABB(rocketBB)) { + status = ErrorCodes.FAIL_CUT; return; + } - // Remove replacable blocks that don't belong on the rocket - removeReplaceableBlocks(bbCache); + // Remove replaceable/blacklisted blocks *inside the tightened bounds* + removeReplaceableBlocks(rocketBB); - StorageChunk storageChunk; + // Cut the world using the tightened box (avoid pad air) + final StorageChunk storageChunk; try { - storageChunk = StorageChunk.cutWorldBB(world, bbCache); - } catch (NegativeArraySizeException e) { + storageChunk = StorageChunk.cutWorldBB(world, rocketBB); + } catch (Throwable t) { // cover NegativeArraySizeException & other edge errors + status = ErrorCodes.FAIL_CUT; return; } - EntityRocket rocket = new EntityRocket(world, storageChunk, stats.copy(), - rocketBB.minX + (rocketBB.maxX - rocketBB.minX) / 2f + .5f, - this.getPos().getY(), - rocketBB.minZ + (rocketBB.maxZ - rocketBB.minZ) / 2f + .5f); + // Center spawn on tightened AABB + final double cx = rocketBB.minX + (rocketBB.maxX - rocketBB.minX) / 2.0 + 0.5; + final double cz = rocketBB.minZ + (rocketBB.maxZ - rocketBB.minZ) / 2.0 + 0.5; + final double cy = this.getPos().getY(); + EntityRocket rocket = new EntityRocket(world, storageChunk, stats.copy(), cx, cy, cz); world.spawnEntity(rocket); - NBTTagCompound nbtdata = new NBTTagCompound(); + NBTTagCompound nbtdata = new NBTTagCompound(); rocket.writeToNBT(nbtdata); - PacketHandler.sendToNearby(new PacketEntity(rocket, (byte) 0, nbtdata), rocket.world.provider.getDimension(), this.pos, 64); + PacketHandler.sendToNearby(new PacketEntity(rocket, (byte) 0, nbtdata), + rocket.world.provider.getDimension(), this.pos, 64); + // Finish & link as before stats.reset(); this.status = ErrorCodes.FINISHED; this.markDirty(); world.notifyBlockUpdate(pos, world.getBlockState(pos), world.getBlockState(pos), 3); for (IInfrastructure infrastructure : getConnectedInfrastructure()) { + if (infrastructure instanceof zmaster587.advancedRocketry.tile.infrastructure.TileRocketMonitoringStation) { + ((zmaster587.advancedRocketry.tile.infrastructure.TileRocketMonitoringStation) infrastructure) + .markRocketFromAssembler(rocket); + } rocket.linkInfrastructure(infrastructure); } - scanRocket(world, pos, bbCache); // to show stats + + // Rescan so UI immediately reflects the post-build state + scanRocket(world, pos, bbCache); } /** @@ -796,6 +933,9 @@ public void useNetworkData(EntityPlayer player, Side side, byte id, } protected void updateText() { + if (thrustText == null || weightText == null || fuelText == null || accelerationText == null || errorText == null) { + return; + } thrustText.setText(isScanning() ? (LibVulpes.proxy.getLocalizedString("msg.rocketbuilder.thrust") + ": ???") : String.format("%s: %dkN", LibVulpes.proxy.getLocalizedString("msg.rocketbuilder.thrust"), getThrust())); weightText.setText(isScanning() ? (LibVulpes.proxy.getLocalizedString("msg.rocketbuilder.weight") + ": ???") : String.format("%s: %.2fkN", LibVulpes.proxy.getLocalizedString("msg.rocketbuilder.weight"), (getWeight() * getGravityMultiplier()))); fuelText.setText(isScanning() ? (LibVulpes.proxy.getLocalizedString("msg.rocketbuilder.fuel") + ": ???") : String.format("%s: %dmb/s", LibVulpes.proxy.getLocalizedString("msg.rocketbuilder.fuel"), 20* getRocketStats().getFuelRate((stats.getFuelCapacity(FuelType.LIQUID_MONOPROPELLANT) > 0) ? FuelType.LIQUID_MONOPROPELLANT : (stats.getFuelCapacity(FuelType.NUCLEAR_WORKING_FLUID) > 0) ? FuelType.NUCLEAR_WORKING_FLUID : FuelType.LIQUID_BIPROPELLANT))); @@ -812,6 +952,17 @@ else if (ErrorCodes.INCOMPLETESTRCUTURE.equals(getStatus())) @Override public List getModules(int ID, EntityPlayer player) { + + // Automatically set status to unscanned if no rocket is present when opening GUI + if (!world.isRemote && status == ErrorCodes.ALREADY_ASSEMBLED) { + AxisAlignedBB box = (bbCache != null) ? bbCache : getRocketPadBounds(world, pos); + if (box == null || world.getEntitiesWithinAABB(EntityRocket.class, box).isEmpty()) { + status = ErrorCodes.UNSCANNED; + markDirty(); + } + } + + List modules = new LinkedList<>(); modules.add(new ModulePower(160, 90, this)); @@ -967,7 +1118,7 @@ public int getData(int id) { switch (id) { case 0: - return (int)(getRocketStats().getWeight_NoFuel()*1000);// because it is a float really so take it *1000 + return (int)(getRocketStats().getWeight_NoFuel()*1000); case 1: return getRocketStats().getThrust(); case 2: @@ -1009,7 +1160,6 @@ public int getData(int id) { @Override public void onInventoryButtonPressed(int buttonId) { PacketHandler.sendToServer(new PacketMachine(this, (byte) (buttonId))); - //updateText(); } @Override @@ -1089,56 +1239,62 @@ public void removeConnectedInfrastructure(TileEntity tile) { } public List getConnectedInfrastructure() { - List infrastructure = new LinkedList<>(); - - Iterator iter = blockPos.iterator(); - - while (iter.hasNext()) { - HashedBlockPosition position = iter.next(); - TileEntity tile = world.getTileEntity(position.getBlockPos()); - if (tile instanceof IInfrastructure) { - infrastructure.add((IInfrastructure) tile); - } else - iter.remove(); + List list = new LinkedList<>(); + for (HashedBlockPosition position : blockPos) { + TileEntity te = world.getTileEntity(position.getBlockPos()); + if (te instanceof IInfrastructure) { + list.add((IInfrastructure) te); + } } - - return infrastructure; + return list; } @SubscribeEvent - public void onRocketLand(RocketLandedEvent event) { - if (event.world.isRemote) - return; - EntityRocketBase rocket = (EntityRocketBase) event.getEntity(); - - - //This apparently happens sometimes - if (world == null) { - AdvancedRocketry.logger.debug("World null for rocket builder during rocket land event @ " + this.pos); - return; + public void onRocketLand(RocketLandedEvent e) { + // Server/world guard + if (e.world.isRemote || e.world != this.world) return; + + // Ensure we have pad bounds + bbCache = getRocketPadBounds(world, pos); + if (bbCache == null) return; + + // Make sure the event entity is a rocket + final net.minecraft.entity.Entity ent = e.getEntity(); + if (!(ent instanceof EntityRocketBase)) return; + final EntityRocketBase landed = (EntityRocketBase) ent; + + // Quick membership test with tiny epsilon + final AxisAlignedBB box = bbCache.grow(1.0E-4, 1.0E-4, 1.0E-4); + if (!landed.getEntityBoundingBox().intersects(box)) return; + + // Track rocket id and (re)link infra + lastRocketID = landed.getEntityId(); + for (IInfrastructure infra : getConnectedInfrastructure()) { + if (infra instanceof zmaster587.advancedRocketry.tile.infrastructure.TileRocketMonitoringStation) { + ((zmaster587.advancedRocketry.tile.infrastructure.TileRocketMonitoringStation) infra) + .markRocketFromAssembler(landed); + } + landed.linkInfrastructure(infra); } - if (getBBCache() == null) { - bbCache = getRocketPadBounds(world, pos); - } - if (getBBCache() != null) { - double buffer = 0.0001; - AxisAlignedBB bufferedBB = bbCache.grow(buffer, buffer, buffer); - List rockets = world.getEntitiesWithinAABB(EntityRocketBase.class, bufferedBB); - - if (rockets.contains(rocket)) { - lastRocketID = rocket.getEntityId(); - for (IInfrastructure infrastructure : getConnectedInfrastructure()) { - rocket.linkInfrastructure(infrastructure); - } - scanRocket(world,pos, bbCache); // rescan on landing - - PacketHandler.sendToPlayersTrackingEntity(new PacketMachine(this, (byte) 3), rocket); - } + // only fast-path when exactly one rocket in the pad + List rockets = world.getEntitiesWithinAABB(EntityRocket.class, box); + if (rockets.size() == 1) { + EntityRocket r = rockets.get(0); + r.recalculateStats(); + this.stats = r.stats.copy(); + this.status = ErrorCodes.ALREADY_ASSEMBLED; + markDirty(); + world.notifyBlockUpdate(pos, world.getBlockState(pos), world.getBlockState(pos), 3); + } else { + // Fallback: rescan if something odd happens + scanRocket(world, pos, bbCache); } + PacketHandler.sendToPlayersTrackingEntity(new PacketMachine(this, (byte)3), landed); } + protected enum ErrorCodes { SUCCESS(LibVulpes.proxy.getLocalizedString("msg.rocketbuilder.success")), NOFUEL(LibVulpes.proxy.getLocalizedString("msg.rocketbuilder.nofuel")), @@ -1155,7 +1311,11 @@ protected enum ErrorCodes { OUTPUTBLOCKED(LibVulpes.proxy.getLocalizedString("msg.rocketbuilder.outputblocked")), INVALIDBLOCK(LibVulpes.proxy.getLocalizedString("msg.rocketbuild.invalidblock")), COMBINEDTHRUST(LibVulpes.proxy.getLocalizedString("msg.rocketbuild.combinedthrust")), - ALREADY_ASSEMBLED("rocket already assembled"); + ALREADY_ASSEMBLED(LibVulpes.proxy.getLocalizedString("msg.rocketbuilder.alreadyassembled")), + UNSCANNED_STATION(LibVulpes.proxy.getLocalizedString("msg.rocketbuilder.unscanned_station")), + FAIL_CUT(LibVulpes.proxy.getLocalizedString("msg.rocketbuilder.fail_cut")), + NOINTAKE(LibVulpes.proxy.getLocalizedString("msg.rocketbuilder.nointake")), + NOTANK(LibVulpes.proxy.getLocalizedString("msg.rocketbuilder.notank")); String code; @@ -1167,4 +1327,42 @@ public String getErrorCode() { return code; } } + + @Override + public void update() { + super.update(); + if (world.isRemote) return; + + if (relinkRetries > 0 && world.getTotalWorldTime() >= nextRelinkAttempt) { + if (tryRelinkNow()) { + relinkRetries = 0; + } else { + relinkRetries--; + nextRelinkAttempt = world.getTotalWorldTime() + 20; // 1s + } + } + } + + private boolean tryRelinkNow() { + if (bbCache == null) bbCache = getRocketPadBounds(world, pos); + if (bbCache == null) return false; + + AxisAlignedBB box = bbCache.grow(1.0e-4,1.0e-4,1.0e-4); + java.util.List rockets = world.getEntitiesWithinAABB(EntityRocketBase.class, box); + if (rockets.isEmpty()) return false; + + java.util.List infraNow = getConnectedInfrastructure(); + if (infraNow.isEmpty()) return false; + + for (EntityRocketBase r : rockets) { + for (IInfrastructure i : infraNow) { + if (i instanceof zmaster587.advancedRocketry.tile.infrastructure.TileRocketMonitoringStation) { + ((zmaster587.advancedRocketry.tile.infrastructure.TileRocketMonitoringStation) i) + .markRocketFromAssembler(r); + } + r.linkInfrastructure(i); + } + } + return true; + } } diff --git a/src/main/java/zmaster587/advancedRocketry/tile/TileStationAssembler.java b/src/main/java/zmaster587/advancedRocketry/tile/TileStationAssembler.java index 74b2fdaaf..abc674c3c 100644 --- a/src/main/java/zmaster587/advancedRocketry/tile/TileStationAssembler.java +++ b/src/main/java/zmaster587/advancedRocketry/tile/TileStationAssembler.java @@ -63,11 +63,13 @@ public boolean canScan() { public AxisAlignedBB scanRocket(World world, BlockPos pos2, AxisAlignedBB bb) { int actualMinX = (int) bb.maxX, - actualMinY = (int) bb.maxY, - actualMinZ = (int) bb.maxZ, - actualMaxX = (int) bb.minX, - actualMaxY = (int) bb.minY, - actualMaxZ = (int) bb.minZ; + actualMinY = (int) bb.maxY, + actualMinZ = (int) bb.maxZ, + actualMaxX = (int) bb.minX, + actualMaxY = (int) bb.minY, + actualMaxZ = (int) bb.minZ; + + boolean foundNonAir = false; for (int xCurr = (int) bb.minX; xCurr <= bb.maxX; xCurr++) { for (int zCurr = (int) bb.minZ; zCurr <= bb.maxZ; zCurr++) { @@ -76,29 +78,32 @@ public AxisAlignedBB scanRocket(World world, BlockPos pos2, AxisAlignedBB bb) { BlockPos posCurr = new BlockPos(xCurr, yCurr, zCurr); if (!world.isAirBlock(posCurr)) { - if (xCurr < actualMinX) - actualMinX = xCurr; - if (yCurr < actualMinY) - actualMinY = yCurr; - if (zCurr < actualMinZ) - actualMinZ = zCurr; - if (xCurr > actualMaxX) - actualMaxX = xCurr; - if (yCurr > actualMaxY) - actualMaxY = yCurr; - if (zCurr > actualMaxZ) - actualMaxZ = zCurr; + foundNonAir = true; + + if (xCurr < actualMinX) actualMinX = xCurr; + if (yCurr < actualMinY) actualMinY = yCurr; + if (zCurr < actualMinZ) actualMinZ = zCurr; + if (xCurr > actualMaxX) actualMaxX = xCurr; + if (yCurr > actualMaxY) actualMaxY = yCurr; + if (zCurr > actualMaxZ) actualMaxZ = zCurr; } } } } - status = ErrorCodes.SUCCESS_STATION; + // Tell the player whats up + if (!foundNonAir) { + status = ErrorCodes.EMPTY; // nothing to pack inside bb + return bb; // sanity check + } else { + status = ErrorCodes.SUCCESS_STATION; // ok to proceed with packing + } return new AxisAlignedBB(actualMinX, actualMinY, actualMinZ, actualMaxX, actualMaxY, actualMaxZ); } + @Override public void assembleRocket() { if (!world.isRemote) { @@ -113,6 +118,9 @@ public void assembleRocket() { try { storageChunk = StorageChunk.cutWorldBB(world, bbCache); } catch (NegativeArraySizeException e) { + status = ErrorCodes.FAIL_CUT; + markDirty(); + world.notifyBlockUpdate(pos, world.getBlockState(pos), world.getBlockState(pos), 3); return; } @@ -152,20 +160,38 @@ public void assembleRocket() { @Override protected void updateText() { - if (!world.isRemote) { - if (getRocketPadBounds(world, pos) == null) + if (world != null && !world.isRemote) { + if (getRocketPadBounds(world, pos) == null) { setStatus(ErrorCodes.INCOMPLETESTRCUTURE.ordinal()); - else if (ErrorCodes.INCOMPLETESTRCUTURE.equals(getStatus())) - setStatus(ErrorCodes.UNSCANNED.ordinal()); + } else if (ErrorCodes.INCOMPLETESTRCUTURE.equals(getStatus())) { + setStatus(ErrorCodes.UNSCANNED_STATION.ordinal()); + } } - errorText.setText(status.getErrorCode()); + if (errorText != null) { + errorText.setText(status.getErrorCode()); + } } @Override public List getModules(int ID, EntityPlayer player) { List modules = new LinkedList<>(); + // GUI-open reset errorcode if pad is valid and we're idle + if (!world.isRemote) { + AxisAlignedBB bounds = getRocketPadBounds(world, pos); + if (bounds == null) { + setStatus(ErrorCodes.INCOMPLETESTRCUTURE.ordinal()); + } else if (!isScanning()) { + ErrorCodes s = getStatus(); + if (s == ErrorCodes.SUCCESS_STATION || s == ErrorCodes.SUCCESS || + s == ErrorCodes.FINISHED || s == ErrorCodes.EMPTY || + s == ErrorCodes.UNSCANNED) { + setStatus(ErrorCodes.UNSCANNED_STATION.ordinal()); + } + } + } + modules.add(new ModulePower(160, 30, this)); modules.add(new ModuleProgress(149, 30, 2, verticalProgressBar, this)); @@ -177,6 +203,7 @@ public List getModules(int ID, EntityPlayer player) { buttonBuild.setColor(0xFFFF2222); modules.add(errorText = new ModuleText(5, 22, "", 0xFFFFFF22)); modules.add(new ModuleSync(4, this)); + modules.add(new ModuleSync(2, this)); // sync error codes to client (on change) updateText(); @@ -190,19 +217,20 @@ public List getModules(int ID, EntityPlayer player) { @Override - public void useNetworkData(EntityPlayer player, Side side, byte id, - NBTTagCompound nbt) { + public void useNetworkData(EntityPlayer player, Side side, byte id, NBTTagCompound nbt) { + + super.useNetworkData(player, side, id, nbt); + // recompute AFTER super boolean isScanningFlag = !isScanning() && canScan(); - super.useNetworkData(player, side, id, nbt); if (id == 1 && isScanningFlag) { - storedId = (long) ItemStationChip.getUUID(inventory.getStackInSlot(1)); if (storedId == 0) storedId = null; } } + @Override public NBTTagCompound writeToNBT(NBTTagCompound nbt) { super.writeToNBT(nbt); diff --git a/src/main/java/zmaster587/advancedRocketry/tile/TileUnmannedVehicleAssembler.java b/src/main/java/zmaster587/advancedRocketry/tile/TileUnmannedVehicleAssembler.java index 6869bb1ee..7e5b26981 100644 --- a/src/main/java/zmaster587/advancedRocketry/tile/TileUnmannedVehicleAssembler.java +++ b/src/main/java/zmaster587/advancedRocketry/tile/TileUnmannedVehicleAssembler.java @@ -18,6 +18,7 @@ import zmaster587.advancedRocketry.entity.EntityStationDeployedRocket; import zmaster587.advancedRocketry.network.PacketInvalidLocationNotify; import zmaster587.advancedRocketry.util.StorageChunk; +import zmaster587.advancedRocketry.util.WeightEngine; import zmaster587.libVulpes.block.BlockFullyRotatable; import zmaster587.libVulpes.block.RotatableBlock; import zmaster587.libVulpes.network.PacketEntity; @@ -99,64 +100,106 @@ public AxisAlignedBB getRocketPadBounds(World world, BlockPos pos2) { return new AxisAlignedBB(xMin, yCurrent, zMin, xMax, yCurrent + yMax - 1, zMax); } + @Override public void assembleRocket() { - if (bbCache == null || world.isRemote) - return; - //Need to scan again b/c something may have changed - scanRocket(world, getPos(), bbCache); + if (bbCache == null || world.isRemote) return; - if (status != ErrorCodes.SUCCESS) - return; - StorageChunk storageChunk; + // 1) Rescan like the parent (may update stats/status and tighten AABB) + AxisAlignedBB rocketBB = scanRocket(world, getPos(), bbCache); + if (status != ErrorCodes.SUCCESS || rocketBB == null) return; - //Breaks if nothing is there + // 2) Remove replaceables **inside the tight box** + removeReplaceableBlocks(rocketBB); + + // 3) Cut the world using the **tight** AABB + final StorageChunk storageChunk; try { - storageChunk = StorageChunk.cutWorldBB(world, bbCache); - } catch (NegativeArraySizeException e) { + storageChunk = StorageChunk.cutWorldBB(world, rocketBB); + } catch (Throwable t) { // covers NegativeArraySizeException, etc. return; } + // 4) Spawn the SD rocket, centered from the *rescanned* bbox + final double cx = rocketBB.minX + (rocketBB.maxX - rocketBB.minX) / 2f + 0.5f; + final double cz = rocketBB.minZ + (rocketBB.maxZ - rocketBB.minZ) / 2f + 0.5f; + final double cy = this.getPos().getY(); - EntityStationDeployedRocket rocket = new EntityStationDeployedRocket(world, storageChunk, stats.copy(), bbCache.minX + (bbCache.maxX - bbCache.minX) / 2f + .5f, getPos().getY(), bbCache.minZ + (bbCache.maxZ - bbCache.minZ) / 2f + .5f); + EntityStationDeployedRocket rocket = + new EntityStationDeployedRocket(world, storageChunk, stats.copy(), cx, cy, cz); - //TODO: setRocketDirection + // Orientations for SD rockets rocket.forwardDirection = RotatableBlock.getFront(world.getBlockState(getPos())).getOpposite(); rocket.launchDirection = EnumFacing.DOWN; - //Change engine direction + // 5) Rotate *all* engine types to match forwardDirection (defensive: only if block supports FACING) for (int x = 0; x < storageChunk.getSizeX(); x++) { for (int y = 0; y < storageChunk.getSizeY(); y++) { for (int z = 0; z < storageChunk.getSizeZ(); z++) { + BlockPos bp = new BlockPos(x, y, z); + IBlockState st = storageChunk.getBlockState(bp); + Block b = st.getBlock(); + + boolean isEngine = (b instanceof BlockRocketMotor) + || (b instanceof BlockBipropellantRocketMotor) + || (b instanceof BlockNuclearRocketMotor); - BlockPos pos3 = new BlockPos(x, y, z); - if (storageChunk.getBlockState(pos3).getBlock() instanceof BlockRocketMotor) { - storageChunk.setBlockState(pos3, storageChunk.getBlockState(pos3).withProperty(BlockFullyRotatable.FACING, rocket.forwardDirection)); + if (isEngine && st.getPropertyKeys().contains(BlockFullyRotatable.FACING)) { + storageChunk.setBlockState(bp, st.withProperty(BlockFullyRotatable.FACING, rocket.forwardDirection)); } } } } + // 6) Spawn + sync world.spawnEntity(rocket); - NBTTagCompound nbtdata = new NBTTagCompound(); - - rocket.writeToNBT(nbtdata); - PacketHandler.sendToNearby(new PacketEntity(rocket, (byte) 0, nbtdata), rocket.world.provider.getDimension(), this.pos, 64); - - stats.reset(); - this.status = ErrorCodes.UNSCANNED; - this.markDirty(); + NBTTagCompound nbt = new NBTTagCompound(); + rocket.writeToNBT(nbt); + PacketHandler.sendToNearby(new PacketEntity(rocket, (byte) 0, nbt), + rocket.world.provider.getDimension(), this.pos, 64); + // Link existing infrastructure (same order as parent) for (IInfrastructure infrastructure : getConnectedInfrastructure()) { rocket.linkInfrastructure(infrastructure); } + + // 7) Directly stamp tile stats from the entity we just created + rocket.recalculateStats(); + this.stats = rocket.stats.copy(); + + // Now finish up — and DO NOT reset after this + this.status = ErrorCodes.FINISHED; + this.markDirty(); + world.notifyBlockUpdate(pos, world.getBlockState(pos), world.getBlockState(pos), 3); + + // Rescan to immediately show fresh stats after build + scanRocket(world, getPos(), bbCache); } - //TODO get direction of rocket + @Override public AxisAlignedBB scanRocket(World world, BlockPos pos2, AxisAlignedBB bb) { - // TODO Refactor! Duplicated with TileRocketAssemblingMachine + // Always refresh local bounds first + AxisAlignedBB fresh = getRocketPadBounds(world, getPos()); + if (fresh == null) { + status = ErrorCodes.INCOMPLETESTRCUTURE; // upstream typo + return null; // avoid using stale bb + } + bbCache = fresh; + bb = fresh; // ensure loops below use the fresh bounds + + // fast-path: rocket entity already present? + final AxisAlignedBB buffered = bb.grow(1.0e-4, 1.0e-4, 1.0e-4); + java.util.List sdr = + world.getEntitiesWithinAABB(EntityStationDeployedRocket.class, buffered); + if (sdr.size() == 1) { + EntityStationDeployedRocket r = sdr.get(0); + r.recalculateStats(); + this.stats = r.stats.copy(); + this.status = ErrorCodes.ALREADY_ASSEMBLED; + return null; + } int thrustMonopropellant = 0; int thrustBipropellant = 0; int thrustNuclearNozzleLimit = 0; @@ -169,45 +212,36 @@ public AxisAlignedBB scanRocket(World world, BlockPos pos2, AxisAlignedBB bb) { int fuelCapacityBipropellant = 0; int fuelCapacityOxidizer = 0; int fuelCapacityNuclearWorkingFluid = 0; - int numBlocks = 0; - float drillPower = 0f; + float weight = 0f; + stats.reset(); int actualMinX = (int) bb.maxX, - actualMinY = (int) bb.maxY, - actualMinZ = (int) bb.maxZ, - actualMaxX = (int) bb.minX, - actualMaxY = (int) bb.minY, - actualMaxZ = (int) bb.minZ; - + actualMinY = (int) bb.maxY, + actualMinZ = (int) bb.maxZ, + actualMaxX = (int) bb.minX, + actualMaxY = (int) bb.minY, + actualMaxZ = (int) bb.minZ; + // tighten AABB to non-air for (int xCurr = (int) bb.minX; xCurr <= bb.maxX; xCurr++) { for (int zCurr = (int) bb.minZ; zCurr <= bb.maxZ; zCurr++) { for (int yCurr = (int) bb.minY; yCurr <= bb.maxY; yCurr++) { - - BlockPos currPos = new BlockPos(xCurr, yCurr, zCurr); - - if (!world.isAirBlock(currPos)) { - if (xCurr < actualMinX) - actualMinX = xCurr; - if (yCurr < actualMinY) - actualMinY = yCurr; - if (zCurr < actualMinZ) - actualMinZ = zCurr; - if (xCurr > actualMaxX) - actualMaxX = xCurr; - if (yCurr > actualMaxY) - actualMaxY = yCurr; - if (zCurr > actualMaxZ) - actualMaxZ = zCurr; + BlockPos p = new BlockPos(xCurr, yCurr, zCurr); + if (!world.isAirBlock(p)) { + if (xCurr < actualMinX) actualMinX = xCurr; + if (yCurr < actualMinY) actualMinY = yCurr; + if (zCurr < actualMinZ) actualMinZ = zCurr; + if (xCurr > actualMaxX) actualMaxX = xCurr; + if (yCurr > actualMaxY) actualMaxY = yCurr; + if (zCurr > actualMaxZ) actualMaxZ = zCurr; } } } } - boolean hasSatellite = false; - boolean hasGuidance = false; boolean invalidBlock = false; + boolean foundFluidTank = false; int fluidCapacity = 0; if (verifyScan(bb, world)) { @@ -216,124 +250,227 @@ public AxisAlignedBB scanRocket(World world, BlockPos pos2, AxisAlignedBB bb) { for (int zCurr = (int) bb.minZ; zCurr <= bb.maxZ; zCurr++) { BlockPos currPos = new BlockPos(xCurr, yCurr, zCurr); - if (!world.isAirBlock(currPos)) { - IBlockState state = world.getBlockState(currPos); - Block block = state.getBlock(); - - if (ARConfiguration.getCurrentConfig().blackListRocketBlocks.contains(block)) { - if (!block.isReplaceable(world, currPos)) { - invalidBlock = true; - if (!world.isRemote) - PacketHandler.sendToNearby(new PacketInvalidLocationNotify(new HashedBlockPosition(xCurr, yCurr, zCurr)), world.provider.getDimension(), getPos(), 64); + if (world.isAirBlock(currPos)) continue; + + IBlockState state = world.getBlockState(currPos); + Block block = state.getBlock(); + + // blacklist guard + if (ARConfiguration.getCurrentConfig().blackListRocketBlocks.contains(block)) { + if (!block.isReplaceable(world, currPos)) { + invalidBlock = true; + if (!world.isRemote) { + PacketHandler.sendToNearby( + new PacketInvalidLocationNotify(new HashedBlockPosition(xCurr, yCurr, zCurr)), + world.provider.getDimension(), getPos(), 64 + ); } - continue; } + continue; + } - numBlocks++; - - //If rocketEngine increaseThrust - if (block instanceof IRocketEngine) { - if (block instanceof BlockNuclearRocketMotor) { - nuclearWorkingFluidUseMax += ((IRocketEngine) block).getFuelConsumptionRate(world, xCurr, yCurr, zCurr); - thrustNuclearNozzleLimit += ((IRocketEngine) block).getThrust(world, currPos); - } else if (block instanceof BlockBipropellantRocketMotor) { - bipropellantfuelUse += ((IRocketEngine) block).getFuelConsumptionRate(world, xCurr, yCurr, zCurr); - thrustBipropellant += ((IRocketEngine) block).getThrust(world, currPos); - } else if (block instanceof BlockRocketMotor) { - monopropellantfuelUse += ((IRocketEngine) block).getFuelConsumptionRate(world, xCurr, yCurr, zCurr); - thrustMonopropellant += ((IRocketEngine) block).getThrust(world, currPos); - } + if (ARConfiguration.getCurrentConfig().advancedWeightSystem) { + weight += WeightEngine.INSTANCE.getWeight(world, currPos); + } else { + weight += 1f; // fallback: count blocks + } - stats.addEngineLocation(xCurr - actualMinX - ((float) (actualMaxX - actualMinX) / 2f), yCurr - actualMinY, zCurr - actualMinZ - ((float) (actualMaxZ - actualMinZ) / 2f)); - //stats.addEngineLocation(xCurr - actualMinX, yCurr - actualMinY, zCurr - actualMinZ); + // Engines + thrust/fuel use + if (block instanceof IRocketEngine) { + if (block instanceof BlockNuclearRocketMotor) { + nuclearWorkingFluidUseMax += ((IRocketEngine) block).getFuelConsumptionRate(world, xCurr, yCurr, zCurr); + thrustNuclearNozzleLimit += ((IRocketEngine) block).getThrust(world, currPos); + } else if (block instanceof BlockBipropellantRocketMotor) { + bipropellantfuelUse += ((IRocketEngine) block).getFuelConsumptionRate(world, xCurr, yCurr, zCurr); + thrustBipropellant += ((IRocketEngine) block).getThrust(world, currPos); + } else if (block instanceof BlockRocketMotor) { + monopropellantfuelUse += ((IRocketEngine) block).getFuelConsumptionRate(world, xCurr, yCurr, zCurr); + thrustMonopropellant += ((IRocketEngine) block).getThrust(world, currPos); } - if (block instanceof IFuelTank) { - if (block instanceof BlockFuelTank) { - fuelCapacityMonopropellant += (((IFuelTank) block).getMaxFill(world, currPos, state) * ARConfiguration.getCurrentConfig().fuelCapacityMultiplier); - } else if (block instanceof BlockBipropellantFuelTank) { - fuelCapacityBipropellant += (((IFuelTank) block).getMaxFill(world, currPos, state) * ARConfiguration.getCurrentConfig().fuelCapacityMultiplier); - } else if (block instanceof BlockOxidizerFuelTank) { - fuelCapacityOxidizer += (((IFuelTank) block).getMaxFill(world, currPos, state) * ARConfiguration.getCurrentConfig().fuelCapacityMultiplier); - } else if (block instanceof BlockNuclearFuelTank) { - fuelCapacityNuclearWorkingFluid += (((IFuelTank) block).getMaxFill(world, currPos, state) * ARConfiguration.getCurrentConfig().fuelCapacityMultiplier); - } - } + // center engine location for UI/particles + final float halfX = (actualMaxX - actualMinX + 1) / 2f; + final float halfZ = (actualMaxZ - actualMinZ + 1) / 2f; - if (block instanceof IRocketNuclearCore) { - thrustNuclearReactorLimit += ((IRocketNuclearCore) block).getMaxThrust(world, currPos); - } + final float ex = (xCurr - actualMinX + 0.5f) - halfX; + final float ez = (zCurr - actualMinZ + 0.5f) - halfZ; + final float ey = (yCurr - actualMinY); // <- no +0.5 here + + stats.addEngineLocation(ex, ey, ez); + } - if (block instanceof IIntake) { - stats.setStatTag("intakePower", (int) stats.getStatTag("intakePower") + ((IIntake) block).getIntakeAmt(state)); + // Fuel tanks (family-specific capacities) + if (block instanceof IFuelTank) { + if (block instanceof BlockBipropellantFuelTank) { + fuelCapacityBipropellant += ((IFuelTank) block).getMaxFill(world, currPos, state) * ARConfiguration.getCurrentConfig().fuelCapacityMultiplier; + } else if (block instanceof BlockOxidizerFuelTank) { + fuelCapacityOxidizer += ((IFuelTank) block).getMaxFill(world, currPos, state) * ARConfiguration.getCurrentConfig().fuelCapacityMultiplier; + } else if (block instanceof BlockNuclearFuelTank) { + fuelCapacityNuclearWorkingFluid += ((IFuelTank) block).getMaxFill(world, currPos, state) * ARConfiguration.getCurrentConfig().fuelCapacityMultiplier; + } else if (block instanceof BlockFuelTank) { + fuelCapacityMonopropellant += ((IFuelTank) block).getMaxFill(world, currPos, state) * ARConfiguration.getCurrentConfig().fuelCapacityMultiplier; } + } - TileEntity tile = world.getTileEntity(currPos); - IFluidHandler handler; + // Nuclear core limits + if (block instanceof IRocketNuclearCore) { + thrustNuclearReactorLimit += ((IRocketNuclearCore) block).getMaxThrust(world, currPos); + } - if (tile != null && (handler = tile.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, null)) != null) { - for (IFluidTankProperties info : handler.getTankProperties()) + // Intakes + if (block instanceof IIntake) { + stats.setStatTag("intakePower", + (int) stats.getStatTag("intakePower") + ((IIntake) block).getIntakeAmt(state)); + } + + // Generic fluid capability presence + capacity + TileEntity tile = world.getTileEntity(currPos); + if (tile != null) { + IFluidHandler handler = tile.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, null); + if (handler != null) { + for (IFluidTankProperties info : handler.getTankProperties()) { + if (info == null) continue; + if (!foundFluidTank && info.getCapacity() > 0) foundFluidTank = true; fluidCapacity += info.getCapacity(); + } } } } } } + // --- Nuclear working fluid scaling (guarded) --- int nuclearWorkingFluidUse = 0; if (thrustNuclearNozzleLimit > 0) { - //Only run the number of engines our cores can support - we can't throttle these effectively because they're small, so they shut off if they don't get full power thrustNuclearTotalLimit = Math.min(thrustNuclearNozzleLimit, thrustNuclearReactorLimit); - nuclearWorkingFluidUse = (int) (nuclearWorkingFluidUseMax * (thrustNuclearTotalLimit / (float) thrustNuclearNozzleLimit)); - thrustNuclearTotalLimit = (nuclearWorkingFluidUse * thrustNuclearNozzleLimit) / nuclearWorkingFluidUseMax; + if (nuclearWorkingFluidUseMax > 0) { + nuclearWorkingFluidUse = (int) (nuclearWorkingFluidUseMax * (thrustNuclearTotalLimit / (float) thrustNuclearNozzleLimit)); + thrustNuclearTotalLimit = (nuclearWorkingFluidUse * thrustNuclearNozzleLimit) / nuclearWorkingFluidUseMax; + } else { + nuclearWorkingFluidUse = 0; + thrustNuclearTotalLimit = 0; + } } - //Set fuel stats - //Thrust depending on rocket type + // Write stats stats.setBaseFuelRate(FuelType.LIQUID_MONOPROPELLANT, monopropellantfuelUse); - stats.setBaseFuelRate(FuelType.LIQUID_BIPROPELLANT, bipropellantfuelUse); - stats.setBaseFuelRate(FuelType.LIQUID_OXIDIZER, bipropellantfuelUse); + stats.setBaseFuelRate(FuelType.LIQUID_BIPROPELLANT, bipropellantfuelUse); + stats.setBaseFuelRate(FuelType.LIQUID_OXIDIZER, bipropellantfuelUse); stats.setBaseFuelRate(FuelType.NUCLEAR_WORKING_FLUID, nuclearWorkingFluidUse); - //Fuel storage depending on rocket type + + stats.setFuelRate(FuelType.LIQUID_MONOPROPELLANT, monopropellantfuelUse); + stats.setFuelRate(FuelType.LIQUID_BIPROPELLANT, bipropellantfuelUse); + stats.setFuelRate(FuelType.LIQUID_OXIDIZER, bipropellantfuelUse); + stats.setFuelRate(FuelType.NUCLEAR_WORKING_FLUID, nuclearWorkingFluidUse); + stats.setFuelCapacity(FuelType.LIQUID_MONOPROPELLANT, fuelCapacityMonopropellant); - stats.setFuelCapacity(FuelType.LIQUID_BIPROPELLANT, fuelCapacityBipropellant); - stats.setFuelCapacity(FuelType.LIQUID_OXIDIZER, fuelCapacityOxidizer); - stats.setFuelCapacity(FuelType.NUCLEAR_WORKING_FLUID, thrustNuclearTotalLimit); + stats.setFuelCapacity(FuelType.LIQUID_BIPROPELLANT, fuelCapacityBipropellant); + stats.setFuelCapacity(FuelType.LIQUID_OXIDIZER, fuelCapacityOxidizer); + stats.setFuelCapacity(FuelType.NUCLEAR_WORKING_FLUID, fuelCapacityNuclearWorkingFluid); - //Non-fuel stats - stats.setThrust(Math.max(thrustMonopropellant, thrustBipropellant)); - stats.setWeight(numBlocks); + stats.setThrust(Math.max(Math.max(thrustMonopropellant, thrustBipropellant), thrustNuclearTotalLimit)); + stats.setWeight(weight); stats.setStatTag("liquidCapacity", fluidCapacity); - //Total stats, used to check if the user has tried to apply two or more types of thrust/fuel - int totalFuel = fuelCapacityBipropellant + fuelCapacityNuclearWorkingFluid + fuelCapacityMonopropellant; + // Cross-family checks + int totalFuel = fuelCapacityBipropellant + fuelCapacityNuclearWorkingFluid + fuelCapacityMonopropellant; int totalFuelUse = bipropellantfuelUse + nuclearWorkingFluidUse + monopropellantfuelUse; - //Set status - if (invalidBlock) + if (invalidBlock) { status = ErrorCodes.INVALIDBLOCK; - else if (((fuelCapacityBipropellant > 0 && totalFuel > fuelCapacityBipropellant) || (fuelCapacityMonopropellant > 0 && totalFuel > fuelCapacityMonopropellant) || (fuelCapacityNuclearWorkingFluid > 0 && totalFuel > fuelCapacityNuclearWorkingFluid)) || ((thrustBipropellant > 0 && totalFuelUse > bipropellantfuelUse) || (thrustMonopropellant > 0 && totalFuelUse > monopropellantfuelUse) || (thrustNuclearTotalLimit > 0 && totalFuelUse > nuclearWorkingFluidUse))) + } else if (((fuelCapacityBipropellant > 0 && totalFuel > fuelCapacityBipropellant) + || (fuelCapacityMonopropellant > 0 && totalFuel > fuelCapacityMonopropellant) + || (fuelCapacityNuclearWorkingFluid > 0 && totalFuel > fuelCapacityNuclearWorkingFluid)) + || + ((thrustBipropellant > 0 && totalFuelUse > bipropellantfuelUse) + || (thrustMonopropellant > 0 && totalFuelUse > monopropellantfuelUse) + || (thrustNuclearTotalLimit > 0 && totalFuelUse > nuclearWorkingFluidUse))) { status = ErrorCodes.COMBINEDTHRUST; - else if (getThrust() < getNeededThrust()) + } else if (getThrust() <= getNeededThrust()) { status = ErrorCodes.NOENGINES; - else if (((thrustBipropellant > 0) && getFuel(FuelType.LIQUID_BIPROPELLANT) < getNeededFuel(FuelType.LIQUID_BIPROPELLANT)) || ((thrustMonopropellant > 0) && getFuel(FuelType.LIQUID_MONOPROPELLANT) < getNeededFuel(FuelType.LIQUID_MONOPROPELLANT)) || ((thrustNuclearTotalLimit > 0) && getFuel(FuelType.NUCLEAR_WORKING_FLUID) < getNeededFuel(FuelType.NUCLEAR_WORKING_FLUID))) + } else if (((int) stats.getStatTag("intakePower")) <= 0) { + status = ErrorCodes.NOINTAKE; + } else if (!foundFluidTank) { + status = ErrorCodes.NOTANK; + } else if (thrustBipropellant > 0 && (fuelCapacityBipropellant <= 0 || fuelCapacityOxidizer <= 0)) { + status = ErrorCodes.NOFUEL; // missing one of the required tanks + } else if (((thrustBipropellant > 0) && !hasEnoughFuelUnmanned(FuelType.LIQUID_BIPROPELLANT)) + || ((thrustMonopropellant > 0) && !hasEnoughFuelUnmanned(FuelType.LIQUID_MONOPROPELLANT)) + || ((thrustNuclearTotalLimit > 0) && !hasEnoughFuelUnmanned(FuelType.NUCLEAR_WORKING_FLUID))) { status = ErrorCodes.NOFUEL; - else + } else { status = ErrorCodes.SUCCESS; + } } - return new AxisAlignedBB(actualMinX, actualMinY, actualMinZ, actualMaxX, actualMaxY, actualMaxZ); + // Normalize bounds to avoid inverted AABBs on edge cases + double minX = Math.min(actualMinX, actualMaxX); + double minY = Math.min(actualMinY, actualMaxY); + double minZ = Math.min(actualMinZ, actualMaxZ); + double maxX = Math.max(actualMinX, actualMaxX); + double maxY = Math.max(actualMaxY, actualMinY); + double maxZ = Math.max(actualMinZ, actualMaxZ); + return new AxisAlignedBB(minX, minY, minZ, maxX, maxY, maxZ); } - public float getNeededFuel(@Nonnull FuelType fuelType) { - return 1; + private boolean hasEnoughFuelUnmanned(@Nonnull FuelType family) { + // SD flight: acceleration in entity code is ≈ 0.005 blocks/tick^2 + final float a_station = 0.005f; + final float targetS = 128f; // SD rocket switches to orbit after ~128 blocks + + float t; // seconds (ticks) we can sustain full burn + + switch (family) { + case LIQUID_MONOPROPELLANT: { + final int cap = stats.getFuelCapacity(FuelType.LIQUID_MONOPROPELLANT); + final int rate = stats.getBaseFuelRate(FuelType.LIQUID_MONOPROPELLANT); + if (cap <= 0 || rate <= 0) return false; + t = cap / (float) rate; + break; + } + + case LIQUID_BIPROPELLANT: { + // Both streams must exist; consume in lockstep at their own rates. + final int capFuel = stats.getFuelCapacity(FuelType.LIQUID_BIPROPELLANT); + final int capOx = stats.getFuelCapacity(FuelType.LIQUID_OXIDIZER); + final int rateFuel= stats.getBaseFuelRate(FuelType.LIQUID_BIPROPELLANT); + final int rateOx = stats.getBaseFuelRate(FuelType.LIQUID_OXIDIZER); + if (capFuel <= 0 || capOx <= 0 || rateFuel <= 0 || rateOx <= 0) return false; + + final float tFuel = capFuel / (float) rateFuel; + final float tOx = capOx / (float) rateOx; + t = Math.min(tFuel, tOx); // limiting stream dictates burn time + break; + } + + case NUCLEAR_WORKING_FLUID: { + final int cap = stats.getFuelCapacity(FuelType.NUCLEAR_WORKING_FLUID); + final int rate = stats.getBaseFuelRate(FuelType.NUCLEAR_WORKING_FLUID); + if (cap <= 0 || rate <= 0) return false; + t = cap / (float) rate; + break; + } + + default: + return false; + } + + // distance under constant accel: s = 0.5 * a * t^2 + final float sCan = 0.5f * a_station * t * t; + return sCan >= targetS; } - //No additional scanning is needed + @Override public void onLoad() { super.onLoad(); } + + @Override public void invalidate() { super.invalidate(); } + + @Override public void onChunkUnload() { super.onChunkUnload(); } + + @Override protected boolean verifyScan(AxisAlignedBB bb, World world) { return true; } -} \ No newline at end of file +} diff --git a/src/main/java/zmaster587/advancedRocketry/tile/atmosphere/TileAtmosphereDetector.java b/src/main/java/zmaster587/advancedRocketry/tile/atmosphere/TileAtmosphereDetector.java index 169933db3..a18de9642 100644 --- a/src/main/java/zmaster587/advancedRocketry/tile/atmosphere/TileAtmosphereDetector.java +++ b/src/main/java/zmaster587/advancedRocketry/tile/atmosphere/TileAtmosphereDetector.java @@ -80,7 +80,7 @@ public List getModules(int id, EntityPlayer player) { i++; } - ModuleContainerPan panningContainer = new ModuleContainerPan(5, 20, btns, new LinkedList<>(), zmaster587.libVulpes.inventory.TextureResources.starryBG, 165, 120, 0, 500); + ModuleContainerPan panningContainer = new ModuleContainerPan(5, 20, btns, new LinkedList<>(), zmaster587.libVulpes.inventory.TextureResources.starryBG, 160, 100, 0, 500); modules.add(panningContainer); return modules; } diff --git a/src/main/java/zmaster587/advancedRocketry/tile/atmosphere/TileGasChargePad.java b/src/main/java/zmaster587/advancedRocketry/tile/atmosphere/TileGasChargePad.java index 503015a99..b5ecf46b3 100644 --- a/src/main/java/zmaster587/advancedRocketry/tile/atmosphere/TileGasChargePad.java +++ b/src/main/java/zmaster587/advancedRocketry/tile/atmosphere/TileGasChargePad.java @@ -6,6 +6,8 @@ import net.minecraft.item.ItemStack; import net.minecraft.util.EnumFacing; import net.minecraft.util.math.AxisAlignedBB; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.energy.CapabilityEnergy; import net.minecraftforge.fluids.Fluid; import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.capability.CapabilityFluidHandler; @@ -19,6 +21,7 @@ import zmaster587.libVulpes.tile.TileInventoriedRFConsumerTank; import zmaster587.libVulpes.util.FluidUtils; import zmaster587.libVulpes.util.IconResource; +import zmaster587.libVulpes.cap.TeslaHandler; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -26,10 +29,23 @@ import java.util.List; public class TileGasChargePad extends TileInventoriedRFConsumerTank implements IModularInventory { + private static final int TICK_INTERVAL = 2; + // Avoid per-tick AABB allocation: cache lazily + @Nullable + private AxisAlignedBB cachedPlayerBox; + public TileGasChargePad() { super(0, 2, 16000); } + // Lazy AABB getter + private AxisAlignedBB getPlayerBox() { + if (cachedPlayerBox == null) { + cachedPlayerBox = new AxisAlignedBB(pos, pos.add(1, 2, 1)); + } + return cachedPlayerBox; + } + @Override @Nonnull public int[] getSlotsForFace(@Nullable EnumFacing side) { @@ -51,10 +67,45 @@ public int getPowerPerOperation() { return 0; } + @Override + public boolean hasCapability(Capability capability, @Nullable EnumFacing facing) { + // Hide Forge Energy capability + if (capability == CapabilityEnergy.ENERGY) return false; + // Hide any Tesla capability the base class would expose + if (TeslaHandler.hasTeslaCapability(this, capability)) return false; + return super.hasCapability(capability, facing); + } + + @Override + @Nullable + public T getCapability(Capability capability, @Nullable EnumFacing facing) { + // Don’t provide energy handlers to probes/pipes + if (capability == CapabilityEnergy.ENERGY) return null; + if (TeslaHandler.hasTeslaCapability(this, capability)) return null; + return super.getCapability(capability, facing); + } + + // Optional (extra safety for mods that query IPower-style methods directly) + @Override public boolean canConnectEnergy(EnumFacing side) { return false; } + @Override public boolean canReceive() { return false; } + @Override public int getEnergyStored(EnumFacing side) { return 0; } + @Override public int getMaxEnergyStored(EnumFacing side) { return 0; } + @Override public boolean canPerformFunction() { if (!world.isRemote) { - for (EntityPlayer player : this.world.getEntitiesWithinAABB(EntityPlayer.class, new AxisAlignedBB(pos, pos.add(1, 2, 1)))) { + + // Throttle: only run every TICK_INTERVAL ticks + if ((world.getTotalWorldTime() % TICK_INTERVAL) != 0) { + return false; + } + + FluidStack tf = this.tank.getFluid(); + if (tf == null || tf.amount <= 0) { + return false; + } + + for (EntityPlayer player : this.world.getEntitiesWithinAABB(EntityPlayer.class, getPlayerBox())) { ItemStack stack = player.getItemStackFromSlot(EntityEquipmentSlot.CHEST); if (!stack.isEmpty()) { @@ -67,15 +118,18 @@ else if (ItemAirUtils.INSTANCE.isStackValidAirContainer(stack)) //Check for O2 fill if (fillable != null) { - int amtFluid = fillable.getMaxAir(stack) - fillable.getAirRemaining(stack); - FluidStack fluidStack = this.drain(amtFluid, false); - - if (amtFluid > 0 && fluidStack != null && FluidUtils.areFluidsSameType(fluidStack.getFluid(), AdvancedRocketryFluids.fluidOxygen) && fluidStack.amount > 0) { - FluidStack fstack = this.drain(amtFluid, true); - this.markDirty(); - world.markChunkDirty(getPos(), this); - fillable.increment(stack, fstack.amount); - return true; + int deficit = fillable.getMaxAir(stack) - fillable.getAirRemaining(stack); + tf = this.tank.getFluid(); // refresh + if (deficit > 0 && tf != null + && FluidUtils.areFluidsSameType(tf.getFluid(), AdvancedRocketryFluids.fluidOxygen) + && tf.amount > 0) { + int toDrain = Math.min(deficit, tf.amount); + FluidStack drained = this.drain(toDrain, true); + if (drained != null && drained.amount > 0) { + fillable.increment(stack, drained.amount); + this.markDirty(); // no world.markChunkDirty + return true; + } } } } @@ -85,36 +139,39 @@ else if (ItemAirUtils.INSTANCE.isStackValidAirContainer(stack)) if (this.tank.getFluid() != null && !FluidUtils.areFluidsSameType(this.tank.getFluid().getFluid(), AdvancedRocketryFluids.fluidOxygen) && !stack.isEmpty() && stack.getItem() instanceof IModularArmor) { IInventory inv = ((IModularArmor) stack.getItem()).loadModuleInventory(stack); - FluidStack fluidStack = this.drain(100, false); - if (fluidStack != null) { + // Create a trial FluidStack up to available amount + final int perAttempt = 100 * TICK_INTERVAL; + int trialAmt = Math.min(perAttempt, this.tank.getFluid().amount); + if (trialAmt > 0) { + FluidStack trial = new FluidStack(this.tank.getFluid(), trialAmt); for (int i = 0; i < inv.getSizeInventory(); i++) { if (!((IModularArmor) stack.getItem()).canBeExternallyModified(stack, i)) continue; ItemStack module = inv.getStackInSlot(i); - if (FluidUtils.containsFluid(module)) { - int amtFilled = module.getCapability(CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY, EnumFacing.UP).fill(fluidStack, true); - if (amtFilled == 100) { - this.drain(100, true); - - this.markDirty(); - world.markChunkDirty(getPos(), this); - - ((IModularArmor) stack.getItem()).saveModuleInventory(stack, inv); - - return true; - } + if (!FluidUtils.containsFluid(module)) continue; // fast path + net.minecraftforge.fluids.capability.IFluidHandlerItem fh = + module.getCapability(CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY, EnumFacing.UP); + if (fh == null) continue; // null-guard + + int amtFilled = fh.fill(trial, true); + // Accept partial fills: drain exactly what was accepted + if (amtFilled > 0) { + this.drain(amtFilled, true); + this.markDirty(); // no world.markChunkDirty + ((IModularArmor) stack.getItem()).saveModuleInventory(stack, inv); + return true; } - } - } - } - - return false; - } - } - return false; - } + } + } + } + } + // no player matched this tick + return false; + } + return false; + } @Override public void performFunction() { @@ -161,4 +218,23 @@ private boolean useBucket(int slot, @Nonnull ItemStack stack) { public boolean isEmpty() { return inventory.isEmpty(); } + + @Override + public void invalidate() { + super.invalidate(); + cachedPlayerBox = null; // drop cached AABB + } + + @Override + public void onChunkUnload() { + super.onChunkUnload(); + cachedPlayerBox = null; // drop cached AABB + } + + @Override + public void onLoad() { + super.onLoad(); + // Ensure cache recomputes from the current pos after NBT load + cachedPlayerBox = null; + } } diff --git a/src/main/java/zmaster587/advancedRocketry/tile/cables/TileWirelessTransciever.java b/src/main/java/zmaster587/advancedRocketry/tile/cables/TileWirelessTransciever.java index 7687b9353..69a3c44ba 100644 --- a/src/main/java/zmaster587/advancedRocketry/tile/cables/TileWirelessTransciever.java +++ b/src/main/java/zmaster587/advancedRocketry/tile/cables/TileWirelessTransciever.java @@ -21,12 +21,13 @@ import zmaster587.advancedRocketry.inventory.TextureResources; import zmaster587.advancedRocketry.world.util.MultiData; import zmaster587.libVulpes.LibVulpes; -import zmaster587.libVulpes.block.RotatableBlock; import zmaster587.libVulpes.interfaces.ILinkableTile; import zmaster587.libVulpes.inventory.modules.IModularInventory; import zmaster587.libVulpes.inventory.modules.IToggleButton; import zmaster587.libVulpes.inventory.modules.ModuleBase; +import zmaster587.libVulpes.inventory.modules.ModuleText; import zmaster587.libVulpes.inventory.modules.ModuleToggleSwitch; +import zmaster587.advancedRocketry.inventory.modules.ModuleWirelessBufferBar; import zmaster587.libVulpes.items.ItemLinker; import zmaster587.libVulpes.network.PacketHandler; import zmaster587.libVulpes.network.PacketMachine; @@ -38,6 +39,20 @@ public class TileWirelessTransciever extends TileEntity implements INetworkMachine, IModularInventory, ILinkableTile, IDataHandler, ITickable, IToggleButton { + + private int transferIntervalTicks = 20; // How often to transfer data (in ticks) + private int phase = -1; // Fixed phase per tile to spread load + private ModuleText netIdLabel; // Show network ID (label) + private final DataStorage uiBuffer = new DataStorage(); // UI-only, never used for logic + + + + // Avoid per-call allocations from DataType.values() + // needs update if DataType enum changes + private static final DataType[] TYPES = { + DataType.DISTANCE, DataType.HUMIDITY, DataType.TEMPERATURE, + DataType.COMPOSITION, DataType.ATMOSPHEREDENSITY, DataType.MASS + }; protected ModuleToggleSwitch toggleSwitch; boolean extractMode; @@ -51,10 +66,79 @@ public TileWirelessTransciever() { networkID = -1; data = new MultiData(); data.setMaxData(100); + uiBuffer.setMaxData(data.getMaxData()); // UI mirror max should match MultiData max + uiBuffer.setData(0, DataType.UNDEFINED); + syncUiBufferFromMultiData(); toggle = new ModuleToggleSwitch(50, 50, 0, LibVulpes.proxy.getLocalizedString("msg.wirelessTransciever.extract"), this, TextureResources.buttonGeneric, 64, 18, false); toggleSwitch = new ModuleToggleSwitch(160, 5, 1, "", this, zmaster587.libVulpes.inventory.TextureResources.buttonToggleImage, 11, 26, true); + + // Align internal booleans with UI defaults + extractMode = toggle.getState(); // false initially + enabled = toggleSwitch.getState(); // true initially + updateToggleLabel(); + + // Network ID label + String initial = LibVulpes.proxy.getLocalizedString("msg.wirelessTransciever.network") + " -"; + netIdLabel = new ModuleText(40, 72, initial, 0x000000); + netIdLabel.setAlwaysOnTop(true); + + } + + // Helpers for other terminals to query state + public boolean isEnabledWireless() { + return this.enabled; } + public boolean isExtractModeWireless() { + return this.extractMode; + } + + // helper for syncing UI buffer from multiData + private void syncUiBufferFromMultiData() { + int total = 0; + int max = data.getMaxData(); + + int nonZero = 0; + DataType lastTypeWithData = DataType.UNDEFINED; + for (DataType t : TYPES) { + int amt = data.getDataAmount(t); + if (amt > 0) { + nonZero++; + lastTypeWithData = t; + total += amt; + } + } + + if (total < 0) total = 0; + if (total > max) total = max; + + final DataType displayType = (nonZero == 1) ? lastTypeWithData : DataType.UNDEFINED; + + // Mirror into UI-only storage (server-authoritative; GUI module will poll this) + uiBuffer.setMaxData(max); + uiBuffer.setData(total, displayType); + } + + private void updateToggleLabel() { + if (toggle != null) { + String key = extractMode + ? "msg.wirelessTransciever.extract" // pulling from side → network + : "msg.wirelessTransciever.insert"; // pushing from network → side + toggle.setText(LibVulpes.proxy.getLocalizedString(key)); + } + } + + private EnumFacing resolveFront(IBlockState state) { + if (state == null) return EnumFacing.NORTH; + if (state.getBlock() instanceof zmaster587.advancedRocketry.block.BlockTransciever) { + return zmaster587.advancedRocketry.block.BlockTransciever.getFront(state); + } + // fallback for legacy blocks if any remain + if (state.getBlock() instanceof zmaster587.libVulpes.block.RotatableBlock) { + return zmaster587.libVulpes.block.RotatableBlock.getFront(state); + } + return EnumFacing.NORTH; + } @Override public boolean onLinkStart(@Nonnull ItemStack item, TileEntity entity, EntityPlayer player, World world) { @@ -70,15 +154,35 @@ public boolean onLinkStart(@Nonnull ItemStack item, TileEntity entity, EntityPla @Override public void onChunkUnload() { super.onChunkUnload(); - if (NetworkRegistry.dataNetwork.doesNetworkExist(networkID)) + + // Leave the network cleanly if linked + if (networkID != -1 && NetworkRegistry.dataNetwork.doesNetworkExist(networkID)) { NetworkRegistry.dataNetwork.getNetwork(networkID).removeFromAll(this); + } + + // Clear per-instance caches + phase = -1; + + // Clear UI-only mirror (logic stays in 'data') + uiBuffer.setMaxData(data.getMaxData()); + uiBuffer.setData(0, DataType.UNDEFINED); } + @Override public boolean onLinkComplete(@Nonnull ItemStack item, TileEntity entity, EntityPlayer player, World world) { BlockPos pos = ItemLinker.getMasterCoords(item); + if (pos == null) return false; // defensive + + if (pos.equals(this.pos)) { + if (world.isRemote) player.sendMessage(new TextComponentTranslation("msg.linker.sameblock")); + return false; + } + + if (!world.isBlockLoaded(pos)) return false; TileEntity tile = world.getTileEntity(pos); + if (!(tile instanceof TileWirelessTransciever)) return false; if (tile instanceof TileWirelessTransciever) { if (world.isRemote) { @@ -103,6 +207,16 @@ public boolean onLinkComplete(@Nonnull ItemStack item, TileEntity entity, Entity addToNetwork(); ((TileWirelessTransciever) tile).addToNetwork(); + + //SYNC CLIENT UI/STATE FOR BOTH TILES + this.markDirty(); + world.notifyBlockUpdate(this.pos, world.getBlockState(this.pos), world.getBlockState(this.pos), 3); + + tile.markDirty(); + world.notifyBlockUpdate(tile.getPos(), world.getBlockState(tile.getPos()), + world.getBlockState(tile.getPos()), 3); + + ItemLinker.resetPosition(item); return true; @@ -159,10 +273,15 @@ public List getModules(int id, EntityPlayer player) { list.add(toggle); list.add(toggleSwitch); + list.add(netIdLabel); + + // Bar-only UI + list.add(new ModuleWirelessBufferBar(14, 22, uiBuffer)); return list; } + @Override public String getModularInventoryName() { return "tile.wirelessTransciever.name"; @@ -189,134 +308,231 @@ public void readDataFromNetwork(ByteBuf in, byte packetId, } @Override - public void useNetworkData(EntityPlayer player, Side side, byte id, - NBTTagCompound nbt) { - - if (side.isServer()) { - if (id == 0) { - extractMode = nbt.getBoolean("state"); - if (NetworkRegistry.dataNetwork.doesNetworkExist(networkID)) { - NetworkRegistry.dataNetwork.getNetwork(networkID).removeFromAll(this); - - if (extractMode) - NetworkRegistry.dataNetwork.getNetwork(networkID).addSource(this, EnumFacing.UP); - else - NetworkRegistry.dataNetwork.getNetwork(networkID).addSink(this, EnumFacing.UP); + public void useNetworkData(EntityPlayer player, Side side, byte id, NBTTagCompound nbt) { + if (!side.isServer()) return; + + if (id == 1) { // enable/disable + enabled = nbt.getBoolean("state"); + + // persist + push to clients + this.markDirty(); + world.notifyBlockUpdate(pos, world.getBlockState(pos), world.getBlockState(pos), 3); + return; + } + + if (id == 0) { // extract/insert + extractMode = nbt.getBoolean("state"); + updateToggleLabel(); + + if (networkID != -1 && NetworkRegistry.dataNetwork.doesNetworkExist(networkID)) { + NetworkRegistry.dataNetwork.getNetwork(networkID).removeFromAll(this); + if (extractMode) { + NetworkRegistry.dataNetwork.getNetwork(networkID).addSource(this, EnumFacing.UP); + } else { + NetworkRegistry.dataNetwork.getNetwork(networkID).addSink(this, EnumFacing.UP); } - } else if (id == 1) { - enabled = nbt.getBoolean("state"); } + + // persist + push to clients + this.markDirty(); + world.notifyBlockUpdate(pos, world.getBlockState(pos), world.getBlockState(pos), 3); + return; } } + + @Override public void readFromNBT(NBTTagCompound nbt) { super.readFromNBT(nbt); extractMode = nbt.getBoolean("mode"); - enabled = nbt.getBoolean("enabled"); - networkID = nbt.getInteger("networkID"); + enabled = nbt.getBoolean("enabled"); + networkID = nbt.getInteger("networkID"); data.readFromNBT(nbt); - //addToNetwork(); - toggle.setToggleState(extractMode); - toggleSwitch.setToggleState(enabled); - } + syncUiBufferFromMultiData(); + + // Mirror persisted booleans into UI widgets (guard null on client) + if (toggle != null) toggle.setToggleState(extractMode); + if (toggleSwitch != null) toggleSwitch.setToggleState(enabled); + updateToggleLabel(); + // Client-only network label text + if (world != null && world.isRemote && netIdLabel != null) { + String idStr = (networkID == -1) + ? net.minecraft.client.resources.I18n.format("msg.wirelessTransciever.network.unlinked") + : Integer.toString(networkID); + String label = LibVulpes.proxy.getLocalizedString("msg.wirelessTransciever.network"); + netIdLabel.setText(label + idStr); + } + } + @Override @Nonnull public NBTTagCompound writeToNBT(NBTTagCompound nbt) { + super.writeToNBT(nbt); nbt.setBoolean("mode", extractMode); nbt.setBoolean("enabled", enabled); nbt.setInteger("networkID", networkID); data.writeToNBT(nbt); - return super.writeToNBT(nbt); + return nbt; } + @Override - public int extractData(int maxAmount, DataType type, EnumFacing dir, - boolean commit) { - return enabled ? data.extractData(maxAmount, type, dir, commit) : 0; + public int extractData(int maxAmount, DataType type, EnumFacing dir, boolean commit) { + int out = enabled ? data.extractData(maxAmount, type, dir, commit) : 0; + if (commit && out > 0) { + syncUiBufferFromMultiData(); + } + return out; } @Override - public int addData(int maxAmount, DataType type, EnumFacing dir, - boolean commit) { - return enabled ? data.addData(maxAmount, type, dir, commit) : 0; + public int addData(int maxAmount, DataType type, EnumFacing dir, boolean commit) { + int in = enabled ? data.addData(maxAmount, type, dir, commit) : 0; + if (commit && in > 0) { + syncUiBufferFromMultiData(); + } + return in; } + @Override public void onLoad() { super.onLoad(); + if (!world.isRemote) { + // Rebuild UI mirror from authoritative MultiData + syncUiBufferFromMultiData(); - if (!NetworkRegistry.dataNetwork.doesNetworkExist(networkID)) - NetworkRegistry.dataNetwork.getNewNetworkID(networkID); + // Mirror persisted booleans -> widgets (do NOT read from widgets) + if (toggle != null) toggle.setToggleState(extractMode); + if (toggleSwitch != null) toggleSwitch.setToggleState(enabled); + updateToggleLabel(); + } - NetworkRegistry.dataNetwork.getNetwork(networkID).removeFromAll(this); + if (world == null || world.isRemote) return; + + // Recompute throttle phase after we cleared it + if (transferIntervalTicks <= 0) transferIntervalTicks = 20; + phase = (int) Math.floorMod(this.pos.toLong(), transferIntervalTicks); - if (extractMode) + // Rejoin the network with the correct role (source/sink) + if (networkID != -1) { + if (!NetworkRegistry.dataNetwork.doesNetworkExist(networkID)) { + NetworkRegistry.dataNetwork.getNewNetworkID(networkID); + } + NetworkRegistry.dataNetwork.getNetwork(networkID).removeFromAll(this); + if (extractMode) { NetworkRegistry.dataNetwork.getNetwork(networkID).addSource(this, EnumFacing.UP); - else + } else { NetworkRegistry.dataNetwork.getNetwork(networkID).addSink(this, EnumFacing.UP); - + } } } + + @Override public void update() { + // Server only + if (world.isRemote) return; - if (!world.isRemote) { - IBlockState state = world.getBlockState(getPos()); - if (state.getBlock() instanceof RotatableBlock) { - EnumFacing facing = RotatableBlock.getFront(state).getOpposite(); - - TileEntity tile = world.getTileEntity(getPos().add(facing.getFrontOffsetX(), facing.getFrontOffsetY(), facing.getFrontOffsetZ())); - - if (tile instanceof IDataHandler && !(tile instanceof TileWirelessTransciever)) { - for (DataType data : DataType.values()) { - - if (data == DataStorage.DataType.UNDEFINED) - continue; - - if (!extractMode) { - int amountCurrent = this.data.getDataAmount(data); - if (amountCurrent > 0) { - int amt = ((IDataHandler) tile).addData(amountCurrent, data, facing.getOpposite(), true); - this.data.extractData(amt, data, facing.getOpposite(), true); - } - } else { - int amt = ((IDataHandler) tile).extractData(this.data.getMaxData() - this.data.getDataAmount(data), data, facing.getOpposite(), true); - this.data.addData(amt, data, facing.getOpposite(), true); - } - } + // Respect front-panel enable switch + if (!enabled) return; + + // Guard against bad values (e.g., NBT edits) + if (transferIntervalTicks <= 0) transferIntervalTicks = 20; + + // Initialize a stable phase to spread load across ticks + if (phase < 0) { + phase = (int) Math.floorMod(this.pos.toLong(), transferIntervalTicks); + } + + // Throttle: only run on the tile's assigned tick + long now = world.getTotalWorldTime(); + if (((now + phase) % transferIntervalTicks) != 0) return; + + IBlockState state = world.getBlockState(getPos()); + + // Resolve front for either 6-way or legacy block + EnumFacing front = resolveFront(state); + EnumFacing facing = front.getOpposite(); + TileEntity neighbor = world.getTileEntity(getPos().offset(facing)); + if (neighbor == null || neighbor instanceof TileWirelessTransciever) return; + if (!(neighbor instanceof IDataHandler)) return; + + boolean changed = false; + + for (DataType dataType : TYPES) { + + if (!extractMode) { + // PUSH: from this buffer -> neighbor + int have = this.data.getDataAmount(dataType); + if (have <= 0) continue; + + int moved = ((IDataHandler) neighbor).addData(have, dataType, facing.getOpposite(), true); + if (moved > 0) { + this.data.extractData(moved, dataType, facing.getOpposite(), true); + changed = true; + } + } else { + // PULL: from neighbor -> this buffer + int room = this.data.getMaxData() - this.data.getDataAmount(dataType); + if (room <= 0) continue; + + int moved = ((IDataHandler) neighbor).extractData(room, dataType, facing.getOpposite(), true); + if (moved > 0) { + this.data.addData(moved, dataType, facing.getOpposite(), true); + changed = true; } } } + + + // Persist changes; a full block update isn't strictly required each tick + if (changed) { + this.markDirty(); + syncUiBufferFromMultiData(); + } } @Override public void onInventoryButtonPressed(int buttonId) { - if (buttonId == 1) + if (buttonId == 1) { enabled = toggleSwitch.getState(); - else if (buttonId == 0) + } else if (buttonId == 0) { extractMode = toggle.getState(); + updateToggleLabel(); + } PacketHandler.sendToServer(new PacketMachine(this, (byte) buttonId)); } - @Override public void stateUpdated(ModuleBase module) { - if (module == toggleSwitch) + if (module == toggleSwitch) { enabled = toggleSwitch.getState(); - else if (module == toggle) + } else if (module == toggle) { extractMode = toggle.getState(); + updateToggleLabel(); + } if (!world.isRemote) { this.markDirty(); world.notifyBlockUpdate(pos, world.getBlockState(pos), world.getBlockState(pos), 3); } } + @Override + public void invalidate() { + if (!world.isRemote && networkID != -1 && NetworkRegistry.dataNetwork.doesNetworkExist(networkID)) { + NetworkRegistry.dataNetwork.getNetwork(networkID).removeFromAll(this); + } + // Clear per-instance caches + phase = -1; + super.invalidate(); + } } diff --git a/src/main/java/zmaster587/advancedRocketry/tile/infrastructure/TileFuelingStation.java b/src/main/java/zmaster587/advancedRocketry/tile/infrastructure/TileFuelingStation.java index 70a77e210..8733f8fd5 100644 --- a/src/main/java/zmaster587/advancedRocketry/tile/infrastructure/TileFuelingStation.java +++ b/src/main/java/zmaster587/advancedRocketry/tile/infrastructure/TileFuelingStation.java @@ -17,7 +17,10 @@ import net.minecraftforge.fluids.FluidRegistry; import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.capability.CapabilityFluidHandler; +import net.minecraftforge.fluids.capability.IFluidHandler; +import net.minecraftforge.fluids.capability.IFluidTankProperties; import net.minecraftforge.fml.relauncher.Side; +import zmaster587.advancedRocketry.AdvancedRocketry; import zmaster587.advancedRocketry.api.*; import zmaster587.advancedRocketry.api.fuel.FuelRegistry; import zmaster587.advancedRocketry.api.fuel.FuelRegistry.FuelType; @@ -41,13 +44,28 @@ import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.List; +import java.util.Objects; public class TileFuelingStation extends TileInventoriedRFConsumerTank implements IModularInventory, IMultiblock, IInfrastructure, ILinkableTile, INetworkMachine, IButtonInventory { + private EntityRocketBase linkedRocket; private HashedBlockPosition masterBlock; private ModuleRedstoneOutputButton redstoneControl; private RedstoneState state; + // Tune cadence: Ticks between operations + private static final int OP_THROTTLE_TICKS = 5; + + // Stop polling after full for current link/fluid + private boolean fuelingActive = false; + + // Cache last emitted redstone to avoid duplicate updates + private Boolean lastRs = null; + + // Cache resolved fluids from rocket stats + private String lastFuelStr = null, lastOxStr = null, lastWorkStr = null; + private Fluid cachedFuelFluid = null, cachedOxFluid = null, cachedWorkFluid = null; + public TileFuelingStation() { super(1000, 3, 5000); masterBlock = new HashedBlockPosition(0, -1, 0); @@ -55,61 +73,268 @@ public TileFuelingStation() { state = RedstoneState.ON; } - @Override - public int getMaxLinkDistance() { - return 10; + private void syncTE() { + markDirty(); + net.minecraft.block.state.IBlockState s = world.getBlockState(pos); + world.notifyBlockUpdate(pos, s, s, 3); } + @Override + public int getMaxLinkDistance() { return 10; } + + // ---- redstone emission with duplicate suppression ---- private void setRedstoneState(boolean condition) { - if (state == RedstoneState.INVERTED) - condition = !condition; - else if (state == RedstoneState.OFF) - condition = false; - ((BlockTileRedstoneEmitter) AdvancedRocketryBlocks.blockFuelingStation).setRedstoneState(world, world.getBlockState(pos), pos, condition); + if (world == null || world.isRemote) return; + + if (state == RedstoneState.INVERTED) condition = !condition; + else if (state == RedstoneState.OFF) condition = false; + + if (lastRs != null && lastRs == condition) return; + lastRs = condition; + net.minecraft.block.state.IBlockState s = world.getBlockState(pos); + if (AdvancedRocketryBlocks.blockFuelingStation instanceof BlockTileRedstoneEmitter) { + ((BlockTileRedstoneEmitter) AdvancedRocketryBlocks.blockFuelingStation) + .setRedstoneState(world, s, pos, condition); + } + markDirty(); } + + // ---- small cache to avoid repeated FluidRegistry lookups per tick ---- + private void refreshFluidCachesIfNeeded() { + if (linkedRocket == null || linkedRocket.stats == null) return; + String f = linkedRocket.stats.getFuelFluid(); + String o = linkedRocket.stats.getOxidizerFluid(); + String w = linkedRocket.stats.getWorkingFluid(); + + if (!Objects.equals(f, lastFuelStr)) { + lastFuelStr = f; + cachedFuelFluid = (f == null || "null".equals(f) || f.isEmpty()) ? null : FluidRegistry.getFluid(f); + } + if (!Objects.equals(o, lastOxStr)) { + lastOxStr = o; + cachedOxFluid = (o == null || "null".equals(o) || o.isEmpty()) ? null : FluidRegistry.getFluid(o); + } + if (!Objects.equals(w, lastWorkStr)) { + lastWorkStr = w; + cachedWorkFluid = (w == null || "null".equals(w) || w.isEmpty()) ? null : FluidRegistry.getFluid(w); + } + } + + private boolean isStationFluidForThisRocket(Fluid current) { + refreshFluidCachesIfNeeded(); + if (current == null || linkedRocket == null) return false; + + // --- compare by fluid name, not by instance --- + final String currentName = current.getName(); + + // If a specific fluid was already chosen, match directly by name. + if (lastFuelStr != null && !"null".equals(lastFuelStr) && !lastFuelStr.isEmpty() && currentName.equals(lastFuelStr)) { + return true; + } + if (lastOxStr != null && !"null".equals(lastOxStr) && !lastOxStr.isEmpty() && currentName.equals(lastOxStr)) { + return true; + } + if (lastWorkStr != null && !"null".equals(lastWorkStr) && !lastWorkStr.isEmpty() && currentName.equals(lastWorkStr)) { + return true; + } + + // Allow first-time lock-in when rocket hasn't chosen a fluid yet, + // but DOES have capacity for the corresponding tank and "current" is valid for that type. + if ("null".equals(linkedRocket.stats.getFuelFluid())) { + if ((linkedRocket.getFuelCapacity(FuelType.LIQUID_MONOPROPELLANT) > 0 && + FuelRegistry.instance.isFuel(FuelType.LIQUID_MONOPROPELLANT, current)) || + (linkedRocket.getFuelCapacity(FuelType.LIQUID_BIPROPELLANT) > 0 && + FuelRegistry.instance.isFuel(FuelType.LIQUID_BIPROPELLANT, current))) { + return true; + } + } + + if ("null".equals(linkedRocket.stats.getOxidizerFluid())) { + if (linkedRocket.getFuelCapacity(FuelType.LIQUID_OXIDIZER) > 0 && + FuelRegistry.instance.isFuel(FuelType.LIQUID_OXIDIZER, current)) { + return true; + } + } + + if ("null".equals(linkedRocket.stats.getWorkingFluid())) { + if (linkedRocket.getFuelCapacity(FuelType.NUCLEAR_WORKING_FLUID) > 0 && + FuelRegistry.instance.isFuel(FuelType.NUCLEAR_WORKING_FLUID, current)) { + return true; + } + } + + return false; + } + + + @Override + public void update() { + if (world.isRemote) return; + + // Lightweight bucket poll every 10 ticks (automation/hoppers) + if ((world.getTotalWorldTime() % 10L) == 0L) { + ItemStack in = inventory.getStackInSlot(0); + if (!in.isEmpty() && useBucket(0, in)) { + syncTE(); // only when something actually changed + } + } + + super.update(); // IMPORTANT: preserve parent RF/ticking pipeline + } + + @Override public void performFunction() { - if (!world.isRemote) { - //Lock rocket to a specific fluid so that it has only one oxidizer/bipropellant/monopropellant/etc - FluidStack currentFluidStack = tank.getFluid(); - if (currentFluidStack != null) { - Fluid currentFluid = currentFluidStack.getFluid(); - - //Check to see if we should set the rocket fuel - if (linkedRocket.stats.getFuelFluid().equals("null")) { - if ((FuelRegistry.instance.isFuel(FuelType.LIQUID_MONOPROPELLANT, currentFluid) && linkedRocket.getFuelCapacity(FuelType.LIQUID_MONOPROPELLANT) > 0) || (FuelRegistry.instance.isFuel(FuelType.LIQUID_BIPROPELLANT, currentFluid) && linkedRocket.getFuelCapacity(FuelType.LIQUID_BIPROPELLANT) > 0)) - linkedRocket.stats.setFuelFluid(currentFluid.getName()); - } - if (linkedRocket.stats.getOxidizerFluid().equals("null")) { - if (FuelRegistry.instance.isFuel(FuelType.LIQUID_OXIDIZER, currentFluid)) - linkedRocket.stats.setOxidizerFluid(currentFluid.getName()); - } - if (linkedRocket.stats.getWorkingFluid().equals("null")) { - if (FuelRegistry.instance.isFuel(FuelType.NUCLEAR_WORKING_FLUID, currentFluid)) - linkedRocket.stats.setWorkingFluid(currentFluid.getName()); - } + if (world.isRemote) return; // server-only + + if (!fuelingActive) { + FluidStack fs = tank.getFluid(); + boolean relevant = false, room = false; + + if (linkedRocket != null && fs != null) { + Fluid f = fs.getFluid(); + // relevant if this fluid matches what this rocket can actually use + relevant = isStationFluidForThisRocket(f); + // room if the matching logical tank has capacity + room = relevant && canRocketFitFluid(f); + } - //Actually fill the fuel if that is the case - if (currentFluid == FluidRegistry.getFluid(linkedRocket.stats.getFuelFluid()) || currentFluid == FluidRegistry.getFluid(linkedRocket.stats.getOxidizerFluid()) || currentFluid == FluidRegistry.getFluid(linkedRocket.stats.getWorkingFluid())) { - if (linkedRocket.getRocketFuelType() == FuelType.LIQUID_BIPROPELLANT && FuelRegistry.instance.isFuel(FuelType.LIQUID_OXIDIZER, currentFluid)) { - int fuelRate = (int) (FuelRegistry.instance.getMultiplier(FuelType.LIQUID_OXIDIZER, currentFluid) * linkedRocket.stats.getBaseFuelRate(FuelType.LIQUID_OXIDIZER)); - tank.drain(linkedRocket.addFuelAmount(FuelType.LIQUID_OXIDIZER, ARConfiguration.getCurrentConfig().fuelPointsPer10Mb), true); - linkedRocket.setFuelConsumptionRate(FuelType.LIQUID_OXIDIZER, fuelRate); - } else { - int fuelRate = (int) (FuelRegistry.instance.getMultiplier(linkedRocket.getRocketFuelType(), currentFluid) * linkedRocket.stats.getBaseFuelRate(linkedRocket.getRocketFuelType())); - tank.drain(linkedRocket.addFuelAmount(linkedRocket.getRocketFuelType(), ARConfiguration.getCurrentConfig().fuelPointsPer10Mb), true); - linkedRocket.setFuelConsumptionRate(linkedRocket.getRocketFuelType(), fuelRate); - } + setRedstoneState(relevant && !room); // emit when relevant but full + fuelingActive = room; // arm when relevant and there’s room + if (!fuelingActive) return; + } - } + // from here: only do rocket-facing work when it's worth it... + if (linkedRocket == null) { + fuelingActive = false; + setRedstoneState(false); + return; + } + + // Stop all work once full (until unlink/relink) + if (!fuelingActive) { + FluidStack fs = tank.getFluid(); + if (fs != null && canRocketFitFluid(fs.getFluid())) { + fuelingActive = true; // resume fueling after reload + // don’t run expensive work this tick; we’ll catch it on the next throttled pass + } else { + // already full or no relevant fluid — keep RS accurate + setRedstoneState(fs != null && isStationFluidForThisRocket(fs.getFluid()) && !canRocketFitFluid(fs.getFluid())); + } + return; + } + + // Throttle only the expensive fueling/redstone path + if ((world.getTotalWorldTime() % OP_THROTTLE_TICKS) != 0L) return; + + FluidStack currentFluidStack = tank.getFluid(); + if (currentFluidStack == null) { + // No fluid to offer; keep fuelingActive so we’ll retry when fluid arrives + setRedstoneState(false); + return; + } + + final Fluid currentFluid = currentFluidStack.getFluid(); + + // Lock rocket to specific fluids if unset + if ("null".equals(linkedRocket.stats.getFuelFluid())) { + if ((FuelRegistry.instance.isFuel(FuelType.LIQUID_MONOPROPELLANT, currentFluid) && linkedRocket.getFuelCapacity(FuelType.LIQUID_MONOPROPELLANT) > 0) + || (FuelRegistry.instance.isFuel(FuelType.LIQUID_BIPROPELLANT, currentFluid) && linkedRocket.getFuelCapacity(FuelType.LIQUID_BIPROPELLANT) > 0)) { + linkedRocket.stats.setFuelFluid(currentFluid.getName()); + } + } + if ("null".equals(linkedRocket.stats.getOxidizerFluid())) { + if (FuelRegistry.instance.isFuel(FuelType.LIQUID_OXIDIZER, currentFluid)) { + linkedRocket.stats.setOxidizerFluid(currentFluid.getName()); + } + } + if ("null".equals(linkedRocket.stats.getWorkingFluid())) { + if (FuelRegistry.instance.isFuel(FuelType.NUCLEAR_WORKING_FLUID, currentFluid)) { + linkedRocket.stats.setWorkingFluid(currentFluid.getName()); + } + } + + // Update caches after potential stat change + refreshFluidCachesIfNeeded(); + + // If station fluid isn't relevant for this rocket, we can't help + if (!isStationFluidForThisRocket(currentFluid)) { + setRedstoneState(false); + fuelingActive = false; // go fully idle if station fluid not relevant + return; + } + + if (!canRocketFitFluid(currentFluid)) { + setRedstoneState(true); + fuelingActive = false; // early-return above- next pass + markDirty(); + return; + } - //If the rocket is full then emit redstone - setRedstoneState(!canRocketFitFluid(currentFluid)); + // Determine which tank to fill + final FuelType typeToFill; + if (FuelRegistry.instance.isFuel(FuelType.LIQUID_OXIDIZER, currentFluid) + && linkedRocket.getFuelCapacity(FuelType.LIQUID_OXIDIZER) > linkedRocket.getFuelAmount(FuelType.LIQUID_OXIDIZER)) { + typeToFill = FuelType.LIQUID_OXIDIZER; + + } else if (FuelRegistry.instance.isFuel(FuelType.LIQUID_BIPROPELLANT, currentFluid) + && linkedRocket.getFuelCapacity(FuelType.LIQUID_BIPROPELLANT) > linkedRocket.getFuelAmount(FuelType.LIQUID_BIPROPELLANT)) { + typeToFill = FuelType.LIQUID_BIPROPELLANT; + + } else if (FuelRegistry.instance.isFuel(FuelType.LIQUID_MONOPROPELLANT, currentFluid) + && linkedRocket.getFuelCapacity(FuelType.LIQUID_MONOPROPELLANT) > linkedRocket.getFuelAmount(FuelType.LIQUID_MONOPROPELLANT)) { + typeToFill = FuelType.LIQUID_MONOPROPELLANT; + + } else if (FuelRegistry.instance.isFuel(FuelType.NUCLEAR_WORKING_FLUID, currentFluid) + && linkedRocket.getFuelCapacity(FuelType.NUCLEAR_WORKING_FLUID) > linkedRocket.getFuelAmount(FuelType.NUCLEAR_WORKING_FLUID)) { + typeToFill = FuelType.NUCLEAR_WORKING_FLUID; + + } else { + // not relevant or no room + setRedstoneState(false); + fuelingActive = false; + return; + } + + // Bounded transfer scaled by throttle; drain exactly the delta that actually landed + int step = ARConfiguration.getCurrentConfig().fuelPointsPer10Mb; + int toOffer = Math.min(step * OP_THROTTLE_TICKS, tank.getFluidAmount()); + if (toOffer > 0) { + final int before = linkedRocket.getFuelAmount(typeToFill); + final int ret = linkedRocket.addFuelAmount(typeToFill, toOffer); + + // Be robust to either contract: + // - if ret == new total -> delta = ret - before + // - if ret == accepted -> delta = ret (clamped to toOffer) + int delta = Math.max(0, ret - before); + if (delta == 0) { + // Assume ret is "accepted amount" + delta = Math.min(toOffer, Math.max(0, ret)); + } + + if (delta > 0) { + tank.drain(delta, true); + + int baseRate = linkedRocket.stats.getBaseFuelRate(typeToFill); + if (baseRate > 0) { + int multRate = (int)(FuelRegistry.instance.getMultiplier(typeToFill, currentFluid) * baseRate); + if (multRate > 0) { + linkedRocket.setFuelConsumptionRate(typeToFill, multRate); + } + } } } - useBucket(0, inventory.getStackInSlot(0)); + + + // Re-evaluate full; if full now, stop within this link + boolean fullNow = !canRocketFitFluid(currentFluid); + setRedstoneState(fullNow); + if (fullNow) { + fuelingActive = false; + markDirty(); + } } @Override @@ -117,6 +342,7 @@ public int getPowerPerOperation() { return 30; } + @Override public SPacketUpdateTileEntity getUpdatePacket() { return new SPacketUpdateTileEntity(pos, getBlockMetadata(), getUpdateTag()); @@ -132,45 +358,75 @@ public NBTTagCompound getUpdateTag() { return writeToNBT(new NBTTagCompound()); } - @Override public boolean canPerformFunction() { - boolean v = linkedRocket != null && (tank.getFluid() != null && tank.getFluidAmount() > 9 && canRocketFitFluid(tank.getFluid().getFluid())); - //System.out.println(v); - return v; + if (world.isRemote) return false; + if (linkedRocket == null) return false; + + FluidStack fs = tank.getFluid(); + if (fs == null || fs.amount <= 9) return false; + + // Only draw power when the rocket can actually take this fluid + return canRocketFitFluid(fs.getFluid()); } @Override public boolean canFill(Fluid fluid) { - return FuelRegistry.instance.isFuel(FuelType.LIQUID_MONOPROPELLANT, fluid) || FuelRegistry.instance.isFuel(FuelType.NUCLEAR_WORKING_FLUID, fluid) || FuelRegistry.instance.isFuel(FuelType.LIQUID_BIPROPELLANT, fluid) || FuelRegistry.instance.isFuel(FuelType.LIQUID_OXIDIZER, fluid); + if (fluid == null) return false; + return FuelRegistry.instance.isFuel(FuelType.LIQUID_MONOPROPELLANT, fluid) + || FuelRegistry.instance.isFuel(FuelType.NUCLEAR_WORKING_FLUID, fluid) + || FuelRegistry.instance.isFuel(FuelType.LIQUID_BIPROPELLANT, fluid) + || FuelRegistry.instance.isFuel(FuelType.LIQUID_OXIDIZER, fluid); } - /** * @param fluid the fluid to check whether the rocket has space for it * @return boolean on whether the rocket can accept the fluid */ - public boolean canRocketFitFluid(Fluid fluid) { - return canFill(fluid) && ((linkedRocket.getRocketFuelType() == FuelType.LIQUID_BIPROPELLANT && FuelRegistry.instance.isFuel(FuelType.LIQUID_OXIDIZER, fluid)) ? linkedRocket.getFuelCapacity(FuelType.LIQUID_OXIDIZER) > linkedRocket.getFuelAmount(FuelType.LIQUID_OXIDIZER) : linkedRocket.getFuelCapacity(linkedRocket.getRocketFuelType()) > linkedRocket.getFuelAmount(linkedRocket.getRocketFuelType())); - } + private boolean canRocketFitFluid(Fluid f) { + if (f == null || linkedRocket == null) return false; + boolean fits = false; + + // Check every type the fluid qualifies for; OR the results. + if (FuelRegistry.instance.isFuel(FuelType.LIQUID_OXIDIZER, f)) { + fits |= linkedRocket.getFuelAmount(FuelType.LIQUID_OXIDIZER) < linkedRocket.getFuelCapacity(FuelType.LIQUID_OXIDIZER); + } + if (FuelRegistry.instance.isFuel(FuelType.LIQUID_BIPROPELLANT, f)) { + fits |= linkedRocket.getFuelAmount(FuelType.LIQUID_BIPROPELLANT) < linkedRocket.getFuelCapacity(FuelType.LIQUID_BIPROPELLANT); + } + if (FuelRegistry.instance.isFuel(FuelType.LIQUID_MONOPROPELLANT, f)) { + fits |= linkedRocket.getFuelAmount(FuelType.LIQUID_MONOPROPELLANT) < linkedRocket.getFuelCapacity(FuelType.LIQUID_MONOPROPELLANT); + } + if (FuelRegistry.instance.isFuel(FuelType.NUCLEAR_WORKING_FLUID, f)) { + fits |= linkedRocket.getFuelAmount(FuelType.NUCLEAR_WORKING_FLUID) < linkedRocket.getFuelCapacity(FuelType.NUCLEAR_WORKING_FLUID); + } + + return fits; + } @Override public String getModularInventoryName() { return AdvancedRocketryBlocks.blockFuelingStation.getLocalizedName(); } + // keep original claim of custom name, but return non-null to avoid GUI NPEs @Override - public boolean hasCustomName() { - return true; + public boolean hasCustomName() { return true; } + + @Override + public String getName() { + return AdvancedRocketryBlocks.blockFuelingStation.getLocalizedName(); } @Override public void setInventorySlotContents(int slot, @Nonnull ItemStack stack) { - super.setInventorySlotContents(slot, stack); - while (useBucket(0, getStackInSlot(0))) ; - + if (!world.isRemote) { + boolean changed = false; + while (useBucket(0, getStackInSlot(0))) changed = true; // drain all at once + if (changed) syncTE(); // one sync if anything changed + } } /** @@ -184,32 +440,54 @@ private boolean useBucket(int slot, @Nonnull ItemStack stack) { return FluidUtils.attemptDrainContainerIInv(inventory, tank, stack, 0, 1); } + @Override public boolean isItemValidForSlot(int slot, @Nonnull ItemStack stack) { - if (stack.hasCapability(CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY, EnumFacing.UP)) { - FluidStack fstack = stack.getCapability(CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY, EnumFacing.UP).getTankProperties()[0].getContents(); - return fstack != null && canFill(fstack.getFluid()); - } - return false; + if (!stack.hasCapability(CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY, EnumFacing.UP)) return false; + IFluidHandler cap = stack.getCapability(CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY, EnumFacing.UP); + if (cap == null) return false; + IFluidTankProperties[] props = cap.getTankProperties(); + if (props == null || props.length == 0) return false; + FluidStack fstack = props[0].getContents(); + return fstack != null && canFill(fstack.getFluid()); } @Override public void unlinkRocket() { this.linkedRocket = null; - ((BlockTileRedstoneEmitter) AdvancedRocketryBlocks.blockFuelingStation).setRedstoneState(world, world.getBlockState(pos), pos, false); - + this.fuelingActive = false; + this.lastRs = null; + lastFuelStr = lastOxStr = lastWorkStr = null; + cachedFuelFluid = cachedOxFluid = cachedWorkFluid = null; + ((BlockTileRedstoneEmitter) AdvancedRocketryBlocks.blockFuelingStation) + .setRedstoneState(world, world.getBlockState(pos), pos, false); + markDirty(); } @Override - public boolean disconnectOnLiftOff() { - return true; - } + public boolean disconnectOnLiftOff() { return true; } @Override public boolean linkRocket(EntityRocketBase rocket) { this.linkedRocket = rocket; - if (tank.getFluid() != null) - setRedstoneState(!canRocketFitFluid(tank.getFluid().getFluid())); + this.lastRs = null; + refreshFluidCachesIfNeeded(); + + boolean room = false; + + if (tank.getFluid() != null) { + Fluid f = tank.getFluid().getFluid(); + boolean relevant = isStationFluidForThisRocket(f); + room = relevant && canRocketFitFluid(f); + setRedstoneState(relevant && !room); + } else { + setRedstoneState(false); + } + + // Arm fueling only if there’s actually room for the current fluid + this.fuelingActive = room; + + syncTE(); return true; } @@ -225,7 +503,9 @@ public boolean onLinkStart(@Nonnull ItemStack item, TileEntity entity, } if (player.world.isRemote) - Minecraft.getMinecraft().ingameGUI.getChatGUI().printChatMessage((new TextComponentString(LibVulpes.proxy.getLocalizedString("msg.fuelingStation.link") + ": " + this.pos.getX() + " " + this.pos.getY() + " " + this.pos.getZ()))); + Minecraft.getMinecraft().ingameGUI.getChatGUI().printChatMessage( + new TextComponentString(LibVulpes.proxy.getLocalizedString("msg.fuelingStation.link") + + ": " + this.pos.getX() + " " + this.pos.getY() + " " + this.pos.getZ())); return true; } @@ -233,18 +513,31 @@ public boolean onLinkStart(@Nonnull ItemStack item, TileEntity entity, public void invalidate() { super.invalidate(); if (getMasterBlock() instanceof TileRocketAssemblingMachine) - ((TileRocketAssemblingMachine) getMasterBlock()).removeConnectedInfrastructure(this); + ((TileRocketAssemblingMachine)getMasterBlock()).removeConnectedInfrastructure(this); - //Mostly for client rendering stuff if (linkedRocket != null) linkedRocket.unlinkInfrastructure(this); + + // Clear caches + lastFuelStr = lastOxStr = lastWorkStr = null; + cachedFuelFluid = cachedOxFluid = cachedWorkFluid = null; + lastRs = null; + fuelingActive = false; + + if (world != null && AdvancedRocketryBlocks.blockFuelingStation instanceof BlockTileRedstoneEmitter) { + ((BlockTileRedstoneEmitter) AdvancedRocketryBlocks.blockFuelingStation) + .setRedstoneState(world, world.getBlockState(pos), pos, false); + } + + markDirty(); } @Override public boolean onLinkComplete(@Nonnull ItemStack item, TileEntity entity, EntityPlayer player, World world) { if (player.world.isRemote) - Minecraft.getMinecraft().ingameGUI.getChatGUI().printChatMessage(new TextComponentTranslation("msg.linker.error.firstMachine")); + Minecraft.getMinecraft().ingameGUI.getChatGUI().printChatMessage( + new TextComponentTranslation("msg.linker.error.firstMachine")); return false; } @@ -273,23 +566,13 @@ public List getModules(int ID, EntityPlayer player) { } @Override - public String getName() { - return null; - } - - @Override - public boolean canInteractWithContainer(EntityPlayer entity) { - return true; - } + public boolean canInteractWithContainer(EntityPlayer entity) { return true; } @Override - public boolean linkMission(IMission mission) { - return false; - } + public boolean linkMission(IMission mission) { return false; } @Override - public void unlinkMission() { - } + public void unlinkMission() { } @Override public NBTTagCompound writeToNBT(NBTTagCompound nbt) { @@ -298,6 +581,7 @@ public NBTTagCompound writeToNBT(NBTTagCompound nbt) { if (hasMaster()) { nbt.setIntArray("masterPos", new int[]{masterBlock.x, masterBlock.y, masterBlock.z}); } + // fuelingActive not persisted on purpose to match original continuous behavior after reload return nbt; } @@ -311,12 +595,11 @@ public void readFromNBT(NBTTagCompound nbt) { int[] pos = nbt.getIntArray("masterPos"); setMasterBlock(new BlockPos(pos[0], pos[1], pos[2])); } + // lastRs/fuelingActive intentionally not restored; link events will reset as needed } @Override - public boolean hasMaster() { - return masterBlock.y > -1; - } + public boolean hasMaster() { return masterBlock.y > -1; } @Override public TileEntity getMasterBlock() { @@ -324,23 +607,15 @@ public TileEntity getMasterBlock() { } @Override - public void setMasterBlock(BlockPos pos) { - masterBlock = new HashedBlockPosition(pos); - } + public void setMasterBlock(BlockPos pos) { masterBlock = new HashedBlockPosition(pos); } @Override - public void setComplete(BlockPos pos) { - - } + public void setComplete(BlockPos pos) { } @Override - public void setIncomplete() { - masterBlock.y = -1; - } + public void setIncomplete() { masterBlock.y = -1; } - public boolean canRenderConnection() { - return true; - } + public boolean canRenderConnection() { return true; } @Override public void onInventoryButtonPressed(int buttonId) { @@ -354,22 +629,63 @@ public void writeDataToNetwork(ByteBuf out, byte id) { } @Override - public void readDataFromNetwork(ByteBuf in, byte packetId, - NBTTagCompound nbt) { + public void readDataFromNetwork(ByteBuf in, byte packetId, NBTTagCompound nbt) { nbt.setByte("state", in.readByte()); } @Override - public void useNetworkData(EntityPlayer player, Side side, byte id, - NBTTagCompound nbt) { + public void useNetworkData(EntityPlayer player, Side side, byte id, NBTTagCompound nbt) { state = RedstoneState.values()[nbt.getByte("state")]; + markDirty(); + if (side == Side.SERVER && linkedRocket != null) { + FluidStack fs = tank.getFluid(); + if (fs == null) { setRedstoneState(false); return; } + Fluid f = fs.getFluid(); + boolean relevant = isStationFluidForThisRocket(f); + boolean room = relevant && canRocketFitFluid(f); + setRedstoneState(relevant && !room); + } + } + - if (linkedRocket != null && tank.getFluid() != null) - setRedstoneState(!canRocketFitFluid(tank.getFluid().getFluid())); + @Override + public void onLoad() { + if (world.isRemote) return; + lastRs = null; // allow first emit + refreshFluidCachesIfNeeded(); + + boolean emit = false; + if (linkedRocket != null) { + FluidStack fs = tank.getFluid(); + if (fs != null) { + Fluid f = fs.getFluid(); + emit = isStationFluidForThisRocket(f) && !canRocketFitFluid(f); + // also re-arm fueling if we actually can fit + if (!emit && isStationFluidForThisRocket(f) && canRocketFitFluid(f)) { + fuelingActive = true; + } + } + } + setRedstoneState(emit); } @Override - public boolean isEmpty() { - return inventory.isEmpty(); + public void onChunkUnload() { + super.onChunkUnload(); + if (world == null || world.isRemote) return; + + // Clear caches + lastFuelStr = lastOxStr = lastWorkStr = null; + cachedFuelFluid = cachedOxFluid = cachedWorkFluid = null; + lastRs = null; + fuelingActive = false; + if (AdvancedRocketryBlocks.blockFuelingStation instanceof BlockTileRedstoneEmitter) { + ((BlockTileRedstoneEmitter) AdvancedRocketryBlocks.blockFuelingStation) + .setRedstoneState(world, world.getBlockState(pos), pos, false); + } + markDirty(); } + + @Override + public boolean isEmpty() { return inventory.isEmpty(); } } diff --git a/src/main/java/zmaster587/advancedRocketry/tile/infrastructure/TileRocketMonitoringStation.java b/src/main/java/zmaster587/advancedRocketry/tile/infrastructure/TileRocketMonitoringStation.java index 9ab6b6334..8630d1b6c 100644 --- a/src/main/java/zmaster587/advancedRocketry/tile/infrastructure/TileRocketMonitoringStation.java +++ b/src/main/java/zmaster587/advancedRocketry/tile/infrastructure/TileRocketMonitoringStation.java @@ -5,62 +5,258 @@ import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.network.NetworkManager; +import net.minecraft.network.play.server.SPacketUpdateTileEntity; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.EnumFacing; import net.minecraft.util.ITickable; +import net.minecraft.util.math.BlockPos; import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.world.World; import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.eventhandler.EventPriority; +import zmaster587.libVulpes.tile.IMultiblock; import zmaster587.advancedRocketry.api.ARConfiguration; import zmaster587.advancedRocketry.api.EntityRocketBase; import zmaster587.advancedRocketry.api.IInfrastructure; import zmaster587.advancedRocketry.api.IMission; +import zmaster587.advancedRocketry.api.RocketEvent; import zmaster587.advancedRocketry.api.fuel.FuelRegistry; import zmaster587.advancedRocketry.api.satellite.SatelliteBase; import zmaster587.advancedRocketry.dimension.DimensionManager; import zmaster587.advancedRocketry.entity.EntityRocket; +import zmaster587.advancedRocketry.entity.EntityStationDeployedRocket; import zmaster587.advancedRocketry.inventory.TextureResources; +import zmaster587.advancedRocketry.tile.TileRocketAssemblingMachine; +import zmaster587.advancedRocketry.tile.TileUnmannedVehicleAssembler; import zmaster587.libVulpes.LibVulpes; import zmaster587.libVulpes.client.util.IndicatorBarImage; import zmaster587.libVulpes.client.util.ProgressBarImage; import zmaster587.libVulpes.interfaces.ILinkableTile; import zmaster587.libVulpes.inventory.modules.*; +import zmaster587.libVulpes.inventory.GuiHandler; import zmaster587.libVulpes.items.ItemLinker; import zmaster587.libVulpes.network.PacketHandler; import zmaster587.libVulpes.network.PacketMachine; import zmaster587.libVulpes.tile.IComparatorOverride; +import zmaster587.libVulpes.util.HashedBlockPosition; import zmaster587.libVulpes.util.IAdjBlockUpdate; import zmaster587.libVulpes.util.INetworkMachine; -import zmaster587.libVulpes.util.ZUtils.RedstoneState; import javax.annotation.Nonnull; import java.util.LinkedList; import java.util.List; -public class TileRocketMonitoringStation extends TileEntity implements IModularInventory, ITickable, IAdjBlockUpdate, IInfrastructure, ILinkableTile, INetworkMachine, IButtonInventory, IProgressBar, IComparatorOverride { +public class TileRocketMonitoringStation extends TileEntity + implements IModularInventory, ITickable, IAdjBlockUpdate, IInfrastructure, + ILinkableTile, INetworkMachine, IButtonInventory, IProgressBar, + IComparatorOverride, IGuiCallback, IMultiblock { + + // 2–3 ticks for height/vel feels live; 5–10 ticks is fine for fuel. + private static final int T_HEIGHTVEL_TICKS = 3; // ~6.7 Hz + private static final int T_FUEL_TICKS = 10; // ~2 Hz + private static final int T_COMPARATOR_TICKS = 3; // match height cadence + // ================================= + + // Server-only: assembler-driven claim window + private int expectedRocketId = -1; + private long expectedRocketExpiry = 0L; + + // "this rocket belongs to me" + public void markRocketFromAssembler(EntityRocketBase rocket) { + if (world == null || world.isRemote || rocket == null) return; + this.expectedRocketId = rocket.getEntityId(); + this.expectedRocketExpiry = world.getTotalWorldTime() + 40; // ~2 seconds + } EntityRocketBase linkedRocket; IMission mission; ModuleText missionText; - //RedstoneState state; - //ModuleRedstoneOutputButton redstoneControl; + + // Cached redstone state from neighbor callbacks (don’t poll every tick) + private boolean isPoweredCached = false, initPower = false; + + // Throttles + private int heightVelTick = 0, fuelTick = 0, comparatorTick = 0; + + // Comparator cache (change-only) + private int lastComparator = -1; + + // Server snapshots (served via ModuleProgress polling) + private int snapHeight = 0, snapVel = 0; + private int snapFuel = 0, snapFuelCap = 0; // active fuel (id=2 semantics) + private int snapOx = 0, snapOxCap = 0; // oxidizer (id=6 semantics) + private int lastKnownFuelCap = 0; // active fuel cap (mono/bi/nuclear) + private int lastKnownOxCap = 0; // oxidizer cap + + + // GUI cached fields (client) boolean was_powered = false; int rocketHeight; int velocity; int fuelLevel, maxFuelLevel; int oxidizerFuelLevel; + + // === GUI event status (server -> client via TE update) === + // 0=idle, 1=prelaunch, 2=launching, 3=orbit, 4=deorbiting, 5=landed, 6=aborted + + private int uiStatus = 0; + private transient ModuleText launchStatus; // client-only widget + private transient ModuleText abortDetail; + private transient int lastUiStatusShown = -1; // client change-detect + // How long a status is considered fresh after the last event (in ticks) + private static final long STATUS_STALE_TICKS = 600L; // over 30 seconds is outdated + private long lastStatusTick = 0L; // server-only; persisted + private String lastAbortReason = ""; + + // Tabs (client-only) + private static final byte TAB_SWITCH = 10; + private ModuleTab tabModule; + // Event bus registration flag + private boolean registeredBus = false; + + private void pushState() { + if (world != null && !world.isRemote) { + markDirty(); + world.notifyBlockUpdate(pos, world.getBlockState(pos), world.getBlockState(pos), 3); + } + } + + private boolean isRocketAllowedForMaster(@Nonnull EntityRocketBase rocket) { + // if something is weird, don't block linking + if (world == null || rocket == null) { + return true; + } + + // Free-floating monitor with no master: no restriction + if (!hasMaster()) { + return true; + } + + TileEntity master = getMasterBlock(); + if (!(master instanceof TileUnmannedVehicleAssembler)) { + // Master is some other assembler type: no SD-only restriction + return true; + } + + // From here: this monitor is owned by an *unmanned* vehicle assembler. + // Only accept SD rockets. + if (rocket instanceof EntityStationDeployedRocket) { + return true; + } + + // Anything else is not allowed for this master + return false; + } + + private void clearUiStatus() { + uiStatus = 0; + lastAbortReason = ""; + lastUiStatusShown = -1; // force client label to refresh to empty + pushState(); + } + public TileRocketMonitoringStation() { mission = null; - missionText = new ModuleText(20, 90, LibVulpes.proxy.getLocalizedString("msg.monitoringStation.missionProgressNA"), 0x2b2b2b); - //redstoneControl = new ModuleRedstoneOutputButton(174, 4, -1, "", this); - //state = RedstoneState.ON; + missionText = null; + + tabModule = new ModuleTab( + 4, 0, 0, this, 2, + new String[] { + LibVulpes.proxy.getLocalizedString("msg.monitoringStation.tab.status"), + LibVulpes.proxy.getLocalizedString("msg.monitoringStation.tab.mission") + }, + new net.minecraft.util.ResourceLocation[][] { + TextureResources.tabPlanet, + TextureResources.tabPlanetTracking + } + ); } + // --- Master / assembler association --- + private HashedBlockPosition masterBlock = new HashedBlockPosition(0, -1, 0); + + @Override + public boolean hasMaster() { + return masterBlock.y > -1; + } + + @Override + public TileEntity getMasterBlock() { + return world == null ? null : world.getTileEntity( + new BlockPos(masterBlock.x, masterBlock.y, masterBlock.z) + ); + } + + @Override + public void setMasterBlock(BlockPos pos) { + masterBlock = new HashedBlockPosition(pos); + } + + @Override + public void setComplete(BlockPos pos) { + } + + @Override + public void setIncomplete() { + masterBlock.y = -1; + } + + // --- Lifecycle / bus registration --- + + @Override + public void onLoad() { + if (world == null) return; + + if (!world.isRemote) { + // Only listen to rocket events if we actually have a rocket + if (linkedRocket != null && !registeredBus) { + MinecraftForge.EVENT_BUS.register(this); + registeredBus = true; + primeSnapshotsFromRocket(); // immediate stats/fuel refresh + } + + if (!initPower) { + boolean now = world.isBlockIndirectlyGettingPowered(pos) > 0; + isPoweredCached = now; + was_powered = now; + initPower = true; + } + + // Status staleness handling unchanged: + boolean stale = lastStatusTick == 0L + || (world.getTotalWorldTime() - lastStatusTick) > STATUS_STALE_TICKS; + + if (stale || (linkedRocket == null && mission == null)) { + clearUiStatus(); + lastStatusTick = 0L; + } else { + pushState(); + } + } + } + + @Override public void invalidate() { super.invalidate(); + if (!world.isRemote && registeredBus) { + MinecraftForge.EVENT_BUS.unregister(this); + registeredBus = false; + } + + // Tell the assembler that this infra is gone + if (!world.isRemote && hasMaster()) { + TileEntity master = getMasterBlock(); + if (master instanceof TileRocketAssemblingMachine) { + ((TileRocketAssemblingMachine) master).removeConnectedInfrastructure(this); + } + } + if (linkedRocket != null) { linkedRocket.unlinkInfrastructure(this); unlinkRocket(); @@ -71,49 +267,311 @@ public void invalidate() { } } - public boolean getEquivalentPower() { - //if (state == RedstoneState.OFF) - // return false; - boolean state2 = world.isBlockIndirectlyGettingPowered(pos) > 0; + @Override + public void onChunkUnload() { + super.onChunkUnload(); + // This tile remains linked across unload/reload and during flight/space. + if (!world.isRemote && registeredBus) { + MinecraftForge.EVENT_BUS.unregister(this); + registeredBus = false; + } + } - //if (state == RedstoneState.INVERTED) - // state2 = !state2; - return state2; + + // --- Redstone power caching via block neighbor callbacks --- + + @Deprecated + public boolean getEquivalentPower() { + return world.isBlockIndirectlyGettingPowered(pos) > 0; } @Override public void onAdjacentBlockUpdated() { + if (world == null || world.isRemote) return; + + boolean now = world.isBlockIndirectlyGettingPowered(pos) > 0; + boolean rising = now && !isPoweredCached; + // Update cache first so it stays correct even with no rocket linked + isPoweredCached = now; + was_powered = now; + + if (rising && linkedRocket != null) { + linkedRocket.prepareLaunch(); + markDirty(); + } } + + + // --- IInfrastructure --- + @Override public int getMaxLinkDistance() { return 300000; } + @Override + public boolean disconnectOnLiftOff() { + return false; + } + + @Override + public boolean linkRocket(EntityRocketBase rocket) { + if (rocket == null || world == null) { + return false; + } + + // If we are bound to an assembler, we only trust: + // - the rocket we already own, or + // - a rocket that the assembler just claimed for us. + if (!world.isRemote && hasMaster()) { + final int rocketId = rocket.getEntityId(); + + boolean allowed = false; + + // 1) Already our rocket? Always allow re-connect (teleports, dim changes). + if (this.linkedRocket != null && this.linkedRocket == rocket) { + allowed = true; + } else { + // 2) Else, require a fresh assembler claim. + boolean haveClaim = + (expectedRocketId == rocketId) && + (world.getTotalWorldTime() <= expectedRocketExpiry); + + if (haveClaim) { + allowed = true; + } + } + + if (!allowed) { + // This rocket has us in its infra list, but assembler did NOT bless it. + // Clean its list and refuse. + rocket.unlinkInfrastructure(this); + return false; + } + if (!isRocketAllowedForMaster(rocket)) { + rocket.unlinkInfrastructure(this); + return false; + } + } + + // From here: either we have no master (free-floating infra), + // or the assembler/owner check passed. + this.linkedRocket = rocket; + + // Always listen to events on the server + if (!world.isRemote && !registeredBus) { + MinecraftForge.EVENT_BUS.register(this); + registeredBus = true; + } + + if (!world.isRemote) { + final int dim = rocket.world.provider.getDimension(); + final int eid = rocket.getEntityId(); + final double rx = rocket.posX, ry = rocket.posY, rz = rocket.posZ; + + final zmaster587.advancedRocketry.api.fuel.FuelRegistry.FuelType ft = + rocket.getRocketFuelType(); + final int fAmt = (ft != null) ? rocket.getFuelAmount(ft) : 0; + final int fCap = (ft != null) ? rocket.getFuelCapacity(ft) : 0; + + int thrust = -1, weight = -1; + if (rocket instanceof zmaster587.advancedRocketry.entity.EntityRocket) { + try { + zmaster587.advancedRocketry.entity.EntityRocket er = + (zmaster587.advancedRocketry.entity.EntityRocket) rocket; + zmaster587.advancedRocketry.api.StatsRocket stats = er.getRocketStats(); + if (stats != null) { + thrust = (int) stats.getThrust(); + weight = (int) stats.getWeight(); + } + } catch (Throwable t) { /* keep simple */ } + } + + // Fresh snapshot + UI as before + primeSnapshotsFromRocket(); + + boolean returning = (rocket instanceof EntityRocket) + && ((EntityRocket) rocket).isInOrbit() + && ((EntityRocket) rocket).isInFlight(); + + if (returning) { + uiStatus = 4; // deorbiting + lastStatusTick = world.getTotalWorldTime(); + pushState(); + } else { + clearUiStatus(); + lastStatusTick = 0L; + } + } + return true; + } + + + + @Override + public void unlinkRocket() { + linkedRocket = null; + + // reset snapshots + snapHeight = snapVel = 0; + snapFuel = snapFuelCap = 0; + snapOx = snapOxCap = 0; + + if (!world.isRemote) { + lastComparator = 0; + world.updateComparatorOutputLevel(pos, world.getBlockState(pos).getBlock()); + + // Keep "Reached orbit" visible while the mission is active. + if (mission == null) { + clearUiStatus(); + lastStatusTick = 0L; // reset tick + } + } + } + + + // --- Ticking --- + @Override public void update() { + if (world.isRemote) return; + + // One-time prime (in case no neighbor event has fired yet) + if (!initPower) { + isPoweredCached = world.isBlockIndirectlyGettingPowered(pos) > 0; + initPower = true; + } + if (!world.isRemote) { - if (linkedRocket instanceof EntityRocket) { - if ((int) (15 * ((EntityRocket) linkedRocket).getRelativeHeightFraction()) != (int) (15 * ((EntityRocket) linkedRocket).getPreviousRelativeHeightFraction())) { - markDirty(); - } - if (getEquivalentPower() && linkedRocket != null) { - if (!was_powered) { - System.out.println("prepare launch (redstone powered)"); - linkedRocket.prepareLaunch(); - //System.out.println("launching..."); - was_powered = true; + long age = world.getTotalWorldTime() - lastStatusTick; + + // Aborted + if (uiStatus == 6 && age > STATUS_STALE_TICKS) clearUiStatus(); + + // Reached orbit — only time out when no mission is linked + if (uiStatus == 3 && mission == null && age > STATUS_STALE_TICKS) clearUiStatus(); + + // Landed + if (uiStatus == 5 && age > STATUS_STALE_TICKS) clearUiStatus(); + } + // Runs infrequently to recover from any missed neighbor events. + if (world.getTotalWorldTime() % 100 == 0) { // every 100 ticks + boolean polled = world.isBlockIndirectlyGettingPowered(pos) > 0; + isPoweredCached = polled; // DO NOT trigger launch here; just reconcile the cache + } + // Idle fast-exit + if (linkedRocket == null) { return; } + + if (snapFuelCap == 0 && linkedRocket.getRocketFuelType() != null) { + primeSnapshotsFromRocket(); + } + // ---- height + velocity snapshots, every T_HEIGHTVEL_TICKS ---- + if (++heightVelTick >= Math.max(1, T_HEIGHTVEL_TICKS)) { + heightVelTick = 0; + + snapHeight = (int) linkedRocket.posY; + snapVel = (int) (linkedRocket.motionY * 100); + + // comparator (0–15) change-only, every T_COMPARATOR_TICKS + if (++comparatorTick >= Math.max(1, T_COMPARATOR_TICKS)) { + comparatorTick = 0; + if (linkedRocket instanceof EntityRocket) { + int comp = (int)(15 * ((EntityRocket) linkedRocket).getRelativeHeightFraction()); + if (comp != lastComparator) { + lastComparator = comp; + world.updateComparatorOutputLevel(pos, world.getBlockState(pos).getBlock()); } } } - if(!getEquivalentPower()){ - was_powered = false; // - } + } + + // ---- fuel snapshots, every T_FUEL_TICKS ---- + if (++fuelTick >= Math.max(1, T_FUEL_TICKS)) { + fuelTick = 0; + + // Original semantics: + // - id=2 shows the *active* rocket fuel + // - id=6 shows oxidizer independently + final FuelRegistry.FuelType active = linkedRocket.getRocketFuelType(); + snapFuel = (active != null) ? linkedRocket.getFuelAmount(active) : 0; + snapFuelCap = (active != null) ? linkedRocket.getFuelCapacity(active) : 0; + + snapOx = linkedRocket.getFuelAmount(FuelRegistry.FuelType.LIQUID_OXIDIZER); + snapOxCap = linkedRocket.getFuelCapacity(FuelRegistry.FuelType.LIQUID_OXIDIZER); + + refreshCapsFromRocket(); + } + } + + // --- Forge Rocket Events -> authorititative UI status (server -> client via TE update) --- + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void onPreLaunch(RocketEvent.RocketPreLaunchEvent e) { + if (world == null || world.isRemote) return; + if (linkedRocket != null && e.getEntity() == linkedRocket) { + uiStatus = e.isCanceled() ? 6 : 1; + if (!e.isCanceled()) lastAbortReason = ""; // fresh launch, drop old reason + lastStatusTick = world.getTotalWorldTime(); + pushState(); } } + @SubscribeEvent + public void onLaunch(RocketEvent.RocketLaunchEvent e) { + if (world == null || world.isRemote) return; + if (linkedRocket != null && e.getEntity() == linkedRocket) { + uiStatus = 2; + lastStatusTick = world.getTotalWorldTime(); + pushState(); + } + } + + @SubscribeEvent + public void onOrbit(RocketEvent.RocketReachesOrbitEvent e) { + if (world == null || world.isRemote) return; + if (linkedRocket != null && e.getEntity() == linkedRocket) { + uiStatus = 3; + lastStatusTick = world.getTotalWorldTime(); + pushState(); + } + } + + @SubscribeEvent + public void onDeorbit(RocketEvent.RocketDeOrbitingEvent e) { + if (world == null || world.isRemote) return; + if (linkedRocket != null && e.getEntity() == linkedRocket) { + uiStatus = 4; // reuse “landed”/returning state + lastStatusTick = world.getTotalWorldTime(); + pushState(); + } + } + + @SubscribeEvent + public void onLanded(RocketEvent.RocketLandedEvent e) { + if (world == null || world.isRemote) return; + if (linkedRocket != null && e.getEntity() == linkedRocket) { + uiStatus = 5; + lastStatusTick = world.getTotalWorldTime(); + pushState(); + } + } + + @SubscribeEvent + public void onAbort(RocketEvent.RocketAbortEvent e) { + if (world == null || world.isRemote) return; + if (linkedRocket != null && e.getEntity() == linkedRocket) { + uiStatus = 6; // “aborted” + lastAbortReason = (e.reason == null) ? "" : e.reason; + lastStatusTick = world.getTotalWorldTime(); + pushState(); + } + } + + + // --- Linker flow --- @Override public boolean onLinkStart(@Nonnull ItemStack item, TileEntity entity, EntityPlayer player, World world) { @@ -128,7 +586,10 @@ public boolean onLinkStart(@Nonnull ItemStack item, TileEntity entity, EntityPla } if (player.world.isRemote) - Minecraft.getMinecraft().ingameGUI.getChatGUI().printChatMessage(new TextComponentTranslation("%s %s", new TextComponentTranslation("msg.monitoringStation.link"), ": " + getPos().getX() + " " + getPos().getY() + " " + getPos().getZ())); + Minecraft.getMinecraft().ingameGUI.getChatGUI().printChatMessage( + new TextComponentTranslation("%s %s", + new TextComponentTranslation("msg.monitoringStation.link"), + ": " + getPos().getX() + " " + getPos().getY() + " " + getPos().getZ())); return true; } @@ -139,148 +600,349 @@ public boolean onLinkComplete(@Nonnull ItemStack item, TileEntity entity, Entity return false; } + // --- NBT / TE sync --- + @Override - public void unlinkRocket() { - linkedRocket = null; + public NBTTagCompound getUpdateTag() { + return writeToNBT(new NBTTagCompound()); } @Override - public boolean disconnectOnLiftOff() { - return false; + public SPacketUpdateTileEntity getUpdatePacket() { + NBTTagCompound tag = new NBTTagCompound(); + writeToNBT(tag); + return new SPacketUpdateTileEntity(pos, 0, tag); } @Override - public boolean linkRocket(EntityRocketBase rocket) { - this.linkedRocket = rocket; - return true; + public void onDataPacket(NetworkManager net, SPacketUpdateTileEntity pkt) { + readFromNBT(pkt.getNbtCompound()); } - @Override - public NBTTagCompound getUpdateTag() { - return writeToNBT(new NBTTagCompound()); + private void refreshCapsFromRocket() { + if (linkedRocket == null) return; + final zmaster587.advancedRocketry.api.fuel.FuelRegistry.FuelType active = linkedRocket.getRocketFuelType(); + snapFuelCap = (active != null) ? linkedRocket.getFuelCapacity(active) : 0; + snapOxCap = linkedRocket.getFuelCapacity(FuelRegistry.FuelType.LIQUID_OXIDIZER); + + // persist fallbacks so a restart still has sane totals + if (snapFuelCap > 0) lastKnownFuelCap = snapFuelCap; + if (snapOxCap > 0) lastKnownOxCap = snapOxCap; } @Override public void readFromNBT(NBTTagCompound nbt) { super.readFromNBT(nbt); - - //state = RedstoneState.values()[nbt.getByte("redstoneState")]; - //redstoneControl.setRedstoneState(state); was_powered = nbt.getBoolean("was_powered"); if (nbt.hasKey("missionID")) { long id = nbt.getLong("missionID"); - int dimid = nbt.getInteger("missionDimId"); - SatelliteBase sat = DimensionManager.getInstance().getSatellite(id); - - if (sat instanceof IMission) + if (sat instanceof IMission) { mission = (IMission) sat; + } + } + uiStatus = nbt.getInteger("uiStatus"); + lastStatusTick = nbt.getLong("lastStatusTick"); + lastAbortReason = nbt.hasKey("abortReason") ? nbt.getString("abortReason") : ""; + lastKnownFuelCap = nbt.getInteger("lastFuelCap"); + lastKnownOxCap = nbt.getInteger("lastOxCap"); + + if (nbt.hasKey("masterY") && nbt.getInteger("masterY") > -1) { + int mx = nbt.getInteger("masterX"); + int my = nbt.getInteger("masterY"); + int mz = nbt.getInteger("masterZ"); + masterBlock = new HashedBlockPosition(mx, my, mz); + } + + // client: force GUI labels to refresh + if (world != null && world.isRemote) { + lastUiStatusShown = -1; } } + @Override public NBTTagCompound writeToNBT(NBTTagCompound nbt) { super.writeToNBT(nbt); - //nbt.setByte("redstoneState", (byte) state.ordinal()); nbt.setBoolean("was_powered", was_powered); if (mission != null) { nbt.setLong("missionID", mission.getMissionId()); nbt.setInteger("missionDimId", mission.getOriginatingDimension()); } + nbt.setInteger("uiStatus", uiStatus); + nbt.setLong("lastStatusTick", lastStatusTick); + nbt.setString("abortReason", lastAbortReason == null ? "" : lastAbortReason); + nbt.setInteger("lastFuelCap", lastKnownFuelCap); + nbt.setInteger("lastOxCap", lastKnownOxCap); + + if (hasMaster()) { + nbt.setInteger("masterX", masterBlock.x); + nbt.setInteger("masterY", masterBlock.y); + nbt.setInteger("masterZ", masterBlock.z); + } return nbt; } + + // --- LibVulpes network bridge --- + @Override public void writeDataToNetwork(ByteBuf out, byte id) { - if (id == 1) - out.writeLong(mission == null ? -1 : mission.getMissionId()); - //else if (id == 2) - //out.writeByte(state.ordinal()); + if (id == 1) out.writeLong(mission == null ? -1 : mission.getMissionId()); + else if (id == TAB_SWITCH) out.writeShort(tabModule.getTab()); } @Override - public void readDataFromNetwork(ByteBuf in, byte packetId, - NBTTagCompound nbt) { - if (packetId == 1) { - nbt.setLong("id", in.readLong()); - } else if (packetId == 2) { - nbt.setByte("state", in.readByte()); - } + public void readDataFromNetwork(ByteBuf in, byte packetId, NBTTagCompound nbt) { + if (packetId == 1) nbt.setLong("id", in.readLong()); + else if (packetId == 2) nbt.setByte("state", in.readByte()); + else if (packetId == TAB_SWITCH) nbt.setShort("tab", in.readShort()); } @Override - public void useNetworkData(EntityPlayer player, Side side, byte id, - NBTTagCompound nbt) { + public void useNetworkData(EntityPlayer player, Side side, byte id, NBTTagCompound nbt) { if (id == 1) { long idNum = nbt.getLong("id"); if (idNum == -1) { mission = null; - setMissionText(); + if (world.isRemote && missionText != null) setMissionText(); } else { SatelliteBase base = DimensionManager.getInstance().getSatellite(idNum); - if (base instanceof IMission) { mission = (IMission) base; - setMissionText(); + if (world.isRemote && missionText != null) setMissionText(); } } - } else if (id == 2) { - //state = RedstoneState.values()[nbt.getByte("state")]; - //redstoneControl.setRedstoneState(state); } + else if (id == 2) { + // redstone control path was commented in original; preserved + } + else if (id == TAB_SWITCH && !world.isRemote) { + tabModule.setTab(nbt.getShort("tab")); + player.openGui(LibVulpes.instance, GuiHandler.guiId.MODULARNOINV.ordinal(), + getWorld(), pos.getX(), pos.getY(), pos.getZ()); + } if (id == 100) { - if (linkedRocket != null) + if (linkedRocket != null) { + // always re-prime before launch to avoid stale weight/fuel decisions + if (linkedRocket instanceof EntityRocket) { + ((EntityRocket) linkedRocket).recalculateStats(); + } + refreshCapsFromRocket(); + primeSnapshotsFromRocket(); // reflect any last-second loading linkedRocket.prepareLaunch(); + } else { + if (!world.isRemote) { + player.sendMessage(new TextComponentTranslation("msg.monitoringStation.noLinkedRocket")); + } + } } } + + // --- GUI / Modules --- @Override public List getModules(int ID, EntityPlayer player) { - LinkedList modules = new LinkedList<>(); - modules.add(new ModuleButton(20, 40, 0, "Launch!", this, zmaster587.libVulpes.inventory.TextureResources.buttonBuild)); - modules.add(new ModuleProgress(98, 4, 0, new IndicatorBarImage(2, 7, 12, 81, 17, 0, 6, 6, 1, 0, EnumFacing.UP, TextureResources.rocketHud), this)); - modules.add(new ModuleProgress(120, 14, 1, new IndicatorBarImage(2, 95, 12, 71, 17, 0, 6, 6, 1, 0, EnumFacing.UP, TextureResources.rocketHud), this)); - modules.add(new ModuleProgress(142, 14, 2, new ProgressBarImage(2, 173, 12, 71, 17, 6, 3, 69, 1, 1, EnumFacing.UP, TextureResources.rocketHud), this)); - modules.add(new ModuleProgress(148, 14, 6, new ProgressBarImage(2, 173, 12, 71, 17, 75, 3, 69, 1, 1, EnumFacing.UP, TextureResources.rocketHud), this)); + // Tabs control + modules.add(tabModule); - //modules.add(redstoneControl); - setMissionText(); + if (tabModule.getTab() == 0) { + // === STATUS TAB === + modules.add(new ModuleButton(20, 40, 0, "Launch!", this, zmaster587.libVulpes.inventory.TextureResources.buttonBuild)); - modules.add(missionText); - modules.add(new ModuleProgress(30, 110, 3, TextureResources.progressToMission, this)); - modules.add(new ModuleProgress(30, 120, 4, TextureResources.workMission, this)); - modules.add(new ModuleProgress(30, 130, 5, TextureResources.progressFromMission, this)); + if (world.isRemote) { + launchStatus = new ModuleText(88, 92, "", 0xFFFFFF22, true); // centered + modules.add(launchStatus); - if (!world.isRemote) { - PacketHandler.sendToPlayer(new PacketMachine(this, (byte) 1), player); + abortDetail = new ModuleText(88, 108, "", 0xFF4444, true); // centered + modules.add(abortDetail); + + lastUiStatusShown = -1; + } + + modules.add(new ModuleProgress(98, 4, 0, new IndicatorBarImage(2, 7, 12, 81, 17, 0, 6, 6, 1, 0, EnumFacing.UP, TextureResources.rocketHud), this)); + modules.add(new ModuleProgress(120, 14, 1, new IndicatorBarImage(2, 95, 12, 71, 17, 0, 6, 6, 1, 0, EnumFacing.UP, TextureResources.rocketHud), this)); + modules.add(new ModuleProgress(142, 14, 2, new ProgressBarImage(2,173, 12, 71, 17, 6, 3, 69, 1, 1, EnumFacing.UP, TextureResources.rocketHud), this)); + modules.add(new ModuleProgress(148, 14, 6, new ProgressBarImage(2,173, 12, 71, 17,75, 3, 69, 1, 1, EnumFacing.UP, TextureResources.rocketHud), this)); + + if (!world.isRemote) { + PacketHandler.sendToPlayer(new PacketMachine(this, (byte)1), player); + pushState(); + } + return modules; } - return modules; - } + // === MISSION TAB === + { + final boolean hasMission = mission != null; + + // If there is NO mission: show a single centered line and exit early + if (!hasMission) { + modules.add(new ModuleText( + 88, 72, + LibVulpes.proxy.getLocalizedString("msg.monitoringStation.missionNoActiveMission"), + 0x2b2b2b, // color + true // centered + )); + if (!world.isRemote) { + PacketHandler.sendToPlayer(new PacketMachine(this, (byte)1), player); + pushState(); + } + return modules; + } + + // ---- Has mission: structured list ---- + final String typeLine; + { + String cls = mission.getClass().getSimpleName().toLowerCase(); + typeLine = cls.contains("gas") + ? LibVulpes.proxy.getLocalizedString("msg.monitoringStation.mission.type.gas") // "Gas Collection Mission" + : LibVulpes.proxy.getLocalizedString("msg.monitoringStation.mission.type.ore"); // "Asteroid Mining Mission" + } + + + modules.add(new ModuleText( + 88, 16, net.minecraft.util.text.TextFormatting.BOLD + typeLine + net.minecraft.util.text.TextFormatting.RESET, + 0x2b2b2b, + true // centered + )); + + // Decide mission type once (use the text you already built) + final boolean isGas = typeLine.toLowerCase().contains("gas"); + + // Target: if GAS mission, show the chosen fluid; otherwise keep default + if (isGas) { + String gasLabel = ""; + try { + if (mission instanceof zmaster587.advancedRocketry.mission.MissionGasCollection) { + net.minecraftforge.fluids.Fluid f = + ((zmaster587.advancedRocketry.mission.MissionGasCollection) mission).getGasFluid(); + if (f != null) { + gasLabel = new net.minecraftforge.fluids.FluidStack(f, 1).getLocalizedName(); + } + } + } catch (Throwable t) { /* be defensive */ } + + modules.add(new ModuleText( + 10, 39, + (gasLabel.isEmpty() + ? LibVulpes.proxy.getLocalizedString("msg.monitoringStation.mission.target.default") + : LibVulpes.proxy.getLocalizedString("msg.monitoringStation.mission.targetPrefix") + " " + gasLabel), + 0x2b2b2b + )); + } else { + // ---------- NON-GAS (ORE) SECTION — single, minimal block ---------- + String oreType = ""; + String shortId = ""; + + try { + if (mission instanceof zmaster587.advancedRocketry.mission.MissionOreMining) { + zmaster587.advancedRocketry.mission.MissionOreMining m = + (zmaster587.advancedRocketry.mission.MissionOreMining) mission; + + oreType = m.getAsteroidTypeOrEmpty(); + Long aUuid = m.getAsteroidUUIDOrNull(); + + if (aUuid != null) { + long base = aUuid; + long th = Integer.toUnsignedLong((oreType == null ? "" : oreType).hashCode()); + long z = base ^ (th << 1); + z += 0x9E3779B97F4A7C15L; + z = (z ^ (z >>> 30)) * 0xBF58476D1CE4E5B9L; + z = (z ^ (z >>> 27)) * 0x94D049BB133111EBL; + z = (z ^ (z >>> 31)); + String hex = Long.toUnsignedString(z, 16).toUpperCase(); + shortId = (hex.length() > 6) ? hex.substring(hex.length() - 6) : hex; + } + } + } catch (Throwable t) { /* defensive */ } + + // Line 1: Asteroid: (or just "Asteroid:" if id missing) + String lineAsteroid = LibVulpes.proxy.getLocalizedString("msg.monitoringStation.mission.Asteroid.targetPrefix") + + (shortId.isEmpty() ? "" : " " + shortId); + modules.add(new ModuleText(10, 39, lineAsteroid, 0x2b2b2b)); + + // Line 2: Type: (omit if unknown) + if (oreType != null && !oreType.isEmpty()) { + String lineType = LibVulpes.proxy.getLocalizedString("msg.monitoringStation.mission.asteroidIdPrefix") + + " " + oreType; + modules.add(new ModuleText(10, 53, lineType, 0x2b2b2b)); + } + } + + + // --- Specific line per mission type --- + if (isGas) { + // Read planned harvest written by the rocket into the mission's persist NBT + long plannedMb = -1L; + try { + if (mission instanceof zmaster587.advancedRocketry.mission.MissionResourceCollection) { + plannedMb = ((zmaster587.advancedRocketry.mission.MissionResourceCollection) mission) + .getPlannedHarvestMbOrDefault(); + } + } catch (Throwable t) { /* be defensive */ } + + final String plannedText = (plannedMb >= 0) + ? (LibVulpes.proxy.getLocalizedString("msg.monitoringStation.mission.plannedAmountPrefix") + " " + plannedMb + " mB") + : LibVulpes.proxy.getLocalizedString("msg.monitoringStation.mission.plannedAmountPending"); + + modules.add(new ModuleText(10, 53, plannedText, 0x2b2b2b)); + } + //else { if we want to add ore-specific lines later, show loot etc. } + + // Duration text (above the stage bars, like original) + missionText = new ModuleText(88, 94, "", 0x2b2b2b, true); + setMissionText(); + modules.add(missionText); + // Stage bars just above the time block + modules.add(new ModuleProgress(30, 110, 3, TextureResources.progressToMission, this)); + modules.add(new ModuleProgress(30, 120, 4, TextureResources.workMission, this)); + modules.add(new ModuleProgress(30, 130, 5, TextureResources.progressFromMission, this)); + + if (!world.isRemote) { + PacketHandler.sendToPlayer(new PacketMachine(this, (byte)1), player); + pushState(); + } + return modules; + } + } + private void setMissionText() { + // If the text widget isn’t built yet (e.g., GUI closed or on other tab), just bail out. + if (missionText == null) return; + if (mission != null) { int time = mission.getTimeRemainingInSeconds(); int seconds = time % 60; int minutes = (time / 60) % 60; int hours = time / 3600; - missionText.setText(((SatelliteBase) mission).getName() + LibVulpes.proxy.getLocalizedString("msg.monitoringStation.progress") + String.format("\n%02dhr:%02dm:%02ds", hours, minutes, seconds)); - } else + missionText.setText( + LibVulpes.proxy.getLocalizedString("msg.monitoringStation.progress") + + String.format(" %02d:%02d:%02d", hours, minutes, seconds) + ); + } else { missionText.setText(LibVulpes.proxy.getLocalizedString("msg.monitoringStation.missionProgressNA")); + } } @Override public void onInventoryButtonPressed(int buttonId) { if (buttonId != -1) - PacketHandler.sendToServer(new PacketMachine(this, (byte) (buttonId + 100))); - else { - //state = redstoneControl.getState(); - PacketHandler.sendToServer(new PacketMachine(this, (byte) 2)); - } + PacketHandler.sendToServer(new PacketMachine(this, (byte)(buttonId + 100))); + else + PacketHandler.sendToServer(new PacketMachine(this, (byte)2)); + } + + private static String wrapToWidthClient(String s, int maxWidthPx) { + if (s == null || s.isEmpty()) return ""; + net.minecraft.client.gui.FontRenderer fr = net.minecraft.client.Minecraft.getMinecraft().fontRenderer; + java.util.List lines = fr.listFormattedStringToWidth(s, Math.max(1, maxWidthPx)); + return String.join("\n", lines); } @Override @@ -290,29 +952,55 @@ public String getModularInventoryName() { @Override public float getNormallizedProgress(int id) { + if (world.isRemote) { + // Status tab label + if (launchStatus != null && uiStatus != lastUiStatusShown) { + lastUiStatusShown = uiStatus; + + String header = ""; + String detail = ""; + + switch (uiStatus) { + case 1: header = LibVulpes.proxy.getLocalizedString("msg.monitoringStation.prelaunch"); break; + case 2: header = LibVulpes.proxy.getLocalizedString("msg.monitoringStation.launching"); break; + case 3: header = LibVulpes.proxy.getLocalizedString("msg.monitoringStation.orbit"); break; + case 4: header = LibVulpes.proxy.getLocalizedString("msg.monitoringStation.deorbiting"); break; + case 5: header = LibVulpes.proxy.getLocalizedString("msg.monitoringStation.landed"); break; + case 6: header = LibVulpes.proxy.getLocalizedString("msg.monitoringStation.aborted"); + if (lastAbortReason != null && !lastAbortReason.isEmpty()) {detail = lastAbortReason;} break; + default: + header = ""; + } + launchStatus.setText(header); + if (abortDetail != null) { + final int ABORT_WRAP_WIDTH = 150; + abortDetail.setText(wrapToWidthClient(detail, ABORT_WRAP_WIDTH)); + } + } + + // Mission tab duration label (make it live) + if (mission != null && missionText != null) { + setMissionText(); + } + } + if (id == 1) { return Math.max(Math.min(0.5f + (getProgress(id) / (float) getTotalProgress(id)), 1), 0f); } else if (id == 3) { - if (mission == null) - return 0f; + if (mission == null) return 0f; return (float) Math.min(3f * mission.getProgress(this.world), 1f); } else if (id == 4) { - if (mission == null) - return 0f; + if (mission == null) return 0f; return (float) Math.min(Math.max(3f * (mission.getProgress(this.world) - 0.333f), 0f), 1f); } else if (id == 5) { - if (mission == null) - return 0f; + if (mission == null) return 0f; return (float) Math.min(Math.max(3f * (mission.getProgress(this.world) - 0.666f), 0f), 1f); } - //keep text updated - if (world.isRemote && mission != null) - setMissionText(); - return Math.min(getProgress(id) / (float) getTotalProgress(id), 1.0f); } + @Override public void setProgress(int id, int progress) { if (id == 0) @@ -325,60 +1013,65 @@ else if (id == 6) oxidizerFuelLevel = progress; } + /** Pulls a full, fresh snapshot from the linked rocket and pushes a TE update. */ + private void primeSnapshotsFromRocket() { + if (world == null || world.isRemote) return; + if (linkedRocket == null) return; + + // 1) make sure the rocket’s internal stats are up to date + if (linkedRocket instanceof EntityRocket) { + ((EntityRocket) linkedRocket).recalculateStats(); // calls storage.recalculateStats(stats) + } + + // 2) fresh fuel types/capacities + final zmaster587.advancedRocketry.api.fuel.FuelRegistry.FuelType active = linkedRocket.getRocketFuelType(); + snapFuel = (active != null) ? linkedRocket.getFuelAmount(active) : 0; + snapFuelCap = (active != null) ? linkedRocket.getFuelCapacity(active) : 0; + + snapOx = linkedRocket.getFuelAmount(zmaster587.advancedRocketry.api.fuel.FuelRegistry.FuelType.LIQUID_OXIDIZER); + snapOxCap = linkedRocket.getFuelCapacity(zmaster587.advancedRocketry.api.fuel.FuelRegistry.FuelType.LIQUID_OXIDIZER); + + // keep persisted fallbacks up to date + if (snapFuelCap > 0) lastKnownFuelCap = snapFuelCap; + if (snapOxCap > 0) lastKnownOxCap = snapOxCap; + + // 3) height/velocity + snapHeight = (int) linkedRocket.posY; + snapVel = (int) (linkedRocket.motionY * 100); + + // 4) make comparator reflect fresh height right away + lastComparator = -1; + world.updateComparatorOutputLevel(pos, world.getBlockState(pos).getBlock()); + + // 5) tell clients now (no waiting for the periodic tick) + pushState(); + } + @Override public int getProgress(int id) { - //Try to keep client synced with server, this also allows us to put the monitor on a different world altogether - if (world.isRemote) - if (mission != null && id == 0) - return getTotalProgress(id); - else if (id == 0) - return rocketHeight; - else if (id == 1) - return velocity; - else if (id == 2) - return fuelLevel; - else if (id == 6) - return oxidizerFuelLevel; - - if (linkedRocket == null) + // Client: use client-side cached fields (preserve original mission/height quirk) + if (world.isRemote) { + if (mission != null && id == 0) return getTotalProgress(id); // original oddity preserved + if (id == 0) return rocketHeight; + if (id == 1) return velocity; + if (id == 2) return fuelLevel; + if (id == 6) return oxidizerFuelLevel; return 0; - - if (id == 0) - return (int) linkedRocket.posY; - else if (id == 1) - return (int) (linkedRocket.motionY * 100); - else if (id == 2) - return linkedRocket.getFuelAmount(linkedRocket.getRocketFuelType()); - else if (id == 6) - return linkedRocket.getFuelAmount(FuelRegistry.FuelType.LIQUID_OXIDIZER); - + } + // Server: return snapshots only (cheap) + if (id == 0) return snapHeight; + if (id == 1) return snapVel; + if (id == 2) return snapFuel; // active fuel amount + if (id == 6) return snapOx; // oxidizer amount return 0; } @Override public int getTotalProgress(int id) { - if (id == 0) - return ARConfiguration.getCurrentConfig().orbit; - else if (id == 1) - return 1000; - else if (id == 2) - if (world.isRemote) - return maxFuelLevel; - else if (linkedRocket == null) - return 0; - else - return linkedRocket.getFuelCapacity(linkedRocket.getRocketFuelType()); - - else if (id == 6) - if (world.isRemote) - return maxFuelLevel; - else if (linkedRocket == null) - return 0; - else - return linkedRocket.getFuelCapacity(FuelRegistry.FuelType.LIQUID_OXIDIZER); - - - + if (id == 0) return ARConfiguration.getCurrentConfig().orbit; + if (id == 1) return 1000; + if (id == 2) return (world.isRemote ? maxFuelLevel : (snapFuelCap > 0 ? snapFuelCap : lastKnownFuelCap)); + if (id == 6) return (world.isRemote ? maxFuelLevel : (snapOxCap > 0 ? snapOxCap : lastKnownOxCap)); return 1; } @@ -393,19 +1086,31 @@ public void setTotalProgress(int id, int progress) { public boolean canInteractWithContainer(EntityPlayer entity) { return true; } + + @Override + public void onModuleUpdated(ModuleBase module) { + PacketHandler.sendToServer(new PacketMachine(this, TAB_SWITCH)); + } @Override public boolean linkMission(IMission mission) { this.mission = mission; - PacketHandler.sendToNearby(new PacketMachine(this, (byte) 1), world.provider.getDimension(), getPos(), 16); + // If we don’t already have a status, show “in orbit” while mission runs. + if (!world.isRemote) { + // If we were at idle/prelaunch/launching, move to "reached orbit" now. + if (uiStatus < 3) { + uiStatus = 3; + lastStatusTick = world.getTotalWorldTime(); + pushState(); + } + } return true; } @Override public void unlinkMission() { mission = null; - setMissionText(); - PacketHandler.sendToNearby(new PacketMachine(this, (byte) 1), world.provider.getDimension(), getPos(), 16); + if (missionText != null) setMissionText(); } @Override diff --git a/src/main/java/zmaster587/advancedRocketry/tile/multiblock/TileObservatory.java b/src/main/java/zmaster587/advancedRocketry/tile/multiblock/TileObservatory.java index 576661819..7a06f4df2 100644 --- a/src/main/java/zmaster587/advancedRocketry/tile/multiblock/TileObservatory.java +++ b/src/main/java/zmaster587/advancedRocketry/tile/multiblock/TileObservatory.java @@ -20,6 +20,7 @@ import zmaster587.advancedRocketry.api.DataStorage.DataType; import zmaster587.advancedRocketry.inventory.TextureResources; import zmaster587.advancedRocketry.inventory.modules.ModuleData; +import zmaster587.advancedRocketry.inventory.modules.ModuleContainerPanYOnlyWithScrollCache; import zmaster587.advancedRocketry.item.ItemAsteroidChip; import zmaster587.advancedRocketry.item.ItemData; import zmaster587.advancedRocketry.tile.hatch.TileDataBus; @@ -48,6 +49,10 @@ public class TileObservatory extends TileMultiPowerConsumer implements IModularInventory, IDataInventory, IGuiCallback { + // Dont allow duplicate chipwrites for the same seed + button + private java.util.HashSet printedButtonsThisSeed = new java.util.HashSet<>(); + private long printedSetSeed = -1; // track which seed the set belongs to + final static int openTime = 100; final static int observationTime = 1000; private static final Block[] lens = {AdvancedRocketryBlocks.blockLens, Blocks.GLASS}; @@ -233,35 +238,56 @@ protected void writeNetworkData(NBTTagCompound nbt) { nbt.setInteger("lastButton", lastButton); if (lastType != null && !lastType.isEmpty()) nbt.setString("lastType", lastType); + + nbt.setLong("printedSetSeed", printedSetSeed); + if (!printedButtonsThisSeed.isEmpty()) { + int[] arr = printedButtonsThisSeed.stream().mapToInt(Integer::intValue).toArray(); + nbt.setIntArray("printedButtons", arr); + } } @Override protected void readNetworkData(NBTTagCompound nbt) { super.readNetworkData(nbt); openProgress = nbt.getInteger("openProgress"); - isOpen = nbt.getBoolean("isOpen"); - viewDistance = nbt.getInteger("viewableDist"); lastSeed = nbt.getLong("lastSeed"); lastButton = nbt.getInteger("lastButton"); lastType = nbt.getString("lastType"); + + printedSetSeed = nbt.getLong("printedSetSeed"); + printedButtonsThisSeed.clear(); + int[] arr = nbt.getIntArray("printedButtons"); + if (arr != null) for (int v : arr) printedButtonsThisSeed.add(v); } + @Override public NBTTagCompound writeToNBT(NBTTagCompound nbt) { super.writeToNBT(nbt); inv.writeToNBT(nbt); + + nbt.setLong("printedSetSeed", printedSetSeed); + if (!printedButtonsThisSeed.isEmpty()) { + int[] arr = printedButtonsThisSeed.stream().mapToInt(Integer::intValue).toArray(); + nbt.setIntArray("printedButtons", arr); + } return nbt; } @Override public void readFromNBT(NBTTagCompound nbt) { super.readFromNBT(nbt); - inv.readFromNBT(nbt); + + printedSetSeed = nbt.getLong("printedSetSeed"); + printedButtonsThisSeed.clear(); + int[] arr = nbt.getIntArray("printedButtons"); + if (arr != null) for (int v : arr) printedButtonsThisSeed.add(v); } + public LinkedList getDataBus() { return dataCables; } @@ -302,8 +328,35 @@ public List getModules(int ID, EntityPlayer player) { modules.add(new ModuleTexturedSlotArray(5, 120, this, 1, 2, TextureResources.idChip)); modules.add(new ModuleOutputSlotArray(45, 120, this, 2, 3)); modules.add(new ModuleProgress(25, 120, 0, new ProgressBarImage(217, 0, 17, 17, 234, 0, EnumFacing.DOWN, TextureResources.progressBars), this)); - modules.add(new ModuleButton(25, 120, 1, "", this, zmaster587.libVulpes.inventory.TextureResources.buttonNull, LibVulpes.proxy.getLocalizedString("msg.observetory.text.processdiscovery"), 17, 17)); + + ModuleButton processBtn = new ModuleButton( + 25, 120, 1, "", + this, + zmaster587.libVulpes.inventory.TextureResources.buttonNull, + // default tooltip (will be replaced conditionally below) + LibVulpes.proxy.getLocalizedString("msg.observetory.text.processdiscovery"), + 17, 17 + ); + + // `isOpen` is synchronized to client via writeNetworkData/readNetworkData, so safe to read here. + boolean alreadyPrinted = (lastButton != -1) && printedButtonsThisSeed.contains(lastButton); + + if (!isOpen) { + processBtn.setToolTipText(LibVulpes.proxy.getLocalizedString("msg.observetory.req.open")); + processBtn.setEnabled(true); + } else if (alreadyPrinted) { + processBtn.setToolTipText(LibVulpes.proxy.getLocalizedString("msg.observetory.print.already")); + processBtn.setEnabled(false); // <-- + } else { + processBtn.setToolTipText(LibVulpes.proxy.getLocalizedString("msg.observetory.text.processdiscovery")); + processBtn.setEnabled(true); + } + + + // Keep it enabled so the tooltip renders (ModuleButton.isMouseOver checks enabled) + processBtn.setEnabled(true); + modules.add(processBtn); ModuleButton scanButton = new ModuleButton(100, 120, 2, LibVulpes.proxy.getLocalizedString("msg.observetory.scan.button"), this, zmaster587.libVulpes.inventory.TextureResources.buttonBuild, LibVulpes.proxy.getLocalizedString("msg.observetory.scan.tooltip"), 64, 18); @@ -382,35 +435,56 @@ public List getModules(int ID, EntityPlayer player) { int sizeX = 52; int sizeY = 46; if (world.isRemote) { - //Border + // Border for RIGHT composition pane (unchanged) modules.add(new ModuleScaledImage(baseX - 3, baseY - 3, 3, baseY + sizeY + 6, TextureResources.verticalBar)); modules.add(new ModuleScaledImage(baseX + sizeX, baseY - 3, -3, baseY + sizeY + 6, TextureResources.verticalBar)); modules.add(new ModuleScaledImage(baseX, baseY - 3, sizeX, 3, TextureResources.horizontalBar)); modules.add(new ModuleScaledImage(baseX, 2 * baseY + sizeY, sizeX, -3, TextureResources.horizontalBar)); } - ModuleContainerPanYOnly pan2 = new ModuleContainerPanYOnly(baseX, baseY, buttonList, new LinkedList<>(), null, 40, 48, 0, 0, 0, 72); - modules.add(pan2); - - //Add borders for asteroid - baseX = 5; - baseY = 32; - sizeX = 112; - sizeY = 46; + // Preserve RIGHT pane coords before reusing baseX/baseY for the LEFT pane + final int compX = baseX; + final int compY = baseY; + final int compScreenX = 40; // same as original + final int compScreenY = 48; // same as original + + // ---- LEFT pane (asteroid list) border + baseX = 5; + baseY = 32; + sizeX = 112; + sizeY = 46; if (world.isRemote) { - //Border + // Border for LEFT asteroid list modules.add(new ModuleScaledImage(baseX - 3, baseY - 3, 3, baseY + sizeY + 6, TextureResources.verticalBar)); modules.add(new ModuleScaledImage(baseX + sizeX, baseY - 3, -3, baseY + sizeY + 6, TextureResources.verticalBar)); modules.add(new ModuleScaledImage(baseX, baseY - 3, sizeX, 3, TextureResources.horizontalBar)); modules.add(new ModuleScaledImage(baseX, 2 * baseY + sizeY, sizeX, -3, TextureResources.horizontalBar)); } - //Relying on a bug, is this safe? + // ---- LEFT asteroid list: wheel-enabled + cached if (lastSeed != -1) { - ModuleContainerPanYOnly pan = new ModuleContainerPanYOnly(baseX, baseY, list2, new LinkedList<>(), null, sizeX - 2, sizeY, 0, -48, 0, 72); - modules.add(pan); + ModuleContainerPanYOnlyWithScrollCache panLeft = new ModuleContainerPanYOnlyWithScrollCache( + baseX, baseY, + list2, new LinkedList<>(), + null, + sizeX - 2, sizeY, // (screen width minus 2 like original) + 0, -48, // paddingX, paddingY + 0, 72 // containerSizeX, containerSizeY + ); + modules.add(panLeft); // LEFT FIRST to consume wheel } + // ---- RIGHT composition pane: parent class (drag-only; wheel will be 0 after left consumes it) + ModuleContainerPanYOnly panRight = new ModuleContainerPanYOnly( + compX, compY, + buttonList, new LinkedList<>(), + null, + compScreenX, compScreenY, + 0, 0, + 0, 72 + ); + modules.add(panRight); + } else if (tabModule.getTab() == 0) { modules.add(new ModulePower(18, 20, getBatteries())); @@ -499,41 +573,77 @@ public void useNetworkData(EntityPlayer player, Side side, byte id, else if (id == TAB_SWITCH && !world.isRemote) { tabModule.setTab(nbt.getShort("tab")); player.openGui(LibVulpes.instance, GuiHandler.guiId.MODULARNOINV.ordinal(), getWorld(), pos.getX(), pos.getY(), pos.getZ()); - } else if (id == BUTTON_PRESS && !world.isRemote) { + } + else if (id == BUTTON_PRESS && !world.isRemote) { lastButton = nbt.getShort("button"); lastType = buttonType.get(lastButton - LIST_OFFSET); markDirty(); world.notifyBlockUpdate(pos, world.getBlockState(pos), world.getBlockState(pos), 2); player.openGui(LibVulpes.instance, GuiHandler.guiId.MODULARNOINV.ordinal(), getWorld(), pos.getX(), pos.getY(), pos.getZ()); - - } else if (id == SEED_CHANGE) { + } + else if (id == SEED_CHANGE) { if (extractData(dataConsumedPerRefresh, DataType.DISTANCE, EnumFacing.UP, false) >= dataConsumedPerRefresh) { lastSeed = world.getTotalWorldTime() / 100; lastButton = -1; lastType = ""; + + printedButtonsThisSeed.clear(); + printedSetSeed = lastSeed; + + // Clear scroll cache when scanning new data + ModuleContainerPanYOnlyWithScrollCache.clearScrollCache(); + extractData(dataConsumedPerRefresh, DataType.DISTANCE, EnumFacing.UP, true); world.notifyBlockUpdate(pos, world.getBlockState(pos), world.getBlockState(pos), 2); markDirty(); player.openGui(LibVulpes.instance, GuiHandler.guiId.MODULARNOINV.ordinal(), getWorld(), pos.getX(), pos.getY(), pos.getZ()); } + } + else if (id == PROCESS_CHIP && !world.isRemote) { + // Keep printed set aligned with current seed (covers edge cases like load) + if (printedSetSeed != lastSeed) { + printedButtonsThisSeed.clear(); + printedSetSeed = lastSeed; + } - } else if (id == PROCESS_CHIP && !world.isRemote) { + // Hard block duplicates for this scan/selection + if (lastButton != -1 && printedButtonsThisSeed.contains(lastButton)) { + // No status message; just refresh UI so tooltip updates + world.notifyBlockUpdate(pos, world.getBlockState(pos), world.getBlockState(pos), 2); + markDirty(); + if (player != null) { + player.openGui(LibVulpes.instance, GuiHandler.guiId.MODULARNOINV.ordinal(), + getWorld(), pos.getX(), pos.getY(), pos.getZ()); + } + return; + } if (inv.getStackInSlot(2).isEmpty() && isOpen && hasEnergy(500) && lastButton != -1) { ItemStack stack = inv.decrStackSize(1, 1); if (stack != ItemStack.EMPTY && stack.getItem() instanceof ItemAsteroidChip) { - ((ItemAsteroidChip) (stack.getItem())).setUUID(stack, lastSeed); - ((ItemAsteroidChip) (stack.getItem())).setType(stack, lastType); - ((ItemAsteroidChip) (stack.getItem())).setMaxData(stack, 1000); + ((ItemAsteroidChip)(stack.getItem())).setUUID(stack, lastSeed + lastButton); + ((ItemAsteroidChip)(stack.getItem())).setType(stack, lastType); + ((ItemAsteroidChip)(stack.getItem())).setMaxData(stack, 1000); inv.setInventorySlotContents(2, stack); - extractData(1000, DataType.COMPOSITION, EnumFacing.UP, true); - extractData(1000, DataType.MASS, EnumFacing.UP, true); useEnergy(500); + + // Mark this selection as consumed for this seed + printedButtonsThisSeed.add(lastButton); + + // Sync + rebuild GUI so tooltip and button state update immediately + markDirty(); + world.notifyBlockUpdate(pos, world.getBlockState(pos), world.getBlockState(pos), 2); + if (player != null) { + player.openGui(LibVulpes.instance, GuiHandler.guiId.MODULARNOINV.ordinal(), + getWorld(), pos.getX(), pos.getY(), pos.getZ()); + } } } } + + } @Override @@ -709,6 +819,26 @@ public void clear() { } + @Override + public void invalidate() { + super.invalidate(); + dataCables.clear(); + buttonType.clear(); + printedButtonsThisSeed.clear(); + printedSetSeed = -1; + lastSeed = -1; + lastButton = -1; + lastType = ""; + ModuleContainerPanYOnlyWithScrollCache.clearScrollCache(); // static GUI cache + } + + @Override + public void onChunkUnload() { + super.onChunkUnload(); + dataCables.clear(); + ModuleContainerPanYOnlyWithScrollCache.clearScrollCache(); + } + @Override public void onModuleUpdated(ModuleBase module) { //ReopenUI on server diff --git a/src/main/java/zmaster587/advancedRocketry/tile/multiblock/energy/TileMicrowaveReciever.java b/src/main/java/zmaster587/advancedRocketry/tile/multiblock/energy/TileMicrowaveReciever.java index 9d33963a5..9ead1c15a 100644 --- a/src/main/java/zmaster587/advancedRocketry/tile/multiblock/energy/TileMicrowaveReciever.java +++ b/src/main/java/zmaster587/advancedRocketry/tile/multiblock/energy/TileMicrowaveReciever.java @@ -2,27 +2,34 @@ import io.netty.buffer.ByteBuf; import net.minecraft.block.state.IBlockState; +import net.minecraft.client.util.ITooltipFlag; import net.minecraft.entity.Entity; +import net.minecraft.entity.item.EntityItem; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.inventory.IInventory; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; import net.minecraft.network.NetworkManager; import net.minecraft.network.play.server.SPacketUpdateTileEntity; +import net.minecraft.tileentity.TileEntity; import net.minecraft.util.*; import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; import zmaster587.advancedRocketry.api.ARConfiguration; import zmaster587.advancedRocketry.api.AdvancedRocketryBlocks; import zmaster587.advancedRocketry.api.SatelliteRegistry; import zmaster587.advancedRocketry.api.satellite.SatelliteBase; +import zmaster587.advancedRocketry.client.TooltipInjector; import zmaster587.advancedRocketry.dimension.DimensionManager; import zmaster587.advancedRocketry.dimension.DimensionProperties; import zmaster587.advancedRocketry.item.ItemSatelliteIdentificationChip; import zmaster587.advancedRocketry.stations.SpaceObjectManager; import zmaster587.advancedRocketry.stations.SpaceStationObject; +import zmaster587.advancedRocketry.util.PlanetaryTravelHelper; import zmaster587.libVulpes.LibVulpes; import zmaster587.libVulpes.api.IUniversalEnergyTransmitter; import zmaster587.libVulpes.block.BlockMeta; @@ -30,15 +37,25 @@ import zmaster587.libVulpes.inventory.modules.ModuleText; import zmaster587.libVulpes.network.PacketHandler; import zmaster587.libVulpes.network.PacketMachine; +import zmaster587.libVulpes.tile.multiblock.TilePlaceholder; +import zmaster587.libVulpes.tile.multiblock.hatch.TileInventoryHatch; import zmaster587.libVulpes.tile.multiblock.TileMultiBlock; import zmaster587.libVulpes.tile.multiblock.TileMultiPowerProducer; import zmaster587.libVulpes.util.Vector3F; +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; +import java.util.Map; + public class TileMicrowaveReciever extends TileMultiPowerProducer implements ITickable { + // key: BlockPos.toLong(), value: saved non-empty stacks for that hatch (slot order preserved) + private final Map> savedHatchInv = new HashMap<>(); + static final BlockMeta iron_block = new BlockMeta(AdvancedRocketryBlocks.blockSolarPanel); static final Object[][][] structure = new Object[][][]{ { @@ -113,20 +130,54 @@ public void onInventoryUpdated() { List list = new LinkedList<>(); - for (IInventory inv : itemInPorts) { - for (int i = 0; i < inv.getSizeInventory(); i++) { - ItemStack stack = inv.getStackInSlot(i); - if (!stack.isEmpty() && stack.getItem() instanceof ItemSatelliteIdentificationChip) { - list.add(SatelliteRegistry.getSatelliteId(stack)); + if (itemInPorts != null) { + for (IInventory inv : itemInPorts) { + for (int i = 0; i < inv.getSizeInventory(); i++) { + ItemStack stack = inv.getStackInSlot(i); + if (!stack.isEmpty() && stack.getItem() instanceof ItemSatelliteIdentificationChip) { + list.add(SatelliteRegistry.getSatelliteId(stack)); + } } } } + connectedSatellites = new LinkedList<>(new LinkedHashSet<>(list)); + } + @Override + public boolean attemptCompleteStructure(IBlockState state) { + if (!world.isRemote) { + // Snapshot BEFORE formation (real hatches) + snapshotHatchInventories(); + } + boolean ok = super.attemptCompleteStructure(state); + + if (!world.isRemote) { + if (ok) { + // Formation succeeded → push snapshot into placeholders (alive state) + writeSnapshotIntoPlaceholders(); + } else { + // Formation failed → discard + savedHatchInv.clear(); + } + } + return ok; + } + + + @Override + public void deconstructMultiBlock(World worldIn, BlockPos destroyedPos, boolean blockBroken, IBlockState state) { + if (!worldIn.isRemote) { + snapshotFromPlaceholders(); + } - connectedSatellites = list; + super.deconstructMultiBlock(worldIn, destroyedPos, blockBroken, state); + if (!worldIn.isRemote) { + restoreHatchInventories(); + } } + @Override public void update() { @@ -137,13 +188,40 @@ public void update() { } //Checks whenever a station changes dimensions or when the multiblock is intialized - ie any time the multipler could concieveably change - if (insolationPowerMultiplier == 0 || ((world.provider.getDimension() == ARConfiguration.getCurrentConfig().spaceDimId) && (powerSourceDimensionID != SpaceObjectManager.getSpaceManager().getSpaceStationFromBlockCoords(this.pos).getOrbitingPlanetId()))) { - DimensionProperties properties = DimensionManager.getInstance().getDimensionProperties(world.provider.getDimension()); - insolationPowerMultiplier = (world.provider.getDimension() == ARConfiguration.getCurrentConfig().spaceDimId) ? SpaceObjectManager.getSpaceManager().getSpaceStationFromBlockCoords(this.pos).getInsolationMultiplier() : properties.getPeakInsolationMultiplierWithoutAtmosphere(); - //Sets the ID of the place it's sourcing power from so it does not have to recheck - if (world.provider.getDimension() == ARConfiguration.getCurrentConfig().spaceDimId) - powerSourceDimensionID = SpaceObjectManager.getSpaceManager().getSpaceStationFromBlockCoords(this.pos).getOrbitingPlanetId(); + // --- BEGIN mirror SatelliteTerminal style) --- + final int curDim = world.provider.getDimension(); + final int spaceDim = ARConfiguration.getCurrentConfig().spaceDimId; + + // Cache station once; can be null + final zmaster587.advancedRocketry.stations.SpaceStationObject station = + (curDim == spaceDim) + ? (zmaster587.advancedRocketry.stations.SpaceStationObject) + zmaster587.advancedRocketry.stations.SpaceObjectManager.getSpaceManager() + .getSpaceStationFromBlockCoords(this.pos) + : null; + + // Recompute when uninitialized OR (in space AND orbiting planet changed and station exists) + final boolean needRecompute = + (insolationPowerMultiplier == 0) + || (curDim == spaceDim && station != null && powerSourceDimensionID != station.getOrbitingPlanetId()); + + if (needRecompute) { + if (curDim == spaceDim && station != null) { + insolationPowerMultiplier = station.getInsolationMultiplier(); + powerSourceDimensionID = station.getOrbitingPlanetId(); + } else { + final zmaster587.advancedRocketry.dimension.DimensionProperties props = + zmaster587.advancedRocketry.dimension.DimensionManager.getInstance() + .getDimensionProperties(curDim); + insolationPowerMultiplier = (props != null) + ? props.getPeakInsolationMultiplierWithoutAtmosphere() + : 1.0; // safe fallback + powerSourceDimensionID = curDim; + } } + // If we're in space but station==null (early ticks), keep previous multiplier and carry on. + // --- END robust insolation block --- + if (!isComplete()) return; @@ -174,37 +252,65 @@ public void update() { } } - DimensionProperties properties; - int dimId = world.provider.getDimension(); - SpaceStationObject spaceStation = (SpaceStationObject) SpaceObjectManager.getSpaceManager().getSpaceStationFromBlockCoords(this.pos); - if (!world.isRemote && (DimensionManager.getInstance().isDimensionCreated(dimId) || world.provider.getDimension() == 0)) { - //This way we check to see if it's on a station, and if so, if it has any satellites in orbit around the planet the station is around to pull from - properties = (spaceStation != null) ? spaceStation.getOrbitingPlanet() : DimensionManager.getInstance().getDimensionProperties(dimId); + // --- BEGIN (mirrors SatelliteTerminal) --- + final int dimId = world.provider.getDimension(); + final boolean dimOk = DimensionManager.getInstance().isDimensionCreated(dimId) || dimId == 0; + + if (!world.isRemote && dimOk) { + // If we’re on a station, prefer its orbiting planet; otherwise use the local dim props + final SpaceStationObject stationHere = (dimId == ARConfiguration.getCurrentConfig().spaceDimId) + ? (SpaceStationObject) SpaceObjectManager.getSpaceManager().getSpaceStationFromBlockCoords(this.pos) + : null; + + final DimensionProperties props = (stationHere != null) + ? stationHere.getOrbitingPlanet() + : DimensionManager.getInstance().getDimensionProperties(dimId); + int energyReceived = 0; - if (enabled) { - for (long lng : connectedSatellites) { - SatelliteBase satellite = properties.getSatellite(lng); - if (satellite instanceof IUniversalEnergyTransmitter) { - energyReceived += ((IUniversalEnergyTransmitter) satellite).transmitEnergy(EnumFacing.UP, false); + if (enabled && props != null && connectedSatellites != null && !connectedSatellites.isEmpty()) { + // Snapshot to avoid concurrent modification + final LinkedHashSet sats = new LinkedHashSet<>(connectedSatellites); + + // Resolve “here” dim exactly like SatelliteTerminal + final int hereDim = DimensionManager.getEffectiveDimId(world, pos).getId(); + + for (long lng : sats) { + final SatelliteBase sat = props.getSatellite(lng); + if (sat == null) continue; + + // Range/link check (same logic as Terminal) + final int satDim = sat.getDimensionId(); + final boolean inRange = PlanetaryTravelHelper.isTravelAnywhereInPlanetarySystem(satDim, hereDim); + if (!inRange) continue; + + if (sat instanceof IUniversalEnergyTransmitter) { + energyReceived += ((IUniversalEnergyTransmitter) sat).transmitEnergy(EnumFacing.UP, false); } } - //Multiplied by two for 520W = 1 RF/t becoming 2 RF/t @ 100% efficiency, and by insolation mult for solar stuff - energyReceived *= 2 * insolationPowerMultiplier; + // 520W = 1 RF/t -> 2 RF/t @ 100%; scale by insolation + energyReceived = (int) Math.round(energyReceived * (2 * insolationPowerMultiplier)); } + powerMadeLastTick = energyReceived; if (powerMadeLastTick != prevPowerMadeLastTick) { prevPowerMadeLastTick = powerMadeLastTick; - PacketHandler.sendToNearby(new PacketMachine(this, (byte) 1), world.provider.getDimension(), pos, 128); - + PacketHandler.sendToNearby(new PacketMachine(this, (byte) 1), + world.provider.getDimension(), pos, 128); } producePower(powerMadeLastTick); } - if (world.isRemote) - textModule.setText(LibVulpes.proxy.getLocalizedString("msg.microwaverec.generating") + " " + powerMadeLastTick + " " + LibVulpes.proxy.getLocalizedString("msg.powerunit.rfpertick")); - } + + if (world.isRemote) { + textModule.setText( + LibVulpes.proxy.getLocalizedString("msg.microwaverec.generating") + " " + + powerMadeLastTick + " " + + LibVulpes.proxy.getLocalizedString("msg.powerunit.rfpertick")); + } + } + @Override public SPacketUpdateTileEntity getUpdatePacket() { @@ -272,15 +378,37 @@ public void useNetworkData(EntityPlayer player, Side side, byte id, NBTTagCompou public NBTTagCompound writeToNBT(NBTTagCompound nbt) { super.writeToNBT(nbt); + // ---- satellites ---- int[] intArray = new int[connectedSatellites.size() * 2]; - for (int i = 0; i < connectedSatellites.size() * 2; i += 2) { - intArray[i] = (connectedSatellites.get(i / 2)).intValue(); - intArray[i + 1] = (int) ((connectedSatellites.get(i / 2) >>> 32)); + intArray[i] = (connectedSatellites.get(i / 2)).intValue(); + intArray[i+1] = (int)((connectedSatellites.get(i / 2) >>> 32)); } - nbt.setIntArray("satilliteList", intArray); + // ---- saved hatch inventories ---- + NBTTagList hatchList = new NBTTagList(); + if (savedHatchInv != null && !savedHatchInv.isEmpty()) { + for (Map.Entry> e : savedHatchInv.entrySet()) { + NBTTagCompound entry = new NBTTagCompound(); + entry.setLong("pos", e.getKey()); + + NBTTagList items = new NBTTagList(); + NonNullList arr = e.getValue(); + for (int slot = 0; slot < arr.size(); slot++) { + ItemStack s = arr.get(slot); + if (s.isEmpty()) continue; + NBTTagCompound it = new NBTTagCompound(); + it.setInteger("slot", slot); + s.writeToNBT(it); + items.appendTag(it); + } + entry.setTag("items", items); + hatchList.appendTag(entry); + } + } + nbt.setTag("savedHatchInv", hatchList); + return nbt; } @@ -288,12 +416,286 @@ public NBTTagCompound writeToNBT(NBTTagCompound nbt) { public void readFromNBT(NBTTagCompound nbt) { super.readFromNBT(nbt); + // ---- satellites ---- int[] intArray = nbt.getIntArray("satilliteList"); connectedSatellites.clear(); - for (int i = 0; i < intArray.length / 2; i += 2) { + for (int i = 0; i < intArray.length; i += 2) { connectedSatellites.add(intArray[i] | (((long) intArray[i + 1]) << 32)); } + // ---- saved hatch inventories ---- + savedHatchInv.clear(); + + NBTTagList hatchList = nbt.getTagList("savedHatchInv", 10); + for (int i = 0; i < hatchList.tagCount(); i++) { + NBTTagCompound entry = hatchList.getCompoundTagAt(i); + long posKey = entry.getLong("pos"); + NBTTagList items = entry.getTagList("items", 10); + + int maxSlot = -1; + for (int j = 0; j < items.tagCount(); j++) { + int slot = items.getCompoundTagAt(j).getInteger("slot"); + if (slot > maxSlot) maxSlot = slot; + } + NonNullList arr = NonNullList.withSize(Math.max(maxSlot + 1, 1), ItemStack.EMPTY); + + for (int j = 0; j < items.tagCount(); j++) { + NBTTagCompound it = items.getCompoundTagAt(j); + int slot = it.getInteger("slot"); + arr.set(slot, new ItemStack(it)); + } + savedHatchInv.put(posKey, arr); + } } + // Push the pre-formation snapshot into the placeholders' replaced inventories (after formation) + private void writeSnapshotIntoPlaceholders() { + if (world == null || savedHatchInv.isEmpty()) return; + + final Object[][][] struct = getStructure(); + if (struct == null) return; + + final Vector3F off = getControllerOffset(struct); + final EnumFacing front = getFrontDirection(world.getBlockState(pos)); + + for (int y = 0; y < struct.length; y++) { + for (int z = 0; z < struct[0].length; z++) { + for (int x = 0; x < struct[0][0].length; x++) { + if (struct[y][z][x] == null) continue; + + int gx = pos.getX() + (x - off.x) * front.getFrontOffsetZ() - (z - off.z) * front.getFrontOffsetX(); + int gy = pos.getY() - y + off.y; + int gz = pos.getZ() - (x - off.x) * front.getFrontOffsetX() - (z - off.z) * front.getFrontOffsetZ(); + BlockPos bp = new BlockPos(gx, gy, gz); + + TileEntity te = world.getTileEntity(bp); + if (!(te instanceof TilePlaceholder)) continue; + + NonNullList snapshot = savedHatchInv.get(bp.toLong()); + if (snapshot == null || snapshot.isEmpty()) continue; + + TileEntity rep = ((TilePlaceholder) te).getReplacedTileEntity(); + if (!(rep instanceof IInventory)) continue; + + IInventory inv = (IInventory) rep; + + // First try to restore to original slots + for (int i = 0; i < snapshot.size(); i++) { + ItemStack src = snapshot.get(i); + if (src.isEmpty()) continue; + ItemStack cur = (i < inv.getSizeInventory()) ? inv.getStackInSlot(i) : ItemStack.EMPTY; + if (i < inv.getSizeInventory() && cur.isEmpty()) { + inv.setInventorySlotContents(i, src.copy()); + snapshot.set(i, ItemStack.EMPTY); + } + } + // Then merge leftovers + for (int i = 0; i < snapshot.size(); i++) { + ItemStack left = snapshot.get(i); + if (left.isEmpty()) continue; + + ItemStack rem = left.copy(); + // merge into existing stacks + for (int slot = 0; slot < inv.getSizeInventory() && !rem.isEmpty(); slot++) { + ItemStack dst = inv.getStackInSlot(slot); + if (dst.isEmpty()) continue; + if (ItemStack.areItemsEqual(dst, rem) && ItemStack.areItemStackTagsEqual(dst, rem)) { + int can = Math.min(inv.getInventoryStackLimit(), dst.getMaxStackSize()) - dst.getCount(); + if (can > 0) { + int move = Math.min(can, rem.getCount()); + dst.grow(move); + rem.shrink(move); + inv.setInventorySlotContents(slot, dst); + } + } + } + // fill empties + for (int slot = 0; slot < inv.getSizeInventory() && !rem.isEmpty(); slot++) { + if (inv.getStackInSlot(slot).isEmpty()) { + int put = Math.min(inv.getInventoryStackLimit(), rem.getMaxStackSize()); + ItemStack putStack = rem.splitStack(put); + inv.setInventorySlotContents(slot, putStack); + } + } + // any remainder stays in snapshot (shouldn’t normally happen) + snapshot.set(i, rem.isEmpty() ? ItemStack.EMPTY : rem); + } + inv.markDirty(); + } + } + } + + // After pushing into placeholders, discard snapshot + savedHatchInv.clear(); + } + + // Pull current contents back out of placeholders' replaced inventories (before teardown) + private void snapshotFromPlaceholders() { + savedHatchInv.clear(); + if (world == null) return; + + final Object[][][] struct = getStructure(); + if (struct == null) return; + + final Vector3F off = getControllerOffset(struct); + final EnumFacing front = getFrontDirection(world.getBlockState(pos)); + + for (int y = 0; y < struct.length; y++) { + for (int z = 0; z < struct[0].length; z++) { + for (int x = 0; x < struct[0][0].length; x++) { + if (struct[y][z][x] == null) continue; + + int gx = pos.getX() + (x - off.x) * front.getFrontOffsetZ() - (z - off.z) * front.getFrontOffsetX(); + int gy = pos.getY() - y + off.y; + int gz = pos.getZ() - (x - off.x) * front.getFrontOffsetX() - (z - off.z) * front.getFrontOffsetZ(); + BlockPos bp = new BlockPos(gx, gy, gz); + + TileEntity te = world.getTileEntity(bp); + + // Prefer the underlying hatch if this position is a placeholder + IInventory inv = null; + if (te instanceof TilePlaceholder) { + TileEntity rep = ((TilePlaceholder) te).getReplacedTileEntity(); + if (rep instanceof TileInventoryHatch) inv = (IInventory) rep; + } else if (te instanceof TileInventoryHatch) { + // Real multiblock component hatch (hidden block), still a live TE + inv = (IInventory) te; + } + + if (inv != null) { + NonNullList copy = NonNullList.withSize(inv.getSizeInventory(), ItemStack.EMPTY); + boolean any = false; + for (int i = 0; i < inv.getSizeInventory(); i++) { + ItemStack s = inv.getStackInSlot(i); + if (!s.isEmpty()) { + copy.set(i, s.copy()); + any = true; + } + } + if (any) savedHatchInv.put(bp.toLong(), copy); + } + } + } + } + } + + + + + private void snapshotHatchInventories() { + savedHatchInv.clear(); + final Object[][][] struct = getStructure(); + if (struct == null || world == null) return; + + final Vector3F off = getControllerOffset(struct); + final EnumFacing front = getFrontDirection(world.getBlockState(pos)); + + for (int y = 0; y < struct.length; y++) { + for (int z = 0; z < struct[0].length; z++) { + for (int x = 0; x < struct[0][0].length; x++) { + if (struct[y][z][x] == null) continue; + + int gx = pos.getX() + (x - off.x) * front.getFrontOffsetZ() - (z - off.z) * front.getFrontOffsetX(); + int gy = pos.getY() - y + off.y; + int gz = pos.getZ() - (x - off.x) * front.getFrontOffsetX() - (z - off.z) * front.getFrontOffsetZ(); + BlockPos bp = new BlockPos(gx, gy, gz); + + if (!world.getChunkFromBlockCoords(bp).isLoaded()) continue; + + TileEntity te = world.getTileEntity(bp); + + // If already replaced, pull from the placeholder’s replaced tile + if (te instanceof TilePlaceholder) te = ((TilePlaceholder) te).getReplacedTileEntity(); + + if (te instanceof IInventory) { + IInventory inv = (IInventory) te; + NonNullList copy = NonNullList.withSize(inv.getSizeInventory(), ItemStack.EMPTY); + boolean any = false; + for (int i = 0; i < inv.getSizeInventory(); i++) { + ItemStack s = inv.getStackInSlot(i); + if (!s.isEmpty()) { + copy.set(i, s.copy()); + any = true; + } + } + if (any) savedHatchInv.put(bp.toLong(), copy); + } + } + } + } + } + + private void restoreHatchInventories() { + if (world == null || savedHatchInv.isEmpty()) return; + + for (Map.Entry> e : savedHatchInv.entrySet()) { + BlockPos bp = BlockPos.fromLong(e.getKey()); + TileEntity te = world.getTileEntity(bp); + + // If placeholder is still present for any reason, restore into the underlying replaced tile + if (te instanceof TilePlaceholder) te = ((TilePlaceholder) te).getReplacedTileEntity(); + + if (te instanceof IInventory) { + IInventory inv = (IInventory) te; + NonNullList items = e.getValue(); + + // naive merge: try to put stacks back in their original slots first, then merge to any slot + // 1) original slots + for (int i = 0; i < items.size(); i++) { + ItemStack src = items.get(i); + if (src.isEmpty()) continue; + ItemStack cur = inv.getStackInSlot(i); + if (cur.isEmpty()) { + inv.setInventorySlotContents(i, src.copy()); + items.set(i, ItemStack.EMPTY); + } + } + // 2) merge leftovers anywhere they fit, otherwise drop + for (int i = 0; i < items.size(); i++) { + ItemStack left = items.get(i); + if (left.isEmpty()) continue; + + ItemStack rem = left.copy(); + // try merging into existing stacks + for (int slot = 0; slot < inv.getSizeInventory() && !rem.isEmpty(); slot++) { + ItemStack dst = inv.getStackInSlot(slot); + if (dst.isEmpty()) continue; + if (ItemStack.areItemsEqual(dst, rem) && ItemStack.areItemStackTagsEqual(dst, rem)) { + int can = Math.min(inv.getInventoryStackLimit(), dst.getMaxStackSize()) - dst.getCount(); + if (can > 0) { + int move = Math.min(can, rem.getCount()); + dst.grow(move); + rem.shrink(move); + inv.setInventorySlotContents(slot, dst); + } + } + } + // fill empty slots + for (int slot = 0; slot < inv.getSizeInventory() && !rem.isEmpty(); slot++) { + if (inv.getStackInSlot(slot).isEmpty()) { + int put = Math.min(inv.getInventoryStackLimit(), rem.getMaxStackSize()); + ItemStack putStack = rem.splitStack(put); + inv.setInventorySlotContents(slot, putStack); + } + } + // drop remainder to world + if (!rem.isEmpty()) { + world.spawnEntity(new EntityItem(world, bp.getX() + 0.5, bp.getY() + 0.5, bp.getZ() + 0.5, rem)); + } + items.set(i, ItemStack.EMPTY); + } + inv.markDirty(); + } else { + // no inventory to restore into → drop all + for (ItemStack s : e.getValue()) { + if (!s.isEmpty()) { + world.spawnEntity(new EntityItem(world, bp.getX() + 0.5, bp.getY() + 0.5, bp.getZ() + 0.5, s.copy())); + } + } + } + } + savedHatchInv.clear(); + } + + } diff --git a/src/main/java/zmaster587/advancedRocketry/tile/satellite/TileSatelliteTerminal.java b/src/main/java/zmaster587/advancedRocketry/tile/satellite/TileSatelliteTerminal.java index 7dcfaef7f..c930fc2c3 100644 --- a/src/main/java/zmaster587/advancedRocketry/tile/satellite/TileSatelliteTerminal.java +++ b/src/main/java/zmaster587/advancedRocketry/tile/satellite/TileSatelliteTerminal.java @@ -4,6 +4,8 @@ import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.math.BlockPos; import net.minecraft.util.EnumFacing; import net.minecraftforge.fml.relauncher.Side; import zmaster587.advancedRocketry.api.AdvancedRocketryBlocks; @@ -17,10 +19,10 @@ import zmaster587.advancedRocketry.inventory.modules.ModuleSatellite; import zmaster587.advancedRocketry.item.ItemData; import zmaster587.advancedRocketry.item.ItemSatelliteIdentificationChip; -import zmaster587.advancedRocketry.network.PacketSatellite; import zmaster587.advancedRocketry.satellite.SatelliteData; import zmaster587.advancedRocketry.util.IDataInventory; import zmaster587.advancedRocketry.util.PlanetaryTravelHelper; + import zmaster587.libVulpes.LibVulpes; import zmaster587.libVulpes.inventory.modules.*; import zmaster587.libVulpes.network.PacketHandler; @@ -30,30 +32,126 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.LinkedList; + +import java.util.ArrayList; import java.util.List; +public class TileSatelliteTerminal extends TileInventoriedRFConsumer + implements INetworkMachine, IModularInventory, IButtonInventory, IDataInventory, IDataHandler { -public class TileSatelliteTerminal extends TileInventoriedRFConsumer implements INetworkMachine, IModularInventory, IButtonInventory, IDataInventory, IDataHandler { + private DataStorage data; + // Auto-download polling with exponential backoff + private static final int AUTO_DL_BASE_INTERVAL_TICKS = 64; // min interval + private static final int AUTO_DL_MAX_INTERVAL_TICKS = 512; // cap + private int autoDlInterval = AUTO_DL_BASE_INTERVAL_TICKS; // current interval + private long nextAutoDlTick = 0L; // worldTime gate - //private ModuleText satelliteText; - private SatelliteBase satellite; - private ModuleText moduleText; - private DataStorage data; public TileSatelliteTerminal() { super(10000, 2); - data = new DataStorage(); data.setMaxData(1000); } + private final BlockPos.MutableBlockPos mpos = new BlockPos.MutableBlockPos(); + + private boolean hasExtractPlugAdjacent() { + if (world == null) return false; + for (EnumFacing f : EnumFacing.values()) { + mpos.setPos(pos.getX() + f.getFrontOffsetX(), + pos.getY() + f.getFrontOffsetY(), + pos.getZ() + f.getFrontOffsetZ()); + if (!world.isBlockLoaded(mpos)) continue; + + TileEntity te = world.getTileEntity(mpos); + if (te instanceof zmaster587.advancedRocketry.tile.cables.TileWirelessTransciever) { + zmaster587.advancedRocketry.tile.cables.TileWirelessTransciever w = + (zmaster587.advancedRocketry.tile.cables.TileWirelessTransciever) te; + if (w.isEnabledWireless() && w.isExtractModeWireless()) return true; + } + } + return false; + } + + + + // Link+power check using an already-looked-up satellite + private boolean hasLinkAndPower(@Nonnull SatelliteBase sat) { + // must be a data satellite + if (!(sat instanceof zmaster587.advancedRocketry.satellite.SatelliteData)) return false; + + // check range + final int hereDim = zmaster587.advancedRocketry.dimension.DimensionManager + .getEffectiveDimId(world, pos).getId(); + final int satDim = sat.getDimensionId(); + + final boolean inRange = zmaster587.advancedRocketry.util.PlanetaryTravelHelper + .isTravelAnywhereInPlanetarySystem(satDim, hereDim); + if (!inRange) return false; + + // check power + return getUniversalEnergyStored() >= getPowerPerOperation(); + } + + // Keep convenience overload + private void maybeAutoDownloadFromSatellite() { + maybeAutoDownloadFromSatellite(false); + } + + private void maybeAutoDownloadFromSatellite(boolean force) { + if (world == null || world.isRemote) return; + + final long now = world.getTotalWorldTime(); + + if (force) { + autoDlInterval = AUTO_DL_BASE_INTERVAL_TICKS; + nextAutoDlTick = now + autoDlInterval; // avoid same-tick multi-pulls + } else if (now < nextAutoDlTick) { + return; + } + + // Buffer full → just schedule next check + if (data.getData() >= data.getMaxData()) { + nextAutoDlTick = now + autoDlInterval; + return; + } + + // No eligible plug → back off (unless forced) + if (!hasExtractPlugAdjacent()) { + if (!force) autoDlInterval = Math.min(autoDlInterval << 1, AUTO_DL_MAX_INTERVAL_TICKS); + nextAutoDlTick = now + autoDlInterval; + return; + } + + // Resolve satellite fresh from the chip each poll + SatelliteBase sat = resolveSatelliteFresh(); + if (sat == null) { + if (!force) autoDlInterval = Math.min(autoDlInterval << 1, AUTO_DL_MAX_INTERVAL_TICKS); + nextAutoDlTick = now + autoDlInterval; + return; + } + if (!hasLinkAndPower(sat)) { + if (!force) autoDlInterval = Math.min(autoDlInterval << 1, AUTO_DL_MAX_INTERVAL_TICKS); + nextAutoDlTick = now + autoDlInterval; + return; + } + + // Do the pull + sat.performAction(null, world, pos); + this.energy.extractEnergy(getPowerPerOperation(), false); + + // Success → reset interval + autoDlInterval = AUTO_DL_BASE_INTERVAL_TICKS; + nextAutoDlTick = now + autoDlInterval; + } + + + private static final int[] NO_SLOTS = new int[0]; + @Override @Nonnull - public int[] getSlotsForFace(@Nullable EnumFacing side) { - return new int[0]; - } + public int[] getSlotsForFace(@Nullable EnumFacing side) { return NO_SLOTS; } @Override public String getModularInventoryName() { @@ -61,203 +159,176 @@ public String getModularInventoryName() { } @Override - public boolean isItemValidForSlot(int slot, @Nonnull ItemStack stack) { - return true; - } + public boolean isItemValidForSlot(int slot, @Nonnull ItemStack stack) { return true; } @Override public boolean canPerformFunction() { - return world.getTotalWorldTime() % 16 == 0 && getSatelliteFromSlot(0) != null; + if (world == null) return false; + final long now = world.getTotalWorldTime(); + return (now % 16 == 0) && (now >= nextAutoDlTick); } @Override - public int getPowerPerOperation() { - return 1; - } + public int getPowerPerOperation() { return 1; } @Override public void performFunction() { - if (world.isRemote) - updateInventoryInfo(); + if (world == null || world.isRemote) return; + maybeAutoDownloadFromSatellite(false); } - @Override - public void writeDataToNetwork(ByteBuf out, byte packetId) { - if (packetId == (byte) 22) { - satellite = getSatelliteFromSlot(0); - if (satellite != null && satellite instanceof SatelliteData) { - if (getUniversalEnergyStored() < getPowerPerOperation()) { - out.writeInt(1); // no power - } else { - if (!PlanetaryTravelHelper.isTravelAnywhereInPlanetarySystem(satellite.getDimensionId(), DimensionManager.getEffectiveDimId(world, pos).getId())) { - out.writeInt(2);//out of range - } else { - out.writeInt(3); - out.writeInt(((SatelliteData) satellite).getPowerPerTick()); - out.writeInt(((SatelliteData) satellite).data.getData()); - out.writeInt(((SatelliteData) satellite).data.getMaxData()); - } - } - } else { - out.writeInt(0); // no link - } - } - } + + // Old custom packet not used anymore; keep empty to satisfy INetworkMachine @Override - public void readDataFromNetwork(ByteBuf in, byte packetId, - NBTTagCompound nbt) { - if (packetId == (byte) 22) { - int status = in.readInt(); - if (status == 3){ - nbt.setInteger("ppt", in.readInt()); - nbt.setInteger("data", in.readInt()); - nbt.setInteger("maxdata", in.readInt()); - } - nbt.setInteger("status", status); - } - } + public void writeDataToNetwork(ByteBuf out, byte packetId) { } + // Old custom packet not used anymore; keep empty to satisfy INetworkMachine @Override - public void update() { - super.update(); + public void readDataFromNetwork(ByteBuf in, byte packetId, NBTTagCompound nbt) { } - if (!world.isRemote) { - //update satellite for players nearby - if ((world.getTotalWorldTime() % 20) == 0) { - PacketHandler.sendToNearby(new PacketMachine(this, (byte) 22), world.provider.getDimension(), pos, 16); - } - } - } + // Tick: nothing needed; the module polls the tile every 9 tick while GUI is open + //@Override + //public void update() { + // super.update(); + // no status pushing needed + //} @Override public void useNetworkData(EntityPlayer player, Side side, byte id, NBTTagCompound nbt) { if (id == 0) { + // store data to item (server handles it inside storeData) storeData(0); + } else if (id == 100) { + // "Connect" / perform action + if (!world.isRemote) { + SatelliteBase sat = getSatelliteFromSlot(0); + + boolean inRange = false; + if (sat != null) { + int satDim = sat.getDimensionId(); + int hereDim = DimensionManager.getEffectiveDimId(world, pos).getId(); + inRange = PlanetaryTravelHelper.isTravelAnywhereInPlanetarySystem(satDim, hereDim); + } + boolean hasLink = (sat instanceof SatelliteData) && inRange; + boolean hasPower = getUniversalEnergyStored() >= getPowerPerOperation(); - if (satellite != null && PlanetaryTravelHelper.isTravelAnywhereInPlanetarySystem(satellite.getDimensionId(), DimensionManager.getEffectiveDimId(world, pos).getId())) { - satellite.performAction(player, world, pos); + if (hasLink && hasPower) { + sat.performAction(player, world, pos); + this.energy.extractEnergy(getPowerPerOperation(), false); + } } - } else if (id == 101) { - onInventoryButtonPressed(id - 100); - } - if (id == 22) { - if (world.isRemote) { // 22 should never arrive at the server - int status = nbt.getInteger("status"); - satellite = getSatelliteFromSlot(0); - if (moduleText != null){ - if (status != 0 && satellite != null) { - if (status == 1) - moduleText.setText(LibVulpes.proxy.getLocalizedString("msg.notenoughpower")); - - else if (status == 2) { - moduleText.setText(satellite.getName() + "\n\n" + LibVulpes.proxy.getLocalizedString("msg.satctrlcenter.toofar")); - } else if (status == 3) { - moduleText.setText(satellite.getName() + "\n\n" + LibVulpes.proxy.getLocalizedString("msg.satctrlcenter.info") + "\n" + - "Power gen.: "+nbt.getInteger("ppt")+"\n"+ - "Data: "+nbt.getInteger("data") +"/"+nbt.getInteger("maxdata")); + } else if (id == 101) { + if (!world.isRemote) { + ItemStack stack = getStackInSlot(0); + if (!stack.isEmpty() && stack.getItem() instanceof ItemSatelliteIdentificationChip) { + ItemSatelliteIdentificationChip idchip = (ItemSatelliteIdentificationChip) stack.getItem(); + + SatelliteBase sat = idchip.getSatellite(stack); + if (sat != null) { + DimensionManager.getInstance() + .getDimensionProperties(sat.getDimensionId()) + .removeSatellite(sat.getId()); } - } else - moduleText.setText(LibVulpes.proxy.getLocalizedString("msg.satctrlcenter.nolink")); + + idchip.erase(stack); + // server mutates the inventory; client will get it via normal container sync + setInventorySlotContents(0, stack); } } } } + @Nullable + private SatelliteBase resolveSatelliteFresh() { + ItemStack s0 = getStackInSlot(0); + return (!s0.isEmpty() && s0.getItem() instanceof ItemSatelliteIdentificationChip) + ? ItemSatelliteIdentificationChip.getSatellite(s0) + : null; + } + @Override public void setInventorySlotContents(int slot, @Nonnull ItemStack stack) { super.setInventorySlotContents(slot, stack); - satellite = getSatelliteFromSlot(0); - updateInventoryInfo(); - } - - public void updateInventoryInfo() { - + if (!world.isRemote && slot == 0) { + maybeAutoDownloadFromSatellite(true); // force reset to base + } } - public SatelliteBase getSatelliteFromSlot(int slot) { - ItemStack stack = getStackInSlot(slot); if (!stack.isEmpty() && stack.getItem() instanceof ItemSatelliteIdentificationChip) { return ItemSatelliteIdentificationChip.getSatellite(stack); } - return null; } @Override public List getModules(int ID, EntityPlayer player) { + List modules = new ArrayList<>(6); + + modules.add(new ModulePower(18, 20, this.energy) { + @Override public int numberOfChangesToSend() { return 2; } + }); + + modules.add(new ModuleButton( + 116, 70, 0, + LibVulpes.proxy.getLocalizedString("msg.satctrlcenter.connect"), + this, + zmaster587.libVulpes.inventory.TextureResources.buttonBuild, + LibVulpes.proxy.getLocalizedString("msg.satctrlcenter.autodl_hint") // tooltip + )); - List modules = new LinkedList<>(); - modules.add(new ModulePower(18, 20, this.energy)); - modules.add(new ModuleButton(116, 70, 0, LibVulpes.proxy.getLocalizedString("msg.satctrlcenter.connect"), this, zmaster587.libVulpes.inventory.TextureResources.buttonBuild)); - modules.add(new ModuleButton(173, 3, 1, "", this, TextureResources.buttonKill, LibVulpes.proxy.getLocalizedString("msg.satctrlcenter.destroysat"), 24, 24)); - modules.add(new ModuleData(28, 20, 1, this, data)); - ModuleSatellite moduleSatellite = new ModuleSatellite(152, 10, this, 0); - moduleSatellite.setSatellite(satellite); - modules.add(moduleSatellite); - //Try to assign a satellite ASAP - //moduleSatellite.setSatellite(getSatelliteFromSlot(0)); + modules.add(new ModuleButton(173, 3, 1, "", + this, TextureResources.buttonKill, + LibVulpes.proxy.getLocalizedString("msg.satctrlcenter.destroysat"), 24, 24)); - moduleText = new ModuleText(60, 20, LibVulpes.proxy.getLocalizedString("msg.satctrlcenter.nolink"), 0x404040); - modules.add(moduleText); + modules.add(new ModuleData(28, 20, 1, this, data) { + @Override public int numberOfChangesToSend() { return 2; } + }); + + modules.add(new ModuleSatellite(152, 10, this, 0) { + @Override public int numberOfChangesToSend() { return 0; } + }); + + // Add status module last; no need to keep a field reference + modules.add(new zmaster587.advancedRocketry.inventory.modules.ModuleSatelliteTerminal( + 60, 20, 0x404040, this, this)); - updateInventoryInfo(); return modules; } + @Override public void onInventoryButtonPressed(int buttonId) { - if (buttonId == 0) { - PacketHandler.sendToServer(new PacketMachine(this, (byte) (100 + buttonId))); - + PacketHandler.sendToServer(new PacketMachine(this, (byte) (100 + buttonId))); // id 100 } else if (buttonId == 1) { - ItemStack stack = getStackInSlot(0); - - if (!stack.isEmpty() && stack.getItem() instanceof ItemSatelliteIdentificationChip) { - ItemSatelliteIdentificationChip idchip = (ItemSatelliteIdentificationChip) stack.getItem(); - - SatelliteBase satellite = idchip.getSatellite(stack); - - //Somebody might want to erase the chip of an already existing satellite - if (satellite != null) - DimensionManager.getInstance().getDimensionProperties(satellite.getDimensionId()).removeSatellite(satellite.getId()); - - idchip.erase(stack); - setInventorySlotContents(0, stack); - PacketHandler.sendToServer(new PacketMachine(this, (byte) (100 + buttonId))); - } + PacketHandler.sendToServer(new PacketMachine(this, (byte) (100 + buttonId))); // id 101 } - } @Override public NBTTagCompound writeToNBT(NBTTagCompound nbt) { super.writeToNBT(nbt); - - NBTTagCompound data = new NBTTagCompound(); - - this.data.writeToNBT(data); - nbt.setTag("data", data); + NBTTagCompound dataTag = new NBTTagCompound(); + this.data.writeToNBT(dataTag); + nbt.setTag("data", dataTag); return nbt; } @Override public void readFromNBT(NBTTagCompound nbt) { super.readFromNBT(nbt); - - NBTTagCompound data = nbt.getCompoundTag("data"); - this.data.readFromNBT(data); + NBTTagCompound dataTag = nbt.getCompoundTag("data"); + this.data.readFromNBT(dataTag); } @Override - public void loadData(int id) { - } + public void loadData(int id) { } @Override public void storeData(int id) { @@ -274,28 +345,58 @@ public void storeData(int id) { @Override public int extractData(int maxAmount, DataType type, EnumFacing dir, boolean commit) { - //TODO + // 1) Type guard + if (type != data.getDataType() && data.getDataType() != DataType.UNDEFINED) return 0; - SatelliteBase satellite = getSatelliteFromSlot(0); - if (satellite instanceof SatelliteData && PlanetaryTravelHelper.isTravelAnywhereInPlanetarySystem(satellite.getDimensionId(), DimensionManager.getEffectiveDimId(world, pos).getId())) { - satellite.performAction(null, world, pos); - } + // 2) Simulation + if (!commit) return Math.min(maxAmount, data.getData()); - if (type == data.getDataType() || data.getDataType() == DataType.UNDEFINED) { - return data.removeData(maxAmount, commit); - } + // 3) Drain LOCAL once + int toGive = Math.min(maxAmount, data.getData()); + int removed = (toGive > 0) ? data.removeData(toGive, true) : 0; - return 0; + // 4) Opportunistic refill (cheap guard inside function) + if (removed > 0) { + maybeAutoDownloadFromSatellite(true); + } + return removed; } + @Override public int addData(int maxAmount, DataType type, EnumFacing dir, boolean commit) { + int added = data.addData(maxAmount, type, commit); + + return added; + } + + @Override + public void onLoad() { + super.onLoad(); + if (!world.isRemote) { + // Reset backoff scheduler to a sane base state + autoDlInterval = AUTO_DL_BASE_INTERVAL_TICKS; + long now = world.getTotalWorldTime(); + // Warm-up so neighbors/registries settle (e.g. 80 ticks ~ 4s) + nextAutoDlTick = now + 80; + } + } - return data.addData(maxAmount, type, commit); + @Override + public void onChunkUnload() { + super.onChunkUnload(); + // Hard-clear any references and scheduling + autoDlInterval = AUTO_DL_BASE_INTERVAL_TICKS; + nextAutoDlTick = 0L; } + // Clear caches if the block/TE is invalidated (broken/replaced) @Override - public boolean canInteractWithContainer(EntityPlayer entity) { - return true; + public void invalidate() { + super.invalidate(); } + + + @Override + public boolean canInteractWithContainer(EntityPlayer entity) { return true; } } diff --git a/src/main/java/zmaster587/advancedRocketry/tile/station/TileStationAltitudeController.java b/src/main/java/zmaster587/advancedRocketry/tile/station/TileStationAltitudeController.java index 6ed4760ab..205fb7f5b 100644 --- a/src/main/java/zmaster587/advancedRocketry/tile/station/TileStationAltitudeController.java +++ b/src/main/java/zmaster587/advancedRocketry/tile/station/TileStationAltitudeController.java @@ -1,6 +1,8 @@ package zmaster587.advancedRocketry.tile.station; import io.netty.buffer.ByteBuf; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.inventory.GuiContainer; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.network.NetworkManager; @@ -30,10 +32,10 @@ public class TileStationAltitudeController extends TileEntity implements IModula int progress; private RedstoneState state = RedstoneState.OFF; - + private long lastAltSyncTick = -10; private ModuleText moduleGrav, numGravPylons, maxGravBuildSpeed, targetGrav; private ModuleRedstoneOutputButton redstoneControl; - + private boolean wasChanging = false; public TileStationAltitudeController() { moduleGrav = new ModuleText(6, 15, "Altitude: ", 0xaa2020); //numGravPylons = new ModuleText(10, 25, "Number Of Thrusters: ", 0xaa2020); @@ -54,7 +56,63 @@ public List getModules(int id, EntityPlayer player) { modules.add(new ModuleSlider(6, 60, 0, TextureResources.doubleWarningSideBarIndicator, this)); modules.add(redstoneControl); - updateText(); + // inline updater that runs only while GUI is open + modules.add(new ModuleBase(0, 0) { + private SpaceStationObject cached; + private int cachedId = Integer.MIN_VALUE; + + // last shown keys (ints in Km) + private int lastAltKm = Integer.MIN_VALUE; + private int lastTgtKm = Integer.MIN_VALUE; + + // localized prefixes (cache per GUI session) + private final String prefixAlt = LibVulpes.proxy.getLocalizedString("msg.stationaltctrl.alt"); + private final String prefixTgt = LibVulpes.proxy.getLocalizedString("msg.stationaltctrl.tgtalt"); + + private boolean ensureStation() { + if (cached == null) { + ISpaceObject so = SpaceObjectManager.getSpaceManager().getSpaceStationFromBlockCoords(pos); + if (!(so instanceof SpaceStationObject)) return false; + cached = (SpaceStationObject) so; + cachedId = so.getId(); + return true; + } + ISpaceObject current = SpaceObjectManager.getSpaceManager().getSpaceStationFromBlockCoords(pos); + if (!(current instanceof SpaceStationObject)) { cached = null; cachedId = Integer.MIN_VALUE; return false; } + if (current.getId() != cachedId) { + cached = (SpaceStationObject) current; + cachedId = current.getId(); + } + return true; + } + + @Override + public void renderBackground(GuiContainer gui, int x, int y, int mouseX, int mouseY, FontRenderer font) { + // Only runs while the GUI is visible → zero idle cost when closed. + if (!ensureStation()) return; + + // Compute display keys (Km as ints) + int curAltKm = (int)Math.round(cached.getOrbitalDistance() * 200.0 + 100.0); + int tgtAltKm = cached.targetOrbitalDistance * 200 + 100; + + // Update only when the visible value changes + if (curAltKm != lastAltKm) { + // "Altitude: XXXKm" + moduleGrav.setText(prefixAlt + " " + curAltKm + "Km"); + lastAltKm = curAltKm; + } + if (tgtAltKm != lastTgtKm) { + // "Target Altitude: YYY" + targetGrav.setText(prefixTgt + " " + tgtAltKm); + lastTgtKm = tgtAltKm; + } + } + + @Override public int getSizeX() { return 0; } + @Override public int getSizeY() { return 0; } + }); + + return modules; } @@ -83,62 +141,50 @@ public void onDataPacket(NetworkManager net, SPacketUpdateTileEntity pkt) { readFromNBT(pkt.getNbtCompound()); } - private void updateText() { - if (world.isRemote) { - ISpaceObject spaceObject = SpaceObjectManager.getSpaceManager().getSpaceStationFromBlockCoords(pos); - if (spaceObject != null) { - moduleGrav.setText(String.format("%s %.0fKm", LibVulpes.proxy.getLocalizedString("msg.stationaltctrl.alt"), spaceObject.getOrbitalDistance() * 200 + 100)); - //maxGravBuildSpeed.setText(String.format("%s%.1f", LibVulpes.proxy.getLocalizedString("msg.stationaltctrl.maxaltrate"), 7200D * spaceObject.getMaxRotationalAcceleration())); - targetGrav.setText(String.format("%s %d", LibVulpes.proxy.getLocalizedString("msg.stationaltctrl.tgtalt"), ((SpaceStationObject) spaceObject).targetOrbitalDistance * 200 + 100)); - } + @Override + public void update() { + if (!(world.provider instanceof WorldProviderSpace) || world.isRemote) return; + + ISpaceObject so = SpaceObjectManager.getSpaceManager().getSpaceStationFromBlockCoords(pos); + if (so == null) return; + SpaceStationObject sso = (SpaceStationObject) so; - //numThrusters.setText("Number Of Thrusters: 0"); + // Redstone → target + if (redstoneControl.getState() == RedstoneState.ON) { + sso.targetOrbitalDistance = Math.max((world.getStrongPower(pos) * 13) + 4, 190); + } else if (redstoneControl.getState() == RedstoneState.INVERTED) { + sso.targetOrbitalDistance = Math.max(Math.abs(15 - world.getStrongPower(pos)) * 13 + 4, 190); } - } - @Override - public void update() { - if (this.world.provider instanceof WorldProviderSpace) { + progress = sso.targetOrbitalDistance; - if (!world.isRemote) { - ISpaceObject spaceObject = SpaceObjectManager.getSpaceManager().getSpaceStationFromBlockCoords(pos); + // Converge with epsilon + double target = sso.targetOrbitalDistance; + double current = so.getOrbitalDistance(); + double diff = target - current; + double acc = 0.02D; + boolean changing = Math.abs(diff) >= 0.001D; - if (spaceObject != null) { - if (redstoneControl.getState() == RedstoneState.ON) - ((SpaceStationObject) spaceObject).targetOrbitalDistance = Math.max((world.getStrongPower(pos) * 13) + 4, 190); - else if (redstoneControl.getState() == RedstoneState.INVERTED) - ((SpaceStationObject) spaceObject).targetOrbitalDistance = Math.max(Math.abs(15 - world.getStrongPower(pos)) * 13 + 4, 190); - - progress = ((SpaceStationObject) spaceObject).targetOrbitalDistance; - - double targetGravity = ((SpaceStationObject) spaceObject).targetOrbitalDistance; - double angVel = spaceObject.getOrbitalDistance(); - double acc = 0.02;//0.1 * (getTotalProgress(0) - angVel + 1) / (float) getTotalProgress(0); - - double difference = targetGravity - angVel; - - if (difference != 0) { - double finalVel = angVel; - if (difference < 0) { - finalVel = angVel + Math.max(difference, -acc); - } else if (difference > 0) { - finalVel = angVel + Math.min(difference, acc); - } - - spaceObject.setOrbitalDistance((float) finalVel); - if (!world.isRemote) { - //PacketHandler.sendToNearby(new PacketStationUpdate(spaceObject, PacketStationUpdate.Type.ROTANGLE_UPDATE), this.worldObj.provider.dimensionId, this.xCoord, this.yCoord, this.zCoord, 1024); - PacketHandler.sendToAll(new PacketStationUpdate(spaceObject, PacketStationUpdate.Type.ALTITUDE_UPDATE)); - markDirty(); - } else - updateText(); - } - } - } else - updateText(); + if (changing) { + double next = current + (diff < 0 ? Math.max(diff, -acc) : Math.min(diff, acc)); + so.setOrbitalDistance((float) next); + + long wt = world.getTotalWorldTime(); + if (wt - lastAltSyncTick >= 10L) { // ~4 Hz while changing + PacketHandler.sendToAll(new PacketStationUpdate(so, PacketStationUpdate.Type.ALTITUDE_UPDATE)); + lastAltSyncTick = wt; + } + markDirty(); + } else if (wasChanging) { + // final flush on settle so clients end exactly on the last value + PacketHandler.sendToAll(new PacketStationUpdate(so, PacketStationUpdate.Type.ALTITUDE_UPDATE)); + markDirty(); } + + wasChanging = changing; } + @Override public String getModularInventoryName() { return AdvancedRocketryBlocks.blockAltitudeController.getLocalizedName(); diff --git a/src/main/java/zmaster587/advancedRocketry/tile/station/TileStationGravityController.java b/src/main/java/zmaster587/advancedRocketry/tile/station/TileStationGravityController.java index 51446c235..34d5e4bcb 100644 --- a/src/main/java/zmaster587/advancedRocketry/tile/station/TileStationGravityController.java +++ b/src/main/java/zmaster587/advancedRocketry/tile/station/TileStationGravityController.java @@ -1,6 +1,8 @@ package zmaster587.advancedRocketry.tile.station; import io.netty.buffer.ByteBuf; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.inventory.GuiContainer; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.network.NetworkManager; @@ -34,6 +36,7 @@ public class TileStationGravityController extends TileEntity implements IModular private RedstoneState state = RedstoneState.OFF; private ModuleText moduleGrav, maxGravBuildSpeed, targetGrav; private ModuleRedstoneOutputButton redstoneControl; + private long lastDimPropSyncTick = -5; public TileStationGravityController() { moduleGrav = new ModuleText(6, 15, LibVulpes.proxy.getLocalizedString("msg.stationgravctrl.alt"), 0xaa2020); @@ -54,14 +57,95 @@ public static int getMinGravity() { public List getModules(int id, EntityPlayer player) { List modules = new LinkedList<>(); modules.add(moduleGrav); - //modules.add(numThrusters); modules.add(maxGravBuildSpeed); modules.add(redstoneControl); modules.add(targetGrav); modules.add(new ModuleSlider(6, 60, 0, TextureResources.doubleWarningSideBarIndicator, this)); - updateText(); + // inline updater that runs only while GUI is open + modules.add(new ModuleBase(0, 0) { + // --- Caches (live only for GUI lifetime) --- + private SpaceStationObject cached; // strong ref during GUI life + private int cachedId = Integer.MIN_VALUE; // station id for validation + + // last *displayed* keys (so we only update text when the user can see a change) + private int lastGravKey = Integer.MIN_VALUE; // 2dp: round(grav*100) + private int lastRateKey = Integer.MIN_VALUE; // 1dp: round(rate*10) + private int lastTgtKey = Integer.MIN_VALUE; // int + + // localized prefixes + private final String prefixGrav = LibVulpes.proxy.getLocalizedString("msg.stationgravctrl.alt"); + private final String prefixMax = LibVulpes.proxy.getLocalizedString("msg.stationgravctrl.maxaltrate"); + private final String prefixTgt = LibVulpes.proxy.getLocalizedString("msg.stationgravctrl.tgtalt"); + + // tiny formatters (no String.format churn) --- + private String twoDpFromKey(int key) { // key = round(value * 100) + int abs = Math.abs(key), whole = abs / 100, frac = abs % 100; + String s = whole + "." + (frac < 10 ? "0" : "") + frac; + return key < 0 ? "-" + s : s; + } + private String oneDpFromKey(int key) { // key = round(value * 10) + int abs = Math.abs(key), whole = abs / 10, frac = abs % 10; + String s = whole + "." + frac; + return key < 0 ? "-" + s : s; + } + + // Resolve or revalidate the cached station safely. + private boolean ensureStation() { + // Resolve if no cache yet + if (cached == null) { + ISpaceObject so = SpaceObjectManager.getSpaceManager().getSpaceStationFromBlockCoords(pos); + if (!(so instanceof SpaceStationObject)) return false; + cached = (SpaceStationObject) so; + cachedId = so.getId(); + return true; + } + // Revalidate in case manager swapped instances + ISpaceObject current = SpaceObjectManager.getSpaceManager().getSpaceStationFromBlockCoords(pos); + if (!(current instanceof SpaceStationObject)) { cached = null; cachedId = Integer.MIN_VALUE; return false; } + if (current.getId() != cachedId) { // instance swapped or different station under pos + cached = (SpaceStationObject) current; + cachedId = current.getId(); + } + return true; + } + + @Override + public void renderBackground(GuiContainer gui, int x, int y, int mouseX, int mouseY, FontRenderer font) { + // Only runs while GUI is visible → zero idle cost when closed. + if (!ensureStation()) return; + + // Pull current (client-synced) values + float grav = cached.getProperties().getGravitationalMultiplier(); // e.g. 0.57 + double maxRate = 7200D * cached.getMaxRotationalAcceleration(); // e.g. 144.0 + int tgt = cached.targetGravity; // 10..100 + + // Compute compare keys at display precision + int gravKey = Math.round(grav * 100f); // 2dp + int rateKey = (int)Math.round(maxRate * 10d); // 1dp + int tgtKey = tgt; // int + + // Only touch ModuleText when the visible value actually changes + if (gravKey != lastGravKey) { + moduleGrav.setText(prefixGrav + twoDpFromKey(gravKey)); + lastGravKey = gravKey; + } + if (rateKey != lastRateKey) { + maxGravBuildSpeed.setText(prefixMax + oneDpFromKey(rateKey)); + lastRateKey = rateKey; + } + if (tgtKey != lastTgtKey) { + targetGrav.setText(prefixTgt + tgtKey); + lastTgtKey = tgtKey; + } + } + + @Override public int getSizeX() { return 0; } // no visual footprint + @Override public int getSizeY() { return 0; } + }); + + return modules; } @@ -103,49 +187,46 @@ private void updateText() { @Override public void update() { + if (!(this.world.provider instanceof WorldProviderSpace)) return; - if (this.world.provider instanceof WorldProviderSpace) { + if (!world.isRemote) { + ISpaceObject spaceObject = SpaceObjectManager.getSpaceManager().getSpaceStationFromBlockCoords(pos); + if (spaceObject == null) return; - if (!world.isRemote) { - ISpaceObject spaceObject = SpaceObjectManager.getSpaceManager().getSpaceStationFromBlockCoords(pos); + if (redstoneControl.getState() == RedstoneState.ON) { + ((SpaceStationObject) spaceObject).targetGravity = (world.getStrongPower(pos) * 6) + 10; + } else if (redstoneControl.getState() == RedstoneState.INVERTED) { + ((SpaceStationObject) spaceObject).targetGravity = Math.abs(15 - world.getStrongPower(pos)) * 6 + 10; + } - if (spaceObject != null) { - if (redstoneControl.getState() == RedstoneState.ON) - ((SpaceStationObject) spaceObject).targetGravity = (world.getStrongPower(pos) * 6) + 10; - else if (redstoneControl.getState() == RedstoneState.INVERTED) - ((SpaceStationObject) spaceObject).targetGravity = Math.abs(15 - world.getStrongPower(pos)) * 6 + 10; - - progress = ((SpaceStationObject) spaceObject).targetGravity - minGravity; - - int targetMultiplier = (ARConfiguration.getCurrentConfig().allowZeroGSpacestations) ? ((SpaceStationObject) spaceObject).targetGravity : Math.max(10, ((SpaceStationObject) spaceObject).targetGravity); - double targetGravity = targetMultiplier / 100D; - double angVel = spaceObject.getProperties().getGravitationalMultiplier(); - double acc = 0.001; - - double difference = targetGravity - angVel; - - if (Math.abs(difference) >= 0.001) { - double finalVel = angVel; - if (difference < 0) { - finalVel = angVel + Math.max(difference, -acc); - } else if (difference > 0) { - finalVel = angVel + Math.min(difference, acc); - } - - spaceObject.getProperties().setGravitationalMultiplier((float) finalVel); - if (!world.isRemote) { - //PacketHandler.sendToNearby(new PacketStationUpdate(spaceObject, PacketStationUpdate.Type.ROTANGLE_UPDATE), this.worldObj.provider.dimensionId, this.xCoord, this.yCoord, this.zCoord, 1024); - PacketHandler.sendToAll(new PacketStationUpdate(spaceObject, PacketStationUpdate.Type.DIM_PROPERTY_UPDATE)); - markDirty(); - } else - updateText(); - } + progress = ((SpaceStationObject) spaceObject).targetGravity - minGravity; + + int targetMultiplier = ARConfiguration.getCurrentConfig().allowZeroGSpacestations + ? ((SpaceStationObject) spaceObject).targetGravity + : Math.max(10, ((SpaceStationObject) spaceObject).targetGravity); + + double targetGravity = targetMultiplier / 100D; + double angVel = spaceObject.getProperties().getGravitationalMultiplier(); + double acc = 0.001; + + double difference = targetGravity - angVel; + if (Math.abs(difference) >= 0.001) { + double finalVel = angVel + (difference < 0 ? Math.max(difference, -acc) : Math.min(difference, acc)); + spaceObject.getProperties().setGravitationalMultiplier((float) finalVel); + + long wt = world.getTotalWorldTime(); + + if ((wt - lastDimPropSyncTick) >= 5) { // every 5 ticks ≈ 4 Hz + PacketHandler.sendToAll(new PacketStationUpdate(spaceObject, PacketStationUpdate.Type.DIM_PROPERTY_UPDATE)); + lastDimPropSyncTick = wt; } - } else - updateText(); + + markDirty(); + } } } + @Override public String getModularInventoryName() { return AdvancedRocketryBlocks.blockGravityController.getLocalizedName(); diff --git a/src/main/java/zmaster587/advancedRocketry/tile/station/TileStationOrientationController.java b/src/main/java/zmaster587/advancedRocketry/tile/station/TileStationOrientationController.java index d3ed36055..11eaa29ed 100644 --- a/src/main/java/zmaster587/advancedRocketry/tile/station/TileStationOrientationController.java +++ b/src/main/java/zmaster587/advancedRocketry/tile/station/TileStationOrientationController.java @@ -1,6 +1,8 @@ package zmaster587.advancedRocketry.tile.station; import io.netty.buffer.ByteBuf; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.inventory.GuiContainer; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.tileentity.TileEntity; @@ -26,6 +28,7 @@ public class TileStationOrientationController extends TileEntity implements ITickable, IModularInventory, INetworkMachine, ISliderBar, IButtonInventory { private int[] progress; + private long lastRotSyncTick = -5; private ModuleText moduleAngularVelocity, numThrusters, maxAngularAcceleration, targetRotations; @@ -56,70 +59,111 @@ public List getModules(int id, EntityPlayer player) { modules.add(new ModuleButton(25, 35, 2, LibVulpes.proxy.getLocalizedString("msg.spacelaser.reset"), this, zmaster587.libVulpes.inventory.TextureResources.buttonBuild, 36, 15)); //modules.add(new ModuleSlider(24, 35, 2, TextureResources.doubleWarningSideBarIndicator, (ISliderBar)this)); - updateText(); - return modules; - } + // inline updater that runs only while GUI is open + modules.add(new ModuleBase(0, 0) { + private SpaceStationObject cached; + private int cachedId = Integer.MIN_VALUE; - private void updateText() { - if (world.isRemote) { - ISpaceObject spaceObject = SpaceObjectManager.getSpaceManager().getSpaceStationFromBlockCoords(pos); - if (spaceObject != null) { - moduleAngularVelocity.setText(String.format("%s%.1f %.1f %.1f", LibVulpes.proxy.getLocalizedString("msg.stationorientctrl.alt"), 72000D * spaceObject.getDeltaRotation(EnumFacing.EAST), 72000D * spaceObject.getDeltaRotation(EnumFacing.UP), 7200D * spaceObject.getDeltaRotation(EnumFacing.NORTH))); - //maxAngularAcceleration.setText(String.format("Maximum Angular Acceleration: %.1f", 7200D*object.getMaxRotationalAcceleration())); + private int lastVelX = Integer.MIN_VALUE, lastVelY = Integer.MIN_VALUE, lastVelZ = Integer.MIN_VALUE; + private int lastTgtX = Integer.MIN_VALUE, lastTgtY = Integer.MIN_VALUE, lastTgtZ = Integer.MIN_VALUE; - //numThrusters.setText("Number Of Thrusters: 0"); - int[] targetRotationsPerHour = ((SpaceStationObject) spaceObject).targetRotationsPerHour; - targetRotations.setText(String.format("%s%d %d %d", LibVulpes.proxy.getLocalizedString("msg.stationorientctrl.tgtalt"), targetRotationsPerHour[0], targetRotationsPerHour[1], targetRotationsPerHour[2])); + private final String prefixVel = LibVulpes.proxy.getLocalizedString("msg.stationorientctrl.alt"); + private final String prefixTgt = LibVulpes.proxy.getLocalizedString("msg.stationorientctrl.tgtalt"); + + private String oneDp(int key) { + int abs = Math.abs(key), whole = abs / 10, frac = abs % 10; + String s = whole + "." + frac; + return key < 0 ? "-" + s : s; } - } + + private boolean ensureStation() { + if (cached == null) { + ISpaceObject so = SpaceObjectManager.getSpaceManager().getSpaceStationFromBlockCoords(pos); + if (!(so instanceof SpaceStationObject)) return false; + cached = (SpaceStationObject) so; + cachedId = so.getId(); + return true; + } + ISpaceObject current = SpaceObjectManager.getSpaceManager().getSpaceStationFromBlockCoords(pos); + if (!(current instanceof SpaceStationObject)) { cached = null; cachedId = Integer.MIN_VALUE; return false; } + if (current.getId() != cachedId) { + cached = (SpaceStationObject) current; + cachedId = current.getId(); + } + return true; + } + + @Override + public void renderBackground(GuiContainer gui, int x, int y, int mouseX, int mouseY, FontRenderer font) { + if (!ensureStation()) return; + + double dX = cached.getDeltaRotation(EnumFacing.EAST); + double dY = cached.getDeltaRotation(EnumFacing.UP); + double dZ = cached.getDeltaRotation(EnumFacing.NORTH); + int[] tgt = cached.targetRotationsPerHour; + + int velX = (int)Math.round(72000D * dX * 10D); + int velY = (int)Math.round(72000D * dY * 10D); + int velZ = (int)Math.round( 7200D * dZ * 10D); + + if (velX != lastVelX || velY != lastVelY || velZ != lastVelZ) { + moduleAngularVelocity.setText(prefixVel + oneDp(velX) + " " + oneDp(velY) + " " + oneDp(velZ)); + lastVelX = velX; lastVelY = velY; lastVelZ = velZ; + } + + if (tgt[0] != lastTgtX || tgt[1] != lastTgtY || tgt[2] != lastTgtZ) { + targetRotations.setText(prefixTgt + tgt[0] + " " + tgt[1] + " " + tgt[2]); + lastTgtX = tgt[0]; lastTgtY = tgt[1]; lastTgtZ = tgt[2]; + } + } + + @Override public int getSizeX() { return 0; } + @Override public int getSizeY() { return 0; } + }); + + + return modules; } @Override public void update() { + // Only relevant in space + if (!(world.provider instanceof WorldProviderSpace)) return; + // Server-side only + if (world.isRemote) return; + + ISpaceObject spaceObject = SpaceObjectManager.getSpaceManager().getSpaceStationFromBlockCoords(pos); + if (spaceObject == null) return; + + EnumFacing[] dirs = { EnumFacing.EAST, EnumFacing.UP, EnumFacing.NORTH }; + int[] targetRotationsPerHour = ((SpaceStationObject) spaceObject).targetRotationsPerHour; + + // keep sliders in sync with server state + for (int i = 0; i < 3; i++) { + setProgress(i, targetRotationsPerHour[i] + (getTotalProgress(i) / 2)); + } - if (this.world.provider instanceof WorldProviderSpace) { - if (!world.isRemote) { - ISpaceObject spaceObject = SpaceObjectManager.getSpaceManager().getSpaceStationFromBlockCoords(pos); - boolean update = false; - - if (spaceObject != null) { - - EnumFacing[] dirs = {EnumFacing.EAST, EnumFacing.UP, EnumFacing.NORTH}; - int[] targetRotationsPerHour = ((SpaceStationObject) spaceObject).targetRotationsPerHour; - for (int i = 0; i < 3; i++) { - setProgress(i, targetRotationsPerHour[i] + (getTotalProgress(i) / 2)); - } - - - for (int i = 0; i < 3; i++) { - - double targetAngularVelocity = targetRotationsPerHour[i] / 72000D; - double angVel = spaceObject.getDeltaRotation(dirs[i]); - double acc = spaceObject.getMaxRotationalAcceleration(); - - double difference = targetAngularVelocity - angVel; - - if (difference != 0) { - double finalVel = angVel; - if (difference < 0) { - finalVel = angVel + Math.max(difference, -acc); - } else if (difference > 0) { - finalVel = angVel + Math.min(difference, acc); - } - - spaceObject.setDeltaRotation(finalVel, dirs[i]); - update = true; - } - } - - if (!world.isRemote && update) { - //PacketHandler.sendToNearby(new PacketStationUpdate(spaceObject, PacketStationUpdate.Type.ROTANGLE_UPDATE), this.worldObj.provider.dimensionId, this.xCoord, this.yCoord, this.zCoord, 1024); - PacketHandler.sendToAll(new PacketStationUpdate(spaceObject, PacketStationUpdate.Type.ROTANGLE_UPDATE)); - } - } else - updateText(); - } else - updateText(); + boolean updated = false; + + for (int i = 0; i < 3; i++) { + double targetAngularVelocity = targetRotationsPerHour[i] / 72000D; + double angVel = spaceObject.getDeltaRotation(dirs[i]); + double acc = spaceObject.getMaxRotationalAcceleration(); + + double difference = targetAngularVelocity - angVel; + if (difference != 0) { + double finalVel = angVel + (difference < 0 ? Math.max(difference, -acc) : Math.min(difference, acc)); + spaceObject.setDeltaRotation(finalVel, dirs[i]); + updated = true; + } + } + + if (updated) { + long t = world.getTotalWorldTime(); + if (t - lastRotSyncTick >= 5) { // ~4 Hz + PacketHandler.sendToAll(new PacketStationUpdate(spaceObject, PacketStationUpdate.Type.ROTANGLE_UPDATE)); + lastRotSyncTick = t; + } } } diff --git a/src/main/java/zmaster587/advancedRocketry/util/StorageChunk.java b/src/main/java/zmaster587/advancedRocketry/util/StorageChunk.java index 48dd09f56..bce2ccd3f 100644 --- a/src/main/java/zmaster587/advancedRocketry/util/StorageChunk.java +++ b/src/main/java/zmaster587/advancedRocketry/util/StorageChunk.java @@ -168,11 +168,12 @@ public void recalculateStats(StatsRocket stats) { int fuelCapacityBipropellant = 0; int fuelCapacityOxidizer = 0; int fuelCapacityNuclearWorkingFluid = 0; - + int intakePower = 0; float drillPower = 0f; //stats.reset_no_fuel(); stats.reset_no_fuel();// Oh Quarter... you can not keep adding engine and seat locations every launch - + final boolean isSD = (this.entity instanceof zmaster587.advancedRocketry.entity.EntityStationDeployedRocket); + float weight = 0; for (int yCurr = 0; yCurr <= this.sizeY; yCurr++) { @@ -193,18 +194,33 @@ public void recalculateStats(StatsRocket stats) { } //If rocketEngine increaseThrust - if (block instanceof IRocketEngine && (world.getBlockState(belowPos).getBlock().isAir(world.getBlockState(belowPos), world, belowPos) || world.getBlockState(belowPos).getBlock() instanceof BlockLandingPad || world.getBlockState(belowPos).getBlock() == AdvancedRocketryBlocks.blockLaunchpad)) { - if (block instanceof BlockNuclearRocketMotor) { - nuclearWorkingFluidUseMax += ((IRocketEngine) block).getFuelConsumptionRate(world, xCurr, yCurr, zCurr); - thrustNuclearNozzleLimit += ((IRocketEngine) block).getThrust(world, currBlockPos); - } else if (block instanceof BlockBipropellantRocketMotor) { - bipropellantfuelUse += ((IRocketEngine) block).getFuelConsumptionRate(world, xCurr, yCurr, zCurr); - thrustBipropellant += ((IRocketEngine) block).getThrust(world, currBlockPos); - } else if (block instanceof BlockRocketMotor) { - monopropellantfuelUse += ((IRocketEngine) block).getFuelConsumptionRate(world, xCurr, yCurr, zCurr); - thrustMonopropellant += ((IRocketEngine) block).getThrust(world, currBlockPos); + if (block instanceof IRocketEngine) { + boolean eligible; + if (isSD) { + // SD rockets: skip vertical requirements + eligible = true; + } else { + // Legacy vertical rule + IBlockState belowState = world.getBlockState(belowPos); + Block below = belowState.getBlock(); + eligible = below.isAir(belowState, world, belowPos) + || below instanceof BlockLandingPad + || below == AdvancedRocketryBlocks.blockLaunchpad; + } + + if (eligible) { + if (block instanceof BlockNuclearRocketMotor) { + nuclearWorkingFluidUseMax += ((IRocketEngine) block).getFuelConsumptionRate(world, xCurr, yCurr, zCurr); + thrustNuclearNozzleLimit += ((IRocketEngine) block).getThrust(world, currBlockPos); + } else if (block instanceof BlockBipropellantRocketMotor) { + bipropellantfuelUse += ((IRocketEngine) block).getFuelConsumptionRate(world, xCurr, yCurr, zCurr); + thrustBipropellant += ((IRocketEngine) block).getThrust(world, currBlockPos); + } else if (block instanceof BlockRocketMotor) { + monopropellantfuelUse += ((IRocketEngine) block).getFuelConsumptionRate(world, xCurr, yCurr, zCurr); + thrustMonopropellant += ((IRocketEngine) block).getThrust(world, currBlockPos); + } + stats.addEngineLocation(xCurr - (float)this.sizeX/2 + 0.5f, yCurr+0.5f, zCurr - (float)this.sizeZ/2 + 0.5f); } - stats.addEngineLocation(xCurr - (float) this.sizeX /2+0.5f, yCurr+0.5f, zCurr- (float) this.sizeZ /2+0.5f); } if (block instanceof IFuelTank) { @@ -219,8 +235,18 @@ public void recalculateStats(StatsRocket stats) { } } - if (block instanceof IRocketNuclearCore && ((world.getBlockState(belowPos).getBlock() instanceof IRocketNuclearCore) || (world.getBlockState(belowPos).getBlock() instanceof IRocketEngine))) { - thrustNuclearReactorLimit += ((IRocketNuclearCore) block).getMaxThrust(world, currBlockPos); + if (block instanceof IRocketNuclearCore) { + boolean counts; + if (isSD) { + // SD rockets: no vertical stack requirement + counts = true; + } else { + Block below = world.getBlockState(belowPos).getBlock(); + counts = (below instanceof IRocketNuclearCore) || (below instanceof IRocketEngine); + } + if (counts) { + thrustNuclearReactorLimit += ((IRocketNuclearCore) block).getMaxThrust(world, currBlockPos); + } } if (block instanceof BlockSeat && world.getBlockState(abovePos).getBlock().isPassable(world, abovePos)) { @@ -230,24 +256,14 @@ public void recalculateStats(StatsRocket stats) { if (block instanceof IMiningDrill) { drillPower += ((IMiningDrill) block).getMiningSpeed(world, currBlockPos); } + if (block instanceof IIntake) { + intakePower += ((IIntake) block).getIntakeAmt(state); + } if (block.getUnlocalizedName().contains("servicemonitor")) { hasServiceMonitor = true; } - - TileEntity tile = world.getTileEntity(currBlockPos); - if (tile instanceof TileSatelliteHatch) { - if (ARConfiguration.getCurrentConfig().advancedWeightSystem) { - TileSatelliteHatch hatch = (TileSatelliteHatch) tile; - if (hatch.getSatellite() != null) { - weight += hatch.getSatellite().getProperties().getWeight(); - } else if (hatch.getStackInSlot(0).getItem() instanceof ItemPackedStructure) { - ItemPackedStructure struct = (ItemPackedStructure) hatch.getStackInSlot(0).getItem(); - weight += struct.getStructure(hatch.getStackInSlot(0)).getWeight(); - } - } - } - } + } } } } @@ -272,10 +288,42 @@ public void recalculateStats(StatsRocket stats) { stats.setFuelCapacity(FuelRegistry.FuelType.LIQUID_OXIDIZER, fuelCapacityOxidizer); stats.setFuelCapacity(FuelRegistry.FuelType.NUCLEAR_WORKING_FLUID, fuelCapacityNuclearWorkingFluid); - //Non-fuel stats + // SAFE liquid capacity sum (saturating at Integer.MAX_VALUE) + long liquidCapacitySum = 0L; + + outer: + for (TileEntity te : this.getFluidTiles()) { + net.minecraftforge.fluids.capability.IFluidHandler fh = + te.getCapability(net.minecraftforge.fluids.capability.CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, null); + if (fh == null) continue; + + net.minecraftforge.fluids.capability.IFluidTankProperties[] props = fh.getTankProperties(); + if (props == null) continue; + + for (net.minecraftforge.fluids.capability.IFluidTankProperties p : props) { + if (p == null) continue; + long cap = Math.max(0L, (long) p.getCapacity()); // guard negatives + if (cap == 0L) continue; + + long next = liquidCapacitySum + cap; // saturating add + if (next >= (long) Integer.MAX_VALUE) { + liquidCapacitySum = (long) Integer.MAX_VALUE; + break outer; // early exit once saturated + } + liquidCapacitySum = next; + } + } + + int liquidCapacitySafe = (int) Math.max(0L, Math.min(liquidCapacitySum, (long) Integer.MAX_VALUE)); + stats.setStatTag("liquidCapacity", liquidCapacitySafe); + + + //Non-fuel stats (keep these after the capacity/tag work) stats.setWeight(weight); stats.setThrust(Math.max(Math.max(thrustMonopropellant, thrustBipropellant), thrustNuclearTotalLimit)); stats.setDrillingPower(drillPower); + stats.setStatTag("intakePower", intakePower); + // (liquidCapacity already set above) } public void addTileEntity(TileEntity te) { diff --git a/src/main/java/zmaster587/advancedRocketry/world/WorldServerNotMulti.java b/src/main/java/zmaster587/advancedRocketry/world/WorldServerNotMulti.java index cf3f46408..3614f1737 100644 --- a/src/main/java/zmaster587/advancedRocketry/world/WorldServerNotMulti.java +++ b/src/main/java/zmaster587/advancedRocketry/world/WorldServerNotMulti.java @@ -56,7 +56,9 @@ protected void saveLevel() throws MinecraftException { } public World init() { - super.init(); + // Removed super.init(): it recreates per-world managers (loot/adv/scoreboard/functions) + // will be reloaded per dimension, slow and breaks custom data. + // load weather data from NBT WorldInfoSavedData wi = (WorldInfoSavedData) perWorldStorage.getOrLoadData(WorldInfoSavedData.class, "WorldInfoSavedData"); if (wi == null) { diff --git a/src/main/java/zmaster587/advancedRocketry/world/util/MultiData.java b/src/main/java/zmaster587/advancedRocketry/world/util/MultiData.java index f523f032d..27b4178d5 100644 --- a/src/main/java/zmaster587/advancedRocketry/world/util/MultiData.java +++ b/src/main/java/zmaster587/advancedRocketry/world/util/MultiData.java @@ -19,6 +19,14 @@ public MultiData() { reset(); } + private static final java.util.EnumSet SUPPORTED_TYPES = + java.util.EnumSet.of( + DataStorage.DataType.COMPOSITION, + DataStorage.DataType.MASS, + DataStorage.DataType.DISTANCE + ); + + public void reset() { for (DataStorage.DataType type : DataStorage.DataType.values()) { if (type != DataStorage.DataType.UNDEFINED) @@ -91,4 +99,4 @@ public void readFromNBT(NBTTagCompound nbt) { } } } -} \ No newline at end of file +} diff --git a/src/main/resources/assets/advancedrocketry/blockstates/wirelesstransciever.json b/src/main/resources/assets/advancedrocketry/blockstates/wirelesstransciever.json index 72b127360..b2c7287f9 100644 --- a/src/main/resources/assets/advancedrocketry/blockstates/wirelesstransciever.json +++ b/src/main/resources/assets/advancedrocketry/blockstates/wirelesstransciever.json @@ -1,13 +1,19 @@ { - "variants": { - "facing=north,state=false": { "model": "advancedrocketry:wirelesstransciever" }, - "facing=south,state=false": { "model": "advancedrocketry:wirelesstransciever", "y": 180 }, - "facing=west,state=false": { "model": "advancedrocketry:wirelesstransciever", "y": 270 }, - "facing=east,state=false": { "model": "advancedrocketry:wirelesstransciever", "y": 90 }, - "facing=north,state=true": { "model": "advancedrocketry:wirelesstransciever" }, - "facing=south,state=true": { "model": "advancedrocketry:wirelesstransciever", "y": 180 }, - "facing=west,state=true": { "model": "advancedrocketry:wirelesstransciever", "y": 270 }, - "facing=east,state=true": { "model": "advancedrocketry:wirelesstransciever", "y": 90 }, - "inventory" : { "model": "advancedrocketry:wirelesstransciever" } - } + "variants": { + "facing=north,state=false": { "model": "advancedrocketry:wirelesstransciever" }, + "facing=south,state=false": { "model": "advancedrocketry:wirelesstransciever", "y": 180 }, + "facing=west,state=false": { "model": "advancedrocketry:wirelesstransciever", "y": 270 }, + "facing=east,state=false": { "model": "advancedrocketry:wirelesstransciever", "y": 90 }, + "facing=up,state=false": { "model": "advancedrocketry:wirelesstransciever", "x": 270 }, + "facing=down,state=false": { "model": "advancedrocketry:wirelesstransciever", "x": 90 }, + + "facing=north,state=true": { "model": "advancedrocketry:wirelesstransciever" }, + "facing=south,state=true": { "model": "advancedrocketry:wirelesstransciever", "y": 180 }, + "facing=west,state=true": { "model": "advancedrocketry:wirelesstransciever", "y": 270 }, + "facing=east,state=true": { "model": "advancedrocketry:wirelesstransciever", "y": 90 }, + "facing=up,state=true": { "model": "advancedrocketry:wirelesstransciever", "x": 270 }, + "facing=down,state=true": { "model": "advancedrocketry:wirelesstransciever", "x": 90 }, + + "inventory": { "model": "advancedrocketry:wirelesstransciever" } + } } diff --git a/src/main/resources/assets/advancedrocketry/lang/de_DE.lang b/src/main/resources/assets/advancedrocketry/lang/de_DE.lang index c0dc83148..4a6d75c7c 100644 --- a/src/main/resources/assets/advancedrocketry/lang/de_DE.lang +++ b/src/main/resources/assets/advancedrocketry/lang/de_DE.lang @@ -345,6 +345,7 @@ msg.rocketbuilder.fuel=Treibstoff msg.rocketbuilder.acc=Beschleunigung msg.rocketbuilder.build=Bauen msg.rocketbuilder.scan=Scannen +msg.rocketbuilder.alreadyassembled=Rakete bereits zusammengebaut msg.solar.collectingEnergy= Energie sammeln: msg.solar.cannotcollectEnergy=Kann keine Energie sammeln msg.asteroidChip.asteroid=Asteroid @@ -406,4 +407,10 @@ msg.notconnected=Nicht verbunden msg.unprogrammed=Unprogrammiert msg.programfail=Programmierung fehlgeschlagen msg.modules=Module -msg.na=Nicht verfügbar \ No newline at end of file +msg.na=Nicht verfügbar + +jei.sb.satellitepreview=Bereit für den Orbit! +jei.sb.copy.source=Quelle +jei.sb.copy.output=Neue Kopie +jei.sb.assemblyhint=Mindestens ein Solarpanel +jei.sb.copychiphint=Erstelle Sicherungskopien! diff --git a/src/main/resources/assets/advancedrocketry/lang/en_US.lang b/src/main/resources/assets/advancedrocketry/lang/en_US.lang index fc1e8d6b3..59217f53f 100644 --- a/src/main/resources/assets/advancedrocketry/lang/en_US.lang +++ b/src/main/resources/assets/advancedrocketry/lang/en_US.lang @@ -11,6 +11,7 @@ death.attack.Heat=%1$s died due to overheating death.attack.Heat.player=%1$s died due to overheating entity.advancedRocketry.rocket.name=Rocket entity.rocket.name=Rocket +entity.deployedRocket.name=Rocket entity.hovercraft.name=Hovercraft tile.landingPad.name=Landing Pad @@ -25,7 +26,6 @@ tile.turf.name=Moon Turf tile.turfDark.name=Dark Moon Turf tile.cuttingMachine.name=Cutting Machine tile.sawBlade.name=Saw Blade Assembly -tile.controlComp.name=Mission Control Computer tile.precisionAssemblingMachine.name=Precision Assembler tile.spaceLaser.name=Orbital Laser Drill tile.Crystallizer.name=Crystallizer @@ -136,7 +136,7 @@ tile.precisionlaseretcher.name=Precision Laser Etcher tile.enrichedLavaBlock.name=Enriched Lava Block tile.basalt.name=Basalt tile.landingfloat.name=Landing Float -tile.solararray.name=Solar Array +tile.solararray.name=Solar Array Controller tile.solararraypanel.name=Solar Array Panel tile.serviceStation.name=Service Station @@ -187,12 +187,12 @@ item.itemUpgrade.4.name=Anti-Fog Visor item.itemUpgrade.5.name=Earthbright Visor item.atmAnalyser.name=Atmosphere Analyzer item.biomeChanger.name=Biome Changer Remote -item.weatherController.name=Weather Satellite Remote +item.weatherController.name=Weather Remote item.basicLaserGun.name=Basic Laser Gun item.beaconFinder.name=Beacon Finder item.thermite.name=Thermite item.hovercraft.name=Hovercraft -item.hovercraft.tooltip=Long lasting dilithium power source. It'll probably outlive you. +item.hovercraft.tooltip=Long lasting dilithium Power source. It'll probably outlive you. item.jetPack.name=Suit Jetpack item.pressureTank.0.name=Low Pressure Tank @@ -227,9 +227,13 @@ fluid.enrichedLava=Enriched Lava mission.asteroidmining.name=Asteroid Mining mission.gascollection.name=Gas Collection -error.rocket.cannotGetThere=Destination Unreachable -error.rocket.destinationNotExist=Cannot launch: Destination does not exist -error.rocket.notSameSystem=Cannot launch: Destination is not in the same planet system +error.rocket.notEnoughMissionFuel=Not enough fuel! +error.rocket.tooHeavy=Rocket is too heavy to launch (insufficient thrust). +error.rocket.cannotGetThere=Selected destination cannot be reached from here. +error.rocket.destinationNotExist=Selected space station does not exist. +error.rocket.notSameSystem=Interstellar travel requires a nuclear rocket and a valid route. +error.rocket.partsWornOut=Critical parts are worn out — launch aborted. +error.rocket.aborted=Launch aborted. advancement.holographic=Holographic advancement.holographic.desc=Craft a Holo-Projector @@ -287,6 +291,8 @@ msg.observetory.text.composition=Composition msg.observetory.text.processdiscovery=Process discovery msg.observetory.text.observabledistance=Observable distance: msg.observetory.text.missionTime=Mission Time: +msg.observetory.req.open=Observatory must be open (night, clear sky, sky access) or be in space! +msg.observetory.print.already=You already have a chip for this asteroid! msg.tooltip.data=Data msg.tooltip.asteroidselection=Asteroid Selection msg.label.name=Name @@ -310,9 +316,31 @@ msg.spaceElevator.warning.anchored1=the station anchored! msg.spaceElevator.warning.unanchored=This elevator has no tether msg.spaceElevator.turnedOff=Elevator is turned off msg.fuelingStation.link=You program the linker with the fueling station at + +msg.monitoringStation.buttonLaunch=Launch! msg.monitoringStation.missionProgressNA=Mission Progress: N/A +msg.monitoringStation.missionNoActiveMission=No Active Missions.. +msg.monitoringStation.mission.type.gas=Gas Collection Mission +msg.monitoringStation.mission.type.ore=Asteroid Mining Mission +msg.monitoringStation.mission.target.default=Harvest: (pending) +msg.monitoringStation.mission.targetPrefix=Harvest: +msg.monitoringStation.mission.Asteroid.target.default=Asteroid: +msg.monitoringStation.mission.Asteroid.targetPrefix=Asteroid: +msg.monitoringStation.mission.asteroidIdPrefix=Type: +msg.monitoringStation.mission.plannedAmountPrefix=Amount: +msg.monitoringStation.mission.plannedAmountPending=Amount: (pending) +msg.monitoringStation.mission.asteroidType=Asteroid type: (shown on chip / mission) msg.monitoringStation.link=You program the linker with the monitoring station at -msg.monitoringStation.progress= Progress: +msg.monitoringStation.progress=ETA: +msg.monitoringStation.prelaunch=Initiating... +msg.monitoringStation.launching=Launching! +msg.monitoringStation.orbit=Reached orbit! +msg.monitoringStation.deorbiting=Returned from orbit! +msg.monitoringStation.landed=Landed +msg.monitoringStation.aborted=Aborted! +msg.monitoringStation.returningToDock=Returning to dock +msg.monitoringStation.noLinkedRocket=Not Linked to any Rocket! + msg.guidanceComputerHatch.loadingState=Loading State: msg.guidanceComputerHatch.ejectonlanding=Auto Eject Upon Landing msg.guidanceComputerHatch.ejectonsatlanding=Allow Ejection of Satellite Chips @@ -331,7 +359,7 @@ msg.rocketLoader.allowredstoneinput=Allow redstone input msg.rocketLoader.allowredstoneoutput=Allow redstone output msg.rocketLoader.none=None msg.rocketLoader.link=You program the linker with the rocket loader at: -msg.microwaverec.notgenerating=Generating 0 FE/t +msg.microwaverec.notgenerating=Generating 0 RF/t msg.microwaverec.generating=Generating msg.abdp.compositionresearch=Composition Research msg.abdp.distanceresearch=Distance Research @@ -359,7 +387,8 @@ msg.satctrlcenter.toofar=Too Far msg.satctrlcenter.nolink=No Link... msg.satctrlcenter.info=Info: msg.satctrlcenter.destroysat=Destroy Satellite -msg.satctrlcenter.connect=Connect! +msg.satctrlcenter.connect=Download +msg.satctrlcenter.autodl_hint=Automatic with Wireless Tranciever (Extract) msg.satbuilder.writesecondchip=Write to Secondary Chip msg.dockingport.target=Target Id msg.dockingport.me=My Id @@ -369,7 +398,7 @@ msg.stationaltctrl.tgtalt=Target Altitude: msg.stationaltctrl.alt=Altitude: msg.stationgravctrl.maxaltrate=Max Gravity Change Rate: msg.stationgravctrl.tgtalt=Target Gravity: -msg.stationgravctrl.alt=Artifical Gravity: +msg.stationgravctrl.alt=Artificial Gravity: msg.stationorientctrl.alt=Angular Velocity: msg.stationorientctrl.tgtalt=Target Ang Vel: msg.warpmon.tab.warp=Warp Selection @@ -394,10 +423,12 @@ msg.warpmon.artifact=Artifacts msg.rocketbuilder.success=Clear for liftoff! msg.rocketbuilder.nofuel=Not enough fuel capacity! msg.rocketbuilder.noseat=Missing seat or satellite bay! -msg.rocketbuilder.noengines=You do not have enough thrust! +msg.rocketbuilder.noengines=Not enough thrust! msg.rocketbuilder.noguidance=Missing Guidance Computer msg.rocketbuilder.unscanned=Rocket unscanned +msg.rocketbuilder.unscanned_station=Standing by for scan msg.rocketbuilder.success_station=Ready! +msg.rocketbuilder.fail_cut=Build failed: area changed msg.rocketbuilder.empty=Nothing here msg.rocketbuilder.finished=Build Complete! msg.rocketbuild.invalidblock=Invalid block! @@ -412,9 +443,13 @@ msg.rocketbuilder.acc=Acc msg.rocketbuilder.build=Build msg.rocketbuilder.scan=Scan msg.rocketbuild.combinedthrust=Fuel types cannot be combined! +msg.rocketbuilder.alreadyassembled=Rocket already assembled +msg.rocketbuilder.nointake=Missing Gas Intake! +msg.rocketbuilder.notank=Missing Fluidtank! msg.solar.collectingEnergy=Collecting Energy: msg.solar.cannotcollectEnergy=Unable to collect Energy msg.asteroidChip.asteroid=Asteroid +msg.asteroidChip.type=Type: msg.atmanal.atmtype=Atmosphere Type: msg.atmanal.canbreathe=Breathable: msg.biomechanger.scan=Scan Biome @@ -448,8 +483,10 @@ msg.itemsatellite.microwavestatus=Collecting Power msg.itemsatellite.data=Data Storage: msg.itemsatellite.nodata=No Data Storage! msg.itemsatellite.empty=Empty Chassis -msg.itemsatellite.weight=Chassis weight: +msg.itemsatellite.weight=Chassis weight: msg.itemsatellite.noweight=Error in weight calculation +msg.itemsatellite.unassembled=Not assembled (preview) + msg.brokenstage.text=Destruction stage @@ -473,18 +510,22 @@ msg.entity.rocket.launch=Launch in T- msg.entity.rocket.launch2=Press [Space] to abort msg.entity.rocket.station=Station msg.entity.rocket.pad=Pad: -msg.entity.rocket.disass=Dissassemble +msg.entity.rocket.disass=Disassemble msg.entity.rocket.seldst=Select Dst msg.entity.rocket.clear=Clear msg.entity.rocket.rcs=RCS Mode msg.entity.rocket.none=None Selected msg.wirelessTransciever.extract=extract +msg.wirelessTransciever.insert=insert +msg.wirelessTransciever.type=Type: %s +msg.wirelessTransciever.network=Network: +msg.wirelessTransciever.network.unlinked=Unlinked -msg.powerunit.rfpertick=FE/t +msg.powerunit.rfpertick=RF/t msg.linker.error.firstMachine=This must be the first machine to link! msg.linker.program=Coordinates programmed into Linker msg.linker.success=Linked Sucessfully -msg.notenoughpower=Not Enough power! +msg.notenoughpower=Not Enough Power! msg.empty=Empty msg.yes=yes msg.no=no @@ -507,3 +548,645 @@ msg.chat.nostation3=You must be on a space station to be in this dimension, and commands.weather.always_not_clear=This planet is always not clear... commands.weather.cannot_rain=Cannot start a rain here commands.weather.cannot_thunder=Cannot start a thunder here + +# Jeistuff +jei.sb.satellitepreview=Ready for orbit! +jei.sb.copy.source=Source +jei.sb.copy.output=New Copy +jei.sb.assemblyhint=Atleast one solar panel +jei.sb.copychiphint=Make backups! + +jei.ar.fuel.role.monopropellant=Monopropellant Fuel +jei.ar.fuel.role.biprop_fuel=Bipropellant Fuel +jei.ar.fuel.role.oxidizer=Oxidizer +jei.ar.fuel.role.working_fluid=Working Fluid +jei.ar.stationAssembler.newStationChipHint=§cThis Points to the new Station! + +# Generic hint +tooltip.advancedrocketry.hold_shift=Hold §eShift§7 for details +tooltip.advancedrocketry.hold_alt=Hold §eAlt§7 for advanced tips + +# Fuel Tank (monoprop) +tooltip.advancedrocketry.fueltank=§cPart of Rocket +tooltip.advancedrocketry.fueltank.shift.1=§fHolds: §b%s +tooltip.advancedrocketry.fueltank.alt.1=Lets Rockety Rockrock! + +# Bipropellant Fuel Tank +tooltip.advancedrocketry.bipropfueltank=§cPart of Rocket +tooltip.advancedrocketry.bipropfueltank.shift.1=§fHolds: §b%s +tooltip.advancedrocketry.bipropfueltank.alt.1=§fBipropellant Rocket requires §bBipropellant§f and §bOxidizer§f tanks + +# Oxidizer Fuel Tank +tooltip.advancedrocketry.oxidizerfueltank=§cPart of Rocket +tooltip.advancedrocketry.oxidizerfueltank.shift.1=§fHolds: §b%s +tooltip.advancedrocketry.oxidizerfueltank.alt.1=§fBipropellant Rocket requires §bBipropellant§f and §bOxidizer§f tanks + +# Nuclear Fuel Tank +tooltip.advancedrocketry.nuclearfueltank=§cPart of Rocket +tooltip.advancedrocketry.nuclearfueltank.shift.1=§fHolds: §b%s +tooltip.advancedrocketry.nuclearfueltank.alt.1=§fRequires §bNuclear Core§f and §bNuclear Engine + +# Monopropellant Engine +tooltip.advancedrocketry.monopropmotor=§cPart of Rocket +tooltip.advancedrocketry.monopropmotor.shift.1=Uses §bMonopropellant Fuel§7 +tooltip.advancedrocketry.monopropmotor.alt.1=Check Fueling Station JEI-page + +# Nuclear Core +tooltip.advancedrocketry.nuclearcore=§cPart of Rocket +tooltip.advancedrocketry.nuclearcore.shift.1=Needs to be directly on top of a +tooltip.advancedrocketry.nuclearcore.shift.2=Nuclear Engine or another Nuclear Core. +tooltip.advancedrocketry.nuclearcore.alt.1=Vertical placement rules doesn't apply +tooltip.advancedrocketry.nuclearcore.alt.2=for Unmanned Vehicles (Gas Mission rockets) + +# Nuclear rocketengine +tooltip.advancedrocketry.nuclearmotor=§cPart of Rocket §6Allows planetary travel! +tooltip.advancedrocketry.nuclearmotor.shift.1=Uses §bWorking fluid§7 +tooltip.advancedrocketry.nuclearmotor.shift.2=Needs Nuclear Core directly above. +tooltip.advancedrocketry.nuclearmotor.alt.1=Check Fueling Station JEI-page + +# Bipropellant Engine +tooltip.advancedrocketry.bipropmotor=§cPart of Rocket +tooltip.advancedrocketry.bipropmotor.shift.1=Uses §bBipropellant Fuel§7 and §bOxidizer§7 +tooltip.advancedrocketry.bipropmotor.alt.1=Check Fueling Station JEI-page + +# Drill +tooltip.advancedrocketry.drill=§cPart of Rocket +tooltip.advancedrocketry.drill.shift.1=Lowers duration of §6Mining Missions§7 +tooltip.advancedrocketry.drill.shift.2=§bEffect stacks +tooltip.advancedrocketry.drill.alt.1=This rocket is built with Rocket Assembling Machine +tooltip.advancedrocketry.drill.alt.2=Requires a programmed Asteroid Chip + +# Gas Intake +tooltip.advancedrocketry.intake=§cPart of Rocket +tooltip.advancedrocketry.intake.shift.1=Lowers duration of §6Gas Missions§7 +tooltip.advancedrocketry.intake.shift.2=§bEffect stacks +tooltip.advancedrocketry.intake.alt.1=This rocket is built with Unmanned Vehicle Assembler +tooltip.advancedrocketry.intake.alt.2=Launched from Space Station orbiting a Gas giant + +# Seat +tooltip.advancedrocketry.seat=§cPart of rocket +tooltip.advancedrocketry.seat.shift.1=Adds a passenger slot to the rocket + +# Guidance Computer +tooltip.advancedrocketry.guidancecomputer=§cPart of Rocket +tooltip.advancedrocketry.guidancecomputer.shift.1=Insert a §cChip§7 or programmed §cLinker§7 +tooltip.advancedrocketry.guidancecomputer.alt.1=Remember to tell rocket which planet to target +tooltip.advancedrocketry.guidancecomputer.alt.2=when deploying Satellites or Space Station into orbit + +# Service Monitor +tooltip.advancedrocketry.servicemonitor=§cPart of Rocket +tooltip.advancedrocketry.servicemonitor.shift.1=Enables damage view +tooltip.advancedrocketry.servicemonitor.shift.2=in rocket GUI +tooltip.advancedrocketry.servicemonitor.alt.1=WIP +tooltip.advancedrocketry.servicemonitor.alt.2= + +# Docking Pad (landingPad) +tooltip.advancedrocketry.landingpad=Replace the launchpad’s center block with this +tooltip.advancedrocketry.landingpad.shift.1=§cLinker§7 can store this block's exact position (dimension-aware). +tooltip.advancedrocketry.landingpad.shift.2=Insert in §4Guidance Computer§7 to land here. +tooltip.advancedrocketry.landingpad.alt.1=Place a programmed Linker in the Docking Pad. +tooltip.advancedrocketry.landingpad.alt.2=Rockets launched from this pad will fly to the Linker’s saved coordinates (used for automation). +tooltip.advancedrocketry.landingpad.alt.3=if there is nothing in the rocket's guidance computer. + +# Launch Pad +tooltip.advancedrocketry.launchpad=Base for Launch Pad platform. +tooltip.advancedrocketry.launchpad.shift.1=Place Launch Pad blocks in a flat square. +tooltip.advancedrocketry.launchpad.shift.2=3x3, 4x4, 5x5, .... +tooltip.advancedrocketry.launchpad.alt.1=§bAdd Structure Tower (minimum 4 high, starting at same y as Launch Pad) +tooltip.advancedrocketry.launchpad.alt.2=§fRocket/Station Assembler will connect automatically + +# Structure Tower +tooltip.advancedrocketry.structuretower=Vertical support for the Launch Pad platform. +tooltip.advancedrocketry.structuretower.shift.1=Tower must be minimum 4 blocks. +tooltip.advancedrocketry.structuretower.shift.2=Bottom block must touch the Launch Pad. +tooltip.advancedrocketry.structuretower.alt.1=Main block for Unmanned Vehicle Assembler structure +tooltip.advancedrocketry.structuretower.alt.2=Check wiki for more info. + +# Terraformer +tooltip.advancedrocketry.terraformer=Terraform +tooltip.advancedrocketry.terraformer.shift.1=Terraform stuff +tooltip.advancedrocketry.terraformer.shift.2= +tooltip.advancedrocketry.terraformer.alt.1=Terraform stuff +tooltip.advancedrocketry.terraformer.alt.2= + +# Rocket Monitoring Station +tooltip.advancedrocketry.monitoringstation=§cInfrastructure +tooltip.advancedrocketry.monitoringstation.shift.1=Launch with Redstone Signal! +tooltip.advancedrocketry.monitoringstation.shift.2=§fLink to §4Rocket Assembler/Dockingpad§f or §4Rocket +tooltip.advancedrocketry.monitoringstation.alt.1=§bMissions becomes active when orbit reached! + +# Satellite Terminal +tooltip.advancedrocketry.satellitemonitor=Communicates with Satellites +tooltip.advancedrocketry.satellitemonitor.shift.1=Insert Satellite Chip +tooltip.advancedrocketry.satellitemonitor.shift.2=Download Data +tooltip.advancedrocketry.satellitemonitor.alt.1=§fTransfer and Auto-Download Data with Wireless Tranciever or Data Unit + +# Satellite Builder +tooltip.advancedrocketry.satellitebuilder=Assembles Satellites +tooltip.advancedrocketry.satellitebuilder.shift.1=Also Copy Chips/Remotes. +tooltip.advancedrocketry.satellitebuilder.shift.2=§bMust be on top of a Power Plug! +tooltip.advancedrocketry.satellitebuilder.alt.1=Insert chassis, Chip/Remote +tooltip.advancedrocketry.satellitebuilder.alt.2=core component + other components + + +### Infrastructure +## BlockARHatch + +# TileDataBus +tooltip.advancedrocketry.hatch.databus=Capacity: §62000 §7Data +tooltip.advancedrocketry.hatch.databus.shift.1=§bCan only hold one type of Data + +# TileSatelliteHatch +tooltip.advancedrocketry.hatch.satellite=§cPart of Rocket +tooltip.advancedrocketry.hatch.satellite.shift.1=Used to put payloads in orbit +tooltip.advancedrocketry.hatch.satellite.shift.2=Remember to set target planet for orbit! +tooltip.advancedrocketry.hatch.satellite.alt.1=§fUsed in §4Station Assembler§f to pack and store the Station + +# TileRocketUnloader +tooltip.advancedrocketry.hatch.item_unloader=§cInfrastructure +tooltip.advancedrocketry.hatch.item_unloader.shift.1=Emits redstone when empty +tooltip.advancedrocketry.hatch.item_unloader.alt.1=§fLink to §4Rocket Assembler/Dockingpad§f or §4Rocket +tooltip.advancedrocketry.hatch.item_unloader.alt.2=§fRockets landing here will auto-link to this. + +# TileRocketLoader +tooltip.advancedrocketry.hatch.item_loader=§cInfrastructure +tooltip.advancedrocketry.hatch.item_loader.shift.1=Emits redstone when full +tooltip.advancedrocketry.hatch.item_loader.alt.1=§fLink to §4Rocket Assembler/Dockingpad§f or §4Rocket +tooltip.advancedrocketry.hatch.item_loader.alt.2=§fRockets landing here will auto-link to this. + +# TileRocketFluidUnloader +tooltip.advancedrocketry.hatch.fluid_unloader=§cInfrastructure +tooltip.advancedrocketry.hatch.fluid_unloader.shift.1=Emits redstone when empty +tooltip.advancedrocketry.hatch.fluid_unloader.alt.1=§fLink to §4Rocket Assembler/Dockingpad§f or §4Rocket +tooltip.advancedrocketry.hatch.fluid_unloader.alt.2=§fRockets landing here will auto-link to this. + +# TileRocketFluidLoader +tooltip.advancedrocketry.hatch.fluid_loader=§cInfrastructure +tooltip.advancedrocketry.hatch.fluid_loader.shift.1=Emits redstone when full +tooltip.advancedrocketry.hatch.fluid_loader.alt.1=§fLink to §4Rocket Assembler/Dockingpad§f or §4Rocket +tooltip.advancedrocketry.hatch.fluid_loader.alt.2=§fRockets landing here will auto-link to this. + +# Guidance Computer Access +tooltip.advancedrocketry.hatch.gca=§cInfrastructure +tooltip.advancedrocketry.hatch.gca.shift.1=Emits redstone when empty +tooltip.advancedrocketry.hatch.gca.alt.1=§fLink to §4Rocket Assembler/Dockingpad§f or §4Rocket +tooltip.advancedrocketry.hatch.gca.alt.2=§fRockets landing here will auto-link to this. + +## /BlockARHatch + +# Fueling Station +tooltip.advancedrocketry.fuelingstation=§cInfrastructure +tooltip.advancedrocketry.fuelingstation.shift.1=Emits redstone when full +tooltip.advancedrocketry.fuelingstation.shift.2=if station has same fueltype. +tooltip.advancedrocketry.fuelingstation.alt.1=§fLink to §4Rocket Assembler/Dockingpad§f or §4Rocket +tooltip.advancedrocketry.fuelingstation.alt.2=§fRockets landing here will auto-link to this. + +# Service Station +tooltip.advancedrocketry.servicestation=§cInfrastructure +tooltip.advancedrocketry.servicestation.shift.1=Repair rocket +tooltip.advancedrocketry.servicestation.shift.2=§o(WIP) +tooltip.advancedrocketry.fuelingstation.alt.1=§fLink to §4Rocket Assembler/Dockingpad§f or §4Rocket + +## // Infrastructure + +# Pressurized Fluid Tank +tooltip.advancedrocketry.fluidtank.shift.1=§cConnects to tanks above or below to make a bigger tank + +# Wireless Transciever +tooltip.advancedrocketry.transceiver=Transfers §6Data +tooltip.advancedrocketry.transceiver.shift.1=Use §cLinker§7 to create Networks +tooltip.advancedrocketry.transceiver.shift.2=Supports multiple Tranceivers +tooltip.advancedrocketry.transceiver.alt.1=§fExtractmode toggles autodownload from Terminal +tooltip.advancedrocketry.transceiver.alt.2=(reinsert chip if stale) + +# Atmosphere Detector +tooltip.advancedrocketry.atmosphereDetector=Emits redstone based on Atmosphere +tooltip.advancedrocketry.atmosphereDetector.shift.1=Select wanted Atmosphere +tooltip.advancedrocketry.atmosphereDetector.shift.2=emits if the condition is true +tooltip.advancedrocketry.atmosphereDetector.alt.1=Can detect: air, vacuum, +tooltip.advancedrocketry.atmosphereDetector.alt.2=low O₂, No O₂, very hot and more. + +# Station Light +tooltip.advancedrocketry.circlelight=Always on +tooltip.advancedrocketry.circlelight.shift.1=Does not require +tooltip.advancedrocketry.circlelight.shift.2=Power or Redstone + +# Gas Charge Pad +tooltip.advancedrocketry.oxygencharger=Charges Space Suit O₂ and H₂ +tooltip.advancedrocketry.oxygencharger.shift.1=Stand on pad to refill +tooltip.advancedrocketry.oxygencharger.alt.1=Does not require Power + +# Docking Port +tooltip.advancedrocketry.dockingport=Marks a station module docking point +tooltip.advancedrocketry.dockingport.shift.1=On the station: set a unique “My ID” +tooltip.advancedrocketry.dockingport.shift.2=On the new module: set “Target ID” +tooltip.advancedrocketry.dockingport.alt.1=Build new module in Station Builder +tooltip.advancedrocketry.dockingport.alt.2=clamp faces must face each other + +# Pipe Seal +tooltip.advancedrocketry.pipeseal=Airtight holes! +tooltip.advancedrocketry.pipeseal.shift.1=Seal a 1×1 gap by framing it with this +tooltip.advancedrocketry.pipeseal.shift.2=§bRequires 4 blocks per hole +tooltip.advancedrocketry.pipeseal.alt.1=Allows pipes through while keeping O₂ in +tooltip.advancedrocketry.pipeseal.alt.2=Entities can pass through the opening + +# Planet Selector (full-screen) +tooltip.advancedrocketry.planetselector=Browse Spacebodies +tooltip.advancedrocketry.planetselector.shift.1=Opens full-screen planet UI. +tooltip.advancedrocketry.planetselector.shift.2=Browse systems, planets and moons +tooltip.advancedrocketry.planetselector.alt.1=Allows you to remotely set a +tooltip.advancedrocketry.planetselector.alt.2=planet destination for warp controller. + +# Holographic Planet Selector +tooltip.advancedrocketry.planetholoselector=Holographic Spacebody Display +tooltip.advancedrocketry.planetholoselector.shift.1=Makes holographs in-world +tooltip.advancedrocketry.planetholoselector.alt.1="Functions the as planet selector +tooltip.advancedrocketry.planetholoselector.alt.2=but with a 3D holographic display" + +# Orientation Controller +tooltip.advancedrocketry.orientationctrl=§cSpace Station Controller +tooltip.advancedrocketry.orientationctrl.shift.1=Customize Angular Velocity +tooltip.advancedrocketry.orientationctrl.alt.1=§bCosmetic only§7 + +# Gravity Controller +tooltip.advancedrocketry.gravityctrl=§cSpace Station Controller +tooltip.advancedrocketry.gravityctrl.shift.1=Artificial gravity! +tooltip.advancedrocketry.gravityctrl.alt.1=§bCosmetic only§7 +tooltip.advancedrocketry.gravityctrl.alt.2=Redstone control + +# Altitude Controller +tooltip.advancedrocketry.altitudectrl=§cSpace Station Controller +tooltip.advancedrocketry.altitudectrl.shift.1=Customize orbital height +tooltip.advancedrocketry.altitudectrl.alt.1=§bCosmetic only§7 +tooltip.advancedrocketry.altitudectrl.alt.2=Redstone control + +# Co2Scrubber +tooltip.advancedrocketry.scrubber=Place next to Oxygen Vent +tooltip.advancedrocketry.scrubber.shift.1=Reduces Oxygen use (max 2) +tooltip.advancedrocketry.scrubber.alt.1=Each scrubber halves O₂ use, increases Power use. +tooltip.advancedrocketry.scrubber.alt.2=With 2 Scrubbers Oxygen Vent won't use Oxygen. + +# Oxygen Vent +tooltip.advancedrocketry.oxygenvent=Creates breathable air in sealed rooms. +tooltip.advancedrocketry.oxygenvent.shift.1=Requires Power and Oxygen. +tooltip.advancedrocketry.oxygenvent.shift.2=Place inside enclosed area. +tooltip.advancedrocketry.oxygenvent.alt.1=Use CO2 Scrubber instead of oxygen. +tooltip.advancedrocketry.oxygenvent.alt.2=Range: %s blocks radius. + +# Airlock Door +tooltip.advancedrocketry.smallairlock=Airtight door! +tooltip.advancedrocketry.smallairlock.shift.1=Not airtight, leaks when open +tooltip.advancedrocketry.smallairlock.alt.1=Build a proper airlock +tooltip.advancedrocketry.smallairlock.alt.2=using 2 doors + +# Warp Controller +tooltip.advancedrocketry.warpcontroller=Turns the station into a §6Starship +tooltip.advancedrocketry.warpcontroller.shift.1=Place §4Warp Controller§7 + §4Warp Core§7 on a space station +tooltip.advancedrocketry.warpcontroller.shift.2=Warp between planets and solar systems +tooltip.advancedrocketry.warpcontroller.alt.1=UI shows location, destinations, and warp fuel +tooltip.advancedrocketry.warpcontroller.alt.2=(You made it this far! check wiki bro) + +# CarbonScrubberCartridge +tooltip.advancedrocketry.scrubbercart=Used in CO₂ Scrubber +tooltip.advancedrocketry.scrubbercart.shift.1=Purifies air while at cost of durability. +tooltip.advancedrocketry.atmanalyzer.alt.1=Should last more than 24h + +# Seal Detector +tooltip.advancedrocketry.sealdetector=Detects if a block is airtight +tooltip.advancedrocketry.sealdetector.shift.1=§fRight Click on block to use + +# Lens +tooltip.advancedrocketry.lens=§cPart of Observatory +tooltip.advancedrocketry.lens.shift.1=§bUse Holo-Projector! + +## SPACE SUIT + COMPONENTS + +# Suit Working Station +tooltip.advancedrocketry.suitworkingstation=Install/remove §5Space Suit Components +tooltip.advancedrocketry.suitworkingstation.shift.1=Works with Space Suit armor +tooltip.advancedrocketry.suitworkingstation.shift.2=(Helmet/Chest/Legs/Boots) +tooltip.advancedrocketry.suitworkingstation.alt.1=Does not require Power + +# Jetpack +tooltip.advancedrocketry.jetpack=§5Space Suit Component +tooltip.advancedrocketry.jetpack.shift.1=§dSlot: Chest§7 + +# AtmosphereAnalyzer +tooltip.advancedrocketry.atmanalyzer=§5Space Suit ComponentRight Click to check atmosphere. +tooltip.advancedrocketry.atmanalyzer.shift.1=§fRight Click to check atmosphere. +tooltip.advancedrocketry.atmanalyzer.shift.2=§dSlot: Helmet§7 +tooltip.advancedrocketry.atmanalyzer.alt.1=Breathable? Depends on atmosphere +tooltip.advancedrocketry.atmanalyzer.alt.2=Shows type and pressure + +# BeaconFinder +tooltip.advancedrocketry.beaconfinder=§5Space Suit Component +tooltip.advancedrocketry.beaconfinder.shift.1=§fShows a HUD arrow toward AR beacons in this dimension +tooltip.advancedrocketry.beaconfinder.shift.2=§dSlot: Helmet§7 +tooltip.advancedrocketry.beaconfinder.alt.1=Arrow offset is relative to your facing +tooltip.advancedrocketry.beaconfinder.alt.2=Works only in AR dimensions that have registered beacons. + +# PressureTank +tooltip.advancedrocketry.pressuretank.shift.1=§dSlot: Chest§7 +tooltip.advancedrocketry.pressuretank.alt.1=§fStores Oxygen for suit +tooltip.advancedrocketry.pressuretank.alt.2=§fStores Hydrogen for Jetpack + +## Item Upgrade +# 0 = Hover +tooltip.advancedrocketry.itemupgrade.0=§5Space Suit Component +tooltip.advancedrocketry.itemupgrade.0.shift.1=§fEnables Jetpack hover mode +tooltip.advancedrocketry.itemupgrade.0.shift.2=§dSlot: Helmet§7 +tooltip.advancedrocketry.itemupgrade.0.alt.1=Requires a Jetpack installed in the chestplate. +tooltip.advancedrocketry.itemupgrade.0.alt.2=Stacking has no additional effect. + +# 1 = Flight Speed Control Upgrade +tooltip.advancedrocketry.itemupgrade.1=§5Space Suit Component +tooltip.advancedrocketry.itemupgrade.1.shift.1=§fBoosts Jetpack flight speed +tooltip.advancedrocketry.itemupgrade.1.shift.2=§dSlot: Helmet§7 +tooltip.advancedrocketry.itemupgrade.1.alt.1=Requires a Jetpack installed in the chestplate. +tooltip.advancedrocketry.itemupgrade.1.alt.2=§bEffect Stacks! + +# 2 = Bionic Leg Upgrade (speed) +tooltip.advancedrocketry.itemupgrade.2=§5Space Suit Component +tooltip.advancedrocketry.itemupgrade.2.shift.1=§fLeg upgrade: increases walk speed +tooltip.advancedrocketry.itemupgrade.2.shift.2=§dSlot: Legs§7 +tooltip.advancedrocketry.itemupgrade.2.alt.1=Sprint to activate +tooltip.advancedrocketry.itemupgrade.2.alt.2=Stacks with multiple modules. + +# 3 = Padded Landing Boots Upgrade (no fall damage; config-aware) +tooltip.advancedrocketry.itemupgrade.3=§5Space Suit Component +tooltip.advancedrocketry.itemupgrade.3.shift.1=§fEliminates fall damage +tooltip.advancedrocketry.itemupgrade.3.shift.2=§dSlot: Feet§7 +tooltip.advancedrocketry.itemupgrade.3.alt.1= +tooltip.advancedrocketry.itemupgrade.3.alt.2=Stacking has no additional effect. + +# 4 = Antifog Visor Upgrade +tooltip.advancedrocketry.itemupgrade.4=§5Space Suit Component +tooltip.advancedrocketry.itemupgrade.4.shift.1=WIP if u know what this does contact me +tooltip.advancedrocketry.itemupgrade.4.shift.2=§dSlot: Helmet§7 +tooltip.advancedrocketry.itemupgrade.4.alt.1=See through fog on high pressure planets +tooltip.advancedrocketry.itemupgrade.4.alt.2=Stacking has no additional effect. + +# 5 = Earthbright Visor +tooltip.advancedrocketry.itemupgrade.5=§5Space Suit Component +tooltip.advancedrocketry.itemupgrade.5.shift.1=WIP if u know what this does contact me +tooltip.advancedrocketry.itemupgrade.5.shift.2=§dSlot: Helmet§7 +tooltip.advancedrocketry.itemupgrade.5.alt.1= +tooltip.advancedrocketry.itemupgrade.5.alt.2=Stacking has no additional effect. + +## Satellite Components +# Primary Function payloads +tooltip.advancedrocketry.satfunc.optical=§5Satellite Core Component +tooltip.advancedrocketry.satfunc.optical.shift.1=§bCollects Distance Data§7 +tooltip.advancedrocketry.satfunc.optical.shift.2=§oDownload Data in Satellite Terminal +tooltip.advancedrocketry.satfunc.optical.alt.1=Combine with Satellite Chip +tooltip.advancedrocketry.satfunc.optical.alt.2=when assembling + +tooltip.advancedrocketry.satfunc.composition=§5Satellite Core Component +tooltip.advancedrocketry.satfunc.composition.shift.1=§bCollects Composition Data§7 +tooltip.advancedrocketry.satfunc.composition.shift.2=§oDownload data in Satellite Terminal +tooltip.advancedrocketry.satfunc.composition.alt.1=Combine with Satellite Chip +tooltip.advancedrocketry.satfunc.composition.alt.2=when assembling + +tooltip.advancedrocketry.satfunc.mass=§5Satellite Core Component +tooltip.advancedrocketry.satfunc.mass.shift.1=§bCollects Mass Data§7 +tooltip.advancedrocketry.satfunc.mass.shift.2=§oDownload data in Satellite Terminal +tooltip.advancedrocketry.satfunc.mass.alt.1=Combine with Satellite Chip +tooltip.advancedrocketry.satfunc.mass.alt.2=when assembling + +tooltip.advancedrocketry.satfunc.microwave=§5Satellite Core Component +tooltip.advancedrocketry.satfunc.microwave.shift.1=§bGenerates Power in Space§7 +tooltip.advancedrocketry.satfunc.microwave.shift.2=§oNeeds Microwave Receiver (5x5 Multiblock) +tooltip.advancedrocketry.satfunc.microwave.alt.1=Combine with Satellite Chip +tooltip.advancedrocketry.satfunc.microwave.alt.2=when assembling + +tooltip.advancedrocketry.satfunc.oremapping=§5Satellite Core Component +tooltip.advancedrocketry.satfunc.oremapping.shift.1=§bScans the planet for Ore§7 +tooltip.advancedrocketry.satfunc.oremapping.alt.1=Combine with Ore Scanner +tooltip.advancedrocketry.satfunc.oremapping.alt.2=when assembling + +tooltip.advancedrocketry.satfunc.biomechanger=§5Satellite Core Component +tooltip.advancedrocketry.satfunc.biomechanger.shift.1=§bAdjusts biomes§7 +tooltip.advancedrocketry.satfunc.biomechanger.alt.1=Combine with Biome Changer Remote +tooltip.advancedrocketry.satfunc.biomechanger.alt.2=when assembling + +tooltip.advancedrocketry.satfunc.weather=§5Satellite Core Component +tooltip.advancedrocketry.satfunc.weather.shift.1=§bDoes some weather stuff! +tooltip.advancedrocketry.satfunc.weather.alt.1=Combine with Weather Remote +tooltip.advancedrocketry.satfunc.weather.alt.2=when assembling + +# Power Sources +tooltip.advancedrocketry.satpower.0=§5Satellite Component +tooltip.advancedrocketry.satpower.0.shift.1=§fGenerates §c4 §fRF/t§7 +tooltip.advancedrocketry.satpower.0.shift.2=§oSatellites requires atleast 1 powergen + +tooltip.advancedrocketry.satpower.1=§5Satellite Component +tooltip.advancedrocketry.satpower.1.shift.1=§fGenerates §c40 §fRF/t§7 +tooltip.advancedrocketry.satpower.1.shift.2=§oSatellites requires atleast 1 powergen + +# LibVulpes Batteries +tooltip.libvulpes.battery.0=§5Satellite Component§7 +tooltip.libvulpes.battery.0.shift.1=Increases Powerstorage +tooltip.libvulpes.battery.0.shift.2=§fCapacity: §c10.000 §fRF§7 + +tooltip.libvulpes.battery.1=§5Satellite Component +tooltip.libvulpes.battery.1.shift.1=Increases Powerstorage +tooltip.libvulpes.battery.1.shift.2=§fCapacity: §c40.000 §fRF§7 + +# Data Unit +tooltip.advancedrocketry.itemdataunit.shift.1=§fIncrease Satellite Data Storage by 1000 +tooltip.advancedrocketry.itemdataunit.alt.1=§fWorks as Datastorage in inventory aswell + +## Chips / remotes +# Asteroid Chip +tooltip.advancedrocketry.asteroidchip.shift.1=§bUsed for Mining Missions§7 +tooltip.advancedrocketry.asteroidchip.shift.2=§fProgram in §cObservatory +tooltip.advancedrocketry.asteroidchip.alt.1=§4Insert programmed Chip in Guidance Computer§7 + +# Station Chip +tooltip.advancedrocketry.stationchip=§bMake Backups! +tooltip.advancedrocketry.stationchip.shift.1=§fProgram in Space Station Assembler +tooltip.advancedrocketry.stationchip.alt.1=§4Insert programmed Chip in Guidance Computer§7 + +# Planet Chip +tooltip.advancedrocketry.planetidchip.shift.1=§fInsert Chip in §4Guidance Computer§7 +tooltip.advancedrocketry.planetidchip.shift.2=§fSet destination in Rocket GUI to program it. +tooltip.advancedrocketry.planetidchip.alt.1=§4Doublecheck that this is programmed before Launch! + +# Satellite Chip +tooltip.advancedrocketry.satidchip=§bMake Backups! +tooltip.advancedrocketry.satidchip.shift.1=§fStores a satellite’s ID. +tooltip.advancedrocketry.satidchip.alt.1=§4Use in Satellite Monitor to link or in the Microwave Receiver Multiblock (Input hatch). §8 (Planet: resolves if put in Terminal) + +# Elevator Chip +tooltip.advancedrocketry.elevatorchip=Space Elevator Chip +tooltip.advancedrocketry.elevatorchip.shift.1=§fLinks an elevator pad/destination. + + +## Multiblocks + +# Black Hole Generator +tooltip.advancedrocketry.blackholegen=Generates Power from compressed mass +tooltip.advancedrocketry.blackholegen.shift.1=§bUse Holo-Projector! + +# Microwave Receiver +tooltip.advancedrocketry.microwavereceiver=Receives Power from Solar Satellites +tooltip.advancedrocketry.microwavereceiver.shift.1=5x5 Multiblock +tooltip.advancedrocketry.microwavereceiver.shift.2=§bUse Holo-Projector! +tooltip.advancedrocketry.microwavereceiver.alt.1=§fSolar Satellites are built with §bMicrowave Transmitter§f and §bSatellite Chip +tooltip.advancedrocketry.microwavereceiver.alt.2=§8RF/t = (sum Satellites RF/t) * (2 * AtmospheredensityFactor) + +# Solar Panel (part of Microwave Receiver) +tooltip.advancedrocketry.solarpanel=§cPart of Multiblock +tooltip.advancedrocketry.solarpanel.shift.1=5x5 Multiblock +tooltip.advancedrocketry.solarpanel.shift.2=§o§f(Microwave Receiver) +tooltip.advancedrocketry.solarpanel.shift.3=§bUse Holo-Projector! + +# Solar Array Controller +tooltip.advancedrocketry.solararray=Generates Power from sunlight +tooltip.advancedrocketry.solararray.shift.1=Requires 63x Solar Array Panels +tooltip.advancedrocketry.solararray.shift.2=§bUse Holo-Projector! +tooltip.advancedrocketry.solararray.alt.1= + +# Solar Array Panel +tooltip.advancedrocketry.solararraypanel=§cPart of Multiblock +tooltip.advancedrocketry.solararraypanel.shift.1=Requires 63x Solar Array Panels and Controller +tooltip.advancedrocketry.solararraypanel.shift.2=§bUse Holo-Projector! +tooltip.advancedrocketry.solararray.alt.1= + +# Solar Generator +tooltip.advancedrocketry.solargenerator=Basic Solar Panel +tooltip.advancedrocketry.solargenerator.shift.1=§fGenerates §c2 §fRF/t§7 + +# Arc Furnace +tooltip.advancedrocketry.arcfurnace=Smelts at extreme temperatures +tooltip.advancedrocketry.arcfurnace.shift.1=§bUse Holo-Projector! + +# Rolling Machine +tooltip.advancedrocketry.rollingmachine=Rolls plates and foils +tooltip.advancedrocketry.rollingmachine.shift.1=§bUse Holo-Projector! + +# Lathe +tooltip.advancedrocketry.lathe=Turns rods and shafts +tooltip.advancedrocketry.lathe.shift.1=§bUse Holo-Projector! + +# Crystallizer +tooltip.advancedrocketry.crystallizer=Grows high-purity crystals +tooltip.advancedrocketry.crystallizer.shift.1=§bUse Holo-Projector! + +# Cutting Machine +tooltip.advancedrocketry.cuttingmachine=Precision cutting of materials +tooltip.advancedrocketry.cuttingmachine.shift.1=§bUse Holo-Projector! + +# Precision Assembler +tooltip.advancedrocketry.precisionassembler=Automates complex assembly +tooltip.advancedrocketry.precisionassembler.shift.1=§bUse Holo-Projector! + +# Electrolyser +tooltip.advancedrocketry.electrolyser=Splits compounds via electrolysis +tooltip.advancedrocketry.electrolyser.shift.1=§bUse Holo-Projector! + +# Chemical Reactor +tooltip.advancedrocketry.chemreactor=Processes chemical reactions +tooltip.advancedrocketry.chemreactor.shift.1=§bUse Holo-Projector! + +# Precision Laser Etcher +tooltip.advancedrocketry.precisionlaseretcher=Laser-etches fine circuits +tooltip.advancedrocketry.precisionlaseretcher.shift.1=§bUse Holo-Projector! + +# Observatory +tooltip.advancedrocketry.observatory=Analyzes celestial bodies +tooltip.advancedrocketry.observatory.shift.1=§fUsed for §6Mining Missions +tooltip.advancedrocketry.observatory.shift.2=§bUse Holo-Projector! +tooltip.advancedrocketry.observatory.alt.1=§fInsert §cAsteroid Chip + + +# Planet Analyser +tooltip.advancedrocketry.planetanalyser=Processes planet scan data +tooltip.advancedrocketry.planetanalyser.shift.1=§bUse Holo-Projector! + +# Centrifuge +tooltip.advancedrocketry.centrifuge=Separates by density +tooltip.advancedrocketry.centrifuge.shift.1=§bUse Holo-Projector! + +# Warp Core +tooltip.advancedrocketry.warpcore=Core for §6Starship +tooltip.advancedrocketry.warpcore.shift.1=§bUse Holo-Projector! + +# Beacon +tooltip.advancedrocketry.beacon=Long-range signal beacon +tooltip.advancedrocketry.beacon.shift.1=§bUse Holo-Projector! + +# Biome Scanner +tooltip.advancedrocketry.biomescan=Scans planetary biomes +tooltip.advancedrocketry.biomescan.shift.1=§bUse Holo-Projector! + +# Railgun +tooltip.advancedrocketry.railgun=Shoots Items into space +tooltip.advancedrocketry.railgun.shift.1=§bUse Holo-Projector! +tooltip.advancedrocketry.railgun.alt.1="The railgun is not powerful enough to transport item stacks between planets and its range is limited to bodies within the same system" + +# Space Elevator Controller +tooltip.advancedrocketry.spaceelevatorctrl=Controls the Space Elevator +tooltip.advancedrocketry.spaceelevatorctrl.shift.1=§bUse Holo-Projector! + +# Atmosphere Terraformer +tooltip.advancedrocketry.atmosterraformer=Changes Atmospheric pressure on an entire planet planet +tooltip.advancedrocketry.atmosterraformer.shift.1=§bUse Holo-Projector! +tooltip.advancedrocketry.atmosterraformer.alt.1=Increase and decrease the atmosphere pressure by using connected §cBiome changing remote. + +# Area Gravity Controller +tooltip.advancedrocketry.gravitymachine=Manipulates Gravity +tooltip.advancedrocketry.gravitymachine.shift.1=§fCan also affect the direction of Gravity +tooltip.advancedrocketry.gravitymachine.shift.2=§bUse Holo-Projector! + +# Orbital Last Drill +tooltip.advancedrocketry.spacelaser=§cSpace Station Multiblock +tooltip.advancedrocketry.spacelaser.shift.1=§bUse Holo-Projector! + +# Force Field Projector +tooltip.advancedrocketry.forcefieldprojector=Projeccts up to 32 blocks away! +tooltip.advancedrocketry.forcefieldprojector.shift.1=Activate with Redstone + +# Vacuum Laser +tooltip.advancedrocketry.vacuumlaser=§cPart of Multiblock +tooltip.advancedrocketry.vacuumlaser.shift.1=§bUse Holo-Projector! + + +# Pump +tooltip.advancedrocketry.pump=Searches for fluid directly below +tooltip.advancedrocketry.pump.shift.1=§cPulls from the connected pool within 64 blocks. +tooltip.advancedrocketry.pump.alt.1=§fAutoejects to nearby tank +tooltip.advancedrocketry.pump.alt.2=Redstone turns it off + +# Parts for Multiblock +tooltip.advancedrocketry.concrete=§cPart of Multiblock +tooltip.advancedrocketry.concrete.shift.1=§bUse Holo-Projector! +tooltip.advancedrocketry.blastbrick=§cPart of Multiblock +tooltip.advancedrocketry.blastbrick.shift.1=§bUse Holo-Projector! +tooltip.advancedrocketry.qcrucible=§cPart of Multiblock +tooltip.advancedrocketry.qcrucible.shift.1=§bUse Holo-Projector! +tooltip.advancedrocketry.sawblade=§cPart of Multiblock +tooltip.advancedrocketry.sawblade.shift.1=§bUse Holo-Projector! + +## Assemblers +# Rocket Assembler +tooltip.advancedrocketry.rocketassembler=§cBuilds Rockets +tooltip.advancedrocketry.rocketassembler.shift.1=§bRequires Launch Pad + Structure Tower Structure +tooltip.advancedrocketry.rocketassembler.shift.2=§fConnecting all infrastructure to this lets them autoconnect to Rocket on Pad +tooltip.advancedrocketry.rocketassembler.alt.1=§fPlace this 1 block higher than Launch Pad, connecting lower corners and facing opposite of the Launch Pad square + +# Station Assembler +tooltip.advancedrocketry.stationassembler=§cPacks Space Stations down for Launch! +tooltip.advancedrocketry.stationassembler.shift.1=§bRequires Launch Pad + Structure Tower Structure +tooltip.advancedrocketry.stationassembler.alt.1=§fPlace this 1 block higher than Launch Pad, connecting lower corners and facing opposite of the Launch Pad square + +# Packet Station +tooltip.advancedrocketry.packedstructure=Insert in §cSatellite Bay§7 to put in Orbit! +tooltip.advancedrocketry.packedstructure.shift.1=§eRemember to tell Rocket what planet it should deplay payload into Orbit! + +# Deployable Rocket Assembler +tooltip.advancedrocketry.deployablerocketassembler=§cBuilds Rockets on Space Stations +tooltip.advancedrocketry.deployablerocketassembler.shift.1=§bRequires Structure Tower blocks +tooltip.advancedrocketry.deployablerocketassembler.shift.2=§fUsed for §6Gas Missions§7 +tooltip.advancedrocketry.deployablerocketassembler.alt.1=§fPlace this in the middle of upside down T facing outwards in space, then a horizontal support outwards from the top of tower. +tooltip.advancedrocketry.deployablerocketassembler.alt.2=Check wiki if unsure! diff --git a/src/main/resources/assets/advancedrocketry/lang/es_ES.lang b/src/main/resources/assets/advancedrocketry/lang/es_ES.lang index d17daaf60..dee6a00a0 100644 --- a/src/main/resources/assets/advancedrocketry/lang/es_ES.lang +++ b/src/main/resources/assets/advancedrocketry/lang/es_ES.lang @@ -108,6 +108,8 @@ item.jackhammer.name=Martillo neumático item.sealDetector.name=Detector de sellado tile.orientationControl.name=Controlador de orientación +msg.rocketbuilder.alreadyassembled=Cohete ya ensamblado + material.Dilithium.name=Dilitio @@ -131,4 +133,10 @@ fluid.oxygen=Oxígeno fluid.hydrogen=Hidrógeno fluid.rocketFuel=Combustible de cohete -mission.asteroidmining.name=Minería de asteroides \ No newline at end of file +mission.asteroidmining.name=Minería de asteroides + +jei.sb.satellitepreview=¡Listo para la órbita! +jei.sb.copy.source=Fuente +jei.sb.copy.output=Nueva copia +jei.sb.assemblyhint=Al menos un panel solar +jei.sb.copychiphint=¡Haz copias de seguridad! diff --git a/src/main/resources/assets/advancedrocketry/lang/fi_FI.lang b/src/main/resources/assets/advancedrocketry/lang/fi_FI.lang index f5270fc2a..4a741dd86 100644 --- a/src/main/resources/assets/advancedrocketry/lang/fi_FI.lang +++ b/src/main/resources/assets/advancedrocketry/lang/fi_FI.lang @@ -18,34 +18,34 @@ tile.seat.name=Istuin tile.pad.name=Laukaisualusta tile.structuretower.name=Rakennetorni tile.rocketAssembler.name=Raketin Kokoamislaite -tile.turf.name=Kuuply -tile.turfDark.name=Tumma Kuuply +tile.turf.name=Kuupöly +tile.turfDark.name=Tumma Kuupöly tile.cuttingMachine.name=Leikkuulaite -tile.sawBlade.name=Sahanter Kokoonpano -tile.controlComp.name=Tehtvn Ohjaustietokone +tile.sawBlade.name=Sahanterä Kokoonpano +tile.controlComp.name=Tehtävän Ohjaustietokone tile.precisionAssemblingMachine.name=Tarkkuuskokoaja tile.spaceLaser.name=Kiertorata Laser Pora -tile.Crystallizer.name=Kiteyttj -tile.blastBrick.name=Lmmnkestv tiili -tile.blastFurnaceController.name=Lmmnkestvn Uunin Ohjain +tile.Crystallizer.name=Kiteyttäjä +tile.blastBrick.name=Lämmönkestävä tiili +tile.blastFurnaceController.name=Lämmönkestävän Uunin Ohjain tile.fuelStation.name=Tankkausasema -tile.loader.0.name=Datavyl +tile.loader.0.name=Dataväylä tile.loader.1.name=Satelliittiasema tile.loader.2.name=Rakettipurkaja -tile.loader.3.name=Rakettityttj +tile.loader.3.name=Rakettitäyttäjä tile.loader.4.name=Rakettinestepurkaja -tile.loader.5.name=Rakettinestetyttj -tile.loader.6.name=Ohjaustietokoneen psyluukku +tile.loader.5.name=Rakettinestetäyttäjä +tile.loader.6.name=Ohjaustietokoneen pääsyluukku tile.observatory.name=Observatorio tile.satelliteBuilder.name=Satelliittirakentaja tile.rocket.name=Yksiajoaineinen Rakettimoottori tile.bipropellantrocket.name=Kaksiajoaineinen Rakettimoottori -tile.nuclearrocket.name=Ydinlamprakettimoottori +tile.nuclearrocket.name=Ydinlampörakettimoottori tile.fuelTank.name=Yksiajoainetankki tile.bipropellantfueltank.name=Kaksiajoainetankki tile.oxidizerfueltank.name=Hapetinpolttoainetankki -tile.nuclearfueltank.name=Ydinlmptystmisnestetankki -tile.nuclearcore.name=Ydinlmpfissioydin +tile.nuclearfueltank.name=Ydinlämpötyöstämisnestetankki +tile.nuclearcore.name=Ydinlämpöfissioydin tile.monitoringstation.name=Raketin Tarkkailuasema tile.satelliteMonitor.name=Satelliitti Terminaali tile.lightwoodlog.name=Valopuu @@ -56,7 +56,7 @@ tile.chipStorage.name=Satelliitti Id Varasto tile.planetanalyser.name=Avaruusobjekti Data Prosessori tile.lunaranalyser.name=Kuuanalysaattori tile.guidanceComputer.name=Ohjaustietokone -tile.electricArcFurnace.name=Shkinen Valokaariuuni +tile.electricArcFurnace.name=Sähköinen Valokaariuuni tile.hotDryturf.name=Hapetettu rautahiekka tile.concrete.name=Betoni tile.lathe.name=Sorvi @@ -78,35 +78,35 @@ tile.oxygenCharger.name=Kaasulataustyyny tile.dockingPad.name=Telakointialusta tile.stationmonitor.name=Hyperavaruusohjain tile.warpCore.name=Hyperavaruusydin -tile.atmosphereDetector.name=Ilmakehtunnistin +tile.atmosphereDetector.name=Ilmakehätunnistin tile.unlittorch.name=Sammunut Soihtu tile.geode.name=Geodikuutio -tile.electricMushroom.name=Shkinen Sieni +tile.electricMushroom.name=Sähköinen Sieni tile.charcoallog.name=Puuhiilitukki tile.vitrifiedSand.name=Lasittunut Hiekka tile.ruby.name=Punainen Kristallikuutio -tile.emerald.name=Vihre Kristallikuutio +tile.emerald.name=Vihreä Kristallikuutio tile.sapphire.name=Sininen Kristallikuutio tile.citrine.name=Keltainen Kristallikuutio tile.wulfentite.name=Oranssi Kristallikuutio tile.amethyst.name=Violetti Kristallikuutio tile.gravityControl.name=Avaruusaseman Painovoiman Ohjain tile.drill.name=Pora -tile.dataPipe.name=Datakaapeli(Poistettu kytst) -tile.liquidPipe.name=Nesteputki(Poistettu kytst) -tile.rfOutput.name=Redstone Flux Lhtpistoke +tile.dataPipe.name=Datakaapeli(Poistettu käytöstä) +tile.liquidPipe.name=Nesteputki(Poistettu käytöstä) +tile.rfOutput.name=Redstone Flux Lähtöpistoke tile.microwaveReciever.name=Mikroaaltovastaanotin tile.solarPanel.name=Aurinkopaneeli -tile.suitWorkStation.name=Pukutyasema +tile.suitWorkStation.name=Pukutyöasema tile.orientationControl.name=Suuntaohjain tile.biomeScanner.name=Biomiskanneri -tile.atmoshereTerraformer.name=Ilmakehterraformaattori -tile.deployableRocketAssembler.name=Miehittmttomn Kulkuneuvon Kokoaja +tile.atmoshereTerraformer.name=Ilmakehäterraformaattori +tile.deployableRocketAssembler.name=Miehittämättomän Kulkuneuvon Kokoaja tile.pressurizedTank.name=Paineistettu Tankki -tile.gasIntake.name=Kaasun Sisnotto -tile.atmosphereTerraformer.name=Ilmakehterraformaattori +tile.gasIntake.name=Kaasun Sisäänotto +tile.atmosphereTerraformer.name=Ilmakehäterraformaattori tile.circleLight.name=Asemavalo -tile.energyPipe.name=Shkputki(Poistettu kytst) +tile.energyPipe.name=Sähköputki(Poistettu käytöstä) tile.solarGenerator.name=Aurinkogeneraattori tile.stationMarker.name=Asematelakointiportti tile.qcrucible.name=Kvartsi Sulatusastia @@ -116,15 +116,15 @@ tile.advRocket.name=Edistynyt Yksiajoainerakettimoottori tile.advbipropellantRocket.name=Edistynyt Kaksiajoainerakettimoottori tile.planetHoloSelector.name=Holographinen Planeettavalitsin tile.lens.name=Linssi -tile.forceField.name=Voimakentt -tile.forceFieldProjector.name=Voimakenttprojektori -tile.vacuumLaser.name=Suuritehoinen Tyhjikammiolaser +tile.forceField.name=Voimakenttä +tile.forceFieldProjector.name=Voimakenttäprojektori +tile.vacuumLaser.name=Suuritehoinen Tyhjiökammiolaser tile.gravityMachine.name=Aluepainovoimaohjain tile.pipeSeal.name=Putkitiiviste tile.spaceElevatorController.name=Avaruushissi tile.beacon.name=Merkkivalo tile.thermiteTorch.name=Termiittisoihtu -tile.wirelessTransciever.name=Langaton Lhetin-Vastaanotin +tile.wirelessTransciever.name=Langaton Lähetin-Vastaanotin tile.blackholegenerator.name=Musta Aukko Generaattori tile.pump.name=Nestepumppu tile.centrifuge.name=Sentrifugi @@ -146,46 +146,46 @@ item.circuitIC.3.name=Ohjauspiiti item.circuitIC.4.name=Tavara IO Piiri item.circuitIC.5.name=Neste IO Piiri item.OreScanner.name=Malmiskanneri -item.dataUnit.0.name=Datatallennusyksikk -item.sawBlade.0.name=Rautainen Sahanter +item.dataUnit.0.name=Datatallennusyksikkö +item.sawBlade.0.name=Rautainen Sahanterä item.satellite.name=Satelliitti item.satellitePowerSource.0.name=Perusaurinkopaneeli item.satellitePowerSource.1.name=Iso Aurinkopaneeli item.satellitePrimaryFunction.0.name=Optinen Sensori item.satellitePrimaryFunction.1.name=Koostumussensori item.satellitePrimaryFunction.2.name=Massasensori -item.satellitePrimaryFunction.3.name=Mikroaaltolhetin +item.satellitePrimaryFunction.3.name=Mikroaaltolähetin item.satellitePrimaryFunction.4.name=Malmikartoittaja item.satellitePrimaryFunction.5.name=Biomimuuttaja item.satelliteIdChip.name=Satelliitti ID-Siru item.planetIdChip.name=Planeetta ID-Siru item.asteroidChip.name=Asteroidi ID-Siru -item.miscpart.0.name=Kyttliittym +item.miscpart.0.name=Käyttöliittymä item.miscpart.1.name=Hiilitiili item.station.name=Avaruusasemakontti item.stationChip.name=Avaruusasema ID-Siru -item.stationchip.openmenu=Kyyki ja klikkaa hiiren oikeaa nppint avataksesi asetusvalikon -item.spaceHelmet.name=Avaruuspukukypt +item.stationchip.openmenu=Kyyki ja klikkaa hiiren oikeaa näppäintä avataksesi asetusvalikon +item.spaceHelmet.name=Avaruuspukukypätä item.spaceChest.name=Avaruuspukupaita item.spaceLeggings.name=Avaruuspukuhousut -item.spaceBoots.name=Avaruuspukukengt +item.spaceBoots.name=Avaruuspukukengät item.smallAirlock.name=Pieni Ilmalukko-ovi -item.carbonScrubberCartridge.name=Hiilenkeryspatruuna +item.carbonScrubberCartridge.name=Hiilenkeräyspatruuna item.jackhammer.name=Nokkavasara item.sealDetector.name=Vuototunnistin -item.itemUpgrade.0.name=Leijumispivitys -item.itemUpgrade.1.name=Lentonopeuden Ohjauspivitys -item.itemUpgrade.2.name=Bioninen Jalkapivitys -item.itemUpgrade.3.name=Pehmustetut Laskeutumiskengt +item.itemUpgrade.0.name=Leijumispäivitys +item.itemUpgrade.1.name=Lentonopeuden Ohjauspäivitys +item.itemUpgrade.2.name=Bioninen Jalkapäivitys +item.itemUpgrade.3.name=Pehmustetut Laskeutumiskengät item.itemUpgrade.4.name=Sumunestovisiiri item.itemUpgrade.5.name=Maankirkas Visiiri -item.atmAnalyser.name=Ilmakehanalysaattori -item.biomeChanger.name=Biomin vaihtokaukosdin +item.atmAnalyser.name=Ilmakehäanalysaattori +item.biomeChanger.name=Biomin vaihtokaukosäädin item.basicLaserGun.name=Peruslaserpyssy -item.beaconFinder.name=Merkkivalon Lytj +item.beaconFinder.name=Merkkivalon Löytäjä item.thermite.name=Termiitti item.hovercraft.name=Leijualus -item.hovercraft.tooltip=Pitkn kestv dilitiumvoimanlhde. Se el todennkisesti pitempn kuin sin. +item.hovercraft.tooltip=Pitkään kestävä dilitiumvoimanlähde. Se elää todennäköisesti pitempään kuin sinä. item.jetPack.name=Asurakettireppu item.pressureTank.0.name=Matalapainetankki @@ -200,14 +200,14 @@ container.monitoringstation=Tarkkailuasema material.TitaniumAluminide.name=Titaanialuminidi material.TitaniumIridium.name=Titaani-Iridiumseos -enchantment.spaceBreathing=Ilmanpitv Tiiviste +enchantment.spaceBreathing=Ilmanpitävä Tiiviste data.undefined.name=Jotain Satunnaista Dataa -data.distance.name=Etisyys +data.distance.name=Etäisyys data.humidity.name=Ilmankosteus -data.temperature.name=Lmptila +data.temperature.name=Lämpötila data.composition.name=Koostumus -data.atmospheredensity.name=Ilmakehn Tiheys +data.atmospheredensity.name=Ilmakehän Tiheys data.mass.name=Massa fluid.oxygenHappi @@ -217,116 +217,116 @@ fluid.rocketFuel=Rakettipolttoaine fluid.enrichedLava=Rikastettu Laava mission.asteroidmining.name=Asteroidin Louhinta -mission.gascollection.name=Kaasun Kerys +mission.gascollection.name=Kaasun Keräys -error.rocket.cannotGetThere=Mrnp Tavoittamaton -error.rocket.destinationNotExist=Ei voi laukaista: Mrnp ei ole olemassa -error.rocket.notSameSystem=Ei voi laukaista: Mrnp ei ole samassa planeettasysteemiss +error.rocket.cannotGetThere=Määränpää Tavoittamaton +error.rocket.destinationNotExist=Ei voi laukaista: Määränpää ei ole olemassa +error.rocket.notSameSystem=Ei voi laukaista: Määränpää ei ole samassa planeettasysteemissä advancement.holographic=Holografinen advancement.holographic.desc=Rakenna Holo-Projektori -advancement.flattening=Litistv +advancement.flattening=Litistävä advancement.flattening.desc=Rakenna Pieni Levy Puristin -advancement.feelTheHeat=Tunne lmp! +advancement.feelTheHeat=Tunne lämpö! advancement.feelTheHeat.desc=Rakenna Valokaariuuni -advancement.electrifying=Shkistv! +advancement.electrifying=Sähköistävää! advancement.electrifying.desc=Rakenna Elekrolysaattori -advancement.spinDoctor=Pyrimistohtori +advancement.spinDoctor=Pyörimistohtori advancement.spinDoctor.desc=Rakenna Sorvi advancement.rollin=Rullaan advancement.rollin.desc=Rakenna Rullauskone advancement.crystalline=Kiteinen -advancement.crystalline.desc=Rakenna Kiteyttj +advancement.crystalline.desc=Rakenna Kiteyttäjä advancement.warp=Hyperavaruus advancement.warp.desc=Rakenna Hyperavaruusydin advancement.moonLanding=Kuulaskeutuminen! advancement.moonLanding.desc=Laskeudu Kuuhun advancement.oneSmallStep=Yksi Pieni Askel... -advancement.oneSmallStep.desc=Ole ensimminen joka laskeutuu kuuhun! +advancement.oneSmallStep.desc=Ole ensimmäinen joka laskeutuu kuuhun! advancement.weReallyWentToTheMoon=Me Oikeasti Menimme Kuuhun! -advancement.weReallyWentToTheMoon.desc=Lyd Apollo 11:n laskeutumispaikka Kuussa +advancement.weReallyWentToTheMoon.desc=Löydä Apollo 11:n laskeutumispaikka Kuussa advancement.dilithium=Dilitium -advancement.dilithium.desc=Lyd Dilitiummalmia -advancement.givingItAllShesGot=Annetaan kaikki mit hnell on! -advancement.givingItAllShesGot.desc=Lenn Hyperavaruuteen kykenevss aluksessa +advancement.dilithium.desc=Löydä Dilitiummalmia +advancement.givingItAllShesGot=Annetaan kaikki mitä hänellä on! +advancement.givingItAllShesGot.desc=Lennä Hyperavaruuteen kykenevässä aluksessa advancement.flightOfThePhoenix=Feeniksin Lento -advancement.flightOfThePhoenix.desc=Rakenna ja Lenn ensimmist Hyperavaruuteen kykenev alusta +advancement.flightOfThePhoenix.desc=Rakenna ja Lennä ensimmäistä Hyperavaruuteen kykenevää alusta advancement.beerOnTheSun=Aikuisten Juomia Auringossa -advancement.beerOnTheSun.desc=Tulet tarvitsemaan enemmn TNT pstksesi kiertoradalle +advancement.beerOnTheSun.desc=Tulet tarvitsemaan enemmän TNTä päästäksesi kiertoradalle advancement.suitedUp=Pukeutunut -advancement.suitedUp.desc=Pid yllsi kokonaista avaruuspukua +advancement.suitedUp.desc=Pidä ylläsi kokonaista avaruuspukua key.controls.advancedrocketry=Advanced Rocketry key.openRocketUI=Avaa Raketti GUI -key.toggleJetpack=Kynnist/Sammuta Rakettireppu -key.togglercs=Kynnist/Sammuta RCS -key.turnRocketLeft=Knn Kulkuneuvoa Vasemmalle -key.turnRocketRight=Knn Kulkuneuvoa Oikealle -key.turnRocketUp=Nosta Kulkuneuvoa Yls +key.toggleJetpack=Käynnistä/Sammuta Rakettireppu +key.togglercs=Käynnistä/Sammuta RCS +key.turnRocketLeft=Käännä Kulkuneuvoa Vasemmalle +key.turnRocketRight=Käännä Kulkuneuvoa Oikealle +key.turnRocketUp=Nosta Kulkuneuvoa Ylös key.turnRocketDown=Laske Kulkuneuvoa Alas -enchantment.advancedrocketry.spacebreathing.desc=Mahdollistaa haarniskan palan luomaan ilmanpitvn eristeen +enchantment.advancedrocketry.spacebreathing.desc=Mahdollistaa haarniskan palan luomaan ilmanpitävän eristeen machine.tooltip.smallplatepress=Tarvitsee obsidiaani kaksi kuutiota alapuolella toimiakseen msg.crystalliser.gravityTooHigh=Painovoima ei ole tarpeeksi alhainen! -msg.observetory.scan.tooltip=Skannaa uusia asteroideja, kytt 100 etisyys dataa +msg.observetory.scan.tooltip=Skannaa uusia asteroideja, käyttää 100 etäisyys dataa msg.observetory.scan.button=Skannaa! msg.observetory.text.asteroids=Asteroidit msg.observetory.text.composition=Koostumus -msg.observetory.text.processdiscovery=Prosessoi lyt -msg.observetory.text.observabledistance=Havaittava etisyys: -msg.observetory.text.missionTime=Tehtvaika: +msg.observetory.text.processdiscovery=Prosessoi löytö +msg.observetory.text.observabledistance=Havaittava etäisyys: +msg.observetory.text.missionTime=Tehtäväaika: msg.tooltip.data=Data msg.tooltip.asteroidselection=Asteroidivalikko msg.label.name=Nimi -msg.label.clear=Tyhjenn -msg.label.add=Lis Uusi -msg.label.rename=Nime Uudelleen +msg.label.clear=Tyhjennä +msg.label.add=Lisää Uusi +msg.label.rename=Nimeä Uudelleen msg.label.delete=Poista -msg.label.noneSelected=Ei Valittua Mrnpt -msg.label.selectDst=Valitse mrnp -msg.label.destName=Mrnpn nimi +msg.label.noneSelected=Ei Valittua Määränpäätä +msg.label.selectDst=Valitse määränpää +msg.label.destName=Määränpään nimi msg.label.coords=Koordinaatit msg.spaceElevator.button.summon=Luo Kapseli -msg.spaceElevator.sameDimensionError=Ei voi yhdist kahta hissi samalla planeetalla! +msg.spaceElevator.sameDimensionError=Ei voi yhdistää kahta hissiä samalla planeetalla! msg.spaceElevator.linkNotGeostationaryError=Asema ei ole geostationaarisella kiertoradalla! -msg.spaceElevator.tetherWouldBreakError=Aseman tytyy olla oikeinpin ja paikalleen vastaanottaakseen kaapelin! -msg.spaceElevator.linkCannotChangeError=Hissikaapelit eivt voi vaihtaa paikkaa kun ne ovat jo yhdistettyj! +msg.spaceElevator.tetherWouldBreakError=Aseman täytyy olla oikeinpäin ja paikalleen vastaanottaakseen kaapelin! +msg.spaceElevator.linkCannotChangeError=Hissikaapelit eivät voi vaihtaa paikkaa kun ne ovat jo yhdistettyjä! msg.spaceElevator.newDstAdded=Hissikaapeli Yhdistetty! msg.spaceElevator.ascentReady=Valmiina nousuun -msg.spaceElevator.warning.anchored0=Tll hissikaapelilla on +msg.spaceElevator.warning.anchored0=Tällä hissikaapelilla on msg.spaceElevator.warning.anchored1=asema ankkuroituna! -msg.spaceElevator.warning.unanchored=Tll hissill ei ole kaapelia -msg.spaceElevator.turnedOff=Hissi on poissa plt -msg.fuelingStation.link=Sin ohjelmoit yhdistjn tankkausaseman kanssa, joka on koordinaateissa -msg.monitoringStation.missionProgressNA=Tehtvn Edistys: N/A -msg.monitoringStation.link=Sin ohjelmoit yhdistjn tarkkailuaseman kanssa, joka on koordinaateissa +msg.spaceElevator.warning.unanchored=Tällä hissillä ei ole kaapelia +msg.spaceElevator.turnedOff=Hissi on poissa päältä +msg.fuelingStation.link=Sinä ohjelmoit yhdistäjän tankkausaseman kanssa, joka on koordinaateissa +msg.monitoringStation.missionProgressNA=Tehtävän Edistys: N/A +msg.monitoringStation.link=Sinä ohjelmoit yhdistäjän tarkkailuaseman kanssa, joka on koordinaateissa msg.monitoringStation.progress= Edistys: msg.guidanceComputerHatch.loadingState=Lastaustila: -msg.guidanceComputerHatch.ejectonlanding=Poista automaattisesti laskeutumisen jlkeen +msg.guidanceComputerHatch.ejectonlanding=Poista automaattisesti laskeutumisen jälkeen msg.guidanceComputerHatch.ejectonsatlanding=Salli Satelliittisirujen poistaminen msg.guidanceComputerHatch.ejectonplanetlanding=Salli Planeettasirujen poistaminen msg.guidanceComputerHatch.ejectonstationlanding=Salli Asemasirujen poistaminen -msg.guidanceComputerHatch.link=Sin ohjelmoit yhdistjn ohjaustietokoneen psyluukun kanssa, joka on koordinaateissa +msg.guidanceComputerHatch.link=Sinä ohjelmoit yhdistäjän ohjaustietokoneen pääsyluukun kanssa, joka on koordinaateissa msg.fluidLoader.loadingState=Lastaustila: msg.fluidLoader.allowLoading=Salli Lastaus: -msg.fluidLoader.allowredstoneinput=Salli redstone sisntulo +msg.fluidLoader.allowredstoneinput=Salli redstone sisääntulo msg.fluidLoader.allowredstoneoutput=Salli redstone ulosmeno -msg.fluidLoader.none=Ei mitn -msg.fluidLoader.link=Sin ohjelmoit yhdistjn nestelastaajan kanssa, joka on koordinaateissa +msg.fluidLoader.none=Ei mitään +msg.fluidLoader.link=Sinä ohjelmoit yhdistäjän nestelastaajan kanssa, joka on koordinaateissa msg.rocketLoader.loadingState=Lastaustila: msg.rocketLoader.allowLoading=Salli Lastaus: -msg.rocketLoader.allowredstoneinput=Salli redstone sisntulo +msg.rocketLoader.allowredstoneinput=Salli redstone sisääntulo msg.rocketLoader.allowredstoneoutput=Salli redstone ulosmeno -msg.rocketLoader.none=Ei mitn -msg.rocketLoader.link=Sin ohjelmoit yhdistjn rakettilastaajan kanssa, joka on koordinaateissa +msg.rocketLoader.none=Ei mitään +msg.rocketLoader.link=Sinä ohjelmoit yhdistäjän rakettilastaajan kanssa, joka on koordinaateissa msg.microwaverec.notgenerating=Tuottaa 0 FE/t msg.microwaverec.generating=Tuottaa msg.abdp.compositionresearch=Koostumustutkimus -msg.abdp.distanceresearch=Etisyystutkimus +msg.abdp.distanceresearch=Etäisyystutkimus msg.abdp.massresearch=Massatutkimus msg.terraformer.atminc=Nosta Ilmanpainetta msg.terraformer.atmdec=Laske Ilmanpainetta @@ -336,22 +336,22 @@ msg.terraformer.outofgas=Peruutettu: Kaasut loppuivat msg.terraformer.notrunning=Ei toimi msg.terraformer.status=Tila msg.terraformer.pressure=Paine -msg.biomescanner.gas=nyehhh, Kaasuista, eik? +msg.biomescanner.gas=nyehhh, Kaasuista, eikö? msg.biomescanner.star=Jos vain minun sensoreilla olisi aurinkolasit -msg.gravitycontroller.radius=Sde: +msg.gravitycontroller.radius=Säde: msg.gravitycontroller.targetgrav=Tavoitepainovoima: msg.gravitycontroller.none=Asettamaton msg.gravitycontroller.activeset=Aktiivinen: aseta -msg.gravitycontroller.activeadd=Aktiivinen: lis +msg.gravitycontroller.activeadd=Aktiivinen: lisää msg.gravitycontroller.targetdir.1=Kohde-> msg.gravitycontroller.targetdir.2=Suunta msg.railgun.transfermin=Minimi Siirtokoko msg.spacelaser.reset=Nollaa msg.satctrlcenter.toofar=Liian Kaukana -msg.satctrlcenter.nolink=Ei Linkki... +msg.satctrlcenter.nolink=Ei Linkkiä... msg.satctrlcenter.info=Info: msg.satctrlcenter.destroysat=Tuhoa Satelliitti -msg.satctrlcenter.connect=Yhdist! +msg.satctrlcenter.connect=Yhdistä! msg.satbuilder.writesecondchip=Kirjoita toiselle sirulle msg.dockingport.target=Kohde Id msg.dockingport.me=Minun Id @@ -370,113 +370,119 @@ msg.warpmon.tab.tracking=Planeettatarkkailu msg.warpmon.selectplanet=Valitse planeetta msg.warpmon.corestatus=Ytimen Tila: msg.warpmon.anchored=Asema on ankkuroitu! -msg.warpmon.nowhere=Ei ole minnekkn minne menn +msg.warpmon.nowhere=Ei ole minnekkään minne mennä msg.warpmon.missingart=Artefakti Puuttuu msg.warpmon.ready=Valmis! msg.warpmon.notready=Ei ole Valmis msg.warpmon.warp=Hyperavaruus! msg.warpmon.fuelcost=Polttoainekustannus: msg.warpmon.fuel=Polttoaine: -msg.warpmon.dest=Mrnp: +msg.warpmon.dest=Määränpää: msg.warpmon.na=N/A msg.warpmon.search=Etsi planeetta msg.warpmon.chip=Ohjelmoi sirusta -msg.warpmon.datareq=Tarvitaan 100 jokaista datatyyppi +msg.warpmon.datareq=Tarvitaan 100 jokaista datatyyppiä msg.warpmon.artifact=Artefaktit msg.rocketbuilder.success=Valmis laukaisuun! msg.rocketbuilder.nofuel=Ei tarpeeksi polttoainekapasiteettia! msg.rocketbuilder.noseat=Istuin tai satelliittiasema puuttuu! -msg.rocketbuilder.noengines=Sinulla ei ole tarpeeksi tyntvoimaa! +msg.rocketbuilder.noengines=Sinulla ei ole tarpeeksi työntövoimaa! msg.rocketbuilder.noguidance=Ohjaustietokone Puuttuu msg.rocketbuilder.unscanned=Raketti skannaamaton msg.rocketbuilder.success_station=Valmis! -msg.rocketbuilder.empty=Ei mitn tll +msg.rocketbuilder.empty=Ei mitään täällä msg.rocketbuilder.finished=Rakennus Valmis! -msg.rocketbuild.invalidblock=Ptemtn kuutio! +msg.rocketbuild.invalidblock=Pätemätön kuutio! msg.rocketbuilder.incompletestructure=Virheellinen Laukaisualustarakennus! msg.rocketbuilder.nosatellitehatch=Satelliittiasema Puuttuu msg.rocketbuilder.nosatellitechip=Siru Puuttuu msg.rocketbuilder.outputblocked=Ulostulo aukko tukittu -msg.rocketbuilder.thrust=Tyntvoima +msg.rocketbuilder.thrust=Työntövoima msg.rocketbuilder.weight=Paino msg.rocketbuilder.fuel=Polttoaine msg.rocketbuilder.acc=Kiihtyvyys msg.rocketbuilder.build=Rakenna msg.rocketbuilder.scan=Skannaa -msg.rocketbuild.combinedthrust=Polttoainetyyppej ei voi yhdist! -msg.solar.collectingEnergy=Energiankerys: -msg.solar.cannotcollectEnergy=Ei pysty kermn Energiaa +msg.rocketbuild.combinedthrust=Polttoainetyyppejä ei voi yhdistää! +msg.solar.collectingEnergy=Energiankeräys: +msg.solar.cannotcollectEnergy=Ei pysty keräämään Energiaa msg.asteroidChip.asteroid=Asteroidi -msg.atmanal.atmtype=Ilmakehtyyppi: -msg.atmanal.canbreathe=Hengitettv: +msg.atmanal.atmtype=Ilmakehätyyppi: +msg.atmanal.canbreathe=Hengitettävä: msg.biomechanger.scan=Skannaa Biomi -msg.biomechanger.nosat=Satelliitti ei ole viel laukaistu +msg.biomechanger.nosat=Satelliitti ei ole vielä laukaistu msg.biomechanger.selBiome=Valittu Biomi: -msg.biomechanger.numBiome=Skannattujen Biomien Mr: -msg.itemorescanner.nosat=Satelliitti ei ole viel laukaistu +msg.biomechanger.numBiome=Skannattujen Biomien Määrä: +msg.itemorescanner.nosat=Satelliitti ei ole vielä laukaistu msg.itemorescanner.maxzoom=Maksimi zoomaus: msg.itemorescanner.filter=Pystyy suodattamaan malmeja: msg.itemorescanner.value=Arvo: msg.itemplanetidchip.planetname=Planeetan Nimi: msg.itemplanetidchip.stationid=Asema Id: msg.itemplanetidchip.artifacts=Artefaktit: -msg.vent.trace=Happijlki +msg.vent.trace=Happijälki -msg.itemsatellite.pwr=Shkn Varastointi: -msg.itemsatellite.nopwr=Ei Shkn Varastointia -msg.itemsatellite.pwrgen=Shkn Tuotanto: -msg.itemsatellite.nopwrgen=Ei Shkn Tuotantoa! -msg.itemsatellite.microwavestatus=Shkn Kerys +msg.itemsatellite.pwr=Sähkön Varastointi: +msg.itemsatellite.nopwr=Ei Sähkön Varastointia +msg.itemsatellite.pwrgen=Sähkön Tuotanto: +msg.itemsatellite.nopwrgen=Ei Sähkön Tuotantoa! +msg.itemsatellite.microwavestatus=Sähkön Keräys msg.itemsatellite.data=Datan Varastointi: msg.itemsatellite.nodata=Ei Datan Varastointia! -msg.itemsatellite.empty=Tyhj Runko +msg.itemsatellite.empty=Tyhjä Runko msg.itemsatchip.id=ID: msg.itemsatchip.planet=Planeetta: msg.itemsatchip.planetunk=Planeetta: Tuntematon msg.itemsatchip.sat=Satelliitti: msg.itemsatchip.satlost=Satelliitti: Yhteys Menetetty -msg.sealdetector.sealed=Pitisi pit hyvn eristeen. -msg.sealdetector.notsealmat=Materiaali ei pid eristett. -msg.sealdetector.notsealblock=Kuutio ei pid eristett. -msg.sealdetector.notfullblock=Ilma kiert tmn kuution ymprilt. -msg.sealdetector.fluid=Ilma kuplii tmn kuution lpi. -msg.sealdetector.other=Ilma karkaa tmn kuution lpi. +msg.sealdetector.sealed=Pitäisi pitää hyvän eristeen. +msg.sealdetector.notsealmat=Materiaali ei pidä eristettä. +msg.sealdetector.notsealblock=Kuutio ei pidä eristettä. +msg.sealdetector.notfullblock=Ilma kiertää tämän kuution ympäriltä. +msg.sealdetector.fluid=Ilma kuplii tämän kuution läpi. +msg.sealdetector.other=Ilma karkaa tämän kuution läpi. msg.stationchip.sation=Asema -msg.entity.rocket.descend.1=Paina Vlilynti laskeutuaksesi! +msg.entity.rocket.descend.1=Paina Välilyöntiä laskeutuaksesi! msg.entity.rocket.descend.2=Automaattinen laskeutuminen -msg.entity.rocket.ascend.1=Paina Vlilynti laukaisemiseen! -msg.entity.rocket.ascend.2=Mrnp: +msg.entity.rocket.ascend.1=Paina Välilyöntiä laukaisemiseen! +msg.entity.rocket.ascend.2=Määränpää: msg.entity.rocket.launch=Laukaisu T- -msg.entity.rocket.launch2=Paina [Vlilynti] keskeyttksesi +msg.entity.rocket.launch2=Paina [Välilyönti] keskeyttääksesi msg.entity.rocket.station=Asema msg.entity.rocket.pad=Alusta: msg.entity.rocket.disass=Pura -msg.entity.rocket.seldst=Valitse Mrnp -msg.entity.rocket.clear=Tyhjenn +msg.entity.rocket.seldst=Valitse Määränpää +msg.entity.rocket.clear=Tyhjennä msg.entity.rocket.rcs=RCS Moodi msg.entity.rocket.none=Ei Valittu msg.wirelessTransciever.extract=ota talteen msg.powerunit.rfpertick=FE/t -msg.linker.error.firstMachine=Tmn tytyy olla ensimminen yhdistettv laite! -msg.linker.program=Koordinaatit ohjelmoitu yhdistjn +msg.linker.error.firstMachine=Tämän täytyy olla ensimmäinen yhdistettävä laite! +msg.linker.program=Koordinaatit ohjelmoitu yhdistäjään msg.linker.success=Onnistuneesti Yhdistetty -msg.notenoughpower=Ei Tarpeeksi shk! -msg.empty=Tyhj -msg.yes=kyll +msg.notenoughpower=Ei Tarpeeksi sähköä! +msg.empty=Tyhjä +msg.yes=kyllä msg.no=ei msg.connected=yhdistetty msg.notconnected=ei yhdistetty msg.unprogrammed=Ohjelmoimaton -msg.programfail=Ohjelmointi Eponnistui +msg.programfail=Ohjelmointi Epäonnistui msg.modules=moduulit msg.na=N/A -msg.entityDeployedRocket.notGasGiant=Ei Kaasua Tll -msg.noOxygen=Varoitus: Ilmakehss ei ole happea! -msg.tooHot=Varoitus: Ilmakeh liian kuuma! +msg.entityDeployedRocket.notGasGiant=Ei Kaasua Täällä +msg.noOxygen=Varoitus: Ilmakehässä ei ole happea! +msg.tooHot=Varoitus: Ilmakehä liian kuuma! msg.tooDense=Varoitus: Ilmanpaine liian korkea! msg.muchTooDense=Varoitus: Ilmanpaine kriittisen korkea! -msg.chat.nostation1=Sin hert avaruusasemalla viipyvn tunteen kanssa, ett sinun pitkkantoinen avaruuskvely oli jonkun vanhan kettujumalan paheksuma ja ett olisis typer yritt niin uudelleen ja odottaa erilaisia tuloksia -msg.chat.nostation2=Ehk sinun pitisi ajatella ennen kuin ylitt jlleen selkesti loogisia ja absoluuttisia rajoja ja sitten ptt, ett se oli hyv idea eik sinun vikasi, jos asiat menevt pieleen -msg.chat.nostation3=Sinun tytyy olla avaruusasemalla ollaksesi tss ulottuvuudessa, ja yhtn ei ole luotu! \ No newline at end of file +msg.chat.nostation1=Sinä heräät avaruusasemalla viipyvän tunteen kanssa, että sinun pitkäkantoinen avaruuskävely oli jonkun vanhan kettujumalan paheksuma ja että olisis typerää yrittää niin uudelleen ja odottaa erilaisia tuloksia +msg.chat.nostation2=Ehkä sinun pitäisi ajatella ennen kuin ylität jälleen selkeästi loogisia ja absoluuttisia rajoja ja sitten päättää, että se oli hyvä idea eikä sinun vikasi, jos asiat menevät pieleen +msg.chat.nostation3=Sinun täytyy olla avaruusasemalla ollaksesi tässä ulottuvuudessa, ja yhtään ei ole luotu! + +jei.sb.satellitepreview=Valmis kiertoradalle! +jei.sb.copy.source=Lähde +jei.sb.copy.output=Uusi kopio +jei.sb.assemblyhint=Vähintään yksi aurinkopaneeli +jei.sb.copychiphint=Tee varmuuskopiot! diff --git a/src/main/resources/assets/advancedrocketry/lang/fr_FR.lang b/src/main/resources/assets/advancedrocketry/lang/fr_FR.lang index f863c40f0..f06e2f69b 100644 --- a/src/main/resources/assets/advancedrocketry/lang/fr_FR.lang +++ b/src/main/resources/assets/advancedrocketry/lang/fr_FR.lang @@ -260,3 +260,10 @@ msg.spaceElevator.ascentReady=Paré pour l'ascension msg.spaceElevator.turnedOff=L'ascenseur est hors tension. msg.entityDeployedRocket.notGasGiant=Absence de gaz msg.noOxygen=Avertissement: oxygène non détecté! + +jei.sb.satellitepreview=Prêt pour l’orbite ! +jei.sb.copy.source=Source +jei.sb.copy.output=Nouvelle copie +jei.sb.assemblyhint=Au moins un panneau solaire +jei.sb.copychiphint=Faites des sauvegardes ! + diff --git a/src/main/resources/assets/advancedrocketry/lang/pt_br.lang b/src/main/resources/assets/advancedrocketry/lang/pt_br.lang index 3f63313c3..56020c67a 100644 --- a/src/main/resources/assets/advancedrocketry/lang/pt_br.lang +++ b/src/main/resources/assets/advancedrocketry/lang/pt_br.lang @@ -222,3 +222,11 @@ advancement.suitedUp.desc=Wear a full spacesuit key.controls.advancedrocketry=Advanced Rocketry key.openRocketUI=Open Rocket GUI key.toggleJetpack=Toggle Jetpack + + +jei.sb.satellitepreview=Pronto para a órbita! +jei.sb.copy.source=Fonte +jei.sb.copy.output=Nova cópia +jei.sb.assemblyhint=Pelo menos um painel solar +jei.sb.copychiphint=Faça backups! + diff --git a/src/main/resources/assets/advancedrocketry/lang/ru_RU.lang b/src/main/resources/assets/advancedrocketry/lang/ru_RU.lang index 9c25fc9d4..5d41eca70 100644 --- a/src/main/resources/assets/advancedrocketry/lang/ru_RU.lang +++ b/src/main/resources/assets/advancedrocketry/lang/ru_RU.lang @@ -376,6 +376,7 @@ msg.rocketbuilder.fuel=Топливо msg.rocketbuilder.acc=Точность msg.rocketbuilder.build=Стройка msg.rocketbuilder.scan=Сканирование +msg.rocketbuilder.alreadyassembled=Ракета уже собрана msg.solar.collectingEnergy=Сбор энергии: msg.solar.cannotcollectEnergy=Невозможно собрать энергию msg.asteroidChip.asteroid=Астероид @@ -448,3 +449,10 @@ msg.na=Н/Д commands.weather.always_not_clear=На этой планете не бывает сухо... commands.weather.cannot_rain=Невозможно начать дождь, увы... commands.weather.cannot_thunder=Невозможно включить грозу, увы... + + +jei.sb.satellitepreview=Готово к орбите! +jei.sb.copy.source=Источник +jei.sb.copy.output=Новая копия +jei.sb.assemblyhint=Как минимум одна солнечная панель +jei.sb.copychiphint=Делайте резервные копии! diff --git a/src/main/resources/assets/advancedrocketry/lang/ua_UA.lang b/src/main/resources/assets/advancedrocketry/lang/ua_UA.lang index 8c922dfd4..6321d6af0 100644 --- a/src/main/resources/assets/advancedrocketry/lang/ua_UA.lang +++ b/src/main/resources/assets/advancedrocketry/lang/ua_UA.lang @@ -159,3 +159,10 @@ mission.asteroidmining.name=Вивчення астероїдів key.controls.advancedrocketry=Advanced Rocketry key.toggleJetpack=Ввімкнути реактивний ранець + +jei.sb.satellitepreview=Готово до орбіти! +jei.sb.copy.source=Джерело +jei.sb.copy.output=Нова копія +jei.sb.assemblyhint=Щонайменше одна сонячна панель +jei.sb.copychiphint=Робіть резервні копії! + diff --git a/src/main/resources/assets/advancedrocketry/lang/zh_CN.lang b/src/main/resources/assets/advancedrocketry/lang/zh_CN.lang index 1f9c728f4..6d5379489 100644 --- a/src/main/resources/assets/advancedrocketry/lang/zh_CN.lang +++ b/src/main/resources/assets/advancedrocketry/lang/zh_CN.lang @@ -412,6 +412,7 @@ msg.rocketbuilder.acc=飞行控制中心 msg.rocketbuilder.build=建造 msg.rocketbuilder.scan=扫描 msg.rocketbuild.combinedthrust=燃料类型不能组合! +msg.rocketbuilder.alreadyassembled=火箭已组装 msg.solar.collectingEnergy=收集能量: msg.solar.cannotcollectEnergy=无法收集能量 msg.asteroidChip.asteroid=小行星 @@ -507,3 +508,9 @@ msg.chat.nostation3=你必须在空间站上才能来到这个时空,而现在 commands.weather.always_not_clear=这个星球的天气总是阴暗... commands.weather.cannot_rain=这里无法启动降雨 commands.weather.cannot_thunder=这里无法启动雷暴 + +jei.sb.satellitepreview=准备进入轨道! +jei.sb.copy.source=来源 +jei.sb.copy.output=新副本 +jei.sb.assemblyhint=至少一个太阳能电池板 +jei.sb.copychiphint=请做好备份! diff --git a/src/main/resources/assets/advancedrocketry/models/block/guidancecomputeraccesshatch.json b/src/main/resources/assets/advancedrocketry/models/block/guidancecomputeraccesshatch.json index 64a7115ed..7c3692ed9 100644 --- a/src/main/resources/assets/advancedrocketry/models/block/guidancecomputeraccesshatch.json +++ b/src/main/resources/assets/advancedrocketry/models/block/guidancecomputeraccesshatch.json @@ -1,8 +1,6 @@ { - "parent": "block/orientable", + "parent": "block/cube_all", "textures": { - "top": "advancedrocketry:blocks/guidancecomputeraccesshatch", - "front": "advancedrocketry:blocks/guidancecomputeraccesshatch", - "side": "advancedrocketry:blocks/guidancecomputeraccesshatch" + "all": "advancedrocketry:blocks/guidancecomputeraccesshatch" } }