-
Notifications
You must be signed in to change notification settings - Fork 56
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
d2b835f
commit b7f030b
Showing
19 changed files
with
889 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
package codechicken.lib.block; | ||
|
||
import net.minecraft.world.level.block.Block; | ||
import net.minecraft.world.level.block.state.BlockState; | ||
import net.minecraft.world.level.block.state.StateDefinition; | ||
import net.minecraft.world.level.block.state.properties.Property; | ||
import org.jetbrains.annotations.ApiStatus; | ||
|
||
import java.util.LinkedHashMap; | ||
import java.util.Map; | ||
|
||
import static net.covers1624.quack.util.SneakyUtils.unsafeCast; | ||
|
||
/** | ||
* An abstract {@link Block} implementation providing lazy | ||
* addition of {@link BlockState} {@link Property Properties} | ||
* from any point during the {@link Block} constructor. | ||
* <p> | ||
* Created by covers1624 on 18/7/22. | ||
*/ | ||
@ApiStatus.Experimental | ||
public abstract class LazyStateBlock extends Block { | ||
|
||
private final Map<Property<?>, Comparable<?>> propMap = new LinkedHashMap<>(); | ||
private boolean computedState = false; | ||
|
||
public LazyStateBlock(Properties props) { | ||
super(props); | ||
} | ||
|
||
/** | ||
* Adds a {@link BlockState} {@link Property} to the {@link Block}. | ||
* <p> | ||
* May be called any time during the {@link Block Block's} constructor. | ||
* <p> | ||
* May be called multiple times to replace the default value. | ||
* | ||
* @param prop The property to add. | ||
* @param default_ The default value for this property. | ||
*/ | ||
protected final <T extends Comparable<T>, V extends T> void addProperty(Property<T> prop, V default_) { | ||
if (computedState) throw new IllegalStateException("State has already been computed."); | ||
|
||
propMap.put(prop, default_); | ||
} | ||
|
||
/** | ||
* Called when the default state is resolved as defined by registered properties. | ||
* <p> | ||
* May be used to alter the default state further. | ||
* | ||
* @param state The state. | ||
* @return The modified state. | ||
*/ | ||
protected BlockState processDefault(BlockState state) { | ||
return state; | ||
} | ||
|
||
@Override | ||
public StateDefinition<Block, BlockState> getStateDefinition() { | ||
computeState(); | ||
return super.getStateDefinition(); | ||
} | ||
|
||
@Override | ||
public BlockState defaultBlockState() { | ||
computeState(); | ||
return super.defaultBlockState(); | ||
} | ||
|
||
private void computeState() { | ||
if (computedState) return; | ||
|
||
BlockState defaultState; | ||
// Don't compute a new state container if we don't have any properties. | ||
if (!propMap.isEmpty()) { | ||
StateDefinition.Builder<Block, BlockState> builder = new StateDefinition.Builder<>(this); | ||
propMap.keySet().forEach(builder::add); | ||
stateDefinition = builder.create(Block::defaultBlockState, BlockState::new); | ||
defaultState = stateDefinition.any(); | ||
|
||
for (Map.Entry<Property<?>, Comparable<?>> entry : propMap.entrySet()) { | ||
defaultState = defaultState.setValue(entry.getKey(), unsafeCast(entry.getValue())); | ||
} | ||
} else { | ||
defaultState = stateDefinition.any(); | ||
} | ||
|
||
registerDefaultState(processDefault(defaultState)); | ||
computedState = true; | ||
propMap.clear(); | ||
} | ||
|
||
@Override | ||
protected final void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) { | ||
// Explicitly disallowed. | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
package codechicken.lib.block; | ||
|
||
import codechicken.lib.block.component.PropertyComponent; | ||
import codechicken.lib.block.component.StateAwareComponent; | ||
import codechicken.lib.block.component.data.DataGenComponent; | ||
import net.minecraft.core.BlockPos; | ||
import net.minecraft.world.item.context.BlockPlaceContext; | ||
import net.minecraft.world.level.LevelAccessor; | ||
import net.minecraft.world.level.block.Rotation; | ||
import net.minecraft.world.level.block.state.BlockState; | ||
import net.minecraftforge.data.loading.DatagenModLoader; | ||
import org.jetbrains.annotations.ApiStatus; | ||
import org.jetbrains.annotations.Nullable; | ||
|
||
import java.util.Collections; | ||
import java.util.LinkedList; | ||
import java.util.List; | ||
import java.util.Objects; | ||
|
||
import static net.covers1624.quack.util.SneakyUtils.unsafeCast; | ||
|
||
/** | ||
* A block whose functionality can be extended via modular 'components'. | ||
* <p> | ||
* Created by covers1624 on 17/7/22. | ||
* @see Component | ||
* @see DataGenComponent | ||
* @see StateAwareComponent | ||
*/ | ||
@ApiStatus.Experimental | ||
public abstract class ModularBlock extends LazyStateBlock { | ||
|
||
protected final List<Component> componentList = new LinkedList<>(); | ||
protected final List<DataGenComponent> datagenComponents = new LinkedList<>(); | ||
protected final List<StateAwareComponent> stateComponents = new LinkedList<>(); | ||
|
||
public ModularBlock(Properties props) { | ||
super(props); | ||
} | ||
|
||
public final <T extends Component> T addComponent(T comp) { | ||
comp.block = this; | ||
|
||
if (comp instanceof PropertyComponent<?> propComp) { | ||
addProperty(propComp.property, unsafeCast(propComp.defaultValue)); | ||
} | ||
|
||
if (comp instanceof StateAwareComponent stateComp) { | ||
stateComponents.add(stateComp); | ||
} | ||
|
||
// DataGenComponents don't get added to the main component list. | ||
if (comp instanceof DataGenComponent dataComp) { | ||
// Only added if datagen is currently running. | ||
if (DatagenModLoader.isRunningDataGen()) { | ||
datagenComponents.add(dataComp); | ||
} | ||
return comp; | ||
} | ||
|
||
componentList.add(comp); | ||
return comp; | ||
} | ||
|
||
@Nullable | ||
@Override | ||
public BlockState getStateForPlacement(BlockPlaceContext ctx) { | ||
BlockState state = defaultBlockState(); | ||
for (StateAwareComponent component : stateComponents) { | ||
state = component.getStateForPlacement(state, ctx); | ||
if (state == null) break; | ||
} | ||
return state; | ||
} | ||
|
||
@Override | ||
public BlockState rotate(BlockState state, LevelAccessor level, BlockPos pos, Rotation rotation) { | ||
for (StateAwareComponent component : stateComponents) { | ||
state = component.rotate(state, level, pos, rotation); | ||
} | ||
return state; | ||
} | ||
|
||
public List<DataGenComponent> getDatagenComponents() { return Collections.unmodifiableList(datagenComponents); } | ||
|
||
public static abstract class Component { | ||
|
||
@Nullable | ||
ModularBlock block; | ||
|
||
public ModularBlock getBlock() { | ||
return Objects.requireNonNull(block, "Not yet added to a block."); | ||
} | ||
} | ||
|
||
} |
84 changes: 84 additions & 0 deletions
84
src/main/java/codechicken/lib/block/ModularBlockEntity.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package codechicken.lib.block; | ||
|
||
import codechicken.lib.block.ModularTileBlock.TileComponent; | ||
import net.minecraft.core.BlockPos; | ||
import net.minecraft.nbt.CompoundTag; | ||
import net.minecraft.world.level.block.Block; | ||
import net.minecraft.world.level.block.entity.BlockEntity; | ||
import net.minecraft.world.level.block.entity.BlockEntityType; | ||
import net.minecraft.world.level.block.state.BlockState; | ||
import org.jetbrains.annotations.ApiStatus; | ||
import org.jetbrains.annotations.MustBeInvokedByOverriders; | ||
|
||
import static net.covers1624.quack.util.SneakyUtils.unsafeCast; | ||
|
||
/** | ||
* Created by covers1624 on 19/7/22. | ||
*/ | ||
@ApiStatus.Experimental | ||
public abstract class ModularBlockEntity extends BlockEntity { | ||
|
||
private final ModularTileBlock<?> block; | ||
private final DataComponent[] components; | ||
|
||
public ModularBlockEntity(BlockEntityType<?> tileType, BlockPos pos, BlockState state) { | ||
super(tileType, pos, state); | ||
Block bl = state.getBlock(); | ||
if (!(bl instanceof ModularTileBlock)) { | ||
throw new IllegalStateException("ModularBlockEntity constructed with the incorrect Block! Expected a ModularTileBlock. Got: " + bl.getClass().getName() + " State: " + state); | ||
} | ||
block = (ModularTileBlock<?>) bl; | ||
|
||
components = new DataComponent[block.namedComponents.size()]; | ||
for (TileComponent<?> component : block.namedComponents.values()) { | ||
components[component.id] = component.createData(this); | ||
} | ||
} | ||
|
||
public final <T extends DataComponent> T getData(TileComponent<T> component) { | ||
assert block.namedComponents.get(component.name) == component; | ||
|
||
return unsafeCast(components[component.id]); | ||
} | ||
|
||
@Override | ||
@MustBeInvokedByOverriders | ||
protected void saveAdditional(CompoundTag tag) { | ||
super.saveAdditional(tag); | ||
|
||
for (DataComponent component : components) { | ||
CompoundTag componentTag = new CompoundTag(); | ||
component.save(componentTag); | ||
tag.put(component.tileComponent.name, componentTag); | ||
} | ||
} | ||
|
||
@Override | ||
@MustBeInvokedByOverriders | ||
public void load(CompoundTag tag) { | ||
super.load(tag); | ||
|
||
for (DataComponent component : components) { | ||
if (tag.contains(component.tileComponent.name)) { | ||
component.load(tag.getCompound(component.tileComponent.name)); | ||
} | ||
} | ||
} | ||
|
||
public static abstract class DataComponent { | ||
|
||
protected final ModularBlockEntity tile; | ||
protected final TileComponent<?> tileComponent; | ||
|
||
protected DataComponent(ModularBlockEntity tile, TileComponent<?> tileComponent) { | ||
this.tile = tile; | ||
this.tileComponent = tileComponent; | ||
} | ||
|
||
protected void save(CompoundTag tag) { | ||
} | ||
|
||
protected void load(CompoundTag tag) { | ||
} | ||
} | ||
} |
113 changes: 113 additions & 0 deletions
113
src/main/java/codechicken/lib/block/ModularTileBlock.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package codechicken.lib.block; | ||
|
||
import codechicken.lib.block.ModularBlockEntity.DataComponent; | ||
import net.covers1624.quack.util.LazyValue; | ||
import net.minecraft.core.BlockPos; | ||
import net.minecraft.world.level.Level; | ||
import net.minecraft.world.level.block.EntityBlock; | ||
import net.minecraft.world.level.block.entity.BlockEntity; | ||
import net.minecraft.world.level.block.entity.BlockEntityTicker; | ||
import net.minecraft.world.level.block.entity.BlockEntityType; | ||
import net.minecraft.world.level.block.state.BlockState; | ||
import org.jetbrains.annotations.ApiStatus; | ||
import org.jetbrains.annotations.Nullable; | ||
|
||
import java.util.HashMap; | ||
import java.util.LinkedList; | ||
import java.util.Map; | ||
import java.util.function.Supplier; | ||
|
||
/** | ||
* Created by covers1624 on 18/7/22. | ||
*/ | ||
@ApiStatus.Experimental | ||
public abstract class ModularTileBlock<T extends ModularBlockEntity> extends ModularBlock implements EntityBlock { | ||
|
||
final Map<String, TileComponent<?>> namedComponents = new HashMap<>(); | ||
private final LazyValue<BlockEntityType<T>> type; | ||
|
||
private final TickList clientTicks = new TickList(); | ||
private final TickList serverTicks = new TickList(); | ||
|
||
public ModularTileBlock(Properties props, Supplier<BlockEntityType<T>> typeSupplier) { | ||
super(props); | ||
type = new LazyValue<>(typeSupplier); | ||
} | ||
|
||
public final <C extends TileComponent<?>> C addComponent(String name, C comp) { | ||
if (namedComponents.containsKey(name)) throw new IllegalArgumentException("DataComponent already exists with name:" + name); | ||
|
||
comp.name = name; | ||
comp.id = namedComponents.size(); | ||
namedComponents.put(name, comp); | ||
return addComponent(comp); | ||
} | ||
|
||
@Nullable | ||
@Override | ||
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { | ||
return type.get().create(pos, state); | ||
} | ||
|
||
@Nullable | ||
@Override | ||
@SuppressWarnings ("unchecked") | ||
public final <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state, BlockEntityType<T> type) { | ||
if (type != this.type.get()) return null; | ||
|
||
if (level.isClientSide) { | ||
return (BlockEntityTicker<T>) clientTicks.compileTicker(); | ||
} | ||
|
||
return (BlockEntityTicker<T>)serverTicks.compileTicker(); | ||
} | ||
|
||
public static abstract class TileComponent<D extends DataComponent> extends Component { | ||
|
||
String name; | ||
int id; | ||
|
||
protected abstract D createData(ModularBlockEntity ent); | ||
} | ||
|
||
private static class TickList { | ||
|
||
private final LinkedList<BlockEntityTicker<?>> tickers = new LinkedList<>(); | ||
@Nullable | ||
private BlockEntityTicker<?> compiled; | ||
|
||
private void addTickerFirst(BlockEntityTicker<?> pre) { | ||
assert compiled == null : "Unable to hot-add new tickers."; | ||
|
||
tickers.addFirst(pre); | ||
} | ||
|
||
private void addTicker(BlockEntityTicker<?> ticker) { | ||
assert compiled == null : "Unable to hot-add new tickers."; | ||
|
||
tickers.add(ticker); | ||
} | ||
|
||
@Nullable | ||
private BlockEntityTicker<?> compileTicker() { | ||
if (compiled != null) return compiled; | ||
if (tickers.isEmpty()) return null; | ||
|
||
if (tickers.size() == 1) { | ||
compiled = tickers.getFirst(); | ||
tickers.clear(); | ||
} else { | ||
@SuppressWarnings ("unchecked") | ||
BlockEntityTicker<BlockEntity>[] tickers = this.tickers.toArray(new BlockEntityTicker[0]); | ||
this.tickers.clear(); | ||
compiled = (level, pos, state, tile) -> { | ||
for (BlockEntityTicker<BlockEntity> ticker : tickers) { | ||
ticker.tick(level, pos, state, tile); | ||
} | ||
}; | ||
} | ||
|
||
return compiled; | ||
} | ||
} | ||
} |
Oops, something went wrong.