Update the Dimension API ()

* Reimplement Dimension API

* Forgot to update dimension-api mixins.json

* Did suggested changes.

* Move Nullable import.

* Remove unneeded whitespace

* Added automatic testing testmod feature.

* ...Removed unused imports...

* And other stuff the auto style system complains about...

* ...

* Drop Networking

* Completed suggested changes.

* Revert "Completed suggested changes."

This reverts commit 97740e6134.

* Did requested changes.

* Forgot colon

* Turn plural to singular

Co-authored-by: i509VCB <git@i509.me>
This commit is contained in:
Waterpicker 2020-11-28 13:47:10 -06:00 committed by modmuss50
parent fddcc0d88f
commit bbf4c01986
15 changed files with 216 additions and 383 deletions
fabric-dimensions-v1
build.gradle
src
main
testmod
java/net/fabricmc/fabric/test/dimension
resources/data/fabric_dimension
dimension
dimension_type
settings.gradle

View file

@ -3,10 +3,10 @@ version = getSubprojectVersion(project, "1.0.0")
dependencies {
testmodCompile project(path: ':fabric-command-api-v1', configuration: 'dev')
testmodCompile project(path: ':fabric-resource-loader-v0', configuration: 'dev')
testmodCompile project(path: ':fabric-lifecycle-events-v1', configuration: 'dev')
}
moduleDependencies(project, [
'fabric-api-base',
'fabric-networking-v0',
'fabric-registry-sync-v0'
'fabric-api-base'
])

View file

@ -1,50 +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 net.minecraft.block.pattern.BlockPattern;
import net.minecraft.entity.Entity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.Direction;
/**
* Responsible for placing an Entity once they have entered a dimension.
* used in Entity::changeDimension.
*
* @deprecated Experimental feature, may be removed or changed without further notice due to potential changes to Dimensions in subsequent versions.
*
* @see FabricDimensions
*/
@Deprecated
@FunctionalInterface
public interface EntityPlacer {
/**
* Handles the placement of an entity going to a dimension.
* Utilized by {@link FabricDimensions#teleport(Entity, ServerWorld, EntityPlacer)} to specify placement logic when needed.
*
* <p>This method may have side effects such as the creation of a portal in the target dimension,
* or the creation of a chunk loading ticket.
*
* @param portalDir the direction the portal is facing, meaningless if no portal was used
* @param horizontalOffset the horizontal offset of the entity relative to the front top left corner of the portal, meaningless if no portal was used
* @param verticalOffset the vertical offset of the entity relative to the front top left corner of the portal, meaningless if no portal was used
* @return a teleportation target, or {@code null} to fall back to further handling
* @apiNote When this method is called, the entity's world is its source dimension.
*/
/* @Nullable */
BlockPattern.TeleportTarget placeEntity(Entity teleported, ServerWorld destination, Direction portalDir, double horizontalOffset, double verticalOffset);
}

View file

@ -17,11 +17,11 @@
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.server.world.ServerWorld;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.World;
import net.minecraft.world.TeleportTarget;
import net.fabricmc.fabric.impl.dimension.FabricDimensionInternals;
@ -37,74 +37,29 @@ public final class FabricDimensions {
}
/**
* Teleports an entity to a different dimension, using custom placement logic.
* Teleports an entity to a different dimension, placing it at the specified destination.
*
* <p>This method behaves as if:
* <pre>{@code teleported.changeDimension(destination)}</pre>
* <p>Using this method will circumvent Vanilla's portal placement code.
*
* <p>If {@code destination} has a default placer, that placer will be used. If {@code destination} is
* the nether or the overworld, the default logic is the vanilla path.
* For any other dimension, the default placement behaviour is undefined.
* When delegating to a placement logic that uses portals, the entity's {@code lastPortalPosition},
* {@code lastPortalDirectionVector}, and {@code lastPortalDirection} fields should be updated
* before calling this method.
*
* <p>After calling this method, {@code teleported} may be invalidated. Callers should use
* the returned entity for any further manipulation.
* <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 destination the dimension the entity will be teleported to
* @return the teleported entity, or a clone of it
* @see #teleport(Entity, ServerWorld)
*/
public static <E extends Entity> E teleport(E teleported, ServerWorld destination) {
return teleport(teleported, destination, null);
}
/**
* Teleports an entity to a different dimension, using custom placement logic.
*
* <p>If {@code customPlacement} is {@code null}, this method behaves as if:
* <pre>{@code teleported.changeDimension(destination)}</pre>
* The {@code customPlacement} may itself return {@code null}, in which case
* the default placement logic for that dimension will be run.
*
* <p>If {@code destination} has a default placer, that placer will be used. If {@code destination} is
* the nether or the overworld, the default logic is the vanilla path.
* For any other dimension, the default placement behaviour is undefined.
* When delegating to a placement logic that uses portals, the entity's {@code lastPortalPosition},
* {@code lastPortalDirectionVector}, and {@code lastPortalDirection} fields should be updated
* before calling this method.
*
* <p>After calling this method, {@code teleported} may be invalidated. Callers should use
* the returned entity for any further manipulation.
*
* @param teleported the entity to teleport
* @param destination the dimension the entity will be teleported to
* @param customPlacer custom placement logic that will run before the default one,
* or {@code null} to use the dimension's default behavior.
* @param <E> the type of the teleported entity
* @return the teleported entity, or a clone of it
* @param target where the entity will be placed in the target world.
* As in Vanilla, the target's velocity is not applied to players.
* If target is null, the entity will not be teleported.
* @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
*/
public static <E extends Entity> E teleport(E teleported, ServerWorld destination, /*Nullable*/ EntityPlacer customPlacer) {
@Nullable
public static <E extends Entity> E teleport(E teleported, ServerWorld destination, TeleportTarget target) {
Preconditions.checkNotNull(target, "A target must be provided");
Preconditions.checkState(!teleported.world.isClient, "Entities can only be teleported on the server side");
return FabricDimensionInternals.changeDimension(teleported, destination, customPlacer);
}
/**
* Register a default placer for a dimension, this is used when an entity is teleported to a dimension without
* a specified {@link EntityPlacer}.
*
* @param registryKey The dimension {@link RegistryKey}
* @param entityPlacer The {@link EntityPlacer}
*/
public static void registerDefaultPlacer(RegistryKey<World> registryKey, EntityPlacer entityPlacer) {
Preconditions.checkState(!FabricDimensionInternals.DEFAULT_PLACERS.containsKey(registryKey), "Only 1 EntityPlacer can be registered per dimension");
Preconditions.checkState(!registryKey.getValue().getNamespace().equals("minecraft"), "Minecraft dimensions cannot have a default placer");
FabricDimensionInternals.DEFAULT_PLACERS.put(registryKey, entityPlacer);
return FabricDimensionInternals.changeDimension(teleported, destination, target);
}
}

View file

@ -16,116 +16,41 @@
package net.fabricmc.fabric.impl.dimension;
import java.util.HashMap;
import java.util.Map;
import com.google.common.base.Preconditions;
import net.minecraft.block.pattern.BlockPattern;
import net.minecraft.entity.Entity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.Direction;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.World;
import net.minecraft.world.TeleportTarget;
import net.fabricmc.fabric.api.dimension.v1.EntityPlacer;
import net.fabricmc.fabric.api.dimension.v1.FabricDimensions;
import net.fabricmc.fabric.mixin.dimension.EntityHooks;
public final class FabricDimensionInternals {
/**
* The target passed to the last call to {@link FabricDimensions#teleport(Entity, ServerWorld, TeleportTarget)}.
*/
private static TeleportTarget currentTarget;
private FabricDimensionInternals() {
throw new AssertionError();
}
public static final Map<RegistryKey<World>, EntityPlacer> DEFAULT_PLACERS = new HashMap<>();
/**
* The entity currently being transported to another dimension.
* Returns the last target set when a user of the API requested teleportation, or null.
*/
private static final ThreadLocal<Entity> PORTAL_ENTITY = new ThreadLocal<>();
/**
* The custom placement logic passed from {@link FabricDimensions#teleport(Entity, ServerWorld, EntityPlacer)}.
*/
private static EntityPlacer customPlacement;
/*
* The dimension change hooks consist of two steps:
* - First, we memorize the currently teleported entity, and set required fields
* - Then, we retrieve the teleported entity in the placement logic in PortalForcer#getPortal
* and use it to call the entity placers
* This lets us use the exact same logic for any entity and prevent the vanilla getPortal (which has unwanted
* side effects) from running, while keeping the patches minimally invasive.
*
* Shortcomings: bugs may arise if another patch cancels the teleportation method between
* #prepareDimensionalTeleportation and #tryFindPlacement, AND a mod calls PortalForcer#getPortal directly
* right after.
*/
public static void prepareDimensionalTeleportation(Entity entity) {
Preconditions.checkNotNull(entity);
PORTAL_ENTITY.set(entity);
// Set values used by `PortalForcer#changeDimension` to prevent a NPE crash.
EntityHooks access = ((EntityHooks) entity);
if (entity.getLastNetherPortalDirectionVector() == null) {
access.setLastNetherPortalDirectionVector(entity.getRotationVector());
}
if (entity.getLastNetherPortalDirection() == null) {
access.setLastNetherPortalDirection(entity.getHorizontalFacing());
}
}
/* Nullable */
public static BlockPattern.TeleportTarget tryFindPlacement(ServerWorld destination, Direction portalDir, double portalX, double portalY) {
Preconditions.checkNotNull(destination);
Entity teleported = PORTAL_ENTITY.get();
PORTAL_ENTITY.set(null);
// If the entity is null, the call does not come from a vanilla context
if (teleported == null) {
return null;
}
// Custom placement logic, falls back to default dimension placement if no placement or target found
EntityPlacer customPlacement = FabricDimensionInternals.customPlacement;
if (customPlacement != null) {
BlockPattern.TeleportTarget customTarget = customPlacement.placeEntity(teleported, destination, portalDir, portalX, portalY);
if (customTarget != null) {
return customTarget;
}
}
// Default placement logic, falls back to vanilla if not a fabric dimension
RegistryKey<World> registryKey = destination.getRegistryKey();
if (DEFAULT_PLACERS.containsKey(registryKey)) {
BlockPattern.TeleportTarget defaultTarget = DEFAULT_PLACERS.get(registryKey).placeEntity(teleported, destination, portalDir, portalX, portalY);
if (defaultTarget == null) {
throw new IllegalStateException("Mod dimension " + destination.getRegistryKey().getValue().toString() + " returned an invalid teleport target");
}
return defaultTarget;
}
// Vanilla / other implementations logic, undefined behaviour on custom dimensions
return null;
public static TeleportTarget getCustomTarget() {
return currentTarget;
}
@SuppressWarnings("unchecked")
public static <E extends Entity> E changeDimension(E teleported, ServerWorld dimension, EntityPlacer placement) {
assert !teleported.world.isClient : "Entities can only be teleported on the server side";
assert Thread.currentThread() == ((ServerWorld) teleported.world).getServer().getThread() : "Entities must be teleported from the main server thread";
public static <E extends Entity> E changeDimension(E teleported, ServerWorld dimension, TeleportTarget target) {
Preconditions.checkArgument(!teleported.world.isClient, "Entities can only be teleported on the server side");
Preconditions.checkArgument(Thread.currentThread() == ((ServerWorld) teleported.world).getServer().getThread(), "Entities must be teleported from the main server thread");
try {
customPlacement = placement;
return (E) teleported.changeDimension(dimension);
currentTarget = target;
return (E) teleported.moveToWorld(dimension);
} finally {
customPlacement = null;
currentTarget = null;
}
}
}

View file

@ -1,33 +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.mixin.dimension;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import net.minecraft.entity.Entity;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d;
@Mixin(Entity.class)
public interface EntityHooks {
@Accessor
void setLastNetherPortalDirectionVector(Vec3d vec);
@Accessor
void setLastNetherPortalDirection(Direction dir);
}

View file

@ -23,14 +23,25 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import net.minecraft.entity.Entity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.world.TeleportTarget;
import net.fabricmc.fabric.impl.dimension.FabricDimensionInternals;
/**
* This mixin implements {@link Entity#getTeleportTarget(ServerWorld)} for modded dimensions, as Vanilla will
* not return a teleport target for anything but Vanilla dimensions.
*/
@Mixin(Entity.class)
public abstract class MixinEntity {
// Inject right before the direction vector is retrieved by the game
@Inject(method = "changeDimension", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;getLastNetherPortalDirectionVector()Lnet/minecraft/util/math/Vec3d;"))
private void onGetPortal(ServerWorld targetWorld, CallbackInfoReturnable<Entity> cir) {
FabricDimensionInternals.prepareDimensionalTeleportation((Entity) (Object) this);
public class EntityMixin {
@SuppressWarnings("ConstantConditions")
@Inject(method = "getTeleportTarget", at = @At("HEAD"), cancellable = true, allow = 1)
public void getTeleportTarget(ServerWorld destination, CallbackInfoReturnable<TeleportTarget> cri) {
Entity self = (Entity) (Object) this;
// Check if a destination has been set for the entity currently being teleported
TeleportTarget customTarget = FabricDimensionInternals.getCustomTarget();
if (customTarget != null) {
cri.setReturnValue(customTarget);
}
}
}

View file

@ -1,55 +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.mixin.dimension;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import net.minecraft.block.pattern.BlockPattern;
import net.minecraft.entity.Entity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.PortalForcer;
import net.fabricmc.fabric.impl.dimension.FabricDimensionInternals;
@Mixin(PortalForcer.class)
public abstract class MixinPortalForcer {
@Shadow
@Final
private ServerWorld world;
@Inject(method = "usePortal", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;getLastNetherPortalDirectionVector()Lnet/minecraft/util/math/Vec3d;"))
private void onUsePortal(Entity teleported, float yaw, CallbackInfoReturnable<Boolean> cir) {
FabricDimensionInternals.prepareDimensionalTeleportation(teleported);
}
@Inject(method = "getPortal", at = @At("HEAD"), cancellable = true)
private void findEntityPlacement(BlockPos pos, Vec3d velocity, Direction portalDir, double portalX, double portalY, boolean player, CallbackInfoReturnable<BlockPattern.TeleportTarget> cir) {
BlockPattern.TeleportTarget ret = FabricDimensionInternals.tryFindPlacement(this.world, portalDir, portalX, portalY);
if (ret != null) {
cir.setReturnValue(ret);
}
}
}

View file

@ -1,58 +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.mixin.dimension;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import net.minecraft.world.World;
import net.minecraft.world.dimension.DimensionType;
@Mixin(World.class)
public abstract class MixinWorld {
@Shadow
public abstract DimensionType getDimension();
@Shadow
private int ambientDarkness;
/* World.isDay() and World.isNight() enable the day-night cycle as well as some entity behavior
* (such as bees). In vanilla, these methods are hardcoded to only work in the overworld. This
* redirector pretends that all dimensions with a visible sky are DimensionType.OVERWORLD, which
* makes the time checks for modded dimensions work.
*
* Dimension.hasVisibleSky() is true for the overworld, false for the nether and the end, and
* customizable for modded dimensions. It is already used for time checking in other places
* such as clocks.
*/
@Inject(method = "isDay", at = @At("HEAD"), cancellable = true)
private void isDay(CallbackInfoReturnable<Boolean> infoReturnable) {
if (getDimension().hasSkyLight()) {
infoReturnable.setReturnValue(ambientDarkness < 4);
}
}
@Inject(method = "isNight", at = @At("HEAD"), cancellable = true)
private void isNight(CallbackInfoReturnable<Boolean> infoReturnable) {
if (getDimension().hasSkyLight()) {
infoReturnable.setReturnValue(!(ambientDarkness < 4));
}
}
}

View file

@ -0,0 +1,92 @@
/*
* 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.dimension;
import com.mojang.serialization.Lifecycle;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import org.spongepowered.asm.mixin.injection.Redirect;
import net.minecraft.nbt.Tag;
import net.minecraft.resource.DataPackSettings;
import net.minecraft.server.Main;
import net.minecraft.util.dynamic.RegistryOps;
import net.minecraft.util.registry.DynamicRegistryManager;
import net.minecraft.world.gen.GeneratorOptions;
import net.minecraft.world.level.LevelInfo;
import net.minecraft.world.level.LevelProperties;
import net.minecraft.world.level.storage.LevelStorage;
/**
* This Mixin aims to solve a Minecraft Vanilla bug where datapacks are ignored during creation of the
* initial LevelProperties when a dedicated server creates a completely new level.
*
* <p>This also includes the datapacks of loaded Fabric mods, and results in modded dimensions only
* being available after restarting the server, once the world has been created.
*
* <p>This Mixin aims to solve this problem by saving and loading the level.dat file once, after
* a new set of level properties is created. This will apply the same logic as reloading the
* level.dat after a restart, now including all datapack dimensions.
*
* <p>See https://bugs.mojang.com/browse/MC-195468 for a related bug report.
*
* <p>In 1.17: Retest if this bug still occurs without this Mixin by launching a dedicated server with the
* dimension testmod, and no world directory. If the dimension is available (i.e. in /execute in, or via
* the testmod's commands), then the bug is fixed and this Mixin can be removed.
*/
@Mixin(value = Main.class, remap = false)
public class ServerBugfixMixin {
@Unique
private static LevelStorage.Session fabric_session;
@Unique
private static DynamicRegistryManager.Impl fabric_dynamicRegistry;
@Unique
private static RegistryOps<Tag> fabric_registryOps;
@ModifyVariable(at = @At(value = "INVOKE_ASSIGN", target = "Lnet/minecraft/util/registry/DynamicRegistryManager;create()Lnet/minecraft/util/registry/DynamicRegistryManager$Impl;"), method = "main", remap = false, allow = 1)
private static DynamicRegistryManager.Impl captureDynamicRegistry(DynamicRegistryManager.Impl value) {
fabric_dynamicRegistry = value;
return value;
}
@ModifyVariable(at = @At(value = "INVOKE_ASSIGN", target = "Lnet/minecraft/world/level/storage/LevelStorage;createSession(Ljava/lang/String;)Lnet/minecraft/world/level/storage/LevelStorage$Session;"), method = "main", remap = false, allow = 1)
private static LevelStorage.Session captureSession(LevelStorage.Session value) {
fabric_session = value;
return value;
}
@ModifyVariable(at = @At(value = "INVOKE_ASSIGN", target = "Lnet/minecraft/util/dynamic/RegistryOps;of(Lcom/mojang/serialization/DynamicOps;Lnet/minecraft/resource/ResourceManager;Lnet/minecraft/util/registry/DynamicRegistryManager$Impl;)Lnet/minecraft/util/dynamic/RegistryOps;"), method = "main", remap = false, allow = 1)
private static RegistryOps<Tag> captureRegistryOps(RegistryOps<Tag> value) {
fabric_registryOps = value;
return value;
}
@Redirect(method = "main", at = @At(value = "NEW", target = "net/minecraft/world/level/LevelProperties"), remap = false, allow = 1)
private static LevelProperties onCreateNewLevelProperties(LevelInfo levelInfo, GeneratorOptions generatorOptions, Lifecycle lifecycle) {
DataPackSettings dataPackSettings = levelInfo.getDataPackSettings();
// Save the level.dat file
fabric_session.backupLevelDataFile(fabric_dynamicRegistry, new LevelProperties(levelInfo, generatorOptions, lifecycle));
// And reload it again, and replace the actual level properties with it
return (LevelProperties) fabric_session.readLevelProperties(fabric_registryOps, dataPackSettings);
}
}

View file

@ -3,10 +3,8 @@
"package": "net.fabricmc.fabric.mixin.dimension",
"compatibilityLevel": "JAVA_8",
"mixins": [
"EntityHooks",
"MixinEntity",
"MixinPortalForcer",
"MixinWorld"
"EntityMixin",
"ServerBugfixMixin"
],
"injectors": {
"defaultRequire": 1

View file

@ -16,65 +16,113 @@
package net.fabricmc.fabric.test.dimension;
import static net.minecraft.entity.EntityType.COW;
import static net.minecraft.server.command.CommandManager.literal;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.minecraft.block.Blocks;
import net.minecraft.block.pattern.BlockPattern;
import net.minecraft.command.CommandException;
import net.minecraft.entity.Entity;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.text.LiteralText;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.registry.Registry;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.TeleportTarget;
import net.minecraft.world.World;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.dimension.DimensionOptions;
import net.minecraft.world.dimension.DimensionType;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
import net.fabricmc.fabric.api.dimension.v1.FabricDimensions;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
public class FabricDimensionTest implements ModInitializer {
private static RegistryKey<World> dimensionRegistryKey;
// The dimension options refer to the JSON-file in the dimension subfolder of the datapack,
// which will always share it's ID with the world that is created from it
private static final RegistryKey<DimensionOptions> DIMENSION_KEY = RegistryKey.of(
Registry.DIMENSION_OPTIONS,
new Identifier("fabric_dimension", "void")
);
private static RegistryKey<World> WORLD_KEY = RegistryKey.of(
Registry.DIMENSION,
DIMENSION_KEY.getValue()
);
private static final RegistryKey<DimensionType> DIMENSION_TYPE_KEY = RegistryKey.of(
Registry.DIMENSION_TYPE_KEY,
new Identifier("fabric_dimension", "void_type")
);
@Override
public void onInitialize() {
Registry.register(Registry.CHUNK_GENERATOR, new Identifier("fabric_dimension", "void"), VoidChunkGenerator.CODEC);
dimensionRegistryKey = RegistryKey.of(Registry.DIMENSION, new Identifier("fabric_dimension", "void"));
WORLD_KEY = RegistryKey.of(Registry.DIMENSION, new Identifier("fabric_dimension", "void"));
FabricDimensions.registerDefaultPlacer(dimensionRegistryKey, FabricDimensionTest::placeEntityInVoid);
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.world.getRegistryKey().equals(World.OVERWORLD)) throw new AssertionError("Entity starting world isn't the overworld");
TeleportTarget target = new TeleportTarget(Vec3d.ZERO, new Vec3d(1, 1, 1), 45f, 60f);
Entity teleported = FabricDimensions.teleport(entity, world, target);
if (teleported == null) throw new AssertionError("Entity didn't teleport");
if (!teleported.world.getRegistryKey().equals(WORLD_KEY)) throw new AssertionError("Target world not reached.");
if (!teleported.getPos().equals(target.position)) throw new AssertionError("Target Position not reached.");
});
CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) ->
dispatcher.register(literal("fabric_dimension_test").executes(FabricDimensionTest.this::executeTestCommand))
dispatcher.register(literal("fabric_dimension_test").executes(FabricDimensionTest.this::swapTargeted))
);
}
private int executeTestCommand(CommandContext<ServerCommandSource> context) throws CommandSyntaxException {
ServerPlayerEntity serverPlayerEntity = context.getSource().getPlayer();
ServerWorld serverWorld = serverPlayerEntity.getServerWorld();
private int swapTargeted(CommandContext<ServerCommandSource> context) throws CommandSyntaxException {
ServerPlayerEntity player = context.getSource().getPlayer();
ServerWorld serverWorld = player.getServerWorld();
ServerWorld modWorld = getModWorld(context);
if (!serverWorld.getRegistryKey().equals(dimensionRegistryKey)) {
serverPlayerEntity.changeDimension(context.getSource().getMinecraftServer().getWorld(dimensionRegistryKey));
if (serverWorld != modWorld) {
TeleportTarget target = new TeleportTarget(new Vec3d(0.5, 101, 0.5), Vec3d.ZERO, 0, 0);
FabricDimensions.teleport(player, modWorld, target);
if (player.world != modWorld) {
throw new CommandException(new LiteralText("Teleportation failed!"));
}
modWorld.setBlockState(new BlockPos(0, 100, 0), Blocks.DIAMOND_BLOCK.getDefaultState());
modWorld.setBlockState(new BlockPos(0, 101, 0), Blocks.TORCH.getDefaultState());
} else {
FabricDimensions.teleport(serverPlayerEntity, context.getSource().getMinecraftServer().getWorld(World.OVERWORLD), FabricDimensionTest::placeEntity);
TeleportTarget target = new TeleportTarget(new Vec3d(0, 100, 0), Vec3d.ZERO,
(float) Math.random() * 360 - 180, (float) Math.random() * 360 - 180);
FabricDimensions.teleport(player, getWorld(context, World.OVERWORLD), target);
}
return 1;
}
private static BlockPattern.TeleportTarget placeEntity(Entity teleported, ServerWorld destination, Direction portalDir, double horizontalOffset, double verticalOffset) {
return new BlockPattern.TeleportTarget(new Vec3d(0, 100, 0), Vec3d.ZERO, 0);
private ServerWorld getModWorld(CommandContext<ServerCommandSource> context) {
return getWorld(context, WORLD_KEY);
}
private static BlockPattern.TeleportTarget placeEntityInVoid(Entity teleported, ServerWorld destination, Direction portalDir, double horizontalOffset, double verticalOffset) {
destination.setBlockState(new BlockPos(0, 100, 0), Blocks.DIAMOND_BLOCK.getDefaultState());
destination.setBlockState(new BlockPos(0, 101, 0), Blocks.TORCH.getDefaultState());
return new BlockPattern.TeleportTarget(new Vec3d(0.5, 101, 0.5), Vec3d.ZERO, 0);
private ServerWorld getWorld(CommandContext<ServerCommandSource> context, RegistryKey<World> dimensionRegistryKey) {
return context.getSource().getMinecraftServer().getWorld(dimensionRegistryKey);
}
}

View file

@ -37,7 +37,7 @@ public class VoidChunkGenerator extends ChunkGenerator {
public static final Codec<VoidChunkGenerator> CODEC = RecordCodecBuilder.create((instance) ->
instance.group(
BiomeSource.field_24713.fieldOf("biome_source")
BiomeSource.CODEC.fieldOf("biome_source")
.forGetter((generator) -> generator.biomeSource),
Codec.BOOL.fieldOf("custom_bool")
.forGetter((generator) -> generator.customBool)
@ -51,7 +51,7 @@ public class VoidChunkGenerator extends ChunkGenerator {
}
@Override
protected Codec<? extends ChunkGenerator> method_28506() {
protected Codec<? extends ChunkGenerator> getCodec() {
return CODEC;
}

View file

@ -1,7 +1,7 @@
{
"ultrawarm": false,
"natural": false,
"shrunk": false,
"coordinate_scale": 1,
"ambient_light": 0.1,
"has_skylight": true,
"has_ceiling": false,

View file

@ -21,7 +21,7 @@ include 'fabric-command-api-v1'
include 'fabric-containers-v0'
include 'fabric-content-registries-v0'
include 'fabric-crash-report-info-v1'
//include 'fabric-dimensions-v1'
include 'fabric-dimensions-v1'
include 'fabric-events-interaction-v0'
include 'fabric-events-lifecycle-v0'
include 'fabric-game-rule-api-v1'