Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ __gradle_version__.py
# eclipse
bin
*.launch

# vscode - machine-specific run configs
.vscode/launch.json
.settings
.metadata
.classpath
Expand Down
4 changes: 3 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"ruff.lint.args": [
"--extend-ignore=I", // format on save is enabled, so don't show the squiggles
],
"python.languageServer": "Pylance",
"python.languageServer": "None",
"python.analysis.diagnosticMode": "workspace",
"python.analysis.packageIndexDepths": [
{"name": "hexdoc", "depth": 3},
Expand All @@ -31,4 +31,6 @@
"*.jcss.jinja": "jinja-css", // for files with a lot of jinja stuff, where the linting isn't useful
"*.json5.jinja": "json5",
},
"cursorpyright.analysis.diagnosticMode": "workspace",
"java.configuration.updateBuildConfiguration": "automatic",
}
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

### Added

- **1.21.1 NeoForge port (draft):** Build system updated to NeoForge (NeoGradle/ModDevGradle), Java 21, Paucal 0.7.x, Patchouli 1.21.1-92, Caelus 7.x. Data component scaffold for Iota/item storage ([HexDataComponents](Common/src/main/java/at/petrak/hexcasting/common/lib/HexDataComponents.java)).
- Added the `cannot_modify_cost` tag for patterns that should ignore the `media_consumption` attribute when calculating cost, by Robotgiggle in [987](https://github.com/FallingColors/HexMod/pull/987).

### Changed
Expand All @@ -16,6 +17,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

### Fixed

- Fixed staff/actionable items not opening the spell GUI and "broken iota" on empty containers on NeoForge 21.1.x. NeoForge's payload direction checks rejected client-bound packets (e.g. hexcasting:cgui, hexcasting:clr_spi_pats_sc). Added `ForgeMixinNetworkRegistry` to bypass the check for hex casting payloads.
- Fixed Patchouli thehexbook failing to load due to malformed `potion_contents` in 1.21.1. Updated potion item strings to use SNBT compound format: `potion_contents={potion:"hexcasting:enlarge_grid"}` (entries: potions.json, nadirs.json, zeniths.json).
- Fixed media bar/tooltip not updating after amethyst consumption until rejoin. Added `broadcastFullState()` after media extraction to sync inventory to client immediately.
- Fixed a crash loop when trying to generate a creative-mode ancient scroll for a Great Spell whose per-world pattern hasn't been calculated yet, by Robotgiggle in [992](https://github.com/FallingColors/HexMod/pull/992).

## `0.11.3` - 2025-11-22
Expand Down
42 changes: 24 additions & 18 deletions Common/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,33 +9,28 @@ minecraft {
}

repositories {
mavenLocal()
mavenCentral()

maven { url 'https://maven.blamejared.com' }

maven {
// location of the maven that hosts JEI files
name = "Progwml6 maven"
url = "https://dvs1.progwml6.com/files/maven/"
}
maven {
// location of a maven mirror for JEI files, as a fallback
name = "ModMaven"
url = "https://modmaven.dev"
}

maven { url "https://maven.shedaniel.me/" }

maven { url = 'https://maven.neoforged.net/releases' }
maven { url = 'https://maven.minecraftforge.net' }
maven { name = "Progwml6 maven"; url = "https://dvs1.progwml6.com/files/maven/" }
maven { name = "ModMaven"; url = "https://modmaven.dev" }
maven { url = "https://maven.shedaniel.me/" }
maven { url = "https://api.modrinth.com/maven" }
}

dependencies {
compileOnly group: 'org.spongepowered', name: 'mixin', version: '0.8.5'
implementation group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.1'

compileOnly "at.petra-k.paucal:paucal-common-$minecraftVersion:$paucalVersion"
compileOnly "vazkii.patchouli:Patchouli-xplat:$minecraftVersion-$patchouliVersion-SNAPSHOT"
// Paucal: from mavenLocal or includeBuild (groupId at.petrak)
compileOnly "at.petrak:paucal-neoforge-$minecraftVersion:$paucalVersion"
compileOnly "vazkii.patchouli:Patchouli-xplat:$patchouliVersion-SNAPSHOT"

compileOnly "com.samsthenerd.inline:inline-forge:$minecraftVersion-$inlineVersion"
// Optional: Inline (pattern tooltips in chat) - uncomment to enable Inline integration
// Also remove 'at/petrak/hexcasting/interop/inline/**' from sourceSets.main.java.exclude in this file
// compileOnly "maven.modrinth:inline:$minecraftVersion-$inlineVersion-neoforge"

compileOnly "org.jetbrains:annotations:$jetbrainsAnnotationsVersion"
testCompileOnly "org.jetbrains:annotations:$jetbrainsAnnotationsVersion"
Expand All @@ -55,3 +50,14 @@ processResources {
expand buildProps
}
}

// Datagen sources are not required to run the client; exclude to unblock NeoForge port.
// Inline interop is excluded so the mod compiles without the Inline dependency.
sourceSets {
main {
java {
exclude 'at/petrak/hexcasting/datagen/**'
exclude 'at/petrak/hexcasting/interop/inline/**'
}
}
}
9 changes: 0 additions & 9 deletions Common/src/main/java/at/petrak/hexcasting/README.md

This file was deleted.

60 changes: 16 additions & 44 deletions Common/src/main/java/at/petrak/hexcasting/api/HexAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -165,49 +165,21 @@ default FrozenPigment getColorizer(Player player) {
return FrozenPigment.DEFAULT.get();
}

ArmorMaterial DUMMY_ARMOR_MATERIAL = new ArmorMaterial() {
@Override
public int getDurabilityForType(ArmorItem.Type type) {
return 0;
}

@Override
public int getDefenseForType(ArmorItem.Type type) {
return 0;
}

@Override
public int getEnchantmentValue() {
return 0;
}

@NotNull
@Override
public SoundEvent getEquipSound() {
return SoundEvents.ARMOR_EQUIP_LEATHER;
}

@NotNull
@Override
public Ingredient getRepairIngredient() {
return Ingredient.EMPTY;
}

@Override
public String getName() {
return "missingno";
}

@Override
public float getToughness() {
return 0;
}

@Override
public float getKnockbackResistance() {
return 0;
}
};
ArmorMaterial DUMMY_ARMOR_MATERIAL = new ArmorMaterial(
java.util.Map.of(
ArmorItem.Type.BOOTS, 0,
ArmorItem.Type.LEGGINGS, 0,
ArmorItem.Type.CHESTPLATE, 0,
ArmorItem.Type.HELMET, 0,
ArmorItem.Type.BODY, 0
),
0,
SoundEvents.ARMOR_EQUIP_LEATHER,
() -> Ingredient.EMPTY,
java.util.List.of(new ArmorMaterial.Layer(modLoc("missingno"))),
0f,
0f
);

default ArmorMaterial robesMaterial() {
return DUMMY_ARMOR_MATERIAL;
Expand All @@ -229,6 +201,6 @@ static HexAPI instance() {
}

static ResourceLocation modLoc(String s) {
return new ResourceLocation(MOD_ID, s);
return ResourceLocation.fromNamespaceAndPath(MOD_ID, s);
}
}
Original file line number Diff line number Diff line change
@@ -1,39 +1,30 @@
package at.petrak.hexcasting.api.advancements;

import com.google.gson.JsonObject;
import net.minecraft.advancements.critereon.*;
import net.minecraft.resources.ResourceLocation;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.advancements.critereon.ContextAwarePredicate;
import net.minecraft.advancements.critereon.SimpleCriterionTrigger;
import net.minecraft.server.level.ServerPlayer;

public class FailToCastGreatSpellTrigger extends SimpleCriterionTrigger<FailToCastGreatSpellTrigger.Instance> {
private static final ResourceLocation ID = new ResourceLocation("hexcasting", "fail_to_cast_great_spell");

@Override
public ResourceLocation getId() {
return ID;
}
import java.util.Optional;

public class FailToCastGreatSpellTrigger extends SimpleCriterionTrigger<FailToCastGreatSpellTrigger.Instance> {
@Override
protected Instance createInstance(JsonObject json, ContextAwarePredicate predicate, DeserializationContext context) {
return new Instance(predicate);
public Codec<Instance> codec() {
return Instance.CODEC;
}

public void trigger(ServerPlayer player) {
super.trigger(player, e -> true);
}

public static class Instance extends AbstractCriterionTriggerInstance {
public Instance(ContextAwarePredicate predicate) {
super(ID, predicate);
}

@Override
public ResourceLocation getCriterion() {
return ID;
}
public static record Instance(Optional<ContextAwarePredicate> player) implements SimpleCriterionTrigger.SimpleInstance {
public static final Codec<Instance> CODEC = RecordCodecBuilder.create(inst -> inst.group(
ContextAwarePredicate.CODEC.optionalFieldOf("player").forGetter(Instance::player)
).apply(inst, Instance::new));

public JsonObject serializeToJson(SerializationContext pConditions) {
return new JsonObject();
public Instance(ContextAwarePredicate player) {
this(Optional.of(player));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ public class HexAdvancementTriggers {
public static final FailToCastGreatSpellTrigger FAIL_GREAT_SPELL_TRIGGER = new FailToCastGreatSpellTrigger();

public static void registerTriggers() {
CriteriaTriggersAccessor.hex$register(OVERCAST_TRIGGER);
CriteriaTriggersAccessor.hex$register(SPEND_MEDIA_TRIGGER);
CriteriaTriggersAccessor.hex$register(FAIL_GREAT_SPELL_TRIGGER);
CriteriaTriggersAccessor.hex$register("hexcasting:overcast", OVERCAST_TRIGGER);
CriteriaTriggersAccessor.hex$register("hexcasting:spend_media", SPEND_MEDIA_TRIGGER);
CriteriaTriggersAccessor.hex$register("hexcasting:fail_to_cast_great_spell", FAIL_GREAT_SPELL_TRIGGER);
}
}
Original file line number Diff line number Diff line change
@@ -1,40 +1,32 @@
package at.petrak.hexcasting.api.advancements;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.exceptions.BuiltInExceptionProvider;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.advancements.critereon.MinMaxBounds;
import net.minecraft.util.GsonHelper;

import javax.annotation.Nullable;
import java.util.Objects;
import java.util.function.Function;
import java.util.Optional;

public class MinMaxLongs extends MinMaxBounds<Long> {
public static final MinMaxLongs ANY = new MinMaxLongs(null, null);
@Nullable
private final Long minSq;
@Nullable
private final Long maxSq;
/**
* Replacement for the old MinMaxBounds<Long> now that MinMaxBounds is an interface in 1.21.
*
* JSON form matches vanilla MinMaxBounds: either a number, or an object with "min"/"max".
*/
public record MinMaxLongs(Optional<Long> min, Optional<Long> max) implements MinMaxBounds<Long> {
public static final MinMaxLongs ANY = new MinMaxLongs(Optional.empty(), Optional.empty());

private static MinMaxLongs create(StringReader reader, @Nullable Long min, @Nullable Long max) throws CommandSyntaxException {
if (min != null && max != null && min > max) {
throw ERROR_SWAPPED.createWithContext(reader);
} else {
return new MinMaxLongs(min, max);
}
}

@Nullable
private static Long squareOpt(@Nullable Long l) {
return l == null ? null : l * l;
}
public static final Codec<MinMaxLongs> CODEC = RecordCodecBuilder.create(inst -> inst.group(
Codec.LONG.optionalFieldOf("min").forGetter(MinMaxLongs::min),
Codec.LONG.optionalFieldOf("max").forGetter(MinMaxLongs::max)
).apply(inst, MinMaxLongs::new));

private MinMaxLongs(@Nullable Long min, @Nullable Long max) {
super(min, max);
this.minSq = squareOpt(min);
this.maxSq = squareOpt(max);
public MinMaxLongs(@Nullable Long min, @Nullable Long max) {
this(Optional.ofNullable(min), Optional.ofNullable(max));
}

public static MinMaxLongs exactly(long l) {
Expand All @@ -53,33 +45,54 @@ public static MinMaxLongs atMost(long max) {
return new MinMaxLongs(null, max);
}

public boolean isAny() {
return min.isEmpty() && max.isEmpty();
}

public boolean matches(long l) {
if (this.min != null && this.min > l) {
return false;
} else {
return this.max == null || this.max >= l;
}
if (min.isPresent() && min.get() > l) return false;
return max.isEmpty() || max.get() >= l;
}

public boolean matchesSqr(long l) {
if (this.minSq != null && this.minSq > l) {
return false;
} else {
return this.maxSq == null || this.maxSq >= l;
}
Long minSq = min.map(v -> v * v).orElse(null);
Long maxSq = max.map(v -> v * v).orElse(null);
if (minSq != null && minSq > l) return false;
return maxSq == null || maxSq >= l;
}

public static MinMaxLongs fromJson(@Nullable JsonElement json) {
return fromJson(json, ANY, GsonHelper::convertToLong, MinMaxLongs::new);
if (json == null || json.isJsonNull()) return ANY;
if (json.isJsonPrimitive()) {
long v = GsonHelper.convertToLong(json, "value");
return exactly(v);
}
JsonObject obj = GsonHelper.convertToJsonObject(json, "range");
Long min = obj.has("min") ? GsonHelper.getAsLong(obj, "min") : null;
Long max = obj.has("max") ? GsonHelper.getAsLong(obj, "max") : null;
return new MinMaxLongs(min, max);
}

public static MinMaxLongs fromReader(StringReader reader) throws CommandSyntaxException {
return fromReader(reader, (l) -> l);
}
// Minimal parser: supports "5", "5..", "..5", "5..10"
int start = reader.getCursor();
String remaining = reader.getRemaining();
int space = remaining.indexOf(' ');
String token = space >= 0 ? remaining.substring(0, space) : remaining;
reader.setCursor(start + token.length());

public static MinMaxLongs fromReader(StringReader reader, Function<Long, Long> map) throws CommandSyntaxException {
BuiltInExceptionProvider builtInExceptions = CommandSyntaxException.BUILT_IN_EXCEPTIONS;
Objects.requireNonNull(builtInExceptions);
return fromReader(reader, MinMaxLongs::create, Long::parseLong, builtInExceptions::readerInvalidInt, map);
int dots = token.indexOf("..");
if (dots < 0) {
long v = Long.parseLong(token);
return exactly(v);
}
String left = token.substring(0, dots);
String right = token.substring(dots + 2);
Long min = left.isEmpty() ? null : Long.parseLong(left);
Long max = right.isEmpty() ? null : Long.parseLong(right);
if (min != null && max != null && min > max) {
throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.readerInvalidInt().createWithContext(reader, token);
}
return new MinMaxLongs(min, max);
}
}
Loading