Inconsistent heat source API use
Da-Technomancer opened this issue · 6 comments
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:
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:
As a result, boilers can have custom heat sources added through an API with arbitrary conditions, while basin crafting can't.
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
.
I would be happy to code it, it sounds cool! Do you have any ideas as to how this should be implemented? (Pepper)
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.
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?