Improve 24w21b port (#3801)

* Delete `FabricDimensions`

This broke during the 1.21 cycle and can be easily replaced with `Entity#teleportTo`.

* Rename testmod data directories

* [Breaking] use singular path in GameTest

* Fix attribute modifier in testmod

* Small mixin refactors related to teleportTo

* Fix behavior change in ModNioResourcePack for invalid paths

* Fix javadocs referencing Identifier ctor

* Add new FabricCodecDataProvider ctor

* Move empty structure

* Fix transfer api testmod

* pro tip: don't write datagen output by hand

* Refactor networking API to remove redundant code

* Stop calling CustomDamageHandler in creative mode
This commit is contained in:
apple502j 2024-05-29 23:31:48 +09:00 committed by GitHub
parent b9828ba31a
commit 6fc22b9905
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 49 additions and 373 deletions

View file

@ -51,7 +51,7 @@ import net.fabricmc.fabric.impl.lookup.block.BlockApiLookupImpl;
* *
* <pre>{@code * <pre>{@code
* public final class MyApi { * public final class MyApi {
* public static final BlockApiLookup<FluidContainer, Direction> FLUID_CONTAINER = BlockApiLookup.get(new Identifier("mymod:fluid_container"), FluidContainer.class, Direction.class); * public static final BlockApiLookup<FluidContainer, Direction> FLUID_CONTAINER = BlockApiLookup.get(Identifier.of("mymod", "fluid_container"), FluidContainer.class, Direction.class);
* }}</pre> * }}</pre>
* Using that, we can query instances of {@code FluidContainer}: * Using that, we can query instances of {@code FluidContainer}:
* *

View file

@ -45,7 +45,7 @@ import net.fabricmc.fabric.impl.lookup.entity.EntityApiLookupImpl;
* <p>We need to create the EntityApiLookup. We don't need any context so we use {@link Void}. * <p>We need to create the EntityApiLookup. We don't need any context so we use {@link Void}.
* <pre>{@code * <pre>{@code
* public class MyApi { * public class MyApi {
* public static final EntityApiLookup<Leveled, Void> LEVELED_ENTITY = EntityApiLookup.get(new Identifier("mymod:leveled_entity"), Leveled.class, Void.class); * public static final EntityApiLookup<Leveled, Void> LEVELED_ENTITY = EntityApiLookup.get(Identifier.of("mymod", "leveled_entity"), Leveled.class, Void.class);
* } * }
* }</pre> * }</pre>
* *

View file

@ -47,7 +47,7 @@ import net.fabricmc.fabric.impl.lookup.item.ItemApiLookupImpl;
* *
* <pre>{@code * <pre>{@code
* public final class MyApi { * public final class MyApi {
* public static final ItemApiLookup<FluidContainer, Void> FLUID_CONTAINER_ITEM = ItemApiLookup.get(new Identifier("mymod:fluid_container"), FluidContainer.class, Void.class); * public static final ItemApiLookup<FluidContainer, Void> FLUID_CONTAINER_ITEM = ItemApiLookup.get(Identifier.of("mymod", "fluid_container"), FluidContainer.class, Void.class);
* }}</pre> * }}</pre>
* API instances are easy to access: * API instances are easy to access:
* *

View file

@ -1,9 +1,8 @@
{ {
"replace": false,
"values": [ "values": [
"minecraft:beach", "minecraft:beach",
"minecraft:desert", "minecraft:desert",
"minecraft:savanna", "minecraft:savanna",
"minecraft:badlands" "minecraft:badlands"
] ]
} }

View file

@ -23,6 +23,7 @@ import net.minecraft.registry.RegistryWrapper;
import net.minecraft.registry.tag.TagKey; import net.minecraft.registry.tag.TagKey;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.world.biome.Biome; import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.BiomeKeys;
import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricTagProvider; import net.fabricmc.fabric.api.datagen.v1.provider.FabricTagProvider;
@ -37,5 +38,10 @@ public class TestBiomeTagProvider extends FabricTagProvider<Biome> {
getOrCreateTagBuilder(TagKey.of(RegistryKeys.BIOME, Identifier.of(FabricBiomeTest.MOD_ID, "biome_tag_test"))) getOrCreateTagBuilder(TagKey.of(RegistryKeys.BIOME, Identifier.of(FabricBiomeTest.MOD_ID, "biome_tag_test")))
.add(TestBiomes.CUSTOM_PLAINS) .add(TestBiomes.CUSTOM_PLAINS)
.add(TestBiomes.TEST_END_HIGHLANDS); .add(TestBiomes.TEST_END_HIGHLANDS);
getOrCreateTagBuilder(TagKey.of(RegistryKeys.BIOME, Identifier.of(FabricBiomeTest.MOD_ID, "tag_selector_test")))
.add(BiomeKeys.BEACH)
.add(BiomeKeys.DESERT)
.add(BiomeKeys.SAVANNA)
.add(BiomeKeys.BADLANDS);
} }
} }

View file

@ -32,6 +32,8 @@ import com.mojang.serialization.JsonOps;
import net.minecraft.data.DataOutput; import net.minecraft.data.DataOutput;
import net.minecraft.data.DataProvider; import net.minecraft.data.DataProvider;
import net.minecraft.data.DataWriter; import net.minecraft.data.DataWriter;
import net.minecraft.registry.Registry;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryOps; import net.minecraft.registry.RegistryOps;
import net.minecraft.registry.RegistryWrapper; import net.minecraft.registry.RegistryWrapper;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
@ -49,12 +51,20 @@ public abstract class FabricCodecDataProvider<T> implements DataProvider {
private final CompletableFuture<RegistryWrapper.WrapperLookup> registriesFuture; private final CompletableFuture<RegistryWrapper.WrapperLookup> registriesFuture;
private final Codec<T> codec; private final Codec<T> codec;
protected FabricCodecDataProvider(FabricDataOutput dataOutput, CompletableFuture<RegistryWrapper.WrapperLookup> registriesFuture, DataOutput.OutputType outputType, String directoryName, Codec<T> codec) { private FabricCodecDataProvider(DataOutput.PathResolver pathResolver, CompletableFuture<RegistryWrapper.WrapperLookup> registriesFuture, Codec<T> codec) {
this.pathResolver = dataOutput.getResolver(outputType, directoryName); this.pathResolver = pathResolver;
this.registriesFuture = Objects.requireNonNull(registriesFuture); this.registriesFuture = Objects.requireNonNull(registriesFuture);
this.codec = codec; this.codec = codec;
} }
protected FabricCodecDataProvider(FabricDataOutput dataOutput, CompletableFuture<RegistryWrapper.WrapperLookup> registriesFuture, DataOutput.OutputType outputType, String directoryName, Codec<T> codec) {
this(dataOutput.getResolver(outputType, directoryName), registriesFuture, codec);
}
protected FabricCodecDataProvider(FabricDataOutput dataOutput, CompletableFuture<RegistryWrapper.WrapperLookup> registriesFuture, RegistryKey<? extends Registry<?>> key, Codec<T> codec) {
this(dataOutput.getResolver(key), registriesFuture, codec);
}
@Override @Override
public CompletableFuture<?> run(DataWriter writer) { public CompletableFuture<?> run(DataWriter writer) {
return this.registriesFuture.thenCompose(lookup -> { return this.registriesFuture.thenCompose(lookup -> {

View file

@ -28,11 +28,11 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.mojang.serialization.JsonOps; import com.mojang.serialization.JsonOps;
import net.minecraft.data.DataOutput;
import net.minecraft.data.DataProvider; import net.minecraft.data.DataProvider;
import net.minecraft.data.DataWriter; import net.minecraft.data.DataWriter;
import net.minecraft.loot.LootTable; import net.minecraft.loot.LootTable;
import net.minecraft.loot.context.LootContextType; import net.minecraft.loot.context.LootContextType;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.RegistryOps; import net.minecraft.registry.RegistryOps;
import net.minecraft.registry.RegistryWrapper; import net.minecraft.registry.RegistryWrapper;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
@ -81,7 +81,7 @@ public final class FabricLootTableProviderImpl {
} }
private static Path getOutputPath(FabricDataOutput dataOutput, Identifier lootTableId) { private static Path getOutputPath(FabricDataOutput dataOutput, Identifier lootTableId) {
return dataOutput.getResolver(DataOutput.OutputType.DATA_PACK, "loot_table").resolveJson(lootTableId); return dataOutput.getResolver(RegistryKeys.LOOT_TABLE).resolveJson(lootTableId);
} }
private FabricLootTableProviderImpl() { private FabricLootTableProviderImpl() {

View file

@ -186,7 +186,7 @@ public class DataGeneratorTestEntrypoint implements DataGeneratorEntrypoint {
/* Generate test recipes using all types of custom ingredients for easy testing */ /* Generate test recipes using all types of custom ingredients for easy testing */
// Testing procedure for vanilla and fabric clients: // Testing procedure for vanilla and fabric clients:
// - Create a new fabric server with the ingredient API. // - Create a new fabric server with the ingredient API.
// - Copy the generated recipes to a datapack, for example to world/datapacks/<packname>/data/test/recipes/. // - Copy the generated recipes to a datapack, for example to world/datapacks/<packname>/data/test/recipe/.
// - Remember to also include a pack.mcmeta file in world/datapacks/<packname>. // - Remember to also include a pack.mcmeta file in world/datapacks/<packname>.
// (see https://minecraft.wiki/w/Tutorials/Creating_a_data_pack) // (see https://minecraft.wiki/w/Tutorials/Creating_a_data_pack)
// - Start the server and connect to it with a vanilla client. // - Start the server and connect to it with a vanilla client.
@ -466,7 +466,7 @@ public class DataGeneratorTestEntrypoint implements DataGeneratorEntrypoint {
private static class TestPredicateProvider extends FabricCodecDataProvider<LootCondition> { private static class TestPredicateProvider extends FabricCodecDataProvider<LootCondition> {
private TestPredicateProvider(FabricDataOutput dataOutput, CompletableFuture<RegistryWrapper.WrapperLookup> registriesFuture) { private TestPredicateProvider(FabricDataOutput dataOutput, CompletableFuture<RegistryWrapper.WrapperLookup> registriesFuture) {
super(dataOutput, registriesFuture, DataOutput.OutputType.DATA_PACK, "predicate", LootCondition.CODEC); super(dataOutput, registriesFuture, RegistryKeys.PREDICATE, LootCondition.CODEC);
} }
@Override @Override

View file

@ -1,9 +1,5 @@
version = getSubprojectVersion(project) version = getSubprojectVersion(project)
moduleDependencies(project, ['fabric-api-base'])
testDependencies(project, [ testDependencies(project, [
':fabric-command-api-v2', ':fabric-resource-loader-v0'
':fabric-resource-loader-v0',
':fabric-lifecycle-events-v1'
]) ])

View file

@ -1,57 +0,0 @@
/*
* 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.dimension.v1;
import com.google.common.base.Preconditions;
import org.jetbrains.annotations.Nullable;
import net.minecraft.entity.Entity;
import net.minecraft.world.TeleportTarget;
import net.fabricmc.fabric.impl.dimension.FabricDimensionInternals;
/**
* This class consists exclusively of static methods that operate on world dimensions.
*/
public final class FabricDimensions {
private FabricDimensions() {
throw new AssertionError();
}
/**
* Teleports an entity to a different dimension, placing it at the specified destination.
*
* <p>When teleporting to another dimension, the entity may be replaced with a new entity in the target
* dimension. This is not the case for players, but needs to be accounted for by the caller.
*
* @param teleported the entity to teleport
* @param target where the entity will be placed in the target world.
* As in Vanilla, the target's velocity is not applied to players.
* @param <E> the type of the teleported entity
* @return Returns the teleported entity in the target dimension, which may be a new entity or <code>teleported</code>,
* depending on the entity type.
* @throws IllegalStateException if this method is called on a client entity
* @apiNote this method must be called from the main server thread
*/
@Nullable
public static <E extends Entity> E teleport(E teleported, TeleportTarget target) {
Preconditions.checkNotNull(target, "A target must be provided");
Preconditions.checkState(!teleported.getWorld().isClient, "Entities can only be teleported on the server side");
return FabricDimensionInternals.changeDimension(teleported, target);
}
}

View file

@ -1,52 +0,0 @@
/*
* 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.impl.dimension;
import com.google.common.base.Preconditions;
import net.minecraft.entity.Entity;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.world.TeleportTarget;
public final class FabricDimensionInternals {
private FabricDimensionInternals() {
throw new AssertionError();
}
@SuppressWarnings("unchecked")
public static <E extends Entity> E changeDimension(E teleported, TeleportTarget target) {
Preconditions.checkArgument(!teleported.getWorld().isClient, "Entities can only be teleported on the server side");
Preconditions.checkArgument(Thread.currentThread() == ((ServerWorld) teleported.getWorld()).getServer().getThread(), "Entities must be teleported from the main server thread");
// Fast path for teleporting within the same dimension.
if (teleported.getWorld() == target.world()) {
if (teleported instanceof ServerPlayerEntity serverPlayerEntity) {
serverPlayerEntity.networkHandler.requestTeleport(target.pos().x, target.pos().y, target.pos().z, target.yaw(), target.pitch());
} else {
teleported.refreshPositionAndAngles(target.pos().x, target.pos().y, target.pos().z, target.yaw(), target.pitch());
}
teleported.setVelocity(target.velocity());
teleported.setHeadYaw(target.yaw());
return teleported;
}
return (E) teleported.teleportTo(target);
}
}

View file

@ -16,184 +16,22 @@
package net.fabricmc.fabric.test.dimension; package net.fabricmc.fabric.test.dimension;
import static net.minecraft.entity.EntityType.COW;
import static net.minecraft.server.command.CommandManager.argument;
import static net.minecraft.server.command.CommandManager.literal;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import net.minecraft.block.Blocks;
import net.minecraft.command.argument.DimensionArgumentType;
import net.minecraft.entity.Entity;
import net.minecraft.registry.Registries; import net.minecraft.registry.Registries;
import net.minecraft.registry.Registry; import net.minecraft.registry.Registry;
import net.minecraft.registry.RegistryKey; import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryKeys; import net.minecraft.registry.RegistryKeys;
import net.minecraft.server.command.CommandManager;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.TeleportTarget;
import net.minecraft.world.World;
import net.minecraft.world.dimension.DimensionOptions; import net.minecraft.world.dimension.DimensionOptions;
import net.fabricmc.api.ModInitializer; import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.fabricmc.fabric.api.dimension.v1.FabricDimensions;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
public class FabricDimensionTest implements ModInitializer { public class FabricDimensionTest implements ModInitializer {
// The dimension options refer to the JSON-file in the dimension subfolder of the data pack, // The dimension options refer to the JSON-file in the dimension subfolder of the data pack,
// which will always share its ID with the world that is created from it // which will always share its ID with the world that is created from it
private static final RegistryKey<DimensionOptions> DIMENSION_KEY = RegistryKey.of(RegistryKeys.DIMENSION, Identifier.of("fabric_dimension", "void")); private static final RegistryKey<DimensionOptions> DIMENSION_KEY = RegistryKey.of(RegistryKeys.DIMENSION, Identifier.of("fabric_dimension", "void"));
private static final SimpleCommandExceptionType FAILED_EXCEPTION = new SimpleCommandExceptionType(Text.literal("Teleportation failed!"));
private static RegistryKey<World> WORLD_KEY = RegistryKey.of(RegistryKeys.WORLD, DIMENSION_KEY.getValue());
@Override @Override
public void onInitialize() { public void onInitialize() {
Registry.register(Registries.CHUNK_GENERATOR, Identifier.of("fabric_dimension", "void"), VoidChunkGenerator.CODEC); Registry.register(Registries.CHUNK_GENERATOR, Identifier.of("fabric_dimension", "void"), VoidChunkGenerator.CODEC);
WORLD_KEY = RegistryKey.of(RegistryKeys.WORLD, Identifier.of("fabric_dimension", "void"));
if (System.getProperty("fabric-api.gametest") != null) {
// The gametest server does not support custom worlds
return;
}
ServerLifecycleEvents.SERVER_STARTED.register(server -> {
ServerWorld overworld = server.getWorld(World.OVERWORLD);
ServerWorld world = server.getWorld(WORLD_KEY);
if (world == null) throw new AssertionError("Test world doesn't exist.");
Entity entity = COW.create(overworld);
if (entity == null) throw new AssertionError("Could not create entity!");
if (!entity.getWorld().getRegistryKey().equals(World.OVERWORLD)) throw new AssertionError("Entity starting world isn't the overworld");
TeleportTarget target = new TeleportTarget(world, Vec3d.ZERO, new Vec3d(1, 1, 1), 45f, 60f);
Entity teleported = FabricDimensions.teleport(entity, target);
if (teleported == null) throw new AssertionError("Entity didn't teleport");
if (!teleported.getWorld().getRegistryKey().equals(WORLD_KEY)) throw new AssertionError("Target world not reached.");
if (!teleported.getPos().equals(target.pos())) throw new AssertionError("Target Position not reached.");
});
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
dispatcher.register(literal("fabric_dimension_test")
.executes(FabricDimensionTest.this::swapTargeted));
// Used to test https://github.com/FabricMC/fabric/issues/2239
// Dedicated-only
if (environment != CommandManager.RegistrationEnvironment.INTEGRATED) {
dispatcher.register(literal("fabric_dimension_test_desync")
.executes(FabricDimensionTest.this::testDesync));
}
// Used to test https://github.com/FabricMC/fabric/issues/2238
dispatcher.register(literal("fabric_dimension_test_entity")
.executes(FabricDimensionTest.this::testEntityTeleport));
// Used to test teleport to vanilla dimension
dispatcher.register(literal("fabric_dimension_test_tp")
.then(argument("target", DimensionArgumentType.dimension())
.executes((context) ->
testVanillaTeleport(context, DimensionArgumentType.getDimensionArgument(context, "target")))));
});
}
private int swapTargeted(CommandContext<ServerCommandSource> context) throws CommandSyntaxException {
ServerPlayerEntity player = context.getSource().getPlayer();
if (player == null) {
context.getSource().sendFeedback(() -> Text.literal("You must be a player to execute this command."), false);
return 1;
}
ServerWorld serverWorld = player.getServerWorld();
ServerWorld modWorld = getModWorld(context);
if (serverWorld != modWorld) {
TeleportTarget target = new TeleportTarget(modWorld, new Vec3d(0.5, 101, 0.5), Vec3d.ZERO, 0, 0);
FabricDimensions.teleport(player, target);
if (player.getWorld() != modWorld) {
throw FAILED_EXCEPTION.create();
}
modWorld.setBlockState(new BlockPos(0, 100, 0), Blocks.DIAMOND_BLOCK.getDefaultState());
modWorld.setBlockState(new BlockPos(0, 101, 0), Blocks.TORCH.getDefaultState());
} else {
TeleportTarget target = new TeleportTarget(getWorld(context, World.OVERWORLD), new Vec3d(0, 100, 0), Vec3d.ZERO,
(float) Math.random() * 360 - 180, (float) Math.random() * 360 - 180);
FabricDimensions.teleport(player, target);
}
return 1;
}
private int testDesync(CommandContext<ServerCommandSource> context) {
ServerPlayerEntity player = context.getSource().getPlayer();
if (player == null) {
context.getSource().sendFeedback(() -> Text.literal("You must be a player to execute this command."), false);
return 1;
}
TeleportTarget target = new TeleportTarget((ServerWorld) player.getWorld(), player.getPos().add(5, 0, 0), player.getVelocity(), player.getYaw(), player.getPitch());
FabricDimensions.teleport(player, target);
return 1;
}
private int testEntityTeleport(CommandContext<ServerCommandSource> context) {
ServerPlayerEntity player = context.getSource().getPlayer();
if (player == null) {
context.getSource().sendFeedback(() -> Text.literal("You must be a player to execute this command."), false);
return 1;
}
Entity entity = player.getWorld()
.getOtherEntities(player, player.getBoundingBox().expand(100, 100, 100))
.stream()
.findFirst()
.orElse(null);
if (entity == null) {
context.getSource().sendFeedback(() -> Text.literal("No entities found."), false);
return 1;
}
TeleportTarget target = new TeleportTarget((ServerWorld) entity.getWorld(), player.getPos(), player.getVelocity(), player.getYaw(), player.getPitch());
FabricDimensions.teleport(entity, target);
return 1;
}
private int testVanillaTeleport(CommandContext<ServerCommandSource> context, ServerWorld targetWorld) throws CommandSyntaxException {
Entity entity = context.getSource().getEntityOrThrow();
TeleportTarget target = new TeleportTarget(targetWorld, entity.getPos(), entity.getVelocity(), entity.getYaw(), entity.getPitch());
FabricDimensions.teleport(entity, target);
return 1;
}
private ServerWorld getModWorld(CommandContext<ServerCommandSource> context) {
return getWorld(context, WORLD_KEY);
}
private ServerWorld getWorld(CommandContext<ServerCommandSource> context, RegistryKey<World> dimensionRegistryKey) {
return context.getSource().getServer().getWorld(dimensionRegistryKey);
} }
} }

View file

@ -39,7 +39,7 @@ abstract class EntityMixin {
private World world; private World world;
@Inject(method = "teleportTo", at = @At("RETURN")) @Inject(method = "teleportTo", at = @At("RETURN"))
private void afterWorldChanged(TeleportTarget targetSupplier, CallbackInfoReturnable<Entity> cir) { private void afterWorldChanged(TeleportTarget target, CallbackInfoReturnable<Entity> cir) {
// Ret will only have an entity if the teleport worked (entity not removed, teleportTarget was valid, entity was successfully created) // Ret will only have an entity if the teleport worked (entity not removed, teleportTarget was valid, entity was successfully created)
Entity ret = cir.getReturnValue(); Entity ret = cir.getReturnValue();

View file

@ -78,8 +78,7 @@ abstract class ServerPlayerEntityMixin extends LivingEntityMixin {
} }
/** /**
* This is called by both "moveToWorld" and "teleport". * This is called by {@code teleportTo}.
* So this is suitable to handle the after event from both call sites.
*/ */
@Inject(method = "worldChanged(Lnet/minecraft/server/world/ServerWorld;)V", at = @At("TAIL")) @Inject(method = "worldChanged(Lnet/minecraft/server/world/ServerWorld;)V", at = @At("TAIL"))
private void afterWorldChanged(ServerWorld origin, CallbackInfo ci) { private void afterWorldChanged(ServerWorld origin, CallbackInfo ci) {

View file

@ -7,7 +7,7 @@ loom {
testmodClient { testmodClient {
client() client()
name = "Testmod Client" name = "Testmod Client"
vmArg "-Dfabric-api.gametest.structures.output-dir=${file("src/testmod/resources/data/fabric-gametest-api-v1-testmod/gametest/structures")}" vmArg "-Dfabric-api.gametest.structures.output-dir=${file("src/testmod/resources/data/fabric-gametest-api-v1-testmod/gametest/structure")}"
ideConfigGenerated = false ideConfigGenerated = false
source sourceSets.testmodClient source sourceSets.testmodClient

View file

@ -35,7 +35,7 @@
* causes explosion, or that hoppers can insert items into barrels. A test method is always annotated * causes explosion, or that hoppers can insert items into barrels. A test method is always annotated
* with {@link net.minecraft.test.GameTest}. For most cases you can set the {@link * with {@link net.minecraft.test.GameTest}. For most cases you can set the {@link
* net.minecraft.test.GameTest#templateName()} as {@link net.fabricmc.fabric.api.gametest.v1.FabricGameTest#EMPTY_STRUCTURE}. * net.minecraft.test.GameTest#templateName()} as {@link net.fabricmc.fabric.api.gametest.v1.FabricGameTest#EMPTY_STRUCTURE}.
* For complex tests, you can also save a structure as an SNBT file under {@code modid/gametest/structures/} * For complex tests, you can also save a structure as an SNBT file under {@code modid/gametest/structure/}
* in the test mod's data pack and reference that structure. It will then be loaded before the test. * in the test mod's data pack and reference that structure. It will then be loaded before the test.
* *
* <p>Test methods are instance methods (i.e. not static) and take exactly one argument - {@link * <p>Test methods are instance methods (i.e. not static) and take exactly one argument - {@link

View file

@ -58,7 +58,7 @@ public final class FabricGameTestHelper {
private static final Logger LOGGER = LoggerFactory.getLogger(FabricGameTestHelper.class); private static final Logger LOGGER = LoggerFactory.getLogger(FabricGameTestHelper.class);
private static final String GAMETEST_STRUCTURE_PATH = "gametest/structures"; private static final String GAMETEST_STRUCTURE_PATH = "gametest/structure";
public static final ResourceFinder GAMETEST_STRUCTURE_FINDER = new ResourceFinder(GAMETEST_STRUCTURE_PATH, ".snbt"); public static final ResourceFinder GAMETEST_STRUCTURE_FINDER = new ResourceFinder(GAMETEST_STRUCTURE_PATH, ".snbt");

View file

@ -49,7 +49,15 @@ public abstract class ItemStackMixin implements FabricItemStack {
private void hookDamage(ItemStack instance, int amount, ServerWorld serverWorld, ServerPlayerEntity serverPlayerEntity, Consumer<Item> consumer, Operation<Void> original, @Local(argsOnly = true) LivingEntity entity, @Local(argsOnly = true) EquipmentSlot slot) { private void hookDamage(ItemStack instance, int amount, ServerWorld serverWorld, ServerPlayerEntity serverPlayerEntity, Consumer<Item> consumer, Operation<Void> original, @Local(argsOnly = true) LivingEntity entity, @Local(argsOnly = true) EquipmentSlot slot) {
CustomDamageHandler handler = ((ItemExtensions) getItem()).fabric_getCustomDamageHandler(); CustomDamageHandler handler = ((ItemExtensions) getItem()).fabric_getCustomDamageHandler();
if (handler != null) { /*
This is called by creative mode players, post-24w21a.
The other damage method (which original.call discards) handles the creative mode check.
Since it doesn't make sense to call an event to calculate a to-be-discarded value
(and to prevent mods from breaking item stacks in Creative mode),
we preserve the pre-24w21a behavior of not calling in creative mode.
*/
if (handler != null && !entity.isInCreativeMode()) {
// Track whether an item has been broken by custom handler // Track whether an item has been broken by custom handler
MutableBoolean mut = new MutableBoolean(false); MutableBoolean mut = new MutableBoolean(false);
amount = handler.damage((ItemStack) (Object) this, amount, entity, slot, () -> { amount = handler.damage((ItemStack) (Object) this, amount, entity, slot, () -> {

View file

@ -26,11 +26,13 @@ import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.Item; import net.minecraft.item.Item;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.util.Hand; import net.minecraft.util.Hand;
import net.minecraft.util.Identifier;
import net.minecraft.world.World; import net.minecraft.world.World;
public class UpdatingItem extends Item { public class UpdatingItem extends Item {
private static final Identifier PLUS_FIVE_ID = Identifier.of("fabric-item-api-v1-testmod", "plus_five");
private static final EntityAttributeModifier PLUS_FIVE = new EntityAttributeModifier( private static final EntityAttributeModifier PLUS_FIVE = new EntityAttributeModifier(
BASE_ATTACK_DAMAGE_MODIFIER_ID, 5, EntityAttributeModifier.Operation.ADD_VALUE); PLUS_FIVE_ID, 5, EntityAttributeModifier.Operation.ADD_VALUE);
private final boolean allowUpdateAnimation; private final boolean allowUpdateAnimation;

View file

@ -1,24 +0,0 @@
/*
* 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.impl.networking;
import net.minecraft.network.packet.Packet;
import net.minecraft.text.Text;
public interface DisconnectPacketSource {
Packet<?> createDisconnectPacket(Text message);
}

View file

@ -22,13 +22,11 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.network.ClientConnection; import net.minecraft.network.ClientConnection;
@ -38,11 +36,9 @@ import net.minecraft.network.NetworkState;
import net.minecraft.network.PacketCallbacks; import net.minecraft.network.PacketCallbacks;
import net.minecraft.network.listener.PacketListener; import net.minecraft.network.listener.PacketListener;
import net.minecraft.network.packet.Packet; import net.minecraft.network.packet.Packet;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.fabricmc.fabric.impl.networking.ChannelInfoHolder; import net.fabricmc.fabric.impl.networking.ChannelInfoHolder;
import net.fabricmc.fabric.impl.networking.DisconnectPacketSource;
import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions; import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions;
import net.fabricmc.fabric.impl.networking.PacketCallbackListener; import net.fabricmc.fabric.impl.networking.PacketCallbackListener;
@ -51,12 +47,6 @@ abstract class ClientConnectionMixin implements ChannelInfoHolder {
@Shadow @Shadow
private PacketListener packetListener; private PacketListener packetListener;
@Shadow
public abstract void disconnect(Text disconnectReason);
@Shadow
public abstract void send(Packet<?> packet, @Nullable PacketCallbacks arg);
@Unique @Unique
private Map<NetworkPhase, Collection<Identifier>> playChannels; private Map<NetworkPhase, Collection<Identifier>> playChannels;
@ -65,19 +55,6 @@ abstract class ClientConnectionMixin implements ChannelInfoHolder {
this.playChannels = new ConcurrentHashMap<>(); this.playChannels = new ConcurrentHashMap<>();
} }
// Must be fully qualified due to mixin not working in production without it
@Redirect(method = "exceptionCaught", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/ClientConnection;send(Lnet/minecraft/network/packet/Packet;Lnet/minecraft/network/PacketCallbacks;)V"))
private void resendOnExceptionCaught(ClientConnection self, Packet<?> packet, PacketCallbacks listener, ChannelHandlerContext context, Throwable ex) {
PacketListener handler = this.packetListener;
Text disconnectMessage = Text.translatable("disconnect.genericReason", "Internal Exception: " + ex);
if (handler instanceof DisconnectPacketSource) {
this.send(((DisconnectPacketSource) handler).createDisconnectPacket(disconnectMessage), listener);
} else {
this.disconnect(disconnectMessage); // Don't send packet if we cannot send proper packets
}
}
@Inject(method = "sendImmediately", at = @At(value = "FIELD", target = "Lnet/minecraft/network/ClientConnection;packetsSentCounter:I")) @Inject(method = "sendImmediately", at = @At(value = "FIELD", target = "Lnet/minecraft/network/ClientConnection;packetsSentCounter:I"))
private void checkPacket(Packet<?> packet, PacketCallbacks callback, boolean flush, CallbackInfo ci) { private void checkPacket(Packet<?> packet, PacketCallbacks callback, boolean flush, CallbackInfo ci) {
if (this.packetListener instanceof PacketCallbackListener) { if (this.packetListener instanceof PacketCallbackListener) {

View file

@ -28,23 +28,19 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.network.ClientConnection; import net.minecraft.network.ClientConnection;
import net.minecraft.network.packet.Packet;
import net.minecraft.network.packet.s2c.common.DisconnectS2CPacket;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ConnectedClientData; import net.minecraft.server.network.ConnectedClientData;
import net.minecraft.server.network.ServerCommonNetworkHandler; import net.minecraft.server.network.ServerCommonNetworkHandler;
import net.minecraft.server.network.ServerConfigurationNetworkHandler; import net.minecraft.server.network.ServerConfigurationNetworkHandler;
import net.minecraft.server.network.ServerPlayerConfigurationTask; import net.minecraft.server.network.ServerPlayerConfigurationTask;
import net.minecraft.text.Text;
import net.fabricmc.fabric.api.networking.v1.FabricServerConfigurationNetworkHandler; import net.fabricmc.fabric.api.networking.v1.FabricServerConfigurationNetworkHandler;
import net.fabricmc.fabric.impl.networking.DisconnectPacketSource;
import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions; import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions;
import net.fabricmc.fabric.impl.networking.server.ServerConfigurationNetworkAddon; import net.fabricmc.fabric.impl.networking.server.ServerConfigurationNetworkAddon;
// We want to apply a bit earlier than other mods which may not use us in order to prevent refCount issues // We want to apply a bit earlier than other mods which may not use us in order to prevent refCount issues
@Mixin(value = ServerConfigurationNetworkHandler.class, priority = 900) @Mixin(value = ServerConfigurationNetworkHandler.class, priority = 900)
public abstract class ServerConfigurationNetworkHandlerMixin extends ServerCommonNetworkHandler implements NetworkHandlerExtensions, DisconnectPacketSource, FabricServerConfigurationNetworkHandler { public abstract class ServerConfigurationNetworkHandlerMixin extends ServerCommonNetworkHandler implements NetworkHandlerExtensions, FabricServerConfigurationNetworkHandler {
@Shadow @Shadow
@Nullable @Nullable
private ServerPlayerConfigurationTask currentTask; private ServerPlayerConfigurationTask currentTask;
@ -146,11 +142,6 @@ public abstract class ServerConfigurationNetworkHandlerMixin extends ServerCommo
return addon; return addon;
} }
@Override
public Packet<?> createDisconnectPacket(Text message) {
return new DisconnectS2CPacket(message);
}
@Override @Override
public void addTask(ServerPlayerConfigurationTask task) { public void addTask(ServerPlayerConfigurationTask task) {
tasks.add(task); tasks.add(task);

View file

@ -27,20 +27,17 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.network.packet.Packet; import net.minecraft.network.packet.Packet;
import net.minecraft.network.packet.c2s.login.LoginQueryResponseC2SPacket; import net.minecraft.network.packet.c2s.login.LoginQueryResponseC2SPacket;
import net.minecraft.network.packet.s2c.login.LoginDisconnectS2CPacket;
import net.minecraft.network.packet.s2c.login.LoginQueryRequestS2CPacket; import net.minecraft.network.packet.s2c.login.LoginQueryRequestS2CPacket;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerLoginNetworkHandler; import net.minecraft.server.network.ServerLoginNetworkHandler;
import net.minecraft.text.Text;
import net.fabricmc.fabric.impl.networking.DisconnectPacketSource;
import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions; import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions;
import net.fabricmc.fabric.impl.networking.PacketCallbackListener; import net.fabricmc.fabric.impl.networking.PacketCallbackListener;
import net.fabricmc.fabric.impl.networking.payload.PacketByteBufLoginQueryResponse; import net.fabricmc.fabric.impl.networking.payload.PacketByteBufLoginQueryResponse;
import net.fabricmc.fabric.impl.networking.server.ServerLoginNetworkAddon; import net.fabricmc.fabric.impl.networking.server.ServerLoginNetworkAddon;
@Mixin(ServerLoginNetworkHandler.class) @Mixin(ServerLoginNetworkHandler.class)
abstract class ServerLoginNetworkHandlerMixin implements NetworkHandlerExtensions, DisconnectPacketSource, PacketCallbackListener { abstract class ServerLoginNetworkHandlerMixin implements NetworkHandlerExtensions, PacketCallbackListener {
@Shadow @Shadow
protected abstract void tickVerify(GameProfile profile); protected abstract void tickVerify(GameProfile profile);
@ -90,9 +87,4 @@ abstract class ServerLoginNetworkHandlerMixin implements NetworkHandlerExtension
public ServerLoginNetworkAddon getAddon() { public ServerLoginNetworkAddon getAddon() {
return this.addon; return this.addon;
} }
@Override
public Packet<?> createDisconnectPacket(Text message) {
return new LoginDisconnectS2CPacket(message);
}
} }

View file

@ -23,22 +23,18 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.network.ClientConnection; import net.minecraft.network.ClientConnection;
import net.minecraft.network.packet.Packet;
import net.minecraft.network.packet.c2s.common.CustomPayloadC2SPacket; import net.minecraft.network.packet.c2s.common.CustomPayloadC2SPacket;
import net.minecraft.network.packet.s2c.common.DisconnectS2CPacket;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ConnectedClientData; import net.minecraft.server.network.ConnectedClientData;
import net.minecraft.server.network.ServerCommonNetworkHandler; import net.minecraft.server.network.ServerCommonNetworkHandler;
import net.minecraft.server.network.ServerPlayNetworkHandler; import net.minecraft.server.network.ServerPlayNetworkHandler;
import net.minecraft.text.Text;
import net.fabricmc.fabric.impl.networking.DisconnectPacketSource;
import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions; import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions;
import net.fabricmc.fabric.impl.networking.server.ServerPlayNetworkAddon; import net.fabricmc.fabric.impl.networking.server.ServerPlayNetworkAddon;
// We want to apply a bit earlier than other mods which may not use us in order to prevent refCount issues // We want to apply a bit earlier than other mods which may not use us in order to prevent refCount issues
@Mixin(value = ServerPlayNetworkHandler.class, priority = 999) @Mixin(value = ServerPlayNetworkHandler.class, priority = 999)
abstract class ServerPlayNetworkHandlerMixin extends ServerCommonNetworkHandler implements NetworkHandlerExtensions, DisconnectPacketSource { abstract class ServerPlayNetworkHandlerMixin extends ServerCommonNetworkHandler implements NetworkHandlerExtensions {
@Unique @Unique
private ServerPlayNetworkAddon addon; private ServerPlayNetworkAddon addon;
@ -64,9 +60,4 @@ abstract class ServerPlayNetworkHandlerMixin extends ServerCommonNetworkHandler
public ServerPlayNetworkAddon getAddon() { public ServerPlayNetworkAddon getAddon() {
return this.addon; return this.addon;
} }
@Override
public Packet<?> createDisconnectPacket(Text message) {
return new DisconnectS2CPacket(message);
}
} }

View file

@ -256,7 +256,7 @@ public class ModNioResourcePack implements ResourcePack, ModResourcePack {
@Override @Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
String filename = nsPath.relativize(file).toString().replace(separator, "/"); String filename = nsPath.relativize(file).toString().replace(separator, "/");
Identifier identifier = Identifier.of(namespace, filename); Identifier identifier = Identifier.tryParse(namespace, filename);
if (identifier == null) { if (identifier == null) {
LOGGER.error("Invalid path in mod resource-pack {}: {}:{}, ignoring", id, namespace, filename); LOGGER.error("Invalid path in mod resource-pack {}: {}:{}, ignoring", id, namespace, filename);

View file

@ -50,7 +50,7 @@ import net.minecraft.screen.ScreenHandlerType;
* // Creating and registering the type * // Creating and registering the type
* public static final ExtendedScreenHandlerType<OvenScreenHandler> OVEN = * public static final ExtendedScreenHandlerType<OvenScreenHandler> OVEN =
* new ExtendedScreenHandlerType((syncId, inventory, data) -> ..., OvenData.PACKET_CODEC); * new ExtendedScreenHandlerType((syncId, inventory, data) -> ..., OvenData.PACKET_CODEC);
* Registry.register(Registry.SCREEN_HANDLER, new Identifier(...), OVEN); * Registry.register(Registry.SCREEN_HANDLER, Identifier.of(...), OVEN);
* *
* // Note: remember to also register the screen using vanilla's HandledScreens! * // Note: remember to also register the screen using vanilla's HandledScreens!
* *