Merge remote-tracking branch 'origin/1.19.2' into 1.19.3

# Conflicts:
#	gradle.properties
This commit is contained in:
modmuss50 2022-11-07 18:36:19 +00:00
commit 28cc6cc789
29 changed files with 727 additions and 29 deletions
fabric-block-api-v1
fabric-entity-events-v1/src
main/java/net/fabricmc/fabric
testmod/java/net/fabricmc/fabric/test/entity/event
fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/entity
fabric-renderer-api-v1
fabric-rendering-data-attachment-v1/src/main/java/net/fabricmc/fabric/api/rendering/data/v1
fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang
gradle.propertiessettings.gradle

View file

@ -0,0 +1,2 @@
archivesBaseName = "fabric-block-api-v1"
version = getSubprojectVersion(project)

View file

@ -0,0 +1,104 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* 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 net.fabricmc.fabric.api.block.v1;
import org.jetbrains.annotations.Nullable;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.world.BlockRenderView;
import net.minecraft.world.World;
/**
* General-purpose Fabric-provided extensions for {@link Block} subclasses.
*
* <p>Note: This interface is automatically implemented on all blocks via Mixin and interface injection.
*/
// Note to maintainers: Functions should only be added to this interface if they are general-purpose enough,
// to be evaluated on a case-by-case basis. Otherwise, they are better suited for more specialized APIs.
public interface FabricBlock {
/**
* Return the current appearance of the block, i.e. which block state this block reports to look like on a given side.
*
* <p>Common implementors are covers and facades, or any other mimic blocks that proxy another block's model.
* These will want to override this method. In that case, make sure to carefully read the implementation guidelines below.
*
* <p>Common consumers are models with connected textures that wish to seamlessly connect to mimic blocks.
* These will want to check the apparent block state using {@link FabricBlockState#getAppearance}.
*
* <p>Generally, the appearance will be queried from a nearby block,
* identified by the optional {@code sourcePos} and {@code sourceState} parameters.
*
* <p>When a block changes appearance, it should trigger a chunk remesh for itself and the adjacent blocks,
* for example by calling {@link World#updateListeners}.
*
* <p>Note: Overriding this method for a block does <strong>not</strong> change how it renders.
* It's up to modded models to check for the appearance of nearby blocks and adjust accordingly.
*
* <h3>Implementation guidelines</h3>
*
* <p>This can be called on the server, where block entity data can be safely accessed,
* and on the client, possibly in a meshing thread, where block entity data is not safe to access!
* Here is an example of how data from a block entity can be handled safely.
* The block entity needs to implement {@code RenderAttachmentBlockEntity} for this to work.
* <pre>{@code @Override
* public BlockState getAppearance(BlockState state, BlockRenderView renderView, BlockPos pos, Direction side, @Nullable BlockState sourceState, @Nullable BlockPos sourcePos) {
* if (renderView instanceof ServerWorld serverWorld) {
* // Server side, ok to use block entity directly!
* BlockEntity blockEntity = serverWorld.getBlockEntity(pos);
*
* if (blockEntity instanceof ...) {
* // Get data from block entity
* return ...;
* }
* } else {
* // Client side, need to use the render attachment!
* RenderAttachedBlockView attachmentView = (RenderAttachedBlockView) renderView;
* Object data = attachmentView.getBlockEntityRenderAttachment(pos);
*
* // Check if data is not null and of the correct type, and use that to determine the appearance
* if (data instanceof ...) {
* // get appearance for side ...
* return ...;
* }
* }
*
* // Example of varying the appearance based on the source pos
* if (sourcePos != null) {
* // get appearance for side ...
* return ...;
* }
*
* // If there is no other appearance, just return the original block state
* return state;
* });
* }</pre>
*
* @param state state of this block, whose appearance is being queried
* @param renderView the world this block is in
* @param pos position of this block, whose appearance is being queried
* @param side the side for which the appearance is being queried
* @param sourceState (optional) state of the block that is querying the appearance, or null if unknown
* @param sourcePos (optional) position of the block that is querying the appearance, or null if unknown
* @return the appearance of the block on the given side; the original {@code state} can be returned if there is no better option
*/
default BlockState getAppearance(BlockState state, BlockRenderView renderView, BlockPos pos, Direction side, @Nullable BlockState sourceState, @Nullable BlockPos sourcePos) {
return state;
}
}

View file

@ -0,0 +1,47 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* 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 net.fabricmc.fabric.api.block.v1;
import org.jetbrains.annotations.Nullable;
import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.world.BlockRenderView;
/**
* General-purpose Fabric-provided extensions for {@link BlockState}, matching the functionality provided in {@link FabricBlock}.
*
* <p>Note: This interface is automatically implemented on all block states via Mixin and interface injection.
*/
public interface FabricBlockState {
/**
* Return the current appearance of the block, i.e. which block state this block reports to look like on a given side.
*
* @param renderView the world this block is in
* @param pos position of this block, whose appearance is being queried
* @param side the side for which the appearance is being queried
* @param sourceState (optional) state of the block that is querying the appearance, or null if unknown
* @param sourcePos (optional) position of the block that is querying the appearance, or null if unknown
* @return the appearance of the block on the given side; the original {@code state} can be returned if there is no better option
* @see FabricBlock#getAppearance
*/
default BlockState getAppearance(BlockRenderView renderView, BlockPos pos, Direction side, @Nullable BlockState sourceState, @Nullable BlockPos sourcePos) {
BlockState self = (BlockState) this;
return self.getBlock().getAppearance(self, renderView, pos, side, sourceState, sourcePos);
}
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* 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 net.fabricmc.fabric.mixin.block;
import org.spongepowered.asm.mixin.Mixin;
import net.minecraft.block.Block;
import net.fabricmc.fabric.api.block.v1.FabricBlock;
@Mixin(Block.class)
public class BlockMixin implements FabricBlock { }

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* 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 net.fabricmc.fabric.mixin.block;
import org.spongepowered.asm.mixin.Mixin;
import net.minecraft.block.BlockState;
import net.fabricmc.fabric.api.block.v1.FabricBlockState;
@Mixin(BlockState.class)
public class BlockStateMixin implements FabricBlockState { }

View file

@ -0,0 +1,12 @@
{
"required": true,
"package": "net.fabricmc.fabric.mixin.block",
"compatibilityLevel": "JAVA_17",
"mixins": [
"BlockMixin",
"BlockStateMixin"
],
"injectors": {
"defaultRequire": 1
}
}

Binary file not shown.

After

(image error) Size: 1.5 KiB

View file

@ -0,0 +1,34 @@
{
"schemaVersion": 1,
"id": "fabric-block-api-v1",
"name": "Fabric Block API (v1)",
"version": "${version}",
"environment": "*",
"license": "Apache-2.0",
"icon": "assets/fabric-block-api-v1/icon.png",
"contact": {
"homepage": "https://fabricmc.net",
"irc": "irc://irc.esper.net:6667/fabric",
"issues": "https://github.com/FabricMC/fabric/issues",
"sources": "https://github.com/FabricMC/fabric"
},
"authors": [
"FabricMC"
],
"depends": {
"fabricloader": ">=0.14.9"
},
"entrypoints": {
},
"description": "Hooks for blocks",
"mixins": [
"fabric-block-api-v1.mixins.json"
],
"custom": {
"fabric-api:module-lifecycle": "stable",
"loom:injected_interfaces": {
"net/minecraft/class_2248": ["net/fabricmc/fabric/api/block/v1/FabricBlock"],
"net/minecraft/class_2680": ["net/fabricmc/fabric/api/block/v1/FabricBlockState"]
}
}
}

View file

@ -0,0 +1,117 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* 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 net.fabricmc.fabric.api.entity.event.v1;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.damage.DamageSource;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
/**
* Various server-side only events related to living entities.
*/
public final class ServerLivingEntityEvents {
/**
* An event that is called when a living entity is going to take damage.
* This is fired from {@link LivingEntity#damage}, before armor or any other mitigation are applied.
* Mods can cancel this to prevent the damage entirely.
*/
public static final Event<AllowDamage> ALLOW_DAMAGE = EventFactory.createArrayBacked(AllowDamage.class, callbacks -> (entity, source, amount) -> {
for (AllowDamage callback : callbacks) {
if (!callback.allowDamage(entity, source, amount)) {
return false;
}
}
return true;
});
/**
* An event that is called when an entity takes fatal damage.
*
* <p>Mods can cancel this to keep the entity alive.
*
* <p>Vanilla checks for entity health {@code <= 0} each tick (with {@link LivingEntity#isDead()}), and kills if true -
* so the entity will still die next tick if this event is cancelled.
* It's assumed that the listener will do something to prevent this, for example, if the entity is a player:
* <ul>
* <li>a minigame mod teleporting the player into a 'respawn room' and setting their health to 20.0</li>
* <li>a mod that changes death mechanics switching the player over to the mod's play-mode, where death doesn't apply</li>
* </ul>
*/
public static final Event<AllowDeath> ALLOW_DEATH = EventFactory.createArrayBacked(AllowDeath.class, callbacks -> (entity, damageSource, damageAmount) -> {
for (AllowDeath callback : callbacks) {
if (!callback.allowDeath(entity, damageSource, damageAmount)) {
return false;
}
}
return true;
});
/**
* An event that is called when a living entity dies.
*/
public static final Event<AfterDeath> AFTER_DEATH = EventFactory.createArrayBacked(AfterDeath.class, callbacks -> (entity, damageSource) -> {
for (AfterDeath callback : callbacks) {
callback.afterDeath(entity, damageSource);
}
});
@FunctionalInterface
public interface AllowDamage {
/**
* Called when a living entity is going to take damage. Can be used to cancel the damage entirely.
*
* <p>The amount corresponds to the "incoming" damage amount, before armor and other mitigations have been applied.
*
* @param entity the entity
* @param source the source of the damage
* @param amount the amount of damage that the entity will take (before mitigations)
* @return true if the damage should go ahead, false to cancel the damage.
*/
boolean allowDamage(LivingEntity entity, DamageSource source, float amount);
}
@FunctionalInterface
public interface AllowDeath {
/**
* Called when a living entity takes fatal damage (before totems of undying can take effect).
*
* @param entity the entity
* @param damageSource the source of the fatal damage
* @param damageAmount the amount of damage that has killed the entity
* @return true if the death should go ahead, false to cancel the death.
*/
boolean allowDeath(LivingEntity entity, DamageSource damageSource, float damageAmount);
}
@FunctionalInterface
public interface AfterDeath {
/**
* Called when a living entity dies. The death cannot be canceled at this point.
*
* @param entity the entity
* @param damageSource the source of the fatal damage
*/
void afterDeath(LivingEntity entity, DamageSource damageSource);
}
private ServerLivingEntityEvents() {
}
}

View file

@ -16,7 +16,6 @@
package net.fabricmc.fabric.api.entity.event.v1;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.server.network.ServerPlayerEntity;
@ -50,18 +49,9 @@ public final class ServerPlayerEvents {
/**
* An event that is called when a player takes fatal damage.
*
* <p>Mods can cancel this to keep the player alive.
*
* <p>Vanilla checks for player health {@code <= 0} each tick (with {@link LivingEntity#isDead()}), and kills if true -
* so the player will still die next tick if this event is cancelled. It's assumed that the listener will do
* something to prevent this, for example:
*
* <ul>
* <li>a minigame mod teleporting the player into a 'respawn room' and setting their health to 20.0</li>
* <li>a mod that changes death mechanics switching the player over to the mod's play-mode, where death doesn't
* apply</li>
* </ul>
* @deprecated Use the more general {@link ServerLivingEntityEvents#ALLOW_DEATH} event instead and check for {@code instanceof ServerPlayerEntity}.
*/
@Deprecated
public static final Event<AllowDeath> ALLOW_DEATH = EventFactory.createArrayBacked(AllowDeath.class, callbacks -> (player, damageSource, damageAmount) -> {
for (AllowDeath callback : callbacks) {
if (!callback.allowDeath(player, damageSource, damageAmount)) {
@ -96,6 +86,10 @@ public final class ServerPlayerEvents {
void afterRespawn(ServerPlayerEntity oldPlayer, ServerPlayerEntity newPlayer, boolean alive);
}
/**
* @deprecated Use the more general {@link ServerLivingEntityEvents#ALLOW_DEATH} event instead and check for {@code instanceof ServerPlayerEntity}.
*/
@Deprecated
@FunctionalInterface
public interface AllowDeath {
/**
@ -111,4 +105,15 @@ public final class ServerPlayerEvents {
private ServerPlayerEvents() {
}
static {
// Forward general living entity event to (older) player-specific event.
ServerLivingEntityEvents.ALLOW_DEATH.register((entity, damageSource, damageAmount) -> {
if (entity instanceof ServerPlayerEntity player) {
return ServerPlayerEvents.ALLOW_DEATH.invoker().allowDeath(player, damageSource, damageAmount);
}
return true;
});
}
}

View file

@ -37,7 +37,6 @@ import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.ActionResult;
import net.minecraft.util.math.BlockPos;
@ -48,7 +47,7 @@ import net.minecraft.world.World;
import net.fabricmc.fabric.api.entity.event.v1.EntitySleepEvents;
import net.fabricmc.fabric.api.entity.event.v1.ServerEntityCombatEvents;
import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents;
import net.fabricmc.fabric.api.entity.event.v1.ServerLivingEntityEvents;
@Mixin(LivingEntity.class)
abstract class LivingEntityMixin {
@ -65,13 +64,21 @@ abstract class LivingEntityMixin {
ServerEntityCombatEvents.AFTER_KILLED_OTHER_ENTITY.invoker().afterKilledOtherEntity((ServerWorld) ((LivingEntity) (Object) this).world, attacker, (LivingEntity) (Object) this);
}
@Redirect(method = "damage", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/LivingEntity;isDead()Z", ordinal = 1))
boolean beforePlayerKilled(LivingEntity livingEntity, DamageSource source, float amount) {
if (livingEntity instanceof ServerPlayerEntity) {
return isDead() && ServerPlayerEvents.ALLOW_DEATH.invoker().allowDeath((ServerPlayerEntity) livingEntity, source, amount);
}
@Inject(method = "onDeath", at = @At(value = "INVOKE", target = "net/minecraft/world/World.sendEntityStatus(Lnet/minecraft/entity/Entity;B)V"))
private void notifyDeath(DamageSource source, CallbackInfo ci) {
ServerLivingEntityEvents.AFTER_DEATH.invoker().afterDeath((LivingEntity) (Object) this, source);
}
return isDead();
@Redirect(method = "damage", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/LivingEntity;isDead()Z", ordinal = 1))
boolean beforeEntityKilled(LivingEntity livingEntity, DamageSource source, float amount) {
return isDead() && ServerLivingEntityEvents.ALLOW_DEATH.invoker().allowDeath(livingEntity, source, amount);
}
@Inject(method = "damage", at = @At(value = "INVOKE", target = "net/minecraft/entity/LivingEntity.isSleeping()Z"), cancellable = true)
private void beforeDamage(DamageSource source, float amount, CallbackInfoReturnable<Boolean> cir) {
if (!ServerLivingEntityEvents.ALLOW_DAMAGE.invoker().allowDamage((LivingEntity) (Object) this, source, amount)) {
cir.setReturnValue(false);
}
}
@Inject(method = "sleep", at = @At("RETURN"))

View file

@ -48,6 +48,7 @@ import net.minecraft.world.World;
import net.fabricmc.fabric.api.entity.event.v1.EntitySleepEvents;
import net.fabricmc.fabric.api.entity.event.v1.ServerEntityCombatEvents;
import net.fabricmc.fabric.api.entity.event.v1.ServerEntityWorldChangeEvents;
import net.fabricmc.fabric.api.entity.event.v1.ServerLivingEntityEvents;
import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents;
@Mixin(ServerPlayerEntity.class)
@ -71,6 +72,11 @@ abstract class ServerPlayerEntityMixin extends LivingEntityMixin {
}
}
@Inject(method = "onDeath", at = @At("TAIL"))
private void notifyDeath(DamageSource source, CallbackInfo ci) {
ServerLivingEntityEvents.AFTER_DEATH.invoker().afterDeath((ServerPlayerEntity) (Object) this, source);
}
/**
* This is called by both "moveToWorld" and "teleport".
* So this is suitable to handle the after event from both call sites.

View file

@ -23,6 +23,7 @@ import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Material;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.item.BlockItem;
@ -44,6 +45,7 @@ import net.fabricmc.fabric.api.entity.event.v1.EntityElytraEvents;
import net.fabricmc.fabric.api.entity.event.v1.EntitySleepEvents;
import net.fabricmc.fabric.api.entity.event.v1.ServerEntityCombatEvents;
import net.fabricmc.fabric.api.entity.event.v1.ServerEntityWorldChangeEvents;
import net.fabricmc.fabric.api.entity.event.v1.ServerLivingEntityEvents;
import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents;
public final class EntityEventTests implements ModInitializer {
@ -77,9 +79,29 @@ public final class EntityEventTests implements ModInitializer {
LOGGER.info("Respawned {}, [{}, {}]", oldPlayer.getGameProfile().getName(), oldPlayer.getWorld().getRegistryKey().getValue(), newPlayer.getWorld().getRegistryKey().getValue());
});
ServerPlayerEvents.ALLOW_DEATH.register((player, source, amount) -> {
LOGGER.info("{} is going to die to {} damage from {} damage source", player.getGameProfile().getName(), amount, source.getName());
// No fall damage if holding a feather in the main hand
ServerLivingEntityEvents.ALLOW_DAMAGE.register((entity, source, amount) -> {
if (source == DamageSource.FALL && entity.getStackInHand(Hand.MAIN_HAND).isOf(Items.FEATHER)) {
LOGGER.info("Avoided {} of fall damage by holding a feather", amount);
return false;
}
return true;
});
ServerLivingEntityEvents.ALLOW_DEATH.register((entity, source, amount) -> {
LOGGER.info("{} is going to die to {} damage from {} damage source", entity.getName().getString(), amount, source.getName());
if (entity.getStackInHand(Hand.MAIN_HAND).getItem() == Items.CARROT) {
entity.setHealth(3.0f);
return false;
}
return true;
});
// Test that the legacy event still works
ServerPlayerEvents.ALLOW_DEATH.register((player, source, amount) -> {
if (player.getStackInHand(Hand.MAIN_HAND).getItem() == Items.APPLE) {
player.setHealth(3.0f);
return false;
@ -88,6 +110,10 @@ public final class EntityEventTests implements ModInitializer {
return true;
});
ServerLivingEntityEvents.AFTER_DEATH.register((entity, source) -> {
LOGGER.info("{} died due to {} damage source", entity.getName().getString(), source.getName());
});
EntitySleepEvents.ALLOW_SLEEPING.register((player, sleepingPos) -> {
// Can't sleep if holds blue wool
if (player.getStackInHand(Hand.MAIN_HAND).isOf(Items.BLUE_WOOL)) {

View file

@ -48,6 +48,17 @@ public final class FabricDefaultAttributeRegistry {
private FabricDefaultAttributeRegistry() {
}
/**
* Registers a default attribute for a type of living entity.
*
* @param type the entity type
* @param builder the builder that creates the default attribute
* @see FabricDefaultAttributeRegistry#register(EntityType, DefaultAttributeContainer)
*/
public static void register(EntityType<? extends LivingEntity> type, DefaultAttributeContainer.Builder builder) {
register(type, builder.build());
}
/**
* Registers a default attribute for a type of living entity.
*
@ -62,12 +73,12 @@ public final class FabricDefaultAttributeRegistry {
*
* <p>For convenience, this can also be done on the {@link FabricEntityTypeBuilder} to simplify the building process.
*
* @param type the entity type
* @param builder the builder that creates the default attribute
* @param type the entity type
* @param container the container for the default attribute
* @see FabricEntityTypeBuilder.Living#defaultAttributes(Supplier)
*/
public static void register(EntityType<? extends LivingEntity> type, DefaultAttributeContainer.Builder builder) {
if (DefaultAttributeRegistryAccessor.getRegistry().put(type, builder.build()) != null) {
public static void register(EntityType<? extends LivingEntity> type, DefaultAttributeContainer container) {
if (DefaultAttributeRegistryAccessor.getRegistry().put(type, container) != null) {
LOGGER.debug("Overriding existing registration for entity type {}", Registry.ENTITY_TYPE.getId(type));
}
}

View file

@ -6,6 +6,7 @@ moduleDependencies(project, [
])
testDependencies(project, [
':fabric-block-api-v1',
':fabric-blockrenderlayer-v1',
':fabric-models-v0',
':fabric-object-builder-api-v1',

View file

@ -30,11 +30,16 @@ import net.minecraft.util.Hand;
import net.minecraft.util.Identifier;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.world.BlockRenderView;
import net.minecraft.world.World;
import net.fabricmc.fabric.api.block.v1.FabricBlock;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
import net.fabricmc.fabric.api.rendering.data.v1.RenderAttachedBlockView;
public final class FrameBlock extends Block implements BlockEntityProvider {
// Need to implement FabricBlock manually because this is a testmod for another Fabric module, otherwise it would be injected.
public final class FrameBlock extends Block implements BlockEntityProvider, FabricBlock {
public final Identifier id;
public FrameBlock(Identifier id) {
@ -94,4 +99,16 @@ public final class FrameBlock extends Block implements BlockEntityProvider {
public BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
return new FrameBlockEntity(pos, state);
}
// The frames don't look exactly like the block they are mimicking,
// but the goal here is just to test the behavior with the pillar's connected textures. ;-)
@Override
public BlockState getAppearance(BlockState state, BlockRenderView renderView, BlockPos pos, Direction side, @Nullable BlockState sourceState, @Nullable BlockPos sourcePos) {
// For this specific block, the render attachment works on both the client and the server, so let's use that.
if (((RenderAttachedBlockView) renderView).getBlockEntityRenderAttachment(pos) instanceof Block mimickedBlock) {
return mimickedBlock.getDefaultState();
}
return state;
}
}

View file

@ -16,6 +16,8 @@
package net.fabricmc.fabric.test.renderer.simple;
import net.minecraft.block.Block;
import net.minecraft.block.Material;
import net.minecraft.block.entity.BlockEntityType;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
@ -23,6 +25,7 @@ import net.minecraft.util.Identifier;
import net.minecraft.util.registry.Registry;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
import net.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityTypeBuilder;
/**
@ -40,6 +43,9 @@ public final class RendererTest implements ModInitializer {
};
public static final BlockEntityType<FrameBlockEntity> FRAME_BLOCK_ENTITY = FabricBlockEntityTypeBuilder.create(FrameBlockEntity::new, FRAMES).build(null);
public static final Identifier PILLAR_ID = id("pillar");
public static final Block PILLAR = new Block(FabricBlockSettings.of(Material.STONE));
@Override
public void onInitialize() {
for (FrameBlock frameBlock : FRAMES) {
@ -47,6 +53,12 @@ public final class RendererTest implements ModInitializer {
Registry.register(Registry.ITEM, frameBlock.id, new BlockItem(frameBlock, new Item.Settings()));
}
// To anyone testing this: pillars are supposed to connect vertically with each other.
// Additionally, they should also connect vertically to frame blocks containing a pillar.
// (The frame block will not change, but adjacent pillars should adjust their textures).
Registry.register(Registry.BLOCK, PILLAR_ID, PILLAR);
Registry.register(Registry.ITEM, PILLAR_ID, new BlockItem(PILLAR, new Item.Settings()));
Registry.register(Registry.BLOCK_ENTITY_TYPE, id("frame"), FRAME_BLOCK_ENTITY);
}

View file

@ -0,0 +1,142 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* 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 net.fabricmc.fabric.test.renderer.simple.client;
import java.util.List;
import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.BakedQuad;
import net.minecraft.client.render.model.json.ModelOverrideList;
import net.minecraft.client.render.model.json.ModelTransformation;
import net.minecraft.client.texture.Sprite;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.random.Random;
import net.minecraft.world.BlockRenderView;
import net.fabricmc.fabric.api.block.v1.FabricBlockState;
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel;
import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.fabricmc.fabric.test.renderer.simple.RendererTest;
/**
* Very crude implementation of a pillar block model that connects with pillars above and below.
*/
public class PillarBakedModel implements BakedModel, FabricBakedModel {
private enum ConnectedTexture {
ALONE, BOTTOM, MIDDLE, TOP
}
// alone, bottom, middle, top
private final Sprite[] sprites;
public PillarBakedModel(Sprite[] sprites) {
this.sprites = sprites;
}
@Override
public boolean isVanillaAdapter() {
return false;
}
@Override
public void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier<Random> randomSupplier, RenderContext context) {
emitQuads(context.getEmitter(), blockView, state, pos);
}
@Override
public void emitItemQuads(ItemStack stack, Supplier<Random> randomSupplier, RenderContext context) {
emitQuads(context.getEmitter(), null, null, null);
}
private void emitQuads(QuadEmitter emitter, @Nullable BlockRenderView blockView, @Nullable BlockState state, @Nullable BlockPos pos) {
for (Direction side : Direction.values()) {
ConnectedTexture texture = ConnectedTexture.ALONE;
if (side.getAxis().isHorizontal() && blockView != null && state != null && pos != null) {
boolean connectAbove = canConnect(blockView, pos.offset(Direction.UP), side, state, pos);
boolean connectBelow = canConnect(blockView, pos.offset(Direction.DOWN), side, state, pos);
if (connectAbove && connectBelow) {
texture = ConnectedTexture.MIDDLE;
} else if (connectAbove) {
texture = ConnectedTexture.BOTTOM;
} else if (connectBelow) {
texture = ConnectedTexture.TOP;
}
}
emitter.square(side, 0, 0, 1, 1, 0);
emitter.spriteBake(0, sprites[texture.ordinal()], MutableQuadView.BAKE_LOCK_UV);
emitter.spriteColor(0, -1, -1, -1, -1);
emitter.emit();
}
}
private static boolean canConnect(BlockRenderView blockView, BlockPos pos, Direction side, BlockState sourceState, BlockPos sourcePos) {
// In this testmod we can't rely on injected interfaces - in normal mods the (FabricBlockState) cast will be unnecessary
return ((FabricBlockState) blockView.getBlockState(pos)).getAppearance(blockView, pos, side, sourceState, sourcePos).isOf(RendererTest.PILLAR);
}
@Override
public List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction face, Random random) {
return List.of();
}
@Override
public boolean useAmbientOcclusion() {
return true;
}
@Override
public boolean hasDepth() {
return false;
}
@Override
public boolean isSideLit() {
return true;
}
@Override
public boolean isBuiltin() {
return false;
}
@Override
public Sprite getParticleSprite() {
return sprites[0];
}
@Override
public ModelTransformation getTransformation() {
return ModelHelper.MODEL_TRANSFORM_BLOCK;
}
@Override
public ModelOverrideList getOverrides() {
return ModelOverrideList.EMPTY;
}
}

View file

@ -0,0 +1,38 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* 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 net.fabricmc.fabric.test.renderer.simple.client;
import org.jetbrains.annotations.Nullable;
import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.client.util.ModelIdentifier;
import net.fabricmc.fabric.api.client.model.ModelProviderContext;
import net.fabricmc.fabric.api.client.model.ModelVariantProvider;
import net.fabricmc.fabric.test.renderer.simple.RendererTest;
public class PillarModelVariantProvider implements ModelVariantProvider {
@Override
@Nullable
public UnbakedModel loadModelVariant(ModelIdentifier modelId, ModelProviderContext context) {
if (RendererTest.PILLAR_ID.equals(modelId)) {
return new PillarUnbakedModel();
} else {
return null;
}
}
}

View file

@ -0,0 +1,62 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* 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 net.fabricmc.fabric.test.renderer.simple.client;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.Baker;
import net.minecraft.client.render.model.ModelBakeSettings;
import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.util.SpriteIdentifier;
import net.minecraft.screen.PlayerScreenHandler;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.test.renderer.simple.RendererTest;
public class PillarUnbakedModel implements UnbakedModel {
private static final List<SpriteIdentifier> SPRITES = Stream.of("alone", "bottom", "middle", "top")
.map(suffix -> new SpriteIdentifier(PlayerScreenHandler.BLOCK_ATLAS_TEXTURE, RendererTest.id("block/pillar_" + suffix)))
.toList();
@Override
public Collection<Identifier> getModelDependencies() {
return List.of();
}
@Override
public void setParents(Function<Identifier, UnbakedModel> modelLoader) {
}
@Nullable
@Override
public BakedModel bake(Baker baker, Function<SpriteIdentifier, Sprite> textureGetter, ModelBakeSettings rotationContainer, Identifier modelId) {
Sprite[] sprites = new Sprite[SPRITES.size()];
for (int i = 0; i < sprites.length; ++i) {
sprites[i] = textureGetter.apply(SPRITES.get(i));
}
return new PillarBakedModel(sprites);
}
}

View file

@ -28,6 +28,7 @@ public final class RendererClientTest implements ClientModInitializer {
@Override
public void onInitializeClient() {
ModelLoadingRegistry.INSTANCE.registerResourceProvider(manager -> new FrameModelResourceProvider());
ModelLoadingRegistry.INSTANCE.registerVariantProvider(manager -> new PillarModelVariantProvider());
for (FrameBlock frameBlock : RendererTest.FRAMES) {
BlockRenderLayerMap.INSTANCE.putBlock(frameBlock, RenderLayer.getCutoutMipped());

View file

@ -48,7 +48,7 @@ import net.minecraft.world.BlockRenderView;
* and then use {@link #getBlockEntityRenderAttachment(BlockPos)} to retrieve it. When called from the
* main thread, that method will simply retrieve the data directly.
*
* <p>This interface is only guaranteed to be present in the client environment.
* <p>This interface is guaranteed to be implemented on every {@link BlockRenderView} subclass.
*/
// XXX can not link net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel
public interface RenderAttachedBlockView extends BlockRenderView {

View file

@ -1,4 +1,4 @@
{
"pack.source.fabricmod": "Fabric 모드",
"pack.source.builtinMod": "%s에 내장"
"pack.source.builtinMod": "%s에 내장"
}

View file

@ -12,6 +12,7 @@ prerelease=true
fabric-api-base-version=0.4.15
fabric-api-lookup-api-v1-version=1.6.13
fabric-biome-api-v1-version=11.0.0
fabric-block-api-v1-version=1.0.0
fabric-blockrenderlayer-v1-version=1.1.24
fabric-command-api-v1-version=1.2.15
fabric-command-api-v2-version=2.1.11

View file

@ -15,6 +15,7 @@ include 'fabric-api-base'
include 'fabric-api-lookup-api-v1'
include 'fabric-biome-api-v1'
include 'fabric-block-api-v1'
include 'fabric-blockrenderlayer-v1'
include 'fabric-command-api-v2'
include 'fabric-content-registries-v0'