Skip to content

Commit

Permalink
Experimental modular block system.
Browse files Browse the repository at this point in the history
  • Loading branch information
covers1624 committed Sep 8, 2023
1 parent d2b835f commit b7f030b
Show file tree
Hide file tree
Showing 19 changed files with 889 additions and 1 deletion.
98 changes: 98 additions & 0 deletions src/main/java/codechicken/lib/block/LazyStateBlock.java
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.
}
}
96 changes: 96 additions & 0 deletions src/main/java/codechicken/lib/block/ModularBlock.java
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 src/main/java/codechicken/lib/block/ModularBlockEntity.java
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 src/main/java/codechicken/lib/block/ModularTileBlock.java
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;
}
}
}
Loading

0 comments on commit b7f030b

Please sign in to comment.