Merge branch 'main' into 1.0

# Conflicts:
#	Common/src/generated/resources/.cache/cache
#	Common/src/generated/resources/assets/hexcasting/models/block/conjured.json
#	Common/src/main/java/at/petrak/hexcasting/api/PatternRegistry.kt
#	Common/src/main/java/at/petrak/hexcasting/api/addldata/ManaHolder.java
#	Common/src/main/java/at/petrak/hexcasting/api/block/circle/BlockEntityAbstractImpetus.java
#	Common/src/main/java/at/petrak/hexcasting/api/item/ColorizerItem.java
#	Common/src/main/java/at/petrak/hexcasting/api/item/DataHolderItem.java
#	Common/src/main/java/at/petrak/hexcasting/api/item/HexHolderItem.java
#	Common/src/main/java/at/petrak/hexcasting/api/item/ManaHolderItem.java
#	Common/src/main/java/at/petrak/hexcasting/api/spell/Action.kt
#	Common/src/main/java/at/petrak/hexcasting/api/spell/SpellDatum.kt
#	Common/src/main/java/at/petrak/hexcasting/api/spell/Widget.kt
#	Common/src/main/java/at/petrak/hexcasting/api/spell/casting/CastingContext.kt
#	Common/src/main/java/at/petrak/hexcasting/api/spell/casting/CastingHarness.kt
#	Common/src/main/java/at/petrak/hexcasting/api/spell/mishaps/MishapNoSpellCircle.kt
#	Common/src/main/java/at/petrak/hexcasting/api/spell/mishaps/MishapNotEnoughArgs.kt
#	Common/src/main/java/at/petrak/hexcasting/api/utils/ManaHelper.kt
#	Common/src/main/java/at/petrak/hexcasting/client/RegisterClientStuff.java
#	Common/src/main/java/at/petrak/hexcasting/client/ShiftScrollListener.java
#	Common/src/main/java/at/petrak/hexcasting/client/gui/GuiSpellcasting.kt
#	Common/src/main/java/at/petrak/hexcasting/common/blocks/akashic/BlockAkashicFloodfiller.java
#	Common/src/main/java/at/petrak/hexcasting/common/blocks/entity/BlockEntityStoredPlayerImpetus.java
#	Common/src/main/java/at/petrak/hexcasting/common/casting/RegisterPatterns.java
#	Common/src/main/java/at/petrak/hexcasting/common/casting/operators/OpBlockAxisRaycast.kt
#	Common/src/main/java/at/petrak/hexcasting/common/casting/operators/OpTheCoolerRead.kt
#	Common/src/main/java/at/petrak/hexcasting/common/casting/operators/OpTheCoolerReadable.kt
#	Common/src/main/java/at/petrak/hexcasting/common/casting/operators/OpWrite.kt
#	Common/src/main/java/at/petrak/hexcasting/common/casting/operators/lists/OpRemove.kt
#	Common/src/main/java/at/petrak/hexcasting/common/casting/operators/math/OpPowProj.kt
#	Common/src/main/java/at/petrak/hexcasting/common/casting/operators/math/logic/OpBoolIdentityKindOf.kt
#	Common/src/main/java/at/petrak/hexcasting/common/casting/operators/spells/OpBreakBlock.kt
#	Common/src/main/java/at/petrak/hexcasting/common/casting/operators/spells/OpConjureBlock.kt
#	Common/src/main/java/at/petrak/hexcasting/common/casting/operators/spells/OpCreateWater.kt
#	Common/src/main/java/at/petrak/hexcasting/common/casting/operators/spells/OpDestroyWater.kt
#	Common/src/main/java/at/petrak/hexcasting/common/casting/operators/spells/OpEdifySapling.kt
#	Common/src/main/java/at/petrak/hexcasting/common/casting/operators/spells/OpErase.kt
#	Common/src/main/java/at/petrak/hexcasting/common/casting/operators/spells/OpExplode.kt
#	Common/src/main/java/at/petrak/hexcasting/common/casting/operators/spells/OpExtinguish.kt
#	Common/src/main/java/at/petrak/hexcasting/common/casting/operators/spells/OpIgnite.kt
#	Common/src/main/java/at/petrak/hexcasting/common/casting/operators/spells/OpMakeBattery.kt
#	Common/src/main/java/at/petrak/hexcasting/common/casting/operators/spells/OpMakePackagedSpell.kt
#	Common/src/main/java/at/petrak/hexcasting/common/casting/operators/spells/OpPlaceBlock.kt
#	Common/src/main/java/at/petrak/hexcasting/common/casting/operators/spells/OpPotionEffect.kt
#	Common/src/main/java/at/petrak/hexcasting/common/casting/operators/spells/OpRecharge.kt
#	Common/src/main/java/at/petrak/hexcasting/common/casting/operators/spells/OpTheOnlyReasonAnyoneDownloadedPsi.kt
#	Common/src/main/java/at/petrak/hexcasting/common/casting/operators/spells/great/OpBrainsweep.kt
#	Common/src/main/java/at/petrak/hexcasting/common/casting/operators/spells/great/OpCreateLava.kt
#	Common/src/main/java/at/petrak/hexcasting/common/casting/operators/spells/great/OpTeleport.kt
#	Common/src/main/java/at/petrak/hexcasting/common/casting/operators/stack/OpAlwinfyHasAscendedToABeingOfPureMath.kt
#	Common/src/main/java/at/petrak/hexcasting/common/casting/operators/stack/OpMask.kt
#	Common/src/main/java/at/petrak/hexcasting/common/command/ListPatternsCommand.java
#	Common/src/main/java/at/petrak/hexcasting/common/items/magic/ItemCreativeUnlocker.java
#	Common/src/main/java/at/petrak/hexcasting/common/items/magic/ItemMediaHolder.java
#	Common/src/main/java/at/petrak/hexcasting/common/lib/HexBlocks.java
#	Common/src/main/java/at/petrak/hexcasting/common/lib/HexItems.java
#	Common/src/main/java/at/petrak/hexcasting/common/misc/AkashicTreeGrower.java
#	Common/src/main/java/at/petrak/hexcasting/common/network/MsgNewSpellPatternAck.java
#	Common/src/main/java/at/petrak/hexcasting/common/network/MsgShiftScrollSyn.java
#	Common/src/main/java/at/petrak/hexcasting/datagen/HexAdvancements.java
#	Common/src/main/java/at/petrak/hexcasting/datagen/HexItemTagProvider.java
#	Common/src/main/java/at/petrak/hexcasting/datagen/recipe/HexplatRecipes.kt
#	Common/src/main/java/at/petrak/hexcasting/mixin/accessor/client/AccessorMouseHandler.java
#	Common/src/main/java/at/petrak/hexcasting/xplat/IXplatAbstractions.java
#	Common/src/main/resources/assets/hexcasting/lang/en_us.json
#	Fabric/gradle.properties
#	Fabric/src/generated/resources/.cache/cache
#	Fabric/src/generated/resources/data/hexcasting/recipes/dye_colorizer_black.json
#	Fabric/src/generated/resources/data/hexcasting/recipes/dye_colorizer_blue.json
#	Fabric/src/generated/resources/data/hexcasting/recipes/dye_colorizer_brown.json
#	Fabric/src/generated/resources/data/hexcasting/recipes/dye_colorizer_cyan.json
#	Fabric/src/generated/resources/data/hexcasting/recipes/dye_colorizer_gray.json
#	Fabric/src/generated/resources/data/hexcasting/recipes/dye_colorizer_green.json
#	Fabric/src/generated/resources/data/hexcasting/recipes/dye_colorizer_light_blue.json
#	Fabric/src/generated/resources/data/hexcasting/recipes/dye_colorizer_light_gray.json
#	Fabric/src/generated/resources/data/hexcasting/recipes/dye_colorizer_lime.json
#	Fabric/src/generated/resources/data/hexcasting/recipes/dye_colorizer_magenta.json
#	Fabric/src/generated/resources/data/hexcasting/recipes/dye_colorizer_orange.json
#	Fabric/src/generated/resources/data/hexcasting/recipes/dye_colorizer_pink.json
#	Fabric/src/generated/resources/data/hexcasting/recipes/dye_colorizer_purple.json
#	Fabric/src/generated/resources/data/hexcasting/recipes/dye_colorizer_red.json
#	Fabric/src/generated/resources/data/hexcasting/recipes/dye_colorizer_white.json
#	Fabric/src/generated/resources/data/hexcasting/recipes/dye_colorizer_yellow.json
#	Fabric/src/generated/resources/data/hexcasting/recipes/pride_colorizer_agender.json
#	Fabric/src/generated/resources/data/hexcasting/recipes/pride_colorizer_aroace.json
#	Fabric/src/generated/resources/data/hexcasting/recipes/pride_colorizer_aromantic.json
#	Fabric/src/generated/resources/data/hexcasting/recipes/pride_colorizer_asexual.json
#	Fabric/src/generated/resources/data/hexcasting/recipes/pride_colorizer_bisexual.json
#	Fabric/src/generated/resources/data/hexcasting/recipes/pride_colorizer_demiboy.json
#	Fabric/src/generated/resources/data/hexcasting/recipes/pride_colorizer_demigirl.json
#	Fabric/src/generated/resources/data/hexcasting/recipes/pride_colorizer_gay.json
#	Fabric/src/generated/resources/data/hexcasting/recipes/pride_colorizer_genderfluid.json
#	Fabric/src/generated/resources/data/hexcasting/recipes/pride_colorizer_genderqueer.json
#	Fabric/src/generated/resources/data/hexcasting/recipes/pride_colorizer_intersex.json
#	Fabric/src/generated/resources/data/hexcasting/recipes/pride_colorizer_lesbian.json
#	Fabric/src/generated/resources/data/hexcasting/recipes/pride_colorizer_nonbinary.json
#	Fabric/src/generated/resources/data/hexcasting/recipes/pride_colorizer_pansexual.json
#	Fabric/src/generated/resources/data/hexcasting/recipes/pride_colorizer_plural.json
#	Fabric/src/generated/resources/data/hexcasting/recipes/pride_colorizer_transgender.json
#	Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexClientInitializer.kt
#	Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexConfig.java
#	Fabric/src/main/java/at/petrak/hexcasting/fabric/FabricHexInitializer.kt
#	Fabric/src/main/java/at/petrak/hexcasting/fabric/cc/CCDataHolder.java
#	Fabric/src/main/java/at/petrak/hexcasting/fabric/cc/HexCardinalComponents.java
#	Fabric/src/main/java/at/petrak/hexcasting/fabric/interop/emi/EmiBrainsweepRecipe.java
#	Fabric/src/main/java/at/petrak/hexcasting/fabric/interop/emi/HexEMIPlugin.java
#	Fabric/src/main/java/at/petrak/hexcasting/fabric/interop/rei/BrainsweepRecipeCategory.java
#	Fabric/src/main/java/at/petrak/hexcasting/fabric/interop/rei/HexREIPlugin.java
#	Fabric/src/main/java/at/petrak/hexcasting/fabric/interop/rei/PatternRendererREI.java
#	Fabric/src/main/java/at/petrak/hexcasting/fabric/recipe/FabricUnsealedIngredient.java
#	Fabric/src/main/java/at/petrak/hexcasting/fabric/storage/FabricImpetusStorage.kt
#	Fabric/src/main/java/at/petrak/hexcasting/fabric/xplat/FabricXplatImpl.java
#	Fabric/src/main/resources/fabric.mod.json
#	Forge/build.gradle
#	Forge/gradle.properties
#	Forge/src/generated/resources/.cache/cache
#	Forge/src/generated/resources/data/hexcasting/recipes/dye_colorizer_black.json
#	Forge/src/generated/resources/data/hexcasting/recipes/dye_colorizer_blue.json
#	Forge/src/generated/resources/data/hexcasting/recipes/dye_colorizer_brown.json
#	Forge/src/generated/resources/data/hexcasting/recipes/dye_colorizer_cyan.json
#	Forge/src/generated/resources/data/hexcasting/recipes/dye_colorizer_gray.json
#	Forge/src/generated/resources/data/hexcasting/recipes/dye_colorizer_green.json
#	Forge/src/generated/resources/data/hexcasting/recipes/dye_colorizer_light_blue.json
#	Forge/src/generated/resources/data/hexcasting/recipes/dye_colorizer_light_gray.json
#	Forge/src/generated/resources/data/hexcasting/recipes/dye_colorizer_lime.json
#	Forge/src/generated/resources/data/hexcasting/recipes/dye_colorizer_magenta.json
#	Forge/src/generated/resources/data/hexcasting/recipes/dye_colorizer_orange.json
#	Forge/src/generated/resources/data/hexcasting/recipes/dye_colorizer_pink.json
#	Forge/src/generated/resources/data/hexcasting/recipes/dye_colorizer_purple.json
#	Forge/src/generated/resources/data/hexcasting/recipes/dye_colorizer_red.json
#	Forge/src/generated/resources/data/hexcasting/recipes/dye_colorizer_white.json
#	Forge/src/generated/resources/data/hexcasting/recipes/dye_colorizer_yellow.json
#	Forge/src/generated/resources/data/hexcasting/recipes/pride_colorizer_agender.json
#	Forge/src/generated/resources/data/hexcasting/recipes/pride_colorizer_aroace.json
#	Forge/src/generated/resources/data/hexcasting/recipes/pride_colorizer_aromantic.json
#	Forge/src/generated/resources/data/hexcasting/recipes/pride_colorizer_asexual.json
#	Forge/src/generated/resources/data/hexcasting/recipes/pride_colorizer_bisexual.json
#	Forge/src/generated/resources/data/hexcasting/recipes/pride_colorizer_demiboy.json
#	Forge/src/generated/resources/data/hexcasting/recipes/pride_colorizer_demigirl.json
#	Forge/src/generated/resources/data/hexcasting/recipes/pride_colorizer_gay.json
#	Forge/src/generated/resources/data/hexcasting/recipes/pride_colorizer_genderfluid.json
#	Forge/src/generated/resources/data/hexcasting/recipes/pride_colorizer_genderqueer.json
#	Forge/src/generated/resources/data/hexcasting/recipes/pride_colorizer_intersex.json
#	Forge/src/generated/resources/data/hexcasting/recipes/pride_colorizer_lesbian.json
#	Forge/src/generated/resources/data/hexcasting/recipes/pride_colorizer_nonbinary.json
#	Forge/src/generated/resources/data/hexcasting/recipes/pride_colorizer_pansexual.json
#	Forge/src/generated/resources/data/hexcasting/recipes/pride_colorizer_plural.json
#	Forge/src/generated/resources/data/hexcasting/recipes/pride_colorizer_transgender.json
#	Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexClientInitializer.java
#	Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexConfig.java
#	Forge/src/main/java/at/petrak/hexcasting/forge/ForgeHexInitializer.java
#	Forge/src/main/java/at/petrak/hexcasting/forge/cap/ForgeCapabilityHandler.java
#	Forge/src/main/java/at/petrak/hexcasting/forge/cap/ForgeImpetusCapability.java
#	Forge/src/main/java/at/petrak/hexcasting/forge/datagen/HexForgeDataGenerators.java
#	Forge/src/main/java/at/petrak/hexcasting/forge/datagen/xplat/HexBlockStatesAndModels.java
#	Forge/src/main/java/at/petrak/hexcasting/forge/interop/jei/HexJEIPlugin.java
#	Forge/src/main/java/at/petrak/hexcasting/forge/recipe/ForgeUnsealedIngredient.java
#	Forge/src/main/java/at/petrak/hexcasting/forge/xplat/ForgeXplatImpl.java
#	Forge/src/main/resources/META-INF/accesstransformer.cfg
#	gradle.properties
This commit is contained in:
yrsegal@gmail.com 2022-11-05 21:49:59 -04:00
commit a01ff27453
193 changed files with 5970 additions and 1250 deletions

View file

@ -38,6 +38,9 @@ dependencies {
compileOnly "at.petra-k.paucal:paucal-common-$minecraftVersion:$paucalVersion"
compileOnly "vazkii.patchouli:Patchouli-xplat:$minecraftVersion-$patchouliVersion"
compileOnly "org.jetbrains:annotations:$jetbrainsAnnotationsVersion"
testCompileOnly "org.jetbrains:annotations:$jetbrainsAnnotationsVersion"
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.6.1'
}

View file

@ -4,9 +4,7 @@
"conditions": {
"items": [
{
"items": [
"hexcasting:charged_amethyst"
]
"tag": "hexcasting:grants_root_advancement"
}
]
},

View file

@ -0,0 +1,267 @@
package at.petrak.hexcasting.api
import at.petrak.hexcasting.api.spell.Operator
import at.petrak.hexcasting.api.spell.math.EulerPathFinder
import at.petrak.hexcasting.api.spell.math.HexDir
import at.petrak.hexcasting.api.spell.math.HexPattern
import at.petrak.hexcasting.api.spell.mishaps.MishapInvalidPattern
import at.petrak.hexcasting.api.utils.getSafe
import net.minecraft.nbt.CompoundTag
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.level.saveddata.SavedData
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentLinkedDeque
import java.util.concurrent.ConcurrentMap
/**
* Register your patterns and associate them with spells here.
*
* Most patterns are matches to their operators solely by their *angle signature*.
* This is a record of all the angles in the pattern, independent of the direction the player started drawing in.
* It's written in shorthand, where `w` is straight ahead, `q` is left, `a` is left-back, `d` is right-back,
* and `e` is right.
*
* For example, the signature for a straight line of 3 segments is `"ww"`. The signatures for the "get caster"
* operator (the diamond) are `"qaq"` and `"ede"`.
*/
object PatternRegistry {
private val operatorLookup = ConcurrentHashMap<ResourceLocation, Operator>()
private val keyLookup = ConcurrentHashMap<Operator, ResourceLocation>()
private val specialHandlers: ConcurrentLinkedDeque<SpecialHandlerEntry> = ConcurrentLinkedDeque()
// Map signatures to the "preferred" direction they start in and their operator ID.
private val regularPatternLookup: ConcurrentMap<String, RegularEntry> =
ConcurrentHashMap()
private val perWorldPatternLookup: ConcurrentMap<ResourceLocation, PerWorldEntry> =
ConcurrentHashMap()
/**
* Associate a given angle signature with a SpellOperator.
*/
@JvmStatic
@JvmOverloads
@Throws(RegisterPatternException::class)
fun mapPattern(pattern: HexPattern, id: ResourceLocation, operator: Operator, isPerWorld: Boolean = false) {
this.operatorLookup[id]?.let {
throw RegisterPatternException("The operator with id `$id` was already registered to: $it")
}
this.operatorLookup[id] = operator
this.keyLookup[operator] = id
if (isPerWorld) {
this.perWorldPatternLookup[id] = PerWorldEntry(pattern, id)
} else {
this.regularPatternLookup[pattern.anglesSignature()] = RegularEntry(pattern.startDir, id)
}
}
/**
* Add a special handler, to take an arbitrary pattern and return whatever kind of operator you like.
*/
@JvmStatic
fun addSpecialHandler(handler: SpecialHandlerEntry) {
this.specialHandlers.add(handler)
}
@JvmStatic
fun addSpecialHandler(id: ResourceLocation, handler: SpecialHandler) {
this.addSpecialHandler(SpecialHandlerEntry(id, handler))
}
/**
* Internal use only.
*/
@JvmStatic
fun matchPattern(pat: HexPattern, overworld: ServerLevel): Operator =
matchPatternAndID(pat, overworld).first
/**
* Internal use only.
*/
@JvmStatic
fun matchPatternAndID(pat: HexPattern, overworld: ServerLevel): Pair<Operator, ResourceLocation> {
// Is it global?
val sig = pat.anglesSignature()
this.regularPatternLookup[sig]?.let {
val op = this.operatorLookup[it.opId] ?: throw MishapInvalidPattern()
return op to it.opId
}
// Look it up in the world?
val ds = overworld.dataStorage
val perWorldPatterns: Save =
ds.computeIfAbsent(Save.Companion::load, { Save.create(overworld.seed) }, TAG_SAVED_DATA)
perWorldPatterns.fillMissingEntries(overworld.seed)
perWorldPatterns.lookup[sig]?.let {
val op = this.operatorLookup[it.first]!!
return op to it.first
}
// Lookup a special handler
// Do this last to prevent conflicts with great spells; this has happened a few times with
// create phial hahaha
for (handler in specialHandlers) {
val op = handler.handler.handlePattern(pat)
if (op != null) return op to handler.id
}
throw MishapInvalidPattern()
}
/**
* Internal use only.
*/
@JvmStatic
fun getPerWorldPatterns(overworld: ServerLevel): Map<String, Pair<ResourceLocation, HexDir>> {
val ds = overworld.dataStorage
val perWorldPatterns: Save =
ds.computeIfAbsent(Save.Companion::load, { Save.create(overworld.seed) }, TAG_SAVED_DATA)
return perWorldPatterns.lookup
}
/**
* Internal use only.
*/
@JvmStatic
fun lookupPattern(op: Operator): ResourceLocation? = this.keyLookup[op]
/**
* Internal use only.
*/
@JvmStatic
fun lookupPattern(opId: ResourceLocation): PatternEntry {
this.perWorldPatternLookup[opId]?.let {
return PatternEntry(it.prototype, this.operatorLookup[it.opId]!!, true)
}
for ((sig, entry) in this.regularPatternLookup) {
if (entry.opId == opId) {
val pattern = HexPattern.fromAngles(sig, entry.preferredStart)
return PatternEntry(pattern, this.operatorLookup[entry.opId]!!, false)
}
}
throw IllegalArgumentException("could not find a pattern for $opId")
}
/**
* Internal use only.
*/
@JvmStatic
fun getAllPerWorldPatternNames(): Set<ResourceLocation> {
return this.perWorldPatternLookup.keys.toSet()
}
/**
* Special handling of a pattern. Before checking any of the normal angle-signature based patterns,
* a given pattern is run by all of these special handlers patterns. If none of them return non-null,
* then its signature is checked.
*
* In the base mod, this is used for number patterns.
*/
fun interface SpecialHandler {
fun handlePattern(pattern: HexPattern): Operator?
}
data class SpecialHandlerEntry(val id: ResourceLocation, val handler: SpecialHandler)
class RegisterPatternException(msg: String) : Exception(msg)
private data class RegularEntry(val preferredStart: HexDir, val opId: ResourceLocation)
private data class PerWorldEntry(val prototype: HexPattern, val opId: ResourceLocation)
// Fake class we pretend to use internally
data class PatternEntry(val prototype: HexPattern, val operator: Operator, val isPerWorld: Boolean)
/**
* Maps angle sigs to resource locations and their preferred start dir so we can look them up in the main registry
*/
class Save(val lookup: MutableMap<String, Pair<ResourceLocation, HexDir>>, var missingEntries: Boolean) : SavedData() {
constructor(lookup: MutableMap<String, Pair<ResourceLocation, HexDir>>) : this(lookup, missingAny(lookup))
override fun save(tag: CompoundTag): CompoundTag {
for ((sig, rhs) in this.lookup) {
val (id, startDir) = rhs
val entry = CompoundTag()
entry.putString(TAG_OP_ID, id.toString())
entry.putByte(TAG_START_DIR, startDir.ordinal.toByte())
tag.put(sig, entry)
}
return tag
}
fun fillMissingEntries(seed: Long) {
if (missingEntries) {
var doneAny = false
val allIds = lookup.values.map { it.first }
for ((prototype, opId) in perWorldPatternLookup.values) {
if (opId !in allIds) {
scrungle(lookup, prototype, opId, seed)
doneAny = true
}
}
if (doneAny) {
setDirty()
missingEntries = false
}
}
}
companion object {
fun missingAny(lookup: MutableMap<String, Pair<ResourceLocation, HexDir>>): Boolean {
val allIds = lookup.values.map { it.first }
return perWorldPatternLookup.values.any { it.opId !in allIds }
}
fun load(tag: CompoundTag): Save {
val map = HashMap<String, Pair<ResourceLocation, HexDir>>()
val allIds = mutableSetOf<ResourceLocation>()
for (sig in tag.allKeys) {
val entry = tag.getCompound(sig)
val opId = ResourceLocation.tryParse(entry.getString(TAG_OP_ID)) ?: continue
allIds.add(opId)
val startDir = HexDir.values().getSafe(entry.getByte(TAG_START_DIR))
map[sig] = opId to startDir
}
val missingEntries = perWorldPatternLookup.values.any { it.opId !in allIds }
return Save(map, missingEntries)
}
fun scrungle(lookup: MutableMap<String, Pair<ResourceLocation, HexDir>>, prototype: HexPattern, opId: ResourceLocation, seed: Long) {
val scrungled = EulerPathFinder.findAltDrawing(prototype, seed) {
val sig = it.anglesSignature()
!lookup.contains(sig) &&
!regularPatternLookup.contains(sig)
&& specialHandlers.none { handler -> handler.handler.handlePattern(it) != null }
}
lookup[scrungled.anglesSignature()] = opId to scrungled.startDir
}
@JvmStatic
fun create(seed: Long): Save {
val map = mutableMapOf<String, Pair<ResourceLocation, HexDir>>()
for ((prototype, opId) in perWorldPatternLookup.values) {
scrungle(map, prototype, opId, seed)
}
val save = Save(map)
save.setDirty()
return save
}
}
}
const val TAG_SAVED_DATA = "hex.per-world-patterns"
private const val TAG_OP_ID = "op_id"
private const val TAG_START_DIR = "start_dir"
@JvmStatic
fun getPatternCountInfo(): String = "Loaded ${regularPatternLookup.size} regular patterns, " +
"${perWorldPatternLookup.size} per-world patterns, and " +
"${specialHandlers.size} special handlers."
}

View file

@ -0,0 +1,103 @@
package at.petrak.hexcasting.api.addldata;
import org.jetbrains.annotations.ApiStatus;
public interface ManaHolder {
/**
* Use {@code withdrawMana(-1, true)}
*
* @see ManaHolder#withdrawMana(int, boolean)
*/
@ApiStatus.OverrideOnly
int getMana();
/**
* Use {@code withdrawMana(-1, true) + insertMana(-1, true)} where possible
*
* @see ManaHolder#insertMana(int, boolean)
* @see ManaHolder#withdrawMana(int, boolean)
*/
@ApiStatus.OverrideOnly
int getMaxMana();
/**
* Use {@code insertMana(mana - withdrawMana(-1, true), false)} where possible
*
* @see ManaHolder#insertMana(int, boolean)
* @see ManaHolder#withdrawMana(int, boolean)
*/
@ApiStatus.OverrideOnly
void setMana(int mana);
/**
* Whether this mana holder can have mana inserted into it.
*/
boolean canRecharge();
/**
* Whether this mana holder can be extracted from.
*/
boolean canProvide();
/**
* The priority for this mana holder to be selected when casting a hex. Higher priorities are taken first.
*
* By default,
* * Charged Amethyst has priority 1
* * Amethyst Shards have priority 2
* * Amethyst Dust has priority 3
* * Items which hold mana have priority 40
*/
int getConsumptionPriority();
/**
* Whether the mana inside this mana holder may be used to construct a battery.
*/
boolean canConstructBattery();
/**
* Withdraws mana from the holder. Returns the amount of mana extracted, which may be less or more than the cost.
*
* Even if {@link ManaHolder#canProvide} is false, you can still withdraw mana this way.
*
* Withdrawing a negative amount will act as though you attempted to withdraw as much mana as the holder contains.
*/
default int withdrawMana(int cost, boolean simulate) {
var manaHere = getMana();
if (cost < 0) {
cost = manaHere;
}
if (!simulate) {
var manaLeft = manaHere - cost;
setMana(manaLeft);
}
return Math.min(cost, manaHere);
}
/**
* Inserts mana into the holder. Returns the amount of mana inserted, which may be less than the requested amount.
*
* Even if {@link ManaHolder#canRecharge} is false, you can still insert mana this way.
*
* Inserting a negative amount will act as though you attempted to insert exactly as much mana as the holder was missing.
*/
default int insertMana(int amount, boolean simulate) {
var manaHere = getMana();
int emptySpace = getMaxMana() - manaHere;
if (emptySpace <= 0) {
return 0;
}
if (amount < 0) {
amount = emptySpace;
}
int inserting = Math.min(amount, emptySpace);
if (!simulate) {
var newMana = manaHere + inserting;
setMana(newMana);
}
return inserting;
}
}

View file

@ -35,7 +35,7 @@ public class OvercastTrigger extends SimpleCriterionTrigger<OvercastTrigger.Inst
super.trigger(player, inst -> {
var manaToHealth = HexConfig.common().manaToHealthRate();
var healthUsed = manaGenerated / manaToHealth;
return inst.test(manaGenerated, healthUsed / player.getMaxHealth(), player.getHealth() - (float) healthUsed);
return inst.test(manaGenerated, healthUsed / player.getMaxHealth(), player.getHealth());
});
}

View file

@ -15,8 +15,6 @@ import at.petrak.hexcasting.common.lib.HexItems;
import at.petrak.hexcasting.common.lib.HexSounds;
import at.petrak.hexcasting.xplat.IXplatAbstractions;
import com.mojang.datafixers.util.Pair;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
@ -35,6 +33,7 @@ import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
@ -43,6 +42,7 @@ import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nullable;
import java.text.DecimalFormat;
import java.util.*;
public abstract class BlockEntityAbstractImpetus extends HexBlockEntity implements WorldlyContainer {
@ -55,6 +55,8 @@ public abstract class BlockEntityAbstractImpetus extends HexBlockEntity implemen
TAG_MANA = "mana",
TAG_LAST_MISHAP = "last_mishap";
private static final DecimalFormat DUST_AMOUNT = new DecimalFormat("###,###.##");
@Nullable
private UUID activator = null;
@Nullable
@ -113,9 +115,9 @@ public abstract class BlockEntityAbstractImpetus extends HexBlockEntity implemen
}
public void applyScryingLensOverlay(List<Pair<ItemStack, Component>> lines,
BlockState state, BlockPos pos,
LocalPlayer observer, ClientLevel world,
Direction hitFace, InteractionHand lensHand) {
BlockState state, BlockPos pos,
Player observer, Level world,
Direction hitFace) {
if (world.getBlockEntity(pos) instanceof BlockEntityAbstractImpetus beai) {
if (beai.getMana() < 0) {
lines.add(new Pair<>(new ItemStack(HexItems.AMETHYST_DUST), ItemCreativeUnlocker.infiniteMedia(world)));
@ -532,23 +534,26 @@ public abstract class BlockEntityAbstractImpetus extends HexBlockEntity implemen
return false;
}
@Override
public void clearContent() {
// NO-OP
}
@Override
public boolean canPlaceItem(int index, ItemStack stack) {
if (remainingManaCapacity() == 0)
return false;
if (stack.is(HexItems.CREATIVE_UNLOCKER))
return true;
var manamount = extractManaFromItem(stack, true);
return manamount > 0;
}
@Override
public void clearContent() {
this.mana = 0;
this.stopCasting();
this.sync();
}
public int extractManaFromItem(ItemStack stack, boolean simulate) {
if (this.mana < 0) {
if (this.mana < 0)
return 0;
}
return ManaHelper.extractMana(stack, remainingManaCapacity(), true, simulate);
}
@ -559,7 +564,7 @@ public abstract class BlockEntityAbstractImpetus extends HexBlockEntity implemen
} else {
var manamount = extractManaFromItem(stack, false);
if (manamount > 0) {
this.mana += manamount;
this.mana = Math.min(manamount + mana, MAX_CAPACITY);
this.sync();
}
}
@ -571,9 +576,8 @@ public abstract class BlockEntityAbstractImpetus extends HexBlockEntity implemen
}
public int remainingManaCapacity() {
if (this.mana < 0) {
if (this.mana < 0)
return 0;
}
return MAX_CAPACITY - this.mana;
return Math.max(0, MAX_CAPACITY - this.mana);
}
}

View file

@ -4,21 +4,20 @@ import at.petrak.hexcasting.xplat.IXplatAbstractions;
import com.google.common.collect.Lists;
import com.mojang.datafixers.util.Pair;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.world.level.Level;
import net.minecraft.world.entity.player.Player;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.BeehiveBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Map;
@ -38,15 +37,22 @@ public final class ScryingLensOverlayRegistry {
private static final List<Pair<OverlayPredicate, OverlayBuilder>> PREDICATE_LOOKUP = new Vector<>();
// implemented as a map to allow for weak dereferencing
private static final Map<LocalPlayer, Pair<BlockPos, Integer>> comparatorData = new WeakHashMap<>();
private static final Map<Player, Pair<BlockPos, Integer>> comparatorData = new WeakHashMap<>();
private static final Map<Player, Pair<BlockPos, Integer>> beeData = new WeakHashMap<>();
public static void receiveComparatorValue(BlockPos pos, int value) {
LocalPlayer player = Minecraft.getInstance().player;
public static void receiveComparatorAndBeeValue(BlockPos pos, int comparator, int bee) {
Player player = Minecraft.getInstance().player;
if (player != null) {
if (pos == null || value == -1) {
if (pos == null || comparator == -1) {
comparatorData.remove(player);
} else {
comparatorData.put(player, new Pair<>(pos, value));
comparatorData.put(player, new Pair<>(pos, comparator));
}
if (pos == null || bee == -1) {
beeData.remove(player);
} else {
beeData.put(player, new Pair<>(pos, bee));
}
}
}
@ -80,6 +86,34 @@ public final class ScryingLensOverlayRegistry {
return comparatorValue.getSecond();
}
public static int getBeeValue() {
var mc = Minecraft.getInstance();
var player = mc.player;
var level = mc.level;
var result = mc.hitResult;
if (player == null || level == null || result == null || result.getType() != HitResult.Type.BLOCK) {
return -1;
}
var beeValue = beeData.get(player);
if (beeValue == null) {
return -1;
}
var pos = ((BlockHitResult) result).getBlockPos();
if (!pos.equals(beeValue.getFirst())) {
return -1;
}
var state = mc.level.getBlockState(pos);
if (!(state.getBlock() instanceof BeehiveBlock)) {
return -1;
}
return beeValue.getSecond();
}
/**
* Add the block to display things when the player is holding a lens and looking at it.
*
@ -115,17 +149,17 @@ public final class ScryingLensOverlayRegistry {
* Internal use only.
*/
public static @NotNull List<Pair<ItemStack, Component>> getLines(BlockState state, BlockPos pos,
LocalPlayer observer, ClientLevel world,
Direction hitFace, @Nullable InteractionHand lensHand) {
Player observer, Level world,
Direction hitFace) {
List<Pair<ItemStack, Component>> lines = Lists.newArrayList();
var idLookedup = ID_LOOKUP.get(IXplatAbstractions.INSTANCE.getID(state.getBlock()));
if (idLookedup != null) {
idLookedup.addLines(lines, state, pos, observer, world, hitFace, lensHand);
idLookedup.addLines(lines, state, pos, observer, world, hitFace);
}
for (var pair : PREDICATE_LOOKUP) {
if (pair.getFirst().test(state, pos, observer, world, hitFace, lensHand)) {
pair.getSecond().addLines(lines, state, pos, observer, world, hitFace, lensHand);
if (pair.getFirst().test(state, pos, observer, world, hitFace)) {
pair.getSecond().addLines(lines, state, pos, observer, world, hitFace);
}
}
@ -140,9 +174,9 @@ public final class ScryingLensOverlayRegistry {
@FunctionalInterface
public interface OverlayBuilder {
void addLines(List<Pair<ItemStack, Component>> lines,
BlockState state, BlockPos pos, LocalPlayer observer,
ClientLevel world,
Direction hitFace, @Nullable InteractionHand lensHand);
BlockState state, BlockPos pos, Player observer,
Level world,
Direction hitFace);
}
/**
@ -150,8 +184,8 @@ public final class ScryingLensOverlayRegistry {
*/
@FunctionalInterface
public interface OverlayPredicate {
boolean test(BlockState state, BlockPos pos, LocalPlayer observer,
ClientLevel world,
Direction hitFace, @Nullable InteractionHand lensHand);
boolean test(BlockState state, BlockPos pos, Player observer,
Level world,
Direction hitFace);
}
}

View file

@ -2,6 +2,7 @@ package at.petrak.hexcasting.api.item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.ApiStatus;
import java.util.UUID;
@ -11,6 +12,7 @@ import java.util.UUID;
* On both the Forge and Fabric sides, the registry will be scanned for all items which implement this interface,
* and the appropriate cap/CC will be attached.
*/
@ApiStatus.OverrideOnly
public interface ColorizerItem {
int color(ItemStack stack, UUID owner, float time, Vec3 position);
}

View file

@ -0,0 +1,60 @@
package at.petrak.hexcasting.api.item;
import at.petrak.hexcasting.api.spell.SpellDatum;
import at.petrak.hexcasting.api.utils.NBTHelper;
import net.minecraft.ChatFormatting;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import java.util.List;
@ApiStatus.OverrideOnly
public interface DataHolderItem {
String TAG_OVERRIDE_VISUALLY = "VisualOverride";
@Nullable CompoundTag readDatumTag(ItemStack stack);
@Nullable
default SpellDatum<?> readDatum(ItemStack stack, ServerLevel world) {
var tag = readDatumTag(stack);
if (tag != null) {
return SpellDatum.fromNBT(tag, world);
} else {
return null;
}
}
@Nullable
default SpellDatum<?> emptyDatum(ItemStack stack) {
return null;
}
boolean canWrite(ItemStack stack, @Nullable SpellDatum<?> datum);
void writeDatum(ItemStack stack, @Nullable SpellDatum<?> datum);
static void appendHoverText(DataHolderItem self, ItemStack pStack, List<Component> pTooltipComponents,
TooltipFlag pIsAdvanced) {
var datumTag = self.readDatumTag(pStack);
if (datumTag != null) {
var component = SpellDatum.displayFromNBT(datumTag);
pTooltipComponents.add(new TranslatableComponent("hexcasting.spelldata.onitem", component));
if (pIsAdvanced.isAdvanced()) {
pTooltipComponents.add(new TextComponent("").append(NbtUtils.toPrettyComponent(datumTag)));
}
} else if (NBTHelper.hasString(pStack, DataHolderItem.TAG_OVERRIDE_VISUALLY)) {
pTooltipComponents.add(new TranslatableComponent("hexcasting.spelldata.onitem",
new TranslatableComponent("hexcasting.spelldata.anything").withStyle(ChatFormatting.LIGHT_PURPLE)));
}
}
}

View file

@ -3,6 +3,7 @@ package at.petrak.hexcasting.api.item;
import at.petrak.hexcasting.api.spell.iota.Iota;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import java.util.List;
@ -13,6 +14,7 @@ import java.util.List;
* On both the Forge and Fabric sides, the registry will be scanned for all items which implement this interface,
* and the appropriate cap/CC will be attached.
*/
@ApiStatus.OverrideOnly
public interface HexHolderItem extends MediaHolderItem {
boolean canDrawManaFromInventory(ItemStack stack);

View file

@ -0,0 +1,59 @@
package at.petrak.hexcasting.api.item;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.ApiStatus;
/**
* This interface should not be accessed direc
*/
@ApiStatus.OverrideOnly
public interface ManaHolderItem {
int getMana(ItemStack stack);
int getMaxMana(ItemStack stack);
void setMana(ItemStack stack, int mana);
boolean manaProvider(ItemStack stack);
boolean canRecharge(ItemStack stack);
default float getManaFullness(ItemStack stack) {
int max = getMaxMana(stack);
if (max == 0) {
return 0;
}
return (float) getMana(stack) / (float) max;
}
default int withdrawMana(ItemStack stack, int cost, boolean simulate) {
var manaHere = getMana(stack);
if (cost < 0) {
cost = manaHere;
}
if (!simulate) {
var manaLeft = manaHere - cost;
setMana(stack, manaLeft);
}
return Math.min(cost, manaHere);
}
default int insertMana(ItemStack stack, int amount, boolean simulate) {
var manaHere = getMana(stack);
int emptySpace = getMaxMana(stack) - manaHere;
if (emptySpace <= 0) {
return 0;
}
if (amount < 0) {
amount = emptySpace;
}
int inserting = Math.min(amount, emptySpace);
if (!simulate) {
var newMana = manaHere + inserting;
setMana(stack, newMana);
}
return inserting;
}
}

View file

@ -0,0 +1,99 @@
package at.petrak.hexcasting.api.misc;
import at.petrak.hexcasting.api.addldata.ManaHolder;
import at.petrak.hexcasting.api.spell.casting.CastingContext;
import at.petrak.hexcasting.api.spell.casting.CastingHarness;
import com.google.common.collect.Lists;
import net.minecraft.util.ToFloatFunction;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
public class DiscoveryHandlers {
private static final List<Predicate<Player>> HAS_LENS_PREDICATE = new ArrayList<>();
private static final List<Function<CastingHarness, List<ManaHolder>>> MANA_HOLDER_DISCOVERY = new ArrayList<>();
private static final List<ToFloatFunction<Player>> GRID_SCALE_MODIFIERS = new ArrayList<>();
private static final List<Function<CastingContext, List<ItemStack>>> ITEM_SLOT_DISCOVERER = new ArrayList<>();
private static final List<Function<CastingContext, List<ItemStack>>> OPERATIVE_SLOT_DISCOVERER = new ArrayList<>();
private static final List<BiFunction<Player, String, ItemStack>> DEBUG_DISCOVERER = new ArrayList<>();
public static boolean hasLens(Player player) {
for (var predicate : HAS_LENS_PREDICATE) {
if (predicate.test(player)) {
return true;
}
}
return false;
}
public static List<ManaHolder> collectManaHolders(CastingHarness harness) {
List<ManaHolder> holders = Lists.newArrayList();
for (var discoverer : MANA_HOLDER_DISCOVERY) {
holders.addAll(discoverer.apply(harness));
}
return holders;
}
public static float gridScaleModifier(Player player) {
float mod = 1;
for (var modifier : GRID_SCALE_MODIFIERS) {
mod *= modifier.apply(player);
}
return mod;
}
public static List<ItemStack> collectItemSlots(CastingContext ctx) {
List<ItemStack> stacks = Lists.newArrayList();
for (var discoverer : ITEM_SLOT_DISCOVERER) {
stacks.addAll(discoverer.apply(ctx));
}
return stacks;
}
public static List<ItemStack> collectOperableSlots(CastingContext ctx) {
List<ItemStack> stacks = Lists.newArrayList();
for (var discoverer : OPERATIVE_SLOT_DISCOVERER) {
stacks.addAll(discoverer.apply(ctx));
}
return stacks;
}
public static ItemStack findDebugItem(Player player, String type) {
for (var discoverer : DEBUG_DISCOVERER) {
var stack = discoverer.apply(player, type);
if (!stack.isEmpty()) {
return stack;
}
}
return ItemStack.EMPTY;
}
public static void addLensPredicate(Predicate<Player> predicate) {
HAS_LENS_PREDICATE.add(predicate);
}
public static void addManaHolderDiscoverer(Function<CastingHarness, List<ManaHolder>> discoverer) {
MANA_HOLDER_DISCOVERY.add(discoverer);
}
public static void addGridScaleModifier(ToFloatFunction<Player> modifier) {
GRID_SCALE_MODIFIERS.add(modifier);
}
public static void addItemSlotDiscoverer(Function<CastingContext, List<ItemStack>> discoverer) {
ITEM_SLOT_DISCOVERER.add(discoverer);
}
public static void addOperativeSlotDiscoverer(Function<CastingContext, List<ItemStack>> discoverer) {
OPERATIVE_SLOT_DISCOVERER.add(discoverer);
}
public static void addDebugItemDiscoverer(BiFunction<Player, String, ItemStack> discoverer) {
DEBUG_DISCOVERER.add(discoverer);
}
}

View file

@ -43,6 +43,8 @@ public class HexConfig {
boolean DEFAULT_INVERT_SPELLBOOK_SCROLL = false;
boolean DEFAULT_INVERT_ABACUS_SCROLL = false;
double DEFAULT_GRID_SNAP_THRESHOLD = 0.5;
boolean DEFAULT_INVERT_SPELLBOOK_SCROLL = false;
boolean DEFAULT_INVERT_ABACUS_SCROLL = false;
}
public interface ServerConfigAccess {

View file

@ -12,6 +12,7 @@ public class HexItemTags {
public static final TagKey<Item> EDIFIED_PLANKS = create("edified_planks");
public static final TagKey<Item> STAVES = create("staves");
public static final TagKey<Item> PHIAL_BASE = create("phial_base");
public static final TagKey<Item> GRANTS_ROOT_ADVANCEMENT = create("grants_root_advancement");
public static TagKey<Item> create(String name) {
return create(modLoc(name));

View file

@ -1,9 +1,15 @@
package at.petrak.hexcasting.api.spell
import at.petrak.hexcasting.api.PatternRegistry
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.casting.SpellContinuation
import at.petrak.hexcasting.api.utils.asTranslatedComponent
import at.petrak.hexcasting.api.utils.lightPurple
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import at.petrak.hexcasting.api.spell.iota.Iota
import net.minecraft.world.phys.Vec3
import java.text.DecimalFormat
/**
* Manipulates the stack in some way, usually by popping some number of values off the stack
@ -46,6 +52,11 @@ interface Action {
*/
val causesBlindDiversion: Boolean get() = this is SpellAction
/**
* The component for displaying this pattern's name. Override for dynamic patterns.
*/
val displayName: Component get() = "hexcasting.spell.${PatternRegistry.lookupPattern(this)}".asTranslatedComponent.lightPurple
companion object {
// I see why vzakii did this: you can't raycast out to infinity!
const val MAX_DISTANCE: Double = 32.0
@ -63,6 +74,20 @@ interface Action {
override fun execute(args: List<Iota>, ctx: CastingContext): List<Iota> =
listOf(x)
}
private val DOUBLE_FORMATTER = DecimalFormat("####.####")
@JvmStatic
fun makeConstantOp(x: Double, key: ResourceLocation): Operator = object : ConstManaOperator {
override val argc: Int
get() = 0
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): List<SpellDatum<*>> =
x.asSpellResult
override val displayName: Component
get() = "hexcasting.spell.$key".asTranslatedComponent(DOUBLE_FORMATTER.format(x)).lightPurple
}
}
}

View file

@ -0,0 +1,288 @@
package at.petrak.hexcasting.api.spell
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.math.HexPattern
import at.petrak.hexcasting.api.spell.mishaps.MishapInvalidSpellDatumType
import at.petrak.hexcasting.api.utils.*
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.ListTag
import net.minecraft.nbt.NbtUtils
import net.minecraft.nbt.Tag
import net.minecraft.network.chat.Component
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.Entity
import net.minecraft.world.phys.Vec3
import java.util.*
/**
* Data allowed into a spell.
*
* We use the following types:
* * [Entity]
* * [Double]
* * [Vec3] as both position and (when normalized) direction
* * [Widget]; [Widget.NULL] is used as our null value
* * [SpellList]
* * [HexPattern]! Yes, we have meta-evaluation everyone.
* The constructor guarantees we won't pass a type that isn't one of those types.
*
*
*/
class SpellDatum<T : Any> private constructor(val payload: T) {
val clazz: Class<T> = payload.javaClass
fun serializeToNBT(): CompoundTag = this.serializeToNBTWithDepthCheck(0, 0)?.first
?: NBTBuilder { TAG_WIDGET %= Widget.GARBAGE.name }
// The second int returned is the number of datums contained in this one.
fun serializeToNBTWithDepthCheck(depth: Int, total: Int): Pair<CompoundTag, Int>? {
if (total > MAX_SERIALIZATION_TOTAL)
return null
val tag = NBTBuilder {
when (val pl = payload) {
is SpellList -> {
// handle it specially!
if (depth + 1 > MAX_SERIALIZATION_DEPTH) {
return null
}
val outList = ListTag()
var total1 = total + 1 // make mutable and include the list itself
for (elt in pl) {
val (t, subtotal) = elt.serializeToNBTWithDepthCheck(depth + 1, total1) ?: return null
total1 = subtotal
outList.add(t)
}
return Pair(
NBTBuilder { TAG_LIST %= outList },
total1
)
}
is Entity -> TAG_ENTITY %= compound {
TAG_ENTITY_UUID %= NbtUtils.createUUID(pl.uuid)
TAG_ENTITY_NAME_CHEATY %= Component.Serializer.toJson(pl.displayName)
}
is Double -> TAG_DOUBLE %= pl
is Vec3 -> TAG_VEC3 %= pl.serializeToNBT()
is Widget -> TAG_WIDGET %= pl.name
is HexPattern -> TAG_PATTERN %= pl.serializeToNBT()
else -> throw RuntimeException("cannot serialize $pl because it is of type ${pl.javaClass.canonicalName} which is not serializable")
}
}
return Pair(tag, total + 1)
}
override fun toString(): String =
buildString {
append("SpellDatum[")
append(this@SpellDatum.payload.toString())
append(']')
}
override fun equals(other: Any?): Boolean {
return other is SpellDatum<*> && other.payload == payload
}
override fun hashCode(): Int {
return Objects.hash(clazz, this.payload)
}
fun display(): Component {
val nbt = this.serializeToNBT()
return displayFromNBT(nbt)
}
fun getType(): DatumType =
when (this.payload) {
is Entity -> DatumType.ENTITY
is Widget -> DatumType.WIDGET
is SpellList -> DatumType.LIST
is HexPattern -> DatumType.PATTERN
is Double -> DatumType.DOUBLE
is Vec3 -> DatumType.VEC
else -> DatumType.OTHER
}
companion object {
const val MAX_SERIALIZATION_DEPTH = 256
const val MAX_SERIALIZATION_TOTAL = 1024
@JvmStatic
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
fun make(payload: Any): SpellDatum<*> =
if (payload is SpellDatum<*>) {
payload
} else if (payload is List<*>) {
SpellDatum(SpellList.LList(0, payload.map {
when (it) {
null -> make(Widget.NULL)
else -> make(it)
}
}))
} else if (payload is Vec3) {
SpellDatum(
Vec3(
fixNAN(payload.x),
fixNAN(payload.y),
fixNAN(payload.z),
)
)
} else if (isValidType(payload)) {
SpellDatum(payload)
} else if (payload is java.lang.Double) {
// Check to see if it's a java *boxed* double, for when we call this from Java
val num = payload.toDouble()
SpellDatum(fixNAN(num))
} else {
throw MishapInvalidSpellDatumType(payload)
}
@JvmStatic
fun fromNBT(nbt: CompoundTag, world: ServerLevel): SpellDatum<*> {
val keys = nbt.allKeys
if (keys.size != 1)
return SpellDatum(Widget.GARBAGE) // Invalid iota format
return when (val key = keys.iterator().next()) {
TAG_ENTITY -> {
val subtag = nbt.getCompound(key)
val uuid = subtag.getUUID(TAG_ENTITY_UUID) // and throw away name
val entity = world.getEntity(uuid)
// If the entity died or something return Unit
SpellDatum(if (entity == null || !entity.isAlive) Widget.NULL else entity)
}
TAG_DOUBLE -> SpellDatum(nbt.getDouble(key))
TAG_VEC3 -> SpellDatum(vecFromNBT(nbt.getLongArray(key)))
TAG_LIST -> {
SpellDatum(SpellList.fromNBT(nbt.getList(key, Tag.TAG_COMPOUND), world))
}
TAG_WIDGET -> {
SpellDatum(Widget.fromString(nbt.getString(key)))
}
TAG_PATTERN -> {
SpellDatum(HexPattern.fromNBT(nbt.getCompound(TAG_PATTERN)))
}
else -> SpellDatum(Widget.GARBAGE) // Invalid iota type
}
}
@Deprecated(
"use the [Level] overload", ReplaceWith(
"DeserializeFromNBT(nbt, ctx.world)",
"at.petrak.hexcasting.api.spell.SpellDatum.Companion.DeserializeFromNBT"
)
)
@JvmStatic
fun fromNBT(nbt: CompoundTag, ctx: CastingContext): SpellDatum<*> =
fromNBT(nbt, ctx.world)
@JvmStatic
fun displayFromNBT(nbt: CompoundTag): Component {
val keys = nbt.allKeys
val out = "".asTextComponent
if (keys.size != 1)
out += "hexcasting.spelldata.unknown".asTranslatedComponent.white
else {
when (val key = keys.iterator().next()) {
TAG_DOUBLE -> out += String.format(
"%.4f",
nbt.getDouble(TAG_DOUBLE)
).green
TAG_VEC3 -> {
val vec = vecFromNBT(nbt.getLongArray(key))
// the focus color is really more red, but we don't want to show an error-y color
out += String.format(
"(%.2f, %.2f, %.2f)",
vec.x,
vec.y,
vec.z
).lightPurple
}
TAG_LIST -> {
out += "[".white
val arr = nbt.getList(key, Tag.TAG_COMPOUND)
for ((i, subtag) in arr.withIndex()) {
out += displayFromNBT(subtag.asCompound)
if (i != arr.lastIndex) {
out += ", ".white
}
}
out += "]".white
}
TAG_WIDGET -> {
val widget = Widget.fromString(nbt.getString(key))
out += if (widget == Widget.GARBAGE)
"arimfexendrapuse".darkGray.obfuscated
else
widget.toString().darkPurple
}
TAG_PATTERN -> {
val pat = HexPattern.fromNBT(nbt.getCompound(TAG_PATTERN))
var angleDesc = pat.anglesSignature()
if (angleDesc.isNotBlank()) angleDesc = " $angleDesc"
out += "HexPattern(".gold
out += "${pat.startDir}$angleDesc".white
out += ")".gold
}
TAG_ENTITY -> {
val subtag = nbt.getCompound(TAG_ENTITY)
val json = subtag.getString(TAG_ENTITY_NAME_CHEATY)
// handle pre-0.5.0 foci not having the tag
out += Component.Serializer.fromJson(json)?.aqua
?: "hexcasting.spelldata.entity.whoknows".asTranslatedComponent.white
}
else -> {
out += "hexcasting.spelldata.unknown".asTranslatedComponent.white
}
}
}
return out
}
// Set of legal types to go in a spell
val ValidTypes: Set<Class<*>> = setOf(
Entity::class.java,
Double::class.java,
Vec3::class.java,
SpellList::class.java,
Widget::class.java,
HexPattern::class.java,
)
const val TAG_ENTITY = "entity"
const val TAG_DOUBLE = "double"
const val TAG_VEC3 = "vec3"
const val TAG_LIST = "list"
const val TAG_WIDGET = "widget"
const val TAG_PATTERN = "pattern"
const val TAG_ENTITY_UUID = "uuid"
// Also encode the entity's name as a component for the benefit of the client
const val TAG_ENTITY_NAME_CHEATY = "name"
fun <T : Any> isValidType(checkee: T): Boolean =
ValidTypes.any { clazz -> clazz.isAssignableFrom(checkee.javaClass) }
@JvmStatic
fun tagForType(datumType: DatumType): String {
return when (datumType) {
DatumType.ENTITY -> TAG_ENTITY
DatumType.WIDGET -> TAG_WIDGET
DatumType.LIST -> TAG_LIST
DatumType.PATTERN -> TAG_PATTERN
DatumType.DOUBLE -> TAG_DOUBLE
DatumType.VEC -> TAG_VEC3
DatumType.OTHER, DatumType.EMPTY -> ""
}
}
}
}

View file

@ -0,0 +1,28 @@
package at.petrak.hexcasting.api.spell
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.utils.getSafe
/**
* Miscellaneous spell datums used as markers, etc.
*
* They act as operators that push themselves.
*/
enum class Widget : ConstManaOperator {
NULL,
OPEN_PAREN, CLOSE_PAREN, ESCAPE,
GARBAGE;
override val argc: Int
get() = 0
override fun execute(args: List<SpellDatum<*>>, ctx: CastingContext): List<SpellDatum<*>> =
this.asSpellResult
companion object {
@JvmStatic
fun fromString(key: String): Widget {
return values().getSafe(key, GARBAGE)
}
}
}

View file

@ -1,12 +1,14 @@
package at.petrak.hexcasting.api.spell.casting
import at.petrak.hexcasting.api.HexAPI.modLoc
import at.petrak.hexcasting.api.misc.DiscoveryHandlers
import at.petrak.hexcasting.api.mod.HexConfig
import at.petrak.hexcasting.api.spell.Action
import at.petrak.hexcasting.api.spell.mishaps.MishapEntityTooFarAway
import at.petrak.hexcasting.api.spell.mishaps.MishapEvalTooDeep
import at.petrak.hexcasting.api.spell.mishaps.MishapLocationTooFarAway
import at.petrak.hexcasting.api.utils.otherHand
import at.petrak.hexcasting.common.items.magic.ItemCreativeUnlocker
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
@ -14,8 +16,8 @@ import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.InteractionHand
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.GameType
import net.minecraft.world.phys.Vec3
import java.util.function.Predicate
import kotlin.math.min
@ -39,10 +41,14 @@ data class CastingContext(
private val entitiesGivenMotion = mutableSetOf<Entity>()
inline fun getHeldItemToOperateOn(acceptItemIf: (ItemStack) -> Boolean): Pair<ItemStack, InteractionHand> {
val handItem = caster.getItemInHand(castingHand)
if (!acceptItemIf(handItem))
return caster.getItemInHand(otherHand) to otherHand
return handItem to castingHand
val handItem = caster.getItemInHand(otherHand)
if (!acceptItemIf(handItem)) {
val castingItem = caster.getItemInHand(castingHand)
if (acceptItemIf(castingItem)) {
return castingItem to castingHand
}
}
return handItem to otherHand
}
/**
@ -87,7 +93,8 @@ data class CastingContext(
return entitiesGivenMotion.contains(target)
}
fun isVecInWorld(vec: Vec3) = world.isInWorldBounds(BlockPos(vec)) && world.worldBorder.isWithinBounds(vec.x, vec.z, 0.5)
fun isVecInWorld(vec: Vec3) =
world.isInWorldBounds(BlockPos(vec)) && world.worldBorder.isWithinBounds(vec.x, vec.z, 0.5)
fun isVecInRange(vec: Vec3): Boolean {
val sentinel = IXplatAbstractions.INSTANCE.getSentinel(caster)
@ -102,15 +109,12 @@ data class CastingContext(
if (this.spellCircle != null) {
// we use the eye position cause thats where the caster gets their "position" from
val range = this.caster.bbHeight
if (this.spellCircle.activatorAlwaysInRange && vec.distanceToSqr(this.caster.eyePosition) < range * range)
if (this.spellCircle.activatorAlwaysInRange && vec.distanceToSqr(this.caster.eyePosition) <= range * range)
return true
return this.spellCircle.aabb.contains(vec)
}
if (vec.distanceToSqr(this.caster.position()) < Action.MAX_DISTANCE * Action.MAX_DISTANCE)
return true
return false
return vec.distanceToSqr(this.caster.eyePosition) <= Operator.MAX_DISTANCE * Operator.MAX_DISTANCE
}
fun isEntityInWorld(entity: Entity) = isVecInWorld(entity.position())
@ -121,6 +125,12 @@ data class CastingContext(
return isVecInRange(entity.position())
}
fun canEditBlockAt(pos: BlockPos): Boolean {
return this.isVecInRange(Vec3.atCenterOf(pos))
&& this.caster.gameMode.gameModeForPlayer != GameType.ADVENTURE
&& this.world.mayInteract(this.caster, pos)
}
/**
* Return the slot from which to take blocks and items.
*/
@ -129,25 +139,12 @@ data class CastingContext(
// for what purpose i cannot imagine
// http://redditpublic.com/images/b/b2/Items_slot_number.png looks right
// and offhand is 150 Inventory.java:464
fun getOperativeSlot(stackOK: Predicate<ItemStack>): Int? {
val otherHandStack = this.caster.getItemInHand(this.otherHand)
if (stackOK.test(otherHandStack)) {
return when (this.otherHand) {
InteractionHand.MAIN_HAND -> this.caster.inventory.selected
InteractionHand.OFF_HAND -> 150
}
}
val anchorSlot = when (this.castingHand) {
// slot to the right of the wand
InteractionHand.MAIN_HAND -> (this.caster.inventory.selected + 1) % 9
// first hotbar slot
InteractionHand.OFF_HAND -> 0
}
for (delta in 0 until 9) {
val slot = (anchorSlot + delta) % 9
val stack = this.caster.inventory.getItem(slot)
fun getOperativeSlot(stackOK: Predicate<ItemStack>): ItemStack? {
val operable = DiscoveryHandlers.collectOperableSlots(this)
for (stack in operable) {
if (stackOK.test(stack)) {
return slot
return stack
}
}
return null
@ -158,17 +155,16 @@ data class CastingContext(
* Return whether the withdrawal was successful.
*/
// https://github.com/VazkiiMods/Psi/blob/master/src/main/java/vazkii/psi/common/spell/trick/block/PieceTrickPlaceBlock.java#L143
fun withdrawItem(item: Item, count: Int, actuallyRemove: Boolean): Boolean {
fun withdrawItem(item: ItemStack, count: Int, actuallyRemove: Boolean): Boolean {
if (this.caster.isCreative) return true
val inv = this.caster.inventory
val operativeItem = item.copy()
// TODO: withdraw from ender chest given a specific ender charm?
val stacksToExamine = inv.items.toMutableList().apply { removeAt(inv.selected) }.asReversed().toMutableList()
stacksToExamine.addAll(inv.offhand)
stacksToExamine.add(inv.getSelected())
val stacksToExamine = DiscoveryHandlers.collectItemSlots(this)
fun matches(stack: ItemStack): Boolean =
!stack.isEmpty && stack.`is`(item)
!stack.isEmpty && ItemStack.isSameItemSameTags(operativeItem, stack)
val presentCount = stacksToExamine.fold(0) { acc, stack ->
acc + if (matches(stack)) stack.count else 0
@ -210,4 +206,32 @@ data class CastingContext(
val advs = this.caster.advancements
return advs.getOrStartProgress(adv!!).isDone
}
val debugPatterns: Boolean by lazy {
!DiscoveryHandlers.findDebugItem(this.caster, ItemCreativeUnlocker.DISPLAY_PATTERNS).isEmpty
}
companion object {
init {
DiscoveryHandlers.addItemSlotDiscoverer {
val inv = it.caster.inventory
inv.items.toMutableList().apply { removeAt(inv.selected) }.asReversed().toMutableList().apply {
addAll(inv.offhand)
add(inv.getSelected())
}
}
DiscoveryHandlers.addOperativeSlotDiscoverer {
val slots = mutableListOf<ItemStack>()
val anchorSlot = if (it.castingHand == InteractionHand.MAIN_HAND) (it.caster.inventory.selected + 1) % 9 else 0
slots.add(it.caster.getItemInHand(it.otherHand))
for (delta in 0 until 9) {
val slot = (anchorSlot + delta) % 9
slots.add(it.caster.inventory.getItem(slot))
}
slots
}
}
}
}

View file

@ -3,6 +3,7 @@ package at.petrak.hexcasting.api.spell.casting
import at.petrak.hexcasting.api.PatternRegistry
import at.petrak.hexcasting.api.advancements.HexAdvancementTriggers
import at.petrak.hexcasting.api.block.circle.BlockEntityAbstractImpetus
import at.petrak.hexcasting.api.misc.DiscoveryHandlers
import at.petrak.hexcasting.api.misc.FrozenColorizer
import at.petrak.hexcasting.api.misc.HexDamageSources
import at.petrak.hexcasting.api.mod.HexConfig
@ -18,9 +19,8 @@ import at.petrak.hexcasting.api.spell.math.HexDir
import at.petrak.hexcasting.api.spell.math.HexPattern
import at.petrak.hexcasting.api.spell.mishaps.*
import at.petrak.hexcasting.api.utils.*
import at.petrak.hexcasting.common.items.magic.ItemCreativeUnlocker
import at.petrak.hexcasting.common.lib.HexIotaTypes
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.Util
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.Tag
import net.minecraft.resources.ResourceLocation
@ -53,6 +53,30 @@ class CastingHarness private constructor(
*/
fun executeIota(iota: Iota, world: ServerLevel): ControllerInfo = executeIotas(listOf(iota), world)
private fun displayPattern(pattern: Operator?, iota: SpellDatum<*>) {
if (this.ctx.debugPatterns) {
this.ctx.caster.sendMessage(pattern?.displayName ?: iota.display(), Util.NIL_UUID)
}
}
private fun getOperatorForPattern(iota: SpellDatum<*>, world: ServerLevel): Operator? {
if (iota.getType() == DatumType.PATTERN)
return PatternRegistry.matchPattern(iota.payload as HexPattern, world)
return null
}
private fun getPatternForFrame(frame: ContinuationFrame): HexPattern? {
if (frame !is ContinuationFrame.Evaluate) return null
return frame.list.car.payload as? HexPattern
}
private fun getOperatorForFrame(frame: ContinuationFrame, world: ServerLevel): Operator? {
if (frame !is ContinuationFrame.Evaluate) return null
return getOperatorForPattern(frame.list.car, world)
}
/**
* Given a list of iotas, execute them in sequence.
*/
@ -66,7 +90,23 @@ class CastingHarness private constructor(
// Take the top of the continuation stack...
val next = continuation.frame
// ...and execute it.
val result = next.evaluate(continuation.next, world, this)
val result = try {
next.evaluate(continuation.next, world, this)
} catch (mishap: Mishap) {
val pattern = getPatternForFrame(next)
val operator = getOperatorForFrame(next, world)
CastResult(
continuation,
null,
mishap.resolutionType(ctx),
listOf(
OperatorSideEffect.DoMishap(
mishap,
Mishap.Context(pattern ?: HexPattern(HexDir.WEST), operator)
)
)
)
}
// Then write all pertinent data back to the harness for the next iteration.
if (result.newData != null) {
this.applyFunctionalData(result.newData)
@ -124,7 +164,7 @@ class CastingHarness private constructor(
listOf(
OperatorSideEffect.DoMishap(
mishap,
Mishap.Context((iota as? PatternIota)?.pattern ?: HexPattern(HexDir.WEST), null)
Mishap.Context((iota as? PatternIota)?.pattern ?: HexPattern(HexDir.WEST), getOperatorForPattern(iota, world))
)
),
)
@ -137,7 +177,7 @@ class CastingHarness private constructor(
listOf(
OperatorSideEffect.DoMishap(
MishapError(exception),
Mishap.Context((iota as? PatternIota)?.pattern ?: HexPattern(HexDir.WEST), null)
Mishap.Context((iota as? PatternIota)?.pattern ?: HexPattern(HexDir.WEST), getOperatorForPattern(iota, world))
)
)
)
@ -174,6 +214,7 @@ class CastingHarness private constructor(
var ravenmindChanged = false
if (!unenlightened || pattern.alwaysProcessGreatSpell) {
displayPattern(pattern, SpellDatum.make(newPat))
val result = pattern.operate(
continuation,
this.stack.toMutableList(),
@ -226,7 +267,7 @@ class CastingHarness private constructor(
continuation,
null,
mishap.resolutionType(ctx),
listOf(OperatorSideEffect.DoMishap(mishap, Mishap.Context(newPat, actionIdPair?.second))),
listOf(OperatorSideEffect.DoMishap(mishap, Mishap.Context(newPat, actionIdPair?.first))),
)
} catch (exception: Exception) {
exception.printStackTrace()
@ -237,7 +278,7 @@ class CastingHarness private constructor(
listOf(
OperatorSideEffect.DoMishap(
MishapError(exception),
Mishap.Context(newPat, actionIdPair?.second)
Mishap.Context(newPat, actionIdPair?.first)
)
)
)
@ -302,7 +343,7 @@ class CastingHarness private constructor(
}
val sig = iota.pattern.anglesSignature()
if (this.parenCount > 0) {
val out = if (this.parenCount > 0) {
if (this.escapeNext) {
val newParens = this.parenthesized.toMutableList()
newParens.add(iota)
@ -387,6 +428,11 @@ class CastingHarness private constructor(
null
}
}
if (out != null) {
displayPattern(operator, iota)
}
return out
}
/**
@ -422,21 +468,21 @@ class CastingHarness private constructor(
val casterHexHolder = IXplatAbstractions.INSTANCE.findHexHolder(casterStack)
val hexHolderDrawsFromInventory = if (casterHexHolder != null) {
if (casterManaHolder != null) {
val manaAvailable = casterManaHolder.media
val manaAvailable = casterManaHolder.withdrawMana(-1, true)
val manaToTake = min(costLeft, manaAvailable)
if (!fake) casterManaHolder.media = manaAvailable - manaToTake
if (!fake) casterManaHolder.withdrawMana(manaToTake, false)
costLeft -= manaToTake
}
casterHexHolder.canDrawManaFromInventory()
} else {
false
}
if (casterStack.`is`(HexItemTags.STAVES) || hexHolderDrawsFromInventory) {
val manableItems = this.ctx.caster.inventory.items
.filter(::isManaItem)
if (casterStack.`is`(HexItemTags.WANDS) || hexHolderDrawsFromInventory) {
val manaSources = DiscoveryHandlers.collectManaHolders(this)
.sortedWith(Comparator(::compareManaItem).reversed())
for (stack in manableItems) {
costLeft -= extractMana(stack, costLeft, simulate = fake && !ItemCreativeUnlocker.isDebug(stack))
for (source in manaSources) {
costLeft -= extractMana(source, costLeft, simulate = fake)
if (costLeft <= 0)
break
}
@ -448,13 +494,17 @@ class CastingHarness private constructor(
val manaAbleToCastFromHP = this.ctx.caster.health * manaToHealth
val manaToActuallyPayFor = min(manaAbleToCastFromHP.toInt(), costLeft)
if (!fake) {
HexAdvancementTriggers.OVERCAST_TRIGGER.trigger(this.ctx.caster, manaToActuallyPayFor)
this.ctx.caster.awardStat(HexStatistics.MANA_OVERCASTED, manaCost - costLeft)
costLeft -= if (!fake) {
Mishap.trulyHurt(this.ctx.caster, HexDamageSources.OVERCAST, healthtoRemove.toFloat())
val actuallyTaken = (manaAbleToCastFromHP - (this.ctx.caster.health * manaToHealth)).toInt()
HexAdvancementTriggers.OVERCAST_TRIGGER.trigger(this.ctx.caster, actuallyTaken)
this.ctx.caster.awardStat(HexStatistics.MANA_OVERCASTED, manaCost - costLeft)
actuallyTaken
} else {
manaToActuallyPayFor
}
costLeft -= manaToActuallyPayFor
}
}
}
@ -503,6 +553,24 @@ class CastingHarness private constructor(
const val TAG_ESCAPE_NEXT = "escape_next"
const val TAG_PREPACKAGED_COLORIZER = "prepackaged_colorizer"
init {
DiscoveryHandlers.addManaHolderDiscoverer {
it.ctx.caster.inventory.items
.filter(::isManaItem)
.mapNotNull(IXplatAbstractions.INSTANCE::findManaHolder)
}
DiscoveryHandlers.addManaHolderDiscoverer {
it.ctx.caster.inventory.armor
.filter(::isManaItem)
.mapNotNull(IXplatAbstractions.INSTANCE::findManaHolder)
}
DiscoveryHandlers.addManaHolderDiscoverer {
it.ctx.caster.inventory.offhand
.filter(::isManaItem)
.mapNotNull(IXplatAbstractions.INSTANCE::findManaHolder)
}
}
@JvmStatic
fun fromNBT(nbt: CompoundTag, ctx: CastingContext): CastingHarness {
return try {

View file

@ -149,7 +149,9 @@ sealed interface ContinuationFrame {
// If we still have data to process...
val (stackTop, newCont) = if (data.nonEmpty) {
// Push the next datum to the top of the stack,
// Increment the evaluation depth,
harness.ctx.incDepth()
// push the next datum to the top of the stack,
data.car to continuation
// put the next Thoth object back on the stack for the next Thoth cycle,
.pushFrame(ForEach(data.cdr, code, stack, acc))

View file

@ -1,6 +1,6 @@
package at.petrak.hexcasting.api.spell.casting
import java.util.*
import at.petrak.hexcasting.api.utils.getSafe
enum class ResolvedPatternType(val color: Int, val fadeColor: Int, val success: Boolean) {
UNRESOLVED(0x7f7f7f, 0xcccccc, false),
@ -12,8 +12,7 @@ enum class ResolvedPatternType(val color: Int, val fadeColor: Int, val success:
companion object {
@JvmStatic
fun fromString(key: String): ResolvedPatternType {
val lowercaseKey = key.lowercase(Locale.ROOT)
return values().firstOrNull { it.name.lowercase(Locale.ROOT) == lowercaseKey } ?: UNRESOLVED
return values().getSafe(key)
}
}
}

View file

@ -1,5 +1,6 @@
package at.petrak.hexcasting.api.spell.math
import at.petrak.hexcasting.api.HexAPI
import java.util.*
import kotlin.random.Random
@ -8,11 +9,31 @@ object EulerPathFinder {
* Find an alternative way to draw the given pattern, based on a random seed.
*/
@JvmStatic
fun findAltDrawing(original: HexPattern, seed: Long): HexPattern {
@JvmOverloads
fun findAltDrawing(original: HexPattern, seed: Long, rule: (HexPattern) -> Boolean = { true }): HexPattern {
// http://www.graph-magics.com/articles/euler.php
val rand = Random(seed)
// Don't try for too long, in case all paths are exhausted.
// Unlikely to ever actually reach this limit, and can only happen if the same pattern
// is registered both as a Great Pattern and as a special handler or regular pattern,
// or if the random seed has some sort of strange repeating property to it.
var iterationsLeft = 100
var path: HexPattern
while (iterationsLeft > 0) {
iterationsLeft--
path = walkPath(original, rand)
if (rule(path)) {
return path
}
}
HexAPI.LOGGER.warn("Didn't find alternate path for {} in time", original)
return original
}
private fun walkPath(original: HexPattern, rand: Random): HexPattern {
val graph = toGraph(original)
val oddNodes = graph.filter { (_, dirs) -> dirs.size % 2 == 1 }
var current: HexCoord = when (oddNodes.size) {

View file

@ -1,6 +1,6 @@
package at.petrak.hexcasting.api.spell.math
import java.util.*
import at.petrak.hexcasting.api.utils.getSafe
enum class HexDir {
NORTH_EAST, EAST, SOUTH_EAST, SOUTH_WEST, WEST, NORTH_WEST;
@ -28,8 +28,7 @@ enum class HexDir {
companion object {
@JvmStatic
fun fromString(key: String): HexDir {
val lowercaseKey = key.lowercase(Locale.ROOT)
return values().firstOrNull { it.name.lowercase(Locale.ROOT) == lowercaseKey } ?: WEST
return values().getSafe(key, WEST)
}
}
}

View file

@ -3,6 +3,7 @@ package at.petrak.hexcasting.api.spell.math
import at.petrak.hexcasting.api.utils.NBTBuilder
import at.petrak.hexcasting.api.utils.coordToPx
import at.petrak.hexcasting.api.utils.findCenter
import at.petrak.hexcasting.api.utils.getSafe
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.Tag
import net.minecraft.world.phys.Vec2
@ -136,8 +137,8 @@ data class HexPattern(public val startDir: HexDir, public val angles: MutableLis
@JvmStatic
fun fromNBT(tag: CompoundTag): HexPattern {
val startDir = HexDir.values()[tag.getByte(TAG_START_DIR).toInt()]
val angles = tag.getByteArray(TAG_ANGLES).map { HexAngle.values()[it.toInt()] }
val startDir = HexDir.values().getSafe(tag.getByte(TAG_START_DIR))
val angles = tag.getByteArray(TAG_ANGLES).map(HexAngle.values()::getSafe)
return HexPattern(startDir, angles.toMutableList())
}

View file

@ -2,6 +2,7 @@ package at.petrak.hexcasting.api.spell.mishaps
import at.petrak.hexcasting.api.misc.FrozenColorizer
import at.petrak.hexcasting.api.mod.HexItemTags
import at.petrak.hexcasting.api.spell.Operator
import at.petrak.hexcasting.api.spell.ParticleSpray
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.casting.ResolvedPatternType
@ -10,11 +11,11 @@ import at.petrak.hexcasting.api.spell.math.HexPattern
import at.petrak.hexcasting.api.utils.asTranslatedComponent
import at.petrak.hexcasting.api.utils.lightPurple
import at.petrak.hexcasting.common.lib.HexItems
import at.petrak.hexcasting.ktxt.lastHurt
import at.petrak.hexcasting.ktxt.*
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.Util
import net.minecraft.core.BlockPos
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.InteractionHand
import net.minecraft.world.damagesource.DamageSource
import net.minecraft.world.entity.LivingEntity
@ -53,8 +54,8 @@ abstract class Mishap : Throwable() {
protected fun error(stub: String, vararg args: Any): Component =
"hexcasting.mishap.$stub".asTranslatedComponent(*args)
protected fun actionName(action: ResourceLocation?): Component =
"hexcasting.spell.${action ?: "unknown"}".asTranslatedComponent.lightPurple
protected fun actionName(action: Operator?): Component =
action?.displayName ?: "hexcasting.spell.null".asTranslatedComponent.lightPurple
protected fun yeetHeldItemsTowards(ctx: CastingContext, targetPos: Vec3) {
// Knock the player's items out of their hands
@ -75,6 +76,8 @@ abstract class Mishap : Throwable() {
protected fun yeetHeldItem(ctx: CastingContext, hand: InteractionHand) {
val item = ctx.caster.getItemInHand(hand).copy()
if (hand == ctx.castingHand && IXplatAbstractions.INSTANCE.findHexHolder(item) != null)
return
ctx.caster.setItemInHand(hand, ItemStack.EMPTY)
val delta = ctx.caster.lookAngle.scale(0.5)
@ -98,10 +101,11 @@ abstract class Mishap : Throwable() {
return ctx.world.getBlockState(pos).block.name
}
data class Context(val pattern: HexPattern, val action: ResourceLocation?)
data class Context(val pattern: HexPattern, val action: Operator?)
companion object {
fun trulyHurt(entity: LivingEntity, source: DamageSource, amount: Float) {
val targetHealth = entity.health - amount
if (entity.invulnerableTime > 10) {
val lastHurt = entity.lastHurt
if (lastHurt < amount)
@ -109,7 +113,29 @@ abstract class Mishap : Throwable() {
else
entity.lastHurt -= amount
}
entity.hurt(source, amount)
if (!entity.hurt(source, amount) &&
!entity.isInvulnerableTo(source) &&
!entity.level.isClientSide &&
!entity.isDeadOrDying) {
// Ok, if you REALLY don't want to play nice...
entity.health = targetHealth
entity.markHurt()
if (entity.isDeadOrDying) {
if (!entity.checkTotemDeathProtection(source)) {
val sound = entity.deathSoundAccessor
if (sound != null) {
entity.playSound(sound, entity.soundVolumeAccessor, entity.voicePitch)
}
entity.die(source)
}
} else {
entity.playHurtSound(source)
}
entity.setHurtWithStamp(source, entity.level.gameTime)
}
}
}
}

View file

@ -0,0 +1,33 @@
package at.petrak.hexcasting.api.spell.mishaps
import at.petrak.hexcasting.api.misc.FrozenColorizer
import at.petrak.hexcasting.api.spell.SpellDatum
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.utils.aqua
import at.petrak.hexcasting.api.utils.asTranslatedComponent
import net.minecraft.network.chat.Component
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.item.DyeColor
class MishapBadEntity(val entity: Entity, val wanted: Component) : Mishap() {
override fun accentColor(ctx: CastingContext, errorCtx: Context): FrozenColorizer =
dyeColor(DyeColor.BROWN)
override fun execute(ctx: CastingContext, errorCtx: Context, stack: MutableList<SpellDatum<*>>) {
yeetHeldItemsTowards(ctx, entity.position())
}
override fun errorMessage(ctx: CastingContext, errorCtx: Context) =
error("bad_entity", actionName(errorCtx.action), wanted, entity.displayName.plainCopy().aqua)
companion object {
@JvmStatic
fun of(entity: Entity, stub: String): Mishap {
val component = "hexcasting.mishap.bad_item.$stub".asTranslatedComponent
if (entity is ItemEntity)
return MishapBadItem(entity, component)
return MishapBadEntity(entity, component)
}
}
}

View file

@ -3,6 +3,7 @@ package at.petrak.hexcasting.api.spell.mishaps
import at.petrak.hexcasting.api.misc.FrozenColorizer
import at.petrak.hexcasting.api.spell.iota.Iota
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.utils.aqua
import net.minecraft.world.entity.Entity
import net.minecraft.world.item.DyeColor
@ -15,5 +16,5 @@ class MishapImmuneEntity(val entity: Entity) : Mishap() {
}
override fun errorMessage(ctx: CastingContext, errorCtx: Context) =
error("immune_entity", actionName(errorCtx.action), entity.displayName)
error("immune_entity", actionName(errorCtx.action), entity.displayName.plainCopy().aqua)
}

View file

@ -3,14 +3,31 @@ package at.petrak.hexcasting.api.spell.mishaps
import at.petrak.hexcasting.api.misc.FrozenColorizer
import at.petrak.hexcasting.api.spell.iota.Iota
import at.petrak.hexcasting.api.spell.casting.CastingContext
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.DyeColor
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.enchantment.EnchantmentHelper
class MishapNoSpellCircle : Mishap() {
override fun accentColor(ctx: CastingContext, errorCtx: Context): FrozenColorizer =
dyeColor(DyeColor.LIGHT_BLUE)
override fun execute(ctx: CastingContext, errorCtx: Context, stack: MutableList<Iota>) {
ctx.caster.inventory.dropAll()
private inline fun dropAll(player: Player, stacks: MutableList<ItemStack>, filter: (ItemStack) -> Boolean = { true }) {
for (index in stacks.indices) {
val item = stacks[index]
if (!item.isEmpty && filter(item)) {
player.drop(item, true, false)
stacks[index] = ItemStack.EMPTY
}
}
}
override fun execute(ctx: CastingContext, errorCtx: Context, stack: MutableList<SpellDatum<*>>) {
dropAll(ctx.caster, ctx.caster.inventory.items)
dropAll(ctx.caster, ctx.caster.inventory.offhand)
dropAll(ctx.caster, ctx.caster.inventory.armor) {
!EnchantmentHelper.hasBindingCurse(it)
}
}
override fun errorMessage(ctx: CastingContext, errorCtx: Context) =

View file

@ -1,5 +1,9 @@
package at.petrak.hexcasting.api.spell.mishaps
import at.petrak.hexcasting.api.misc.FrozenColorizer
import at.petrak.hexcasting.api.spell.SpellDatum
import at.petrak.hexcasting.api.spell.Widget
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.misc.FrozenColorizer
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.iota.Iota
@ -15,5 +19,8 @@ class MishapNotEnoughArgs(val expected: Int, val got: Int) : Mishap() {
}
override fun errorMessage(ctx: CastingContext, errorCtx: Context) =
error("not_enough_args", actionName(errorCtx.action), expected, got)
if (got == 0)
error("no_args", actionName(errorCtx.action), expected)
else
error("not_enough_args", actionName(errorCtx.action), expected, got)
}

View file

@ -89,6 +89,18 @@ fun pxToCoord(px: Vec2, size: Float, offset: Vec2): HexCoord {
HexCoord(q, r + (rf + 0.5 * qf).roundToInt())
}
@JvmOverloads
fun <T : Enum<T>> Array<T>.getSafe(key: String, default: T = this[0]): T {
val lowercaseKey = key.lowercase(Locale.ROOT)
return firstOrNull { it.name.lowercase(Locale.ROOT) == lowercaseKey } ?: default
}
@JvmOverloads
fun <T : Enum<T>> Array<T>.getSafe(index: Byte, default: T = this[0]) = getSafe(index.toInt(), default)
@JvmOverloads
fun <T : Enum<T>> Array<T>.getSafe(index: Int, default: T = this[0]) = if (index in indices) this[index] else default
fun String.withStyle(op: (Style) -> Style): MutableComponent = asTextComponent.withStyle(op)
fun String.withStyle(style: Style): MutableComponent = asTextComponent.withStyle(style)
fun String.withStyle(formatting: ChatFormatting): MutableComponent = asTextComponent.withStyle(formatting)

View file

@ -2,6 +2,7 @@
package at.petrak.hexcasting.api.utils
import at.petrak.hexcasting.api.addldata.ManaHolder
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.util.Mth
import net.minecraft.world.item.ItemStack
@ -31,26 +32,38 @@ fun extractMana(
): Int {
val manaHolder = IXplatAbstractions.INSTANCE.findManaHolder(stack) ?: return 0
if (drainForBatteries && !manaHolder.canConstructBattery())
return extractMana(manaHolder, cost, drainForBatteries, simulate)
}
/**
* Extract [cost] mana from [holder]. If [cost] is less than zero, extract all mana instead.
* This may mutate the stack underlying [holder] (and may consume it) unless [simulate] is set.
*
* If [drainForBatteries] is false, this will only consider forms of mana that can be used to make new batteries.
*
* Return the amount of mana extracted. This may be over [cost] if mana is wasted.
*/
fun extractMana(
holder: ManaHolder,
cost: Int = -1,
drainForBatteries: Boolean = false,
simulate: Boolean = false
): Int {
if (drainForBatteries && !holder.canConstructBattery())
return 0
return manaHolder.withdrawMedia(cost, simulate)
return holder.withdrawMana(cost, simulate)
}
/**
* Sorted from least important to most important
*/
fun compareManaItem(astack: ItemStack, bstack: ItemStack): Int {
val aMana = IXplatAbstractions.INSTANCE.findManaHolder(astack)
val bMana = IXplatAbstractions.INSTANCE.findManaHolder(bstack)
fun compareManaItem(aMana: ManaHolder, bMana: ManaHolder): Int {
val priority = aMana.consumptionPriority - bMana.consumptionPriority
if (priority != 0)
return priority
return if (astack.item != bstack.item) {
(aMana?.consumptionPriority ?: 0) - (bMana?.consumptionPriority ?: 0)
} else if (aMana != null && bMana != null) {
aMana.media - bMana.media
} else {
astack.count - bstack.count
}
return aMana.withdrawMana(-1, true) - bMana.withdrawMana(-1, true)
}
fun manaBarColor(mana: Int, maxMana: Int): Int {

View file

@ -1,8 +1,8 @@
package at.petrak.hexcasting.client;
import at.petrak.hexcasting.api.client.ScryingLensOverlayRegistry;
import at.petrak.hexcasting.api.misc.DiscoveryHandlers;
import at.petrak.hexcasting.api.player.Sentinel;
import at.petrak.hexcasting.common.lib.HexItems;
import at.petrak.hexcasting.xplat.IXplatAbstractions;
import com.google.common.collect.Lists;
import com.mojang.blaze3d.platform.GlStateManager;
@ -22,8 +22,6 @@ import net.minecraft.locale.Language;
import net.minecraft.network.chat.FormattedText;
import net.minecraft.network.chat.Style;
import net.minecraft.util.Mth;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
@ -157,22 +155,8 @@ public class HexAdditionalRenderers {
return;
}
boolean foundLens = false;
InteractionHand lensHand = null;
for (var hand : InteractionHand.values()) {
if (player.getItemInHand(hand).is(HexItems.SCRYING_LENS)) {
lensHand = hand;
foundLens = true;
break;
}
}
if (!foundLens && player.getItemBySlot(EquipmentSlot.HEAD).is(HexItems.SCRYING_LENS)) {
foundLens = true;
}
if (!foundLens) {
if (!DiscoveryHandlers.hasLens(player))
return;
}
var hitRes = mc.hitResult;
if (hitRes != null && hitRes.getType() == HitResult.Type.BLOCK) {
@ -180,7 +164,7 @@ public class HexAdditionalRenderers {
var pos = bhr.getBlockPos();
var bs = level.getBlockState(pos);
var lines = ScryingLensOverlayRegistry.getLines(bs, pos, player, level, bhr.getDirection(), lensHand);
var lines = ScryingLensOverlayRegistry.getLines(bs, pos, player, level, bhr.getDirection());
int totalHeight = 8;
List<Pair<ItemStack, List<FormattedText>>> actualLines = Lists.newArrayList();

View file

@ -25,6 +25,7 @@ import net.minecraft.client.color.block.BlockColor;
import net.minecraft.client.color.item.ItemColor;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.Style;
@ -33,11 +34,14 @@ import net.minecraft.util.Mth;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.*;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.ComparatorMode;
import net.minecraft.world.level.block.state.properties.NoteBlockInstrument;
import net.minecraft.world.level.block.state.properties.RailShape;
import net.minecraft.world.level.material.MaterialColor;
import org.jetbrains.annotations.NotNull;
@ -144,15 +148,15 @@ public class RegisterClientStuff {
private static void addScryingLensStuff() {
ScryingLensOverlayRegistry.addPredicateDisplayer(
(state, pos, observer, world, direction, lensHand) -> state.getBlock() instanceof BlockAbstractImpetus,
(lines, state, pos, observer, world, direction, lensHand) -> {
(state, pos, observer, world, direction) -> state.getBlock() instanceof BlockAbstractImpetus,
(lines, state, pos, observer, world, direction) -> {
if (world.getBlockEntity(pos) instanceof BlockEntityAbstractImpetus beai) {
beai.applyScryingLensOverlay(lines, state, pos, observer, world, direction, lensHand);
beai.applyScryingLensOverlay(lines, state, pos, observer, world, direction);
}
});
ScryingLensOverlayRegistry.addDisplayer(Blocks.NOTE_BLOCK,
(lines, state, pos, observer, world, direction, lensHand) -> {
(lines, state, pos, observer, world, direction) -> {
int note = state.getValue(NoteBlock.NOTE);
float rCol = Math.max(0.0F, Mth.sin((note / 24F + 0.0F) * Mth.TWO_PI) * 0.65F + 0.35F);
@ -174,7 +178,7 @@ public class RegisterClientStuff {
});
ScryingLensOverlayRegistry.addDisplayer(HexBlocks.AKASHIC_BOOKSHELF,
(lines, state, pos, observer, world, direction, lensHand) -> {
(lines, state, pos, observer, world, direction) -> {
if (world.getBlockEntity(pos) instanceof BlockEntityAkashicBookshelf tile) {
var iotaTag = tile.getIotaTag();
if (iotaTag != null) {
@ -184,8 +188,20 @@ public class RegisterClientStuff {
}
});
ScryingLensOverlayRegistry.addDisplayer(HexBlocks.AKASHIC_RECORD,
(lines, state, pos, observer, world, direction) -> {
if (world.getBlockEntity(pos) instanceof BlockEntityAkashicRecord tile) {
int count = tile.getCount();
lines.add(new Pair<>(new ItemStack(HexBlocks.AKASHIC_BOOKSHELF), new TranslatableComponent(
"hexcasting.tooltip.lens.akashic.record.count" + (count == 1 ? ".single" : ""),
count
)));
}
});
ScryingLensOverlayRegistry.addDisplayer(Blocks.COMPARATOR,
(lines, state, pos, observer, world, direction, lensHand) -> {
(lines, state, pos, observer, world, direction) -> {
int comparatorValue = ScryingLensOverlayRegistry.getComparatorValue(true);
lines.add(new Pair<>(
new ItemStack(Items.REDSTONE),
@ -200,16 +216,36 @@ public class RegisterClientStuff {
.withStyle(redstoneColor(compare ? 0 : 15))));
});
ScryingLensOverlayRegistry.addDisplayer(Blocks.POWERED_RAIL,
(lines, state, pos, observer, world, direction) -> {
int power = getPoweredRailStrength(world, pos, state);
lines.add(new Pair<>(
new ItemStack(Items.POWERED_RAIL),
new TextComponent(String.valueOf(power))
.withStyle(redstoneColor(power, 9))));
});
ScryingLensOverlayRegistry.addDisplayer(Blocks.REPEATER,
(lines, state, pos, observer, world, direction, lensHand) -> lines.add(new Pair<>(
(lines, state, pos, observer, world, direction) -> lines.add(new Pair<>(
new ItemStack(Items.CLOCK),
Component.literal(String.valueOf(state.getValue(RepeaterBlock.DELAY)))
.withStyle(ChatFormatting.YELLOW))));
ScryingLensOverlayRegistry.addPredicateDisplayer(
(state, pos, observer, world, direction, lensHand) -> state.isSignalSource() && !state.is(
(state, pos, observer, world, direction) -> state.getBlock() instanceof BeehiveBlock,
(lines, state, pos, observer, world, direction) -> {
int count = ScryingLensOverlayRegistry.getBeeValue();
lines.add(new Pair<>(new ItemStack(Items.BEE_NEST), count == -1 ? new TextComponent("") :
new TranslatableComponent(
"hexcasting.tooltip.lens.bee" + (count == 1 ? ".single" : ""),
count
)));
});
ScryingLensOverlayRegistry.addPredicateDisplayer(
(state, pos, observer, world, direction) -> state.isSignalSource() && !state.is(
Blocks.COMPARATOR),
(lines, state, pos, observer, world, direction, lensHand) -> {
(lines, state, pos, observer, world, direction) -> {
int signalStrength = 0;
if (state.getBlock() instanceof RedStoneWireBlock) {
signalStrength = state.getValue(RedStoneWireBlock.POWER);
@ -226,8 +262,8 @@ public class RegisterClientStuff {
});
ScryingLensOverlayRegistry.addPredicateDisplayer(
(state, pos, observer, world, direction, lensHand) -> state.hasAnalogOutputSignal(),
(lines, state, pos, observer, world, direction, lensHand) -> {
(state, pos, observer, world, direction) -> state.hasAnalogOutputSignal(),
(lines, state, pos, observer, world, direction) -> {
int comparatorValue = ScryingLensOverlayRegistry.getComparatorValue(false);
lines.add(
new Pair<>(
@ -242,7 +278,11 @@ public class RegisterClientStuff {
}
private static UnaryOperator<Style> redstoneColor(int power) {
return color(RedStoneWireBlock.getColorForPower(Mth.clamp(power, 0, 15)));
return redstoneColor(power, 15);
}
private static UnaryOperator<Style> redstoneColor(int power, int max) {
return color(RedStoneWireBlock.getColorForPower(Mth.clamp((power * max) / 15, 0, 15)));
}
private static int instrumentColor(NoteBlockInstrument instrument) {
@ -265,7 +305,7 @@ public class RegisterClientStuff {
}
private static void registerDataHolderOverrides(IotaHolderItem item, Predicate<ItemStack> hasIota,
Predicate<ItemStack> isSealed) {
Predicate<ItemStack> isSealed) {
IClientXplatAbstractions.INSTANCE.registerItemProperty((Item) item, ItemFocus.OVERLAY_PRED,
(stack, level, holder, holderID) -> {
if (!hasIota.test(stack) && !NBTHelper.hasString(stack, IotaHolderItem.TAG_OVERRIDE_VISUALLY)) {
@ -278,6 +318,114 @@ public class RegisterClientStuff {
});
}
private static int getPoweredRailStrength(Level level, BlockPos pos, BlockState state) {
if (level.hasNeighborSignal(pos))
return 9;
int positiveValue = findPoweredRailSignal(level, pos, state, true, 0);
int negativeValue = findPoweredRailSignal(level, pos, state, false, 0);
return Math.max(positiveValue, negativeValue);
}
// Copypasta from PoweredRailBlock.class
private static int findPoweredRailSignal(Level level, BlockPos pos, BlockState state, boolean travelPositive, int depth) {
if (depth >= 8) {
return 0;
} else {
int x = pos.getX();
int y = pos.getY();
int z = pos.getZ();
boolean descending = true;
RailShape shape = state.getValue(PoweredRailBlock.SHAPE);
switch(shape) {
case NORTH_SOUTH:
if (travelPositive) {
++z;
} else {
--z;
}
break;
case EAST_WEST:
if (travelPositive) {
--x;
} else {
++x;
}
break;
case ASCENDING_EAST:
if (travelPositive) {
--x;
} else {
++x;
++y;
descending = false;
}
shape = RailShape.EAST_WEST;
break;
case ASCENDING_WEST:
if (travelPositive) {
--x;
++y;
descending = false;
} else {
++x;
}
shape = RailShape.EAST_WEST;
break;
case ASCENDING_NORTH:
if (travelPositive) {
++z;
} else {
--z;
++y;
descending = false;
}
shape = RailShape.NORTH_SOUTH;
break;
case ASCENDING_SOUTH:
if (travelPositive) {
++z;
++y;
descending = false;
} else {
--z;
}
shape = RailShape.NORTH_SOUTH;
}
int power = getPowerFromRail(level, new BlockPos(x, y, z), travelPositive, depth, shape);
if (power > 0) {
return power;
} else if (descending) {
return getPowerFromRail(level, new BlockPos(x, y - 1, z), travelPositive, depth, shape);
} else {
return 0;
}
}
}
private static int getPowerFromRail(Level level, BlockPos pos, boolean travelPositive, int depth, RailShape shape) {
BlockState otherState = level.getBlockState(pos);
if (!otherState.is(Blocks.POWERED_RAIL)) {
return 0;
} else {
RailShape otherShape = otherState.getValue(PoweredRailBlock.SHAPE);
if (shape == RailShape.EAST_WEST && (otherShape == RailShape.NORTH_SOUTH || otherShape == RailShape.ASCENDING_NORTH || otherShape == RailShape.ASCENDING_SOUTH)) {
return 0;
} else if (shape == RailShape.NORTH_SOUTH && (otherShape == RailShape.EAST_WEST || otherShape == RailShape.ASCENDING_EAST || otherShape == RailShape.ASCENDING_WEST)) {
return 0;
} else if (otherState.getValue(PoweredRailBlock.POWERED)) {
return level.hasNeighborSignal(pos) ? 8 - depth : findPoweredRailSignal(level, pos, otherState, travelPositive, depth + 1);
} else {
return 0;
}
}
}
private static void registerScrollOverrides(ItemScroll scroll) {
IClientXplatAbstractions.INSTANCE.registerItemProperty(scroll, ItemScroll.ANCIENT_PREDICATE,
(stack, level, holder, holderID) -> NBTHelper.hasString(stack, ItemScroll.TAG_OP_ID) ? 1f : 0f);

View file

@ -26,6 +26,11 @@ public class ShiftScrollListener {
// not .isCrouching! that fails for players who are not on the ground
// yes, this does work if you remap your sneak key
if (player != null && (player.isShiftKeyDown() || !needsSneaking)) {
// Spectators shouldn't interact with items!
if (player.isSpectator()) {
return false;
}
if (IsScrollableItem(player.getMainHandItem().getItem())) {
mainHandDelta += delta;
return true;

View file

@ -125,7 +125,7 @@ public class WallScrollRenderer extends EntityRenderer<EntityWallScroll> {
if (wallScroll.getShowsStrokeOrder()) {
var spotFrac = 0.8f * wallScroll.blockSize;
var animTime = wallScroll.tickCount;
var animTime = wallScroll.tickCount + partialTicks;
var pointCircuit =
(animTime * HexConfig.client().patternPointSpeedMultiplier()) % (points.size() + 10);
if (pointCircuit < points.size() - 1) {

View file

@ -1,5 +1,6 @@
package at.petrak.hexcasting.client.gui
import at.petrak.hexcasting.api.misc.DiscoveryHandlers
import at.petrak.hexcasting.api.mod.HexConfig
import at.petrak.hexcasting.api.mod.HexItemTags
import at.petrak.hexcasting.api.spell.casting.ControllerInfo
@ -14,6 +15,10 @@ import at.petrak.hexcasting.api.utils.gold
import at.petrak.hexcasting.api.utils.otherHand
import at.petrak.hexcasting.client.*
import at.petrak.hexcasting.client.ktxt.accumulatedScroll
import at.petrak.hexcasting.client.ShiftScrollListener
import at.petrak.hexcasting.client.drawPatternFromPoints
import at.petrak.hexcasting.client.drawSpot
import at.petrak.hexcasting.client.ktxt.accumulatedScroll
import at.petrak.hexcasting.client.sound.GridSoundInstance
import at.petrak.hexcasting.common.lib.HexIotaTypes
import at.petrak.hexcasting.common.lib.HexItems
@ -34,6 +39,10 @@ import net.minecraft.util.Mth
import net.minecraft.world.InteractionHand
import net.minecraft.world.phys.Vec2
import kotlin.math.*
import kotlin.math.atan2
import kotlin.math.roundToInt
import kotlin.math.sign
import kotlin.math.sqrt
class GuiSpellcasting constructor(
private val handOpenedWith: InteractionHand,
@ -61,8 +70,9 @@ class GuiSpellcasting constructor(
this.calculateIotaDisplays()
}
fun recvServerUpdate(info: ControllerInfo) {
this.patterns.lastOrNull()?.let {
fun recvServerUpdate(info: ControllerInfo, index: Int) {
this.stackDescs = info.stackDesc
this.patterns.getOrNull(index)?.let {
it.type = info.resolutionType
}
@ -250,10 +260,6 @@ class GuiSpellcasting constructor(
is PatternDrawState.JustStarted -> {
// Well, we never managed to get anything on the stack this go-around.
this.drawState = PatternDrawState.BetweenPatterns
if (this.patterns.isEmpty()) {
Minecraft.getInstance().setScreen(null)
Minecraft.getInstance().soundManager.stop(HexSounds.CASTING_AMBIANCE.location, null)
}
}
is PatternDrawState.Drawing -> {
val (start, _, pat) = this.drawState as PatternDrawState.Drawing
@ -452,13 +458,12 @@ class GuiSpellcasting constructor(
/** Distance between adjacent hex centers */
fun hexSize(): Float {
val hasLens = Minecraft.getInstance().player!!
.getItemInHand(otherHand(this.handOpenedWith)).`is`(HexItems.SCRYING_LENS)
val scaleModifier = DiscoveryHandlers.gridScaleModifier(Minecraft.getInstance().player)
// Originally, we allowed 32 dots across. Assuming a 1920x1080 screen this allowed like 500-odd area.
// Let's be generous and give them 512.
val baseScale = sqrt(this.width.toDouble() * this.height / 512.0)
return baseScale.toFloat() * if (hasLens) 0.75f else 1f
return baseScale.toFloat() * scaleModifier
}
fun coordsOffset(): Vec2 = Vec2(this.width.toFloat() * 0.5f, this.height.toFloat() * 0.5f)

View file

@ -0,0 +1,83 @@
package at.petrak.hexcasting.common.blocks.akashic;
import at.petrak.hexcasting.api.misc.TriPredicate;
import at.petrak.hexcasting.common.lib.HexBlocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayDeque;
import java.util.HashSet;
public class BlockAkashicFloodfiller extends Block {
public BlockAkashicFloodfiller(Properties p_49795_) {
super(p_49795_);
}
public @Nullable
BlockPos getRecordPosition(BlockPos here, BlockState state, Level world) {
return floodFillFor(here, world,
(pos, bs, level) -> bs.is(HexBlocks.AKASHIC_RECORD));
}
@Override
public void onRemove(BlockState pState, Level pLevel, BlockPos pPos, BlockState pNewState, boolean pIsMoving) {
var recordPos = this.getRecordPosition(pPos, pState, pLevel);
if (recordPos != null && pLevel.getBlockEntity(recordPos) instanceof BlockEntityAkashicRecord akashic) {
akashic.removeFloodfillerAt(pPos);
}
super.onRemove(pState, pLevel, pPos, pNewState, pIsMoving);
}
public boolean canBeFloodedThrough(BlockPos pos, BlockState state, Level world) {
return true;
}
@Nullable
public static BlockPos floodFillFor(BlockPos start, Level world,
TriPredicate<BlockPos, BlockState, Level> isValid, TriPredicate<BlockPos, BlockState, Level> isTarget, int maxRange) {
var seenBlocks = new HashSet<BlockPos>();
var todo = new ArrayDeque<BlockPos>();
todo.add(start);
while (!todo.isEmpty()) {
var here = todo.remove();
for (var dir : Direction.values()) {
var neighbor = here.relative(dir);
if (neighbor.distSqr(start) > maxRange * maxRange)
continue;
if (seenBlocks.add(neighbor)) {
var bs = world.getBlockState(neighbor);
if (isTarget.test(neighbor, bs, world)) {
return neighbor;
} else if (isValid.test(neighbor, bs, world)) {
todo.add(neighbor);
}
}
}
}
return null;
}
@Nullable
public static BlockPos floodFillFor(BlockPos start, Level world,
TriPredicate<BlockPos, BlockState, Level> isTarget) {
return floodFillFor(start, world, BlockAkashicFloodfiller::canItBeFloodedThrough, isTarget, 32);
}
public static boolean canItBeFloodedThrough(BlockPos pos, BlockState state, Level world) {
if (!(state.getBlock() instanceof BlockAkashicFloodfiller flooder)) {
return false;
}
return flooder.canBeFloodedThrough(pos, state, world);
}
}

View file

@ -5,18 +5,18 @@ import at.petrak.hexcasting.api.utils.NBTHelper;
import at.petrak.hexcasting.common.lib.HexBlockEntities;
import com.mojang.authlib.GameProfile;
import com.mojang.datafixers.util.Pair;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.nbt.Tag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import org.jetbrains.annotations.Nullable;
@ -82,10 +82,10 @@ public class BlockEntityStoredPlayerImpetus extends BlockEntityAbstractImpetus {
}
public void applyScryingLensOverlay(List<Pair<ItemStack, Component>> lines,
BlockState state, BlockPos pos, LocalPlayer observer,
ClientLevel world,
Direction hitFace, InteractionHand lensHand) {
super.applyScryingLensOverlay(lines, state, pos, observer, world, hitFace, lensHand);
BlockState state, BlockPos pos, Player observer,
Level world,
Direction hitFace) {
super.applyScryingLensOverlay(lines, state, pos, observer, world, hitFace);
var name = this.getPlayerName();
if (name != null) {

View file

@ -40,6 +40,10 @@ import at.petrak.hexcasting.common.casting.operators.stack.*;
import at.petrak.hexcasting.common.lib.HexItems;
import it.unimi.dsi.fastutil.booleans.BooleanArrayList;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.LayeredCauldronBlock;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.phys.Vec3;
import static at.petrak.hexcasting.api.HexAPI.modLoc;
@ -211,10 +215,13 @@ public class RegisterPatterns {
modLoc("colorize"),
OpColorize.INSTANCE);
PatternRegistry.mapPattern(HexPattern.fromAngles("aqawqadaq", HexDir.SOUTH_EAST), modLoc("create_water"),
OpCreateWater.INSTANCE);
new OpCreateFluid(false, ManaConstants.DUST_UNIT,
Items.WATER_BUCKET,
Blocks.WATER_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, LayeredCauldronBlock.MAX_FILL_LEVEL),
Fluids.WATER));
PatternRegistry.mapPattern(HexPattern.fromAngles("dedwedade", HexDir.SOUTH_WEST),
modLoc("destroy_water"),
OpDestroyWater.INSTANCE);
OpDestroyFluid.INSTANCE);
PatternRegistry.mapPattern(HexPattern.fromAngles("aaqawawa", HexDir.SOUTH_EAST), modLoc("ignite"),
OpIgnite.INSTANCE);
PatternRegistry.mapPattern(HexPattern.fromAngles("ddedwdwd", HexDir.SOUTH_WEST), modLoc("extinguish"),
@ -296,13 +303,15 @@ public class RegisterPatterns {
modLoc("sentinel/wayfind"),
OpGetSentinelWayfind.INSTANCE);
PatternRegistry.mapPattern(HexPattern.fromAngles("waadwawdaaweewq", HexDir.EAST),
modLoc("lightning"), OpLightning.INSTANCE, true);
PatternRegistry.mapPattern(HexPattern.fromAngles("eawwaeawawaa", HexDir.NORTH_WEST),
modLoc("flight"), OpFlight.INSTANCE, true);
PatternRegistry.mapPattern(HexPattern.fromAngles("eaqawqadaqd", HexDir.EAST),
modLoc("create_lava"), OpCreateLava.INSTANCE, true);
modLoc("create_lava"), new OpCreateFluid(true, ManaConstants.CRYSTAL_UNIT,
Items.LAVA_BUCKET,
Blocks.LAVA_CAULDRON.defaultBlockState(),
Fluids.LAVA), true);
PatternRegistry.mapPattern(
HexPattern.fromAngles("wwwqqqwwwqqeqqwwwqqwqqdqqqqqdqq", HexDir.EAST),
modLoc("teleport"), OpTeleport.INSTANCE, true);
@ -505,7 +514,7 @@ public class RegisterPatterns {
if (negate) {
accumulator = -accumulator;
}
return Action.makeConstantOp(new DoubleIota(accumulator));
return Operator.makeConstantOp(new DoubleIota(accumulator), modLoc("number"));
} else {
return null;
}
@ -541,7 +550,7 @@ public class RegisterPatterns {
return null;
}
return new OpMask(mask);
return new OpMask(mask, modLoc("mask"));
});
}
}

View file

@ -19,6 +19,8 @@ object OpBlockAxisRaycast : ConstManaAction {
val origin = args.getVec3(0, argc)
val look = args.getVec3(1, argc)
ctx.assertVecInRange(origin)
val blockHitResult = ctx.world.clip(
ClipContext(
origin,

View file

@ -19,6 +19,8 @@ object OpBlockRaycast : ConstManaAction {
val origin = args.getVec3(0, argc)
val look = args.getVec3(1, argc)
ctx.assertVecInRange(origin)
val blockHitResult = ctx.world.clip(
ClipContext(
origin,

View file

@ -19,6 +19,8 @@ object OpEntityRaycast : ConstManaAction {
val look = args.getVec3(1, argc)
val endp = Action.raycastEnd(origin, look)
ctx.assertVecInRange(origin)
val entityHitResult = ProjectileUtil.getEntityHitResult(
ctx.caster,
origin,

View file

@ -1,11 +1,16 @@
package at.petrak.hexcasting.common.casting.operators
import at.petrak.hexcasting.api.spell.ConstManaOperator
import at.petrak.hexcasting.api.spell.SpellDatum
import at.petrak.hexcasting.api.spell.ConstManaAction
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.getChecked
import at.petrak.hexcasting.api.spell.mishaps.MishapBadEntity
import at.petrak.hexcasting.api.spell.getItemEntity
import at.petrak.hexcasting.api.spell.iota.Iota
import at.petrak.hexcasting.api.spell.mishaps.MishapBadItem
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.world.entity.Entity
object OpTheCoolerRead : ConstManaAction {
override val argc = 1
@ -14,13 +19,12 @@ object OpTheCoolerRead : ConstManaAction {
args: List<Iota>,
ctx: CastingContext
): List<Iota> {
val target = args.getItemEntity(0, argc)
val target = args.getEntity(0, argc)
ctx.assertEntityInRange(target)
val stack = target.item
val datumHolder = IXplatAbstractions.INSTANCE.findDataHolder(stack)
?: throw MishapBadItem.of(target, "iota.read")
val datumHolder = IXplatAbstractions.INSTANCE.findDataHolder(target)
?: throw MishapBadEntity.of(target, "iota.read")
val datum = datumHolder.readIota(ctx.world)
?: datumHolder.emptyIota()

View file

@ -14,15 +14,15 @@ object OpTheCoolerReadable : ConstManaAction {
args: List<Iota>,
ctx: CastingContext
): List<Iota> {
val target = args.getItemEntity(0, argc)
val target = args.getEntity(0, argc)
ctx.assertEntityInRange(target)
val stack = target.item
val datumHolder = IXplatAbstractions.INSTANCE.findDataHolder(stack)
?: return false.asActionResult
val datumHolder = IXplatAbstractions.INSTANCE.findDataHolder(target)
?: return false.asSpellResult
if (datumHolder.readIota(ctx.world) == null && datumHolder.emptyIota() == null)
return false.asActionResult
datumHolder.readDatum(ctx.world)
?: datumHolder.emptyDatum()
?: return false.asSpellResult
return true.asActionResult
}

View file

@ -1,5 +1,6 @@
package at.petrak.hexcasting.common.casting.operators
import at.petrak.hexcasting.api.addldata.DataHolder
import at.petrak.hexcasting.api.spell.ParticleSpray
import at.petrak.hexcasting.api.spell.RenderedSpell
import at.petrak.hexcasting.api.spell.SpellAction
@ -35,22 +36,15 @@ object OpWrite : SpellAction {
throw MishapOthersName(trueName)
return Triple(
Spell(datum),
Spell(datum, datumHolder),
0,
listOf()
)
}
private data class Spell(val datum: Iota) : RenderedSpell {
private data class Spell(val datum: Iota, val datumHolder: DataHolder) : RenderedSpell {
override fun cast(ctx: CastingContext) {
val (handStack) = ctx.getHeldItemToOperateOn {
val datumHolder = IXplatAbstractions.INSTANCE.findDataHolder(it)
datumHolder != null && datumHolder.writeIota(datum, true)
}
val datumHolder = IXplatAbstractions.INSTANCE.findDataHolder(handStack)
datumHolder?.writeIota(datum, false)
datumHolder.writeDatum(datum, false)
}
}
}

View file

@ -13,7 +13,9 @@ object OpRemove : ConstManaAction {
override fun execute(args: List<Iota>, ctx: CastingContext): List<Iota> {
val list = args.getList(0, argc).toMutableList()
val index = args.getPositiveIntUnder(1, list.size, argc)
val index = args.getInt(1, argc)
if (index < 0 || index >= list.size)
return list.asActionResult
list.removeAt(index)
return list.asActionResult
}

View file

@ -1,11 +1,15 @@
package at.petrak.hexcasting.common.casting.operators.math
import at.petrak.hexcasting.api.spell.ConstManaOperator
import at.petrak.hexcasting.api.spell.SpellDatum
import at.petrak.hexcasting.api.spell.ConstManaAction
import at.petrak.hexcasting.api.spell.asActionResult
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.getNumOrVec
import at.petrak.hexcasting.api.spell.iota.Iota
import at.petrak.hexcasting.api.spell.mishaps.MishapDivideByZero
import at.petrak.hexcasting.api.spell.numOrVec
import at.petrak.hexcasting.api.spell.spellListOf
import net.minecraft.world.phys.Vec3
import kotlin.math.pow
@ -41,7 +45,7 @@ object OpPowProj : ConstManaAction {
{ rvec ->
if (lvec == Vec3.ZERO)
throw MishapDivideByZero.of(args[0], args[1], "project")
rvec.scale(rvec.dot(lvec) / lvec.dot(lvec)).asActionResult
lvec.scale(rvec.dot(lvec) / lvec.dot(lvec))
}
)
})

View file

@ -6,9 +6,8 @@ import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.iota.Iota
import at.petrak.hexcasting.api.spell.mishaps.MishapImmuneEntity
import at.petrak.hexcasting.api.spell.mishaps.MishapLocationTooFarAway
import at.petrak.hexcasting.common.network.MsgBlinkAck
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.server.level.ServerPlayer
import at.petrak.hexcasting.common.casting.operators.spells.great.OpTeleport
import at.petrak.hexcasting.common.lib.HexEntityTags
import net.minecraft.world.entity.Entity
import kotlin.math.roundToInt
@ -22,7 +21,7 @@ object OpBlink : SpellAction {
val delta = args.getDouble(1, argc)
ctx.assertEntityInRange(target)
if (!target.canChangeDimensions())
if (!target.canChangeDimensions() || target.type.`is`(HexEntityTags.CANNOT_TELEPORT))
throw MishapImmuneEntity(target)
val dvec = target.lookAngle.scale(delta)
@ -47,12 +46,8 @@ object OpBlink : SpellAction {
private data class Spell(val target: Entity, val delta: Double) : RenderedSpell {
override fun cast(ctx: CastingContext) {
val dvec = target.lookAngle.scale(delta)
target.setPos(target.position().add(dvec))
if (target is ServerPlayer) {
target.connection.resetPosition()
IXplatAbstractions.INSTANCE.sendPacketToPlayer(target, MsgBlinkAck(dvec))
}
val delta = target.lookAngle.scale(delta)
OpTeleport.teleportRespectSticky(target, delta)
}
}
}

View file

@ -24,7 +24,7 @@ object OpBreakBlock : SpellAction {
ctx.assertVecInRange(pos)
return Triple(
Spell(pos),
Spell(bpos),
(ManaConstants.DUST_UNIT * 1.125).toInt(),
listOf(ParticleSpray.burst(Vec3.atCenterOf(pos), 1.0))
)
@ -32,7 +32,7 @@ object OpBreakBlock : SpellAction {
private data class Spell(val pos: BlockPos) : RenderedSpell {
override fun cast(ctx: CastingContext) {
if (!ctx.world.mayInteract(ctx.caster, pos))
if (!ctx.canEditBlockAt(pos))
return
val blockstate = ctx.world.getBlockState(pos)

View file

@ -9,6 +9,7 @@ import at.petrak.hexcasting.api.spell.SpellAction
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.mishaps.MishapBadOffhandItem
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.world.item.ItemStack
object OpColorize : SpellAction {
override val argc = 0
@ -26,22 +27,20 @@ object OpColorize : SpellAction {
)
}
return Triple(
Spell,
Spell(handStack),
ManaConstants.DUST_UNIT,
listOf()
)
}
private object Spell : RenderedSpell {
private data class Spell(val stack: ItemStack) : RenderedSpell {
override fun cast(ctx: CastingContext) {
val handStack = ctx.getHeldItemToOperateOn(IXplatAbstractions.INSTANCE::isColorizer).first.copy()
if (IXplatAbstractions.INSTANCE.isColorizer(handStack)) {
if (ctx.withdrawItem(handStack.item, 1, true)) {
IXplatAbstractions.INSTANCE.setColorizer(
ctx.caster,
FrozenColorizer(handStack, ctx.caster.uuid)
)
}
val copy = stack.copy()
if (ctx.withdrawItem(copy, 1, true)) {
IXplatAbstractions.INSTANCE.setColorizer(
ctx.caster,
FrozenColorizer(copy, ctx.caster.uuid)
)
}
}
}

View file

@ -44,7 +44,7 @@ class OpConjureBlock(val light: Boolean) : SpellAction {
private data class Spell(val pos: BlockPos, val light: Boolean) : RenderedSpell {
override fun cast(ctx: CastingContext) {
if (!ctx.world.mayInteract(ctx.caster, pos))
if (!ctx.canEditBlockAt(pos))
return
val placeContext = DirectionalPlaceContext(ctx.world, pos, Direction.DOWN, ItemStack.EMPTY, Direction.UP)

View file

@ -0,0 +1,59 @@
package at.petrak.hexcasting.common.casting.operators.spells
import at.petrak.hexcasting.api.spell.*
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.core.BlockPos
import net.minecraft.world.item.BucketItem
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.material.Fluid
import net.minecraft.world.phys.Vec3
class OpCreateFluid(override val isGreat: Boolean, val cost: Int, val bucket: Item, val cauldron: BlockState, val fluid: Fluid) : SpellOperator {
override val argc = 1
override fun execute(
args: List<SpellDatum<*>>,
ctx: CastingContext
): Triple<RenderedSpell, Int, List<ParticleSpray>> {
val target = args.getChecked<Vec3>(0, argc)
ctx.assertVecInRange(target)
return Triple(
Spell(target, bucket, cauldron, fluid),
cost,
listOf(ParticleSpray.burst(Vec3.atCenterOf(BlockPos(target)), 1.0))
)
}
private data class Spell(val target: Vec3, val bucket: Item, val cauldron: BlockState, val fluid: Fluid) : RenderedSpell {
override fun cast(ctx: CastingContext) {
val pos = BlockPos(target)
if (!ctx.canEditBlockAt(pos) || !IXplatAbstractions.INSTANCE.isPlacingAllowed(
ctx.world,
pos,
ItemStack(bucket),
ctx.caster
)
)
return
val state = ctx.world.getBlockState(pos)
if (state.block == Blocks.CAULDRON)
ctx.world.setBlock(pos, cauldron, 3)
else if (!IXplatAbstractions.INSTANCE.tryPlaceFluid(
ctx.world,
ctx.castingHand,
pos,
fluid
) && bucket is BucketItem) {
// make the player null so we don't give them a usage statistic for example
bucket.emptyContents(null, ctx.world, pos, null)
}
}
}
}

View file

@ -1,68 +0,0 @@
package at.petrak.hexcasting.common.casting.operators.spells
import at.petrak.hexcasting.api.HexAPI
import at.petrak.hexcasting.api.misc.ManaConstants
import at.petrak.hexcasting.api.spell.ParticleSpray
import at.petrak.hexcasting.api.spell.RenderedSpell
import at.petrak.hexcasting.api.spell.SpellAction
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.getBlockPos
import at.petrak.hexcasting.api.spell.iota.Iota
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.core.BlockPos
import net.minecraft.world.item.BucketItem
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.level.block.AbstractCauldronBlock
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.LayeredCauldronBlock
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.phys.Vec3
object OpCreateWater : SpellAction {
override val argc = 1
override fun execute(
args: List<Iota>,
ctx: CastingContext
): Triple<RenderedSpell, Int, List<ParticleSpray>> {
val target = args.getBlockPos(0, argc)
return Triple(
Spell(target),
ManaConstants.DUST_UNIT,
listOf(ParticleSpray.burst(Vec3.atCenterOf(target), 1.0))
)
}
private data class Spell(val pos: BlockPos) : RenderedSpell {
override fun cast(ctx: CastingContext) {
if (!ctx.world.mayInteract(ctx.caster, pos) || !IXplatAbstractions.INSTANCE.isPlacingAllowed(ctx.world, pos, ItemStack(Items.WATER_BUCKET), ctx.caster))
return
val state = ctx.world.getBlockState(pos)
if (state.block is AbstractCauldronBlock)
ctx.world.setBlock(
pos,
Blocks.WATER_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, 3),
3
)
else if (!IXplatAbstractions.INSTANCE.tryPlaceFluid(
ctx.world,
ctx.castingHand,
pos,
ItemStack(Items.WATER_BUCKET),
Fluids.WATER
)
) {
// Just steal bucket code lmao
val charlie = Items.WATER_BUCKET
if (charlie is BucketItem) {
// make the player null so we don't give them a usage statistic for example
charlie.emptyContents(null, ctx.world, pos, null)
} else {
HexAPI.LOGGER.warn("Items.WATER_BUCKET wasn't a BucketItem?")
}
}
}
}
}

View file

@ -3,42 +3,54 @@ package at.petrak.hexcasting.common.casting.operators.spells
import at.petrak.hexcasting.api.misc.ManaConstants
import at.petrak.hexcasting.api.spell.*
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.iota.Iota
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.particles.ParticleTypes
import net.minecraft.sounds.SoundEvents
import net.minecraft.sounds.SoundSource
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.BucketPickup
import net.minecraft.world.level.block.LiquidBlock
import net.minecraft.world.level.block.*
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.level.material.Material
import net.minecraft.world.phys.Vec3
object OpDestroyWater : SpellAction {
object OpDestroyFluid : SpellOperator {
override val argc = 1
override fun execute(
args: List<Iota>,
args: List<SpellDatum<*>>,
ctx: CastingContext
): Triple<RenderedSpell, Int, List<ParticleSpray>> {
val target = args.getBlockPos(0, argc)
val target = args.getChecked<Vec3>(0, argc)
ctx.assertVecInRange(target)
return Triple(
Spell(target),
2 * ManaConstants.CRYSTAL_UNIT,
listOf(ParticleSpray.burst(Vec3.atCenterOf(target), 3.0))
listOf(ParticleSpray.burst(target, 3.0))
)
}
const val MAX_DESTROY_COUNT = 1024
private data class Spell(val basePos: BlockPos) : RenderedSpell {
private data class Spell(val target: Vec3) : RenderedSpell {
override fun cast(ctx: CastingContext) {
val basePos = BlockPos(target)
// Try draining from fluid handlers first, and if so, don't do the normal behavior
if (ctx.canEditBlockAt(basePos)) {
if (IXplatAbstractions.INSTANCE.drainAllFluid(ctx.world, basePos)) {
return
} else {
val state = ctx.world.getBlockState(basePos)
if (state.block is AbstractCauldronBlock && state.block != Blocks.CAULDRON) {
ctx.world.setBlock(basePos, Blocks.CAULDRON.defaultBlockState(), 3)
return
}
}
}
// SpongeBlock.java
val todo = ArrayDeque<BlockPos>()
val seen = HashSet<BlockPos>()
@ -51,12 +63,7 @@ object OpDestroyWater : SpellAction {
var successes = 0
while (todo.isNotEmpty() && successes <= MAX_DESTROY_COUNT) {
val here = todo.removeFirst()
val distFromFocus =
ctx.caster.position().distanceToSqr(Vec3.atCenterOf(here))
if (distFromFocus < Action.MAX_DISTANCE * Action.MAX_DISTANCE
&& seen.add(here)
&& ctx.world.mayInteract(ctx.caster, here)
) {
if (ctx.canEditBlockAt(here) && seen.add(here)) {
// never seen this pos in my life
val fluid = ctx.world.getFluidState(here)
if (fluid != Fluids.EMPTY.defaultFluidState()) {
@ -115,9 +122,9 @@ object OpDestroyWater : SpellAction {
if (successes > 0) {
ctx.world.playSound(
null,
basePos.x.toDouble(),
basePos.y.toDouble(),
basePos.z.toDouble(),
target.x,
target.y,
target.z,
SoundEvents.FIRE_EXTINGUISH,
SoundSource.BLOCKS,
1.0f,

View file

@ -4,6 +4,7 @@ import at.petrak.hexcasting.api.misc.ManaConstants
import at.petrak.hexcasting.api.spell.ParticleSpray
import at.petrak.hexcasting.api.spell.RenderedSpell
import at.petrak.hexcasting.api.spell.SpellAction
import at.petrak.hexcasting.api.spell.*
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.getBlockPos
import at.petrak.hexcasting.api.spell.iota.Iota
@ -36,8 +37,9 @@ object OpEdifySapling : SpellAction {
private data class Spell(val pos: BlockPos) : RenderedSpell {
override fun cast(ctx: CastingContext) {
val blockstate = ctx.world.getBlockState(pos)
if (!ctx.world.mayInteract(ctx.caster, pos) ||
!IXplatAbstractions.INSTANCE.isBreakingAllowed(ctx.world, pos, blockstate, ctx.caster))
if (!ctx.canEditBlockAt(pos) ||
!IXplatAbstractions.INSTANCE.isBreakingAllowed(ctx.world, pos, blockstate, ctx.caster)
)
return
val bs = ctx.world.getBlockState(pos)

View file

@ -8,6 +8,7 @@ import at.petrak.hexcasting.api.spell.SpellAction
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.mishaps.MishapBadOffhandItem
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.world.item.ItemStack
class OpErase : SpellAction {
override val argc = 0
@ -33,22 +34,15 @@ class OpErase : SpellAction {
}
return Triple(
Spell,
Spell(handStack),
ManaConstants.DUST_UNIT, listOf()
)
}
private object Spell : RenderedSpell {
private data class Spell(val stack: ItemStack) : RenderedSpell {
override fun cast(ctx: CastingContext) {
val (handStack) = ctx.getHeldItemToOperateOn {
val hexHolder = IXplatAbstractions.INSTANCE.findHexHolder(it)
val datumHolder = IXplatAbstractions.INSTANCE.findDataHolder(it)
(hexHolder?.hasHex() == true) ||
(datumHolder?.writeIota(null, true) == true)
}
val hexHolder = IXplatAbstractions.INSTANCE.findHexHolder(handStack)
val datumHolder = IXplatAbstractions.INSTANCE.findDataHolder(handStack)
val hexHolder = IXplatAbstractions.INSTANCE.findHexHolder(stack)
val datumHolder = IXplatAbstractions.INSTANCE.findDataHolder(stack)
if (hexHolder?.hasHex() == true)
hexHolder.clearHex()

View file

@ -19,7 +19,8 @@ class OpExplode(val fire: Boolean) : SpellAction {
val pos = args.getVec3(0, argc)
val strength = args.getPositiveDoubleUnder(1, 10.0, argc)
ctx.assertVecInRange(pos)
val cost = ManaConstants.DUST_UNIT * (3 * strength + if (fire) 0.125 else 1.0)
val clampedStrength = Mth.clamp(strength, 0.0, 10.0)
val cost = ManaConstants.DUST_UNIT * (3 * clampedStrength + if (fire) 1.0 else 0.125)
return Triple(
Spell(pos, strength, this.fire),
cost.toInt(),
@ -29,7 +30,8 @@ class OpExplode(val fire: Boolean) : SpellAction {
private data class Spell(val pos: Vec3, val strength: Double, val fire: Boolean) : RenderedSpell {
override fun cast(ctx: CastingContext) {
if (!ctx.world.mayInteract(ctx.caster, BlockPos(pos)))
// TODO: you can use this to explode things *outside* of the worldborder?
if (!ctx.canEditBlockAt(BlockPos(pos)))
return
ctx.world.explode(

View file

@ -46,14 +46,9 @@ object OpExtinguish : SpellAction {
var successes = 0
while (todo.isNotEmpty() && successes <= MAX_DESTROY_COUNT) {
val here = todo.removeFirst()
val distFromFocus =
ctx.caster.position().distanceToSqr(Vec3.atCenterOf(here))
val distFromTarget = target.distSqr(here) // max distance to prevent runaway shenanigans
if (distFromFocus < Action.MAX_DISTANCE * Action.MAX_DISTANCE
&& seen.add(here)
&& distFromTarget < 10 * 10
&& ctx.world.mayInteract(ctx.caster, here)
) {
val distFromTarget =
target.distanceTo(Vec3.atCenterOf(here)) // max distance to prevent runaway shenanigans
if (ctx.canEditBlockAt(here) && distFromTarget < 10 && seen.add(here)) {
// never seen this pos in my life
val blockstate = ctx.world.getBlockState(here)
if (IXplatAbstractions.INSTANCE.isBreakingAllowed(ctx.world, here, blockstate, ctx.caster)) {

View file

@ -1,6 +1,5 @@
package at.petrak.hexcasting.common.casting.operators.spells
import at.petrak.hexcasting.api.HexAPI
import at.petrak.hexcasting.api.misc.ManaConstants
import at.petrak.hexcasting.api.spell.ParticleSpray
import at.petrak.hexcasting.api.spell.RenderedSpell
@ -13,7 +12,7 @@ import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.InteractionHand
import net.minecraft.world.item.FireChargeItem
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.phys.BlockHitResult
@ -37,27 +36,26 @@ object OpIgnite : SpellAction {
private data class Spell(val pos: BlockPos) : RenderedSpell {
override fun cast(ctx: CastingContext) {
// steal petra code that steals bucket code
val maxwell = Items.FIRE_CHARGE
// TODO should we do these checks in the action part of the spell
if (!ctx.world.mayInteract(ctx.caster, pos) || !IXplatAbstractions.INSTANCE.isPlacingAllowed(ctx.world, pos, ItemStack(maxwell), ctx.caster))
if (!ctx.canEditBlockAt(pos))
return
if (maxwell is FireChargeItem) {
// help
maxwell.useOn(
UseOnContext(
ctx.world,
null,
InteractionHand.MAIN_HAND,
ItemStack(maxwell.asItem()),
BlockHitResult(Vec3.atCenterOf(pos), Direction.UP, pos, false)
)
)
} else {
HexAPI.LOGGER.warn("Items.FIRE_CHARGE wasn't a FireChargeItem?")
// help
if (!tryToClick(ctx, pos, Items.FIRE_CHARGE)) {
tryToClick(ctx, pos, Items.FLINT_AND_STEEL)
}
}
fun tryToClick(ctx: CastingContext, pos: BlockPos, item: Item): Boolean {
return IXplatAbstractions.INSTANCE.isPlacingAllowed(ctx.world, pos, ItemStack(item), ctx.caster) &&
item.useOn(
UseOnContext(
ctx.world,
null,
InteractionHand.MAIN_HAND,
ItemStack(item),
BlockHitResult(target, Direction.UP, pos, false)
)
).consumesAction()
}
}
}

View file

@ -5,6 +5,7 @@ import at.petrak.hexcasting.api.mod.HexItemTags
import at.petrak.hexcasting.api.spell.ParticleSpray
import at.petrak.hexcasting.api.spell.RenderedSpell
import at.petrak.hexcasting.api.spell.SpellAction
import at.petrak.hexcasting.api.spell.*
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.getItemEntity
import at.petrak.hexcasting.api.spell.iota.Iota
@ -14,6 +15,7 @@ import at.petrak.hexcasting.api.utils.extractMana
import at.petrak.hexcasting.api.utils.isManaItem
import at.petrak.hexcasting.common.items.magic.ItemMediaHolder
import at.petrak.hexcasting.common.lib.HexItems
import net.minecraft.world.InteractionHand
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.item.ItemStack
@ -59,16 +61,13 @@ object OpMakeBattery : SpellAction {
)
}
return Triple(
Spell(entity),
ManaConstants.CRYSTAL_UNIT, listOf(ParticleSpray.burst(entity.position(), 0.5))
)
return Triple(Spell(entity, hand),
ManaConstants.CRYSTAL_UNIT, listOf(ParticleSpray.burst(entity.position(), 0.5)))
}
private data class Spell(val itemEntity: ItemEntity) : RenderedSpell {
private data class Spell(val itemEntity: ItemEntity, val hand: InteractionHand) : RenderedSpell {
override fun cast(ctx: CastingContext) {
val (handStack, hand) = ctx.getHeldItemToOperateOn { it.`is`(HexItemTags.PHIAL_BASE) }
if (handStack.`is`(HexItemTags.PHIAL_BASE) && itemEntity.isAlive) {
if (itemEntity.isAlive) {
val entityStack = itemEntity.item.copy()
val manaAmt = extractMana(entityStack, drainForBatteries = true)
if (manaAmt > 0) {

View file

@ -11,6 +11,7 @@ import at.petrak.hexcasting.api.utils.isManaItem
import at.petrak.hexcasting.common.items.magic.ItemPackagedHex
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.item.ItemStack
class OpMakePackagedSpell<T : ItemPackagedHex>(val itemType: T, val cost: Int) : SpellAction {
override val argc = 2
@ -49,13 +50,12 @@ class OpMakePackagedSpell<T : ItemPackagedHex>(val itemType: T, val cost: Int) :
if (trueName != null)
throw MishapOthersName(trueName)
return Triple(Spell(entity, patterns), cost, listOf(ParticleSpray.burst(entity.position(), 0.5)))
return Triple(Spell(entity, patterns, handStack), cost, listOf(ParticleSpray.burst(entity.position(), 0.5)))
}
private inner class Spell(val itemEntity: ItemEntity, val patterns: List<Iota>) : RenderedSpell {
private inner class Spell(val itemEntity: ItemEntity, val patterns: List<Iota>, val stack: ItemStack) : RenderedSpell {
override fun cast(ctx: CastingContext) {
val (handStack) = ctx.getHeldItemToOperateOn { it.`is`(itemType) }
val hexHolder = IXplatAbstractions.INSTANCE.findHexHolder(handStack)
val hexHolder = IXplatAbstractions.INSTANCE.findHexHolder(stack)
if (hexHolder != null
&& !hexHolder.hasHex()
&& itemEntity.isAlive

View file

@ -31,12 +31,8 @@ object OpPlaceBlock : SpellAction {
val pos = args.getBlockPos(0, argc)
ctx.assertVecInRange(pos)
if (!ctx.world.mayInteract(ctx.caster, pos))
return null
val blockHit = BlockHitResult(
Vec3.ZERO, ctx.caster.direction, pos, false
target, ctx.caster.direction, pos, false
)
val itemUseCtx = UseOnContext(ctx.caster, ctx.castingHand, blockHit)
val placeContext = BlockPlaceContext(itemUseCtx)
@ -54,18 +50,16 @@ object OpPlaceBlock : SpellAction {
private data class Spell(val pos: BlockPos) : RenderedSpell {
override fun cast(ctx: CastingContext) {
if (!ctx.world.mayInteract(ctx.caster, pos))
if (!ctx.canEditBlockAt(pos))
return
val blockHit = BlockHitResult(
Vec3.ZERO, ctx.caster.direction, pos, false
vec, ctx.caster.direction, pos, false
)
val bstate = ctx.world.getBlockState(pos)
val placeeSlot = ctx.getOperativeSlot { it.item is BlockItem }
if (placeeSlot != null) {
val placeeStack = ctx.caster.inventory.getItem(placeeSlot).copy()
val placeeStack = ctx.getOperativeSlot { it.item is BlockItem }?.copy()
if (placeeStack != null) {
if (!IXplatAbstractions.INSTANCE.isPlacingAllowed(ctx.world, pos, placeeStack, ctx.caster))
return
@ -81,13 +75,12 @@ object OpPlaceBlock : SpellAction {
val itemUseCtx = UseOnContext(ctx.caster, ctx.castingHand, blockHit)
val placeContext = BlockPlaceContext(itemUseCtx)
if (bstate.canBeReplaced(placeContext)) {
val placee = placeeStack.item as BlockItem
if (ctx.withdrawItem(placee, 1, false)) {
if (ctx.withdrawItem(placeeStack, 1, false)) {
val res = spoofedStack.useOn(placeContext)
ctx.caster.setItemInHand(ctx.castingHand, oldStack)
if (res != InteractionResult.FAIL) {
ctx.withdrawItem(placee, 1, true)
ctx.withdrawItem(placeeStack, 1, true)
ctx.world.playSound(
ctx.caster,

View file

@ -44,8 +44,10 @@ class OpPotionEffect(
private class Spell(val effect: MobEffect, val target: LivingEntity, val duration: Double, val potency: Double) :
RenderedSpell {
override fun cast(ctx: CastingContext) {
val effectInst = MobEffectInstance(effect, (duration * 20).toInt(), potency.toInt() - 1)
target.addEffect(effectInst)
if (duration > 1.0 / 20.0) {
val effectInst = MobEffectInstance(effect, (duration * 20).toInt(), potency.toInt() - 1)
target.addEffect(effectInst)
}
}
}
}

View file

@ -13,6 +13,7 @@ import at.petrak.hexcasting.api.utils.extractMana
import at.petrak.hexcasting.api.utils.isManaItem
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.item.ItemStack
object OpRecharge : SpellAction {
override val argc = 1
@ -24,7 +25,7 @@ object OpRecharge : SpellAction {
val (handStack, hand) = ctx.getHeldItemToOperateOn {
val mana = IXplatAbstractions.INSTANCE.findManaHolder(it)
mana != null && mana.canRecharge() && mana.media /* doo doo da do doo */ < mana.maxMedia
mana != null && mana.canRecharge() && mana.insertMana(-1, true) != 0
}
val mana = IXplatAbstractions.INSTANCE.findManaHolder(handStack)
@ -45,33 +46,28 @@ object OpRecharge : SpellAction {
)
}
if (mana.media >= mana.maxMedia)
if (mana.insertMana(-1, true) == 0)
return null
return Triple(
Spell(entity),
Spell(entity, handStack),
ManaConstants.SHARD_UNIT,
listOf(ParticleSpray.burst(entity.position(), 0.5))
)
}
private data class Spell(val itemEntity: ItemEntity) : RenderedSpell {
private data class Spell(val itemEntity: ItemEntity, val stack: ItemStack) : RenderedSpell {
override fun cast(ctx: CastingContext) {
val (handStack) = ctx.getHeldItemToOperateOn {
val mana = IXplatAbstractions.INSTANCE.findManaHolder(it)
mana != null && mana.canRecharge() && mana.media < mana.maxMedia
}
val mana = IXplatAbstractions.INSTANCE.findManaHolder(handStack)
val mana = IXplatAbstractions.INSTANCE.findManaHolder(stack)
if (mana != null && itemEntity.isAlive) {
val entityStack = itemEntity.item.copy()
val maxMana = mana.maxMedia
val existingMana = mana.media
val emptySpace = mana.insertMana(-1, true)
val manaAmt = extractMana(entityStack, maxMana - existingMana)
val manaAmt = extractMana(entityStack, emptySpace)
mana.media = manaAmt + existingMana
mana.insertMana(manaAmt, false)
itemEntity.item = entityStack
if (entityStack.isEmpty)

View file

@ -28,7 +28,7 @@ object OpTheOnlyReasonAnyoneDownloadedPsi : SpellAction {
return Triple(
Spell(target),
(ManaConstants.DUST_UNIT * 1.125).toInt(),
listOf(ParticleSpray.burst(Vec3.atCenterOf(target), 1.0))
listOf(ParticleSpray.burst(Vec3.atCenterOf(BlockPos(target)), 1.0))
)
}

View file

@ -40,6 +40,9 @@ object OpBrainsweep : SpellAction {
val state = ctx.world.getBlockState(pos)
if (!ctx.canEditBlockAt(pos))
return null
val recman = ctx.world.recipeManager
val recipes = recman.getAllRecipesFor(HexRecipeStuffRegistry.BRAINSWEEP_TYPE)
val recipe = recipes.find { it.matches(state, sacrifice) }
@ -60,6 +63,7 @@ object OpBrainsweep : SpellAction {
) : RenderedSpell {
override fun cast(ctx: CastingContext) {
ctx.world.setBlockAndUpdate(pos, BrainsweepRecipe.copyProperties(state, recipe.result))
Brainsweeping.brainsweep(sacrifice)
if (HexConfig.server().doVillagersTakeOffenseAtMindMurder()) {
sacrifice.tellWitnessesThatIWasMurdered(ctx.caster)

View file

@ -1,66 +0,0 @@
package at.petrak.hexcasting.common.casting.operators.spells.great
import at.petrak.hexcasting.api.HexAPI
import at.petrak.hexcasting.api.misc.ManaConstants
import at.petrak.hexcasting.api.spell.ParticleSpray
import at.petrak.hexcasting.api.spell.RenderedSpell
import at.petrak.hexcasting.api.spell.SpellAction
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.getBlockPos
import at.petrak.hexcasting.api.spell.iota.Iota
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.core.BlockPos
import net.minecraft.world.item.BucketItem
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.level.block.AbstractCauldronBlock
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.phys.Vec3
object OpCreateLava : SpellAction {
override val argc = 1
override val isGreat = true
override fun execute(
args: List<Iota>,
ctx: CastingContext
): Triple<RenderedSpell, Int, List<ParticleSpray>> {
val target = args.getBlockPos(0, argc)
ctx.assertVecInRange(target)
return Triple(
Spell(target),
ManaConstants.CRYSTAL_UNIT,
listOf(ParticleSpray.burst(Vec3.atCenterOf(BlockPos(target)), 1.0)),
)
}
private data class Spell(val pos: BlockPos) : RenderedSpell {
override fun cast(ctx: CastingContext) {
if (!ctx.world.mayInteract(ctx.caster, pos) || !IXplatAbstractions.INSTANCE.isPlacingAllowed(ctx.world, pos, ItemStack(Items.LAVA_BUCKET), ctx.caster))
return
val state = ctx.world.getBlockState(pos)
if (state.block is AbstractCauldronBlock)
ctx.world.setBlock(pos, Blocks.LAVA_CAULDRON.defaultBlockState(), 3)
else if (!IXplatAbstractions.INSTANCE.tryPlaceFluid(
ctx.world,
ctx.castingHand,
pos,
ItemStack(Items.LAVA_BUCKET),
Fluids.LAVA
)
) {
// Just steal bucket code lmao
val charlie = Items.LAVA_BUCKET
if (charlie is BucketItem) {
// make the player null so we don't give them a usage statistic for example
charlie.emptyContents(null, ctx.world, pos, null)
} else {
HexAPI.LOGGER.warn("Items.LAVA_BUCKET wasn't a BucketItem?")
}
}
}
}
}

View file

@ -6,6 +6,7 @@ import at.petrak.hexcasting.api.spell.*
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.iota.Iota
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.phys.Vec3
@ -27,7 +28,7 @@ object OpFlight : SpellAction {
val time = (timeRaw * 20.0).roundToInt()
return Triple(
Spell(target, time, radiusRaw, ctx.position),
ManaConstants.DUST_UNIT * (0.25 * (timeRaw * radiusRaw + 1.0)).roundToInt(),
(ManaConstants.DUST_UNIT * 0.25 * (timeRaw * radiusRaw + 1.0)).roundToInt(),
listOf(ParticleSpray(target.position(), Vec3(0.0, 2.0, 0.0), 0.0, 0.1))
)
}
@ -97,4 +98,10 @@ object OpFlight : SpellAction {
}
}
fun tickAllPlayers(world: ServerLevel) {
for (player in world.players()) {
tickDownFlight(player)
}
}
}

View file

@ -6,10 +6,12 @@ import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.iota.Iota
import at.petrak.hexcasting.api.spell.mishaps.MishapImmuneEntity
import at.petrak.hexcasting.api.spell.mishaps.MishapLocationTooFarAway
import at.petrak.hexcasting.common.lib.HexEntityTags
import at.petrak.hexcasting.common.network.MsgBlinkAck
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.Entity
import net.minecraft.world.item.enchantment.EnchantmentHelper
import net.minecraft.world.phys.Vec3
// TODO while we're making breaking changes I *really* want to have the vector in the entity's local space
@ -25,7 +27,7 @@ object OpTeleport : SpellAction {
val delta = args.getVec3(1, argc)
ctx.assertEntityInRange(teleportee)
if (!teleportee.canChangeDimensions())
if (!teleportee.canChangeDimensions() || teleportee.type.`is`(HexEntityTags.CANNOT_TELEPORT))
throw MishapImmuneEntity(teleportee)
val targetPos = teleportee.position().add(delta)
@ -49,10 +51,7 @@ object OpTeleport : SpellAction {
// TODO make this not a magic number (config?)
if (distance < 32768.0) {
teleportee.setPos(teleportee.position().add(delta))
if (teleportee is ServerPlayer) {
IXplatAbstractions.INSTANCE.sendPacketToPlayer(teleportee, MsgBlinkAck(delta))
}
teleportRespectSticky(teleportee, delta)
}
if (teleportee is ServerPlayer && teleportee == ctx.caster) {
@ -65,6 +64,9 @@ object OpTeleport : SpellAction {
// having to rearrange those. Also it makes sense for LORE REASONS probably, since the caster is more
// aware of items they use often.
for (armorItem in teleportee.inventory.armor) {
if (EnchantmentHelper.hasBindingCurse(armorItem))
continue
if (Math.random() < baseDropChance * 0.25) {
teleportee.drop(armorItem.copy(), true, false)
armorItem.shrink(armorItem.count)
@ -83,6 +85,39 @@ object OpTeleport : SpellAction {
// we also don't drop the offhand just to be nice
}
}
}
fun teleportRespectSticky(teleportee: Entity, delta: Vec3) {
val base = teleportee.rootVehicle
val playersToUpdate = mutableListOf<ServerPlayer>()
val indirect = base.indirectPassengers
val sticky = indirect.any { it.type.`is`(HexEntityTags.STICKY_TELEPORTERS) }
val cannotSticky = indirect.none { it.type.`is`(HexEntityTags.CANNOT_TELEPORT) }
if (sticky && cannotSticky)
return
if (sticky) {
// this handles teleporting the passengers
val target = base.position().add(delta)
base.teleportTo(target.x, target.y, target.z)
indirect
.filterIsInstance<ServerPlayer>()
.forEach(playersToUpdate::add)
} else {
// Break it into two stacks
teleportee.stopRiding()
teleportee.passengers.forEach(Entity::stopRiding)
teleportee.setPos(teleportee.position().add(delta))
if (teleportee is ServerPlayer) {
playersToUpdate.add(teleportee)
}
}
for (player in playersToUpdate) {
player.connection.resetPosition()
IXplatAbstractions.INSTANCE.sendPacketToPlayer(player, MsgBlinkAck(delta))
}
}
}

View file

@ -30,6 +30,12 @@ class OpWeather(val rain: Boolean) : SpellAction {
val w = ctx.world
if (w.isRaining != rain) {
w.levelData.isRaining = rain // i hex the rains down in minecraftia
if (rain) {
w.setWeatherParameters(0, 6000, true, w.random.nextDouble() < 0.05)
} else {
w.setWeatherParameters(6000, 0, false, false)
}
}
}
}

View file

@ -24,7 +24,7 @@ class OpCreateSentinel(val extendsRange: Boolean) : SpellAction {
return Triple(
Spell(target, this.extendsRange),
ManaConstants.DUST_UNIT,
ManaConstants.DUST_UNIT * if (extendsRange) 2 else 1,
listOf(ParticleSpray.burst(target, 2.0))
)
}

View file

@ -8,6 +8,8 @@ import at.petrak.hexcasting.api.spell.getPositiveInt
import at.petrak.hexcasting.api.spell.iota.Iota
import at.petrak.hexcasting.api.spell.mishaps.MishapNotEnoughArgs
import it.unimi.dsi.fastutil.ints.IntArrayList
import kotlin.math.abs
import kotlin.math.roundToInt
// "lehmer code"
object OpAlwinfyHasAscendedToABeingOfPureMath : Action {
@ -43,8 +45,6 @@ object OpAlwinfyHasAscendedToABeingOfPureMath : Action {
editTarget = editTarget.subList(1, editTarget.size)
}
// val cost = (ln((strides.lastOrNull() ?: 0).toFloat()) * ManaConstants.DUST_UNIT).toInt()
return OperationResult(
continuation,
stack,

View file

@ -3,9 +3,13 @@ package at.petrak.hexcasting.common.casting.operators.stack
import at.petrak.hexcasting.api.spell.ConstManaAction
import at.petrak.hexcasting.api.spell.iota.Iota
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.utils.asTranslatedComponent
import at.petrak.hexcasting.api.utils.lightPurple
import it.unimi.dsi.fastutil.booleans.BooleanList
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
class OpMask(val mask: BooleanList) : ConstManaAction {
class OpMask(val mask: BooleanList, val key: ResourceLocation) : ConstManaAction {
override val argc: Int
get() = mask.size
@ -17,4 +21,7 @@ class OpMask(val mask: BooleanList) : ConstManaAction {
}
return out
}
override val displayName: Component
get() = "hexcasting.spell.$key".asTranslatedComponent(mask.map { if (it) '-' else 'v' }.joinToString("")).lightPurple
}

View file

@ -8,6 +8,7 @@ import at.petrak.hexcasting.common.lib.HexItems;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.EntityArgument;
import net.minecraft.commands.arguments.ResourceLocationArgument;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
@ -15,96 +16,130 @@ import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.ItemStack;
import java.util.Collection;
import java.util.List;
public class ListPatternsCommand {
public static void add(LiteralArgumentBuilder<CommandSourceStack> cmd) {
public static void register(LiteralArgumentBuilder<CommandSourceStack> cmd) {
cmd.then(Commands.literal("patterns")
.requires(dp -> dp.hasPermission(Commands.LEVEL_ADMINS))
.then(Commands.literal("list").executes(ctx -> {
.requires(dp -> dp.hasPermission(Commands.LEVEL_GAMEMASTERS))
.then(Commands.literal("list")
.executes(ctx -> list(ctx.getSource())))
.then(Commands.literal("give")
.then(Commands.argument("patternName", PatternResLocArgument.id())
.executes(ctx ->
giveOne(ctx.getSource(),
getDefaultTarget(ctx.getSource()),
ResourceLocationArgument.getId(ctx, "patternName"),
PatternResLocArgument.getPattern(ctx, "patternName")))
.then(Commands.argument("targets", EntityArgument.players())
.executes(ctx ->
giveOne(ctx.getSource(),
EntityArgument.getPlayers(ctx, "targets"),
ResourceLocationArgument.getId(ctx, "patternName"),
PatternResLocArgument.getPattern(ctx, "patternName"))))))
.then(Commands.literal("giveAll")
.executes(ctx ->
giveAll(ctx.getSource(),
getDefaultTarget(ctx.getSource())))
.then(Commands.argument("targets", EntityArgument.players())
.executes(ctx ->
giveAll(ctx.getSource(),
EntityArgument.getPlayers(ctx, "targets")))))
);
}
private static Collection<ServerPlayer> getDefaultTarget(CommandSourceStack source) {
if (source.getEntity() instanceof ServerPlayer player) {
return List.of(player);
}
return List.of();
}
var lookup = PatternRegistry.getPerWorldPatterns(ctx.getSource().getLevel());
var listing = lookup.entrySet()
.stream()
.sorted((a, b) -> compareResLoc(a.getValue().getFirst(), b.getValue().getFirst()))
.toList();
private static int list(CommandSourceStack source) {
var lookup = PatternRegistry.getPerWorldPatterns(source.getLevel());
var listing = lookup.entrySet()
.stream()
.sorted((a, b) -> compareResLoc(a.getValue().getFirst(), b.getValue().getFirst()))
.toList();
ctx.getSource().sendSuccess(Component.translatable("command.hexcasting.pats.listing"), false);
for (var pair : listing) {
HexPattern hexPattern = HexPattern.fromAngles(pair.getKey(), pair.getValue().getSecond());
ctx.getSource().sendSuccess(Component.literal(pair.getValue().getFirst().toString())
.append(": ")
.append(PatternIota.display(hexPattern)), false);
}
source.sendSuccess(new TranslatableComponent("command.hexcasting.pats.listing"), false);
for (var pair : listing) {
source.sendSuccess(new TextComponent(pair.getValue().getFirst().toString())
.append(": ")
.append(SpellDatum.make(HexPattern.fromAngles(pair.getKey(), pair.getValue().getSecond()))
.display()), false);
}
return lookup.size();
}))
.then(Commands.literal("give")
.then(Commands.argument("patternName", PatternResLocArgument.id()).executes(ctx -> {
var sender = ctx.getSource().getEntity();
if (sender instanceof ServerPlayer player) {
var targetId = ResourceLocationArgument.getId(ctx, "patternName");
var pat = PatternResLocArgument.getPattern(ctx, "patternName");
return lookup.size();
}
private static int giveAll(CommandSourceStack source, Collection<ServerPlayer> targets) {
if (!targets.isEmpty()) {
var lookup = PatternRegistry.getPerWorldPatterns(source.getLevel());
var tag = new CompoundTag();
tag.putString(ItemScroll.TAG_OP_ID, targetId.toString());
tag.put(ItemScroll.TAG_PATTERN,
pat.serializeToNBT());
lookup.forEach((pattern, entry) -> {
var opId = entry.component1();
var startDir = entry.component2();
var stack = new ItemStack(HexItems.SCROLL_LARGE);
stack.setTag(tag);
var tag = new CompoundTag();
tag.putString(ItemScroll.TAG_OP_ID, opId.toString());
tag.put(ItemScroll.TAG_PATTERN,
HexPattern.fromAngles(pattern, startDir).serializeToNBT());
ctx.getSource().sendSuccess(
Component.translatable(
"command.hexcasting.pats.specific.success",
stack.getDisplayName(),
targetId),
true);
var stack = new ItemStack(HexItems.SCROLL_LARGE);
stack.setTag(tag);
var stackEntity = player.drop(stack, false);
if (stackEntity != null) {
stackEntity.setNoPickUpDelay();
stackEntity.setOwner(player.getUUID());
}
return 1;
} else {
return 0;
}
for (var player : targets) {
var stackEntity = player.drop(stack, false);
if (stackEntity != null) {
stackEntity.setNoPickUpDelay();
stackEntity.setOwner(player.getUUID());
}
)))
.then(Commands.literal("giveAll").executes(ctx -> {
var sender = ctx.getSource().getEntity();
if (sender instanceof ServerPlayer player) {
var lookup = PatternRegistry.getPerWorldPatterns(ctx.getSource().getLevel());
lookup.forEach((pattern, entry) -> {
var opId = entry.getFirst();
var startDir = entry.getSecond();
var tag = new CompoundTag();
tag.putString(ItemScroll.TAG_OP_ID, opId.toString());
tag.put(ItemScroll.TAG_PATTERN,
HexPattern.fromAngles(pattern, startDir).serializeToNBT());
var stack = new ItemStack(HexItems.SCROLL_LARGE);
stack.setTag(tag);
var stackEntity = player.drop(stack, false);
if (stackEntity != null) {
stackEntity.setNoPickUpDelay();
stackEntity.setOwner(player.getUUID());
}
});
ctx.getSource().sendSuccess(
Component.translatable("command.hexcasting.pats.all", lookup.size()), true);
return lookup.size();
} else {
return 0;
}
}))
);
});
source.sendSuccess(
new TranslatableComponent("command.hexcasting.pats.all",
lookup.size(),
targets.size() == 1 ? targets.iterator().next().getDisplayName() : targets.size()),
true);
return lookup.size();
} else {
return 0;
}
}
private static int giveOne(CommandSourceStack source, Collection<ServerPlayer> targets, ResourceLocation patternName, HexPattern pat) {
if (!targets.isEmpty()) {
var tag = new CompoundTag();
tag.putString(ItemScroll.TAG_OP_ID, patternName.toString());
tag.put(ItemScroll.TAG_PATTERN,
pat.serializeToNBT());
var stack = new ItemStack(HexItems.SCROLL_LARGE);
stack.setTag(tag);
source.sendSuccess(
new TranslatableComponent(
"command.hexcasting.pats.specific.success",
stack.getDisplayName(),
patternName,
targets.size() == 1 ? targets.iterator().next().getDisplayName() : targets.size()),
true);
for (var player : targets) {
var stackEntity = player.drop(stack, false);
if (stackEntity != null) {
stackEntity.setNoPickUpDelay();
stackEntity.setOwner(player.getUUID());
}
}
return targets.size();
} else {
return 0;
}
}
private static int compareResLoc(ResourceLocation a, ResourceLocation b) {

View file

@ -1,29 +1,28 @@
package at.petrak.hexcasting.common.items;
import at.petrak.hexcasting.annotations.SoftImplement;
import at.petrak.hexcasting.api.misc.DiscoveryHandlers;
import at.petrak.hexcasting.common.lib.HexItems;
import at.petrak.hexcasting.common.network.MsgUpdateComparatorVisualsAck;
import at.petrak.hexcasting.xplat.IXplatAbstractions;
import com.mojang.datafixers.util.Pair;
import net.minecraft.core.BlockPos;
import net.minecraft.core.BlockSource;
import net.minecraft.core.dispenser.OptionalDispenseItemBehavior;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.stats.Stats;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResultHolder;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ArmorItem;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Wearable;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BeehiveBlock;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.DispenserBlock;
import net.minecraft.world.level.block.entity.BeehiveBlockEntity;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
@ -35,6 +34,15 @@ import java.util.WeakHashMap;
public class ItemLens extends Item implements Wearable {
static {
DiscoveryHandlers.addLensPredicate(player -> player.getItemBySlot(EquipmentSlot.MAINHAND).is(HexItems.SCRYING_LENS));
DiscoveryHandlers.addLensPredicate(player -> player.getItemBySlot(EquipmentSlot.OFFHAND).is(HexItems.SCRYING_LENS));
DiscoveryHandlers.addLensPredicate(player -> player.getItemBySlot(EquipmentSlot.HEAD).is(HexItems.SCRYING_LENS));
DiscoveryHandlers.addGridScaleModifier(player -> player.getItemBySlot(EquipmentSlot.MAINHAND).is(HexItems.SCRYING_LENS) ? 0.75f : 1);
DiscoveryHandlers.addGridScaleModifier(player -> player.getItemBySlot(EquipmentSlot.OFFHAND).is(HexItems.SCRYING_LENS) ? 0.75f : 1);
}
public ItemLens(Properties pProperties) {
super(pProperties);
DispenserBlock.registerBehavior(this, new OptionalDispenseItemBehavior() {
@ -53,67 +61,62 @@ public class ItemLens extends Item implements Wearable {
return EquipmentSlot.HEAD;
}
@Override
public InteractionResultHolder<ItemStack> use(Level world, Player player, InteractionHand hand) {
ItemStack itemstack = player.getItemInHand(hand);
EquipmentSlot equipmentslot = Mob.getEquipmentSlotForItem(itemstack);
ItemStack stack = player.getItemBySlot(equipmentslot);
if (stack.isEmpty()) {
player.setItemSlot(equipmentslot, itemstack.copy());
if (!world.isClientSide()) {
player.awardStat(Stats.ITEM_USED.get(this));
}
itemstack.setCount(0);
return InteractionResultHolder.sidedSuccess(itemstack, world.isClientSide());
} else {
return InteractionResultHolder.fail(itemstack);
public static void tickAllPlayers(ServerLevel world) {
for (ServerPlayer player : world.players()) {
tickLens(player);
}
}
@Override
public void inventoryTick(ItemStack pStack, Level pLevel, Entity pEntity, int pSlotId, boolean pIsSelected) {
if (!pLevel.isClientSide() && pEntity instanceof ServerPlayer player) {
if (pStack == player.getItemBySlot(EquipmentSlot.HEAD) ||
pStack == player.getItemBySlot(EquipmentSlot.MAINHAND) ||
pStack == player.getItemBySlot(EquipmentSlot.OFFHAND)) {
sendComparatorDataToClient(player);
}
public static void tickLens(Entity pEntity) {
if (!pEntity.getLevel().isClientSide() && pEntity instanceof ServerPlayer player && DiscoveryHandlers.hasLens(player)) {
sendComparatorDataToClient(player);
}
}
private static final Map<ServerPlayer, Pair<BlockPos, Integer>> comparatorDataMap = new WeakHashMap<>();
private static final Map<ServerPlayer, Pair<BlockPos, Integer>> beeDataMap = new WeakHashMap<>();
private void sendComparatorDataToClient(ServerPlayer player) {
private static void sendComparatorDataToClient(ServerPlayer player) {
double reachAttribute = IXplatAbstractions.INSTANCE.getReachDistance(player);
double distance = player.isCreative() ? reachAttribute : reachAttribute - 0.5;
var hitResult = player.pick(distance, 0, false);
if (hitResult.getType() == HitResult.Type.BLOCK) {
var pos = ((BlockHitResult) hitResult).getBlockPos();
var state = player.level.getBlockState(pos);
int bee = -1;
if (state.getBlock() instanceof BeehiveBlock && player.level.getBlockEntity(pos) instanceof BeehiveBlockEntity bees) {
bee = bees.getOccupantCount();
}
if (state.is(Blocks.COMPARATOR)) {
syncComparatorValue(player, pos,
state.getDirectSignal(player.level, pos, state.getValue(BlockStateProperties.HORIZONTAL_FACING)));
state.getDirectSignal(player.level, pos, state.getValue(BlockStateProperties.HORIZONTAL_FACING)), bee);
} else if (state.hasAnalogOutputSignal()) {
syncComparatorValue(player, pos, state.getAnalogOutputSignal(player.level, pos));
syncComparatorValue(player, pos, state.getAnalogOutputSignal(player.level, pos), bee);
} else {
syncComparatorValue(player, null, -1);
syncComparatorValue(player, null, -1, bee);
}
} else {
syncComparatorValue(player, null, -1);
syncComparatorValue(player, null, -1, -1);
}
}
private void syncComparatorValue(ServerPlayer player, BlockPos pos, int value) {
var previous = comparatorDataMap.get(player);
if (value == -1) {
if (previous != null) {
private static void syncComparatorValue(ServerPlayer player, BlockPos pos, int comparator, int bee) {
var previousComparator = comparatorDataMap.get(player);
var previousBee = beeDataMap.get(player);
if (comparator == -1 && bee == -1) {
if (previousComparator != null || previousBee != null) {
comparatorDataMap.remove(player);
IXplatAbstractions.INSTANCE.sendPacketToPlayer(player, new MsgUpdateComparatorVisualsAck(null, -1));
beeDataMap.remove(player);
IXplatAbstractions.INSTANCE.sendPacketToPlayer(player, new MsgUpdateComparatorVisualsAck(null, -1, -1));
}
} else if (previous == null || (!pos.equals(previous.getFirst()) || value != previous.getSecond())) {
comparatorDataMap.put(player, new Pair<>(pos, value));
IXplatAbstractions.INSTANCE.sendPacketToPlayer(player, new MsgUpdateComparatorVisualsAck(pos, value));
} else if (previousComparator == null || !pos.equals(previousComparator.getFirst()) || comparator != previousComparator.getSecond() ||
previousBee == null || !pos.equals(previousBee.getFirst()) || bee != previousBee.getSecond()) {
comparatorDataMap.put(player, new Pair<>(pos, comparator));
beeDataMap.put(player, new Pair<>(pos, bee));
IXplatAbstractions.INSTANCE.sendPacketToPlayer(player, new MsgUpdateComparatorVisualsAck(pos, comparator, bee));
}
}

View file

@ -0,0 +1,55 @@
package at.petrak.hexcasting.common.items.magic;
import at.petrak.hexcasting.api.addldata.ManaHolder;
import net.minecraft.world.item.ItemStack;
public record DebugUnlockerHolder(ItemStack creativeUnlocker) implements ManaHolder {
@Override
public int getMana() {
return Integer.MAX_VALUE;
}
@Override
public int getMaxMana() {
return Integer.MAX_VALUE - 1;
}
@Override
public void setMana(int mana) {
// NO-OP
}
@Override
public boolean canRecharge() {
return true;
}
@Override
public boolean canProvide() {
return true;
}
@Override
public int getConsumptionPriority() {
return 1000;
}
@Override
public boolean canConstructBattery() {
return false;
}
@Override
public int withdrawMana(int cost, boolean simulate) {
ItemCreativeUnlocker.addToIntArray(creativeUnlocker, ItemCreativeUnlocker.TAG_EXTRACTIONS, cost);
return cost < 0 ? getMana() : cost;
}
@Override
public int insertMana(int amount, boolean simulate) {
ItemCreativeUnlocker.addToIntArray(creativeUnlocker, ItemCreativeUnlocker.TAG_INSERTIONS, amount);
return amount;
}
}

View file

@ -1,9 +1,13 @@
package at.petrak.hexcasting.common.items.magic;
import at.petrak.hexcasting.api.block.circle.BlockEntityAbstractImpetus;
import at.petrak.hexcasting.api.item.ManaHolderItem;
import at.petrak.hexcasting.api.misc.DiscoveryHandlers;
import at.petrak.hexcasting.api.item.MediaHolderItem;
import at.petrak.hexcasting.api.misc.ManaConstants;
import at.petrak.hexcasting.api.utils.NBTHelper;
import at.petrak.hexcasting.common.lib.HexItems;
import at.petrak.hexcasting.common.lib.HexSounds;
import net.minecraft.ChatFormatting;
import net.minecraft.advancements.Advancement;
import net.minecraft.locale.Language;
@ -12,13 +16,17 @@ import net.minecraft.network.chat.MutableComponent;
import net.minecraft.network.chat.TextColor;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.Mth;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
@ -30,25 +38,74 @@ import static at.petrak.hexcasting.api.HexAPI.modLoc;
public class ItemCreativeUnlocker extends Item implements MediaHolderItem {
public static final String DISPLAY_MEDIA = "media";
public static final String DISPLAY_PATTERNS = "patterns";
static {
DiscoveryHandlers.addDebugItemDiscoverer((player, type) -> {
for (ItemStack item : player.getInventory().items) {
if (isDebug(item, type)) {
return item;
}
}
// Technically possible with commands!
for (ItemStack item : player.getInventory().armor) {
if (isDebug(item, type)) {
return item;
}
}
for (ItemStack item : player.getInventory().offhand) {
if (isDebug(item, type)) {
return item;
}
}
return ItemStack.EMPTY;
});
DiscoveryHandlers.addManaHolderDiscoverer(harness -> {
var player = harness.getCtx().getCaster();
if (!player.isCreative())
return List.of();
ItemStack stack = DiscoveryHandlers.findDebugItem(player, DISPLAY_MEDIA);
if (!stack.isEmpty())
return List.of(new DebugUnlockerHolder(stack));
return List.of();
});
}
public static boolean isDebug(ItemStack stack) {
return stack.is(HexItems.CREATIVE_UNLOCKER)
&& stack.hasCustomHoverName()
&& stack.getHoverName().getString().toLowerCase(Locale.ROOT).contains("debug");
return isDebug(stack, null);
}
public static boolean isDebug(ItemStack stack, String flag) {
if (!stack.is(HexItems.CREATIVE_UNLOCKER) || !stack.hasCustomHoverName()) {
return false;
}
var keywords = Arrays.asList(stack.getHoverName().getString().toLowerCase(Locale.ROOT).split(" "));
if (!keywords.contains("debug")) {
return false;
}
return flag == null || keywords.contains(flag);
}
public static Component infiniteMedia(Level level) {
String prefix = "item.hexcasting.creative_unlocker.";
String emphasis = Language.getInstance().getOrDefault(prefix + "for_emphasis");
MutableComponent emphasized = Component.literal("");
MutableComponent emphasized = new TextComponent("");
for (int i = 0; i < emphasis.length(); i++) {
emphasized.append(rainbow(Component.literal("" + emphasis.charAt(i)), i, level));
emphasized.append(rainbow(new TextComponent("" + emphasis.charAt(i)), i, level));
}
return emphasized;
}
private static final String TAG_EXTRACTIONS = "extractions";
public static final String TAG_EXTRACTIONS = "extractions";
public static final String TAG_INSERTIONS = "insertions";
public ItemCreativeUnlocker(Properties properties) {
super(properties);
@ -76,22 +133,37 @@ public class ItemCreativeUnlocker extends Item implements MediaHolderItem {
@Override
public boolean canRecharge(ItemStack stack) {
return false;
return true;
}
public static void addToIntArray(ItemStack stack, String tag, int n) {
int[] arr = NBTHelper.getIntArray(stack, tag);
if (arr == null) {
arr = new int[0];
}
int[] newArr = Arrays.copyOf(arr, arr.length + 1);
newArr[newArr.length - 1] = n;
NBTHelper.putIntArray(stack, tag, newArr);
}
@Override
public int withdrawMana(ItemStack stack, int cost, boolean simulate) {
if (!simulate && isDebug(stack)) {
int[] arr = NBTHelper.getIntArray(stack, TAG_EXTRACTIONS);
if (arr == null) {
arr = new int[0];
}
int[] newArr = Arrays.copyOf(arr, arr.length + 1);
newArr[newArr.length - 1] = cost;
NBTHelper.putIntArray(stack, TAG_EXTRACTIONS, newArr);
// In case it's withdrawn through other means
if (!simulate && isDebug(stack, DISPLAY_MEDIA)) {
addToIntArray(stack, TAG_EXTRACTIONS, cost);
}
return cost < 0 ? 1 : cost;
return cost < 0 ? getMana(stack) : cost;
}
@Override
public int insertMana(ItemStack stack, int amount, boolean simulate) {
// In case it's inserted through other means
if (!simulate && isDebug(stack, DISPLAY_MEDIA)) {
addToIntArray(stack, TAG_INSERTIONS, amount);
}
return amount < 0 ? getMaxMana(stack) : amount;
}
@Override
@ -101,29 +173,45 @@ public class ItemCreativeUnlocker extends Item implements MediaHolderItem {
@Override
public void inventoryTick(ItemStack stack, Level level, Entity entity, int slot, boolean selected) {
if (isDebug(stack) && !level.isClientSide) {
int[] arr = NBTHelper.getIntArray(stack, TAG_EXTRACTIONS);
if (arr != null) {
NBTHelper.remove(stack, TAG_EXTRACTIONS);
for (int i : arr) {
if (i < 0) {
entity.sendSystemMessage(Component.translatable("hexcasting.debug.mana_withdrawn",
stack.getDisplayName(),
Component.translatable("hexcasting.debug.all_mana").withStyle(ChatFormatting.GRAY))
.withStyle(ChatFormatting.LIGHT_PURPLE));
} else {
entity.sendSystemMessage(Component.translatable("hexcasting.debug.mana_withdrawn.with_dust",
stack.getDisplayName(),
Component.literal("" + i).withStyle(ChatFormatting.WHITE),
Component.literal(String.format("%.2f", i * 1.0 / ManaConstants.DUST_UNIT)).withStyle(
ChatFormatting.WHITE))
.withStyle(ChatFormatting.LIGHT_PURPLE));
}
if (isDebug(stack, DISPLAY_MEDIA) && !level.isClientSide) {
debugDisplay(stack, TAG_EXTRACTIONS, "withdrawn", "all_mana", entity);
debugDisplay(stack, TAG_INSERTIONS, "inserted", "infinite_mana", entity);
}
}
private void debugDisplay(ItemStack stack, String tag, String langKey, String allKey, Entity entity) {
int[] arr = NBTHelper.getIntArray(stack, tag);
if (arr != null) {
NBTHelper.remove(stack, tag);
for (int i : arr) {
if (i < 0) {
entity.sendMessage(new TranslatableComponent("hexcasting.debug.mana_" + langKey,
stack.getDisplayName(),
new TranslatableComponent("hexcasting.debug." + allKey).withStyle(ChatFormatting.GRAY))
.withStyle(ChatFormatting.LIGHT_PURPLE), Util.NIL_UUID);
} else {
entity.sendMessage(new TranslatableComponent("hexcasting.debug.mana_" + langKey + ".with_dust",
stack.getDisplayName(),
new TextComponent("" + i).withStyle(ChatFormatting.WHITE),
new TextComponent(String.format("%.2f", i * 1.0 / ManaConstants.DUST_UNIT)).withStyle(
ChatFormatting.WHITE))
.withStyle(ChatFormatting.LIGHT_PURPLE), Util.NIL_UUID);
}
}
}
}
@Override
public InteractionResult useOn(UseOnContext context) {
BlockEntity be = context.getLevel().getBlockEntity(context.getClickedPos());
if (be instanceof BlockEntityAbstractImpetus impetus) {
impetus.setInfiniteMana();
context.getLevel().playSound(null, context.getClickedPos(), HexSounds.SPELL_CIRCLE_FIND_BLOCK, SoundSource.PLAYERS, 1f, 1f);
return InteractionResult.sidedSuccess(context.getLevel().isClientSide());
}
return InteractionResult.PASS;
}
@Override
public ItemStack finishUsingItem(ItemStack stack, Level level, LivingEntity consumer) {
if (level instanceof ServerLevel slevel && consumer instanceof ServerPlayer player) {
@ -151,8 +239,6 @@ public class ItemCreativeUnlocker extends Item implements MediaHolderItem {
return copy;
}
private static final TextColor HEX_COLOR = TextColor.fromRgb(0xb38ef3);
private static MutableComponent rainbow(MutableComponent component, int shift, Level level) {
if (level == null) {
return component.withStyle(ChatFormatting.WHITE);
@ -165,16 +251,14 @@ public class ItemCreativeUnlocker extends Item implements MediaHolderItem {
@Override
public void appendHoverText(ItemStack stack, @Nullable Level level, List<Component> tooltipComponents,
TooltipFlag isAdvanced) {
String prefix = "item.hexcasting.creative_unlocker.";
Component emphasized = infiniteMedia(level);
MutableComponent modName = Component.translatable(prefix + "mod_name").withStyle(
(s) -> s.withColor(HEX_COLOR));
MutableComponent modName = new TranslatableComponent("item.hexcasting.creative_unlocker.mod_name").withStyle(
(s) -> s.withColor(ItemManaHolder.HEX_COLOR));
tooltipComponents.add(
Component.translatable(prefix + "tooltip.0", emphasized).withStyle(ChatFormatting.GRAY));
tooltipComponents.add(Component.translatable(prefix + "tooltip.1", modName).withStyle(ChatFormatting.GRAY));
new TranslatableComponent("hexcasting.spelldata.onitem", emphasized).withStyle(ChatFormatting.GRAY));
tooltipComponents.add(new TranslatableComponent("item.hexcasting.creative_unlocker.tooltip", modName).withStyle(ChatFormatting.GRAY));
}
private static void addChildren(Advancement root, List<Advancement> out) {

View file

@ -1,10 +1,14 @@
package at.petrak.hexcasting.common.items.magic;
import at.petrak.hexcasting.api.item.ManaHolderItem;
import at.petrak.hexcasting.api.misc.ManaConstants;
import at.petrak.hexcasting.api.item.MediaHolderItem;
import at.petrak.hexcasting.api.utils.ManaHelper;
import at.petrak.hexcasting.api.utils.NBTHelper;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TextColor;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.util.Mth;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
@ -12,12 +16,24 @@ import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.Nullable;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.List;
public abstract class ItemMediaHolder extends Item implements MediaHolderItem {
public static final String TAG_MANA = "hexcasting:mana";
public static final String TAG_MAX_MANA = "hexcasting:start_mana";
public static final TextColor HEX_COLOR = TextColor.fromRgb(0xb38ef3);
private static final DecimalFormat PERCENTAGE = new DecimalFormat("####");
static {
PERCENTAGE.setRoundingMode(RoundingMode.DOWN);
}
private static final DecimalFormat DUST_AMOUNT = new DecimalFormat("###,###.##");
public ItemMediaHolder(Properties pProperties) {
super(pProperties);
}
@ -74,12 +90,24 @@ public abstract class ItemMediaHolder extends Item implements MediaHolderItem {
@Override
public void appendHoverText(ItemStack pStack, @Nullable Level pLevel, List<Component> pTooltipComponents,
TooltipFlag pIsAdvanced) {
if (pIsAdvanced.isAdvanced() && getMaxMedia(pStack) > 0) {
var maxMana = getMaxMana(pStack);
if (maxMana > 0) {
var mana = getMana(pStack);
var fullness = getManaFullness(pStack);
var color = TextColor.fromRgb(ManaHelper.manaBarColor(mana, maxMana));
var manaAmount = new TextComponent(DUST_AMOUNT.format(mana / (float) ManaConstants.DUST_UNIT));
var percentFull = new TextComponent(PERCENTAGE.format(100f * fullness) + "%");
var maxCapacity = new TranslatableComponent("hexcasting.tooltip.mana", DUST_AMOUNT.format(maxMana / (float) ManaConstants.DUST_UNIT));
manaAmount.withStyle(style -> style.withColor(HEX_COLOR));
maxCapacity.withStyle(style -> style.withColor(HEX_COLOR));
percentFull.withStyle(style -> style.withColor(color));
pTooltipComponents.add(
Component.translatable("item.hexcasting.manaholder.amount",
String.format("%,d", getMedia(pStack)),
String.format("%,d", getMaxMedia(pStack)),
100f * getManaFullness(pStack)).withStyle(ChatFormatting.GRAY));
new TranslatableComponent("hexcasting.tooltip.mana_amount.advanced",
manaAmount, maxCapacity, percentFull));
}
super.appendHoverText(pStack, pLevel, pTooltipComponents, pIsAdvanced);

View file

@ -0,0 +1,16 @@
package at.petrak.hexcasting.common.lib;
import net.minecraft.core.Registry;
import net.minecraft.tags.TagKey;
import net.minecraft.world.entity.EntityType;
import static at.petrak.hexcasting.api.HexAPI.modLoc;
public class HexEntityTags {
public static final TagKey<EntityType<?>> STICKY_TELEPORTERS = create("sticky_teleporters");
public static final TagKey<EntityType<?>> CANNOT_TELEPORT = create("cannot_teleport");
public static TagKey<EntityType<?>> create(String name) {
return TagKey.create(Registry.ENTITY_TYPE_REGISTRY, modLoc(name));
}
}

View file

@ -65,7 +65,7 @@ public class HexItems {
public static final ItemSlate SLATE = make("slate", new ItemSlate(HexBlocks.SLATE, props()));
public static final ItemMediaBattery BATTERY = make("battery",
new ItemMediaBattery(new Item.Properties().stacksTo(1)));
new ItemMediaBattery(unstackable()));
public static final EnumMap<DyeColor, ItemDyeColorizer> DYE_COLORIZERS = Util.make(() -> {
var out = new EnumMap<DyeColor, ItemDyeColorizer>(DyeColor.class);

View file

@ -20,13 +20,14 @@ import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.OptionalInt;
import java.util.Random;
public class AkashicTreeGrower extends AbstractTreeGrower {
public static final AkashicTreeGrower INSTANCE = new AkashicTreeGrower();
public static final List<Holder<ConfiguredFeature<TreeConfiguration, ?>>> GROWERS = Lists.newArrayList();
static {
public static void init() {
GROWERS.add(buildTreeFeature(HexBlocks.AMETHYST_EDIFIED_LEAVES, "1"));
GROWERS.add(buildTreeFeature(HexBlocks.AVENTURINE_EDIFIED_LEAVES, "2"));
GROWERS.add(buildTreeFeature(HexBlocks.CITRINE_EDIFIED_LEAVES, "3"));

View file

@ -16,7 +16,7 @@ import static at.petrak.hexcasting.api.HexAPI.modLoc;
/**
* Sent server->client when the player finishes casting a spell.
*/
public record MsgNewSpellPatternAck(ControllerInfo info) implements IMessage {
public record MsgNewSpellPatternAck(ControllerInfo info, int index) implements IMessage {
public static final ResourceLocation ID = modLoc("pat_sc");
@Override
@ -30,6 +30,7 @@ public record MsgNewSpellPatternAck(ControllerInfo info) implements IMessage {
var wasSpellCast = buf.readBoolean();
var isStackEmpty = buf.readBoolean();
var resolutionType = buf.readEnum(ResolvedPatternType.class);
var index = buf.readInt();
var stack = buf.readList(FriendlyByteBuf::readNbt);
var parens = buf.readList(FriendlyByteBuf::readNbt);
@ -38,7 +39,7 @@ public record MsgNewSpellPatternAck(ControllerInfo info) implements IMessage {
var parenCount = buf.readVarInt();
return new MsgNewSpellPatternAck(
new ControllerInfo(wasSpellCast, isStackEmpty, resolutionType, stack, parens, raven, parenCount)
new ControllerInfo(wasSpellCast, isStackEmpty, resolutionType, stack, parens, raven, parenCount), index
);
}
@ -47,6 +48,7 @@ public record MsgNewSpellPatternAck(ControllerInfo info) implements IMessage {
buf.writeBoolean(this.info.getMakesCastSound());
buf.writeBoolean(this.info.isStackClear());
buf.writeEnum(this.info.getResolutionType());
buf.writeInt(this.index);
buf.writeCollection(this.info.getStack(), FriendlyByteBuf::writeNbt);
buf.writeCollection(this.info.getParenthesized(), FriendlyByteBuf::writeNbt);
@ -69,7 +71,7 @@ public record MsgNewSpellPatternAck(ControllerInfo info) implements IMessage {
if (self.info().isStackClear()) {
mc.setScreen(null);
} else {
spellGui.recvServerUpdate(self.info());
spellGui.recvServerUpdate(self.info(), self.index());
}
}
}

View file

@ -110,7 +110,7 @@ public record MsgNewSpellPatternSyn(InteractionHand handUsed, HexPattern pattern
IXplatAbstractions.INSTANCE.setPatterns(sender, resolvedPatterns);
}
IXplatAbstractions.INSTANCE.sendPacketToPlayer(sender, new MsgNewSpellPatternAck(clientInfo));
IXplatAbstractions.INSTANCE.sendPacketToPlayer(sender, new MsgNewSpellPatternAck(clientInfo, resolvedPatterns.size() - 1));
}
});
}

View file

@ -1,5 +1,6 @@
package at.petrak.hexcasting.common.network;
import at.petrak.hexcasting.api.utils.HexUtils;
import at.petrak.hexcasting.common.entities.EntityWallScroll;
import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
@ -34,7 +35,7 @@ public record MsgNewWallScrollAck(ClientboundAddEntityPacket inner, BlockPos pos
public static MsgNewWallScrollAck deserialize(FriendlyByteBuf buf) {
var inner = new ClientboundAddEntityPacket(buf);
var pos = buf.readBlockPos();
var dir = Direction.values()[buf.readByte()];
var dir = HexUtils.getSafe(Direction.values(), buf.readByte());
var scroll = buf.readItem();
var strokeOrder = buf.readBoolean();
var blockSize = buf.readVarInt();

View file

@ -12,7 +12,7 @@ import static at.petrak.hexcasting.api.HexAPI.modLoc;
/**
* Sent server->client when a player is looking at a block through a lens whose comparator value is not the same as what they last saw.
*/
public record MsgUpdateComparatorVisualsAck(BlockPos pos, int value) implements IMessage {
public record MsgUpdateComparatorVisualsAck(BlockPos pos, int comparator, int bee) implements IMessage {
public static final ResourceLocation ID = modLoc("cmp");
@Override
@ -23,15 +23,17 @@ public record MsgUpdateComparatorVisualsAck(BlockPos pos, int value) implements
public static MsgUpdateComparatorVisualsAck deserialize(ByteBuf buffer) {
var buf = new FriendlyByteBuf(buffer);
int value = buf.readInt();
BlockPos pos = value == -1 ? null : buf.readBlockPos();
int comparator = buf.readInt();
int bee = buf.readInt();
BlockPos pos = comparator == -1 && bee == -1 ? null : buf.readBlockPos();
return new MsgUpdateComparatorVisualsAck(pos, value);
return new MsgUpdateComparatorVisualsAck(pos, comparator, bee);
}
public void serialize(FriendlyByteBuf buf) {
buf.writeInt(this.value);
if (this.value != -1) {
buf.writeInt(this.comparator);
buf.writeInt(this.bee);
if (this.comparator != -1 || this.bee != -1) {
buf.writeBlockPos(this.pos);
}
}
@ -40,7 +42,7 @@ public record MsgUpdateComparatorVisualsAck(BlockPos pos, int value) implements
Minecraft.getInstance().execute(new Runnable() {
@Override
public void run() {
ScryingLensOverlayRegistry.receiveComparatorValue(msg.pos(), msg.value());
ScryingLensOverlayRegistry.receiveComparatorAndBeeValue(msg.pos(), msg.comparator(), msg.bee());
}
});
}

View file

@ -7,12 +7,17 @@ import at.petrak.hexcasting.api.advancements.SpendManaTrigger;
import at.petrak.hexcasting.api.misc.ManaConstants;
import at.petrak.hexcasting.common.items.ItemLoreFragment;
import at.petrak.hexcasting.common.lib.HexBlocks;
import at.petrak.hexcasting.api.mod.HexItemTags;
import at.petrak.hexcasting.common.lib.HexItems;
import at.petrak.paucal.api.datagen.PaucalAdvancementProvider;
import net.minecraft.advancements.Advancement;
import net.minecraft.advancements.DisplayInfo;
import net.minecraft.advancements.FrameType;
import net.minecraft.advancements.critereon.*;
import net.minecraft.advancements.critereon.EntityPredicate;
import net.minecraft.advancements.critereon.InventoryChangeTrigger;
import net.minecraft.advancements.critereon.ItemPredicate;
import net.minecraft.advancements.critereon.MinMaxBounds;
import net.minecraft.data.DataGenerator;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
@ -37,8 +42,8 @@ public class HexAdvancements extends PaucalAdvancementProvider {
new ResourceLocation("minecraft", "textures/block/calcite.png"),
FrameType.TASK, true, true, true))
// the only thing making this vaguely tolerable is the knowledge the json files are worse somehow
.addCriterion("has_charged_amethyst",
InventoryChangeTrigger.TriggerInstance.hasItems(HexItems.CHARGED_AMETHYST))
.addCriterion("has_charged_amethyst", InventoryChangeTrigger.TriggerInstance.hasItems(
ItemPredicate.Builder.item().of(HexItemTags.GRANTS_ROOT_ADVANCEMENT).build()))
.save(consumer, prefix("root")); // how the hell does one even read this
// weird names so we have alphabetical parity

View file

@ -31,6 +31,8 @@ public class HexItemTagProvider extends PaucalItemTagProvider {
HexItems.STAFF_CRIMSON, HexItems.STAFF_WARPED);
tag(HexItemTags.PHIAL_BASE).add(Items.GLASS_BOTTLE);
tag(HexItemTags.GRANTS_ROOT_ADVANCEMENT).add(HexItems.AMETHYST_DUST, Items.AMETHYST_SHARD,
HexItems.CHARGED_AMETHYST);
this.copy(HexBlockTags.EDIFIED_LOGS, HexItemTags.EDIFIED_LOGS);
this.copy(HexBlockTags.EDIFIED_PLANKS, HexItemTags.EDIFIED_PLANKS);

View file

@ -0,0 +1,9 @@
package at.petrak.hexcasting.datagen;
import net.minecraft.data.recipes.RecipeBuilder;
public interface IXplatConditionsBuilder extends RecipeBuilder {
IXplatConditionsBuilder whenModLoaded(String modid);
IXplatConditionsBuilder whenModMissing(String modid);
}

View file

@ -1,5 +1,6 @@
package at.petrak.hexcasting.datagen;
import at.petrak.hexcasting.datagen.recipe.builders.FarmersDelightToolIngredient;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.item.crafting.Ingredient;
@ -23,4 +24,10 @@ public interface IXplatIngredients {
EnumMap<DyeColor, Ingredient> dyes();
Ingredient stick();
Ingredient whenModIngredient(Ingredient defaultIngredient, String modid, Ingredient modIngredient);
FarmersDelightToolIngredient axeStrip();
FarmersDelightToolIngredient axeDig();
}

View file

@ -1,388 +0,0 @@
package at.petrak.hexcasting.datagen.recipe;
import at.petrak.hexcasting.api.HexAPI;
import at.petrak.hexcasting.api.advancements.OvercastTrigger;
import at.petrak.hexcasting.api.mod.HexItemTags;
import at.petrak.hexcasting.common.items.ItemStaff;
import at.petrak.hexcasting.common.items.colorizer.ItemPrideColorizer;
import at.petrak.hexcasting.common.lib.HexBlocks;
import at.petrak.hexcasting.common.lib.HexItems;
import at.petrak.hexcasting.common.recipe.SealFocusRecipe;
import at.petrak.hexcasting.common.recipe.SealSpellbookRecipe;
import at.petrak.hexcasting.common.recipe.ingredient.StateIngredientHelper;
import at.petrak.hexcasting.common.recipe.ingredient.VillagerIngredient;
import at.petrak.hexcasting.datagen.IXplatIngredients;
import at.petrak.hexcasting.datagen.recipe.builders.BrainsweepRecipeBuilder;
import at.petrak.paucal.api.datagen.PaucalRecipeProvider;
import net.minecraft.advancements.critereon.EntityPredicate;
import net.minecraft.advancements.critereon.MinMaxBounds;
import net.minecraft.core.Registry;
import net.minecraft.data.DataGenerator;
import net.minecraft.data.recipes.*;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.ItemTags;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.item.DyeItem;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.crafting.SimpleRecipeSerializer;
import net.minecraft.world.level.block.Blocks;
import java.util.function.Consumer;
public class HexplatRecipes extends PaucalRecipeProvider {
public DataGenerator generator;
public IXplatIngredients ingredients;
public HexplatRecipes(DataGenerator pGenerator, IXplatIngredients ingredients) {
super(pGenerator, HexAPI.MOD_ID);
this.generator = pGenerator;
this.ingredients = ingredients;
}
protected void makeRecipes(Consumer<FinishedRecipe> recipes) {
specialRecipe(recipes, SealFocusRecipe.SERIALIZER);
specialRecipe(recipes, SealSpellbookRecipe.SERIALIZER);
wandRecipe(recipes, HexItems.STAFF_OAK, Items.OAK_PLANKS);
wandRecipe(recipes, HexItems.STAFF_BIRCH, Items.BIRCH_PLANKS);
wandRecipe(recipes, HexItems.STAFF_SPRUCE, Items.SPRUCE_PLANKS);
wandRecipe(recipes, HexItems.STAFF_JUNGLE, Items.JUNGLE_PLANKS);
wandRecipe(recipes, HexItems.STAFF_DARK_OAK, Items.DARK_OAK_PLANKS);
wandRecipe(recipes, HexItems.STAFF_ACACIA, Items.ACACIA_PLANKS);
wandRecipe(recipes, HexItems.STAFF_CRIMSON, Items.CRIMSON_PLANKS);
wandRecipe(recipes, HexItems.STAFF_WARPED, Items.WARPED_PLANKS);
wandRecipe(recipes, HexItems.STAFF_EDIFIED, HexBlocks.EDIFIED_PLANKS.asItem());
ringCornered(HexItems.FOCUS, 1, ingredients.glowstoneDust(),
ingredients.leather(), Ingredient.of(HexItems.CHARGED_AMETHYST))
.unlockedBy("has_item", hasItem(HexItemTags.STAVES))
.save(recipes);
ShapedRecipeBuilder.shaped(HexItems.SPELLBOOK)
.define('N', ingredients.goldNugget())
.define('B', Items.WRITABLE_BOOK)
.define('A', HexItems.CHARGED_AMETHYST)
.define('F', Items.CHORUS_FRUIT) // i wanna gate this behind the end SOMEHOW
// hey look its my gender ^^
.pattern("NBA")
.pattern("NFA")
.pattern("NBA")
.unlockedBy("has_focus", hasItem(HexItems.FOCUS))
.unlockedBy("has_chorus", hasItem(Items.CHORUS_FRUIT)).save(recipes);
ringCornerless(HexItems.CYPHER, 1,
ingredients.copperIngot(),
Ingredient.of(HexItems.AMETHYST_DUST))
.unlockedBy("has_item", hasItem(HexItemTags.STAVES)).save(recipes);
ringCornerless(HexItems.TRINKET, 1,
ingredients.ironIngot(),
Ingredient.of(Items.AMETHYST_SHARD))
.unlockedBy("has_item", hasItem(HexItemTags.STAVES)).save(recipes);
ShapedRecipeBuilder.shaped(HexItems.ARTIFACT)
.define('F', ingredients.goldIngot())
.define('A', HexItems.CHARGED_AMETHYST)
// why in god's name does minecraft have two different places for item tags
.define('D', ItemTags.MUSIC_DISCS)
.pattern(" F ")
.pattern("FAF")
.pattern(" D ")
.unlockedBy("has_item", hasItem(HexItemTags.STAVES)).save(recipes);
ringCornerless(HexItems.SCRYING_LENS, 1, Items.GLASS, HexItems.AMETHYST_DUST)
.unlockedBy("has_item", hasItem(HexItemTags.STAVES)).save(recipes);
ShapedRecipeBuilder.shaped(HexItems.ABACUS)
.define('S', Items.STICK)
.define('A', Items.AMETHYST_SHARD)
.define('W', ItemTags.PLANKS)
.pattern("WAW")
.pattern("SAS")
.pattern("WAW")
.unlockedBy("has_item", hasItem(HexItemTags.STAVES)).save(recipes);
// Why am I like this
ShapedRecipeBuilder.shaped(HexItems.SUBMARINE_SANDWICH)
.define('S', Items.STICK)
.define('A', Items.AMETHYST_SHARD)
.define('C', Items.COOKED_BEEF)
.define('B', Items.BREAD)
.pattern(" SA")
.pattern(" C ")
.pattern(" B ")
.unlockedBy("has_item", hasItem(Items.AMETHYST_SHARD)).save(recipes);
for (var dye : DyeColor.values()) {
var item = HexItems.DYE_COLORIZERS.get(dye);
ShapedRecipeBuilder.shaped(item)
.define('D', HexItems.AMETHYST_DUST)
.define('C', DyeItem.byColor(dye))
.pattern(" D ")
.pattern("DCD")
.pattern(" D ")
.unlockedBy("has_item", hasItem(HexItems.AMETHYST_DUST)).save(recipes);
}
gayRecipe(recipes, ItemPrideColorizer.Type.AGENDER, Items.GLASS);
gayRecipe(recipes, ItemPrideColorizer.Type.AROACE, Items.WHEAT_SEEDS);
gayRecipe(recipes, ItemPrideColorizer.Type.AROMANTIC, Items.ARROW);
gayRecipe(recipes, ItemPrideColorizer.Type.ASEXUAL, Items.BREAD);
gayRecipe(recipes, ItemPrideColorizer.Type.BISEXUAL, Items.WHEAT);
gayRecipe(recipes, ItemPrideColorizer.Type.DEMIBOY, Items.RAW_IRON);
gayRecipe(recipes, ItemPrideColorizer.Type.DEMIGIRL, Items.RAW_COPPER);
gayRecipe(recipes, ItemPrideColorizer.Type.GAY, Items.STONE_BRICK_WALL);
gayRecipe(recipes, ItemPrideColorizer.Type.GENDERFLUID, Items.WATER_BUCKET);
gayRecipe(recipes, ItemPrideColorizer.Type.GENDERQUEER, Items.GLASS_BOTTLE);
gayRecipe(recipes, ItemPrideColorizer.Type.INTERSEX, Items.AZALEA);
gayRecipe(recipes, ItemPrideColorizer.Type.LESBIAN, Items.HONEYCOMB);
gayRecipe(recipes, ItemPrideColorizer.Type.NONBINARY, Items.MOSS_BLOCK);
gayRecipe(recipes, ItemPrideColorizer.Type.PANSEXUAL, Items.CARROT);
gayRecipe(recipes, ItemPrideColorizer.Type.PLURAL, Items.REPEATER);
gayRecipe(recipes, ItemPrideColorizer.Type.TRANSGENDER, Items.EGG);
ShapedRecipeBuilder.shaped(HexItems.UUID_COLORIZER)
.define('B', Items.BOWL)
.define('D', HexItems.AMETHYST_DUST)
.define('C', Items.AMETHYST_SHARD)
.pattern(" C ")
.pattern(" D ")
.pattern(" B ")
.unlockedBy("has_item", hasItem(HexItems.AMETHYST_DUST)).save(recipes);
ShapedRecipeBuilder.shaped(HexItems.SCROLL_SMOL)
.define('P', Items.PAPER)
.define('A', Items.AMETHYST_SHARD)
.pattern(" A")
.pattern("P ")
.unlockedBy("has_item", hasItem(Items.AMETHYST_SHARD)).save(recipes);
ShapedRecipeBuilder.shaped(HexItems.SCROLL_MEDIUM)
.define('P', Items.PAPER)
.define('A', Items.AMETHYST_SHARD)
.pattern(" A")
.pattern("PP ")
.pattern("PP ")
.unlockedBy("has_item", hasItem(Items.AMETHYST_SHARD)).save(recipes);
ShapedRecipeBuilder.shaped(HexItems.SCROLL_LARGE)
.define('P', Items.PAPER)
.define('A', Items.AMETHYST_SHARD)
.pattern("PPA")
.pattern("PPP")
.pattern("PPP")
.unlockedBy("has_item", hasItem(Items.AMETHYST_SHARD)).save(recipes);
ShapedRecipeBuilder.shaped(HexItems.SLATE, 6)
.define('S', Items.DEEPSLATE)
.define('A', HexItems.AMETHYST_DUST)
.pattern(" A ")
.pattern("SSS")
.unlockedBy("has_item", hasItem(HexItems.AMETHYST_DUST)).save(recipes);
ShapedRecipeBuilder.shaped(HexItems.JEWELER_HAMMER)
.define('I', ingredients.ironIngot())
.define('N', ingredients.ironNugget())
.define('A', Items.AMETHYST_SHARD)
.define('S', ingredients.stick())
.pattern("IAN")
.pattern(" S ")
.pattern(" S ")
.unlockedBy("has_item", hasItem(Items.AMETHYST_SHARD)).save(recipes);
ShapedRecipeBuilder.shaped(HexBlocks.SLATE_BLOCK)
.define('S', HexItems.SLATE)
.pattern("S")
.pattern("S")
.unlockedBy("has_item", hasItem(HexItems.SLATE))
.save(recipes, modLoc("slate_block_from_slates"));
ringAll(HexBlocks.SLATE_BLOCK, 8, Blocks.DEEPSLATE, HexItems.AMETHYST_DUST)
.unlockedBy("has_item", hasItem(HexItems.SLATE)).save(recipes);
packing(HexItems.AMETHYST_DUST, HexBlocks.AMETHYST_DUST_BLOCK.asItem(), "amethyst_dust",
false, recipes);
ringAll(HexBlocks.AMETHYST_TILES, 8, Blocks.AMETHYST_BLOCK, HexItems.AMETHYST_DUST)
.unlockedBy("has_item", hasItem(HexItems.AMETHYST_DUST)).save(recipes);
SingleItemRecipeBuilder.stonecutting(Ingredient.of(Blocks.AMETHYST_BLOCK), HexBlocks.AMETHYST_TILES)
.unlockedBy("has_item", hasItem(Blocks.AMETHYST_BLOCK))
.save(recipes, modLoc("stonecutting/amethyst_tiles"));
ringAll(HexBlocks.SCROLL_PAPER, 8, Items.PAPER, Items.AMETHYST_SHARD)
.unlockedBy("has_item", hasItem(Items.AMETHYST_SHARD)).save(recipes);
ShapelessRecipeBuilder.shapeless(HexBlocks.ANCIENT_SCROLL_PAPER, 8)
.requires(ingredients.dyes().get(DyeColor.BROWN))
.requires(HexBlocks.SCROLL_PAPER, 8)
.unlockedBy("has_item", hasItem(HexBlocks.SCROLL_PAPER)).save(recipes);
stack(HexBlocks.SCROLL_PAPER_LANTERN, 1, HexBlocks.SCROLL_PAPER, Items.TORCH)
.unlockedBy("has_item", hasItem(HexBlocks.SCROLL_PAPER)).save(recipes);
stack(HexBlocks.ANCIENT_SCROLL_PAPER_LANTERN, 1, HexBlocks.ANCIENT_SCROLL_PAPER, Items.TORCH)
.unlockedBy("has_item", hasItem(HexBlocks.ANCIENT_SCROLL_PAPER)).save(recipes);
ShapelessRecipeBuilder.shapeless(HexBlocks.ANCIENT_SCROLL_PAPER_LANTERN, 8)
.requires(ingredients.dyes().get(DyeColor.BROWN))
.requires(HexBlocks.SCROLL_PAPER_LANTERN, 8)
.unlockedBy("has_item", hasItem(HexBlocks.SCROLL_PAPER_LANTERN))
.save(recipes, modLoc("ageing_scroll_paper_lantern"));
stack(HexBlocks.SCONCE, 4, Ingredient.of(HexItems.CHARGED_AMETHYST),
ingredients.copperIngot())
.unlockedBy("has_item", hasItem(HexItems.CHARGED_AMETHYST)).save(recipes);
ShapelessRecipeBuilder.shapeless(HexBlocks.EDIFIED_PLANKS, 4)
.requires(HexItemTags.EDIFIED_LOGS)
.unlockedBy("has_item", hasItem(HexItemTags.EDIFIED_LOGS)).save(recipes);
ShapedRecipeBuilder.shaped(HexBlocks.EDIFIED_WOOD, 3)
.define('W', HexBlocks.EDIFIED_LOG)
.pattern("WW")
.pattern("WW")
.unlockedBy("has_item", hasItem(HexBlocks.EDIFIED_LOG)).save(recipes);
ShapedRecipeBuilder.shaped(HexBlocks.STRIPPED_EDIFIED_WOOD, 3)
.define('W', HexBlocks.STRIPPED_EDIFIED_LOG)
.pattern("WW")
.pattern("WW")
.unlockedBy("has_item", hasItem(HexBlocks.STRIPPED_EDIFIED_LOG)).save(recipes);
ring(HexBlocks.EDIFIED_PANEL, 8, HexItemTags.EDIFIED_PLANKS, null)
.unlockedBy("has_item", hasItem(HexItemTags.EDIFIED_PLANKS)).save(recipes);
ShapedRecipeBuilder.shaped(HexBlocks.EDIFIED_TILE, 6)
.define('W', HexItemTags.EDIFIED_PLANKS)
.pattern("WW ")
.pattern("W W")
.pattern(" WW")
.unlockedBy("has_item", hasItem(HexItemTags.EDIFIED_PLANKS)).save(recipes);
ShapedRecipeBuilder.shaped(HexBlocks.EDIFIED_DOOR, 3)
.define('W', HexItemTags.EDIFIED_PLANKS)
.pattern("WW")
.pattern("WW")
.pattern("WW")
.unlockedBy("has_item", hasItem(HexItemTags.EDIFIED_PLANKS)).save(recipes);
ShapedRecipeBuilder.shaped(HexBlocks.EDIFIED_TRAPDOOR, 2)
.define('W', HexItemTags.EDIFIED_PLANKS)
.pattern("WWW")
.pattern("WWW")
.unlockedBy("has_item", hasItem(HexItemTags.EDIFIED_PLANKS)).save(recipes);
ShapedRecipeBuilder.shaped(HexBlocks.EDIFIED_STAIRS, 4)
.define('W', HexItemTags.EDIFIED_PLANKS)
.pattern("W ")
.pattern("WW ")
.pattern("WWW")
.unlockedBy("has_item", hasItem(HexItemTags.EDIFIED_PLANKS)).save(recipes);
ShapedRecipeBuilder.shaped(HexBlocks.EDIFIED_SLAB, 6)
.define('W', HexItemTags.EDIFIED_PLANKS)
.pattern("WWW")
.unlockedBy("has_item", hasItem(HexItemTags.EDIFIED_PLANKS)).save(recipes);
ShapedRecipeBuilder.shaped(HexBlocks.EDIFIED_PRESSURE_PLATE, 1)
.define('W', HexItemTags.EDIFIED_PLANKS)
.pattern("WW")
.unlockedBy("has_item", hasItem(HexItemTags.EDIFIED_PLANKS)).save(recipes);
ShapelessRecipeBuilder.shapeless(HexBlocks.EDIFIED_BUTTON)
.requires(HexItemTags.EDIFIED_PLANKS)
.unlockedBy("has_item", hasItem(HexItemTags.EDIFIED_PLANKS)).save(recipes);
var enlightenment = new OvercastTrigger.Instance(EntityPredicate.Composite.ANY,
MinMaxBounds.Ints.ANY,
// add a little bit of slop here
MinMaxBounds.Doubles.atLeast(0.8),
MinMaxBounds.Doubles.between(0.1, 2.05));
ShapedRecipeBuilder.shaped(HexBlocks.EMPTY_IMPETUS)
.define('B', Items.IRON_BARS)
.define('A', HexItems.CHARGED_AMETHYST)
.define('S', HexBlocks.SLATE_BLOCK)
.define('P', Items.PURPUR_BLOCK)
.pattern("PSS")
.pattern("BAB")
.pattern("SSP")
.unlockedBy("enlightenment", enlightenment).save(recipes);
ShapedRecipeBuilder.shaped(HexBlocks.EMPTY_DIRECTRIX)
.define('C', Items.COMPARATOR)
.define('O', Items.OBSERVER)
.define('A', HexItems.CHARGED_AMETHYST)
.define('S', HexBlocks.SLATE_BLOCK)
.pattern("CSS")
.pattern("OAO")
.pattern("SSC")
.unlockedBy("enlightenment", enlightenment).save(recipes);
ShapedRecipeBuilder.shaped(HexBlocks.AKASHIC_BOOKSHELF)
.define('L', HexItemTags.EDIFIED_LOGS)
.define('P', HexItemTags.EDIFIED_PLANKS)
.define('C', Items.BOOK)
/*this is the*/.pattern("LPL") // and what i have for you today is
.pattern("CCC")
.pattern("LPL")
.unlockedBy("enlightenment", enlightenment).save(recipes);
ShapedRecipeBuilder.shaped(HexBlocks.AKASHIC_LIGATURE)
.define('L', HexItemTags.EDIFIED_LOGS)
.define('P', HexItemTags.EDIFIED_PLANKS)
.define('C', HexItems.CHARGED_AMETHYST)
.pattern("LPL")
.pattern("CCC")
.pattern("LPL")
.unlockedBy("enlightenment", enlightenment).save(recipes);
new BrainsweepRecipeBuilder(StateIngredientHelper.of(Blocks.AMETHYST_BLOCK),
new VillagerIngredient(null, null, 3),
Blocks.BUDDING_AMETHYST.defaultBlockState())
.unlockedBy("enlightenment", enlightenment)
.save(recipes, modLoc("brainsweep/budding_amethyst"));
new BrainsweepRecipeBuilder(StateIngredientHelper.of(HexBlocks.EMPTY_IMPETUS),
new VillagerIngredient(new ResourceLocation("toolsmith"), null, 2),
HexBlocks.IMPETUS_RIGHTCLICK.defaultBlockState())
.unlockedBy("enlightenment", enlightenment)
.save(recipes, modLoc("brainsweep/impetus_rightclick"));
new BrainsweepRecipeBuilder(StateIngredientHelper.of(HexBlocks.EMPTY_IMPETUS),
new VillagerIngredient(new ResourceLocation("fletcher"), null, 2),
HexBlocks.IMPETUS_LOOK.defaultBlockState())
.unlockedBy("enlightenment", enlightenment)
.save(recipes, modLoc("brainsweep/impetus_look"));
new BrainsweepRecipeBuilder(StateIngredientHelper.of(HexBlocks.EMPTY_IMPETUS),
new VillagerIngredient(new ResourceLocation("cleric"), null, 2),
HexBlocks.IMPETUS_STOREDPLAYER.defaultBlockState())
.unlockedBy("enlightenment", enlightenment)
.save(recipes, modLoc("brainsweep/impetus_storedplayer"));
new BrainsweepRecipeBuilder(StateIngredientHelper.of(HexBlocks.EMPTY_DIRECTRIX),
new VillagerIngredient(new ResourceLocation("mason"), null, 1),
HexBlocks.DIRECTRIX_REDSTONE.defaultBlockState())
.unlockedBy("enlightenment", enlightenment)
.save(recipes, modLoc("brainsweep/directrix_redstone"));
new BrainsweepRecipeBuilder(StateIngredientHelper.of(HexBlocks.AKASHIC_LIGATURE),
new VillagerIngredient(new ResourceLocation("librarian"), null, 5),
HexBlocks.AKASHIC_RECORD.defaultBlockState())
.unlockedBy("enlightenment", enlightenment)
.save(recipes, modLoc("brainsweep/akashic_record"));
}
private void wandRecipe(Consumer<FinishedRecipe> recipes, ItemStaff wand, Item plank) {
ShapedRecipeBuilder.shaped(wand)
.define('W', plank)
.define('S', Items.STICK)
.define('A', HexItems.CHARGED_AMETHYST)
.pattern(" SA")
.pattern(" WS")
.pattern("S ")
.unlockedBy("has_item", hasItem(HexItems.CHARGED_AMETHYST))
.save(recipes);
}
private void gayRecipe(Consumer<FinishedRecipe> recipes, ItemPrideColorizer.Type type, Item material) {
var colorizer = HexItems.PRIDE_COLORIZERS.get(type);
ShapedRecipeBuilder.shaped(colorizer)
.define('D', HexItems.AMETHYST_DUST)
.define('C', material)
.pattern(" D ")
.pattern("DCD")
.pattern(" D ")
.unlockedBy("has_item", hasItem(HexItems.AMETHYST_DUST)).save(recipes);
}
protected void specialRecipe(Consumer<FinishedRecipe> consumer, SimpleRecipeSerializer<?> serializer) {
var name = Registry.RECIPE_SERIALIZER.getKey(serializer);
SpecialRecipeBuilder.special(serializer).save(consumer, HexAPI.MOD_ID + ":dynamic/" + name.getPath());
}
}

View file

@ -0,0 +1,486 @@
package at.petrak.hexcasting.datagen.recipe
import at.petrak.hexcasting.api.HexAPI
import at.petrak.hexcasting.api.advancements.OvercastTrigger
import at.petrak.hexcasting.api.mod.HexItemTags
import at.petrak.hexcasting.common.items.ItemWand
import at.petrak.hexcasting.common.items.colorizer.ItemPrideColorizer
import at.petrak.hexcasting.common.lib.HexBlocks
import at.petrak.hexcasting.common.lib.HexItems
import at.petrak.hexcasting.common.recipe.SealFocusRecipe
import at.petrak.hexcasting.common.recipe.SealSpellbookRecipe
import at.petrak.hexcasting.common.recipe.ingredient.StateIngredientHelper
import at.petrak.hexcasting.common.recipe.ingredient.VillagerIngredient
import at.petrak.hexcasting.datagen.IXplatConditionsBuilder
import at.petrak.hexcasting.datagen.IXplatIngredients
import at.petrak.hexcasting.datagen.recipe.builders.BrainsweepRecipeBuilder
import at.petrak.hexcasting.datagen.recipe.builders.CompatIngredientValue
import at.petrak.hexcasting.datagen.recipe.builders.CreateCrushingRecipeBuilder
import at.petrak.hexcasting.datagen.recipe.builders.FarmersDelightCuttingRecipeBuilder
import at.petrak.paucal.api.datagen.PaucalRecipeProvider
import net.minecraft.advancements.critereon.EntityPredicate
import net.minecraft.advancements.critereon.MinMaxBounds
import net.minecraft.core.Registry
import net.minecraft.data.DataGenerator
import net.minecraft.data.recipes.*
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvents
import net.minecraft.tags.ItemTags
import net.minecraft.world.item.DyeColor
import net.minecraft.world.item.DyeItem
import net.minecraft.world.item.Item
import net.minecraft.world.item.Items
import net.minecraft.world.item.crafting.Ingredient
import net.minecraft.world.item.crafting.SimpleRecipeSerializer
import net.minecraft.world.level.block.Blocks
import java.util.function.Consumer
class HexplatRecipes(
val generator: DataGenerator,
val ingredients: IXplatIngredients,
val conditions: (RecipeBuilder) -> IXplatConditionsBuilder
) : PaucalRecipeProvider(generator, HexAPI.MOD_ID) {
override fun makeRecipes(recipes: Consumer<FinishedRecipe>) {
specialRecipe(recipes, SealFocusRecipe.SERIALIZER)
specialRecipe(recipes, SealSpellbookRecipe.SERIALIZER)
wandRecipe(recipes, HexItems.WAND_OAK, Items.OAK_PLANKS)
wandRecipe(recipes, HexItems.WAND_BIRCH, Items.BIRCH_PLANKS)
wandRecipe(recipes, HexItems.WAND_SPRUCE, Items.SPRUCE_PLANKS)
wandRecipe(recipes, HexItems.WAND_JUNGLE, Items.JUNGLE_PLANKS)
wandRecipe(recipes, HexItems.WAND_DARK_OAK, Items.DARK_OAK_PLANKS)
wandRecipe(recipes, HexItems.WAND_ACACIA, Items.ACACIA_PLANKS)
wandRecipe(recipes, HexItems.WAND_CRIMSON, Items.CRIMSON_PLANKS)
wandRecipe(recipes, HexItems.WAND_WARPED, Items.WARPED_PLANKS)
wandRecipe(recipes, HexItems.WAND_AKASHIC, HexBlocks.AKASHIC_PLANKS.asItem())
ringCornered(HexItems.FOCUS, 1,
ingredients.glowstoneDust(),
ingredients.leather(),
Ingredient.of(HexItems.CHARGED_AMETHYST))
.unlockedBy("has_item", hasItem(HexItemTags.WANDS))
.save(recipes)
ShapedRecipeBuilder.shaped(HexItems.SPELLBOOK)
.define('N', ingredients.goldNugget())
.define('B', Items.WRITABLE_BOOK)
.define('A', HexItems.CHARGED_AMETHYST)
.define('F', Items.CHORUS_FRUIT) // i wanna gate this behind the end SOMEHOW
// hey look its my gender ^^
.pattern("NBA")
.pattern("NFA")
.pattern("NBA")
.unlockedBy("has_focus", hasItem(HexItems.FOCUS))
.unlockedBy("has_chorus", hasItem(Items.CHORUS_FRUIT)).save(recipes)
ringCornerless(
HexItems.CYPHER, 1,
ingredients.copperIngot(),
Ingredient.of(HexItems.AMETHYST_DUST))
.unlockedBy("has_item", hasItem(HexItemTags.WANDS)).save(recipes)
ringCornerless(
HexItems.TRINKET, 1,
ingredients.ironIngot(),
Ingredient.of(Items.AMETHYST_SHARD))
.unlockedBy("has_item", hasItem(HexItemTags.WANDS)).save(recipes)
ShapedRecipeBuilder.shaped(HexItems.ARTIFACT)
.define('F', ingredients.goldIngot())
.define('A', HexItems.CHARGED_AMETHYST)
// why in god's name does minecraft have two different places for item tags
.define('D', ItemTags.MUSIC_DISCS)
.pattern(" F ")
.pattern("FAF")
.pattern(" D ")
.unlockedBy("has_item", hasItem(HexItemTags.WANDS)).save(recipes)
ringCornerless(HexItems.SCRYING_LENS, 1, Items.GLASS, HexItems.AMETHYST_DUST)
.unlockedBy("has_item", hasItem(HexItemTags.WANDS)).save(recipes)
ShapedRecipeBuilder.shaped(HexItems.ABACUS)
.define('S', Items.STICK)
.define('A', Items.AMETHYST_SHARD)
.define('W', ItemTags.PLANKS)
.pattern("WAW")
.pattern("SAS")
.pattern("WAW")
.unlockedBy("has_item", hasItem(HexItemTags.WANDS)).save(recipes)
// Why am I like this
ShapedRecipeBuilder.shaped(HexItems.SUBMARINE_SANDWICH)
.define('S', Items.STICK)
.define('A', Items.AMETHYST_SHARD)
.define('C', Items.COOKED_BEEF)
.define('B', Items.BREAD)
.pattern(" SA")
.pattern(" C ")
.pattern(" B ")
.unlockedBy("has_item", hasItem(Items.AMETHYST_SHARD)).save(recipes)
for (dye in DyeColor.values()) {
val item = HexItems.DYE_COLORIZERS[dye]!!
ShapedRecipeBuilder.shaped(item)
.define('D', HexItems.AMETHYST_DUST)
.define('C', DyeItem.byColor(dye))
.pattern(" D ")
.pattern("DCD")
.pattern(" D ")
.unlockedBy("has_item", hasItem(HexItems.AMETHYST_DUST)).save(recipes)
}
gayRecipe(recipes, ItemPrideColorizer.Type.AGENDER, Ingredient.of(Items.GLASS))
gayRecipe(recipes, ItemPrideColorizer.Type.AROACE, Ingredient.of(Items.WHEAT_SEEDS))
gayRecipe(recipes, ItemPrideColorizer.Type.AROMANTIC, Ingredient.of(Items.ARROW))
gayRecipe(recipes, ItemPrideColorizer.Type.ASEXUAL, Ingredient.of(Items.BREAD))
gayRecipe(recipes, ItemPrideColorizer.Type.BISEXUAL, Ingredient.of(Items.WHEAT))
gayRecipe(recipes, ItemPrideColorizer.Type.DEMIBOY, Ingredient.of(Items.RAW_IRON))
gayRecipe(recipes, ItemPrideColorizer.Type.DEMIGIRL, Ingredient.of(Items.RAW_COPPER))
gayRecipe(recipes, ItemPrideColorizer.Type.GAY, Ingredient.of(Items.STONE_BRICK_WALL))
gayRecipe(recipes, ItemPrideColorizer.Type.GENDERFLUID, Ingredient.of(Items.WATER_BUCKET))
gayRecipe(recipes, ItemPrideColorizer.Type.GENDERQUEER, Ingredient.of(Items.GLASS_BOTTLE))
gayRecipe(recipes, ItemPrideColorizer.Type.INTERSEX, Ingredient.of(Items.AZALEA))
gayRecipe(recipes, ItemPrideColorizer.Type.LESBIAN, Ingredient.of(Items.HONEYCOMB))
gayRecipe(recipes, ItemPrideColorizer.Type.NONBINARY, Ingredient.of(Items.MOSS_BLOCK))
gayRecipe(recipes, ItemPrideColorizer.Type.PANSEXUAL, ingredients.whenModIngredient(
Ingredient.of(Items.CARROT),
"farmersdelight",
CompatIngredientValue.of("farmersdelight:skillet")
))
gayRecipe(recipes, ItemPrideColorizer.Type.PLURAL, Ingredient.of(Items.REPEATER))
gayRecipe(recipes, ItemPrideColorizer.Type.TRANSGENDER, Ingredient.of(Items.EGG))
ShapedRecipeBuilder.shaped(HexItems.UUID_COLORIZER)
.define('B', Items.BOWL)
.define('D', HexItems.AMETHYST_DUST)
.define('C', Items.AMETHYST_SHARD)
.pattern(" C ")
.pattern(" D ")
.pattern(" B ")
.unlockedBy("has_item", hasItem(HexItems.AMETHYST_DUST)).save(recipes)
ShapedRecipeBuilder.shaped(HexItems.SCROLL_SMOL)
.define('P', Items.PAPER)
.define('A', Items.AMETHYST_SHARD)
.pattern(" A")
.pattern("P ")
.unlockedBy("has_item", hasItem(Items.AMETHYST_SHARD)).save(recipes)
ShapedRecipeBuilder.shaped(HexItems.SCROLL_MEDIUM)
.define('P', Items.PAPER)
.define('A', Items.AMETHYST_SHARD)
.pattern(" A")
.pattern("PP ")
.pattern("PP ")
.unlockedBy("has_item", hasItem(Items.AMETHYST_SHARD)).save(recipes)
ShapedRecipeBuilder.shaped(HexItems.SCROLL_LARGE)
.define('P', Items.PAPER)
.define('A', Items.AMETHYST_SHARD)
.pattern("PPA")
.pattern("PPP")
.pattern("PPP")
.unlockedBy("has_item", hasItem(Items.AMETHYST_SHARD)).save(recipes)
ShapedRecipeBuilder.shaped(HexItems.SLATE, 6)
.define('S', Items.DEEPSLATE)
.define('A', HexItems.AMETHYST_DUST)
.pattern(" A ")
.pattern("SSS")
.unlockedBy("has_item", hasItem(HexItems.AMETHYST_DUST)).save(recipes)
ShapedRecipeBuilder.shaped(HexItems.JEWELER_HAMMER)
.define('I', ingredients.ironIngot())
.define('N', ingredients.ironNugget())
.define('A', Items.AMETHYST_SHARD)
.define('S', ingredients.stick())
.pattern("IAN")
.pattern(" S ")
.pattern(" S ")
.unlockedBy("has_item", hasItem(Items.AMETHYST_SHARD)).save(recipes)
ShapedRecipeBuilder.shaped(HexBlocks.SLATE_BLOCK)
.define('S', HexItems.SLATE)
.pattern("S")
.pattern("S")
.unlockedBy("has_item", hasItem(HexItems.SLATE))
.save(recipes, modLoc("slate_block_from_slates"))
ringAll(HexBlocks.SLATE_BLOCK, 8, Blocks.DEEPSLATE, HexItems.AMETHYST_DUST)
.unlockedBy("has_item", hasItem(HexItems.SLATE)).save(recipes)
packing(HexItems.AMETHYST_DUST, HexBlocks.AMETHYST_DUST_BLOCK.asItem(), "amethyst_dust",
false, recipes)
ringAll(HexBlocks.AMETHYST_TILES, 8, Blocks.AMETHYST_BLOCK, HexItems.AMETHYST_DUST)
.unlockedBy("has_item", hasItem(HexItems.AMETHYST_DUST)).save(recipes)
SingleItemRecipeBuilder.stonecutting(Ingredient.of(Blocks.AMETHYST_BLOCK), HexBlocks.AMETHYST_TILES)
.unlockedBy("has_item", hasItem(Blocks.AMETHYST_BLOCK))
.save(recipes, modLoc("stonecutting/amethyst_tiles"))
ringAll(HexBlocks.SCROLL_PAPER, 8, Items.PAPER, Items.AMETHYST_SHARD)
.unlockedBy("has_item", hasItem(Items.AMETHYST_SHARD)).save(recipes)
ShapelessRecipeBuilder.shapeless(HexBlocks.ANCIENT_SCROLL_PAPER, 8)
.requires(ingredients.dyes()[DyeColor.BROWN]!!)
.requires(HexBlocks.SCROLL_PAPER, 8)
.unlockedBy("has_item", hasItem(HexBlocks.SCROLL_PAPER)).save(recipes)
stack(HexBlocks.SCROLL_PAPER_LANTERN, 1, HexBlocks.SCROLL_PAPER, Items.TORCH)
.unlockedBy("has_item", hasItem(HexBlocks.SCROLL_PAPER)).save(recipes)
stack(HexBlocks.ANCIENT_SCROLL_PAPER_LANTERN, 1, HexBlocks.ANCIENT_SCROLL_PAPER, Items.TORCH)
.unlockedBy("has_item", hasItem(HexBlocks.ANCIENT_SCROLL_PAPER)).save(recipes)
ShapelessRecipeBuilder.shapeless(HexBlocks.ANCIENT_SCROLL_PAPER_LANTERN, 8)
.requires(ingredients.dyes()[DyeColor.BROWN]!!)
.requires(HexBlocks.SCROLL_PAPER_LANTERN, 8)
.unlockedBy("has_item", hasItem(HexBlocks.SCROLL_PAPER_LANTERN))
.save(recipes, modLoc("ageing_scroll_paper_lantern"))
stack(HexBlocks.SCONCE, 4,
Ingredient.of(HexItems.CHARGED_AMETHYST),
ingredients.copperIngot())
.unlockedBy("has_item", hasItem(HexItems.CHARGED_AMETHYST)).save(recipes)
ShapelessRecipeBuilder.shapeless(HexBlocks.AKASHIC_PLANKS, 4)
.requires(HexItemTags.AKASHIC_LOGS)
.unlockedBy("has_item", hasItem(HexItemTags.AKASHIC_LOGS)).save(recipes)
ShapedRecipeBuilder.shaped(HexBlocks.AKASHIC_WOOD, 3)
.define('W', HexBlocks.AKASHIC_LOG)
.pattern("WW")
.pattern("WW")
.unlockedBy("has_item", hasItem(HexBlocks.AKASHIC_LOG)).save(recipes)
ShapedRecipeBuilder.shaped(HexBlocks.AKASHIC_WOOD_STRIPPED, 3)
.define('W', HexBlocks.AKASHIC_LOG_STRIPPED)
.pattern("WW")
.pattern("WW")
.unlockedBy("has_item", hasItem(HexBlocks.AKASHIC_LOG_STRIPPED)).save(recipes)
ring(HexBlocks.AKASHIC_PANEL, 8,
HexItemTags.AKASHIC_PLANKS, null)
.unlockedBy("has_item", hasItem(HexItemTags.AKASHIC_PLANKS)).save(recipes)
ShapedRecipeBuilder.shaped(HexBlocks.AKASHIC_TILE, 6)
.define('W', HexItemTags.AKASHIC_PLANKS)
.pattern("WW ")
.pattern("W W")
.pattern(" WW")
.unlockedBy("has_item", hasItem(HexItemTags.AKASHIC_PLANKS)).save(recipes)
ShapedRecipeBuilder.shaped(HexBlocks.AKASHIC_DOOR, 3)
.define('W', HexItemTags.AKASHIC_PLANKS)
.pattern("WW")
.pattern("WW")
.pattern("WW")
.unlockedBy("has_item", hasItem(HexItemTags.AKASHIC_PLANKS)).save(recipes)
ShapedRecipeBuilder.shaped(HexBlocks.AKASHIC_TRAPDOOR, 2)
.define('W', HexItemTags.AKASHIC_PLANKS)
.pattern("WWW")
.pattern("WWW")
.unlockedBy("has_item", hasItem(HexItemTags.AKASHIC_PLANKS)).save(recipes)
ShapedRecipeBuilder.shaped(HexBlocks.AKASHIC_STAIRS, 4)
.define('W', HexItemTags.AKASHIC_PLANKS)
.pattern("W ")
.pattern("WW ")
.pattern("WWW")
.unlockedBy("has_item", hasItem(HexItemTags.AKASHIC_PLANKS)).save(recipes)
ShapedRecipeBuilder.shaped(HexBlocks.AKASHIC_SLAB, 6)
.define('W', HexItemTags.AKASHIC_PLANKS)
.pattern("WWW")
.unlockedBy("has_item", hasItem(HexItemTags.AKASHIC_PLANKS)).save(recipes)
ShapedRecipeBuilder.shaped(HexBlocks.AKASHIC_PRESSURE_PLATE, 1)
.define('W', HexItemTags.AKASHIC_PLANKS)
.pattern("WW")
.unlockedBy("has_item", hasItem(HexItemTags.AKASHIC_PLANKS)).save(recipes)
ShapelessRecipeBuilder.shapeless(HexBlocks.AKASHIC_BUTTON)
.requires(HexItemTags.AKASHIC_PLANKS)
.unlockedBy("has_item", hasItem(HexItemTags.AKASHIC_PLANKS)).save(recipes)
val enlightenment = OvercastTrigger.Instance(
EntityPredicate.Composite.ANY,
MinMaxBounds.Ints.ANY, // add a little bit of slop here
MinMaxBounds.Doubles.atLeast(0.8),
MinMaxBounds.Doubles.between(0.1, 2.05)
)
ShapedRecipeBuilder.shaped(HexBlocks.EMPTY_IMPETUS)
.define('B', Items.IRON_BARS)
.define('A', HexItems.CHARGED_AMETHYST)
.define('S', HexBlocks.SLATE_BLOCK)
.define('P', Items.PURPUR_BLOCK)
.pattern("PSS")
.pattern("BAB")
.pattern("SSP")
.unlockedBy("enlightenment", enlightenment).save(recipes)
ShapedRecipeBuilder.shaped(HexBlocks.EMPTY_DIRECTRIX)
.define('C', Items.COMPARATOR)
.define('O', Items.OBSERVER)
.define('A', HexItems.CHARGED_AMETHYST)
.define('S', HexBlocks.SLATE_BLOCK)
.pattern("CSS")
.pattern("OAO")
.pattern("SSC")
.unlockedBy("enlightenment", enlightenment).save(recipes)
ShapedRecipeBuilder.shaped(HexBlocks.AKASHIC_BOOKSHELF)
.define('L', HexItemTags.AKASHIC_LOGS)
.define('P', HexItemTags.AKASHIC_PLANKS)
.define('C', Items.BOOK)
/*this is the*/ .pattern("LPL") // and what i have for you today is
.pattern("CCC")
.pattern("LPL")
.unlockedBy("enlightenment", enlightenment).save(recipes)
ShapedRecipeBuilder.shaped(HexBlocks.AKASHIC_CONNECTOR)
.define('L', HexItemTags.AKASHIC_LOGS)
.define('P', HexItemTags.AKASHIC_PLANKS)
.define('C', HexItems.CHARGED_AMETHYST)
.pattern("LPL")
.pattern("CCC")
.pattern("LPL")
.unlockedBy("enlightenment", enlightenment).save(recipes)
BrainsweepRecipeBuilder(StateIngredientHelper.of(Blocks.AMETHYST_BLOCK),
VillagerIngredient(null, null, 3),
Blocks.BUDDING_AMETHYST.defaultBlockState())
.unlockedBy("enlightenment", enlightenment)
.save(recipes, modLoc("brainsweep/budding_amethyst"))
BrainsweepRecipeBuilder(StateIngredientHelper.of(HexBlocks.EMPTY_IMPETUS),
VillagerIngredient(ResourceLocation("toolsmith"), null, 2),
HexBlocks.IMPETUS_RIGHTCLICK.defaultBlockState())
.unlockedBy("enlightenment", enlightenment)
.save(recipes, modLoc("brainsweep/impetus_rightclick"))
BrainsweepRecipeBuilder(StateIngredientHelper.of(HexBlocks.EMPTY_IMPETUS),
VillagerIngredient(ResourceLocation("fletcher"), null, 2),
HexBlocks.IMPETUS_LOOK.defaultBlockState())
.unlockedBy("enlightenment", enlightenment)
.save(recipes, modLoc("brainsweep/impetus_look"))
BrainsweepRecipeBuilder(StateIngredientHelper.of(HexBlocks.EMPTY_IMPETUS),
VillagerIngredient(ResourceLocation("cleric"), null, 2),
HexBlocks.IMPETUS_STOREDPLAYER.defaultBlockState())
.unlockedBy("enlightenment", enlightenment)
.save(recipes, modLoc("brainsweep/impetus_storedplayer"))
BrainsweepRecipeBuilder(StateIngredientHelper.of(HexBlocks.EMPTY_DIRECTRIX),
VillagerIngredient(ResourceLocation("mason"), null, 1),
HexBlocks.DIRECTRIX_REDSTONE.defaultBlockState())
.unlockedBy("enlightenment", enlightenment)
.save(recipes, modLoc("brainsweep/directrix_redstone"))
BrainsweepRecipeBuilder(StateIngredientHelper.of(HexBlocks.AKASHIC_CONNECTOR),
VillagerIngredient(ResourceLocation("librarian"), null, 5),
HexBlocks.AKASHIC_RECORD.defaultBlockState())
.unlockedBy("enlightenment", enlightenment)
.save(recipes, modLoc("brainsweep/akashic_record"))
// Create compat
CreateCrushingRecipeBuilder()
.withInput(Blocks.AMETHYST_CLUSTER)
.duration(150)
.withOutput(Items.AMETHYST_SHARD, 7)
.withOutput(HexItems.AMETHYST_DUST, 5)
.withOutput(0.25f, HexItems.CHARGED_AMETHYST)
.withConditions()
.whenModLoaded("create")
.save(recipes, ResourceLocation("create", "crushing/amethyst_cluster"))
CreateCrushingRecipeBuilder()
.withInput(Blocks.AMETHYST_BLOCK)
.duration(150)
.withOutput(Items.AMETHYST_SHARD, 3)
.withOutput(0.5f, HexItems.AMETHYST_DUST, 4)
.withConditions()
.whenModLoaded("create")
.save(recipes, ResourceLocation("create", "crushing/amethyst_block"))
CreateCrushingRecipeBuilder()
.withInput(Items.AMETHYST_SHARD)
.duration(150)
.withOutput(HexItems.AMETHYST_DUST, 4)
.withOutput(0.5f, HexItems.AMETHYST_DUST)
.withConditions()
.whenModLoaded("create")
.save(recipes, modLoc("compat/create/crushing/amethyst_shard"))
// FD compat
FarmersDelightCuttingRecipeBuilder()
.withInput(HexBlocks.AKASHIC_LOG)
.withTool(ingredients.axeStrip())
.withOutput(HexBlocks.AKASHIC_LOG_STRIPPED)
.withOutput("farmersdelight:tree_bark")
.withSound(SoundEvents.AXE_STRIP)
.withConditions()
.whenModLoaded("farmersdelight")
.save(recipes, modLoc("compat/farmersdelight/cutting/akashic_log"))
FarmersDelightCuttingRecipeBuilder()
.withInput(HexBlocks.AKASHIC_WOOD)
.withTool(ingredients.axeStrip())
.withOutput(HexBlocks.AKASHIC_WOOD_STRIPPED)
.withOutput("farmersdelight:tree_bark")
.withSound(SoundEvents.AXE_STRIP)
.withConditions()
.whenModLoaded("farmersdelight")
.save(recipes, modLoc("compat/farmersdelight/cutting/akashic_wood"))
FarmersDelightCuttingRecipeBuilder()
.withInput(HexBlocks.AKASHIC_TRAPDOOR)
.withTool(ingredients.axeDig())
.withOutput(HexBlocks.AKASHIC_PLANKS)
.withConditions()
.whenModLoaded("farmersdelight")
.save(recipes, modLoc("compat/farmersdelight/cutting/akashic_trapdoor"))
FarmersDelightCuttingRecipeBuilder()
.withInput(HexBlocks.AKASHIC_DOOR)
.withTool(ingredients.axeDig())
.withOutput(HexBlocks.AKASHIC_PLANKS)
.withConditions()
.whenModLoaded("farmersdelight")
.save(recipes, modLoc("compat/farmersdelight/cutting/akashic_door"))
}
private fun wandRecipe(recipes: Consumer<FinishedRecipe>, wand: ItemWand, plank: Item) {
ShapedRecipeBuilder.shaped(wand)
.define('W', plank)
.define('S', Items.STICK)
.define('A', HexItems.CHARGED_AMETHYST)
.pattern(" SA")
.pattern(" WS")
.pattern("S ")
.unlockedBy("has_item", hasItem(HexItems.CHARGED_AMETHYST))
.save(recipes)
}
private fun gayRecipe(recipes: Consumer<FinishedRecipe>, type: ItemPrideColorizer.Type, material: Ingredient) {
val colorizer = HexItems.PRIDE_COLORIZERS[type]!!
ShapedRecipeBuilder.shaped(colorizer)
.define('D', HexItems.AMETHYST_DUST)
.define('C', material)
.pattern(" D ")
.pattern("DCD")
.pattern(" D ")
.unlockedBy("has_item", hasItem(HexItems.AMETHYST_DUST))
.save(recipes)
}
private fun specialRecipe(consumer: Consumer<FinishedRecipe>, serializer: SimpleRecipeSerializer<*>) {
val name = Registry.RECIPE_SERIALIZER.getKey(serializer)
SpecialRecipeBuilder.special(serializer).save(consumer, HexAPI.MOD_ID + ":dynamic/" + name!!.path)
}
private fun RecipeBuilder.withConditions(): IXplatConditionsBuilder = conditions(this)
}

View file

@ -0,0 +1,33 @@
package at.petrak.hexcasting.datagen.recipe.builders;
import com.google.gson.JsonObject;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.Collections;
import java.util.stream.Stream;
public class CompatIngredientValue implements Ingredient.Value {
public final String item;
public CompatIngredientValue(String name) {
this.item = name;
}
public @NotNull Collection<ItemStack> getItems() {
return Collections.emptyList();
}
public @NotNull JsonObject serialize() {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("item", item);
return jsonObject;
}
public static Ingredient of(String itemName) {
return new Ingredient(Stream.of(new CompatIngredientValue(itemName)));
}
}

View file

@ -0,0 +1,18 @@
package at.petrak.hexcasting.datagen.recipe.builders;
import com.google.gson.JsonObject;
public record CompatProcessingOutput(String name, int count, float chance) implements ProcessingOutput {
@Override
public JsonObject serialize() {
JsonObject json = new JsonObject();
json.addProperty("item", name);
if (count != 1) {
json.addProperty("count", count);
}
if (chance != 1) {
json.addProperty("chance", chance);
}
return json;
}
}

View file

@ -0,0 +1,163 @@
package at.petrak.hexcasting.datagen.recipe.builders;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import net.minecraft.advancements.CriterionTriggerInstance;
import net.minecraft.data.recipes.FinishedRecipe;
import net.minecraft.data.recipes.RecipeBuilder;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.level.ItemLike;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
// Largely adapted from the following classes:
// https://github.com/Creators-of-Create/Create/blob/82be76d8934af03b4e52cad6a9f74a4175ba7b05/src/main/java/com/simibubi/create/content/contraptions/processing/ProcessingOutput.java
// https://github.com/Creators-of-Create/Create/blob/82be76d8934af03b4e52cad6a9f74a4175ba7b05/src/main/java/com/simibubi/create/foundation/data/recipe/ProcessingRecipeGen.java
// https://github.com/Creators-of-Create/Create/blob/82be76d8934af03b4e52cad6a9f74a4175ba7b05/src/main/java/com/simibubi/create/content/contraptions/processing/ProcessingRecipeBuilder.java
// https://github.com/Creators-of-Create/Create/blob/82be76d8934af03b4e52cad6a9f74a4175ba7b05/src/main/java/com/simibubi/create/content/contraptions/processing/ProcessingRecipeSerializer.java
public class CreateCrushingRecipeBuilder implements RecipeBuilder {
private String group = "";
private Ingredient input;
private final List<ProcessingOutput> results = new ArrayList<>();
private int processingTime = 100;
@Override
public @NotNull CreateCrushingRecipeBuilder unlockedBy(@NotNull String name, @NotNull CriterionTriggerInstance trigger) {
return this;
}
@Override
public @NotNull CreateCrushingRecipeBuilder group(@Nullable String name) {
group = name;
return this;
}
public CreateCrushingRecipeBuilder duration(int duration) {
this.processingTime = duration;
return this;
}
public CreateCrushingRecipeBuilder withInput(ItemLike item) {
return withInput(Ingredient.of(item));
}
public CreateCrushingRecipeBuilder withInput(ItemStack stack) {
return withInput(Ingredient.of(stack));
}
public CreateCrushingRecipeBuilder withInput(Ingredient ingredient) {
this.input = ingredient;
return this;
}
public CreateCrushingRecipeBuilder withOutput(ItemLike output) {
return withOutput(1f, output, 1);
}
public CreateCrushingRecipeBuilder withOutput(float chance, ItemLike output) {
return withOutput(chance, output, 1);
}
public CreateCrushingRecipeBuilder withOutput(ItemLike output, int count) {
return withOutput(1f, output, count);
}
public CreateCrushingRecipeBuilder withOutput(float chance, ItemLike output, int count) {
return withOutput(new ItemStack(output, count), chance);
}
public CreateCrushingRecipeBuilder withOutput(ItemStack output, float chance) {
this.results.add(new ItemProcessingOutput(output, chance));
return this;
}
public CreateCrushingRecipeBuilder withOutput(String name) {
return withOutput(1f, name, 1);
}
public CreateCrushingRecipeBuilder withOutput(String name, int count) {
return withOutput(1f, name, count);
}
public CreateCrushingRecipeBuilder withOutput(float chance, String name) {
return withOutput(chance, name, 1);
}
public CreateCrushingRecipeBuilder withOutput(float chance, String name, int count) {
this.results.add(new CompatProcessingOutput(name, count, chance));
return this;
}
@Override
public @NotNull Item getResult() {
return Items.AIR; // Irrelevant, we implement serialization ourselves
}
@Override
public void save(@NotNull Consumer<FinishedRecipe> consumer, @NotNull ResourceLocation resourceLocation) {
consumer.accept(new CrushingRecipe(resourceLocation));
}
public class CrushingRecipe implements FinishedRecipe {
private final ResourceLocation id;
public CrushingRecipe(ResourceLocation id) {
this.id = id;
}
@Override
public void serializeRecipeData(@NotNull JsonObject json) {
json.addProperty("type", "create:crushing");
if (!group.isEmpty()) {
json.addProperty("group", group);
}
JsonArray jsonIngredients = new JsonArray();
JsonArray jsonOutputs = new JsonArray();
jsonIngredients.add(input.toJson());
results.forEach(o -> jsonOutputs.add(o.serialize()));
json.add("ingredients", jsonIngredients);
json.add("results", jsonOutputs);
int processingDuration = processingTime;
if (processingDuration > 0) {
json.addProperty("processingTime", processingDuration);
}
}
@Override
public @NotNull ResourceLocation getId() {
return id;
}
@Override
public @NotNull RecipeSerializer<?> getType() {
return RecipeSerializer.SHAPELESS_RECIPE; // Irrelevant, we implement serialization ourselves
}
@Override
public JsonObject serializeAdvancement() {
return null;
}
@Override
public ResourceLocation getAdvancementId() {
return null;
}
}
}

View file

@ -0,0 +1,165 @@
package at.petrak.hexcasting.datagen.recipe.builders;
import com.google.common.collect.Lists;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import net.minecraft.advancements.CriterionTriggerInstance;
import net.minecraft.core.Registry;
import net.minecraft.data.recipes.FinishedRecipe;
import net.minecraft.data.recipes.RecipeBuilder;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.level.ItemLike;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.function.Consumer;
public class FarmersDelightCuttingRecipeBuilder implements RecipeBuilder {
private String group = "";
private final List<ProcessingOutput> outputs = Lists.newArrayList();
private Ingredient input;
private FarmersDelightToolIngredient toolAction;
private SoundEvent sound;
@Override
public FarmersDelightCuttingRecipeBuilder unlockedBy(String s, CriterionTriggerInstance criterionTriggerInstance) {
return this;
}
@Override
public @NotNull FarmersDelightCuttingRecipeBuilder group(@Nullable String name) {
group = name;
return this;
}
@Override
public Item getResult() {
return Items.AIR; // Irrelevant, we implement serialization ourselves
}
public FarmersDelightCuttingRecipeBuilder withInput(ItemLike item) {
return withInput(Ingredient.of(item));
}
public FarmersDelightCuttingRecipeBuilder withInput(ItemStack stack) {
return withInput(Ingredient.of(stack));
}
public FarmersDelightCuttingRecipeBuilder withInput(Ingredient ingredient) {
this.input = ingredient;
return this;
}
public FarmersDelightCuttingRecipeBuilder withOutput(ItemLike output) {
return withOutput(1f, output, 1);
}
public FarmersDelightCuttingRecipeBuilder withOutput(float chance, ItemLike output) {
return withOutput(chance, output, 1);
}
public FarmersDelightCuttingRecipeBuilder withOutput(ItemLike output, int count) {
return withOutput(1f, output, count);
}
public FarmersDelightCuttingRecipeBuilder withOutput(float chance, ItemLike output, int count) {
return withOutput(new ItemStack(output, count), chance);
}
public FarmersDelightCuttingRecipeBuilder withOutput(ItemStack output, float chance) {
this.outputs.add(new ItemProcessingOutput(output, chance));
return this;
}
public FarmersDelightCuttingRecipeBuilder withOutput(String name) {
return withOutput(1f, name, 1);
}
public FarmersDelightCuttingRecipeBuilder withOutput(String name, int count) {
return withOutput(1f, name, count);
}
public FarmersDelightCuttingRecipeBuilder withOutput(float chance, String name) {
return withOutput(chance, name, 1);
}
public FarmersDelightCuttingRecipeBuilder withOutput(float chance, String name, int count) {
this.outputs.add(new CompatProcessingOutput(name, count, chance));
return this;
}
public FarmersDelightCuttingRecipeBuilder withTool(FarmersDelightToolIngredient ingredient) {
this.toolAction = ingredient;
return this;
}
public FarmersDelightCuttingRecipeBuilder withSound(SoundEvent sound) {
this.sound = sound;
return this;
}
@Override
public void save(Consumer<FinishedRecipe> consumer, ResourceLocation resourceLocation) {
consumer.accept(new CuttingRecipe(resourceLocation));
}
public class CuttingRecipe implements FinishedRecipe {
private final ResourceLocation id;
public CuttingRecipe(ResourceLocation id) {
this.id = id;
}
@Override
public void serializeRecipeData(@NotNull JsonObject json) {
json.addProperty("type", "farmersdelight:cutting");
if (!group.isEmpty()) {
json.addProperty("group", group);
}
JsonArray jsonIngredients = new JsonArray();
JsonArray jsonOutputs = new JsonArray();
jsonIngredients.add(input.toJson());
outputs.forEach(o -> jsonOutputs.add(o.serialize()));
json.add("ingredients", jsonIngredients);
json.add("tool", toolAction.serialize());
json.add("result", jsonOutputs);
if (sound != null) {
json.addProperty("sound", Registry.SOUND_EVENT.getKey(sound).toString());
}
}
@Override
public @NotNull ResourceLocation getId() {
return id;
}
@Override
public @NotNull RecipeSerializer<?> getType() {
return RecipeSerializer.SHAPELESS_RECIPE; // Irrelevant, we implement serialization ourselves
}
@Override
public JsonObject serializeAdvancement() {
return null;
}
@Override
public ResourceLocation getAdvancementId() {
return null;
}
}
}

View file

@ -0,0 +1,7 @@
package at.petrak.hexcasting.datagen.recipe.builders;
import com.google.gson.JsonObject;
public interface FarmersDelightToolIngredient {
JsonObject serialize();
}

View file

@ -0,0 +1,27 @@
package at.petrak.hexcasting.datagen.recipe.builders;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
public record ItemProcessingOutput(ItemStack stack, float chance) implements ProcessingOutput {
@Override
public JsonObject serialize() {
JsonObject json = new JsonObject();
ResourceLocation resourceLocation = Registry.ITEM.getKey(stack.getItem());
json.addProperty("item", resourceLocation.toString());
int count = stack.getCount();
if (count != 1) {
json.addProperty("count", count);
}
if (stack.hasTag()) {
json.add("nbt", JsonParser.parseString(stack.getTag().toString()));
}
if (chance != 1) {
json.addProperty("chance", chance);
}
return json;
}
}

View file

@ -0,0 +1,7 @@
package at.petrak.hexcasting.datagen.recipe.builders;
import com.google.gson.JsonObject;
public interface ProcessingOutput {
JsonObject serialize();
}

View file

@ -1,6 +1,7 @@
package at.petrak.hexcasting.interop;
import at.petrak.hexcasting.interop.pehkui.PehkuiInterop;
import at.petrak.hexcasting.xplat.IClientXplatAbstractions;
import at.petrak.hexcasting.xplat.IXplatAbstractions;
import at.petrak.hexcasting.xplat.Platform;
import vazkii.patchouli.api.PatchouliAPI;
@ -13,10 +14,12 @@ public class HexInterop {
public static final String PEHKUI_ID = "pehkui";
public static final class Forge {
public static final String CURIOS_API_ID = "curios";
}
public static final class Fabric {
public static final String GRAVITY_CHANGER_API_ID = "gravitychanger";
public static final String TRINKETS_API_ID = "trinkets";
}
public static void init() {
@ -30,6 +33,10 @@ public class HexInterop {
xplat.initPlatformSpecific();
}
public static void clientInit() {
IClientXplatAbstractions.INSTANCE.initPlatformSpecific();
}
private static void initPatchouli() {
var integrations = List.of(PEHKUI_ID);

View file

@ -0,0 +1,33 @@
package at.petrak.hexcasting.interop.patchouli;
import net.minecraft.client.resources.language.I18n;
import vazkii.patchouli.api.IComponentProcessor;
import vazkii.patchouli.api.IVariable;
import vazkii.patchouli.api.IVariableProvider;
public class PatternProcessor implements IComponentProcessor {
private String translationKey;
@Override
public void setup(IVariableProvider vars) {
if (vars.has("header"))
translationKey = vars.get("header").asString();
else {
IVariable key = vars.get("op_id");
String opName = key.asString();
String prefix = "hexcasting.spell.";
boolean hasOverride = I18n.exists(prefix + "book." + opName);
translationKey = prefix + (hasOverride ? "book." : "") + opName;
}
}
@Override
public IVariable process(String key) {
if (key.equals("translation_key")) {
return IVariable.wrap(translationKey);
}
return null;
}
}

Some files were not shown because too many files have changed in this diff Show more