Compare commits

..
5 Commits
Author SHA1 Message Date
Dream Master 6283931667 update 2025-08-04 10:28:35 +07:00
Abdiel Kavash 3b3bc4b9ad Add preset IAlignmentLimits constants. (#43)
Many multiblocks can only be placed or rotated in certain orientations.
This has been always done ad-hoc, with individual checks for each
controller. This is inconsistent and sometimes it results in bugs where
certain orientations are not allowed for no discernible reason; for
example
https://github.com/GTNewHorizons/GT-New-Horizons-Modpack/issues/14206.

This PR introduces a short list of named constants to
`IAlignmentLimits`, which describe common multiplboc orientations. These
are currently:

* `UNLIMITED`: Any orientation is allowed. Most multiblocks.
* `UPRIGHT`: The multiblock must be in an upright position, with the top
side facing up. Example: Distillation Tower, Ore Washing Plant.
* `NOT_UPSIDE_DOWN`: The multiblock can be in any orientation, except
for the top side facing directly downwards. This is currently only the
Large Heat Exchanger and Integrated Ore Factory.
* `FACING_UP`: The front of the controller must face up. Example:
Flotation Cell, XL Turbines.

Currently, these constants describe the allowed facings of *all* GT5U
multiblocks.

A multiblock can then simply return one of these constants from
`getInitialAlignmentLimits()` to describe its valid orientations,
instead of implementing the logic itself. This concentrates the code to
one place, improves readability, and fixes any bugs connected with these
checks being done wrong.

Of course, even though the list is currently complete, if any future
multiblock wants to implement its own strange restrictions on
orientation, it can still implement its own check instead of returning
one of the constants.

A follow-up PR to GT5U implements this for all multiblocks which
restrict valid orientations:
https://github.com/GTNewHorizons/GT5-Unofficial/pull/4595
2025-08-01 10:54:59 +07:00
Alexdoru bef4fdd949 Delete overkill mixin registration system (#44) 2025-07-14 04:31:17 +08:00
Martin Robertz 4a06c8f95b update 2025-06-19 21:09:58 +02:00
koolkrafter5 241bed1989 Update dev username (#41) 2025-06-16 10:53:32 +08:00
6 changed files with 38 additions and 210 deletions

No files matched your search

+2 -2
View File
@@ -1,8 +1,8 @@
// Add your dependencies here
dependencies {
api("com.github.GTNewHorizons:GTNHLib:0.6.21:dev")
implementation("com.github.GTNewHorizons:NotEnoughItems:2.7.40-GTNH:dev")
api("com.github.GTNewHorizons:GTNHLib:0.6.38:dev")
implementation("com.github.GTNewHorizons:NotEnoughItems:2.7.69-GTNH:dev")
testImplementation(platform('org.junit:junit-bom:5.9.2'))
testImplementation('org.junit.jupiter:junit-jupiter')
+2 -2
View File
@@ -37,7 +37,7 @@ remoteMappings = https\://raw.githubusercontent.com/MinecraftForge/FML/1.7.10/co
# Select a default username for testing your mod. You can always override this per-run by running
# `./gradlew runClient --username=AnotherPlayer`, or configuring this command in your IDE.
developmentEnvironmentUserName = "Developer"
developmentEnvironmentUserName = Developer
# Enables using modern Java syntax (up to version 17) via Jabel, while still targeting JVM 8.
# See https://github.com/bsideup/jabel for details on how this works.
@@ -151,7 +151,7 @@ modrinthProjectId =
# type can be one of [project, version],
# and the name is the Modrinth project or version slug/id of the other mod.
# Example: required-project:fplib;optional-project:gasstation;incompatible-project:gregtech
# Note: GTNH Mixins is automatically set as a required dependency if usesMixins = true
# Note: UniMixins is automatically set as a required dependency if usesMixins = true.
modrinthRelations =
# Publishing to CurseForge requires you to set the CURSEFORGE_TOKEN environment variable to one of your CurseForge API tokens.
+1 -1
View File
@@ -17,7 +17,7 @@ pluginManagement {
}
plugins {
id 'com.gtnewhorizons.gtnhsettingsconvention' version '1.0.38'
id 'com.gtnewhorizons.gtnhsettingsconvention' version '1.0.41'
}
@@ -14,8 +14,28 @@ import com.gtnewhorizon.structurelib.alignment.enumerable.Rotation;
public interface IAlignmentLimits {
/**
* Can be placed in any orientation.
*/
IAlignmentLimits UNLIMITED = (direction, rotation, flip) -> true;
/**
* Must be standing upright; i.e., top side of the block facing up.
*/
IAlignmentLimits UPRIGHT = (direction, rotation, flip) -> direction.offsetY == 0
&& (flip.isVerticallyFliped() ? rotation.isUpsideDown() : rotation.isNotRotated());
/**
* Can be in any orientation except upside down; i.e., top side of the block facing down.
*/
IAlignmentLimits NOT_UPSIDE_DOWN = (direction, rotation, flip) -> direction.offsetY != 0
|| (flip.isVerticallyFliped() ? !rotation.isNotRotated() : !rotation.isUpsideDown());
/**
* Front side of the block must be facing up.
*/
IAlignmentLimits FACING_UP = (direction, rotation, flip) -> direction == ForgeDirection.UP;
boolean isNewExtendedFacingValid(ForgeDirection direction, Rotation rotation, Flip flip);
default boolean isNewExtendedFacingValid(ExtendedFacing alignment) {
@@ -1,34 +1,20 @@
package com.gtnewhorizon.structurelib.core;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.gtnewhorizon.gtnhmixins.IEarlyMixinLoader;
import com.gtnewhorizon.structurelib.mixins.Mixins;
import cpw.mods.fml.relauncher.IFMLLoadingPlugin;
@IFMLLoadingPlugin.MCVersion("1.7.10")
public class StructureLibCore implements IFMLLoadingPlugin, IEarlyMixinLoader {
public static final Logger log = LogManager.getLogger("StructureLibCore");
@Override
public String getMixinConfig() {
return "mixins.structurelib.early.json";
}
@Override
public List<String> getMixins(Set<String> loadedCoreMods) {
return Mixins.getEarlyMixins(loadedCoreMods);
}
@Override
public String[] getASMTransformerClass() {
return new String[0];
return null;
}
@Override
@@ -50,4 +36,14 @@ public class StructureLibCore implements IFMLLoadingPlugin, IEarlyMixinLoader {
public String getAccessTransformerClass() {
return null;
}
@Override
public String getMixinConfig() {
return "mixins.structurelib.early.json";
}
@Override
public List<String> getMixins(Set<String> loadedCoreMods) {
return Arrays.asList("blockChangeNotifier.MixinForgeHooks", "blockChangeNotifier.MixinWorld");
}
}
@@ -1,188 +0,0 @@
package com.gtnewhorizon.structurelib.mixins;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import com.gtnewhorizon.structurelib.core.StructureLibCore;
import cpw.mods.fml.relauncher.FMLLaunchHandler;
public enum Mixins {
BLOCK_CHANGE_NOTIFIER(new Builder("Block Change Notifier").setPhase(Phase.EARLY)
.addMixinClasses("blockChangeNotifier.MixinForgeHooks", "blockChangeNotifier.MixinWorld")
.addTargetedMod(TargetedMod.VANILLA).setSide(Side.BOTH).setApplyIf(() -> true));
private final List<String> mixinClasses;
private final List<TargetedMod> targetedMods;
private final List<TargetedMod> excludedMods;
private final Supplier<Boolean> applyIf;
private final Phase phase;
private final Side side;
Mixins(Builder builder) {
this.mixinClasses = builder.mixinClasses;
this.targetedMods = builder.targetedMods;
this.excludedMods = builder.excludedMods;
this.applyIf = builder.applyIf;
this.phase = builder.phase;
this.side = builder.side;
if (this.mixinClasses.isEmpty()) {
throw new RuntimeException("No mixin class specified for Mixin : " + this.name());
}
if (this.targetedMods.isEmpty()) {
throw new RuntimeException("No targeted mods specified for Mixin : " + this.name());
}
if (this.applyIf == null) {
throw new RuntimeException("No ApplyIf function specified for Mixin : " + this.name());
}
if (this.phase == null) {
throw new RuntimeException("No Phase specified for Mixin : " + this.name());
}
if (this.side == null) {
throw new RuntimeException("No Side function specified for Mixin : " + this.name());
}
}
public static List<String> getEarlyMixins(Set<String> loadedCoreMods) {
final List<String> mixins = new ArrayList<>();
final List<String> notLoading = new ArrayList<>();
for (Mixins mixin : Mixins.values()) {
if (mixin.phase == Phase.EARLY) {
if (mixin.shouldLoad(loadedCoreMods, Collections.emptySet())) {
mixins.addAll(mixin.mixinClasses);
} else {
notLoading.addAll(mixin.mixinClasses);
}
}
}
StructureLibCore.log.info("Not loading the following EARLY mixins: {}", notLoading.toString());
return mixins;
}
public static List<String> getLateMixins(Set<String> loadedMods) {
// NOTE: Any targetmod here needs a modid, not a coremod id
final List<String> mixins = new ArrayList<>();
final List<String> notLoading = new ArrayList<>();
for (Mixins mixin : Mixins.values()) {
if (mixin.phase == Phase.LATE) {
if (mixin.shouldLoad(Collections.emptySet(), loadedMods)) {
mixins.addAll(mixin.mixinClasses);
} else {
notLoading.addAll(mixin.mixinClasses);
}
}
}
StructureLibCore.log.info("Not loading the following LATE mixins: {}", notLoading.toString());
return mixins;
}
private boolean shouldLoadSide() {
return side == Side.BOTH || (side == Side.SERVER && FMLLaunchHandler.side().isServer())
|| (side == Side.CLIENT && FMLLaunchHandler.side().isClient());
}
private boolean allModsLoaded(List<TargetedMod> targetedMods, Set<String> loadedCoreMods, Set<String> loadedMods) {
if (targetedMods.isEmpty()) return false;
for (TargetedMod target : targetedMods) {
if (target == TargetedMod.VANILLA) continue;
// Check coremod first
if (!loadedCoreMods.isEmpty() && target.coreModClass != null
&& !loadedCoreMods.contains(target.coreModClass))
return false;
else if (!loadedMods.isEmpty() && target.modId != null && !loadedMods.contains(target.modId)) return false;
}
return true;
}
private boolean noModsLoaded(List<TargetedMod> targetedMods, Set<String> loadedCoreMods, Set<String> loadedMods) {
if (targetedMods.isEmpty()) return true;
for (TargetedMod target : targetedMods) {
if (target == TargetedMod.VANILLA) continue;
// Check coremod first
if (!loadedCoreMods.isEmpty() && target.coreModClass != null
&& loadedCoreMods.contains(target.coreModClass))
return false;
else if (!loadedMods.isEmpty() && target.modId != null && loadedMods.contains(target.modId)) return false;
}
return true;
}
private boolean shouldLoad(Set<String> loadedCoreMods, Set<String> loadedMods) {
return (shouldLoadSide() && applyIf.get()
&& allModsLoaded(targetedMods, loadedCoreMods, loadedMods)
&& noModsLoaded(excludedMods, loadedCoreMods, loadedMods));
}
private static class Builder {
private final String name;
private final List<String> mixinClasses = new ArrayList<>();
private final List<TargetedMod> targetedMods = new ArrayList<>();
private final List<TargetedMod> excludedMods = new ArrayList<>();
private Supplier<Boolean> applyIf = null;
private Phase phase = null;
private Side side = null;
public Builder(String name) {
this.name = name;
}
public Builder addMixinClasses(String... mixinClasses) {
this.mixinClasses.addAll(Arrays.asList(mixinClasses));
return this;
}
public Builder setPhase(Phase phase) {
if (this.phase != null) {
throw new RuntimeException("Trying to define Phase twice for " + this.name);
}
this.phase = phase;
return this;
}
public Builder setSide(Side side) {
if (this.side != null) {
throw new RuntimeException("Trying to define Side twice for " + this.name);
}
this.side = side;
return this;
}
public Builder setApplyIf(Supplier<Boolean> applyIf) {
this.applyIf = applyIf;
return this;
}
public Builder addTargetedMod(TargetedMod mod) {
this.targetedMods.add(mod);
return this;
}
public Builder addExcludedMod(TargetedMod mod) {
this.excludedMods.add(mod);
return this;
}
}
private enum Side {
BOTH,
CLIENT,
SERVER
}
private enum Phase {
EARLY,
LATE,
}
}