mirror of
https://github.com/FabricMC/fabric.git
synced 2025-04-21 03:10:54 -04:00
Bring dimension API to 1.15 branch (#373)
* Update versioning to match 1.15 branch * Fix builtin mods being added as resource packs * Update fabric-loader to replace deprecated methods * Add more metadata to the modules (#353) * Add fabric-api-base as a dep to fabric-networking-v0 * Allow for running fabric-api-base on the server-side environment (#360) * Set curse version to 1.14.4 Also forces it to build again * Fix NPE in fluid renderer mixin (#361) * Fix NPE in fluid renderer mixin * Ensure state, view, and pos are never null * Bump fabric-rendering-fluids-v1 version * Cooler dimension API, #309 (#319) * pyro API * Update fabric-dimensions-v1/src/main/java/net/fabricmc/fabric/api/dimension/EntityPlacer.java clarify portalDir param Co-Authored-By: Pyrofab <redstoneinfire@gmail.com> * Update fabric-dimensions-v1/src/main/java/net/fabricmc/fabric/api/dimension/EntityPlacer.java rename & clarify verticalOffset param Co-Authored-By: Pyrofab <redstoneinfire@gmail.com> * Update fabric-dimensions-v1/src/main/java/net/fabricmc/fabric/api/dimension/FabricDimensions.java Co-Authored-By: Pyrofab <redstoneinfire@gmail.com> * Update fabric-dimensions-v1/src/main/java/net/fabricmc/fabric/api/dimension/EntityPlacer.java Co-Authored-By: Pyrofab <redstoneinfire@gmail.com> * update parameter * Add fabric-networking and fabric-registry-sync to dependencies * Refactor FabricDimensionType to use a builder Also adds a desiredRawId field separate from the actual raw id fixed internally. The fixedRawId field is now set by DimensionIdsFixer through reflection. This change addresses concerns about the ability for any mod to set a FabricDimensionType's raw id at any time. * Improve javadoc of FabricDimensions#teleport * Add an overload for FabricDimensions#teleport * Update javadoc on DimensionIdsFixer * Inline clientside packet handling * Add side assertions to FabricDimensions#teleport * Nuke minecraft's attempts to overwrite bad level properties * Add license headers * Reformat dimension API source code * Update FabricDimensionType javadoc * Remove redundant ThreadLocal from FabricDimensionInternals * Fix crash on dedicated servers * Fix Illegal Access during remapping * Fix dimension remap exception propagation * clarify EntityPlacer docs * spacing fixes * api.dimension -> api.dimension.v1 * Increment API patch version Signifies inclusion of new Dimension API * Partial fix to FabricDimensionType * Bump dimension version * Updates based on feedback
This commit is contained in:
parent
c208a44d66
commit
96946203be
21 changed files with 1152 additions and 1 deletions
build.gradle
fabric-dimensions-v1
build.gradle
settings.gradlesrc/main
java/net/fabricmc/fabric
api/dimension/v1
impl/dimension
DimensionIdsFixer.javaDimensionIdsHolder.javaDimensionRemapException.javaFabricDimensionClientInit.javaFabricDimensionInternals.java
mixin
resources
|
@ -12,7 +12,7 @@ plugins {
|
|||
def ENV = System.getenv()
|
||||
|
||||
class Globals {
|
||||
static def baseVersion = "0.3.2"
|
||||
static def baseVersion = "0.3.3"
|
||||
static def mcVersion = "19w38b"
|
||||
static def yarnVersion = "+build.4"
|
||||
}
|
||||
|
|
8
fabric-dimensions-v1/build.gradle
Normal file
8
fabric-dimensions-v1/build.gradle
Normal file
|
@ -0,0 +1,8 @@
|
|||
archivesBaseName = "fabric-dimensions-v1"
|
||||
version = getSubprojectVersion(project, "0.2.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')
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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;
|
||||
import net.minecraft.world.dimension.DimensionType;
|
||||
|
||||
/**
|
||||
* Responsible for placing an Entity once they have entered a dimension.
|
||||
* Stored by a FabricDimensionType, and used in Entity::changeDimension.
|
||||
*
|
||||
* @see FabricDimensions
|
||||
* @see FabricDimensionType
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface EntityPlacer {
|
||||
/**
|
||||
* Handles the placement of an entity going to a dimension.
|
||||
* Utilized by {@link FabricDimensions#teleport(Entity, DimensionType, 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);
|
||||
}
|
|
@ -0,0 +1,244 @@
|
|||
/*
|
||||
* 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 net.minecraft.class_4545;
|
||||
import net.minecraft.class_4546;
|
||||
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.dimension.Dimension;
|
||||
import net.minecraft.world.dimension.DimensionType;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* 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, 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 class_4545 biomeAccessStrategy = class_4546.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 biomeFunction Function to be used for biome generation.
|
||||
* @return this {@code Builder} object
|
||||
*/
|
||||
public Builder biomeAccessStrategy(class_4545 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.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, dimensionId, built);
|
||||
|
||||
if (this.desiredRawId != 0) {
|
||||
built.desiredRawId = this.desiredRawId;
|
||||
} else {
|
||||
built.desiredRawId = Registry.DIMENSION.getRawId(built) - 1;
|
||||
}
|
||||
|
||||
built.fixedRawId = built.desiredRawId;
|
||||
return built;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* 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 net.fabricmc.fabric.impl.dimension.FabricDimensionInternals;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.world.dimension.DimensionType;
|
||||
|
||||
/**
|
||||
* 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, using custom placement logic.
|
||||
*
|
||||
* <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
|
||||
* 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
|
||||
* @return the teleported entity, or a clone of it
|
||||
* @see #teleport(Entity, DimensionType, EntityPlacer)
|
||||
*/
|
||||
public static <E extends Entity> E teleport(E teleported, DimensionType 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} 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.
|
||||
* 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 (see {@link FabricDimensionType#getDefaultPlacement()}).
|
||||
* @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) {
|
||||
Preconditions.checkState(!teleported.world.isClient, "Entities can only be teleported on the server side");
|
||||
|
||||
return FabricDimensionInternals.changeDimension(teleported, destination, customPlacer);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* 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 io.netty.buffer.Unpooled;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import net.fabricmc.fabric.api.dimension.v1.FabricDimensionType;
|
||||
import net.fabricmc.fabric.api.network.ServerSidePacketRegistry;
|
||||
import net.fabricmc.fabric.impl.registry.RemapException;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.Packet;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.PacketByteBuf;
|
||||
import net.minecraft.util.registry.Registry;
|
||||
import net.minecraft.world.dimension.DimensionType;
|
||||
import net.minecraft.world.level.LevelProperties;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 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.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(LevelProperties levelProperties) {
|
||||
PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer());
|
||||
buf.writeCompoundTag(((DimensionIdsHolder) levelProperties).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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.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();
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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.RemapException;
|
||||
|
||||
public class DimensionRemapException extends RuntimeException {
|
||||
public DimensionRemapException(String message, RemapException cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.fabric.impl.dimension;
|
||||
|
||||
import net.fabricmc.fabric.api.network.ClientSidePacketRegistry;
|
||||
import net.fabricmc.fabric.api.network.PacketContext;
|
||||
import net.fabricmc.fabric.impl.registry.RemapException;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.network.ClientPlayerEntity;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.text.LiteralText;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* 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)
|
||||
));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* 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.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.EntityHooks;
|
||||
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 org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
public final class FabricDimensionInternals {
|
||||
private FabricDimensionInternals() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
public static final boolean DEBUG = System.getProperty("fabric.dimension.debug", "false").equalsIgnoreCase("true");
|
||||
public static final Logger LOGGER = LogManager.getLogger();
|
||||
|
||||
/**
|
||||
* 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)}
|
||||
*/
|
||||
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.getLastPortalDirectionVector() == null) {
|
||||
access.setLastPortalDirectionVector(entity.getRotationVector());
|
||||
}
|
||||
if (entity.getLastPortalDirection() == null) {
|
||||
access.setLastPortalDirection(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
|
||||
DimensionType dimType = destination.getDimension().getType();
|
||||
if (dimType instanceof FabricDimensionType) {
|
||||
BlockPattern.TeleportTarget defaultTarget = ((FabricDimensionType) dimType).getDefaultPlacement().placeEntity(teleported, destination, portalDir, portalX, portalY);
|
||||
|
||||
if (defaultTarget == null) {
|
||||
throw new IllegalStateException("Mod dimension " + DimensionType.getId(dimType) + " returned an invalid teleport target");
|
||||
}
|
||||
return defaultTarget;
|
||||
}
|
||||
|
||||
// Vanilla / other implementations logic, undefined behaviour on custom dimensions
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <E extends Entity> E changeDimension(E teleported, DimensionType 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";
|
||||
|
||||
try {
|
||||
customPlacement = placement;
|
||||
return (E) teleported.changeDimension(dimension);
|
||||
} finally {
|
||||
customPlacement = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.util.math.Direction;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(Entity.class)
|
||||
public interface EntityHooks {
|
||||
@Accessor
|
||||
void setLastPortalDirectionVector(Vec3d vec);
|
||||
|
||||
@Accessor
|
||||
void setLastPortalDirection(Direction dir);
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import net.fabricmc.fabric.impl.dimension.FabricDimensionInternals;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.world.dimension.DimensionType;
|
||||
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;
|
||||
|
||||
@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;getLastPortalDirectionVector()Lnet/minecraft/util/math/Vec3d;"))
|
||||
private void onGetPortal(DimensionType dimension, CallbackInfoReturnable<Entity> cir) {
|
||||
FabricDimensionInternals.prepareDimensionalTeleportation((Entity) (Object) this);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import net.fabricmc.fabric.impl.dimension.FabricDimensionInternals;
|
||||
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 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;
|
||||
|
||||
@Mixin(PortalForcer.class)
|
||||
public abstract class MixinPortalForcer {
|
||||
@Shadow
|
||||
@Final
|
||||
private ServerWorld world;
|
||||
|
||||
@Inject(method = "usePortal", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;getLastPortalDirectionVector()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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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.idremap;
|
||||
|
||||
import net.minecraft.util.registry.Registry;
|
||||
import net.minecraft.world.dimension.DimensionType;
|
||||
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;
|
||||
|
||||
// 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) {
|
||||
if (dimension.getRawId() == id) {
|
||||
info.setReturnValue(dimension);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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.idremap;
|
||||
|
||||
import com.mojang.datafixers.DataFixer;
|
||||
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.RemapException;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.world.level.LevelProperties;
|
||||
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;
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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.idremap;
|
||||
|
||||
import net.fabricmc.fabric.impl.dimension.DimensionRemapException;
|
||||
import net.minecraft.world.level.storage.LevelStorage;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||
|
||||
@Mixin(LevelStorage.class)
|
||||
public abstract class MixinLevelStorage {
|
||||
@ModifyArg(method = "readLevelProperties", 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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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.idremap;
|
||||
|
||||
import net.fabricmc.fabric.impl.dimension.DimensionIdsFixer;
|
||||
import net.fabricmc.fabric.impl.dimension.FabricDimensionInternals;
|
||||
import net.minecraft.network.ClientConnection;
|
||||
import net.minecraft.server.PlayerManager;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
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;
|
||||
|
||||
@Mixin(PlayerManager.class)
|
||||
public abstract class MixinPlayerManager {
|
||||
/**
|
||||
* Synchronizes raw dimension ids to connecting players
|
||||
*/
|
||||
@Inject(method = "onPlayerConnect", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/packet/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) {
|
||||
player.networkHandler.sendPacket(DimensionIdsFixer.createPacket(player.world.getLevelProperties()));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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.idremap;
|
||||
|
||||
import net.fabricmc.fabric.impl.dimension.DimensionIdsHolder;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.world.level.LevelProperties;
|
||||
import net.minecraft.world.level.UnmodifiableLevelProperties;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
@Mixin(UnmodifiableLevelProperties.class)
|
||||
public abstract class MixinUnmodifiableLevelProperties implements DimensionIdsHolder {
|
||||
@Shadow
|
||||
@Final
|
||||
private LevelProperties properties;
|
||||
|
||||
/**
|
||||
* Delegates to the main level properties
|
||||
*/
|
||||
@Override
|
||||
public CompoundTag fabric_getDimensionIds() {
|
||||
return ((DimensionIdsHolder) this.properties).fabric_getDimensionIds();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"required": true,
|
||||
"package": "net.fabricmc.fabric.mixin",
|
||||
"compatibilityLevel": "JAVA_8",
|
||||
"mixins": [
|
||||
"EntityHooks",
|
||||
"MixinEntity",
|
||||
"MixinPortalForcer",
|
||||
"idremap.MixinDimensionRawIndexFix",
|
||||
"idremap.MixinLevelProperties",
|
||||
"idremap.MixinLevelStorage",
|
||||
"idremap.MixinPlayerManager",
|
||||
"idremap.MixinUnmodifiableLevelProperties"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
}
|
||||
}
|
20
fabric-dimensions-v1/src/main/resources/fabric.mod.json
Normal file
20
fabric-dimensions-v1/src/main/resources/fabric.mod.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "fabric-dimensions-v1",
|
||||
"version": "${version}",
|
||||
"license": "Apache-2.0",
|
||||
"depends": {
|
||||
"fabricloader": ">=0.4.0",
|
||||
"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"
|
||||
]
|
||||
}
|
|
@ -39,3 +39,4 @@ include 'fabric-rendering-fluids-v1'
|
|||
include 'fabric-resource-loader-v0'
|
||||
include 'fabric-tag-extensions-v0'
|
||||
include 'fabric-textures-v0'
|
||||
include 'fabric-dimensions-v1'
|
Loading…
Add table
Add a link
Reference in a new issue