Rework Dimensions for 1.16 ()

* dim api initial port

* A whole bunch of work

* Checkstyle :)

* Minor tweaks based on feedback

* Update to latest snapshot

* Checkstyle ;)

* Some more dim work

* Re add default placer's, the example mod includes a test dim that currently marks the world as experimental

* license

* Fixup javadoc
This commit is contained in:
modmuss50 2020-06-11 11:38:38 +01:00 committed by GitHub
parent 5a6e8f4ce1
commit a71b3053ad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 257 additions and 780 deletions

View file

@ -1,8 +1,10 @@
archivesBaseName = "fabric-dimensions-v1"
version = getSubprojectVersion(project, "0.4.2")
version = getSubprojectVersion(project, "1.0.0")
dependencies {
compile project(path: ':fabric-api-base', configuration: 'dev')
compile project(path: ':fabric-networking-v0', configuration: 'dev')
compile project(path: ':fabric-registry-sync-v0', configuration: 'dev')
testmodCompile project(path: ':fabric-command-api-v1', configuration: 'dev')
}

View file

@ -23,16 +23,18 @@ import net.minecraft.util.math.Direction;
/**
* Responsible for placing an Entity once they have entered a dimension.
* Stored by a FabricDimensionType, and used in Entity::changeDimension.
* 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
* @see FabricDimensionType
*/
@Deprecated
@FunctionalInterface
public interface EntityPlacer {
/**
* Handles the placement of an entity going to a dimension.
* Utilized by {@link FabricDimensions#teleport(Entity, net.minecraft.world.dimension.DimensionType, EntityPlacer)} to specify placement logic when needed.
* 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.

View file

@ -1,244 +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 java.util.function.BiFunction;
import com.google.common.base.Preconditions;
import net.minecraft.entity.Entity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.Direction;
import net.minecraft.util.registry.Registry;
import net.minecraft.world.World;
import net.minecraft.world.biome.source.BiomeAccessType;
import net.minecraft.world.biome.source.VoronoiBiomeAccessType;
import net.minecraft.world.dimension.Dimension;
import net.minecraft.world.dimension.DimensionType;
/**
* An extended version of {@link DimensionType} with automatic raw id management and default placement settings.
* {@code FabricDimensionType} instances are constructed and registered through a {@link Builder}.
*
* @see #builder()
* @see #getDefaultPlacement()
* @see #getDesiredRawId()
*/
public final class FabricDimensionType extends DimensionType {
private final EntityPlacer defaultPlacement;
private int desiredRawId;
/** The fixed raw id for this dimension type, set through reflection. */
private int fixedRawId;
/**
* Returns a new {@link Builder}.
*/
public static Builder builder() {
return new FabricDimensionType.Builder();
}
/**
* @param suffix the string suffix unique to the dimension type
* @param saveDir the name of the save directory for the dimension type
* @param builder builder instance containing other parameters
* @see #builder()
*/
private FabricDimensionType(String suffix, String saveDir, Builder builder) {
// Pass an arbitrary raw id that does not map to any vanilla dimension. That id should never get used.
super(3, suffix, saveDir, builder.factory, builder.skyLight, false, false, builder.biomeAccessStrategy);
this.defaultPlacement = builder.defaultPlacer;
}
/**
* Return the desired raw id of this dimension type.
*
* @return the preferred raw id of this dimension type
* @see Builder#desiredRawId(int)
*/
public int getDesiredRawId() {
return desiredRawId;
}
/**
* Return the current raw id for this dimension type.
*
* <p>The returned id is guaranteed to be unique and persistent in a save,
* as well as synchronized between a server and its connected clients.
* It may change when connecting to a different server or opening a new save.
*
* @return the current raw id for this dimension type
* @see #getDesiredRawId()
*/
@Override
public int getRawId() {
return this.fixedRawId;
}
/**
* Return the default placement logic for this dimension. The returned placer
* never returns {@code null} when called.
*
* @return the default placement logic for this dimension
* @see FabricDimensions#teleport(Entity, DimensionType, EntityPlacer)
*/
public EntityPlacer getDefaultPlacement() {
return this.defaultPlacement;
}
/**
* A builder for creating and registering {@code FabricDimensionType} instances. Example: <pre> {@code
*
* public static final FabricDimensionType MY_DIMENSION
* = FabricDimensionType.builder()
* .defaultPlacement((oldEntity, destination, portalDir, horizontalOffset, verticalOffset) ->
* new BlockPattern.TeleportTarget(new Vec3d(0, 100, 0), teleported.getVelocity(), 0))
* .factory(MyDimension::new)
* .skyLight(true)
* .buildAndRegister();}</pre>
*
* <p>Builder instances can be reused; it is safe to call {@link #buildAndRegister(Identifier)} multiple
* times (with different identifiers) to build and register multiple dimension types in series.
* Each new dimension type uses the settings of the builder at the time it is built.
*
* @see FabricDimensionType#builder()
*/
public static final class Builder {
private EntityPlacer defaultPlacer;
private BiFunction<World, DimensionType, ? extends Dimension> factory;
private int desiredRawId = 0;
private boolean skyLight = true;
private BiomeAccessType biomeAccessStrategy = VoronoiBiomeAccessType.INSTANCE;
private Builder() {
}
/**
* Set the default placer used when teleporting entities to dimensions of the built type.
* The default placer must be set before building a dimension type.
*
* <p>A dimension type's default placer must never return {@code null} when its
* {@link EntityPlacer#placeEntity(Entity, ServerWorld, Direction, double, double) placeEntity} method
* is called.
*
* @param defaultPlacer a default entity placer for dimensions of the built type
* @return this {@code Builder} object
* @throws NullPointerException if {@code defaultPlacer} is {@code null}
*/
public Builder defaultPlacer(EntityPlacer defaultPlacer) {
Preconditions.checkNotNull(defaultPlacer);
this.defaultPlacer = defaultPlacer;
return this;
}
/**
* Set the factory used to create new {@link Dimension} instances of the built type.
* The dimension factory must be set before building a dimension type.
*
* @param factory a function creating new {@code Dimension} instances
* @return this {@code Builder} object
* @throws NullPointerException if {@code factory} is {@code null}
*/
public Builder factory(BiFunction<World, DimensionType, ? extends Dimension> factory) {
Preconditions.checkNotNull(factory);
this.factory = factory;
return this;
}
/**
* Set whether built dimension types use skylight like the Overworld.
* If this method is not called, the value defaults to {@code true}.
*
* @param skyLight {@code true} if the dimension of the built type should use skylight,
* {@code false} otherwise
* @return this {@code Builder} object
*/
public Builder skyLight(boolean skyLight) {
this.skyLight = skyLight;
return this;
}
/**
* Governs how biome information is retrieved from random seed and world coordinates.
* If this method is not called, value defaults to the three-dimensional strategy
* used by the End and Nether dimensions.
*
* @param biomeAccessStrategy Function to be used for biome generation.
* @return this {@code Builder} object
*/
public Builder biomeAccessStrategy(BiomeAccessType biomeAccessStrategy) {
Preconditions.checkNotNull(biomeAccessStrategy);
this.biomeAccessStrategy = biomeAccessStrategy;
return this;
}
/**
* Sets this dimension's desired raw id.
* If this method is not called, the value defaults to the raw registry id
* of the dimension type.
*
* <p>A Fabric Dimension's desired raw id is used as its actual raw id
* when it does not conflict with any existing id, and the world
* save does not map the dimension to a different raw id.
*
* @param desiredRawId the new raw id for this dimension type
* @return this {@code Builder} object
* @apiNote Mods that used to have a dimension with a manually set id
* may use this method to set a default id corresponding to the old one,
* so as not to break compatibility with old worlds.
*/
public Builder desiredRawId(int desiredRawId) {
this.desiredRawId = desiredRawId;
return this;
}
/**
* Build and register a {@code FabricDimensionType}.
*
* <p>The {@code dimensionId} is used as a registry ID, and as
* a unique name both for the dimension suffix and the save directory.
*
* @param dimensionId the id used to name and register the dimension
* @return the built {@code FabricDimensionType}
* @throws IllegalArgumentException if an existing dimension has already been registered with {@code dimensionId}
* @throws IllegalStateException if no {@link #factory(BiFunction) factory} or {@link #defaultPlacer(EntityPlacer) default placer}
* have been set
*/
public FabricDimensionType buildAndRegister(Identifier dimensionId) {
Preconditions.checkArgument(Registry.DIMENSION_TYPE.get(dimensionId) == null);
Preconditions.checkState(this.defaultPlacer != null, "No defaultPlacer has been specified!");
Preconditions.checkState(this.factory != null, "No dimension factory has been specified!");
String suffix = dimensionId.getNamespace() + "_" + dimensionId.getPath();
String saveDir = "DIM_" + dimensionId.getNamespace() + "_" + dimensionId.getPath();
FabricDimensionType built = new FabricDimensionType(suffix, saveDir, this);
Registry.register(Registry.DIMENSION_TYPE, dimensionId, built);
if (this.desiredRawId != 0) {
built.desiredRawId = this.desiredRawId;
} else {
built.desiredRawId = Registry.DIMENSION_TYPE.getRawId(built) - 1;
}
built.fixedRawId = built.desiredRawId;
return built;
}
}
}

View file

@ -19,13 +19,18 @@ package net.fabricmc.fabric.api.dimension.v1;
import com.google.common.base.Preconditions;
import net.minecraft.entity.Entity;
import net.minecraft.world.dimension.DimensionType;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.World;
import net.fabricmc.fabric.impl.dimension.FabricDimensionInternals;
/**
* This class consists exclusively of static methods that operate on world dimensions.
*
* @deprecated Experimental feature, may be removed or changed without further notice due to potential changes to Dimensions in subsequent versions.
*/
@Deprecated
public final class FabricDimensions {
private FabricDimensions() {
throw new AssertionError();
@ -37,8 +42,7 @@ public final class FabricDimensions {
* <p>This method behaves as if:
* <pre>{@code teleported.changeDimension(destination)}</pre>
*
* <p>If {@code destination} is a {@link FabricDimensionType}, the placement logic used
* is {@link FabricDimensionType#getDefaultPlacement()}. If {@code destination} is
* <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},
@ -51,9 +55,9 @@ public final class FabricDimensions {
* @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, DimensionType, EntityPlacer)
* @see #teleport(Entity, ServerWorld)
*/
public static <E extends Entity> E teleport(E teleported, DimensionType destination) {
public static <E extends Entity> E teleport(E teleported, ServerWorld destination) {
return teleport(teleported, destination, null);
}
@ -65,9 +69,8 @@ public final class FabricDimensions {
* 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} is a {@link FabricDimensionType}, the default placement logic
* is {@link FabricDimensionType#getDefaultPlacement()}. If {@code destination} is the nether
* or the overworld, the default logic is the vanilla path.
* <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
@ -79,15 +82,29 @@ public final class FabricDimensions {
* @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 (see {@link FabricDimensionType#getDefaultPlacement()}).
* 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
* @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, DimensionType destination, /*Nullable*/ EntityPlacer customPlacer) {
public static <E extends Entity> E teleport(E teleported, ServerWorld destination, /*Nullable*/ EntityPlacer customPlacer) {
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);
}
}

View file

@ -1,151 +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 java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import io.netty.buffer.Unpooled;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.Packet;
import net.minecraft.util.Identifier;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.util.registry.Registry;
import net.minecraft.world.dimension.DimensionType;
import net.fabricmc.fabric.api.dimension.v1.FabricDimensionType;
import net.fabricmc.fabric.api.network.ServerSidePacketRegistry;
import net.fabricmc.fabric.impl.registry.sync.RemapException;
/**
* Handles fixing raw dimension ids between saves and servers,
* and synchronizes said ids.
*/
public class DimensionIdsFixer {
private static final Field FABRIC_DIMENSION_TYPE$RAW_ID;
static final Identifier ID = new Identifier("fabric", "dimension/sync");
/**
* Assigns a unique id to every registered {@link FabricDimensionType}, keeping the known ids
* from {@code savedIds}.
*
* @param savedIds a compound tag mapping dimension ids to raw ids
* @return id to raw id mappings of the current instance
* @throws RemapException if dimensions IDs conflict irredeemably
*/
public static CompoundTag apply(CompoundTag savedIds) throws RemapException {
/*
* We want to give to each fabric dimension a unique ID. We also want to give back previously assigned ids.
* And we have to take into account non-fabric dimensions, which raw IDs cannot change.
* So we iterate over every dimension, note the ones which id cannot change, then update the free ones.
*/
Int2ObjectMap<Identifier> fixedIds = new Int2ObjectOpenHashMap<>();
List<FabricDimensionType> fabricDimensions = new ArrayList<>();
CompoundTag fabricDimensionIds = new CompoundTag();
// step 1: detect all fabric and non-fabric dimensions
for (Identifier id : Registry.DIMENSION_TYPE.getIds()) {
DimensionType dimensionType = Objects.requireNonNull(DimensionType.byId(id));
if (dimensionType instanceof FabricDimensionType) {
FabricDimensionType fabricDimension = (FabricDimensionType) dimensionType;
fabricDimensions.add(fabricDimension);
// reset the fixed raw id to the preferred raw id
setFixedRawId(fabricDimension, fabricDimension.getDesiredRawId());
} else {
Identifier existing = fixedIds.put(dimensionType.getRawId(), id);
if (existing != null) {
throw new RemapException("Two non-fabric dimensions have the same raw dim id (" + dimensionType.getRawId() + ") : " + existing + " and " + id);
}
}
}
// step 2: read saved ids
for (String key : savedIds.getKeys()) {
int savedRawId = savedIds.getInt(key);
Identifier dimId = new Identifier(key);
Identifier existing = fixedIds.putIfAbsent(savedRawId, dimId);
if (existing != null && !existing.equals(dimId)) {
throw new RemapException("Saved fabric dimension got replaced with a non-fabric one! " + dimId + " replaced with " + existing + " (raw id: " + savedRawId + ")");
}
DimensionType dim = DimensionType.byId(dimId);
if (dim instanceof FabricDimensionType) {
setFixedRawId((FabricDimensionType) dim, savedRawId);
} else {
FabricDimensionInternals.LOGGER.warn("A saved dimension has {}: {}", dim == null ? "been removed" : "stopped using the dimensions API", dimId);
// Preserve saved ids in case the mod is eventually added back
fabricDimensionIds.putInt(dimId.toString(), savedRawId);
}
}
// step 3: de-duplicate raw ids for dimensions which ids are not fixed yet
int nextFreeId = 0;
for (FabricDimensionType fabricDimension : fabricDimensions) {
int rawDimId = fabricDimension.getRawId();
Identifier dimId = Objects.requireNonNull(DimensionType.getId(fabricDimension));
if (fixedIds.containsKey(rawDimId) && !fixedIds.get(rawDimId).equals(dimId)) {
while (fixedIds.containsKey(nextFreeId)) ++nextFreeId;
setFixedRawId(fabricDimension, nextFreeId);
rawDimId = nextFreeId;
}
fixedIds.put(rawDimId, dimId);
fabricDimensionIds.putInt(dimId.toString(), rawDimId);
}
return fabricDimensionIds;
}
/**
* Reflectively set the fixed raw id on a {@link FabricDimensionType}.
*
* @see FabricDimensionType#getRawId()
*/
private static void setFixedRawId(FabricDimensionType fabricDimension, int rawId) {
try {
FABRIC_DIMENSION_TYPE$RAW_ID.setInt(fabricDimension, rawId);
} catch (IllegalAccessException e) {
throw new RuntimeException("Failed to fix a raw id on a FabricDimensionType", e);
}
}
public static Packet<?> createPacket(DimensionIdsHolder dimensionIdsHolder) {
PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer());
buf.writeCompoundTag(dimensionIdsHolder.fabric_getDimensionIds());
return ServerSidePacketRegistry.INSTANCE.toPacket(ID, buf);
}
static {
try {
FABRIC_DIMENSION_TYPE$RAW_ID = FabricDimensionType.class.getDeclaredField("fixedRawId");
FABRIC_DIMENSION_TYPE$RAW_ID.setAccessible(true);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
}
}

View file

@ -1,26 +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 net.minecraft.nbt.CompoundTag;
/**
* An object holding a raw id -> full id map for fabric dimensions.
*/
public interface DimensionIdsHolder {
CompoundTag fabric_getDimensionIds();
}

View file

@ -1,25 +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 net.fabricmc.fabric.impl.registry.sync.RemapException;
public class DimensionRemapException extends RuntimeException {
public DimensionRemapException(String message, RemapException cause) {
super(message, cause);
}
}

View file

@ -1,63 +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 org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.text.LiteralText;
import net.fabricmc.fabric.api.network.ClientSidePacketRegistry;
import net.fabricmc.fabric.api.network.PacketContext;
import net.fabricmc.fabric.impl.registry.sync.RemapException;
/**
* Client entry point for fabric-dimensions.
*/
public final class FabricDimensionClientInit {
private static final Logger LOGGER = LogManager.getLogger();
public static void onClientInit() {
ClientSidePacketRegistry.INSTANCE.register(DimensionIdsFixer.ID, (ctx, buf) -> {
CompoundTag compound = buf.readCompoundTag();
ctx.getTaskQueue().execute(() -> {
if (compound == null) {
handleError(ctx, new RemapException("Received null compound tag in dimension sync packet!"));
return;
}
try {
DimensionIdsFixer.apply(compound);
} catch (RemapException e) {
handleError(ctx, e);
}
});
});
}
private static void handleError(PacketContext ctx, Exception e) {
LOGGER.error("Dimension id remapping failed!", e);
MinecraftClient.getInstance().execute(() -> ((ClientPlayerEntity) ctx.getPlayer()).networkHandler.getConnection().disconnect(
new LiteralText("Dimension id remapping failed: " + e)
));
}
}

View file

@ -16,18 +16,19 @@
package net.fabricmc.fabric.impl.dimension;
import java.util.HashMap;
import java.util.Map;
import com.google.common.base.Preconditions;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
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.world.dimension.DimensionType;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.World;
import net.fabricmc.fabric.api.dimension.v1.EntityPlacer;
import net.fabricmc.fabric.api.dimension.v1.FabricDimensionType;
import net.fabricmc.fabric.api.dimension.v1.FabricDimensions;
import net.fabricmc.fabric.mixin.dimension.EntityHooks;
@ -36,15 +37,14 @@ public final class FabricDimensionInternals {
throw new AssertionError();
}
public static final boolean DEBUG = System.getProperty("fabric.dimension.debug", "false").equalsIgnoreCase("true");
public static final Logger LOGGER = LogManager.getLogger();
public static final Map<RegistryKey<World>, EntityPlacer> DEFAULT_PLACERS = new HashMap<>();
/**
* The entity currently being transported to another dimension.
*/
private static final ThreadLocal<Entity> PORTAL_ENTITY = new ThreadLocal<>();
/**
* The custom placement logic passed from {@link FabricDimensions#teleport(Entity, DimensionType, EntityPlacer)}.
* The custom placement logic passed from {@link FabricDimensions#teleport(Entity, ServerWorld, EntityPlacer)}.
*/
private static EntityPlacer customPlacement;
@ -100,13 +100,13 @@ public final class FabricDimensionInternals {
}
// Default placement logic, falls back to vanilla if not a fabric dimension
DimensionType dimType = destination.getDimension().getType();
RegistryKey<World> registryKey = destination.getRegistryKey();
if (dimType instanceof FabricDimensionType) {
BlockPattern.TeleportTarget defaultTarget = ((FabricDimensionType) dimType).getDefaultPlacement().placeEntity(teleported, destination, portalDir, portalX, portalY);
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 " + DimensionType.getId(dimType) + " returned an invalid teleport target");
throw new IllegalStateException("Mod dimension " + destination.getRegistryKey().getValue().toString() + " returned an invalid teleport target");
}
return defaultTarget;
@ -117,7 +117,7 @@ public final class FabricDimensionInternals {
}
@SuppressWarnings("unchecked")
public static <E extends Entity> E changeDimension(E teleported, DimensionType dimension, EntityPlacer placement) {
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";

View file

@ -22,7 +22,7 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import net.minecraft.entity.Entity;
import net.minecraft.world.dimension.DimensionType;
import net.minecraft.server.world.ServerWorld;
import net.fabricmc.fabric.impl.dimension.FabricDimensionInternals;
@ -30,7 +30,7 @@ import net.fabricmc.fabric.impl.dimension.FabricDimensionInternals;
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(DimensionType dimension, CallbackInfoReturnable<Entity> cir) {
private void onGetPortal(ServerWorld targetWorld, CallbackInfoReturnable<Entity> cir) {
FabricDimensionInternals.prepareDimensionalTeleportation((Entity) (Object) this);
}
}

View file

@ -23,12 +23,12 @@ 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.Dimension;
import net.minecraft.world.dimension.DimensionType;
@Mixin(World.class)
public abstract class MixinWorld {
@Shadow
public abstract Dimension getDimension();
public abstract DimensionType getDimension();
@Shadow
private int ambientDarkness;
@ -44,14 +44,14 @@ public abstract class MixinWorld {
*/
@Inject(method = "isDay", at = @At("HEAD"), cancellable = true)
private void isDay(CallbackInfoReturnable<Boolean> infoReturnable) {
if (getDimension().hasVisibleSky()) {
if (getDimension().hasSkyLight()) {
infoReturnable.setReturnValue(ambientDarkness < 4);
}
}
@Inject(method = "isNight", at = @At("HEAD"), cancellable = true)
private void isNight(CallbackInfoReturnable<Boolean> infoReturnable) {
if (getDimension().hasVisibleSky()) {
if (getDimension().hasSkyLight()) {
infoReturnable.setReturnValue(!(ambientDarkness < 4));
}
}

View file

@ -1,41 +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.idremap;
import org.spongepowered.asm.mixin.Mixin;
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.util.registry.Registry;
import net.minecraft.world.dimension.DimensionType;
// NOTE: This probably goes into dimension-fixes
@Mixin(DimensionType.class)
public abstract class MixinDimensionRawIndexFix {
@Inject(at = @At("RETURN"), method = "byRawId", cancellable = true)
private static void byRawId(final int id, final CallbackInfoReturnable<DimensionType> info) {
if (info.getReturnValue() == null || info.getReturnValue().getRawId() != id) {
for (DimensionType dimension : Registry.DIMENSION_TYPE) {
if (dimension.getRawId() == id) {
info.setReturnValue(dimension);
return;
}
}
}
}
}

View file

@ -1,59 +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.idremap;
import com.mojang.datafixers.DataFixer;
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.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.level.LevelProperties;
import net.fabricmc.fabric.impl.dimension.DimensionIdsFixer;
import net.fabricmc.fabric.impl.dimension.DimensionIdsHolder;
import net.fabricmc.fabric.impl.dimension.DimensionRemapException;
import net.fabricmc.fabric.impl.registry.sync.RemapException;
@Mixin(LevelProperties.class)
public abstract class MixinLevelProperties implements DimensionIdsHolder {
@Unique
private CompoundTag fabricDimensionIds = new CompoundTag();
@Override
public CompoundTag fabric_getDimensionIds() {
return fabricDimensionIds;
}
@Inject(method = "<init>(Lnet/minecraft/nbt/CompoundTag;Lcom/mojang/datafixers/DataFixer;ILnet/minecraft/nbt/CompoundTag;)V", at = @At("RETURN"))
private void readDimensionIds(CompoundTag data, DataFixer fixer, int version, CompoundTag player, CallbackInfo ci) {
CompoundTag savedIds = data.getCompound("fabric_DimensionIds");
try {
this.fabricDimensionIds = DimensionIdsFixer.apply(savedIds);
} catch (RemapException e) {
throw new DimensionRemapException("Failed to assign unique dimension ids!", e);
}
}
@Inject(method = "updateProperties", at = @At("RETURN"))
private void writeDimensionIds(CompoundTag data, CompoundTag player, CallbackInfo ci) {
data.put("fabric_DimensionIds", fabricDimensionIds);
}
}

View file

@ -1,37 +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.idremap;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import net.minecraft.world.level.storage.LevelStorage;
import net.fabricmc.fabric.impl.dimension.DimensionRemapException;
@Mixin(LevelStorage.class)
public abstract class MixinLevelStorage {
@ModifyArg(method = "readLevelProperties(Ljava/io/File;Lcom/mojang/datafixers/DataFixer;)Lnet/minecraft/class_5219;", at = @At(value = "INVOKE", target = "Lorg/apache/logging/log4j/Logger;error(Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;)V", remap = false), index = 2)
private static Object disableRecovery(Object e) {
if (e instanceof DimensionRemapException) {
throw (DimensionRemapException) e;
}
return e;
}
}

View file

@ -1,48 +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.idremap;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.network.ClientConnection;
import net.minecraft.server.PlayerManager;
import net.minecraft.server.network.ServerPlayerEntity;
import net.fabricmc.fabric.impl.dimension.DimensionIdsFixer;
import net.fabricmc.fabric.impl.dimension.FabricDimensionInternals;
import net.fabricmc.fabric.impl.dimension.DimensionIdsHolder;
@Mixin(PlayerManager.class)
public abstract class MixinPlayerManager {
/**
* Synchronizes raw dimension ids to connecting players.
*/
@Inject(method = "onPlayerConnect", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/packet/s2c/play/DifficultyS2CPacket;<init>(Lnet/minecraft/world/Difficulty;Z)V"))
private void onPlayerConnect(ClientConnection conn, ServerPlayerEntity player, CallbackInfo info) {
// TODO: Refactor out into network + move dimension hook to event
// No need to send the packet if the player is using the same game instance (dimension types are static)
if (!player.server.isSinglePlayer() || !conn.isLocal() || FabricDimensionInternals.DEBUG) {
if (player.world.getLevelProperties() instanceof DimensionIdsHolder) {
player.networkHandler.sendPacket(DimensionIdsFixer.createPacket((DimensionIdsHolder) player.world.getLevelProperties()));
}
}
}
}

View file

@ -1,42 +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.idremap;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.level.UnmodifiableLevelProperties;
import net.minecraft.class_5268;
import net.fabricmc.fabric.impl.dimension.DimensionIdsHolder;
@Mixin(UnmodifiableLevelProperties.class)
public abstract class MixinUnmodifiableLevelProperties implements DimensionIdsHolder {
@Shadow
@Final
private class_5268 properties;
/**
* Delegates to the main level properties.
*/
@Override
public CompoundTag fabric_getDimensionIds() {
return ((DimensionIdsHolder) this.properties).fabric_getDimensionIds();
}
}

View file

@ -6,12 +6,7 @@
"EntityHooks",
"MixinEntity",
"MixinPortalForcer",
"MixinWorld",
"idremap.MixinDimensionRawIndexFix",
"idremap.MixinLevelProperties",
"idremap.MixinLevelStorage",
"idremap.MixinPlayerManager",
"idremap.MixinUnmodifiableLevelProperties"
"MixinWorld"
],
"injectors": {
"defaultRequire": 1

View file

@ -5,15 +5,11 @@
"license": "Apache-2.0",
"depends": {
"fabricloader": ">=0.4.0",
"minecraft": ">=1.16-rc.3",
"fabric-api-base": "*",
"fabric-registry-sync-v0": "*",
"fabric-networking-v0": "*"
},
"entrypoints": {
"client": [
"net.fabricmc.fabric.impl.dimension.FabricDimensionClientInit::onClientInit"
]
},
"mixins": [
"fabric-dimensions-v1.mixins.json"
]

View file

@ -0,0 +1,80 @@
/*
* 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.dimension;
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.entity.Entity;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.registry.Registry;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.World;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.Vec3d;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
import net.fabricmc.fabric.api.dimension.v1.FabricDimensions;
public class FabricDimensionTest implements ModInitializer {
private static RegistryKey<World> dimensionRegistryKey;
@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"));
FabricDimensions.registerDefaultPlacer(dimensionRegistryKey, FabricDimensionTest::placeEntityInVoid);
CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) ->
dispatcher.register(literal("fabric_dimension_test").executes(FabricDimensionTest.this::executeTestCommand))
);
}
private int executeTestCommand(CommandContext<ServerCommandSource> context) throws CommandSyntaxException {
ServerPlayerEntity serverPlayerEntity = context.getSource().getPlayer();
ServerWorld serverWorld = serverPlayerEntity.getServerWorld();
if (!serverWorld.getRegistryKey().equals(dimensionRegistryKey)) {
serverPlayerEntity.changeDimension(context.getSource().getMinecraftServer().getWorld(dimensionRegistryKey));
} else {
FabricDimensions.teleport(serverPlayerEntity, context.getSource().getMinecraftServer().getWorld(World.OVERWORLD), FabricDimensionTest::placeEntity);
}
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 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);
}
}

View file

@ -0,0 +1,80 @@
/*
* 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.dimension;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.block.BlockState;
import net.minecraft.world.BlockView;
import net.minecraft.world.ChunkRegion;
import net.minecraft.world.Heightmap;
import net.minecraft.world.WorldAccess;
import net.minecraft.world.biome.source.BiomeSource;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.gen.StructureAccessor;
import net.minecraft.world.gen.chunk.ChunkGenerator;
import net.minecraft.world.gen.chunk.StructuresConfig;
import net.minecraft.world.gen.chunk.VerticalBlockSample;
public class VoidChunkGenerator extends ChunkGenerator {
// Just an example of adding a custom boolean
protected final boolean customBool;
public static final Codec<VoidChunkGenerator> CODEC = RecordCodecBuilder.create((instance) ->
instance.group(
BiomeSource.field_24713.fieldOf("biome_source")
.forGetter((generator) -> generator.biomeSource),
Codec.BOOL.fieldOf("custom_bool")
.forGetter((generator) -> generator.customBool)
)
.apply(instance, instance.stable(VoidChunkGenerator::new))
);
public VoidChunkGenerator(BiomeSource biomeSource, boolean customBool) {
super(biomeSource, new StructuresConfig(false));
this.customBool = customBool;
}
@Override
protected Codec<? extends ChunkGenerator> method_28506() {
return CODEC;
}
@Override
public ChunkGenerator withSeed(long seed) {
return this;
}
@Override
public void buildSurface(ChunkRegion region, Chunk chunk) {
}
@Override
public void populateNoise(WorldAccess world, StructureAccessor accessor, Chunk chunk) {
}
@Override
public int getHeight(int x, int z, Heightmap.Type heightmapType) {
return 0;
}
@Override
public BlockView getColumnSample(int x, int z) {
return new VerticalBlockSample(new BlockState[0]);
}
}

View file

@ -0,0 +1,11 @@
{
"generator": {
"type": "fabric_dimension:void",
"custom_bool": true,
"biome_source": {
"type": "minecraft:fixed",
"biome": "minecraft:plains"
}
},
"type": "fabric_dimension:void_type"
}

View file

@ -0,0 +1,14 @@
{
"ultrawarm": false,
"natural": false,
"shrunk": false,
"ambient_light": 0.1,
"has_skylight": true,
"has_ceiling": false,
"infiniburn": "minecraft:infiniburn_overworld",
"logical_height" : 256,
"has_raids" : false,
"respawn_anchor_works": false,
"bed_works" : false,
"piglin_safe" : false
}

View file

@ -0,0 +1,16 @@
{
"schemaVersion": 1,
"id": "fabric-dimensions-v1-testmod",
"name": "Fabric Dimensions (v1) Test Mod",
"version": "1.0.0",
"environment": "*",
"license": "Apache-2.0",
"depends": {
"fabric-dimensions-v1": "*"
},
"entrypoints": {
"main": [
"net.fabricmc.fabric.test.dimension.FabricDimensionTest"
]
}
}

View file

@ -21,6 +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-events-interaction-v0'
include 'fabric-events-lifecycle-v0'
include 'fabric-item-groups-v0'
@ -44,5 +45,4 @@ include 'fabric-rendering-fluids-v1'
include 'fabric-resource-loader-v0'
include 'fabric-tag-extensions-v0'
include 'fabric-textures-v0'
//include 'fabric-dimensions-v1'
include 'fabric-tool-attribute-api-v1'