Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Block Extension QSL #100

Draft
wants to merge 15 commits into
base: 1.18
Choose a base branch
from
Draft
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Copyright 2022 QuiltMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.quiltmc.qsl.block.extensions.api;

import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.state.property.Property;
import org.quiltmc.qsl.block.extensions.impl.BlockExtension;
import org.quiltmc.qsl.block.extensions.impl.QuiltBlockImpl;
import org.quiltmc.qsl.block.extensions.mixin.BlockMixin;

import java.util.function.Function;

public class QuiltBlock {

/**
* Creates a proxy from one or multiple blocks (will be temporarily stocked in a {@link ThreadLocal} and used in {@link BlockMixin}).
* @param settings Block settings of the block you want to add Proxies for (dummy usage, to be able to insert logic at {@code super()} call.
* @param proxies Array of proxy blocks to be "merged" together as one (property merging).
* @return Same Block settings as above.
*/
public static <S extends AbstractBlock.Settings> S createProxy(S settings, Block... proxies) {
QuiltBlockImpl.PROXY_BLOCKS_TEMP_CONTAINER.set(proxies);
return settings;
}

/**
* Creates a default state {@link BlockState} for blocks that have Proxies.
* @param ownerBlock The block that "owns" the proxies ot create the state from.
* @param proxyState A {@link Function} taking a Block as its input, and outputting a Blockstate, this is used to tell each proxy what state to give this method.
* @return A {@link BlockState} usable as a default state for our block.
*/
public static BlockState mergeStates(Block ownerBlock, Function<Block ,BlockState> proxyState) {
var proxies = ((BlockExtension) ownerBlock).getProxies();
var ownerState = ownerBlock.getDefaultState();
for (var proxy : proxies) {
var appliedProxyState = proxyState.apply(proxy);
for (Property<?> property : appliedProxyState.getProperties()) {
ownerState = QuiltBlock.copyProperty(appliedProxyState, ownerState, property);
}
}
return ownerState;
}

/**
* Copies a {@code property} from a {@code source} to a {@code target} {@link BlockState}.
* Shamelessly copied from {@link Block} because it just works.
* @param source The source {@link BlockState}
* @param target The target {@link BlockState}
* @param property The {@link Property} to copy from {@code source} to {@code target}
* @return A {@link BlockState} where all properties have been transferred from {@code source} to {@code target}
* @param <T> The {@link Property}'s type
*/
public static <T extends Comparable<T>> BlockState copyProperty(BlockState source, BlockState target, Property<T> property) {
if (target.contains(property)) {
return target.with(property, source.get(property));
}
else {
QuiltBlockImpl.LOGGER.warn("Failed to copy properties from {}, {} does not exist in {}", source, property, target);
return target;
}
}

/**
* Safely get a property's value from our {@code Block}'s current {@code state}
* @param state The state we want to get the value from.
* @param property The property we're looking for.
* @return The value of the targeted property, OR a typed null if the peroperty doesn't exist in this block.
* @param <T> The type of the target property.
*/
public static <T extends Comparable<T>> T getProperty(BlockState state, Property<T> property) {
if (state.contains(property)) {
return state.get(property);
} else {
QuiltBlockImpl.LOGGER.warn("Cannot get property {} as it does not exist in {}, falling back on null", property, state.getBlock());
return (T) null;
}
}

/**
* Checks if this block has been built using {@code blockClass} as a Proxy
* @param blockClass The Block class to check this block against
* @return boolean telling us weither this block has {@code blockClass} as a proxy or not
*/
public boolean isInstanceOfProxy(Class<? extends Block> blockClass) {
return ((BlockExtension) this).isInstanceOf(blockClass);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright 2022 QuiltMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.quiltmc.qsl.block.extensions.impl;

import net.minecraft.block.Block;

public interface BlockExtension {
Block getProxyOfType(Class<? extends Block> type);
boolean isInstanceOf(Class<? extends Block> type);
Block[] getProxies();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2022 QuiltMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.quiltmc.qsl.block.extensions.impl;

import net.minecraft.block.Block;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

//TODO: add a way to make any item place-able
//TODO: access widen stairs and other privated blocks by default
public class QuiltBlockImpl {
public static final Logger LOGGER = LoggerFactory.getLogger("Quilt Block Impl");
public static final ThreadLocal<Block[]> PROXY_BLOCKS_TEMP_CONTAINER = new ThreadLocal<>();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* Copyright 2022 QuiltMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.quiltmc.qsl.block.extensions.mixin;

import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.state.StateManager;
import net.minecraft.state.property.Property;
import org.apache.commons.compress.utils.Lists;
import org.quiltmc.qsl.block.extensions.impl.BlockExtension;
import org.quiltmc.qsl.block.extensions.impl.QuiltBlockImpl;
import org.slf4j.Logger;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;

import java.util.LinkedList;

@Mixin(Block.class)
public abstract class BlockMixin implements BlockExtension {

@Shadow protected abstract Block asBlock();
@Shadow @Final private static Logger LOGGER;
private final @Unique LinkedList<Block> proxies = new LinkedList<>();

@Inject(method = "<init>",
at = @At(value = "INVOKE", target = "Lnet/minecraft/state/StateManager$Builder;build(Ljava/util/function/Function;Lnet/minecraft/state/StateManager$Factory;)Lnet/minecraft/state/StateManager;"),
locals = LocalCapture.CAPTURE_FAILHARD)
private void injectProxyProperties(AbstractBlock.Settings settings, CallbackInfo ci, StateManager.Builder<Block, BlockState> builder) {
if (QuiltBlockImpl.PROXY_BLOCKS_TEMP_CONTAINER.get() != null && QuiltBlockImpl.PROXY_BLOCKS_TEMP_CONTAINER.get().length > 0){
var properties = new ArrayList<Property>();
for (var proxyBlock : QuiltBlockImpl.PROXY_BLOCKS_TEMP_CONTAINER.get()) {
for (var property : proxyBlock.getStateManager().getProperties()) {
if (!properties.contains(property)) {
properties.add(property);
}
}
this.proxies.add(proxyBlock);
}
builder.add(properties.toArray(Property[]::new));
QuiltBlockImpl.PROXY_BLOCKS_TEMP_CONTAINER.remove();
}
}

@Override
public boolean isInstanceOf(Class<? extends Block> type) {
if (!this.proxies.isEmpty()){
for (var proxy : this.proxies) {
if (proxy.getClass().isAssignableFrom(type)) {
return true;
}
}
}
return false;
}

@SuppressWarnings("ConstantConditions")
@Override
public Block getProxyOfType(Class<? extends Block> type) {
var result = (Block) null;
if (!this.proxies.isEmpty()){
for (var proxy : this.proxies) {
if (proxy.getClass().isAssignableFrom(type)) {
result = proxy;
}
}
}
if (result != null) {
return result;
} else {
LOGGER.warn(String.format("%s has no proxy of type %s! Returning self to avoid issues", this.asBlock().toString(), type.getName()));
return (Block) (Object) this;
}
}

@Override
public Block[] getProxies() {
var array = new Block[proxies.size()];
for (int i = 0; i < proxies.size(); i++) {
array[i] = proxies.get(i);
}
return array;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"mixins": [
"AbstractBlockAccessor",
"AbstractBlockSettingsAccessor",
"MaterialBuilderAccessor"
"MaterialBuilderAccessor",
"BlockMixin"
],
"client": [
"client.RenderLayersMixin"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,11 @@

package org.quiltmc.qsl.block.extensions.test;

import net.minecraft.block.Block;
import net.minecraft.block.Blocks;
import net.minecraft.block.GlassBlock;
import net.minecraft.block.MapColor;
import net.minecraft.block.Material;
import net.minecraft.block.*;
import net.minecraft.block.piston.PistonBehavior;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemGroup;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.Registry;

Expand All @@ -36,12 +35,31 @@ public final class Initializer implements ModInitializer {
.build();

public static final Block BLOCK = Registry.register(Registry.BLOCK,
new Identifier("quilt_block_extensions_testmod", "test_block"),
testModId("test_block"),
new GlassBlock(QuiltBlockSettings.copyOf(Blocks.GLASS)
.material(MATERIAL)
.luminance(15)));

public static Identifier testModId(String path) {
return new Identifier("quilt_block_extensions_testmod", path);
}

public static void registerBlockAndItem(Identifier identifier, Block block) {
Registry.register(Registry.BLOCK, identifier, block);
Registry.register(Registry.ITEM, identifier, new BlockItem(block, new Item.Settings().group(ItemGroup.MATERIALS)));

}

public static final Block QUILT_BLOCK = new TestBlock(QuiltBlockSettings.of(Material.STONE), Blocks.OAK_STAIRS, Blocks.QUARTZ_PILLAR);
public static final Block QUILT_BLOCK_2 = new TestBlock(QuiltBlockSettings.of(Material.STONE), Blocks.OAK_SLAB, Blocks.QUARTZ_PILLAR);
public static final Block QUILT_BLOCK_3 = new TestBlock(QuiltBlockSettings.of(Material.STONE), Blocks.ANDESITE_WALL, Blocks.OAK_SLAB);
public static final Block QUILT_BLOCK_4 = new TestBlock(QuiltBlockSettings.of(Material.STONE), Blocks.POTTED_ALLIUM, Blocks.LANTERN);

@Override
public void onInitialize(ModContainer mod) {
registerBlockAndItem(testModId("oak_stairs__quartz_pillar"), QUILT_BLOCK);
registerBlockAndItem(testModId("oak_slab__quartz_pillar"), QUILT_BLOCK_2);
registerBlockAndItem(testModId("andesite_wall__oak_slab"), QUILT_BLOCK_3);
registerBlockAndItem(testModId("potted_allium__lantern"), QUILT_BLOCK_4);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright 2022 QuiltMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.quiltmc.qsl.block.extensions.test;

import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.StairsBlock;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.text.LiteralText;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import org.quiltmc.qsl.block.extensions.api.QuiltBlock;

public class TestBlock extends Block {

public TestBlock(Settings settings, Block... proxies) {
super(QuiltBlock.createProxy(settings, proxies));
this.setDefaultState(QuiltBlock.mergeStates(this, Block::getDefaultState));
}

@Override
public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
var prop = QuiltBlock.getProperty(state, StairsBlock.HALF);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This usage of getProperty doesn't sound useful?

If the stuff that was merged were merged properly, it should already include the property.

if (prop != null) {
player.sendMessage(new LiteralText(prop.asString()), false);
return ActionResult.success(world.isClient);
}
return super.onUse(state, world, pos, player, hand, hit);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 QuiltMC
* Copyright 2021-2022 QuiltMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 QuiltMC
* Copyright 2021-2022 QuiltMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 QuiltMC
* Copyright 2021-2022 QuiltMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 QuiltMC
* Copyright 2021-2022 QuiltMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down
Loading