Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
### Changed

- Players now cannot captchalogue items if they contain certain nested item components. This replaces the bugged maximum data system introduced in the last update
- Uranium fuel is now data driven
- Uranium power is now a neoforge capability, add-ons will have to update their machines

### Fixed

- Fixed players starting off with no captcha cards

### Contributors for this release

- Dweblenod
- Dweblenod, medsal15

## [1.21.1-1.14.0.0] - 2026-04-13

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"values": {
"minestuck:raw_uranium": {
"uranium_power": 32
},
"minestuck:uranium_block": {
"uranium_power": 320
}
}
}
49 changes: 49 additions & 0 deletions src/main/java/com/mraof/minestuck/api/uranium/IUraniumHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.mraof.minestuck.api.uranium;

import net.neoforged.neoforge.energy.IEnergyStorage;

/**
* Works similarly to {@link IEnergyStorage} but with uranium
*/
public interface IUraniumHandler
{
/**
* Adds power to the storage. Returns the amount of power that was accepted.
*
* @param toReceive The amount of power being received.
* @param simulate If true, the insertion will only be simulated, meaning {@link #getUraniumStored()} will not change.
* @return Amount of power that was (or would have been, if simulated) accepted by the storage.
*/
int receiveUranium(int toReceive, boolean simulate);

/**
* Removes power from the storage. Returns the amount of power that was removed.
*
* @param toExtract The amount of power being extracted.
* @param simulate If true, the extraction will only be simulated, meaning {@link #getUraniumStored()} will not change.
* @return Amount of power that was (or would have been, if simulated) extracted from the storage.
*/
int extractUranium(int toExtract, boolean simulate);

/**
* Returns the amount of power currently stored.
*/
int getUraniumStored();

/**
* Returns the maximum amount of power that can be stored.
*/
int getMaxUraniumStored();

/**
* Returns if this storage can have power extracted.
* If this is false, then any calls to extractUranium will return 0.
*/
boolean canExtractUranium();

/**
* Used to determine if this storage can receive power.
* If this is false, then any calls to receiveUranium will return 0.
*/
boolean canReceiveUranium();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.mraof.minestuck.api.uranium;

import java.util.function.Consumer;
import java.util.function.Supplier;

public class SimpleUraniumHandler implements IUraniumHandler
{
private final Supplier<Integer> maxGetter;
private final Supplier<Integer> powerGetter;
private final Consumer<Integer> powerSetter;

public SimpleUraniumHandler(Supplier<Integer> max, Supplier<Integer> powerGetter, Consumer<Integer> powerSetter)
{
this.maxGetter = max;
this.powerGetter = powerGetter;
this.powerSetter = powerSetter;
}

@Override
public int receiveUranium(int toReceive, boolean simulate)
{
int power = powerGetter.get();
int toAdd = Math.min(toReceive, maxGetter.get() - power);

if(!simulate) powerSetter.accept(power + toAdd);

return toAdd;
}

@Override
public int extractUranium(int toExtract, boolean simulate)
{
int power = powerGetter.get();
int toRemove = Math.min(toExtract, power);

if(!simulate) powerSetter.accept(power - toRemove);

return toRemove;
}

@Override
public int getUraniumStored()
{
return powerGetter.get();
}

@Override
public int getMaxUraniumStored()
{
return maxGetter.get();
}

@Override
public boolean canExtractUranium()
{
return true;
}

@Override
public boolean canReceiveUranium()
{
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.mraof.minestuck.api.uranium;

import com.mraof.minestuck.Minestuck;

import net.minecraft.core.Direction;
import net.neoforged.neoforge.capabilities.BlockCapability;
import net.neoforged.neoforge.capabilities.EntityCapability;
import net.neoforged.neoforge.capabilities.ItemCapability;

public final class UraniumCapabilities
{
public static BlockCapability<IUraniumHandler, Direction> BLOCK = BlockCapability.createSided(Minestuck.id("uranium"), IUraniumHandler.class);
public static EntityCapability<IUraniumHandler, Direction> ENTITY = EntityCapability.createSided(Minestuck.id("uranium"), IUraniumHandler.class);
public static ItemCapability<IUraniumHandler, Void> ITEM = ItemCapability.createVoid(Minestuck.id("uranium"), IUraniumHandler.class);
}
48 changes: 48 additions & 0 deletions src/main/java/com/mraof/minestuck/api/uranium/UraniumPower.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.mraof.minestuck.api.uranium;

import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import com.mraof.minestuck.Minestuck;

import net.minecraft.core.registries.Registries;
import net.minecraft.util.ExtraCodecs;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.registries.datamaps.DataMapType;
import net.neoforged.neoforge.registries.datamaps.RegisterDataMapTypesEvent;

@EventBusSubscriber(modid = Minestuck.MOD_ID, bus = EventBusSubscriber.Bus.MOD)
public record UraniumPower(int power)
{
public static final Codec<UraniumPower> POWER_CODEC = ExtraCodecs.POSITIVE_INT.xmap(UraniumPower::new, UraniumPower::power);

public static final Codec<UraniumPower> CODEC = Codec.withAlternative(RecordCodecBuilder.create(instance -> instance.group(ExtraCodecs.POSITIVE_INT.fieldOf("uranium_power").forGetter(UraniumPower::power)).apply(instance, UraniumPower::new)), POWER_CODEC);

public static final DataMapType<Item, UraniumPower> URANIUM_POWER_MAP = DataMapType.builder(Minestuck.id("uranium_power"), Registries.ITEM, CODEC).synced(CODEC, true).build();

@SubscribeEvent
public static void registerDataMap(final RegisterDataMapTypesEvent event)
{
event.register(URANIUM_POWER_MAP);
}

/**
* Returns the available uranium power for a single item in the stack
*/
public static int getUraniumPower(ItemStack stack)
{
var power = stack.getItemHolder().getData(UraniumPower.URANIUM_POWER_MAP);
if(power == null) return 0;
return power.power();
}

/**
* Checks if the stack can provide uranium power
*/
public static boolean hasUraniumPower(ItemStack stack)
{
return stack.getItemHolder().getData(UraniumPower.URANIUM_POWER_MAP) != null;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.mraof.minestuck.blockentity;

import com.mraof.minestuck.Minestuck;
import com.mraof.minestuck.api.uranium.UraniumCapabilities;
import com.mraof.minestuck.block.AspectTreeBlocks;
import com.mraof.minestuck.block.MSBlocks;
import com.mraof.minestuck.block.SkaiaBlocks;
Expand Down Expand Up @@ -132,5 +133,10 @@ public static void registerCapabilities(RegisterCapabilitiesEvent event)
event.registerBlockEntity(Capabilities.ItemHandler.BLOCK, MSBlockEntityTypes.GRIST_WIDGET.get(), GristWidgetBlockEntity::getItemHandler);
event.registerBlockEntity(Capabilities.ItemHandler.BLOCK, MSBlockEntityTypes.URANIUM_COOKER.get(), UraniumCookerBlockEntity::getItemHandler);
event.registerBlockEntity(Capabilities.ItemHandler.BLOCK, MSBlockEntityTypes.ANTHVIL.get(), AnthvilBlockEntity::getItemHandler);

event.registerBlockEntity(UraniumCapabilities.BLOCK, MSBlockEntityTypes.SENDIFICATOR.get(), SendificatorBlockEntity::getUraniumHandler);
event.registerBlockEntity(UraniumCapabilities.BLOCK, MSBlockEntityTypes.URANIUM_COOKER.get(), UraniumCookerBlockEntity::getUraniumHandler);
event.registerBlockEntity(UraniumCapabilities.BLOCK, MSBlockEntityTypes.ANTHVIL.get(), AnthvilBlockEntity::getUraniumHandler);
event.registerBlockEntity(UraniumCapabilities.BLOCK, MSBlockEntityTypes.POWER_HUB.get(), PowerHubBlockEntity::getUraniumHandler);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
import com.mraof.minestuck.api.alchemy.GristType;
import com.mraof.minestuck.api.alchemy.GristTypes;
import com.mraof.minestuck.api.alchemy.recipe.GristCostRecipe;
import com.mraof.minestuck.api.uranium.IUraniumHandler;
import com.mraof.minestuck.api.uranium.SimpleUraniumHandler;
import com.mraof.minestuck.api.uranium.UraniumPower;
import com.mraof.minestuck.blockentity.MSBlockEntityTypes;
import com.mraof.minestuck.inventory.AnthvilMenu;
import com.mraof.minestuck.player.GristCache;
Expand Down Expand Up @@ -35,13 +38,20 @@
/**
* Mends an item at the cost of uranium power and grist. The grist used is the one with the highest value impact (weighted against build grist)
*/
public class AnthvilBlockEntity extends MachineProcessBlockEntity implements MenuProvider, UraniumPowered
public class AnthvilBlockEntity extends MachineProcessBlockEntity implements MenuProvider
{
public static final String TITLE = "container.minestuck.anthvil";
public static final short MAX_FUEL = 128;
public static final short MEND_FUEL_COST = 5;

private short fuel = 0;
private short fuel;
private final IUraniumHandler uraniumHandler = new SimpleUraniumHandler(() -> (int) MAX_FUEL, () -> (int) this.fuel, fuel -> this.fuel = (short) ((int) fuel))
{
public boolean canExtractUranium()
{
return false;
}
};

private final DataSlot fuelHolder = new DataSlot()
{
Expand Down Expand Up @@ -102,15 +112,18 @@ public static void attemptMendAndRefuel(AnthvilBlockEntity anthvil, ServerPlayer
Level level = anthvil.level;
ItemStackHandler itemHandler = anthvil.itemHandler;
ItemStack slotStack = itemHandler.getStackInSlot(0);
ItemStack fuel = itemHandler.getStackInSlot(1);
GristCache playerCache = GristCache.get(player);

if(level == null || !isMendableItem(slotStack))
return;

if(anthvil.canBeRefueled() && itemHandler.getStackInSlot(1).is(ExtraModTags.Items.URANIUM_CHUNKS))
if(anthvil.canBeRefueled(fuel))
{
anthvil.addFuel((short) FUEL_INCREASE);
itemHandler.extractItem(1, 1, false);
anthvil.addFuel(fuel);
ItemStack taken = itemHandler.extractItem(1, 1, false);
ItemStack remainder = taken.getCraftingRemainingItem();
if(!remainder.isEmpty() && !player.getInventory().add(remainder)) player.drop(remainder, false);
}

GristSet pickedGrist = mendingGrist(level, slotStack);
Expand Down Expand Up @@ -173,21 +186,16 @@ private static double getModifiedGristValue(GristAmount grist)
/**
* Checks that fuel can be added without any excess/wasted points being attributed
*/
public boolean canBeRefueled()
public boolean canBeRefueled(ItemStack fuelStack)
{
return fuel <= MAX_FUEL - FUEL_INCREASE;
int amount = UraniumPower.getUraniumPower(fuelStack);
return fuel + amount <= MAX_FUEL;
}

@Override
public void addFuel(short fuelAmount)
public void addFuel(ItemStack fuelStack)
{
fuel += fuelAmount;
}

@Override
public boolean atMaxFuel()
{
return fuel >= MAX_FUEL;
int amount = UraniumPower.getUraniumPower(fuelStack);
fuel += amount;
}

@Nullable
Expand All @@ -204,6 +212,12 @@ public IItemHandler getItemHandler(@Nullable Direction side)
return new RangedWrapper(itemHandler, 1, 2);
}

@Nullable
public IUraniumHandler getUraniumHandler(@Nullable Direction side)
{
return uraniumHandler;
}

@Nullable
@Override
public AbstractContainerMenu createMenu(int windowId, Inventory playerInventory, Player player)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public abstract class MachineProcessBlockEntity extends BlockEntity
{
protected final ItemStackHandler itemHandler = createItemHandler();

@Deprecated
public static final int FUEL_INCREASE = 32; //how many units of fuel a chunk of uranium adds to a machine powered by it, used by Sendificator and UraniumCooker

protected MachineProcessBlockEntity(BlockEntityType<?> blockEntityType, BlockPos pos, BlockState state)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
package com.mraof.minestuck.blockentity.machine;

import javax.annotation.Nullable;

import com.mraof.minestuck.api.uranium.IUraniumHandler;
import com.mraof.minestuck.api.uranium.SimpleUraniumHandler;
import com.mraof.minestuck.api.uranium.UraniumCapabilities;
import com.mraof.minestuck.blockentity.MSBlockEntityTypes;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.HolderLookup;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
Expand All @@ -16,7 +22,14 @@ public class PowerHubBlockEntity extends BlockEntity

public static final short MAX_POWER = 256;

private short power = 0;
private short power;
private final IUraniumHandler powerHandler = new SimpleUraniumHandler(() -> (int) MAX_POWER, () -> (int) this.power, power -> this.power = (short) ((int) power))
{
public boolean canReceiveUranium()
{
return false;
}
};

public PowerHubBlockEntity(BlockPos pos, BlockState state)
{
Expand Down Expand Up @@ -53,6 +66,12 @@ public static void serverTick(Level level, BlockPos pos, BlockState state, Power
poweredBlockEntity.addFuel((short) 1);
blockEntity.changePower(-1);
}
var capability = UraniumCapabilities.BLOCK.getCapability(level, pos.above(), null, null, null);
if(capability != null && capability.canReceiveUranium() && capability.getUraniumStored() < capability.getMaxUraniumStored())
{
int inserted = capability.receiveUranium(blockEntity.getPower(), false);
blockEntity.changePower(-inserted);
}
}
}

Expand All @@ -71,13 +90,19 @@ public void increasePower()

public short getPower()
{
return power;
return (short) power;
}

private void changePower(int amount)
{
this.power += (short) amount;
power += amount;

this.setChanged();
}

@Nullable
public IUraniumHandler getUraniumHandler(@Nullable Direction side)
{
return powerHandler;
}
}
Loading
Loading