Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
### Fixed

- Fixed Great Spells causing a mishap when drawn without enlightenment even while parenthesized ([#1290](https://github.com/FallingColors/HexMod/pull/1290)) @Robotgiggle
- Spells with a variable cost can no longer be made free by providing an extremely small input value ([#1308](https://github.com/FallingColors/HexMod/pull/1308)) @Robotgiggle

## `0.11.4` - 2026-08-26

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import java.util.function.DoubleUnaryOperator
import kotlin.math.abs
import kotlin.math.roundToInt
import kotlin.math.roundToLong
import kotlin.math.sign

fun List<Iota>.getDouble(idx: Int, argc: Int = 0): Double {
val x = this.getOrElse(idx) { throw MishapNotEnoughArgs(idx + 1, this.size) }
Expand Down Expand Up @@ -299,6 +300,18 @@ fun evaluatable(datum: Iota, reverseIdx: Int): Either<Iota, SpellList> =

fun Iota?.orNull() = this ?: NullIota()

/**
* Converts to long while ensuring that the final result is always nonzero.
* Used when a spell's cost depends on an arbitrary multiplier provided by
* the player, so that an extremely small value doesn't make the spell free.
*/
fun Double.toNonZeroLong(): Long {
val result = this.roundToLong()
if (result == 0L)
return if (this >= 0) 1L else -1L
return result
}

// TODO do we make this work on lists
// there should probably be some way to abstract function application over lists, vecs, and numbers,
// and i bet it's fucking monads
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package at.petrak.hexcasting.api.casting.eval;

import at.petrak.hexcasting.api.casting.OperatorUtils;
import at.petrak.hexcasting.api.casting.ParticleSpray;
import at.petrak.hexcasting.api.casting.PatternShapeMatch;
import at.petrak.hexcasting.api.casting.eval.env.PlayerBasedCastEnv;
import at.petrak.hexcasting.api.casting.eval.vm.CastingImage;
import at.petrak.hexcasting.api.casting.mishaps.Mishap;
import at.petrak.hexcasting.api.casting.mishaps.MishapBadLocation;
Expand All @@ -12,7 +12,6 @@
import at.petrak.hexcasting.api.mod.HexTags;
import at.petrak.hexcasting.api.pigment.FrozenPigment;
import at.petrak.hexcasting.api.utils.HexUtils;
import at.petrak.hexcasting.common.lib.HexAttributes;
import at.petrak.hexcasting.xplat.IXplatAbstractions;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
Expand Down Expand Up @@ -269,7 +268,8 @@ public boolean isEnlightened() {
* positive.
*/
public long extractMedia(long cost, boolean simulate) {
cost = (long) (cost * costModifier);
if (cost == 0) return 0;
cost = OperatorUtils.toNonZeroLong(cost * costModifier);
for (var extractMediaComponent : preMediaExtract)
cost = extractMediaComponent.onExtractMedia(cost, simulate);
cost = extractMediaEnvironment(cost, simulate);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import at.petrak.hexcasting.api.casting.eval.vm.CastingImage
import at.petrak.hexcasting.api.casting.getEntity
import at.petrak.hexcasting.api.casting.getVec3
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.toNonZeroLong
import at.petrak.hexcasting.api.misc.MediaConstants
import net.minecraft.nbt.CompoundTag
import net.minecraft.world.entity.Entity
Expand Down Expand Up @@ -40,7 +41,7 @@ object OpAddMotion : SpellAction {

return SpellAction.Result(
Spell(target, shrunkMotion),
(motionForCost * MediaConstants.DUST_UNIT).toLong(),
(motionForCost * MediaConstants.DUST_UNIT).toNonZeroLong(),
listOf(
ParticleSpray(
target.position().add(0.0, target.eyeHeight / 2.0, 0.0),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ import at.petrak.hexcasting.api.casting.getEntity
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.mishaps.MishapBadLocation
import at.petrak.hexcasting.api.casting.mishaps.MishapImmuneEntity
import at.petrak.hexcasting.api.casting.toNonZeroLong
import at.petrak.hexcasting.api.misc.MediaConstants
import at.petrak.hexcasting.api.mod.HexConfig
import at.petrak.hexcasting.api.mod.HexTags
import at.petrak.hexcasting.common.casting.actions.spells.great.OpTeleport
import net.minecraft.world.entity.Entity
import net.minecraft.world.phys.Vec3
import kotlin.math.absoluteValue
import kotlin.math.roundToLong

object OpBlink : SpellAction {
override val argc = 2
Expand Down Expand Up @@ -54,7 +54,7 @@ object OpBlink : SpellAction {

return SpellAction.Result(
Spell(target, dvec),
(MediaConstants.SHARD_UNIT * delta.absoluteValue * 0.5).roundToLong(),
(MediaConstants.SHARD_UNIT * delta.absoluteValue * 0.5).toNonZeroLong(),
listOf(
ParticleSpray.cloud(targetMiddlePos, 2.0, 50),
ParticleSpray.burst(targetMiddlePos.add(dvec), 2.0, 100)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import at.petrak.hexcasting.api.casting.eval.CastingEnvironment
import at.petrak.hexcasting.api.casting.getPositiveDoubleUnderInclusive
import at.petrak.hexcasting.api.casting.getVec3
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.toNonZeroLong
import at.petrak.hexcasting.api.misc.MediaConstants
import at.petrak.hexcasting.common.casting.actions.selectors.OpGetEntitiesBy
import net.minecraft.core.BlockPos
Expand Down Expand Up @@ -42,7 +43,7 @@ class OpExplode(val fire: Boolean) : SpellAction {
val cost = MediaConstants.DUST_UNIT * (3 * clampedStrength + if (fire) 1.0 else 0.125)
return SpellAction.Result(
Spell(pos, strength, this.fire),
cost.toLong(),
cost.toNonZeroLong(),
listOf(ParticleSpray.burst(pos, strength, 50))
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import at.petrak.hexcasting.api.casting.eval.CastingEnvironment
import at.petrak.hexcasting.api.casting.getPlayer
import at.petrak.hexcasting.api.casting.getPositiveDouble
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.toNonZeroLong
import at.petrak.hexcasting.api.misc.MediaConstants
import at.petrak.hexcasting.api.pigment.FrozenPigment
import at.petrak.hexcasting.api.player.FlightAbility
Expand All @@ -23,7 +24,6 @@ import net.minecraft.world.item.ItemStack
import net.minecraft.world.phys.Vec3
import kotlin.math.max
import kotlin.math.roundToInt
import kotlin.math.roundToLong

class OpFlight(val type: Type) : SpellAction {
override val argc = 2
Expand All @@ -37,7 +37,7 @@ class OpFlight(val type: Type) : SpellAction {

// One block of radius, or one second of duration, costs 2 dust
val costUnit = 2 * MediaConstants.DUST_UNIT
var cost = (theArg * costUnit).roundToLong()
var cost = (theArg * costUnit).toNonZeroLong()
// Cost for anchorite does not decrease below 1 meter
if (type == Type.LimitRange) cost = max(cost, costUnit)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@ class OpPotionEffect(
}
return SpellAction.Result(
Spell(effect, target, duration, potency),
cost.toLong(),
cost.toNonZeroLong(),
listOf(ParticleSpray.cloud(target.position().add(0.0, target.eyeHeight / 2.0, 0.0), 1.0))
)
}

private class Spell(val effect: MobEffect, val target: LivingEntity, val duration: Double, val potency: Double) :
RenderedSpell {
override fun cast(env: CastingEnvironment) {
if (duration > 1.0 / 20.0) {
if (duration >= 1.0 / 20.0) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why was this originally a >?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Honestly, no idea. Might have just been an oversight from whoever originally wrote it, since the intent seems to pretty clearly be "minimum duration of 1 tick".

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yep, it was a misimpl: 81e5f85

val effectInst = MobEffectInstance(effect, (duration * 20).toInt(), potency.toInt() - 1)
target.addEffect(effectInst)
}
Expand Down
Loading