Create

Create

86M Downloads

Inconsistent heat source API use

Da-Technomancer opened this issue · 6 comments

commented

Describe the Suggestion

Change the basin to use the same heat source API as boiler heaters.

Screenshots and Videos

No response

Additional Context

Create has an API that lets other mods register heat sources with conditions, shown here:

/**
* A return value of {@code -1} represents no heat.
* A return value of {@code 0} represents passive heat.
* All other positive values are used as the amount of active heat.
*/
public static float getActiveHeat(Level level, BlockPos pos, BlockState state) {
Heater heater = BLOCK_HEATERS.get(state.getBlock());
if (heater != null) {
return heater.getActiveHeat(level, pos, state);
}
for (HeaterProvider provider : GLOBAL_HEATERS) {
heater = provider.getHeater(level, pos, state);
if (heater != null) {
return heater.getActiveHeat(level, pos, state);
}
}
return -1;
}

Create also has a tag that lets blocks be registered as a heat source with no conditions, AllTags.AllBlockTags.PASSIVE_BOILER_HEATERS.

However, some things in Create that check heat sources use both the tag and the API (in BoilerHeaters.java), while others only use the tag and special-case the blaze burner (such as basins, here:

public static HeatLevel getHeatLevelOf(BlockState state) {
if (state.hasProperty(BlazeBurnerBlock.HEAT_LEVEL))
return state.getValue(BlazeBurnerBlock.HEAT_LEVEL);
return AllTags.AllBlockTags.PASSIVE_BOILER_HEATERS.matches(state) ? HeatLevel.SMOULDERING : HeatLevel.NONE;
}
).
As a result, boilers can have custom heat sources added through an API with arbitrary conditions, while basin crafting can't.

commented

I agree that basin heating should have an API, but it doesn't make sense to use the boiler heater API because it returns passive/active heat while basins use the HeatLevel.

commented

I would be happy to code it, it sounds cool! Do you have any ideas as to how this should be implemented? (Pepper)

commented

Is there any progression on this?

commented

Not right now. I will work on this at some point but I’m not sure exactly how this should work and I also have other projects I’m working on. But yes, I will work on it eventually.

commented

Just my first thought:

An Interface for the heat providing Blocks (maybe Entities too?)

public interface HeatProvider {
    HeatLevel getHeatLevel(Level level, BlockState state, BlockPos pos, BlockState targetState, BlockPos targetPos);

    int getHeatRange(Level level, BlockState state, BlockPos pos);

    default boolean isInHeatRange(Level level, BlockPos pos, BlockPos targetPos) {
        return pos.closerThan(targetPos, getHeatRange(level, level.getBlockState(pos), pos));
    }
}

Something that holds the Data

public class SavedHeatData extends SavedData {
    private static final String key = "create:heat";
    private final Map<BlockPos, HeatProvider> providerMap = new HashMap<>();
    private final Level level;

    /**
     * A way to access the {@link SavedHeatData}
     *
     * @param level {@link ServerLevel} containing the {@link SavedHeatData}
     */
    public static SavedHeatData load(ServerLevel level) {
        return level.getDataStorage().computeIfAbsent(tag -> load(level, tag), () -> new SavedHeatData(level), key);
    }

    private static SavedHeatData load(Level level, CompoundTag tag) {
        return new SavedHeatData(level, tag);
    }

    public SavedHeatData(Level level) {
        this.level = level;
    }

    public SavedHeatData(Level level, CompoundTag tag) {
        this(level);
        if (tag.contains("heat_provider_positions", Tag.TAG_LIST)) {
            ListTag positionList = tag.getList("heat_provider_positions", Tag.TAG_COMPOUND);
            positionList.forEach(compound -> {
                CompoundTag data = (CompoundTag) compound;
                BlockPos pos = new BlockPos(data.getInt("x"), data.getInt("y"), data.getInt("z"));
                BlockState state = level.getBlockState(pos);

                if (state.getBlock() instanceof HeatProvider provider) {
                    providerMap.put(pos, provider);
                }
            });
        }
    }

    @Override
    public CompoundTag save(CompoundTag pCompoundTag) {
        ListTag positionList = new ListTag();

        providerMap.forEach((pos, provider) -> {
            CompoundTag data = new CompoundTag();
            data.putInt("x", pos.getX());
            data.putInt("y", pos.getY());
            data.putInt("z", pos.getZ());
            positionList.add(data);
        });

        pCompoundTag.put("heat_provider_positions", positionList);
        return pCompoundTag;
    }

    /**
     * Can be used to "register" a {@link HeatProvider} in the current level.
     *
     * @param pos      position of the {@link HeatProvider}
     * @param provider the {@link HeatProvider} itself
     */
    public void addHeatProvider(BlockPos pos, HeatProvider provider) {
        this.providerMap.put(pos, provider);
        setDirty();
    }

    /**
     * Can be used to locate the nearest {@link HeatProvider} to check the {@link HeatLevel} with {@link HeatProvider#getHeatLevel(Level, BlockState, BlockPos, BlockState, BlockPos)}
     *
     * @param targetPos position of the heat "consuming" block
     * @return Optional containing nearest the {@link BlockPos} and {@link HeatProvider} or {@link Optional#empty()} if no {@link HeatProvider} is in range
     */
    public Optional<Pair<BlockPos, HeatProvider>> findClosest(BlockPos targetPos) {
        return this.providerMap.entrySet()
                .stream()
                .filter(entry -> entry.getValue().isInHeatRange(this.level, entry.getKey(), targetPos))
                .min((o1, o2) -> {
                    double distance1 = o1.getKey().distSqr(targetPos);
                    double distance2 = o2.getKey().distSqr(targetPos);
                    return Double.compare(distance1, distance2);
                })
                .map(entry -> Pair.of(entry.getKey(), entry.getValue()));
    }
}

And an example block implementing this stuff:

public class ExampleHeatProviderBlock extends Block implements HeatProvider {
    public ExampleHeatProviderBlock(Properties properties) {
        super(properties);
    }

    @Override
    public HeatLevel getHeatLevel(Level level, BlockState state, BlockPos pos, BlockState targetState, BlockPos targetPos) {
        return HeatLevel.FADING;
    }

    @Override
    public int getHeatRange(Level level, BlockState state, BlockPos pos) {
        return 16;
    }

    @Override
    public void onPlace(BlockState pState, Level pLevel, BlockPos pPos, BlockState pOldState, boolean pMovedByPiston) {
        super.onPlace(pState, pLevel, pPos, pOldState, pMovedByPiston);
        if (pLevel instanceof ServerLevel level) {
            SavedHeatData.load(level).addHeatProvider(pPos, this);
        }
    }
}

Heat consuming objects could just check for the closest source of heat by using SavedHeatData.load(ServerLevel).findClosest(BlockPos)

Could something like this work?

commented

It looks like it could work! I’ll try it out. Thanks!