Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions fabric-dimensions-v1/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
version = getSubprojectVersion(project)

moduleDependencies(project, [
'fabric-api-base',
'fabric-lifecycle-events-v1'
])

testDependencies(project, [
':fabric-resource-loader-v1'
])
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* 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.core.Holder;
import net.minecraft.core.HolderLookup;
import net.minecraft.world.attribute.EnvironmentAttributeMap;
import net.minecraft.world.level.dimension.DimensionType;

import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;

/**
* Events for manipulating dimensions.
*/
public class DimensionEvents {
/**
* This event can be used to modify the environment attributes of a dimension.
* The main use case is to add modded attributes to vanilla or modded dimensions
*/
public static final Event<ModifyAttributes> MODIFY_ATTRIBUTES = EventFactory.createArrayBacked(ModifyAttributes.class, listeners -> (dimension, attributes, registries) -> {
for (ModifyAttributes listener : listeners) {
listener.modifyDimensionAttributes(dimension, attributes, registries);
}
});

@FunctionalInterface
public interface ModifyAttributes {
void modifyDimensionAttributes(Holder<DimensionType> dimension, EnvironmentAttributeMap.Builder attributes, HolderLookup.Provider registries);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* 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.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.minecraft.core.Holder;
import net.minecraft.core.MappedRegistry;
import net.minecraft.core.RegistrationInfo;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.attribute.EnvironmentAttributeMap;
import net.minecraft.world.level.dimension.DimensionType;

import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.dimension.v1.DimensionEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.fabric.mixin.dimension.DimensionTypeAccessor;
import net.fabricmc.fabric.mixin.dimension.MappedRegistryAccessor;

public class DimensionModificationImpl implements ModInitializer {
private static final Logger LOGGER = LoggerFactory.getLogger(DimensionModificationImpl.class);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: LOGGER is never used


@Override
public void onInitialize() {
ServerLifecycleEvents.SERVER_STARTING.register(server -> DimensionModificationImpl.finalizeWorldGen(server.registryAccess()));
}

public static void finalizeWorldGen(RegistryAccess registries) {
// Now that we apply dimension modifications when the server is starting, we should only ever do this once for a
// dynamic registry manager. Marking the dynamic registry manager as modified ensures a crash if the
// precondition is violated.
DimensionModificationMarker modificationTracker = (DimensionModificationMarker) registries;
modificationTracker.fabric_markDimensionsModified();

Registry<DimensionType> dimensions = registries.lookupOrThrow(Registries.DIMENSION_TYPE);

// Build a list of all dimension keys in ascending order of their raw-id to get a consistent result.
List<ResourceKey<DimensionType>> keys = dimensions.entrySet().stream()
.map(Map.Entry::getKey)
.sorted(Comparator.comparingInt(key -> dimensions.getId(dimensions.getValueOrThrow(key))))
.toList();

for (ResourceKey<DimensionType> key : keys) {
Holder.Reference<DimensionType> reference = dimensions.getOrThrow(key);

if (applyChanges(reference, registries)) {
// Re-freeze and apply certain cleanup actions
if (dimensions instanceof MappedRegistry<DimensionType> registry) {
Map<ResourceKey<DimensionType>, RegistrationInfo> registrationInfos = ((MappedRegistryAccessor<DimensionType>) registry).fabric_getRegistrationInfos();
RegistrationInfo info = registrationInfos.get(key);
RegistrationInfo newInfo = new RegistrationInfo(Optional.empty(), info.lifecycle());
registrationInfos.put(key, newInfo);
}
}
}
}

/**
* Applies the changes from the events of {@link DimensionEvents} to a dimension.
*
* @return true if the dimension was changed
*/
private static boolean applyChanges(Holder<DimensionType> dimension, RegistryAccess registries) {
EnvironmentAttributeMap oldAttributes = dimension.value().attributes();
EnvironmentAttributeMap.Builder attributeBuilder = EnvironmentAttributeMap.builder().putAll(oldAttributes);
DimensionEvents.MODIFY_ATTRIBUTES.invoker().modifyDimensionAttributes(dimension, attributeBuilder, registries);
EnvironmentAttributeMap newAttributes = attributeBuilder.build();
boolean changed = !oldAttributes.equals(newAttributes);

if (changed) {
((DimensionTypeAccessor) (Object) dimension.value()).fabric_setAttributes(newAttributes);
}

return changed;
}
}
Original file line number Diff line number Diff line change
@@ -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;

/**
* Prevents double-modification of dimensions in the same dynamic registry manager from occurring and fails-fast
* if it does occur.
*/
public interface DimensionModificationMarker {
void fabric_markDimensionsModified();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package net.fabricmc.fabric.mixin.dimension;

import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.gen.Accessor;

import net.minecraft.world.attribute.EnvironmentAttributeMap;
import net.minecraft.world.level.dimension.DimensionType;

@Mixin(DimensionType.class)
public interface DimensionTypeAccessor {
@Accessor("attributes")
@Mutable
void fabric_setAttributes(EnvironmentAttributeMap attributes);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package net.fabricmc.fabric.mixin.dimension;

import java.util.Map;

import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;

import net.minecraft.core.MappedRegistry;
import net.minecraft.core.RegistrationInfo;
import net.minecraft.resources.ResourceKey;

@Mixin(MappedRegistry.class)
public interface MappedRegistryAccessor<T> {
@Accessor("registrationInfos")
Map<ResourceKey<T>, RegistrationInfo> fabric_getRegistrationInfos();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package net.fabricmc.fabric.mixin.dimension;

import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;

import net.minecraft.core.RegistryAccess;

import net.fabricmc.fabric.impl.dimension.DimensionModificationImpl;
import net.fabricmc.fabric.impl.dimension.DimensionModificationMarker;

/**
* This Mixin allows us to prevent double-modifications of dimensions via
* {@link DimensionModificationImpl} on a per-DynamicRegistryManager basis.
*/
@Mixin(RegistryAccess.ImmutableRegistryAccess.class)
public class RegistryAccessImmutableRegistryAccessMixin implements DimensionModificationMarker {
@Unique
private boolean dimensionsModified;

@Override
public void fabric_markDimensionsModified() {
if (dimensionsModified) {
throw new IllegalStateException("Dimensions in this dynamic registries instance have already been modified");
}
Comment on lines +38 to +40
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Is this the only use for the dimensionsModified boolean?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep! This is meant to prevent any accidental double-modification, but that should never occur


dimensionsModified = true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
"WorldDimensionsMixin",
"V2832Mixin",
"TaggedChoiceMixin",
"TaggedChoiceTaggedChoiceTypeMixin"
"TaggedChoiceTaggedChoiceTypeMixin",
"DimensionTypeAccessor",
"MappedRegistryAccessor",
"RegistryAccessImmutableRegistryAccessMixin"
],
"injectors": {
"defaultRequire": 1
Expand Down
5 changes: 5 additions & 0 deletions fabric-dimensions-v1/src/main/resources/fabric.mod.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
"authors": [
"FabricMC"
],
"entrypoints": {
"main": [
"net.fabricmc.fabric.impl.dimension.DimensionModificationImpl"
]
},
"depends": {
"fabricloader": ">=0.18.3",
"minecraft": ">=1.16-rc.3",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,46 @@

package net.fabricmc.fabric.test.dimension;

import net.minecraft.core.BlockPos;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.attribute.EnvironmentAttributes;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.dimension.BuiltinDimensionTypes;
import net.minecraft.world.level.dimension.LevelStem;

import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.dimension.v1.DimensionEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;

public class FabricDimensionTest implements ModInitializer {
// The level stem refers to the JSON-file in the dimension subfolder of the data pack,
// which will always share its ID with the level that is created from it
private static final ResourceKey<LevelStem> DIMENSION_KEY = ResourceKey.create(Registries.LEVEL_STEM, Identifier.fromNamespaceAndPath("fabric_dimension", "void"));
private static final int PURPLE = 0xFFE580FF;

@Override
public void onInitialize() {
Registry.register(BuiltInRegistries.CHUNK_GENERATOR, Identifier.fromNamespaceAndPath("fabric_dimension", "void"), VoidChunkGenerator.CODEC);

DimensionEvents.MODIFY_ATTRIBUTES.register((dimension, attributes, _) -> {
if (dimension.is(BuiltinDimensionTypes.OVERWORLD)) {
attributes.set(EnvironmentAttributes.CLOUD_COLOR, PURPLE);
}
});

ServerLifecycleEvents.SERVER_STARTED.register(server -> {
ServerLevel overworld = server.getLevel(Level.OVERWORLD);
overworld.setDayTime(6000);
int overworldCloudColor = overworld.environmentAttributes().getValue(EnvironmentAttributes.CLOUD_COLOR, BlockPos.ZERO);

if (overworldCloudColor != PURPLE) {
throw new AssertionError("Expected overworld cloud color to be (%d) but was (%d)".formatted(PURPLE, overworldCloudColor));
}
});
}
}
Loading