diff --git a/StaffMod/src/main/java/opekope2/avm_staff/mixin/AbstractFurnaceBlockEntityMixin.java b/StaffMod/src/main/java/opekope2/avm_staff/mixin/AbstractFurnaceBlockEntityMixin.java
new file mode 100644
index 000000000..fe5cdfac9
--- /dev/null
+++ b/StaffMod/src/main/java/opekope2/avm_staff/mixin/AbstractFurnaceBlockEntityMixin.java
@@ -0,0 +1,39 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.mixin;
+
+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
+import net.minecraft.block.entity.AbstractFurnaceBlockEntity;
+import net.minecraft.util.Identifier;
+import opekope2.avm_staff.api.block.IClearableBeforeInsertedIntoStaff;
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+
+@Mixin(AbstractFurnaceBlockEntity.class)
+public abstract class AbstractFurnaceBlockEntityMixin implements IClearableBeforeInsertedIntoStaff {
+ @Shadow
+ @Final
+ private Object2IntOpenHashMap recipesUsed;
+
+ @Override
+ public void staffMod_clearBeforeRemovedFromWorld() {
+ recipesUsed.clear();
+ }
+}
diff --git a/StaffMod/src/main/java/opekope2/avm_staff/mixin/BipedEntityModelMixin.java b/StaffMod/src/main/java/opekope2/avm_staff/mixin/BipedEntityModelMixin.java
index 87a9f0e56..e0ee5c45f 100644
--- a/StaffMod/src/main/java/opekope2/avm_staff/mixin/BipedEntityModelMixin.java
+++ b/StaffMod/src/main/java/opekope2/avm_staff/mixin/BipedEntityModelMixin.java
@@ -21,8 +21,6 @@
import net.minecraft.client.model.ModelPart;
import net.minecraft.client.render.entity.model.BipedEntityModel;
import net.minecraft.entity.LivingEntity;
-import opekope2.avm_staff.api.StaffMod;
-import opekope2.avm_staff.api.component.StaffRendererOverrideComponent;
import opekope2.avm_staff.util.ItemStackUtil;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
@@ -71,9 +69,6 @@ private void positionRightArm(LivingEntity entity, CallbackInfo ci) {
@Unique
private boolean staffMod$pointForward(BipedEntityModel.ArmPose armPose, LivingEntity entity) {
if (armPose != BipedEntityModel.ArmPose.ITEM) return false;
- if (!ItemStackUtil.isStaff(entity.getActiveItem())) return false;
-
- StaffRendererOverrideComponent rendererOverride = entity.getActiveItem().get(StaffMod.getStaffRendererOverrideComponentType().get());
- return rendererOverride == null || rendererOverride.isActive().orElse(true);
+ return ItemStackUtil.isStaff(entity.getActiveItem());
}
}
diff --git a/StaffMod/src/main/java/opekope2/avm_staff/mixin/IBellBlockEntityAccessor.java b/StaffMod/src/main/java/opekope2/avm_staff/mixin/IBellBlockEntityAccessor.java
new file mode 100644
index 000000000..8ae446fd8
--- /dev/null
+++ b/StaffMod/src/main/java/opekope2/avm_staff/mixin/IBellBlockEntityAccessor.java
@@ -0,0 +1,38 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.mixin;
+
+import net.minecraft.block.entity.BellBlockEntity;
+import net.minecraft.entity.LivingEntity;
+import net.minecraft.util.math.BlockPos;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Invoker;
+
+@Mixin(BellBlockEntity.class)
+public interface IBellBlockEntityAccessor {
+ @Invoker
+ static void callApplyGlowToEntity(LivingEntity entity) {
+ throw new AssertionError();
+ }
+
+ @Invoker
+ static boolean callIsRaiderEntity(BlockPos pos, LivingEntity entity) {
+ throw new AssertionError();
+ }
+}
diff --git a/StaffMod/src/main/java/opekope2/avm_staff/mixin/SculkShriekerBlockEntityMixin.java b/StaffMod/src/main/java/opekope2/avm_staff/mixin/SculkShriekerBlockEntityMixin.java
new file mode 100644
index 000000000..277137e8d
--- /dev/null
+++ b/StaffMod/src/main/java/opekope2/avm_staff/mixin/SculkShriekerBlockEntityMixin.java
@@ -0,0 +1,41 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.mixin;
+
+import net.minecraft.block.entity.SculkShriekerBlockEntity;
+import net.minecraft.entity.Entity;
+import net.minecraft.server.network.ServerPlayerEntity;
+import opekope2.avm_staff.api.entity.CakeEntity;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+
+@Mixin(SculkShriekerBlockEntity.class)
+public abstract class SculkShriekerBlockEntityMixin {
+ @Inject(method = "findResponsiblePlayerFromEntity", at = @At("TAIL"), cancellable = true)
+ private static void findResponsiblePlayerFromCake(Entity entity, CallbackInfoReturnable cir) {
+ if (entity instanceof CakeEntity cake) {
+ Entity owner = cake.getOwner();
+ if (owner instanceof ServerPlayerEntity player) {
+ cir.setReturnValue(player);
+ }
+ }
+ }
+}
diff --git a/StaffMod/src/main/java/opekope2/avm_staff/mixin/TurtleEggBlockMixin.java b/StaffMod/src/main/java/opekope2/avm_staff/mixin/TurtleEggBlockMixin.java
index 65fe7c0cc..8a359b6a2 100644
--- a/StaffMod/src/main/java/opekope2/avm_staff/mixin/TurtleEggBlockMixin.java
+++ b/StaffMod/src/main/java/opekope2/avm_staff/mixin/TurtleEggBlockMixin.java
@@ -35,7 +35,7 @@
import org.spongepowered.asm.mixin.Shadow;
@Mixin(TurtleEggBlock.class)
-public class TurtleEggBlockMixin implements IBlockAfterDestroyHandler {
+public abstract class TurtleEggBlockMixin implements IBlockAfterDestroyHandler {
@Shadow
@Final
public static IntProperty EGGS;
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/IStaffModPlatform.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/IStaffModPlatform.kt
index 4f2b7a563..4b42f6bfc 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/IStaffModPlatform.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/IStaffModPlatform.kt
@@ -21,6 +21,7 @@ package opekope2.avm_staff.api
import net.minecraft.block.Block
import net.minecraft.item.Item
import net.minecraft.particle.SimpleParticleType
+import net.minecraftforge.registries.RegistryObject
import opekope2.avm_staff.api.IStaffModPlatform.Instance
import opekope2.avm_staff.api.item.CrownItem
import opekope2.avm_staff.api.item.StaffItem
@@ -37,7 +38,7 @@ interface IStaffModPlatform {
*
* @param settings The item settings to pass to the constructor
*/
- fun staffItem(settings: Item.Settings): StaffItem
+ fun staffItem(settings: Item.Settings, repairIngredient: RegistryObject- ?): StaffItem
/**
* Creates an item, which is rendered like a staff.
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/StaffMod.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/StaffMod.kt
deleted file mode 100644
index 00a74327d..000000000
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/StaffMod.kt
+++ /dev/null
@@ -1,323 +0,0 @@
-/*
- * AvM Staff Mod
- * Copyright (c) 2024 opekope2
- *
- * This mod is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This mod is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this mod. If not, see .
- */
-
-@file: JvmName("StaffMod")
-@file: Suppress("unused")
-
-package opekope2.avm_staff.api
-
-import net.minecraft.block.AbstractBlock
-import net.minecraft.block.enums.NoteBlockInstrument
-import net.minecraft.block.piston.PistonBehavior
-import net.minecraft.client.particle.ParticleManager
-import net.minecraft.component.ComponentType
-import net.minecraft.entity.EntityType
-import net.minecraft.entity.SpawnGroup
-import net.minecraft.entity.damage.DamageType
-import net.minecraft.item.Item
-import net.minecraft.item.ItemGroup
-import net.minecraft.item.Items
-import net.minecraft.item.SmithingTemplateItem
-import net.minecraft.network.codec.PacketCodec
-import net.minecraft.particle.SimpleParticleType
-import net.minecraft.registry.RegistryKey
-import net.minecraft.registry.RegistryKeys
-import net.minecraft.sound.BlockSoundGroup
-import net.minecraft.sound.SoundEvent
-import net.minecraft.text.Text
-import net.minecraft.util.Identifier
-import net.minecraft.util.Rarity
-import net.minecraft.world.GameRules
-import net.minecraftforge.registries.DeferredRegister
-import net.minecraftforge.registries.ForgeRegistries
-import net.minecraftforge.registries.RegistryObject
-import opekope2.avm_staff.api.block.CrownBlock
-import opekope2.avm_staff.api.block.WallCrownBlock
-import opekope2.avm_staff.api.component.StaffFurnaceDataComponent
-import opekope2.avm_staff.api.component.StaffItemComponent
-import opekope2.avm_staff.api.component.StaffRendererOverrideComponent
-import opekope2.avm_staff.api.component.StaffRendererPartComponent
-import opekope2.avm_staff.api.entity.CakeEntity
-import opekope2.avm_staff.api.entity.ImpactTntEntity
-import opekope2.avm_staff.api.item.CrownItem
-import opekope2.avm_staff.api.item.StaffItem
-import opekope2.avm_staff.api.staff.StaffHandler
-import opekope2.avm_staff.api.staff.StaffInfusionSmithingRecipeTextures
-import opekope2.avm_staff.internal.MinecraftUnit
-import opekope2.avm_staff.mixin.ICakeBlockAccessor
-import opekope2.avm_staff.mixin.ISmithingTemplateItemAccessor
-import opekope2.avm_staff.util.MOD_ID
-import opekope2.avm_staff.util.mutableItemStackInStaff
-import thedarkcolour.kotlinforforge.forge.MOD_BUS
-import kotlin.math.max
-
-private val BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, MOD_ID)
-private val ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, MOD_ID)
-private val ITEM_GROUPS = DeferredRegister.create(RegistryKeys.ITEM_GROUP, MOD_ID)
-private val ENTITY_TYPES = DeferredRegister.create(ForgeRegistries.ENTITY_TYPES, MOD_ID)
-private val PARTICLE_TYPES = DeferredRegister.create(ForgeRegistries.PARTICLE_TYPES, MOD_ID)
-private val DATA_COMPONENT_TYPES = DeferredRegister.create(RegistryKeys.DATA_COMPONENT_TYPE, MOD_ID)
-private val SOUND_EVENTS = DeferredRegister.create(ForgeRegistries.SOUND_EVENTS, MOD_ID)
-
-/**
- * Block registered as `avm_staff:crown_of_king_orange`.
- */
-val crownOfKingOrangeBlock: RegistryObject = BLOCKS.register("crown_of_king_orange") {
- CrownBlock(
- AbstractBlock.Settings.create().instrument(NoteBlockInstrument.BELL).strength(1.0f)
- .pistonBehavior(PistonBehavior.DESTROY).sounds(BlockSoundGroup.COPPER_GRATE).nonOpaque()
- )
-}
-
-/**
- * Block registered as `avm_staff:wall_crown_of_king_orange`.
- */
-val wallCrownOfKingOrangeBlock: RegistryObject = BLOCKS.register("wall_crown_of_king_orange") {
- WallCrownBlock(AbstractBlock.Settings.copy(crownOfKingOrangeBlock.get()))
-}
-
-/**
- * Item registered as `avm_staff:faint_staff_rod`.
- */
-val faintStaffRodItem: RegistryObject
- = ITEMS.register("faint_staff_rod") {
- Item(Item.Settings())
-}
-
-/**
- * Item registered as `avm_staff:faint_royal_staff_head`.
- */
-val faintRoyalStaffHeadItem: RegistryObject
- = ITEMS.register("faint_royal_staff_head") {
- Item(Item.Settings().maxCount(16).rarity(Rarity.RARE))
-}
-
-/**
- * Item registered as `avm_staff:faint_royal_staff`.
- */
-val faintRoyalStaffItem: RegistryObject
- = ITEMS.register("faint_royal_staff") {
- IStaffModPlatform.itemWithStaffRenderer(Item.Settings().maxCount(1).rarity(Rarity.RARE))
-}
-
-/**
- * Item registered as `avm_staff:royal_staff`.
- */
-val royalStaffItem: RegistryObject = ITEMS.register("royal_staff") {
- IStaffModPlatform.staffItem(
- Item.Settings().maxCount(1).rarity(Rarity.EPIC).attributeModifiers(StaffHandler.Default.ATTRIBUTE_MODIFIERS)
- )
-}
-
-/**
- * Item registered as `avm_staff:royal_staff_ingredient`.
- */
-val royalStaffIngredientItem: RegistryObject
- = ITEMS.register("royal_staff_ingredient") {
- Item(Item.Settings())
-}
-
-/**
- * Item registered as `avm_staff:crown_of_king_orange`.
- */
-val crownOfKingOrangeItem: RegistryObject = ITEMS.register("crown_of_king_orange") {
- IStaffModPlatform.crownItem(
- crownOfKingOrangeBlock.get(),
- wallCrownOfKingOrangeBlock.get(),
- Item.Settings().maxCount(1).rarity(Rarity.UNCOMMON)
- )
-}
-
-/**
- * Item registered as `avm_staff:staff_infusion_smithing_template`.
- */
-val staffInfusionSmithingTemplateItem: RegistryObject
- = ITEMS.register("staff_infusion_smithing_template") {
- SmithingTemplateItem(
- Text.translatable("item.$MOD_ID.staff_infusion_smithing_template.applies_to")
- .formatted(ISmithingTemplateItemAccessor.descriptionFormatting()),
- ISmithingTemplateItemAccessor.armorTrimIngredientsText(),
- Text.translatable("item.$MOD_ID.staff_infusion_smithing_template.title")
- .formatted(ISmithingTemplateItemAccessor.titleFormatting()),
- Text.translatable("item.$MOD_ID.staff_infusion_smithing_template.base_slot_description"),
- ISmithingTemplateItemAccessor.armorTrimAdditionsSlotDescriptionText(),
- StaffInfusionSmithingRecipeTextures.baseSlotTextures,
- StaffInfusionSmithingRecipeTextures.additionsSlotTextures
- )
-}
-
-/**
- * Item group containing items added by Staff Mod.
- */
-val staffModItemGroup: RegistryObject = ITEM_GROUPS.register("${MOD_ID}_items") {
- ItemGroup.builder()
- .displayName(Text.translatable("itemGroup.${MOD_ID}_items"))
- .icon {
- royalStaffItem.get().defaultStack.apply {
- mutableItemStackInStaff = Items.COMMAND_BLOCK.defaultStack
- }
- }
- .entries { _, entries ->
- entries.add(faintStaffRodItem.get())
- entries.add(faintRoyalStaffHeadItem.get())
- entries.add(faintRoyalStaffItem.get())
- entries.add(royalStaffItem.get())
- entries.add(royalStaffIngredientItem.get())
- entries.add(crownOfKingOrangeItem.get())
- entries.add(staffInfusionSmithingTemplateItem.get())
- }
- .build()
-}
-
-/**
- * Entity registered as `avm_staff:impact_tnt`.
- */
-val impactTntEntityType: RegistryObject> = ENTITY_TYPES.register("impact_tnt") {
- EntityType.Builder.create(::ImpactTntEntity, SpawnGroup.MISC)
- .makeFireImmune()
- .dimensions(EntityType.TNT.dimensions.width, EntityType.TNT.dimensions.height)
- .eyeHeight(EntityType.TNT.dimensions.eyeHeight)
- .maxTrackingRange(EntityType.TNT.maxTrackDistance)
- .trackingTickInterval(EntityType.TNT.trackTickInterval)
- .build(Identifier.of(MOD_ID, "impact_tnt").toString())
-}
-
-/**
- * Entity registered as `avm_staff:cake`
- */
-val cakeEntityType: RegistryObject> = ENTITY_TYPES.register("cake") {
- val cakeBox = ICakeBlockAccessor.bitesToShape()[0].boundingBox
- val cakeSize = max(cakeBox.lengthX, max(cakeBox.lengthY, cakeBox.lengthZ))
-
- EntityType.Builder.create(::CakeEntity, SpawnGroup.MISC)
- .dimensions(cakeSize.toFloat(), cakeSize.toFloat())
- .maxTrackingRange(EntityType.FALLING_BLOCK.maxTrackDistance)
- .trackingTickInterval(EntityType.FALLING_BLOCK.trackTickInterval)
- .build(Identifier.of(MOD_ID, "cake").toString())
-}
-
-/**
- * Particle registered as `avm_staff:flame`.
- *
- * @see ParticleManager.addParticle
- */
-val flamethrowerParticleType: RegistryObject =
- PARTICLE_TYPES.register("flame") { IStaffModPlatform.simpleParticleType(false) }
-
-/**
- * Particle registered as `avm_staff:soul_fire_flame`.
- *
- * @see ParticleManager.addParticle
- */
-val soulFlamethrowerParticleType: RegistryObject =
- PARTICLE_TYPES.register("soul_fire_flame") { IStaffModPlatform.simpleParticleType(false) }
-
-/**
- * Data component registered as `avm_staff:staff_item`. Stores the item inserted into the staff.
- */
-val staffItemComponentType: RegistryObject> =
- DATA_COMPONENT_TYPES.register("staff_item") {
- ComponentType.builder()
- .codec(StaffItemComponent.CODEC)
- .packetCodec(StaffItemComponent.PACKET_CODEC)
- .build()
- }
-
-/**
- * Data component registered as `avm_staff:rocket_mode`. Stores if a campfire staff should propel its user.
- */
-val rocketModeComponentType: RegistryObject> =
- DATA_COMPONENT_TYPES.register("rocket_mode") {
- ComponentType.builder()
- .packetCodec(PacketCodec.unit(MinecraftUnit.INSTANCE))
- .build()
- }
-
-/**
- * Data component registered as `avm_staff:furnace_data`. If this is present, the furnace is lit.
- */
-val staffFurnaceDataComponentType: RegistryObject> =
- DATA_COMPONENT_TYPES.register("furnace_data") {
- ComponentType.builder()
- .packetCodec(StaffFurnaceDataComponent.PACKET_CODEC)
- .build()
- }
-
-/**
- * Data component registered as `avm_staff:staff_renderer_override`. Specifies how a staff is rendered. Intended for
- * Isometric Renders mod compatibility.
- */
-val staffRendererOverrideComponentType: RegistryObject> =
- DATA_COMPONENT_TYPES.register("staff_renderer_override") {
- ComponentType.builder()
- .codec(StaffRendererOverrideComponent.CODEC)
- .packetCodec(StaffRendererOverrideComponent.PACKET_CODEC)
- .build()
- }
-
-/**
- * Data component registered as `avm_staff:staff_renderer_part`. Only used for rendering.
- */
-val staffRendererPartComponentType: RegistryObject> =
- DATA_COMPONENT_TYPES.register("staff_renderer_part") {
- ComponentType.builder()
- .packetCodec(StaffRendererPartComponent.PACKET_CODEC)
- .build()
- }
-
-/**
- * Sound event registered as `avm_staff:entity.cake.splash`.
- */
-val cakeSplashSoundEvent: RegistryObject = SOUND_EVENTS.register("entity.cake.splash") {
- SoundEvent.of(Identifier.of(MOD_ID, "entity.cake.splash"))
-}
-
-/**
- * Sound event registered as `avm_staff:entity.cake.throw`.
- */
-val cakeThrowSoundEvent: RegistryObject = SOUND_EVENTS.register("entity.cake.throw") {
- SoundEvent.of(Identifier.of(MOD_ID, "entity.cake.throw"))
-}
-
-/**
- * `avm_staff:pranked` damage type.
- */
-val cakeDamageType: RegistryKey =
- RegistryKey.of(RegistryKeys.DAMAGE_TYPE, Identifier.of(MOD_ID, "pranked"))
-
-/**
- * `avm_staff:pranked_by_player` damage type.
- */
-val playerCakeDamageType: RegistryKey =
- RegistryKey.of(RegistryKeys.DAMAGE_TYPE, Identifier.of(MOD_ID, "pranked_by_player"))
-
-/**
- * Throwable cakes game rule. When set to true, cakes can be thrown by right clicking, and dispensers will shoot cakes
- * instead of dropping them as item.
- */
-val throwableCakesGameRule: GameRules.Key =
- GameRules.register("throwableCakes", GameRules.Category.MISC, GameRules.BooleanRule.create(false))
-
-/**
- * @suppress
- */
-@JvmSynthetic
-internal fun registerContent() {
- BLOCKS.register(MOD_BUS)
- ITEMS.register(MOD_BUS)
- ITEM_GROUPS.register(MOD_BUS)
- ENTITY_TYPES.register(MOD_BUS)
- PARTICLE_TYPES.register(MOD_BUS)
- DATA_COMPONENT_TYPES.register(MOD_BUS)
- SOUND_EVENTS.register(MOD_BUS)
-}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/advancement/criterion/BreakBlockWithStaffCriterion.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/advancement/criterion/BreakBlockWithStaffCriterion.kt
new file mode 100644
index 000000000..652560ea8
--- /dev/null
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/advancement/criterion/BreakBlockWithStaffCriterion.kt
@@ -0,0 +1,87 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.api.advancement.criterion
+
+import com.mojang.serialization.Codec
+import com.mojang.serialization.codecs.RecordCodecBuilder
+import net.minecraft.advancement.criterion.AbstractCriterion
+import net.minecraft.predicate.entity.EntityPredicate
+import net.minecraft.predicate.entity.LocationPredicate
+import net.minecraft.predicate.entity.LootContextPredicate
+import net.minecraft.server.network.ServerPlayerEntity
+import net.minecraft.server.world.ServerWorld
+import net.minecraft.util.math.BlockPos
+import opekope2.avm_staff.util.component1
+import opekope2.avm_staff.util.component2
+import opekope2.avm_staff.util.component3
+import java.util.*
+
+/**
+ * A criterion, which triggers when a player breaks a block using a staff.
+ */
+class BreakBlockWithStaffCriterion : AbstractCriterion() {
+ override fun getConditionsCodec() = Conditions.CODEC
+
+ /**
+ * Triggers the criterion.
+ *
+ * @param player The player triggering the criterion
+ * @param world The world where the player broke the block
+ * @param pos The position of the broken block
+ */
+ fun trigger(player: ServerPlayerEntity, world: ServerWorld, pos: BlockPos) {
+ trigger(player) { conditions -> conditions.test(world, pos) }
+ }
+
+ /**
+ * Conditions of [BreakBlockWithStaffCriterion].
+ *
+ * @param player Predicate matching the player
+ * @param location Predicate matching the location of the broken block
+ */
+ data class Conditions(val player: Optional, val location: Optional) :
+ AbstractCriterion.Conditions {
+ override fun player() = player
+
+ /**
+ * Tests the given parameters against the datapack-specified conditions.
+ *
+ * @param world The world where the player broke the block
+ * @param pos The position of the broken block
+ */
+ fun test(world: ServerWorld, pos: BlockPos): Boolean {
+ val (x, y, z) = pos.toCenterPos()
+ return location.isEmpty || location.get().test(world, x, y, z)
+ }
+
+ companion object {
+ /**
+ * [Codec] for [Conditions].
+ */
+ @JvmField
+ val CODEC: Codec = RecordCodecBuilder.create { instance ->
+ instance.group(
+ EntityPredicate.LOOT_CONTEXT_PREDICATE_CODEC.optionalFieldOf("player")
+ .forGetter(AbstractCriterion.Conditions::player),
+ LocationPredicate.CODEC.optionalFieldOf("location").forGetter(Conditions::location)
+ ).apply(instance, ::Conditions)
+ }
+ }
+ }
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/advancement/criterion/TakeDamageWhileUsingItemCriterion.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/advancement/criterion/TakeDamageWhileUsingItemCriterion.kt
new file mode 100644
index 000000000..60d3fb5ae
--- /dev/null
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/advancement/criterion/TakeDamageWhileUsingItemCriterion.kt
@@ -0,0 +1,95 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.api.advancement.criterion
+
+import com.mojang.serialization.Codec
+import com.mojang.serialization.codecs.RecordCodecBuilder
+import net.minecraft.advancement.criterion.AbstractCriterion
+import net.minecraft.advancement.criterion.EntityHurtPlayerCriterion
+import net.minecraft.advancement.criterion.UsingItemCriterion
+import net.minecraft.entity.damage.DamageSource
+import net.minecraft.item.ItemStack
+import net.minecraft.predicate.entity.DamageSourcePredicate
+import net.minecraft.predicate.entity.EntityPredicate
+import net.minecraft.predicate.entity.LootContextPredicate
+import net.minecraft.predicate.item.ItemPredicate
+import net.minecraft.server.network.ServerPlayerEntity
+import java.util.*
+
+/**
+ * A fusion criterion between [UsingItemCriterion] and [EntityHurtPlayerCriterion].
+ */
+class TakeDamageWhileUsingItemCriterion : AbstractCriterion() {
+ override fun getConditionsCodec() = Conditions.CODEC
+
+ /**
+ * Triggers the criterion.
+ *
+ * @param player The player triggering the criterion
+ * @param stack The item stack the player is using
+ * @param damageSource The damage the player took
+ */
+ fun trigger(player: ServerPlayerEntity, stack: ItemStack, damageSource: DamageSource) {
+ trigger(player) { conditions -> conditions.test(player, stack, damageSource) }
+ }
+
+ /**
+ * Conditions of [TakeDamageWhileUsingItemCriterion].
+ *
+ * @param player Predicate matching the player
+ * @param item Predicate matching the item the player is using
+ * @param damageType Predicate matching the damage the player took
+ */
+ data class Conditions(
+ val player: Optional,
+ val item: Optional,
+ val damageType: Optional
+ ) : AbstractCriterion.Conditions {
+ override fun player() = player
+
+ /**
+ * Tests the given parameters against the datapack-specified conditions.
+ *
+ * @param player The player triggering the criterion
+ * @param stack The item stack the player is using
+ * @param damageSource The damage the player took
+ */
+ fun test(player: ServerPlayerEntity, stack: ItemStack, damageSource: DamageSource): Boolean {
+ if (item.isPresent && !item.get().test(stack)) return false
+ if (damageType.isPresent && !damageType.get().test(player, damageSource)) return false
+
+ return true
+ }
+
+ companion object {
+ /**
+ * [Codec] for [Conditions].
+ */
+ @JvmField
+ val CODEC: Codec = RecordCodecBuilder.create { instance ->
+ instance.group(
+ EntityPredicate.LOOT_CONTEXT_PREDICATE_CODEC.optionalFieldOf("player")
+ .forGetter(AbstractCriterion.Conditions::player),
+ ItemPredicate.CODEC.optionalFieldOf("item").forGetter(Conditions::item),
+ DamageSourcePredicate.CODEC.optionalFieldOf("damage_type").forGetter(Conditions::damageType)
+ ).apply(instance, ::Conditions)
+ }
+ }
+ }
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/block/IClearableBeforeInsertedIntoStaff.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/block/IClearableBeforeInsertedIntoStaff.kt
new file mode 100644
index 000000000..410c92e50
--- /dev/null
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/block/IClearableBeforeInsertedIntoStaff.kt
@@ -0,0 +1,38 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.api.block
+
+import net.minecraft.block.entity.AbstractFurnaceBlockEntity
+import net.minecraft.util.Clearable
+
+/**
+ * Performs additional clearing after [Clearable.clear]. Mixed into [AbstractFurnaceBlockEntity] to prevent XP
+ * duplication.
+ *
+ * @see Clearable.clear
+ */
+interface IClearableBeforeInsertedIntoStaff {
+ /**
+ * Called before a block is inserted into a staff and removed from the world.
+ *
+ * @see Clearable.clear
+ */
+ @Suppress("FunctionName") // Mixin
+ fun staffMod_clearBeforeRemovedFromWorld()
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/block/dispenser/CakeDispenserBehavior.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/block/dispenser/CakeDispenserBehavior.kt
index a81bba9ae..bf66256e3 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/block/dispenser/CakeDispenserBehavior.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/block/dispenser/CakeDispenserBehavior.kt
@@ -24,17 +24,18 @@ import net.minecraft.item.ItemStack
import net.minecraft.util.math.BlockPointer
import net.minecraft.util.math.Vec3d
import net.minecraft.world.WorldEvents
-import opekope2.avm_staff.api.cakeEntityType
import opekope2.avm_staff.api.entity.CakeEntity
-import opekope2.avm_staff.api.throwableCakesGameRule
+import opekope2.avm_staff.content.EntityTypes
+import opekope2.avm_staff.content.GameRules
/**
- * Dispenser behavior, which throws [cakes][CakeEntity], if [throwableCakes][throwableCakesGameRule] game rule is
+ * Dispenser behavior, which throws [cakes][CakeEntity], if [throwableCakes][GameRules.THROWABLE_CAKES] game rule is
* enabled.
*/
class CakeDispenserBehavior : ItemDispenserBehavior() {
override fun dispenseSilently(pointer: BlockPointer, stack: ItemStack): ItemStack {
- if (!pointer.world.gameRules.getBoolean(throwableCakesGameRule)) return super.dispenseSilently(pointer, stack)
+ if (!pointer.world.gameRules.getBoolean(GameRules.THROWABLE_CAKES))
+ return super.dispenseSilently(pointer, stack)
var spawnPos = DispenserBlock.getOutputLocation(pointer, 1.0, Vec3d.ZERO)
spawnPos = Vec3d(spawnPos.x, spawnPos.y, spawnPos.z).add(0.0, NEGATIVE_HALF_CAKE_HEIGHT, 0.0)
@@ -54,12 +55,12 @@ class CakeDispenserBehavior : ItemDispenserBehavior() {
}
override fun playSound(pointer: BlockPointer) {
- if (!pointer.world.gameRules.getBoolean(throwableCakesGameRule)) return super.playSound(pointer)
+ if (!pointer.world.gameRules.getBoolean(GameRules.THROWABLE_CAKES)) return super.playSound(pointer)
pointer.world().syncWorldEvent(WorldEvents.DISPENSER_LAUNCHES_PROJECTILE, pointer.pos(), 0)
}
private companion object {
- private val NEGATIVE_HALF_CAKE_HEIGHT = cakeEntityType.get().dimensions.height / -2.0
+ private val NEGATIVE_HALF_CAKE_HEIGHT = EntityTypes.cake.dimensions.height / -2.0
}
}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/component/BlockPickupDataComponent.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/component/BlockPickupDataComponent.kt
new file mode 100644
index 000000000..524e8be47
--- /dev/null
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/component/BlockPickupDataComponent.kt
@@ -0,0 +1,42 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.api.component
+
+import net.minecraft.block.BlockState
+import net.minecraft.block.Blocks
+import net.minecraft.network.RegistryByteBuf
+import net.minecraft.network.codec.PacketCodec
+import net.minecraft.util.math.BlockPos
+
+/**
+ * Data component to store the block about to be picked up by an empty staff.
+ *
+ * @param pos The position of the block to be picked up from. Only available server-side
+ * @param state The block state to be picked up. Only available server-side
+ */
+data class BlockPickupDataComponent(val pos: BlockPos, val state: BlockState) {
+ companion object {
+ /**
+ * [PacketCodec] for [BlockPickupDataComponent], which doesn't sync its data.
+ */
+ @JvmField
+ val NON_SYNCING_PACKET_CODEC: PacketCodec =
+ PacketCodec.of({ _, _ -> }, { BlockPickupDataComponent(BlockPos.ORIGIN, Blocks.AIR.defaultState) })
+ }
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/component/StaffFurnaceDataComponent.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/component/StaffFurnaceDataComponent.kt
index 47dd2b5a8..c39c5872a 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/component/StaffFurnaceDataComponent.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/component/StaffFurnaceDataComponent.kt
@@ -24,24 +24,15 @@ import net.minecraft.network.codec.PacketCodec
/**
* Data components to store the state of a furnace staff.
*
- * @param serverBurnTicks The ticks the furnace has been on for minus the items smelted. This data is not synced to
- * the client
+ * @param burnTicks The ticks the furnace has been on for minus the items smelted. Only available server-side
*/
-class StaffFurnaceDataComponent(var serverBurnTicks: Int) {
- override fun equals(other: Any?) = when {
- this === other -> true
- javaClass != other?.javaClass -> false
- else -> true
- }
-
- override fun hashCode() = javaClass.hashCode()
-
+data class StaffFurnaceDataComponent(var burnTicks: Int) {
companion object {
/**
- * [PacketCodec] for [StaffFurnaceDataComponent], which doesn't sync [serverBurnTicks].
+ * [PacketCodec] for [StaffFurnaceDataComponent], which doesn't sync its data.
*/
@JvmField
- val PACKET_CODEC: PacketCodec =
+ val NON_SYNCING_PACKET_CODEC: PacketCodec =
PacketCodec.of({ _, _ -> }, { StaffFurnaceDataComponent(0) })
}
}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/component/StaffItemComponent.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/component/StaffItemComponent.kt
index 10f185db0..f24261e2c 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/component/StaffItemComponent.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/component/StaffItemComponent.kt
@@ -19,11 +19,13 @@
package opekope2.avm_staff.api.component
import com.mojang.serialization.Codec
+import com.mojang.serialization.DataResult
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.component.ComponentType
import net.minecraft.item.ItemStack
import net.minecraft.network.RegistryByteBuf
import net.minecraft.network.codec.PacketCodec
+import opekope2.avm_staff.api.staff.StaffHandler
/**
* [ItemStack] wrapper to make them compatible with [ComponentType]s.
@@ -43,6 +45,11 @@ class StaffItemComponent(val item: ItemStack) {
return ItemStack.hashCode(item)
}
+ private fun validate(): DataResult {
+ return if (item.item in StaffHandler.Registry) DataResult.success(this)
+ else DataResult.error { "There is no staff handler registered for item ${item.item}" }
+ }
+
companion object {
/**
* [Codec] for [StaffItemComponent].
@@ -50,10 +57,16 @@ class StaffItemComponent(val item: ItemStack) {
@JvmField
val CODEC: Codec = RecordCodecBuilder.create { instance ->
instance.group(
- ItemStack.CODEC.fieldOf("item").forGetter(StaffItemComponent::item)
+ ItemStack.VALIDATED_CODEC.fieldOf("item").forGetter(StaffItemComponent::item)
).apply(instance, ::StaffItemComponent)
}
+ /**
+ * Validated [Codec] for [StaffItemComponent]. This only allows [item]s registered in [StaffHandler.Registry].
+ */
+ @JvmField
+ val VALIDATED_CODEC: Codec = CODEC.validate(StaffItemComponent::validate)
+
/**
* [PacketCodec] for [StaffItemComponent].
*/
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/component/StaffRendererOverrideComponent.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/component/StaffRendererOverrideComponent.kt
deleted file mode 100644
index 1abeca086..000000000
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/component/StaffRendererOverrideComponent.kt
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * AvM Staff Mod
- * Copyright (c) 2024 opekope2
- *
- * This mod is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This mod is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this mod. If not, see .
- */
-
-package opekope2.avm_staff.api.component
-
-import com.mojang.serialization.Codec
-import com.mojang.serialization.codecs.RecordCodecBuilder
-import net.minecraft.block.BlockState
-import net.minecraft.client.render.model.json.ModelTransformationMode
-import net.minecraft.entity.LivingEntity
-import net.minecraft.network.PacketByteBuf
-import net.minecraft.network.RegistryByteBuf
-import net.minecraft.network.codec.PacketCodec
-import opekope2.avm_staff.api.item.renderer.StaffRenderer
-import java.util.*
-
-/**
- * Data component to override the behavior of a [StaffRenderer].
- *
- * @param renderMode The display transform of the model to use
- * @param isActive The item should be treated as if it was [LivingEntity.getActiveItem]
- * @param blockState The blocks state to render in the staff
- */
-data class StaffRendererOverrideComponent(
- val renderMode: Optional,
- val isActive: Optional,
- val blockState: Optional
-) {
- private constructor(buf: RegistryByteBuf) : this(
- buf.readOptional {
- it.readEnumConstant(ModelTransformationMode::class.java)
- },
- buf.readOptional(PacketByteBuf::readBoolean),
- buf.readOptional {
- it.decodeAsJson(BlockState.CODEC)
- }
- )
-
- private fun encode(buf: RegistryByteBuf) {
- buf.writeOptional(renderMode, PacketByteBuf::writeEnumConstant)
- buf.writeOptional(isActive, PacketByteBuf::writeBoolean)
- buf.writeOptional(blockState) { buffer, state ->
- buffer.encodeAsJson(BlockState.CODEC, state)
- }
- }
-
- companion object {
- /**
- * [Codec] for [StaffRendererOverrideComponent].
- */
- @JvmField
- val CODEC: Codec = RecordCodecBuilder.create { instance ->
- instance.group(
- ModelTransformationMode.CODEC.optionalFieldOf("renderMode")
- .forGetter(StaffRendererOverrideComponent::renderMode),
- Codec.BOOL.optionalFieldOf("pointForward").forGetter(StaffRendererOverrideComponent::isActive),
- BlockState.CODEC.optionalFieldOf("blockState").forGetter(StaffRendererOverrideComponent::blockState)
- ).apply(instance, ::StaffRendererOverrideComponent)
- }
-
- /**
- * [PacketCodec] for [StaffRendererOverrideComponent].
- */
- @JvmField
- val PACKET_CODEC: PacketCodec =
- PacketCodec.of(StaffRendererOverrideComponent::encode, ::StaffRendererOverrideComponent)
- }
-}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/component/StaffTntDataComponent.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/component/StaffTntDataComponent.kt
new file mode 100644
index 000000000..8090f0106
--- /dev/null
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/component/StaffTntDataComponent.kt
@@ -0,0 +1,39 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.api.component
+
+import net.minecraft.network.RegistryByteBuf
+import net.minecraft.network.codec.PacketCodec
+import opekope2.avm_staff.api.entity.ImpactTntEntity
+
+/**
+ * Data components to store the shot TNT in a TNT staff.
+ *
+ * @param tnt The shot TNT entity. Only available server-side
+ */
+data class StaffTntDataComponent(val tnt: ImpactTntEntity?) {
+ companion object {
+ /**
+ * [PacketCodec] for [StaffTntDataComponent], which doesn't sync its data.
+ */
+ @JvmField
+ val NON_SYNCING_PACKET_CODEC: PacketCodec =
+ PacketCodec.of({ _, _ -> }, { StaffTntDataComponent(null) })
+ }
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/entity/CakeEntity.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/entity/CakeEntity.kt
index 443f1db70..866f53d3a 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/entity/CakeEntity.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/entity/CakeEntity.kt
@@ -18,22 +18,20 @@
package opekope2.avm_staff.api.entity
+import net.minecraft.advancement.criterion.Criteria
import net.minecraft.block.Blocks
-import net.minecraft.client.MinecraftClient
import net.minecraft.client.option.GraphicsMode
import net.minecraft.client.particle.BlockDustParticle
import net.minecraft.client.world.ClientWorld
-import net.minecraft.entity.Entity
-import net.minecraft.entity.EntityType
-import net.minecraft.entity.LivingEntity
-import net.minecraft.entity.MovementType
+import net.minecraft.entity.*
import net.minecraft.entity.damage.DamageSource
import net.minecraft.entity.data.DataTracker
-import net.minecraft.entity.data.TrackedDataHandlerRegistry
import net.minecraft.nbt.NbtCompound
import net.minecraft.network.packet.s2c.play.EntitySpawnS2CPacket
import net.minecraft.predicate.entity.EntityPredicates
+import net.minecraft.registry.tag.DamageTypeTags
import net.minecraft.server.network.EntityTrackerEntry
+import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.sound.SoundCategory
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Vec3d
@@ -41,57 +39,64 @@ import net.minecraft.util.math.random.Random
import net.minecraft.world.World
import net.minecraftforge.api.distmarker.Dist
import net.minecraftforge.api.distmarker.OnlyIn
-import opekope2.avm_staff.api.*
-import opekope2.avm_staff.util.damageSource
-import opekope2.avm_staff.util.times
+import opekope2.avm_staff.content.DamageTypes
+import opekope2.avm_staff.content.EntityTypes
+import opekope2.avm_staff.content.SoundEvents
+import opekope2.avm_staff.util.*
/**
* A flying cake entity, which splashes on collision damaging target(s).
*/
-class CakeEntity(entityType: EntityType, world: World) : Entity(entityType, world) {
+class CakeEntity(entityType: EntityType, world: World) : Entity(entityType, world), Ownable {
private var thrower: LivingEntity? = null
private var timeFalling = 0
+ private var redirectedByImpactTnt = false
+ /**
+ * Creates a new [CakeEntity].
+ *
+ * @param world The world to create the cake in
+ * @param position The position of the cake to spawn at
+ * @param velocity The velocity of the spawned cake
+ * @param thrower The entity that threw the cake
+ */
constructor(world: World, position: Vec3d, velocity: Vec3d, thrower: LivingEntity?) :
- this(cakeEntityType.get(), world) {
+ this(EntityTypes.cake, world) {
+ val (x, y, z) = position
+ val (vx, vy, vz) = velocity
+ init(x, y, z, vx, vy, vz, thrower)
+ }
+
+ private fun init(x: Double, y: Double, z: Double, vx: Double, vy: Double, vz: Double, thrower: LivingEntity?) {
intersectionChecked = true
- setPosition(position)
- this.velocity = velocity
- prevX = position.x
- prevY = position.y
- prevZ = position.z
+ setPosition(x, y, z)
+ setVelocity(vx, vy, vz)
+ prevX = x
+ prevY = y
+ prevZ = z
+ lookForward()
+ setPrevData()
startPos = blockPos
this.thrower = thrower
}
- override fun handleAttack(attacker: Entity?): Boolean {
- if (!world.isClient) {
- discard()
- }
- return true
- }
-
/**
* The position, where the cake was spawned.
*/
- var startPos: BlockPos
- get() = dataTracker[BLOCK_POS]
- set(pos) {
- dataTracker[BLOCK_POS] = pos
- }
+ var startPos: BlockPos = BlockPos.ORIGIN
+ private set
override fun getMoveEffect(): MoveEffect {
return MoveEffect.NONE
}
override fun initDataTracker(builder: DataTracker.Builder) {
- builder.add(BLOCK_POS, BlockPos.ORIGIN)
}
override fun onRemoved() {
world.playSound(
x, y, z,
- cakeSplashSoundEvent.get(), SoundCategory.BLOCKS,
+ SoundEvents.cakeSplash, SoundCategory.BLOCKS,
(CAKE_STATE.soundGroup.volume + 1f) / 2f, CAKE_STATE.soundGroup.pitch * .8f,
false
)
@@ -104,7 +109,6 @@ class CakeEntity(entityType: EntityType, world: World) : Entity(enti
val particlePerSide = particlePerSide - 1
val width = type.dimensions.width
val height = type.dimensions.height
- val particleManager = MinecraftClient.getInstance().particleManager
for (i in 0..particlePerSide) {
for (j in 0..particlePerSide) {
@@ -137,9 +141,11 @@ class CakeEntity(entityType: EntityType, world: World) : Entity(enti
override fun getGravity() = 0.04
override fun tick() {
+ setPrevData()
++timeFalling
applyGravity()
move(MovementType.SELF, velocity)
+ lookForward()
if (!world.isClient) {
if (timeFalling > 100 && blockPos.y !in world.topY downTo (world.bottomY + 1) || timeFalling > 600) {
discard()
@@ -150,6 +156,12 @@ class CakeEntity(entityType: EntityType, world: World) : Entity(enti
velocity *= 0.98
}
+ private fun setPrevData() {
+ prevYaw = yaw
+ prevPitch = pitch
+ prevHorizontalSpeed = horizontalSpeed
+ }
+
private fun splashOnImpact() {
if (horizontalCollision || verticalCollision) {
damageCollidingEntities()
@@ -172,8 +184,8 @@ class CakeEntity(entityType: EntityType, world: World) : Entity(enti
val damageables = EntityPredicates.EXCEPT_CREATIVE_OR_SPECTATOR.and(EntityPredicates.VALID_LIVING_ENTITY)
val thrower = thrower
val damageSource =
- if (thrower == null) world.damageSource(cakeDamageType)
- else world.damageSource(playerCakeDamageType, this, thrower)
+ if (thrower == null) world.damageSource(DamageTypes.PRANKED)
+ else world.damageSource(DamageTypes.PRANKED_BY_PLAYER, this, thrower)
world.getOtherEntities(this, boundingBox, damageables).forEach {
it.damage(damageSource, 1f)
@@ -182,12 +194,35 @@ class CakeEntity(entityType: EntityType, world: World) : Entity(enti
override fun handleFallDamage(fallDistance: Float, damageMultiplier: Float, damageSource: DamageSource) = false
+ override fun damage(source: DamageSource, amount: Float): Boolean {
+ if (world.isClient) return super.damage(source, amount)
+
+ val causer = source.source
+ if (causer is ImpactTntEntity && causer.owner != null) {
+ thrower = causer.owner
+ redirectedByImpactTnt = true
+ }
+
+ if (!isRemoved && !isInvulnerableTo(source) && !source.isIn(DamageTypeTags.IS_EXPLOSION)) {
+ discard()
+
+ val attacker = source.attacker
+ if (attacker is ServerPlayerEntity) {
+ Criteria.PLAYER_KILLED_ENTITY.trigger(attacker, this, source)
+ }
+ }
+
+ return super.damage(source, amount)
+ }
+
override fun writeCustomDataToNbt(nbt: NbtCompound) {
- nbt.putInt("Time", timeFalling)
+ nbt.putInt(TIME_KEY, timeFalling)
+ nbt.putBoolean(REDIRECTED_BY_IMPACT_TNT_KEY, redirectedByImpactTnt)
}
override fun readCustomDataFromNbt(nbt: NbtCompound) {
- timeFalling = nbt.getInt("Time")
+ timeFalling = nbt.getInt(TIME_KEY)
+ redirectedByImpactTnt = nbt.getBoolean(REDIRECTED_BY_IMPACT_TNT_KEY)
}
override fun doesRenderOnFire() = false
@@ -195,26 +230,29 @@ class CakeEntity(entityType: EntityType, world: World) : Entity(enti
override fun entityDataRequiresOperator() = true
override fun createSpawnPacket(entityTrackerEntry: EntityTrackerEntry) =
- EntitySpawnS2CPacket(this, entityTrackerEntry)
+ EntitySpawnS2CPacket(this, entityTrackerEntry, thrower?.id ?: 0)
override fun onSpawnPacket(packet: EntitySpawnS2CPacket) {
super.onSpawnPacket(packet)
- intersectionChecked = true
- setPosition(packet.x, packet.y, packet.z)
- startPos = blockPos
+ init(
+ packet.x, packet.y, packet.z,
+ packet.velocityX, packet.velocityY, packet.velocityZ,
+ world.getEntityById(packet.entityData) as? LivingEntity
+ )
}
+ override fun getOwner() = thrower
+
companion object {
+ private const val TIME_KEY = "Time"
+ private const val REDIRECTED_BY_IMPACT_TNT_KEY = "EngineeredAttack"
private val CAKE_STATE = Blocks.CAKE.defaultState
- private val BLOCK_POS = DataTracker.registerData(
- CakeEntity::class.java, TrackedDataHandlerRegistry.BLOCK_POS
- )
private val particlePerSide: Int
@OnlyIn(Dist.CLIENT)
- get() = when (MinecraftClient.getInstance().options.graphicsMode.value!!) {
- GraphicsMode.FAST -> 4
- GraphicsMode.FANCY -> 5
+ get() = when (clientOptions.graphicsMode.value) {
GraphicsMode.FABULOUS -> 6
+ GraphicsMode.FANCY -> 5
+ else -> 4
}
/**
@@ -231,7 +269,7 @@ class CakeEntity(entityType: EntityType, world: World) : Entity(enti
world.playSound(
null,
position.x, position.y, position.z,
- cakeThrowSoundEvent.get(), thrower?.soundCategory ?: return,
+ SoundEvents.cakeThrow, thrower?.soundCategory ?: return,
0.5f, 0.4f / (world.getRandom().nextFloat() * 0.4f + 0.8f)
)
}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/entity/CampfireFlameEntity.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/entity/CampfireFlameEntity.kt
new file mode 100644
index 000000000..02ba26f85
--- /dev/null
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/entity/CampfireFlameEntity.kt
@@ -0,0 +1,395 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.api.entity
+
+import net.minecraft.block.*
+import net.minecraft.block.piston.PistonBehavior
+import net.minecraft.client.option.GraphicsMode
+import net.minecraft.entity.Entity
+import net.minecraft.entity.EntityType
+import net.minecraft.entity.LivingEntity
+import net.minecraft.entity.data.DataTracker
+import net.minecraft.entity.projectile.ProjectileUtil
+import net.minecraft.nbt.NbtCompound
+import net.minecraft.network.PacketByteBuf
+import net.minecraft.particle.ParticleEffect
+import net.minecraft.particle.ParticleType
+import net.minecraft.registry.Registries
+import net.minecraft.registry.RegistryKey
+import net.minecraft.registry.RegistryKeys
+import net.minecraft.state.property.Properties.LIT
+import net.minecraft.util.hit.BlockHitResult
+import net.minecraft.util.hit.HitResult
+import net.minecraft.util.math.BlockPos
+import net.minecraft.util.math.Box
+import net.minecraft.util.math.Vec3d
+import net.minecraft.world.RaycastContext
+import net.minecraft.world.World
+import net.minecraft.world.event.GameEvent
+import net.minecraftforge.api.distmarker.Dist
+import net.minecraftforge.api.distmarker.OnlyIn
+import net.minecraftforge.entity.IEntityAdditionalSpawnData
+import opekope2.avm_staff.content.EntityTypes
+import opekope2.avm_staff.content.ParticleTypes
+import opekope2.avm_staff.util.*
+import java.util.*
+
+/**
+ * Technical entity representing a part of a flame of a campfire staff.
+ */
+class CampfireFlameEntity : Entity, IEntityAdditionalSpawnData {
+ private var currentRelativeRight: Vec3d = Vec3d.ZERO
+ private var currentRelativeUp: Vec3d = Vec3d.ZERO
+
+ private lateinit var parameters: Parameters
+
+ private lateinit var shooter: LivingEntity
+
+ private var rays: BitSet
+ private inline val rayResolution: Int
+ get() = (parameters as? ServerParameters)?.rayResolution ?: flameParticleRayResolution
+
+ @Deprecated("This constructor is not supported on the server")
+ internal constructor(type: EntityType<*>, world: World) : super(type, world) {
+ require(world.isClient) { "This constructor is not supported on the server" }
+ // Leave edges unset for accurate visuals
+ rays = BitSet(flameParticleRayResolution * flameParticleRayResolution).apply {
+ for (i in 1 until flameParticleRayResolution - 1) {
+ for (j in 1 until flameParticleRayResolution - 1) {
+ this.set(i * flameParticleRayResolution + j)
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates a new [CampfireFlameEntity].
+ *
+ * @param world The world to create the flame entity in
+ * @param parameters Parameters for the flame
+ * @param shooter The entity shooting the flame
+ */
+ constructor(world: World, parameters: ServerParameters, shooter: LivingEntity) :
+ super(EntityTypes.campfireFlame, world) {
+ this.setPosition(parameters.origin)
+ this.velocity = shooter.velocity + parameters.relativeTarget * (1.0 / parameters.stepResolution)
+
+ this.parameters = parameters
+
+ this.shooter = shooter
+
+ val rayCount = rayResolution * rayResolution
+ this.rays = BitSet(rayCount).apply { set(0, rayCount) }
+ }
+
+ override fun initDataTracker(builder: DataTracker.Builder) {
+ }
+
+ override fun readCustomDataFromNbt(nbt: NbtCompound?) {
+ }
+
+ override fun writeCustomDataToNbt(nbt: NbtCompound?) {
+ }
+
+ override fun tick() {
+ super.tick()
+
+ val nextPos = pos + velocity
+ val nextRelativeRight = parameters.flameConeWidth * (age.toDouble() / parameters.stepResolution)
+ val nextRelativeUp = parameters.flameConeHeight * (age.toDouble() / parameters.stepResolution)
+
+ if (world.isClient) {
+ @Suppress("UNCHECKED_CAST")
+ val particleType = Registries.PARTICLE_TYPE[parameters.particleType as RegistryKey>]
+ val particleEffect = particleType as? ParticleEffect ?: ParticleTypes.flame
+
+ tickRays(nextPos, nextRelativeRight, nextRelativeUp) { start, end ->
+ val result = tickRayClient(start, end)
+ if (!result.stopsRay) spawnParticle(particleEffect, start, end)
+ result
+ }
+ } else {
+ val parameters = parameters as ServerParameters
+ val entitiesToBurn = mutableSetOf()
+ val blocksToLight = mutableSetOf()
+ val blocksToSetOnFire = mutableSetOf()
+
+ tickRays(nextPos, nextRelativeRight, nextRelativeUp) { start, end ->
+ tickRayServer(start, end, parameters, entitiesToBurn, blocksToLight, blocksToSetOnFire)
+ }
+
+ burnEntities(entitiesToBurn, parameters.flameFireTicks)
+ lightBlocks(blocksToLight)
+ setBlocksOnFire(blocksToSetOnFire)
+ }
+
+ currentRelativeRight = nextRelativeRight
+ currentRelativeUp = nextRelativeUp
+ setPosition(nextPos)
+ lookForward()
+
+ if (!world.isClient && age >= parameters.stepResolution) discard()
+ }
+
+ @OnlyIn(Dist.CLIENT)
+ private fun spawnParticle(particleEffect: ParticleEffect, start: Vec3d, end: Vec3d) {
+ val particleOffset = (end - start) * random.nextDouble() +
+ currentRelativeRight * ((random.nextDouble() * 2 - 1) / rayResolution) +
+ currentRelativeUp * ((random.nextDouble() * 2 - 1) / rayResolution)
+ val (x, y, z) = start + particleOffset
+ particleManager.addParticle(particleEffect, x, y, z, 0.0, 0.0, 0.0)!!.apply {
+ scale(random.nextFloat() - random.nextFloat() + 1f)
+ maxAge = (0.25 * FLAME_MAX_AGE / (Math.random() * 0.8 + 0.2) - 0.05 * FLAME_MAX_AGE).toInt()
+ }
+ }
+
+ private inline fun tickRays(
+ nextPos: Vec3d,
+ nextRelativeRight: Vec3d,
+ nextRelativeUp: Vec3d,
+ tickRay: (start: Vec3d, end: Vec3d) -> HitResult.Type
+ ) {
+ for (offsetX in 0 until rayResolution) {
+ for (offsetY in 0 until rayResolution) {
+ val index = offsetX * rayResolution + offsetY
+ if (!rays[index]) continue
+
+ val scaleX = offsetX / (rayResolution - 1.0) - 0.5
+ val scaleY = offsetY / (rayResolution - 1.0) - 0.5
+ val start = pos + currentRelativeRight * scaleX + currentRelativeUp * scaleY
+ val end = nextPos + nextRelativeRight * scaleX + nextRelativeUp * scaleY
+
+ rays[index] = !tickRay(start, end).stopsRay
+ }
+ }
+ }
+
+ @OnlyIn(Dist.CLIENT)
+ private fun tickRayClient(start: Vec3d, end: Vec3d): HitResult.Type {
+ val blockHit = raycastBlock(start, end)
+ val entityHit = raycastEntity(start, end, false)
+
+ return when {
+ entityHit != null && entityHit.pos.squaredDistanceTo(start) < blockHit.pos.squaredDistanceTo(start) -> HitResult.Type.ENTITY
+ blockHit.type == HitResult.Type.BLOCK -> HitResult.Type.BLOCK
+ else -> HitResult.Type.MISS
+ }
+ }
+
+ private fun tickRayServer(
+ start: Vec3d,
+ end: Vec3d,
+ parameters: ServerParameters,
+ entitiesToBurn: MutableSet,
+ blocksToLight: MutableSet,
+ blocksToSetOnFire: MutableSet
+ ): HitResult.Type {
+ val blockHit = raycastBlock(start, end)
+ val entityHit = raycastEntity(start, end, parameters.damageShooter)
+
+ return when {
+ entityHit != null && entityHit.pos.squaredDistanceTo(start) < blockHit.pos.squaredDistanceTo(start) -> {
+ entitiesToBurn += entityHit.entity
+ HitResult.Type.ENTITY
+ }
+
+ blockHit.type == HitResult.Type.BLOCK -> {
+ collectBlockToLight(
+ blockHit,
+ blocksToLight,
+ blocksToSetOnFire,
+ parameters.flammableBlockFireChance,
+ parameters.nonFlammableBlockFireChance
+ )
+ HitResult.Type.BLOCK
+ }
+
+ else -> HitResult.Type.MISS
+ }
+ }
+
+ private fun raycastBlock(start: Vec3d, end: Vec3d) = world.raycast(
+ RaycastContext(
+ start,
+ end,
+ RaycastContext.ShapeType.COLLIDER,
+ RaycastContext.FluidHandling.ANY,
+ ShapeContext.absent()
+ )
+ )
+
+ private fun raycastEntity(start: Vec3d, end: Vec3d, includeShooter: Boolean) = ProjectileUtil.raycast(
+ this,
+ start,
+ end,
+ Box(start, end),
+ { it != shooter || includeShooter },
+ velocity.lengthSquared()
+ )
+
+ private fun collectBlockToLight(
+ blockHit: BlockHitResult,
+ blocksToLight: MutableSet,
+ blocksToSetOnFire: MutableSet,
+ flammableBlockFireChance: Double,
+ nonFlammableBlockFireChance: Double
+ ) {
+ val firePos = blockHit.blockPos.offset(blockHit.side)
+ val blockToLight = world.getBlockState(blockHit.blockPos)
+ if (CampfireBlock.canBeLit(blockToLight) ||
+ CandleBlock.canBeLit(blockToLight) ||
+ CandleCakeBlock.canBeLit(blockToLight)
+ ) {
+ blocksToLight += blockHit.blockPos
+ return
+ }
+
+ if (!world.canSetBlock(firePos)) return
+ if (!AbstractFireBlock.canPlaceAt(world, firePos, horizontalFacing)) return
+
+ var fireCauseChance =
+ if (world.getBlockState(blockHit.blockPos).isBurnable) flammableBlockFireChance
+ else nonFlammableBlockFireChance
+ fireCauseChance /= rayResolution * rayResolution
+
+ if (Math.random() < fireCauseChance) {
+ blocksToSetOnFire += firePos
+ }
+ }
+
+ private fun burnEntities(entities: Set, flameFireTicks: Int) {
+ for (target in entities) {
+ if (target.isOnFire) {
+ // Technically inFire, but use onFire, because it has a more fitting death message
+ target.damage(target.damageSources.onFire(), flameFireTicks.toFloat())
+ }
+ target.fireTicks = target.fireTicks.coerceAtLeast(0) + flameFireTicks + 1
+ (target as? LivingEntity)?.attacker = shooter
+ }
+ }
+
+ private fun lightBlocks(blocksToLight: MutableSet) {
+ for (pos in blocksToLight) {
+ world.setBlockState(pos, world.getBlockState(pos).with(LIT, true), Block.NOTIFY_ALL_AND_REDRAW)
+ world.emitGameEvent(this, GameEvent.BLOCK_CHANGE, pos)
+ }
+ }
+
+ private fun setBlocksOnFire(blocksToSetOnFire: MutableSet) {
+ for (pos in blocksToSetOnFire) {
+ world.setBlockState(pos, AbstractFireBlock.getState(world, pos), Block.NOTIFY_ALL_AND_REDRAW)
+ world.emitGameEvent(this, GameEvent.BLOCK_PLACE, pos)
+ }
+ }
+
+ override fun canUsePortals(allowVehicles: Boolean) = false
+
+ override fun getPistonBehavior() = PistonBehavior.IGNORE
+
+ override fun writeSpawnData(buf: PacketByteBuf) {
+ parameters.write(buf)
+ buf.writeVarInt(shooter.id)
+ }
+
+ override fun readSpawnData(buf: PacketByteBuf) {
+ parameters = Parameters(buf)
+ shooter = world.getEntityById(buf.readVarInt()) as LivingEntity
+ }
+
+ /**
+ * Parameters of a [CampfireFlameEntity].
+ *
+ * @param origin Starting position of the entity
+ * @param relativeTarget The position towards the fire goes relative to [origin]
+ * @param flameConeWidth The width of the fire cone, points right relative to the shooter's POV
+ * @param flameConeHeight The height of the fire cone, points up relative to the shooter's POV
+ * @param stepResolution How many ticks to divide the distance between [origin] and [relativeTarget]
+ * @param particleType The registry key of the flame particle type in [Registries.PARTICLE_TYPE]
+ */
+ open class Parameters(
+ val origin: Vec3d,
+ val relativeTarget: Vec3d,
+ val flameConeWidth: Vec3d,
+ val flameConeHeight: Vec3d,
+ val stepResolution: Int,
+ val particleType: RegistryKey>,
+ ) {
+ constructor(buf: PacketByteBuf) : this(
+ buf.readVec3d(),
+ buf.readVec3d(),
+ buf.readVec3d(),
+ buf.readVec3d(),
+ buf.readVarInt(),
+ buf.readRegistryKey(RegistryKeys.PARTICLE_TYPE)
+ )
+
+ fun write(buf: PacketByteBuf) {
+ buf.writeVec3d(origin)
+ buf.writeVec3d(relativeTarget)
+ buf.writeVec3d(flameConeWidth)
+ buf.writeVec3d(flameConeHeight)
+ buf.writeVarInt(stepResolution)
+ buf.writeRegistryKey(particleType)
+ }
+ }
+
+ /**
+ * Server-side parameters of a [CampfireFlameEntity].
+ *
+ * @param origin Starting position of the entity
+ * @param relativeTarget The position towards the fire goes relative to [origin]
+ * @param flameConeWidth The width of the fire cone, points right relative to the shooter's POV
+ * @param flameConeHeight The height of the fire cone, points up relative to the shooter's POV
+ * @param stepResolution How many ticks to divide the distance between [origin] and [relativeTarget]
+ * @param rayResolution The resolution to divide the fire cone both horizontally and vertically
+ * @param particleType The registry key of the flame particle type in [Registries.PARTICLE_TYPE]
+ * @param flammableBlockFireChance The chance a [flammable][BlockState.isBurnable] block is set on fire
+ * @param nonFlammableBlockFireChance The chance a [non-flammable][BlockState.isBurnable] block is set on fire
+ * @param flameFireTicks The number of ticks an entity is additionally set on fire for
+ * @param damageShooter Whether the flame should damage the shooter
+ */
+ class ServerParameters(
+ origin: Vec3d,
+ relativeTarget: Vec3d,
+ flameConeWidth: Vec3d,
+ flameConeHeight: Vec3d,
+ stepResolution: Int,
+ particleType: RegistryKey>,
+ val rayResolution: Int,
+ val flammableBlockFireChance: Double,
+ val nonFlammableBlockFireChance: Double,
+ val flameFireTicks: Int,
+ val damageShooter: Boolean
+ ) : Parameters(origin, relativeTarget, flameConeWidth, flameConeHeight, stepResolution, particleType)
+
+ private companion object {
+ private const val FLAME_MAX_AGE = 16
+
+ private val HitResult.Type.stopsRay: Boolean
+ get() = this == HitResult.Type.BLOCK
+
+ private val flameParticleRayResolution: Int
+ @OnlyIn(Dist.CLIENT)
+ get() = when (clientOptions.graphicsMode.value) {
+ GraphicsMode.FABULOUS -> 6
+ GraphicsMode.FANCY -> 5
+ else -> 4
+ }
+ }
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/entity/ImpactTntEntity.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/entity/ImpactTntEntity.kt
index 4c65015e0..da27de112 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/entity/ImpactTntEntity.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/entity/ImpactTntEntity.kt
@@ -18,16 +18,25 @@
package opekope2.avm_staff.api.entity
+import net.minecraft.advancement.criterion.Criteria
+import net.minecraft.enchantment.EnchantmentHelper
import net.minecraft.entity.*
+import net.minecraft.entity.damage.DamageSource
+import net.minecraft.nbt.NbtCompound
import net.minecraft.predicate.entity.EntityPredicates
+import net.minecraft.registry.tag.DamageTypeTags
+import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.math.Vec3d
import net.minecraft.world.World
-import opekope2.avm_staff.api.impactTntEntityType
+import opekope2.avm_staff.content.Enchantments
+import opekope2.avm_staff.content.EntityTypes
+import opekope2.avm_staff.util.plus
/**
* A TNT entity, which explodes on collision.
*/
class ImpactTntEntity(entityType: EntityType, world: World) : TntEntity(entityType, world), Ownable {
+ private var juggles = 0
private var owner: LivingEntity? = null
/**
@@ -42,7 +51,7 @@ class ImpactTntEntity(entityType: EntityType, world: World) : T
*
*/
constructor(world: World, x: Double, y: Double, z: Double, velocity: Vec3d, owner: LivingEntity?) :
- this(impactTntEntityType.get(), world) {
+ this(EntityTypes.impactTnt, world) {
setPosition(x, y, z)
this.velocity = velocity
fuse = 80
@@ -52,6 +61,11 @@ class ImpactTntEntity(entityType: EntityType, world: World) : T
this.owner = owner
}
+ override fun tick() {
+ super.tick()
+ if (timeUntilRegen > 0) timeUntilRegen--
+ }
+
override fun move(movementType: MovementType?, movement: Vec3d?) {
super.move(movementType, movement)
if (!world.isClient) {
@@ -59,6 +73,53 @@ class ImpactTntEntity(entityType: EntityType, world: World) : T
}
}
+ override fun damage(source: DamageSource, amount: Float): Boolean {
+ if (world.isClient) return super.damage(source, amount)
+
+ if (!isRemoved && !isInvulnerableTo(source) && !source.isIn(DamageTypeTags.IS_EXPLOSION) && timeUntilRegen == 0) {
+ val attacker = source.attacker
+ if (attacker is LivingEntity) {
+ if (owner === attacker) {
+ juggles++
+ } else {
+ owner = attacker
+ juggles = 0
+ }
+
+ val redirect = EnchantmentHelper.hasAnyEnchantmentsIn(
+ attacker.mainHandStack,
+ Enchantments.Tags.REDIRECTS_IMPACT_TNT
+ )
+ if (redirect) {
+ timeUntilRegen = 10
+ velocity += attacker.rotationVector
+
+ if (attacker is ServerPlayerEntity) {
+ Criteria.PLAYER_HURT_ENTITY.trigger(attacker, this, source, amount, 0f, false)
+ }
+ } else {
+ explodeLater()
+
+ if (attacker is ServerPlayerEntity) {
+ Criteria.PLAYER_KILLED_ENTITY.trigger(attacker, this, source)
+ }
+ }
+ }
+ }
+
+ return super.damage(source, amount)
+ }
+
+ override fun readCustomDataFromNbt(nbt: NbtCompound) {
+ super.readCustomDataFromNbt(nbt)
+ juggles = nbt.getInt(JUGGLES_KEY)
+ }
+
+ override fun writeCustomDataToNbt(nbt: NbtCompound) {
+ super.writeCustomDataToNbt(nbt)
+ nbt.putInt(JUGGLES_KEY, juggles)
+ }
+
private fun explodeOnImpact() {
if (horizontalCollision || verticalCollision) {
explodeLater()
@@ -94,4 +155,8 @@ class ImpactTntEntity(entityType: EntityType, world: World) : T
}
override fun getOwner() = owner
+
+ private companion object {
+ private const val JUGGLES_KEY = "Juggles"
+ }
}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/entity/renderer/CakeEntityRenderer.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/entity/renderer/CakeEntityRenderer.kt
index fe0d7a245..d78cc0c79 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/entity/renderer/CakeEntityRenderer.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/entity/renderer/CakeEntityRenderer.kt
@@ -20,14 +20,12 @@ package opekope2.avm_staff.api.entity.renderer
import net.minecraft.block.Blocks
import net.minecraft.client.render.OverlayTexture
-import net.minecraft.client.render.RenderLayers
import net.minecraft.client.render.VertexConsumerProvider
import net.minecraft.client.render.entity.EntityRenderer
import net.minecraft.client.render.entity.EntityRendererFactory
import net.minecraft.client.texture.SpriteAtlasTexture
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.util.Identifier
-import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.MathHelper
import net.minecraft.util.math.random.Random
import net.minecraftforge.api.distmarker.Dist
@@ -36,7 +34,6 @@ import opekope2.avm_staff.api.entity.CakeEntity
import opekope2.avm_staff.mixin.ICakeBlockAccessor
import opekope2.avm_staff.util.push
import org.joml.Quaternionf
-import kotlin.math.sqrt
/**
* Renderer of [CakeEntity].
@@ -57,30 +54,15 @@ class CakeEntityRenderer(context: EntityRendererFactory.Context) : EntityRendere
vertexConsumers: VertexConsumerProvider,
light: Int
) {
- val normalSpeed = cake.velocity.normalize()
- val horizontalSpeed = sqrt(normalSpeed.x * normalSpeed.x + normalSpeed.z * normalSpeed.z)
- val cakeYaw = MathHelper.atan2(normalSpeed.x, normalSpeed.z).toFloat()
- val cakePitch = MathHelper.atan2(horizontalSpeed, normalSpeed.y).toFloat()
+ val cakeYaw = MathHelper.lerpAngleDegrees(tickDelta, cake.prevYaw, cake.yaw)
+ val cakePitch = MathHelper.lerpAngleDegrees(tickDelta, cake.prevPitch, cake.pitch)
matrices.push {
- matrices.translate(0f, cake.getDimensions(cake.pose).height / 2, 0f)
- matrices.multiply(Quaternionf().rotationYXZ(cakeYaw, cakePitch, 0f))
- matrices.translate(-.5f, NEGATIVE_HALF_CAKE_HEIGHT, -.5f)
+ translate(0f, cake.getDimensions(cake.pose).height / 2, 0f)
+ multiply(Quaternionf().rotationYXZ(cakeYaw, cakePitch, 0f))
+ translate(-.5f, NEGATIVE_HALF_CAKE_HEIGHT, -.5f)
- blockRenderManager.modelRenderer.render(
- cake.world,
- blockRenderManager.getModel(CAKE_STATE),
- CAKE_STATE,
- BlockPos.ofFloored(cake.x, cake.boundingBox.maxY, cake.z),
- matrices,
- vertexConsumers.getBuffer(
- RenderLayers.getMovingBlockLayer(CAKE_STATE)
- ),
- false,
- Random.create(),
- CAKE_STATE.getRenderingSeed(cake.startPos),
- OverlayTexture.DEFAULT_UV
- )
+ blockRenderManager.renderBlockAsEntity(CAKE_STATE, this, vertexConsumers, light, OverlayTexture.DEFAULT_UV)
}
super.render(cake, yaw, tickDelta, matrices, vertexConsumers, light)
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/item/StaffItem.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/item/StaffItem.kt
index a8474cdf8..6204522d5 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/item/StaffItem.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/item/StaffItem.kt
@@ -20,6 +20,7 @@ package opekope2.avm_staff.api.item
import net.minecraft.component.DataComponentTypes
import net.minecraft.entity.Entity
+import net.minecraft.entity.EquipmentSlot
import net.minecraft.entity.ItemEntity
import net.minecraft.entity.LivingEntity
import net.minecraft.entity.player.PlayerEntity
@@ -34,6 +35,7 @@ import net.minecraft.util.TypedActionResult
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.world.World
+import net.minecraftforge.registries.RegistryObject
import opekope2.avm_staff.api.staff.StaffHandler
import opekope2.avm_staff.util.*
@@ -42,7 +44,11 @@ import opekope2.avm_staff.util.*
* Implementing loader-specific interfaces is highly recommended when extending the class to pass loader-specific
* functionality to [StaffHandler].
*/
-abstract class StaffItem(settings: Settings) : Item(settings) {
+abstract class StaffItem(settings: Settings, private val repairIngredientSupplier: RegistryObject
- ?) :
+ Item(settings) {
+ override fun canRepair(stack: ItemStack, ingredient: ItemStack) =
+ repairIngredientSupplier != null && ingredient.isOf(repairIngredientSupplier.get())
+
override fun onItemEntityDestroyed(entity: ItemEntity) {
val staffStack = entity.stack
val staffItem = staffStack.mutableItemStackInStaff ?: return
@@ -50,32 +56,38 @@ abstract class StaffItem(settings: Settings) : Item(settings) {
}
override fun postProcessComponents(stack: ItemStack) {
- stack[DataComponentTypes.ATTRIBUTE_MODIFIERS] = stack.itemInStaff.staffHandlerOrDefault.attributeModifiers
+ stack[DataComponentTypes.ATTRIBUTE_MODIFIERS] = stack.itemInStaff.staffHandlerOrFallback.attributeModifiers
}
override fun getMaxUseTime(stack: ItemStack, user: LivingEntity): Int {
- return stack.itemInStaff.staffHandlerOrDefault.maxUseTime // TODO extend API
+ return stack.itemInStaff.staffHandlerOrFallback.getMaxUseTime(stack, user.entityWorld, user)
}
override fun use(world: World, user: PlayerEntity, hand: Hand): TypedActionResult {
val staffStack = user.getStackInHand(hand)
- return staffStack.itemInStaff.staffHandlerOrDefault.use(staffStack, world, user, hand)
+ return staffStack.itemInStaff.staffHandlerOrFallback.use(staffStack, world, user, hand)
}
override fun usageTick(world: World, user: LivingEntity, stack: ItemStack, remainingUseTicks: Int) {
- stack.itemInStaff.staffHandlerOrDefault.usageTick(stack, world, user, remainingUseTicks)
+ stack.itemInStaff.staffHandlerOrFallback.usageTick(stack, world, user, remainingUseTicks)
}
override fun onStoppedUsing(stack: ItemStack, world: World, user: LivingEntity, remainingUseTicks: Int) {
- stack.itemInStaff.staffHandlerOrDefault.onStoppedUsing(stack, world, user, remainingUseTicks)
+ stack.itemInStaff.staffHandlerOrFallback.onStoppedUsing(stack, world, user, remainingUseTicks)
}
override fun finishUsing(stack: ItemStack, world: World, user: LivingEntity): ItemStack {
- return stack.itemInStaff.staffHandlerOrDefault.finishUsing(stack, world, user)
+ return stack.itemInStaff.staffHandlerOrFallback.finishUsing(stack, world, user)
+ }
+
+ override fun postHit(stack: ItemStack, target: LivingEntity, attacker: LivingEntity) = true
+
+ override fun postDamageEntity(stack: ItemStack, target: LivingEntity, attacker: LivingEntity) {
+ stack.damage(1, attacker, EquipmentSlot.MAINHAND)
}
override fun useOnBlock(context: ItemUsageContext): ActionResult {
- return context.stack.itemInStaff.staffHandlerOrDefault.useOnBlock(
+ return context.stack.itemInStaff.staffHandlerOrFallback.useOnBlock(
context.stack,
context.world,
context.player ?: return ActionResult.PASS,
@@ -86,40 +98,40 @@ abstract class StaffItem(settings: Settings) : Item(settings) {
}
override fun useOnEntity(stack: ItemStack, user: PlayerEntity, entity: LivingEntity, hand: Hand): ActionResult {
- return stack.itemInStaff.staffHandlerOrDefault.useOnEntity(stack, user.world, user, entity, hand)
+ return stack.itemInStaff.staffHandlerOrFallback.useOnEntity(stack, user.world, user, entity, hand)
}
/**
* @see StaffHandler.attack
*/
open fun attack(staffStack: ItemStack, world: World, attacker: LivingEntity, hand: Hand) =
- staffStack.itemInStaff.staffHandlerOrDefault.attack(staffStack, world, attacker, hand)
+ staffStack.itemInStaff.staffHandlerOrFallback.attack(staffStack, world, attacker, hand)
/**
* @see StaffHandler.attackBlock
*/
open fun attackBlock(
staffStack: ItemStack, world: World, attacker: LivingEntity, target: BlockPos, side: Direction, hand: Hand
- ) = staffStack.itemInStaff.staffHandlerOrDefault.attackBlock(staffStack, world, attacker, target, side, hand)
+ ) = staffStack.itemInStaff.staffHandlerOrFallback.attackBlock(staffStack, world, attacker, target, side, hand)
/**
* @see StaffHandler.attackEntity
*/
open fun attackEntity(
staffStack: ItemStack, world: World, attacker: LivingEntity, target: Entity, hand: Hand
- ) = staffStack.itemInStaff.staffHandlerOrDefault.attackEntity(staffStack, world, attacker, target, hand)
+ ) = staffStack.itemInStaff.staffHandlerOrFallback.attackEntity(staffStack, world, attacker, target, hand)
/**
* @see StaffHandler.canSwingHand
*/
open fun canSwingHand(staffStack: ItemStack, world: World, holder: LivingEntity, hand: Hand) =
- staffStack.itemInStaff.staffHandlerOrDefault.canSwingHand(staffStack, world, holder, hand)
+ staffStack.itemInStaff.staffHandlerOrFallback.canSwingHand(staffStack, world, holder, hand)
/**
* @see StaffHandler.disablesShield
*/
open fun disablesShield(staffStack: ItemStack, world: World, attacker: LivingEntity, hand: Hand) =
- staffStack.itemInStaff.staffHandlerOrDefault.disablesShield(staffStack, world, attacker, hand)
+ staffStack.itemInStaff.staffHandlerOrFallback.disablesShield(staffStack, world, attacker, hand)
override fun getName(stack: ItemStack): Text {
val staffItem = stack.itemStackInStaff ?: return super.getName(stack)
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/item/renderer/BlockStateStaffItemRenderer.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/item/renderer/BlockStateStaffItemRenderer.kt
index 49d8b02f2..55e9cf7f8 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/item/renderer/BlockStateStaffItemRenderer.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/item/renderer/BlockStateStaffItemRenderer.kt
@@ -18,27 +18,35 @@
package opekope2.avm_staff.api.item.renderer
+import net.minecraft.block.Block
import net.minecraft.block.BlockState
-import net.minecraft.client.MinecraftClient
import net.minecraft.client.render.VertexConsumerProvider
import net.minecraft.client.render.block.BlockModels
import net.minecraft.client.render.model.json.ModelTransformationMode
-import net.minecraft.client.util.ModelIdentifier
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.item.ItemStack
import net.minecraftforge.api.distmarker.Dist
import net.minecraftforge.api.distmarker.OnlyIn
+import opekope2.avm_staff.util.bakedModelManager
+import opekope2.avm_staff.util.itemRenderer
/**
- * A [IStaffItemRenderer], always which renders a single block state.
+ * A [StaffItemRenderer], always which renders a single block state.
*
* @param blockState The block state to render
*/
@OnlyIn(Dist.CLIENT)
-class BlockStateStaffItemRenderer(blockState: BlockState) : IStaffItemRenderer {
+class BlockStateStaffItemRenderer(blockState: BlockState) : StaffItemRenderer() {
private val blockStateId = BlockModels.getModelId(blockState)
private val blockItem = blockState.block.asItem().defaultStack
+ /**
+ * Creates a new [BlockStateStaffItemRenderer] with the [default state][Block.defaultState] of the given block.
+ *
+ * @param block The block to render its default state
+ */
+ constructor(block: Block) : this(block.defaultState)
+
override fun renderItemInStaff(
staffStack: ItemStack,
mode: ModelTransformationMode,
@@ -46,56 +54,16 @@ class BlockStateStaffItemRenderer(blockState: BlockState) : IStaffItemRenderer {
vertexConsumers: VertexConsumerProvider,
light: Int,
overlay: Int
- ) = renderBlockState(blockStateId, blockItem, matrices, vertexConsumers, light, overlay)
-
- companion object {
- /**
- * Renders a [BlockState].
- *
- * @param blockState The block state to render
- * @param matrices The render transformation matrix
- * @param vertexConsumers The render output
- * @param light Light parameter from the game
- * @param overlay Overlay parameter from the game
- */
- @JvmStatic
- fun renderBlockState(
- blockState: BlockState,
- matrices: MatrixStack,
- vertexConsumers: VertexConsumerProvider,
- light: Int,
- overlay: Int
- ) {
- val blockStateId = BlockModels.getModelId(blockState)
- val blockStateItem = blockState.block.asItem().defaultStack
- renderBlockState(blockStateId, blockStateItem, matrices, vertexConsumers, light, overlay)
- }
-
- /**
- * Renders a [BlockState].
- *
- * @param blockStateId The ID of the block state
- * @param blockStateItem The item form of the block state
- * @param matrices The render transformation matrix
- * @param vertexConsumers The render output
- * @param light Light parameter from the game
- * @param overlay Overlay parameter from the game
- */
- @JvmStatic
- fun renderBlockState(
- blockStateId: ModelIdentifier,
- blockStateItem: ItemStack,
- matrices: MatrixStack,
- vertexConsumers: VertexConsumerProvider,
- light: Int,
- overlay: Int
- ) {
- val itemRenderer = MinecraftClient.getInstance().itemRenderer
- val modelManager = MinecraftClient.getInstance().bakedModelManager
- val model = modelManager.getModel(blockStateId)
- itemRenderer.renderItem(
- blockStateItem, ModelTransformationMode.NONE, false, matrices, vertexConsumers, light, overlay, model
- )
- }
+ ) {
+ itemRenderer.renderItem(
+ blockItem,
+ ModelTransformationMode.NONE,
+ false,
+ matrices,
+ vertexConsumers,
+ light,
+ overlay,
+ bakedModelManager.getModel(blockStateId)
+ )
}
}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/item/renderer/MissingModelStaffItemRenderer.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/item/renderer/MissingModelStaffItemRenderer.kt
new file mode 100644
index 000000000..64ea00fc7
--- /dev/null
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/item/renderer/MissingModelStaffItemRenderer.kt
@@ -0,0 +1,54 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.api.item.renderer
+
+import net.minecraft.client.render.VertexConsumerProvider
+import net.minecraft.client.render.model.json.ModelTransformationMode
+import net.minecraft.client.util.math.MatrixStack
+import net.minecraft.item.ItemStack
+import opekope2.avm_staff.util.bakedModelManager
+import opekope2.avm_staff.util.itemRenderer
+import opekope2.avm_staff.util.itemStackInStaff
+
+/**
+ * A staff item renderer, which renders the missing model.
+ */
+object MissingModelStaffItemRenderer : StaffItemRenderer() {
+ override fun renderItemInStaff(
+ staffStack: ItemStack,
+ mode: ModelTransformationMode,
+ matrices: MatrixStack,
+ vertexConsumers: VertexConsumerProvider,
+ light: Int,
+ overlay: Int
+ ) {
+ staffStack.itemStackInStaff?.let { itemInStaff ->
+ itemRenderer.renderItem(
+ itemInStaff,
+ ModelTransformationMode.NONE,
+ false,
+ matrices,
+ vertexConsumers,
+ light,
+ overlay,
+ bakedModelManager.missingModel
+ )
+ }
+ }
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/item/renderer/IStaffItemRenderer.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/item/renderer/StaffItemRenderer.kt
similarity index 60%
rename from StaffMod/src/main/kotlin/opekope2/avm_staff/api/item/renderer/IStaffItemRenderer.kt
rename to StaffMod/src/main/kotlin/opekope2/avm_staff/api/item/renderer/StaffItemRenderer.kt
index af6dc5973..f49620e47 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/item/renderer/IStaffItemRenderer.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/item/renderer/StaffItemRenderer.kt
@@ -21,18 +21,21 @@ package opekope2.avm_staff.api.item.renderer
import net.minecraft.client.render.VertexConsumerProvider
import net.minecraft.client.render.model.json.ModelTransformationMode
import net.minecraft.client.util.math.MatrixStack
+import net.minecraft.item.Item
import net.minecraft.item.ItemStack
+import net.minecraft.registry.Registries
import net.minecraft.util.Identifier
import net.minecraftforge.api.distmarker.Dist
import net.minecraftforge.api.distmarker.OnlyIn
+import opekope2.avm_staff.api.registry.RegistryBase
/**
* A renderer for an item, which can be placed into a staff.
*
- * @see IStaffItemRenderer.register
+ * @see StaffItemRenderer.register
*/
@OnlyIn(Dist.CLIENT)
-fun interface IStaffItemRenderer {
+abstract class StaffItemRenderer {
/**
* Renders an item.
*
@@ -44,7 +47,7 @@ fun interface IStaffItemRenderer {
* @param light Light component for rendering calls
* @param overlay Overlay component for rendering calls
*/
- fun renderItemInStaff(
+ abstract fun renderItemInStaff(
staffStack: ItemStack,
mode: ModelTransformationMode,
matrices: MatrixStack,
@@ -54,38 +57,30 @@ fun interface IStaffItemRenderer {
)
@OnlyIn(Dist.CLIENT)
- companion object {
- private val staffItemRenderers = mutableMapOf()
+ companion object Registry : RegistryBase() {
+ private inline val Item.registryId: Identifier
+ get() = Registries.ITEM.getId(this)
/**
- * Registers a renderer for a given [item ID][staffItem].
+ * Registers an entry to this registry.
*
- * @param staffItem The item ID to register a renderer for
- * @param renderer The item's renderer
- * @return `true`, if the registration was successful, `false`, if a renderer for the item was already registered
+ * @param key The key to associate a value with
+ * @param value The value to register
*/
- @JvmStatic
- fun register(staffItem: Identifier, renderer: IStaffItemRenderer): Boolean {
- if (staffItem in staffItemRenderers) return false
-
- staffItemRenderers[staffItem] = renderer
- return true
- }
+ fun register(key: Item, value: StaffItemRenderer) = register(key.registryId, value)
/**
- * Checks if a renderer for the [given item][staffItem] is registered.
+ * Checks if the given key is present in the registry
*
- * @param staffItem The item ID, which can be inserted into the staff
+ * @param key The key to check
*/
- @JvmStatic
- operator fun contains(staffItem: Identifier): Boolean = staffItem in staffItemRenderers
+ operator fun contains(key: Item) = key.registryId in this
/**
- * Gets the registered renderer for the [given item][staffItem] or `null`, if no renderer was registered.
+ * Gets the value associated with the given key or throws an exception, if the key is not present in this registry.
*
- * @param staffItem The item ID, which can be inserted into the staff
+ * @param key The key to check
*/
- @JvmStatic
- operator fun get(staffItem: Identifier): IStaffItemRenderer? = staffItemRenderers[staffItem]
+ operator fun get(key: Item) = this[key.registryId]
}
}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/item/renderer/StaffRenderer.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/item/renderer/StaffRenderer.kt
index 07b1627b0..d147a1663 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/item/renderer/StaffRenderer.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/item/renderer/StaffRenderer.kt
@@ -18,21 +18,19 @@
package opekope2.avm_staff.api.item.renderer
-import net.minecraft.client.MinecraftClient
import net.minecraft.client.render.VertexConsumerProvider
import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.render.model.json.ModelTransformationMode
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.item.ItemStack
-import net.minecraft.registry.Registries
import net.minecraftforge.api.distmarker.Dist
import net.minecraftforge.api.distmarker.OnlyIn
import opekope2.avm_staff.api.component.StaffRendererPartComponent
-import opekope2.avm_staff.api.staffRendererOverrideComponentType
-import opekope2.avm_staff.api.staffRendererPartComponentType
+import opekope2.avm_staff.content.DataComponentTypes
+import opekope2.avm_staff.util.bakedModelManager
+import opekope2.avm_staff.util.itemRenderer
import opekope2.avm_staff.util.itemStackInStaff
import opekope2.avm_staff.util.push
-import kotlin.jvm.optionals.getOrNull
/**
* Builtin model item renderer for staffs.
@@ -57,19 +55,17 @@ object StaffRenderer {
light: Int,
overlay: Int
) {
- val renderMode = staffStack[staffRendererOverrideComponentType.get()]?.renderMode?.getOrNull() ?: mode
-
- when (renderMode) {
+ when (mode) {
ModelTransformationMode.GUI -> renderInventoryStaff(
- staffStack, renderMode, matrices, vertexConsumers, light, overlay
+ staffStack, mode, matrices, vertexConsumers, light, overlay
)
ModelTransformationMode.FIXED -> renderItemFrameStaff(
- staffStack, renderMode, matrices, vertexConsumers, light, overlay
+ staffStack, mode, matrices, vertexConsumers, light, overlay
)
else -> renderFullStaff(
- staffStack, renderMode, matrices, vertexConsumers, light, overlay
+ staffStack, mode, matrices, vertexConsumers, light, overlay
)
}
}
@@ -168,41 +164,15 @@ object StaffRenderer {
matrices.push {
safeGetModel(staffStack, StaffRendererPartComponent.ITEM).transformation.fixed.apply(false, this)
- val blockStateOverride = staffStack[staffRendererOverrideComponentType.get()]?.blockState?.getOrNull()
- if (blockStateOverride != null) {
- BlockStateStaffItemRenderer.renderBlockState(
- blockStateOverride, matrices, vertexConsumers, light, overlay
- )
- } else {
- staffStack.itemStackInStaff?.let { itemInStaff ->
- renderItem(staffStack, itemInStaff, mode, matrices, light, overlay, vertexConsumers)
- }
+ staffStack.itemStackInStaff?.let { itemInStaff ->
+ val staffItemRenderer =
+ if (itemInStaff.item !in StaffItemRenderer.Registry) MissingModelStaffItemRenderer
+ else StaffItemRenderer.Registry[itemInStaff.item]
+ staffItemRenderer.renderItemInStaff(staffStack, mode, matrices, vertexConsumers, light, overlay)
}
}
}
- private fun renderItem(
- staffStack: ItemStack,
- itemStackInStaff: ItemStack,
- mode: ModelTransformationMode,
- matrices: MatrixStack,
- light: Int,
- overlay: Int,
- vertexConsumers: VertexConsumerProvider
- ) {
- val staffItemRenderer = IStaffItemRenderer[Registries.ITEM.getId(itemStackInStaff.item)]
- if (staffItemRenderer != null) {
- staffItemRenderer.renderItemInStaff(staffStack, mode, matrices, vertexConsumers, light, overlay)
- } else {
- val itemRenderer = MinecraftClient.getInstance().itemRenderer
- val model = MinecraftClient.getInstance().bakedModelManager.missingModel
-
- itemRenderer.renderItem(
- itemStackInStaff, ModelTransformationMode.NONE, false, matrices, vertexConsumers, light, overlay, model
- )
- }
- }
-
private fun renderPart(
staffStack: ItemStack,
matrices: MatrixStack,
@@ -211,23 +181,25 @@ object StaffRenderer {
overlay: Int,
part: StaffRendererPartComponent
) {
- val itemRenderer = MinecraftClient.getInstance().itemRenderer
- val model = safeGetModel(staffStack, part)
-
itemRenderer.renderItem(
- staffStack, ModelTransformationMode.NONE, false, matrices, vertexConsumers, light, overlay, model
+ staffStack,
+ ModelTransformationMode.NONE,
+ false,
+ matrices,
+ vertexConsumers,
+ light,
+ overlay,
+ safeGetModel(staffStack, part)
)
}
private fun safeGetModel(staffStack: ItemStack, part: StaffRendererPartComponent): BakedModel {
- val itemRenderer = MinecraftClient.getInstance().itemRenderer
-
- staffStack[staffRendererPartComponentType.get()] = part
+ staffStack[DataComponentTypes.staffRendererPart] = part
val model = itemRenderer.getModel(staffStack, null, null, 0)
- staffStack.remove(staffRendererPartComponentType.get())
+ staffStack.remove(DataComponentTypes.staffRendererPart)
// Prevent StackOverflowError if an override is missing
return if (!model.isBuiltin) model
- else MinecraftClient.getInstance().bakedModelManager.missingModel
+ else bakedModelManager.missingModel
}
}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/particle/FlamethrowerParticle.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/particle/FlamethrowerParticle.kt
index 35e2ac3c8..f38bee6d1 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/particle/FlamethrowerParticle.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/particle/FlamethrowerParticle.kt
@@ -24,8 +24,7 @@ import net.minecraft.particle.SimpleParticleType
import net.minecraft.util.math.BlockPos
import net.minecraftforge.api.distmarker.Dist
import net.minecraftforge.api.distmarker.OnlyIn
-import opekope2.avm_staff.api.flamethrowerParticleType
-import opekope2.avm_staff.api.soulFlamethrowerParticleType
+import opekope2.avm_staff.content.ParticleTypes
import opekope2.avm_staff.mixin.IParticleMixin
/**
@@ -107,8 +106,8 @@ class FlamethrowerParticle(
* Factory class for [FlamethrowerParticle], intended to register in Minecraft instead of direct consumption.
*
* @param spriteProvider Flame sprite provider
- * @see flamethrowerParticleType
- * @see soulFlamethrowerParticleType
+ * @see ParticleTypes.flame
+ * @see ParticleTypes.soulFireFlame
* @see ParticleManager.addParticle
*/
class Factory(private val spriteProvider: SpriteProvider) : ParticleFactory {
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/registry/RegistryBase.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/registry/RegistryBase.kt
new file mode 100644
index 000000000..008f61037
--- /dev/null
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/registry/RegistryBase.kt
@@ -0,0 +1,69 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.api.registry
+
+/**
+ * AvM Staff mod base registration utility.
+ * This is not to be confused with Minecraft registries.
+ */
+abstract class RegistryBase : Iterable> {
+ private val entries = mutableMapOf()
+
+ /**
+ * Gets all the registered keys in this registry.
+ */
+ val keys: Set
+ get() = entries.keys
+
+ /**
+ * Validates an entry to be registered. Throws an exception, if the entry is invalid.
+ *
+ * Implementors must call the super method to check if the key is not already registered.
+ */
+ open fun validateEntry(key: TKey, value: TValue) {
+ require(key !in entries) { "Key `$key` is already registered" }
+ }
+
+ /**
+ * Registers an entry to this registry.
+ *
+ * @param key The key to associate a value with
+ * @param value The value to register
+ */
+ open fun register(key: TKey, value: TValue) {
+ validateEntry(key, value)
+ entries[key] = value
+ }
+
+ /**
+ * Checks if the given key is present in the registry
+ *
+ * @param key The key to check
+ */
+ operator fun contains(key: TKey) = key in entries
+
+ /**
+ * Gets the value associated with the given key or throws an exception, if the key is not present in this registry.
+ *
+ * @param key The key to check
+ */
+ operator fun get(key: TKey) = entries.getValue(key)
+
+ override fun iterator(): Iterator> = entries.iterator()
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/staff/StaffHandler.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/staff/StaffHandler.kt
index 7ecdf0a5a..5ed9c127c 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/staff/StaffHandler.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/staff/StaffHandler.kt
@@ -19,43 +19,59 @@
package opekope2.avm_staff.api.staff
import net.minecraft.advancement.criterion.Criteria
+import net.minecraft.block.BlockState
import net.minecraft.component.type.AttributeModifiersComponent
import net.minecraft.entity.Entity
import net.minecraft.entity.LivingEntity
import net.minecraft.entity.player.PlayerEntity
+import net.minecraft.item.BlockItem
import net.minecraft.item.Item
import net.minecraft.item.ItemStack
+import net.minecraft.item.Items
+import net.minecraft.registry.Registries
+import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.stat.Stats
-import net.minecraft.util.ActionResult
-import net.minecraft.util.Hand
-import net.minecraft.util.Identifier
-import net.minecraft.util.TypedActionResult
+import net.minecraft.util.*
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.world.World
import net.minecraft.world.event.GameEvent
+import opekope2.avm_staff.api.block.IClearableBeforeInsertedIntoStaff
+import opekope2.avm_staff.api.component.BlockPickupDataComponent
+import opekope2.avm_staff.api.registry.RegistryBase
+import opekope2.avm_staff.content.DataComponentTypes
+import opekope2.avm_staff.content.Enchantments
+import opekope2.avm_staff.util.approximateStaffItemPosition
+import opekope2.avm_staff.util.getEnchantmentLevel
+import opekope2.avm_staff.util.incrementStaffItemUseStat
+import opekope2.avm_staff.util.mutableItemStackInStaff
+import kotlin.math.roundToInt
/**
* Provides functionality for a staff, when an item is inserted into it.
*/
abstract class StaffHandler {
/**
- * The number of ticks the staff can be used for using the current item.
+ * Gets the attribute modifiers (damage, attack speed, etc.) of the staff when held.
*/
- open val maxUseTime: Int
- get() = 0
+ open val attributeModifiers: AttributeModifiersComponent
+ get() = Fallback.ATTRIBUTE_MODIFIERS
/**
- * Gets the attribute modifiers (damage, attack speed, etc.) of the staff when held.
+ * Called on both the client and the server my Minecraft to get the number of ticks the staff can be used for using
+ * the current item.
+ *
+ * @param staffStack The item stack used to perform the action
+ * @param world The world the [user] is in
+ * @param user The player, which uses the staff
*/
- open val attributeModifiers: AttributeModifiersComponent
- get() = Default.ATTRIBUTE_MODIFIERS
+ open fun getMaxUseTime(staffStack: ItemStack, world: World, user: LivingEntity): Int = 0
/**
* Called on both the client and the server by Minecraft when the player uses the staff.
*
- * If the staff can be used for multiple ticks, override [maxUseTime] to return a positive number, and call
- * [PlayerEntity.setCurrentHand] on [user] with [hand] as the argument.
+ * If the staff can be used for multiple ticks, override [getMaxUseTime] to return a positive number, and call
+ * [LivingEntity.setCurrentHand] on [user] with [hand] as the argument.
*
* @return
* On the logical client:
@@ -80,7 +96,7 @@ abstract class StaffHandler {
* @param hand The hand of the [user], in which the [staff][staffStack] is
* @see Item.use
*/
- open fun use(staffStack: ItemStack, world: World, user: PlayerEntity, hand: Hand): TypedActionResult =
+ open fun use(staffStack: ItemStack, world: World, user: LivingEntity, hand: Hand): TypedActionResult =
TypedActionResult.pass(user.getStackInHand(hand))
/**
@@ -90,7 +106,7 @@ abstract class StaffHandler {
* @param world The world [user] is in
* @param user The entity, which uses the staff
* @param remainingUseTicks The number of ticks remaining before an entity finishes using the staff counting down
- * from [maxUseTime] to 0
+ * from [getMaxUseTime] to 0
* @see Item.usageTick
*/
open fun usageTick(staffStack: ItemStack, world: World, user: LivingEntity, remainingUseTicks: Int) {
@@ -98,12 +114,12 @@ abstract class StaffHandler {
/**
* Called on both the client and the server by Minecraft, when an entity stops using the staff before being used for
- * [maxUseTime]. If that time is reached, [finishUsing] will be called.
+ * [getMaxUseTime]. If that time is reached, [finishUsing] will be called.
*
* @param staffStack The item stack used to perform the action
* @param world The world the [user] is in
* @param user The entity, which used the staff
- * @param remainingUseTicks The number of ticks left until reaching [maxUseTime]
+ * @param remainingUseTicks The number of ticks left until reaching [getMaxUseTime]
* @see Item.onStoppedUsing
*/
open fun onStoppedUsing(staffStack: ItemStack, world: World, user: LivingEntity, remainingUseTicks: Int) {
@@ -111,7 +127,7 @@ abstract class StaffHandler {
/**
* Called on both the client and the server by Minecraft, when an entity finishes using the staff
- * (usage ticks reach [maxUseTime]).
+ * (usage ticks reach [getMaxUseTime]).
*
* @param staffStack The item stack used to perform the action
* @param world The world the [user] is in
@@ -229,6 +245,7 @@ abstract class StaffHandler {
* @param target The block the [attacker] attacked
* @param side The side of the [block][target], which was attacked
* @param hand The hand of the [attacker], in which the [staff][staffStack] is
+ * @see opekope2.avm_staff.content.Criteria.destroyBlockWithStaff
*/
open fun attackBlock(
staffStack: ItemStack, world: World, attacker: LivingEntity, target: BlockPos, side: Direction, hand: Hand
@@ -304,48 +321,139 @@ abstract class StaffHandler {
) = oldStaffStack != newStaffStack
/**
- * Handler of a staff with no item inserted into it.
+ * Default implementation of [StaffHandler]. Used for staffs with no [registered][Registry.register] handler.
*/
- object Default : StaffHandler() {
+ object Fallback : StaffHandler() {
@JvmField
val ATTRIBUTE_MODIFIERS = StaffAttributeModifiersComponentBuilder.default()
}
- companion object {
- private val staffItemsHandlers = mutableMapOf()
+ /**
+ * Handler of a staff with no item inserted into it.
+ */
+ object Empty : StaffHandler() {
+ private inline val LivingEntity.targetPos: BlockPos
+ get() = BlockPos.ofFloored(approximateStaffItemPosition)
- /**
- * Registers a [StaffHandler] for the given [item ID][staffItem]. Call this from your common mod initializer.
- *
- * @param staffItem The item ID to register a handler for. This is the item, which can be
- * inserted into the staff
- * @param handler The staff item handler, which processes staff interactions, while the
- * [registered item][staffItem] is inserted into it
- * @return `true`, if the registration was successful, `false`, if the item was already registered
- */
- @JvmStatic
- fun register(staffItem: Identifier, handler: StaffHandler): Boolean {
- if (staffItem in staffItemsHandlers) return false
+ override fun getMaxUseTime(staffStack: ItemStack, world: World, user: LivingEntity): Int {
+ val targetPos = user.targetPos
+ val state = world.getBlockState(targetPos)
+ val quickDraw = staffStack.getEnchantmentLevel(Enchantments.QUICK_DRAW, world.registryManager) + 1
+
+ return if (!canPickUp(world, targetPos, state)) 0
+ else 10 + (state.getHardness(world, targetPos) / quickDraw).roundToInt()
+ }
+
+ override fun use(
+ staffStack: ItemStack,
+ world: World,
+ user: LivingEntity,
+ hand: Hand
+ ): TypedActionResult {
+ val targetPos = user.targetPos
+ val state = world.getBlockState(targetPos)
+ if (!canPickUp(world, targetPos, state)) return TypedActionResult.fail(staffStack)
+
+ staffStack[DataComponentTypes.blockPickupData] = BlockPickupDataComponent(targetPos, state)
+
+ user.setCurrentHand(hand)
+ return TypedActionResult.consume(staffStack)
+ }
+
+ private fun canPickUp(world: World, pos: BlockPos, state: BlockState) = !state.isAir &&
+ state.getHardness(world, pos) != -1f &&
+ state.block.asItem() in Registry
+
+ private fun userChangedTarget(
+ world: World,
+ user: LivingEntity,
+ blockPickupData: BlockPickupDataComponent?
+ ): Boolean {
+ val targetPos = user.targetPos
+ val state = world.getBlockState(targetPos)
+ return blockPickupData == null || blockPickupData.pos != targetPos || blockPickupData.state != state
+ }
+
+ override fun usageTick(staffStack: ItemStack, world: World, user: LivingEntity, remainingUseTicks: Int) {
+ if (!world.isClient && userChangedTarget(world, user, staffStack[DataComponentTypes.blockPickupData])) {
+ user.stopUsingItem()
+ }
+ }
+
+ override fun onStoppedUsing(staffStack: ItemStack, world: World, user: LivingEntity, remainingUseTicks: Int) {
+ staffStack.remove(DataComponentTypes.blockPickupData)
+ }
+
+ override fun finishUsing(staffStack: ItemStack, world: World, user: LivingEntity): ItemStack {
+ val blockPickupData = staffStack[DataComponentTypes.blockPickupData]
+ if (!userChangedTarget(world, user, blockPickupData)) {
+ require(blockPickupData != null)
+ tryPickUp(world, blockPickupData.pos, blockPickupData.state, staffStack)
+ (user as? PlayerEntity)?.resetLastAttackedTicks()
+ }
+
+ onStoppedUsing(staffStack, world, user, 0)
+ (user as? ServerPlayerEntity)?.incrementStaffItemUseStat(Items.AIR)
+ return staffStack
+ }
+
+ private fun tryPickUp(world: World, pos: BlockPos, state: BlockState, staffStack: ItemStack): Boolean {
+ if (!canPickUp(world, pos, state)) return false
+
+ val pickStack = state.block.getPickStack(world, pos, state)
+ world.getBlockEntity(pos)?.apply {
+ val nbt = createComponentlessNbtWithIdentifyingData(world.registryManager)
+ removeFromCopiedStackNbt(nbt)
+ BlockItem.setBlockEntityData(pickStack, type, nbt)
+ pickStack.applyComponentsFrom(createComponentMap())
+ (this as? Clearable)?.clear()
+ (this as? IClearableBeforeInsertedIntoStaff)?.staffMod_clearBeforeRemovedFromWorld()
+ }
+
+ staffStack.mutableItemStackInStaff = pickStack
+ world.removeBlock(pos, false)
- staffItemsHandlers[staffItem] = handler
return true
}
+ override fun allowComponentsUpdateAnimation(
+ oldStaffStack: ItemStack,
+ newStaffStack: ItemStack,
+ player: PlayerEntity,
+ hand: Hand
+ ) = false
+
+ override fun allowReequipAnimation(
+ oldStaffStack: ItemStack,
+ newStaffStack: ItemStack,
+ selectedSlotChanged: Boolean
+ ) = selectedSlotChanged
+ }
+
+ companion object Registry : RegistryBase() {
+ private inline val Item.registryId: Identifier
+ get() = Registries.ITEM.getId(this)
+
+ /**
+ * Registers an entry to this registry.
+ *
+ * @param key The key to associate a value with
+ * @param value The value to register
+ */
+ fun register(key: Item, value: StaffHandler) = register(key.registryId, value)
+
/**
- * Checks, if a staff item handler for the [given item][staffItem] is registered.
+ * Checks if the given key is present in the registry
*
- * @param staffItem The item ID, which can be inserted into the staff
+ * @param key The key to check
*/
- @JvmStatic
- operator fun contains(staffItem: Identifier): Boolean = staffItem in staffItemsHandlers
+ operator fun contains(key: Item) = key.registryId in this
/**
- * Gets the registered staff item handler for the [given item][staffItem] or `null`, if no staff item handler was
- * registered.
+ * Gets the value associated with the given key or throws an exception, if the key is not present in this registry.
*
- * @param staffItem The item ID, which can be inserted into the staff
+ * @param key The key to check
*/
- @JvmStatic
- operator fun get(staffItem: Identifier): StaffHandler? = staffItemsHandlers[staffItem]
+ operator fun get(key: Item) = this[key.registryId]
}
}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/staff/StaffInfusionSmithingRecipeTextures.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/staff/StaffInfusionSmithingRecipeTextures.kt
index e0233501e..ff7d8bb43 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/api/staff/StaffInfusionSmithingRecipeTextures.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/api/staff/StaffInfusionSmithingRecipeTextures.kt
@@ -19,35 +19,38 @@
package opekope2.avm_staff.api.staff
import net.minecraft.util.Identifier
-import opekope2.avm_staff.api.staffInfusionSmithingTemplateItem
+import opekope2.avm_staff.content.Items
/**
* Object holding textures to be displayed in a smithing table, when using a
- * [staff infusion smithing template][staffInfusionSmithingTemplateItem].
+ * [staff infusion smithing template][Items.STAFF_INFUSION_SMITHING_TEMPLATE].
*/
object StaffInfusionSmithingRecipeTextures {
+ private val registeredBaseSlotTextures = mutableListOf()
+ private val registeredAdditionsSlotTextures = mutableListOf()
+
/**
- * @suppress
+ * Gets a copy of the registered background textures of the 2nd slot of the smithing table.
*/
- @JvmSynthetic
- internal val baseSlotTextures = mutableListOf()
+ val baseSlotTextures: List
+ get() = registeredBaseSlotTextures.toList()
/**
- * @suppress
+ * Gets a copy of the registered background textures of the 3rd slot of the smithing table.
*/
- @JvmSynthetic
- internal val additionsSlotTextures = mutableListOf()
+ val additionsSlotTextures: List
+ get() = registeredAdditionsSlotTextures.toList()
/**
* Registers a pair of staff texture and an ingredient texture to be displayed in a smithing table, when using a
- * [staff infusion smithing template][staffInfusionSmithingTemplateItem]. This method should be called for every
+ * [staff infusion smithing template][Items.STAFF_INFUSION_SMITHING_TEMPLATE]. This method should be called for every
* `minecraft:smithing_transform` recipe in the mod's data pack, which infuses an ingredient into a faint staff.
*
- * @param baseSlotTexture The background texture of the 2nd slot of the smithing table.
- * @param additionsSlotTexture The background texture of the 3rd slot of the smithing table.
+ * @param baseSlotTexture The background texture of the 2nd slot of the smithing table
+ * @param additionsSlotTexture The background texture of the 3rd slot of the smithing table
*/
fun register(baseSlotTexture: Identifier, additionsSlotTexture: Identifier) {
- baseSlotTextures += baseSlotTexture
- additionsSlotTextures += additionsSlotTexture
+ registeredBaseSlotTextures += baseSlotTexture
+ registeredAdditionsSlotTextures += additionsSlotTexture
}
}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/content/Blocks.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/content/Blocks.kt
new file mode 100644
index 000000000..a4721dadf
--- /dev/null
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/content/Blocks.kt
@@ -0,0 +1,72 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.content
+
+import net.minecraft.block.AbstractBlock
+import net.minecraft.block.Block
+import net.minecraft.block.enums.NoteBlockInstrument
+import net.minecraft.block.piston.PistonBehavior
+import net.minecraft.sound.BlockSoundGroup
+import net.minecraftforge.registries.ForgeRegistries
+import net.minecraftforge.registries.RegistryObject
+import opekope2.avm_staff.api.block.CrownBlock
+import opekope2.avm_staff.api.block.WallCrownBlock
+import opekope2.avm_staff.util.MOD_ID
+import opekope2.avm_staff.util.RegistryUtil
+
+/**
+ * Blocks added by AVM Staffs mod.
+ */
+object Blocks : RegistryUtil(MOD_ID, ForgeRegistries.BLOCKS) {
+ private fun settings() = AbstractBlock.Settings.create()
+ private fun settings(block: RegistryObject) = AbstractBlock.Settings.copy(block.get())
+
+ /**
+ * Block registered as `avm_staff:crown_of_king_orange`.
+ */
+ @JvmField
+ val CROWN_OF_KING_ORANGE = register("crown_of_king_orange") {
+ CrownBlock(
+ settings().instrument(NoteBlockInstrument.BELL).strength(1.0f).pistonBehavior(PistonBehavior.DESTROY)
+ .sounds(BlockSoundGroup.COPPER_GRATE).nonOpaque()
+ )
+ }
+
+ /**
+ * @see CROWN_OF_KING_ORANGE
+ */
+ val crownOfKingOrange: CrownBlock
+ @JvmName("crownOfKingOrange")
+ get() = CROWN_OF_KING_ORANGE.get()
+
+ /**
+ * Block registered as `avm_staff:wall_crown_of_king_orange`.
+ */
+ @JvmField
+ val WALL_CROWN_OF_KING_ORANGE = register("wall_crown_of_king_orange") {
+ WallCrownBlock(settings(CROWN_OF_KING_ORANGE))
+ }
+
+ /**
+ * @see WALL_CROWN_OF_KING_ORANGE
+ */
+ val wallCrownOfKingOrange: WallCrownBlock
+ @JvmName("wallCrownOfKingOrange")
+ get() = WALL_CROWN_OF_KING_ORANGE.get()
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/content/Criteria.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/content/Criteria.kt
new file mode 100644
index 000000000..58c15ca50
--- /dev/null
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/content/Criteria.kt
@@ -0,0 +1,61 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.content
+
+import net.minecraft.advancement.criterion.Criterion
+import net.minecraft.registry.RegistryKeys
+import opekope2.avm_staff.api.advancement.criterion.BreakBlockWithStaffCriterion
+import opekope2.avm_staff.api.advancement.criterion.TakeDamageWhileUsingItemCriterion
+import opekope2.avm_staff.util.MOD_ID
+import opekope2.avm_staff.util.RegistryUtil
+
+/**
+ * Criteria added by AVM Staffs mod.
+ */
+object Criteria : RegistryUtil>(MOD_ID, RegistryKeys.CRITERION) {
+ /**
+ * Criterion registered as `avm_staff:destroy_block_with_staff`. Triggers before a block is destroyed by a staff.
+ */
+ @JvmField
+ val DESTROY_BLOCK_WITH_STAFF = register("destroy_block_with_staff") {
+ BreakBlockWithStaffCriterion()
+ }
+
+ /**
+ * @see DESTROY_BLOCK_WITH_STAFF
+ */
+ val destroyBlockWithStaff: BreakBlockWithStaffCriterion
+ @JvmName("destroyBlockWithStaff")
+ get() = DESTROY_BLOCK_WITH_STAFF.get()
+
+ /**
+ * Criterion registered as `avm_staff:get_hurt_while_using_item`.
+ */
+ @JvmField
+ val TAKE_DAMAGE_WHILE_USING_ITEM = register("get_hurt_while_using_item") {
+ TakeDamageWhileUsingItemCriterion()
+ }
+
+ /**
+ * @see TAKE_DAMAGE_WHILE_USING_ITEM
+ */
+ val takeDamageWhileUsingItem: TakeDamageWhileUsingItemCriterion
+ @JvmName("takeDamageWhileUsingItem")
+ get() = TAKE_DAMAGE_WHILE_USING_ITEM.get()
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/content/DamageTypes.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/content/DamageTypes.kt
new file mode 100644
index 000000000..3ded0f8d2
--- /dev/null
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/content/DamageTypes.kt
@@ -0,0 +1,41 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.content
+
+import net.minecraft.entity.damage.DamageType
+import net.minecraft.registry.RegistryKeys
+import opekope2.avm_staff.util.MOD_ID
+import opekope2.avm_staff.util.RegistryKeyUtil
+
+/**
+ * Damage types added by AVM Staffs mod.
+ */
+object DamageTypes : RegistryKeyUtil(MOD_ID, RegistryKeys.DAMAGE_TYPE) {
+ /**
+ * `avm_staff:pranked` damage type.
+ */
+ @JvmField
+ val PRANKED = registryKey("pranked")
+
+ /**
+ * `avm_staff:pranked_by_player` damage type.
+ */
+ @JvmField
+ val PRANKED_BY_PLAYER = registryKey("pranked_by_player")
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/content/DataComponentTypes.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/content/DataComponentTypes.kt
new file mode 100644
index 000000000..0348adbf1
--- /dev/null
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/content/DataComponentTypes.kt
@@ -0,0 +1,137 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.content
+
+import net.minecraft.component.ComponentType
+import net.minecraft.network.codec.PacketCodec
+import net.minecraft.registry.RegistryKeys
+import opekope2.avm_staff.api.component.*
+import opekope2.avm_staff.internal.MinecraftUnit
+import opekope2.avm_staff.internal.minecraftUnit
+import opekope2.avm_staff.util.MOD_ID
+import opekope2.avm_staff.util.RegistryUtil
+
+/**
+ * Component types added by AVM Staffs mod.
+ */
+object DataComponentTypes : RegistryUtil>(MOD_ID, RegistryKeys.DATA_COMPONENT_TYPE) {
+ /**
+ * Data component registered as `avm_staff:block_pickup_data`.
+ */
+ @JvmField
+ val BLOCK_PICKUP_DATA = register("block_pickup_data") {
+ ComponentType.builder()
+ .packetCodec(BlockPickupDataComponent.NON_SYNCING_PACKET_CODEC)
+ .build()
+ }
+
+ /**
+ * @see BLOCK_PICKUP_DATA
+ */
+ val blockPickupData: ComponentType
+ @JvmName("blockPickupData")
+ get() = BLOCK_PICKUP_DATA.get()
+
+ /**
+ * Data component registered as `avm_staff:furnace_data`. If this is present, the furnace is lit.
+ */
+ @JvmField
+ val FURNACE_DATA = register("furnace_data") {
+ ComponentType.builder()
+ .packetCodec(StaffFurnaceDataComponent.NON_SYNCING_PACKET_CODEC)
+ .build()
+ }
+
+ /**
+ * @see FURNACE_DATA
+ */
+ val furnaceData: ComponentType
+ @JvmName("furnaceData")
+ get() = FURNACE_DATA.get()
+
+ /**
+ * Data component registered as `avm_staff:rocket_mode`. Stores if a campfire staff should propel its user.
+ */
+ @JvmField
+ val ROCKET_MODE = register("rocket_mode") {
+ ComponentType.builder()
+ .codec(MinecraftUnit.CODEC)
+ .packetCodec(PacketCodec.unit(minecraftUnit))
+ .build()
+ }
+
+ /**
+ * @see ROCKET_MODE
+ */
+ val rocketMode: ComponentType
+ @JvmName("rocketMode")
+ get() = ROCKET_MODE.get()
+
+ /**
+ * Data component registered as `avm_staff:staff_item`. Stores the item inserted into the staff.
+ */
+ @JvmField
+ val STAFF_ITEM = register("staff_item") {
+ ComponentType.builder()
+ .codec(StaffItemComponent.VALIDATED_CODEC)
+ .packetCodec(StaffItemComponent.PACKET_CODEC)
+ .build()
+ }
+
+ /**
+ * @see STAFF_ITEM
+ */
+ val staffItem: ComponentType
+ @JvmName("staffItem")
+ get() = STAFF_ITEM.get()
+
+ /**
+ * Data component registered as `avm_staff:staff_renderer_part`. Only used for rendering.
+ */
+ @JvmField
+ val STAFF_RENDERER_PART = register("staff_renderer_part") {
+ ComponentType.builder()
+ .packetCodec(StaffRendererPartComponent.PACKET_CODEC)
+ .build()
+ }
+
+ /**
+ * @see STAFF_RENDERER_PART
+ */
+ val staffRendererPart: ComponentType
+ @JvmName("staffRendererPart")
+ get() = STAFF_RENDERER_PART.get()
+
+ /**
+ * Data component registered as `avm_staff:tnt_data`.
+ */
+ @JvmField
+ val TNT_DATA = register("tnt_data") {
+ ComponentType.builder()
+ .packetCodec(StaffTntDataComponent.NON_SYNCING_PACKET_CODEC)
+ .build()
+ }
+
+ /**
+ * @see TNT_DATA
+ */
+ val tntData: ComponentType
+ @JvmName("tntData")
+ get() = TNT_DATA.get()
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/content/Enchantments.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/content/Enchantments.kt
new file mode 100644
index 000000000..3934be026
--- /dev/null
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/content/Enchantments.kt
@@ -0,0 +1,56 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.content
+
+import net.minecraft.enchantment.Enchantment
+import net.minecraft.registry.RegistryKeys
+import opekope2.avm_staff.util.MOD_ID
+import opekope2.avm_staff.util.RegistryKeyUtil
+import opekope2.avm_staff.util.TagKeyUtil
+
+/**
+ * Enchantments added by AVM Staffs mod.
+ */
+object Enchantments : RegistryKeyUtil(MOD_ID, RegistryKeys.ENCHANTMENT) {
+ @JvmField
+ val DISTANT_DETONATION = registryKey("distant_detonation")
+
+ @JvmField
+ val POWER_CHARGE = registryKey("power_charge")
+
+ @JvmField
+ val QUICK_DRAW = registryKey("quick_draw")
+
+ @JvmField
+ val RAPID_FIRE = registryKey("rapid_fire")
+
+ @JvmField
+ val SPECTRE = registryKey("spectre")
+
+ /**
+ * Enchantment tags added by AVM Staffs mod.
+ */
+ object Tags : TagKeyUtil(MOD_ID, RegistryKeys.ENCHANTMENT) {
+ /**
+ * Enchantment registered as `avm_staff:redirects_impact_tnt`.
+ */
+ @JvmField
+ val REDIRECTS_IMPACT_TNT = tagKey("redirects_impact_tnt")
+ }
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/content/EntityTypes.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/content/EntityTypes.kt
new file mode 100644
index 000000000..2b5a1c367
--- /dev/null
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/content/EntityTypes.kt
@@ -0,0 +1,103 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.content
+
+import net.minecraft.entity.EntityType
+import net.minecraft.entity.SpawnGroup
+import net.minecraft.util.Identifier
+import net.minecraftforge.registries.ForgeRegistries
+import opekope2.avm_staff.api.entity.CakeEntity
+import opekope2.avm_staff.api.entity.CampfireFlameEntity
+import opekope2.avm_staff.api.entity.ImpactTntEntity
+import opekope2.avm_staff.mixin.ICakeBlockAccessor
+import opekope2.avm_staff.util.MOD_ID
+import opekope2.avm_staff.util.RegistryUtil
+import kotlin.math.max
+
+/**
+ * Entity types added by AVM Staffs mod.
+ */
+object EntityTypes : RegistryUtil>(MOD_ID, ForgeRegistries.ENTITY_TYPES) {
+ /**
+ * Entity registered as `avm_staff:cake`
+ */
+ @JvmField
+ val CAKE = register("cake") {
+ val cakeBox = ICakeBlockAccessor.bitesToShape()[0].boundingBox
+ val cakeSize = max(cakeBox.lengthX, max(cakeBox.lengthY, cakeBox.lengthZ))
+
+ EntityType.Builder.create(::CakeEntity, SpawnGroup.MISC)
+ .dimensions(cakeSize.toFloat(), cakeSize.toFloat())
+ .maxTrackingRange(EntityType.FALLING_BLOCK.maxTrackDistance)
+ .trackingTickInterval(EntityType.FALLING_BLOCK.trackTickInterval)
+ .build(Identifier.of(MOD_ID, "cake").toString())
+ }
+
+ /**
+ * @see CAKE
+ */
+ val cake: EntityType
+ @JvmName("cake")
+ get() = CAKE.get()
+
+ /**
+ * Technical entity registered as `avm_staff:campfire_flame`
+ */
+ @JvmField
+ val CAMPFIRE_FLAME = register("campfire_flame") {
+ EntityType.Builder.create(::CampfireFlameEntity, SpawnGroup.MISC)
+ .dimensions(0f, 0f)
+ .maxTrackingRange(EntityType.AREA_EFFECT_CLOUD.maxTrackDistance)
+ // Don't send existing entities (the ones entering tracking distance) to the client
+ // The tracking distance is high enough compared to the max age of the flame
+ .trackingTickInterval(Int.MAX_VALUE)
+ .disableSaving()
+ .disableSummon()
+ .makeFireImmune()
+ .build(Identifier.of(MOD_ID, "campfire_flame").toString())
+ }
+
+ /**
+ * @see CAMPFIRE_FLAME
+ */
+ val campfireFlame: EntityType
+ @JvmName("campfireFlame")
+ get() = CAMPFIRE_FLAME.get()
+
+ /**
+ * Entity registered as `avm_staff:impact_tnt`.
+ */
+ @JvmField
+ val IMPACT_TNT = register("impact_tnt") {
+ EntityType.Builder.create(::ImpactTntEntity, SpawnGroup.MISC)
+ .makeFireImmune()
+ .dimensions(EntityType.TNT.dimensions.width, EntityType.TNT.dimensions.height)
+ .eyeHeight(EntityType.TNT.dimensions.eyeHeight)
+ .maxTrackingRange(EntityType.TNT.maxTrackDistance)
+ .trackingTickInterval(EntityType.TNT.trackTickInterval)
+ .build(Identifier.of(MOD_ID, "impact_tnt").toString())
+ }
+
+ /**
+ * @see IMPACT_TNT
+ */
+ val impactTnt: EntityType
+ @JvmName("impactTnt")
+ get() = IMPACT_TNT.get()
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/content/GameRules.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/content/GameRules.kt
new file mode 100644
index 000000000..ee9af9b34
--- /dev/null
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/content/GameRules.kt
@@ -0,0 +1,34 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.content
+
+import net.minecraft.world.GameRules
+
+/**
+ * Game rules added by AVM Staffs mod.
+ */
+object GameRules {
+ /**
+ * Throwable cakes game rule. When set to true, cakes can be thrown by right clicking, and dispensers will shoot cakes
+ * instead of dropping them as item.
+ */
+ @JvmField
+ val THROWABLE_CAKES: GameRules.Key =
+ GameRules.register("throwableCakes", GameRules.Category.MISC, GameRules.BooleanRule.create(false))
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/content/ItemGroups.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/content/ItemGroups.kt
new file mode 100644
index 000000000..4aba5ee7a
--- /dev/null
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/content/ItemGroups.kt
@@ -0,0 +1,63 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.content
+
+import net.minecraft.item.ItemGroup
+import net.minecraft.registry.RegistryKeys
+import net.minecraft.text.Text
+import net.minecraftforge.registries.RegistryObject
+import opekope2.avm_staff.util.MOD_ID
+import opekope2.avm_staff.util.RegistryUtil
+import opekope2.avm_staff.util.mutableItemStackInStaff
+
+/**
+ * Item groups added by AVM Staffs mod.
+ */
+object ItemGroups : RegistryUtil(MOD_ID, RegistryKeys.ITEM_GROUP) {
+ /**
+ * Item group containing items added by Staff Mod.
+ */
+ @JvmField
+ val AVM_STAFF_MOD_ITEMS: RegistryObject = ItemGroups.register("${MOD_ID}_items") {
+ ItemGroup.builder()
+ .displayName(Text.translatable("itemGroup.${MOD_ID}_items"))
+ .icon {
+ Items.royalStaff.defaultStack.apply {
+ mutableItemStackInStaff = net.minecraft.item.Items.COMMAND_BLOCK.defaultStack
+ }
+ }
+ .entries { _, entries ->
+ entries.add(Items.faintStaffRod)
+ entries.add(Items.faintRoyalStaffHead)
+ entries.add(Items.faintRoyalStaff)
+ entries.add(Items.royalStaff)
+ entries.add(Items.royalStaffIngredient)
+ entries.add(Items.crownOfKingOrange)
+ entries.add(Items.staffInfusionSmithingTemplate)
+ }
+ .build()
+ }
+
+ /**
+ * @see AVM_STAFF_MOD_ITEMS
+ */
+ val avmStaffModItems: ItemGroup
+ @JvmName("avmStaffModItems")
+ get() = AVM_STAFF_MOD_ITEMS.get()
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/content/Items.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/content/Items.kt
new file mode 100644
index 000000000..180bec42a
--- /dev/null
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/content/Items.kt
@@ -0,0 +1,180 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.content
+
+import net.minecraft.component.DataComponentTypes
+import net.minecraft.component.type.ToolComponent
+import net.minecraft.item.Item
+import net.minecraft.item.SmithingTemplateItem
+import net.minecraft.registry.RegistryKeys
+import net.minecraft.text.Text
+import net.minecraft.util.Rarity
+import net.minecraftforge.registries.ForgeRegistries
+import opekope2.avm_staff.api.IStaffModPlatform
+import opekope2.avm_staff.api.item.CrownItem
+import opekope2.avm_staff.api.item.StaffItem
+import opekope2.avm_staff.api.staff.StaffHandler
+import opekope2.avm_staff.api.staff.StaffInfusionSmithingRecipeTextures
+import opekope2.avm_staff.mixin.ISmithingTemplateItemAccessor
+import opekope2.avm_staff.util.MOD_ID
+import opekope2.avm_staff.util.RegistryUtil
+import opekope2.avm_staff.util.TagKeyUtil
+
+/**
+ * Items added by AVM Staffs mod.
+ */
+object Items : RegistryUtil
- (MOD_ID, ForgeRegistries.ITEMS) {
+ private fun settings() = Item.Settings()
+
+ /**
+ * Item registered as `avm_staff:crown_of_king_orange`.
+ */
+ @JvmField
+ val CROWN_OF_KING_ORANGE = register("crown_of_king_orange") {
+ IStaffModPlatform.crownItem(
+ Blocks.CROWN_OF_KING_ORANGE.get(),
+ Blocks.WALL_CROWN_OF_KING_ORANGE.get(),
+ settings().maxCount(1).rarity(Rarity.UNCOMMON)
+ )
+ }
+
+ /**
+ * @see CROWN_OF_KING_ORANGE
+ */
+ val crownOfKingOrange: CrownItem
+ @JvmName("crownOfKingOrange")
+ get() = CROWN_OF_KING_ORANGE.get()
+
+ /**
+ * Item registered as `avm_staff:faint_royal_staff`.
+ */
+ @JvmField
+ val FAINT_ROYAL_STAFF = register("faint_royal_staff") {
+ IStaffModPlatform.itemWithStaffRenderer(
+ settings().maxCount(1).rarity(Rarity.RARE)
+ )
+ }
+
+ /**
+ * @see FAINT_ROYAL_STAFF
+ */
+ val faintRoyalStaff: Item
+ @JvmName("faintRoyalStaff")
+ get() = FAINT_ROYAL_STAFF.get()
+
+ /**
+ * Item registered as `avm_staff:faint_royal_staff_head`.
+ */
+ @JvmField
+ val FAINT_ROYAL_STAFF_HEAD = register("faint_royal_staff_head") {
+ Item(settings().maxCount(16).rarity(Rarity.RARE))
+ }
+
+ /**
+ * @see FAINT_ROYAL_STAFF_HEAD
+ */
+ val faintRoyalStaffHead: Item
+ @JvmName("faintRoyalStaffHead")
+ get() = FAINT_ROYAL_STAFF_HEAD.get()
+
+ /**
+ * Item registered as `avm_staff:faint_staff_rod`.
+ */
+ @JvmField
+ val FAINT_STAFF_ROD = register("faint_staff_rod") {
+ Item(settings())
+ }
+
+ /**
+ * @see FAINT_STAFF_ROD
+ */
+ val faintStaffRod: Item
+ @JvmName("faintStaffRod")
+ get() = FAINT_STAFF_ROD.get()
+
+ /**
+ * Item registered as `avm_staff:royal_staff`.
+ */
+ @JvmField
+ val ROYAL_STAFF = register("royal_staff") {
+ IStaffModPlatform.staffItem(
+ settings().maxCount(1).rarity(Rarity.EPIC).attributeModifiers(StaffHandler.Fallback.ATTRIBUTE_MODIFIERS)
+ .maxDamage(5179).component(DataComponentTypes.TOOL, ToolComponent(listOf(), 1f, 1)),
+ ROYAL_STAFF_INGREDIENT
+ )
+ }
+
+ /**
+ * @see ROYAL_STAFF
+ */
+ val royalStaff: StaffItem
+ @JvmName("royalStaff")
+ get() = ROYAL_STAFF.get()
+
+ /**
+ * Item registered as `avm_staff:royal_staff_ingredient`.
+ */
+ @JvmField
+ val ROYAL_STAFF_INGREDIENT = register("royal_staff_ingredient") {
+ Item(settings())
+ }
+
+ /**
+ * @see ROYAL_STAFF_INGREDIENT
+ */
+ val royalStaffIngredient: Item
+ @JvmName("royalStaffIngredient")
+ get() = ROYAL_STAFF_INGREDIENT.get()
+
+ /**
+ * Item registered as `avm_staff:staff_infusion_smithing_template`.
+ */
+ @JvmField
+ val STAFF_INFUSION_SMITHING_TEMPLATE = register("staff_infusion_smithing_template") {
+ SmithingTemplateItem(
+ Text.translatable("item.$MOD_ID.staff_infusion_smithing_template.applies_to")
+ .formatted(ISmithingTemplateItemAccessor.descriptionFormatting()),
+ ISmithingTemplateItemAccessor.armorTrimIngredientsText(),
+ Text.translatable("item.$MOD_ID.staff_infusion_smithing_template.title")
+ .formatted(ISmithingTemplateItemAccessor.titleFormatting()),
+ Text.translatable("item.$MOD_ID.staff_infusion_smithing_template.base_slot_description"),
+ ISmithingTemplateItemAccessor.armorTrimAdditionsSlotDescriptionText(),
+ StaffInfusionSmithingRecipeTextures.baseSlotTextures,
+ StaffInfusionSmithingRecipeTextures.additionsSlotTextures
+ )
+ }
+
+ /**
+ * @see STAFF_INFUSION_SMITHING_TEMPLATE
+ */
+ val staffInfusionSmithingTemplate: SmithingTemplateItem
+ @JvmName("staffInfusionSmithingTemplate")
+ get() = STAFF_INFUSION_SMITHING_TEMPLATE.get()
+
+ /**
+ * Item tags added by AVM Staffs mod.
+ */
+ object Tags : TagKeyUtil
- (MOD_ID, RegistryKeys.ITEM) {
+ /**
+ * Item tag registered as `avm_staff:staffs`.
+ */
+ @JvmField
+ val STAFFS = tagKey("staffs")
+ }
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/content/ParticleTypes.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/content/ParticleTypes.kt
new file mode 100644
index 000000000..1e5e193c6
--- /dev/null
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/content/ParticleTypes.kt
@@ -0,0 +1,62 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.content
+
+import net.minecraft.client.particle.ParticleManager
+import net.minecraft.particle.ParticleType
+import net.minecraft.particle.SimpleParticleType
+import net.minecraftforge.registries.ForgeRegistries
+import opekope2.avm_staff.api.IStaffModPlatform
+import opekope2.avm_staff.util.MOD_ID
+import opekope2.avm_staff.util.RegistryUtil
+
+/**
+ * Particle types added by AVM Staffs mod.
+ */
+object ParticleTypes : RegistryUtil>(MOD_ID, ForgeRegistries.PARTICLE_TYPES) {
+ /**
+ * Particle registered as `avm_staff:flame`.
+ *
+ * @see ParticleManager.addParticle
+ */
+ @JvmField
+ val FLAME = register("flame") { IStaffModPlatform.simpleParticleType(false) }
+
+ /**
+ * @see FLAME
+ */
+ val flame: SimpleParticleType
+ @JvmName("flame")
+ get() = FLAME.get()
+
+ /**
+ * Particle registered as `avm_staff:soul_fire_flame`.
+ *
+ * @see ParticleManager.addParticle
+ */
+ @JvmField
+ val SOUL_FIRE_FLAME = register("soul_fire_flame") { IStaffModPlatform.simpleParticleType(false) }
+
+ /**
+ * @see SOUL_FIRE_FLAME
+ */
+ val soulFireFlame: SimpleParticleType
+ @JvmName("soulFireFlame")
+ get() = SOUL_FIRE_FLAME.get()
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/content/SoundEvents.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/content/SoundEvents.kt
new file mode 100644
index 000000000..17a6e0167
--- /dev/null
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/content/SoundEvents.kt
@@ -0,0 +1,55 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.content
+
+import net.minecraft.sound.SoundEvent
+import net.minecraftforge.registries.ForgeRegistries
+import opekope2.avm_staff.util.MOD_ID
+import opekope2.avm_staff.util.RegistryUtil
+
+/**
+ * Sound events added by AVM Staffs mod.
+ */
+object SoundEvents : RegistryUtil(MOD_ID, ForgeRegistries.SOUND_EVENTS) {
+ /**
+ * Sound event registered as `avm_staff:entity.cake.splash`.
+ */
+ @JvmField
+ val CAKE_SPLASH = register("entity.cake.splash") { SoundEvent.of(it.value) }
+
+ /**
+ * @see CAKE_SPLASH
+ */
+ val cakeSplash: SoundEvent
+ @JvmName("cakeSplash")
+ get() = CAKE_SPLASH.get()
+
+ /**
+ * Sound event registered as `avm_staff:entity.cake.throw`.
+ */
+ @JvmField
+ val CAKE_THROW = register("entity.cake.throw") { SoundEvent.of(it.value) }
+
+ /**
+ * @see CAKE_THROW
+ */
+ val cakeThrow: SoundEvent
+ @JvmName("cakeThrow")
+ get() = CAKE_THROW.get()
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/content/StatTypes.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/content/StatTypes.kt
new file mode 100644
index 000000000..7c73dad37
--- /dev/null
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/content/StatTypes.kt
@@ -0,0 +1,47 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.content
+
+import net.minecraft.item.Item
+import net.minecraft.registry.Registries
+import net.minecraft.registry.RegistryKeys
+import net.minecraft.stat.StatType
+import net.minecraft.text.Text
+import opekope2.avm_staff.util.MOD_ID
+import opekope2.avm_staff.util.RegistryUtil
+
+/**
+ * Stats added by AVM Staffs mod.
+ */
+object StatTypes : RegistryUtil>(MOD_ID, RegistryKeys.STAT_TYPE) {
+ /**
+ * Stat type registered as `avm_staff:used_item_in_staff`
+ */
+ @JvmField
+ val USED_ITEM_IN_STAFF = register("used_item_in_staff") { key ->
+ StatType(Registries.ITEM, Text.translatable("stat_type.${key.value.namespace}.${key.value.path}"))
+ }
+
+ /**
+ * @see USED_ITEM_IN_STAFF
+ */
+ val usedItemInStaff: StatType
-
+ @JvmName("usedItemInStaff")
+ get() = USED_ITEM_IN_STAFF.get()
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/Aliases.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/Aliases.kt
index c6afad535..9af6214ee 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/Aliases.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/Aliases.kt
@@ -19,3 +19,6 @@
package opekope2.avm_staff.internal
internal typealias MinecraftUnit = net.minecraft.util.Unit
+
+internal val minecraftUnit: MinecraftUnit
+ get() = MinecraftUnit.INSTANCE
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/Initializer.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/Initializer.kt
deleted file mode 100644
index 1a07678d2..000000000
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/Initializer.kt
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
- * AvM Staff Mod
- * Copyright (c) 2024 opekope2
- *
- * This mod is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This mod is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this mod. If not, see .
- */
-
-package opekope2.avm_staff.internal
-
-import net.minecraft.entity.ItemEntity
-import net.minecraft.entity.LivingEntity
-import net.minecraft.entity.mob.AbstractPiglinEntity
-import net.minecraft.entity.player.PlayerEntity
-import net.minecraft.entity.player.PlayerInventory
-import net.minecraft.item.Items
-import net.minecraft.loot.LootPool
-import net.minecraft.loot.entry.LootTableEntry
-import net.minecraft.registry.RegistryKey
-import net.minecraft.registry.RegistryKeys
-import net.minecraft.util.ActionResult
-import net.minecraft.util.Hand
-import net.minecraft.util.Identifier
-import net.minecraft.util.math.Box
-import net.minecraftforge.api.distmarker.Dist
-import net.minecraftforge.api.distmarker.OnlyIn
-import net.minecraftforge.event.LootTableLoadEvent
-import net.minecraftforge.event.entity.item.ItemTossEvent
-import net.minecraftforge.event.entity.living.LivingDeathEvent
-import net.minecraftforge.event.entity.player.AttackEntityEvent
-import net.minecraftforge.event.entity.player.PlayerInteractEvent.LeftClickBlock
-import net.minecraftforge.event.entity.player.PlayerInteractEvent.RightClickItem
-import net.minecraftforge.eventbus.api.Event
-import opekope2.avm_staff.api.cakeEntityType
-import opekope2.avm_staff.api.crownOfKingOrangeItem
-import opekope2.avm_staff.api.entity.CakeEntity
-import opekope2.avm_staff.api.item.StaffItem
-import opekope2.avm_staff.api.staff.StaffInfusionSmithingRecipeTextures
-import opekope2.avm_staff.api.throwableCakesGameRule
-import opekope2.avm_staff.internal.networking.c2s.play.AttackC2SPacket
-import opekope2.avm_staff.internal.networking.c2s.play.InsertItemIntoStaffC2SPacket
-import opekope2.avm_staff.internal.networking.c2s.play.RemoveItemFromStaffC2SPacket
-import opekope2.avm_staff.internal.networking.s2c.play.MassDestructionS2CPacket
-import opekope2.avm_staff.mixin.IPiglinBrainAccessor
-import opekope2.avm_staff.mixin.ISmithingTemplateItemAccessor
-import opekope2.avm_staff.util.*
-import thedarkcolour.kotlinforforge.forge.FORGE_BUS
-
-fun registerContent() {
- opekope2.avm_staff.api.registerContent()
-}
-
-fun initializeNetworking() {
- AttackC2SPacket
- InsertItemIntoStaffC2SPacket
- RemoveItemFromStaffC2SPacket
-
- MassDestructionS2CPacket
-}
-
-private val MODIFIABLE_LOOT_TABLES = setOf(
- Identifier.ofVanilla("chests/bastion_treasure"),
- Identifier.ofVanilla("chests/trial_chambers/reward_unique")
-)
-
-fun subscribeToEvents() {
- FORGE_BUS.addListener(::stopUsingStaffOnPlayerDeath)
- FORGE_BUS.addListener(::dispatchStaffBlockAttack)
- FORGE_BUS.addListener(::tryThrowCake)
- FORGE_BUS.addListener(::modifyLootTables)
- FORGE_BUS.addListener(::tryAngerPiglins)
- FORGE_BUS.addListener(::stopUsingStaffWhenDropped)
-}
-
-private fun stopUsingStaffOnPlayerDeath(entity: LivingDeathEvent) {
- val player = entity.entity
- if (player !is PlayerEntity) return
-
- iterator {
- yieldAll(0 until PlayerInventory.MAIN_SIZE)
- yield(PlayerInventory.OFF_HAND_SLOT)
- }.forEach { slot ->
- if (player.inventory.getStack(slot).isStaff) {
- player.stopUsingItem()
- }
- }
-}
-
-private fun dispatchStaffBlockAttack(event: LeftClickBlock) {
- val player = event.entity
- val staffStack = player.getStackInHand(event.hand)
- val staffItem = staffStack.item as? StaffItem ?: return
-
- val result = staffItem.attackBlock(staffStack, player.entityWorld, player, event.pos, event.face!!, event.hand)
- if (result != ActionResult.PASS) {
- event.isCanceled = true
- event.cancellationResult = result
- event.useBlock = Event.Result.DENY
- event.useItem = Event.Result.DENY
- }
-}
-
-private fun tryThrowCake(event: RightClickItem) {
- val player = event.entity
- val world = player.entityWorld
- val cake = player.getStackInHand(event.hand)
- val spawnPos = cakeEntityType.get().getSpawnPosition(world, player.approximateStaffTipPosition)
-
- if (!cake.isOf(Items.CAKE)) return
- if (spawnPos == null) return
- if (world.isClient) {
- event.isCanceled = true
- event.cancellationResult = ActionResult.SUCCESS
- return
- }
- if (!world.gameRules.getBoolean(throwableCakesGameRule)) return
-
- CakeEntity.throwCake(world, spawnPos, player.rotationVector * .5 + player.velocity, player)
- cake.decrementUnlessCreative(1, player)
-
- event.isCanceled = true
- event.cancellationResult = ActionResult.FAIL
-}
-
-private fun modifyLootTables(event: LootTableLoadEvent) {
- if (event.name !in MODIFIABLE_LOOT_TABLES) return
-
- event.table.addPool(
- LootPool.builder().with(
- LootTableEntry.builder(
- RegistryKey.of(RegistryKeys.LOOT_TABLE, Identifier.of(MOD_ID, "add_loot_pool/${event.name.path}"))
- )
- ).build()
- )
-}
-
-private const val maxAngerDistance = 16.0
-
-private fun tryAngerPiglins(event: AttackEntityEvent) {
- val player = event.entity
- val world = player.entityWorld
- val target = event.target
-
- if (world.isClient) return
- if (target !is LivingEntity) return
- if (!player.mainHandStack.isStaff) return
- if (!player.armorItems.any { it.isOf(crownOfKingOrangeItem.get()) }) return
-
- val box = Box.of(player.pos, 2 * maxAngerDistance, 2 * maxAngerDistance, 2 * maxAngerDistance)
- world.getEntitiesByClass(AbstractPiglinEntity::class.java, box) {
- it !== target && it.squaredDistanceTo(player) <= maxAngerDistance * maxAngerDistance
- }.forEach {
- IPiglinBrainAccessor.callBecomeAngryWith(it, target)
- }
-}
-
-fun stopUsingStaffWhenDropped(event: ItemTossEvent) {
- stopUsingStaffWhenDropped(event.player, event.entity)
-}
-
-fun stopUsingStaffWhenDropped(entity: LivingEntity, item: ItemEntity) {
- val staffItem = item.stack.item as? StaffItem ?: return
- staffItem.onStoppedUsing(item.stack, entity.entityWorld, entity, entity.itemUseTimeLeft)
-}
-
-@OnlyIn(Dist.CLIENT)
-fun registerSmithingTableTextures() {
- StaffInfusionSmithingRecipeTextures.register(
- Identifier.of(MOD_ID, "item/smithing_table/empty_slot_royal_staff"),
- ISmithingTemplateItemAccessor.emptySlotRedstoneDustTexture()
- )
-}
-
-@OnlyIn(Dist.CLIENT)
-fun clientAttack(player: PlayerEntity, hand: Hand) {
- val staffStack = player.getStackInHand(hand)
- val staffItem = staffStack.item as? StaffItem ?: return
-
- staffItem.attack(staffStack, player.entityWorld, player, hand)
- AttackC2SPacket(hand).sendToServer()
-}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/event_handler/ClientEventHandlers.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/event_handler/ClientEventHandlers.kt
new file mode 100644
index 000000000..297dee693
--- /dev/null
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/event_handler/ClientEventHandlers.kt
@@ -0,0 +1,43 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.internal.event_handler
+
+import net.minecraftforge.api.distmarker.Dist
+import net.minecraftforge.api.distmarker.OnlyIn
+import net.minecraftforge.event.entity.player.PlayerInteractEvent.LeftClickEmpty
+import opekope2.avm_staff.api.item.StaffItem
+import opekope2.avm_staff.internal.networking.c2s.play.AttackC2SPacket
+import thedarkcolour.kotlinforforge.forge.FORGE_BUS
+
+@OnlyIn(Dist.CLIENT)
+object ClientEventHandlers {
+ init {
+ FORGE_BUS.addListener(::click)
+ }
+
+ private fun click(event: LeftClickEmpty) {
+ val player = event.entity
+ val hand = event.hand
+ val staffStack = player.getStackInHand(hand)
+ val staffItem = staffStack.item as? StaffItem ?: return
+
+ staffItem.attack(staffStack, player.entityWorld, player, hand)
+ AttackC2SPacket(hand).sendToServer()
+ }
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/event_handler/EventHandlers.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/event_handler/EventHandlers.kt
new file mode 100644
index 000000000..561cc7ef1
--- /dev/null
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/event_handler/EventHandlers.kt
@@ -0,0 +1,180 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.internal.event_handler
+
+import net.minecraft.block.DispenserBlock
+import net.minecraft.entity.ItemEntity
+import net.minecraft.entity.LivingEntity
+import net.minecraft.entity.mob.AbstractPiglinEntity
+import net.minecraft.entity.player.PlayerEntity
+import net.minecraft.item.Items
+import net.minecraft.loot.LootPool
+import net.minecraft.loot.entry.LootTableEntry
+import net.minecraft.registry.RegistryKey
+import net.minecraft.registry.RegistryKeys
+import net.minecraft.server.network.ServerPlayerEntity
+import net.minecraft.util.ActionResult
+import net.minecraft.util.Identifier
+import net.minecraft.util.math.Box
+import net.minecraftforge.event.LootTableLoadEvent
+import net.minecraftforge.event.entity.item.ItemTossEvent
+import net.minecraftforge.event.entity.living.LivingDeathEvent
+import net.minecraftforge.event.entity.living.LivingHurtEvent
+import net.minecraftforge.event.entity.player.AttackEntityEvent
+import net.minecraftforge.event.entity.player.PlayerInteractEvent.LeftClickBlock
+import net.minecraftforge.event.entity.player.PlayerInteractEvent.RightClickItem
+import net.minecraftforge.eventbus.api.Event
+import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent
+import opekope2.avm_staff.api.block.dispenser.CakeDispenserBehavior
+import opekope2.avm_staff.api.entity.CakeEntity
+import opekope2.avm_staff.api.item.StaffItem
+import opekope2.avm_staff.content.Criteria
+import opekope2.avm_staff.content.EntityTypes
+import opekope2.avm_staff.content.GameRules
+import opekope2.avm_staff.mixin.IPiglinBrainAccessor
+import opekope2.avm_staff.util.*
+import thedarkcolour.kotlinforforge.forge.FORGE_BUS
+
+object EventHandlers {
+ private val MODIFIABLE_LOOT_TABLES = setOf(
+ Identifier.ofVanilla("chests/ancient_city"),
+ Identifier.ofVanilla("chests/bastion_other"),
+ Identifier.ofVanilla("chests/bastion_treasure"),
+ Identifier.ofVanilla("chests/trial_chambers/reward_unique"),
+ )
+ private const val MAX_ANGER_DISTANCE = 16.0
+
+ init {
+ FORGE_BUS.addListener(::die)
+ FORGE_BUS.addListener(::hurt)
+ FORGE_BUS.addListener(::leftClickBlock)
+ FORGE_BUS.addListener(::rightClickItem)
+ FORGE_BUS.addListener(::setup)
+ FORGE_BUS.addListener(::modifyLootTable)
+ FORGE_BUS.addListener(::attack)
+ FORGE_BUS.addListener(::drop)
+ }
+
+ private fun die(event: LivingDeathEvent) {
+ val entity = event.entity
+ if (entity !is PlayerEntity) return
+
+ if (entity.activeItem.isStaff) {
+ entity.stopUsingItem()
+ }
+ }
+
+ private fun hurt(event: LivingHurtEvent) {
+ val entity = event.entity
+ val damage = event.source
+
+ if (entity is ServerPlayerEntity && entity.isUsingItem) {
+ Criteria.takeDamageWhileUsingItem.trigger(entity, entity.activeItem, damage)
+ }
+ }
+
+ private fun leftClickBlock(event: LeftClickBlock) {
+ val player = event.entity
+ val hand = event.hand
+ val target = event.pos
+ val direction = event.face!!
+ val staffStack = player.getStackInHand(hand)
+ val staffItem = staffStack.item as? StaffItem ?: return
+
+ val result = staffItem.attackBlock(staffStack, player.entityWorld, player, target, direction, hand)
+ if (result != ActionResult.PASS) {
+ event.isCanceled = true
+ event.cancellationResult = result
+ event.useBlock = Event.Result.DENY
+ event.useItem = Event.Result.DENY
+ }
+ }
+
+ private fun rightClickItem(event: RightClickItem) {
+ val player = event.entity
+ val hand = event.hand
+ val world = player.entityWorld
+ val cake = player.getStackInHand(hand)
+ val spawnPos = EntityTypes.cake.getSpawnPosition(world, player.approximateStaffTipPosition)
+
+ if (!cake.isOf(Items.CAKE)) return
+ if (spawnPos == null) return
+ if (world.isClient) {
+ event.isCanceled = true
+ event.cancellationResult = ActionResult.SUCCESS
+ return
+ }
+ if (!world.gameRules.getBoolean(GameRules.THROWABLE_CAKES)) return
+
+ CakeEntity.throwCake(world, spawnPos, player.rotationVector * .5 + player.velocity, player)
+ cake.decrementUnlessCreative(1, player)
+
+ event.isCanceled = true
+ event.cancellationResult = ActionResult.FAIL
+ }
+
+ private fun setup(event: FMLCommonSetupEvent) {
+ event.enqueueWork {
+ DispenserBlock.registerBehavior(Items.CAKE, CakeDispenserBehavior())
+ }
+ }
+
+ private fun modifyLootTable(event: LootTableLoadEvent) {
+ val lootTable = event.name
+ if (lootTable !in MODIFIABLE_LOOT_TABLES) return
+
+ event.table.addPool(
+ LootPool.builder().with(
+ LootTableEntry.builder(
+ RegistryKey.of(
+ RegistryKeys.LOOT_TABLE,
+ Identifier.of(MOD_ID, "add_loot_pool/${lootTable.path}")
+ )
+ )
+ ).build()
+ )
+ }
+
+ private fun attack(event: AttackEntityEvent) {
+ val player = event.entity
+ val world = player.entityWorld
+ val target = event.target
+ if (world.isClient) return
+ if (target !is LivingEntity) return
+ if (!player.mainHandStack.isStaff) return
+ if (!player.armorItems.any { it.isOf(opekope2.avm_staff.content.Items.crownOfKingOrange) }) return
+
+ val box = Box.of(player.pos, 2 * MAX_ANGER_DISTANCE, 2 * MAX_ANGER_DISTANCE, 2 * MAX_ANGER_DISTANCE)
+ world.getEntitiesByClass(AbstractPiglinEntity::class.java, box) {
+ it !== target && it.squaredDistanceTo(player) <= MAX_ANGER_DISTANCE * MAX_ANGER_DISTANCE
+ }.forEach {
+ IPiglinBrainAccessor.callBecomeAngryWith(it, target)
+ }
+ }
+
+ private fun drop(event: ItemTossEvent) {
+ drop(event.player, event.entity)
+ }
+
+ fun drop(entity: PlayerEntity, item: ItemEntity) {
+ val staffItem = item.stack.item as? StaffItem ?: return
+
+ staffItem.onStoppedUsing(item.stack, entity.entityWorld, entity, entity.itemUseTimeLeft)
+ }
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/event_handler/KeyBindingHandler.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/event_handler/KeyBindingHandler.kt
index 418328a6e..a0e521fa1 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/event_handler/KeyBindingHandler.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/event_handler/KeyBindingHandler.kt
@@ -16,9 +16,6 @@
* along with this mod. If not, see .
*/
-@file: OnlyIn(Dist.CLIENT)
-@file: Suppress("UNUSED_PARAMETER")
-
package opekope2.avm_staff.internal.event_handler
import net.minecraft.client.MinecraftClient
@@ -29,43 +26,55 @@ import net.minecraft.item.ItemStack
import net.minecraftforge.api.distmarker.Dist
import net.minecraftforge.api.distmarker.OnlyIn
import net.minecraftforge.client.event.RegisterKeyMappingsEvent
+import net.minecraftforge.event.TickEvent.ClientTickEvent
import opekope2.avm_staff.internal.networking.c2s.play.InsertItemIntoStaffC2SPacket
import opekope2.avm_staff.internal.networking.c2s.play.InsertItemIntoStaffC2SPacket.Companion.tryInsertItemIntoStaff
import opekope2.avm_staff.internal.networking.c2s.play.RemoveItemFromStaffC2SPacket
import opekope2.avm_staff.internal.networking.c2s.play.RemoveItemFromStaffC2SPacket.Companion.tryRemoveItemFromStaff
import opekope2.avm_staff.util.MOD_ID
import org.lwjgl.glfw.GLFW
+import thedarkcolour.kotlinforforge.forge.FORGE_BUS
-private val addRemoveStaffItemKeyBinding by lazy {
- KeyBinding(
+@OnlyIn(Dist.CLIENT)
+internal object KeyBindingHandler {
+ private val ADD_REMOVE_STAFF_ITEM = KeyBinding(
"key.$MOD_ID.add_remove_staff_item",
InputUtil.Type.KEYSYM,
GLFW.GLFW_KEY_R,
"key.categories.$MOD_ID"
)
-}
-internal fun registerKeyBindings(event: RegisterKeyMappingsEvent) {
- event.register(addRemoveStaffItemKeyBinding)
-}
+ init {
+ FORGE_BUS.addListener(::register)
+ FORGE_BUS.addListener(::tick)
+ }
+
+ private fun register(event: RegisterKeyMappingsEvent) {
+ event.register(ADD_REMOVE_STAFF_ITEM)
+ }
-internal fun handleKeyBindings(client: MinecraftClient) {
- if (!addRemoveStaffItemKeyBinding.isPressed) return
- addRemoveStaffItemKeyBinding.isPressed = false
+ @Suppress("UNUSED_PARAMETER")
+ private fun tick(event: ClientTickEvent.Post) {
+ val client = MinecraftClient.getInstance()
+ if (!ADD_REMOVE_STAFF_ITEM.isPressed) return
+ ADD_REMOVE_STAFF_ITEM.isPressed = false
- val player = client.player ?: return
+ val player = client.player ?: return
- if (!player.tryInsertItemIntoStaff(::sendInsertPacket)) {
- player.tryRemoveItemFromStaff(::sendRemovePacket)
+ if (!player.tryInsertItemIntoStaff(KeyBindingHandler::sendInsertPacket)) {
+ player.tryRemoveItemFromStaff(KeyBindingHandler::sendRemovePacket)
+ }
}
-}
-private fun sendRemovePacket(player: PlayerEntity, staffStack: ItemStack, targetSlot: Int) {
- RemoveItemFromStaffC2SPacket().sendToServer()
- player.resetLastAttackedTicks()
-}
+ @Suppress("UNUSED_PARAMETER")
+ private fun sendRemovePacket(player: PlayerEntity, staffStack: ItemStack, targetSlot: Int) {
+ RemoveItemFromStaffC2SPacket().sendToServer()
+ player.resetLastAttackedTicks()
+ }
-private fun sendInsertPacket(player: PlayerEntity, staffStack: ItemStack, itemStackToAdd: ItemStack) {
- InsertItemIntoStaffC2SPacket().sendToServer()
- player.resetLastAttackedTicks()
+ @Suppress("UNUSED_PARAMETER")
+ private fun sendInsertPacket(player: PlayerEntity, staffStack: ItemStack, itemStackToAdd: ItemStack) {
+ InsertItemIntoStaffC2SPacket().sendToServer()
+ player.resetLastAttackedTicks()
+ }
}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/forge/StaffMod.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/forge/StaffMod.kt
index 1fa9241da..64f84622a 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/forge/StaffMod.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/forge/StaffMod.kt
@@ -19,20 +19,20 @@
package opekope2.avm_staff.internal.forge
import net.minecraft.block.Block
+import net.minecraft.entity.player.PlayerEntity
import net.minecraft.item.Item
import net.minecraft.particle.SimpleParticleType
import net.minecraftforge.api.distmarker.Dist
import net.minecraftforge.event.entity.living.LivingDropsEvent
import net.minecraftforge.fml.common.Mod
+import net.minecraftforge.registries.RegistryObject
import opekope2.avm_staff.api.IStaffModPlatform
+import opekope2.avm_staff.internal.event_handler.EventHandlers
import opekope2.avm_staff.internal.forge.item.ForgeCrownItem
import opekope2.avm_staff.internal.forge.item.ForgeStaffItem
import opekope2.avm_staff.internal.forge.item.ForgeStaffRendererItem
-import opekope2.avm_staff.internal.initializeNetworking
-import opekope2.avm_staff.internal.registerContent
+import opekope2.avm_staff.internal.initializer.Initializer
import opekope2.avm_staff.internal.staff.handler.registerVanillaStaffHandlers
-import opekope2.avm_staff.internal.stopUsingStaffWhenDropped
-import opekope2.avm_staff.internal.subscribeToEvents
import opekope2.avm_staff.util.MOD_ID
import thedarkcolour.kotlinforforge.forge.FORGE_BUS
import thedarkcolour.kotlinforforge.forge.runWhenOn
@@ -40,9 +40,8 @@ import thedarkcolour.kotlinforforge.forge.runWhenOn
@Mod(MOD_ID)
object StaffMod : IStaffModPlatform {
init {
- registerContent()
- initializeNetworking()
- subscribeToEvents()
+ Initializer
+ EventHandlers
subscribeForgeEvents()
registerVanillaStaffHandlers()
runWhenOn(Dist.CLIENT) { StaffModClient.initializeClient() }
@@ -53,12 +52,14 @@ object StaffMod : IStaffModPlatform {
}
private fun dropInventory(event: LivingDropsEvent) {
+ val player = event.entity as? PlayerEntity ?: return
for (item in event.drops) {
- stopUsingStaffWhenDropped(event.entity, item)
+ EventHandlers.drop(player, item)
}
}
- override fun staffItem(settings: Item.Settings) = ForgeStaffItem(settings)
+ override fun staffItem(settings: Item.Settings, repairIngredient: RegistryObject
- ?) =
+ ForgeStaffItem(settings, repairIngredient)
override fun itemWithStaffRenderer(settings: Item.Settings) = ForgeStaffRendererItem(settings)
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/forge/StaffModClient.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/forge/StaffModClient.kt
index 1c2b4d7b3..1f16f5329 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/forge/StaffModClient.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/forge/StaffModClient.kt
@@ -18,75 +18,41 @@
package opekope2.avm_staff.internal.forge
-import net.minecraft.block.DispenserBlock
-import net.minecraft.client.MinecraftClient
-import net.minecraft.client.render.entity.TntEntityRenderer
-import net.minecraft.item.Items
+import net.minecraft.client.item.ModelPredicateProviderRegistry
import net.minecraftforge.api.distmarker.Dist
import net.minecraftforge.api.distmarker.OnlyIn
-import net.minecraftforge.client.event.EntityRenderersEvent.RegisterRenderers
import net.minecraftforge.client.event.RegisterParticleProvidersEvent
-import net.minecraftforge.event.TickEvent
-import net.minecraftforge.event.entity.player.PlayerInteractEvent.LeftClickEmpty
import net.minecraftforge.eventbus.api.SubscribeEvent
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent
-import opekope2.avm_staff.api.block.dispenser.CakeDispenserBehavior
-import opekope2.avm_staff.api.cakeEntityType
-import opekope2.avm_staff.api.entity.renderer.CakeEntityRenderer
-import opekope2.avm_staff.api.flamethrowerParticleType
-import opekope2.avm_staff.api.impactTntEntityType
import opekope2.avm_staff.api.particle.FlamethrowerParticle
-import opekope2.avm_staff.api.soulFlamethrowerParticleType
-import opekope2.avm_staff.internal.clientAttack
-import opekope2.avm_staff.internal.event_handler.handleKeyBindings
-import opekope2.avm_staff.internal.event_handler.registerKeyBindings
-import opekope2.avm_staff.internal.model.registerModelPredicateProviders
-import opekope2.avm_staff.internal.registerSmithingTableTextures
+import opekope2.avm_staff.content.ParticleTypes
+import opekope2.avm_staff.internal.event_handler.ClientEventHandlers
+import opekope2.avm_staff.internal.initializer.ClientInitializer
+import opekope2.avm_staff.internal.model.ModelPredicates
import opekope2.avm_staff.internal.staff.handler.registerVanillaStaffItemRenderers
-import thedarkcolour.kotlinforforge.forge.FORGE_BUS
import thedarkcolour.kotlinforforge.forge.MOD_BUS
@OnlyIn(Dist.CLIENT)
object StaffModClient {
fun initializeClient() {
- registerSmithingTableTextures()
- subscribeToClientEvents()
+ ClientInitializer
+ ClientEventHandlers
registerVanillaStaffItemRenderers()
- MOD_BUS.register(::registerKeyBindings)
MOD_BUS.register(this)
}
- private fun subscribeToClientEvents() {
- FORGE_BUS.addListener(::clientTick)
- FORGE_BUS.addListener(::clientAttack)
- }
-
@SubscribeEvent
fun initializeClient(event: FMLClientSetupEvent) {
event.enqueueWork {
- registerModelPredicateProviders()
- DispenserBlock.registerBehavior(Items.CAKE, CakeDispenserBehavior())
+ for ((key, value) in ModelPredicates) {
+ ModelPredicateProviderRegistry.registerGeneric(key, value)
+ }
}
}
@SubscribeEvent
fun registerParticleProviders(event: RegisterParticleProvidersEvent) {
- event.registerSpriteSet(flamethrowerParticleType.get(), FlamethrowerParticle::Factory)
- event.registerSpriteSet(soulFlamethrowerParticleType.get(), FlamethrowerParticle::Factory)
- }
-
- @SubscribeEvent
- fun registerRenderers(event: RegisterRenderers) {
- event.registerEntityRenderer(impactTntEntityType.get(), ::TntEntityRenderer)
- event.registerEntityRenderer(cakeEntityType.get(), ::CakeEntityRenderer)
- }
-
- @Suppress("UNUSED_PARAMETER")
- fun clientTick(event: TickEvent.ClientTickEvent.Post) {
- handleKeyBindings(MinecraftClient.getInstance())
- }
-
- fun clientAttack(event: LeftClickEmpty) {
- clientAttack(event.entity, event.hand)
+ event.registerSpriteSet(ParticleTypes.flame, FlamethrowerParticle::Factory)
+ event.registerSpriteSet(ParticleTypes.soulFireFlame, FlamethrowerParticle::Factory)
}
}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/forge/item/ForgeStaffItem.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/forge/item/ForgeStaffItem.kt
index d24261654..2d2291c3f 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/forge/item/ForgeStaffItem.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/forge/item/ForgeStaffItem.kt
@@ -18,7 +18,6 @@
package opekope2.avm_staff.internal.forge.item
-import net.minecraft.client.MinecraftClient
import net.minecraft.client.render.VertexConsumerProvider
import net.minecraft.client.render.item.BuiltinModelItemRenderer
import net.minecraft.client.render.model.json.ModelTransformationMode
@@ -26,18 +25,23 @@ import net.minecraft.client.util.math.MatrixStack
import net.minecraft.entity.Entity
import net.minecraft.entity.LivingEntity
import net.minecraft.entity.player.PlayerEntity
+import net.minecraft.item.Item
import net.minecraft.item.ItemStack
import net.minecraft.util.ActionResult
import net.minecraft.util.Hand
import net.minecraftforge.client.extensions.common.IClientItemExtensions
import net.minecraftforge.common.extensions.IForgeItem
+import net.minecraftforge.registries.RegistryObject
import opekope2.avm_staff.api.item.StaffItem
import opekope2.avm_staff.api.item.renderer.StaffRenderer
+import opekope2.avm_staff.util.blockEntityRenderDispatcher
+import opekope2.avm_staff.util.entityModelLoader
import opekope2.avm_staff.util.itemInStaff
-import opekope2.avm_staff.util.staffHandlerOrDefault
+import opekope2.avm_staff.util.staffHandlerOrFallback
import java.util.function.Consumer
-class ForgeStaffItem(settings: Settings) : StaffItem(settings), IForgeItem {
+class ForgeStaffItem(settings: Settings, repairIngredientSupplier: RegistryObject
- ?) :
+ StaffItem(settings, repairIngredientSupplier), IForgeItem {
override fun canDisableShield(stack: ItemStack, shield: ItemStack, entity: LivingEntity, attacker: LivingEntity) =
disablesShield(stack, attacker.entityWorld, attacker, Hand.MAIN_HAND) ||
super.canDisableShield(stack, shield, entity, attacker)
@@ -54,8 +58,8 @@ class ForgeStaffItem(settings: Settings) : StaffItem(settings), IForgeItem {
attackEntity(stack, player.entityWorld, player, entity, Hand.MAIN_HAND) != ActionResult.PASS
override fun shouldCauseReequipAnimation(oldStack: ItemStack, newStack: ItemStack, slotChanged: Boolean): Boolean {
- val oldHandler = oldStack.itemInStaff.staffHandlerOrDefault
- val newHandler = newStack.itemInStaff.staffHandlerOrDefault
+ val oldHandler = oldStack.itemInStaff.staffHandlerOrFallback
+ val newHandler = newStack.itemInStaff.staffHandlerOrFallback
return if (oldHandler !== newHandler) true
else oldHandler.allowReequipAnimation(oldStack, newStack, slotChanged)
@@ -67,10 +71,7 @@ class ForgeStaffItem(settings: Settings) : StaffItem(settings), IForgeItem {
})
}
- object Renderer : BuiltinModelItemRenderer(
- MinecraftClient.getInstance().blockEntityRenderDispatcher,
- MinecraftClient.getInstance().entityModelLoader
- ) {
+ object Renderer : BuiltinModelItemRenderer(blockEntityRenderDispatcher, entityModelLoader) {
override fun render(
stack: ItemStack,
mode: ModelTransformationMode,
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/initializer/ClientInitializer.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/initializer/ClientInitializer.kt
new file mode 100644
index 000000000..95ae84996
--- /dev/null
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/initializer/ClientInitializer.kt
@@ -0,0 +1,55 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.internal.initializer
+
+import net.minecraft.client.render.entity.EmptyEntityRenderer
+import net.minecraft.client.render.entity.TntEntityRenderer
+import net.minecraft.util.Identifier
+import net.minecraftforge.api.distmarker.Dist
+import net.minecraftforge.api.distmarker.OnlyIn
+import net.minecraftforge.client.event.EntityRenderersEvent.RegisterRenderers
+import opekope2.avm_staff.api.entity.renderer.CakeEntityRenderer
+import opekope2.avm_staff.api.staff.StaffInfusionSmithingRecipeTextures
+import opekope2.avm_staff.content.EntityTypes
+import opekope2.avm_staff.internal.event_handler.KeyBindingHandler
+import opekope2.avm_staff.mixin.ISmithingTemplateItemAccessor
+import opekope2.avm_staff.util.MOD_ID
+import thedarkcolour.kotlinforforge.forge.MOD_BUS
+
+@OnlyIn(Dist.CLIENT)
+object ClientInitializer {
+ init {
+ KeyBindingHandler
+ MOD_BUS.addListener(::registerEntityRenderers)
+ registerSmithingTableTextures()
+ }
+
+ private fun registerEntityRenderers(event: RegisterRenderers) {
+ event.registerEntityRenderer(EntityTypes.impactTnt, ::TntEntityRenderer)
+ event.registerEntityRenderer(EntityTypes.cake, ::CakeEntityRenderer)
+ event.registerEntityRenderer(EntityTypes.campfireFlame, ::EmptyEntityRenderer)
+ }
+
+ private fun registerSmithingTableTextures() {
+ StaffInfusionSmithingRecipeTextures.register(
+ Identifier.of(MOD_ID, "item/smithing_table/empty_slot_royal_staff"),
+ ISmithingTemplateItemAccessor.emptySlotRedstoneDustTexture()
+ )
+ }
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/initializer/Initializer.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/initializer/Initializer.kt
new file mode 100644
index 000000000..14d4c707d
--- /dev/null
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/initializer/Initializer.kt
@@ -0,0 +1,57 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.internal.initializer
+
+import opekope2.avm_staff.content.*
+import opekope2.avm_staff.internal.networking.c2s.play.AttackC2SPacket
+import opekope2.avm_staff.internal.networking.c2s.play.InsertItemIntoStaffC2SPacket
+import opekope2.avm_staff.internal.networking.c2s.play.RemoveItemFromStaffC2SPacket
+import opekope2.avm_staff.internal.networking.s2c.play.MassDestructionS2CPacket
+
+object Initializer {
+ init {
+ registerContent()
+ initializeNetworking()
+ }
+
+ private fun registerContent() {
+ Blocks.register()
+ Criteria.register()
+ DamageTypes
+ DataComponentTypes.register()
+ Enchantments
+ Enchantments.Tags
+ EntityTypes.register()
+ GameRules
+ ItemGroups.register()
+ Items.register()
+ Items.Tags
+ ParticleTypes.register()
+ SoundEvents.register()
+ StatTypes.register()
+ }
+
+ private fun initializeNetworking() {
+ AttackC2SPacket
+ InsertItemIntoStaffC2SPacket
+ RemoveItemFromStaffC2SPacket
+
+ MassDestructionS2CPacket
+ }
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/model/ModelPredicates.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/model/ModelPredicates.kt
index 9677d77c1..104c557e4 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/model/ModelPredicates.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/model/ModelPredicates.kt
@@ -16,44 +16,38 @@
* along with this mod. If not, see .
*/
-@file: OnlyIn(Dist.CLIENT)
-
package opekope2.avm_staff.internal.model
import net.minecraft.client.item.ClampedModelPredicateProvider
-import net.minecraft.client.item.ModelPredicateProvider
-import net.minecraft.client.item.ModelPredicateProviderRegistry
import net.minecraft.item.ItemStack
import net.minecraft.util.Identifier
import net.minecraftforge.api.distmarker.Dist
import net.minecraftforge.api.distmarker.OnlyIn
import opekope2.avm_staff.api.component.StaffRendererPartComponent
-import opekope2.avm_staff.api.staffRendererOverrideComponentType
-import opekope2.avm_staff.api.staffRendererPartComponentType
+import opekope2.avm_staff.api.registry.RegistryBase
+import opekope2.avm_staff.content.DataComponentTypes
import opekope2.avm_staff.util.MOD_ID
-import kotlin.jvm.optionals.getOrNull
-
-private fun register(id: Identifier, provider: ModelPredicateProvider) {
- ModelPredicateProviderRegistry.registerGeneric(id, provider)
-}
-fun registerModelPredicateProviders() {
- register(Identifier.of(MOD_ID, "using_item")) { stack, _, entity, _ ->
- val isActiveOverride = stack[staffRendererOverrideComponentType.get()]?.isActive?.getOrNull()
- when {
- isActiveOverride == true -> 1f
- isActiveOverride == false -> 0f
- entity != null && entity.isUsingItem && ItemStack.areEqual(entity.activeItem, stack) -> 1f
- else -> 0f
+@OnlyIn(Dist.CLIENT)
+object ModelPredicates : RegistryBase() {
+ init {
+ register(Identifier.of(MOD_ID, "using_item")) { stack, _, entity, _ ->
+ if (entity == null || !entity.isUsingItem) return@register 0f
+ // When the item's components get changed server-side, Minecraft client is just janky with references
+ val sameItem = ItemStack.areEqual(entity.activeItem, stack) ||
+ ItemStack.areEqual(entity.getStackInHand(entity.activeHand), stack)
+ if (sameItem) 1f
+ else 0f
}
+ register(Identifier.of(MOD_ID, "head"), matchStaffRendererPart(StaffRendererPartComponent.HEAD))
+ register(Identifier.of(MOD_ID, "item"), matchStaffRendererPart(StaffRendererPartComponent.ITEM))
+ register(Identifier.of(MOD_ID, "rod_top"), matchStaffRendererPart(StaffRendererPartComponent.ROD_TOP))
+ register(Identifier.of(MOD_ID, "rod_bottom"), matchStaffRendererPart(StaffRendererPartComponent.ROD_BOTTOM))
}
- register(Identifier.of(MOD_ID, "head"), matchStaffRendererPart(StaffRendererPartComponent.HEAD))
- register(Identifier.of(MOD_ID, "item"), matchStaffRendererPart(StaffRendererPartComponent.ITEM))
- register(Identifier.of(MOD_ID, "rod_top"), matchStaffRendererPart(StaffRendererPartComponent.ROD_TOP))
- register(Identifier.of(MOD_ID, "rod_bottom"), matchStaffRendererPart(StaffRendererPartComponent.ROD_BOTTOM))
-}
-private fun matchStaffRendererPart(part: StaffRendererPartComponent) = ClampedModelPredicateProvider { stack, _, _, _ ->
- if (stack[staffRendererPartComponentType.get()] == part) 1f
- else 0f
+ private fun matchStaffRendererPart(part: StaffRendererPartComponent) =
+ ClampedModelPredicateProvider { stack, _, _, _ ->
+ if (stack[DataComponentTypes.staffRendererPart] == part) 1f
+ else 0f
+ }
}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/networking/c2s/play/InsertItemIntoStaffC2SPacket.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/networking/c2s/play/InsertItemIntoStaffC2SPacket.kt
index 94dfa7886..e40290642 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/networking/c2s/play/InsertItemIntoStaffC2SPacket.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/networking/c2s/play/InsertItemIntoStaffC2SPacket.kt
@@ -22,6 +22,7 @@ import net.minecraft.entity.player.PlayerEntity
import net.minecraft.item.ItemStack
import net.minecraft.network.RegistryByteBuf
import net.minecraft.util.Identifier
+import net.minecraft.world.event.GameEvent
import net.minecraftforge.event.network.CustomPayloadEvent
import net.minecraftforge.network.NetworkDirection
import opekope2.avm_staff.internal.networking.IC2SPacket
@@ -47,6 +48,7 @@ internal class InsertItemIntoStaffC2SPacket() : IC2SPacket
staffStack.mutableItemStackInStaff = toInsert.split(1)
player.resetLastAttackedTicks()
+ player.entityWorld.emitGameEvent(player, GameEvent.RESONATE_5, player.pos)
}
}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/networking/c2s/play/RemoveItemFromStaffC2SPacket.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/networking/c2s/play/RemoveItemFromStaffC2SPacket.kt
index 47ad0f0e9..6ea64f422 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/networking/c2s/play/RemoveItemFromStaffC2SPacket.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/networking/c2s/play/RemoveItemFromStaffC2SPacket.kt
@@ -23,6 +23,7 @@ import net.minecraft.entity.player.PlayerInventory
import net.minecraft.item.ItemStack
import net.minecraft.network.RegistryByteBuf
import net.minecraft.util.Identifier
+import net.minecraft.world.event.GameEvent
import net.minecraftforge.event.network.CustomPayloadEvent
import net.minecraftforge.network.NetworkDirection
import opekope2.avm_staff.internal.networking.IC2SPacket
@@ -49,6 +50,7 @@ internal class RemoveItemFromStaffC2SPacket() : IC2SPacket.
+ */
+
+package opekope2.avm_staff.internal.staff.handler
+
+import net.minecraft.entity.LivingEntity
+import net.minecraft.entity.player.PlayerEntity
+import net.minecraft.item.ItemStack
+import net.minecraft.server.network.ServerPlayerEntity
+import net.minecraft.server.world.ServerWorld
+import net.minecraft.util.ActionResult
+import net.minecraft.util.Hand
+import net.minecraft.util.math.BlockPos
+import net.minecraft.util.math.Direction
+import net.minecraft.world.World
+import opekope2.avm_staff.api.staff.StaffHandler
+import opekope2.avm_staff.util.destruction.BlockDestructionPredicate
+import opekope2.avm_staff.util.destruction.IShapedBlockDestructionPredicate
+import opekope2.avm_staff.util.destruction.destroyBox
+import opekope2.avm_staff.util.dropcollector.ChunkedBlockDropCollector
+import opekope2.avm_staff.util.dropcollector.IBlockDropCollector
+import opekope2.avm_staff.util.dropcollector.NoOpBlockDropCollector
+import opekope2.avm_staff.util.incrementItemUseStat
+import opekope2.avm_staff.util.incrementStaffItemUseStat
+import opekope2.avm_staff.util.isAttackCoolingDown
+import opekope2.avm_staff.util.itemInStaff
+
+internal abstract class AbstractMassDestructiveStaffHandler : StaffHandler() {
+ override fun attackBlock(
+ staffStack: ItemStack,
+ world: World,
+ attacker: LivingEntity,
+ target: BlockPos,
+ side: Direction,
+ hand: Hand
+ ): ActionResult {
+ if (world.isClient) return ActionResult.PASS
+ if (attacker is PlayerEntity && attacker.isAttackCoolingDown) return ActionResult.PASS
+ require(world is ServerWorld)
+
+ val shapePredicate = createBlockDestructionShapePredicate(world, attacker, target)
+ val dropCollector =
+ if (attacker is PlayerEntity && attacker.abilities.creativeMode) NoOpBlockDropCollector()
+ else createBlockDropCollector(shapePredicate)
+
+ destroyBox(
+ world,
+ shapePredicate.volume,
+ dropCollector,
+ attacker,
+ staffStack,
+ createDestructionPredicate(shapePredicate)
+ )
+ dropCollector.dropAll(world)
+
+ (attacker as? ServerPlayerEntity)?.incrementItemUseStat(staffStack.item)
+ (attacker as? ServerPlayerEntity)?.incrementStaffItemUseStat(staffStack.itemInStaff!!)
+
+ // "Mismatch in destroy block pos" in server logs if I interrupt on server but not on client side. Nothing bad should happen, right?
+ return ActionResult.PASS
+ }
+
+ protected abstract fun createBlockDestructionShapePredicate(
+ world: World,
+ attacker: LivingEntity,
+ target: BlockPos
+ ): IShapedBlockDestructionPredicate
+
+ protected abstract fun createDestructionPredicate(shapePredicate: IShapedBlockDestructionPredicate): BlockDestructionPredicate
+
+ protected open fun createBlockDropCollector(shapePredicate: IShapedBlockDestructionPredicate): IBlockDropCollector =
+ ChunkedBlockDropCollector(shapePredicate.volume, MAX_CHUNK_SIZE)
+
+ companion object {
+ const val MAX_CHUNK_SIZE = 3
+ }
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/AbstractProjectileShootingStaffHandler.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/AbstractProjectileShootingStaffHandler.kt
new file mode 100644
index 000000000..24d073ab8
--- /dev/null
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/AbstractProjectileShootingStaffHandler.kt
@@ -0,0 +1,102 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.internal.staff.handler
+
+import net.minecraft.entity.LivingEntity
+import net.minecraft.entity.player.PlayerEntity
+import net.minecraft.item.ItemStack
+import net.minecraft.server.network.ServerPlayerEntity
+import net.minecraft.util.Hand
+import net.minecraft.util.TypedActionResult
+import net.minecraft.world.World
+import opekope2.avm_staff.api.staff.StaffHandler
+import opekope2.avm_staff.content.Enchantments
+import opekope2.avm_staff.util.*
+
+internal abstract class AbstractProjectileShootingStaffHandler : StaffHandler() {
+ protected abstract fun getFireRateDenominator(rapidFireLevel: Int): Int
+
+ override fun getMaxUseTime(staffStack: ItemStack, world: World, user: LivingEntity) = 72000
+
+ override fun use(
+ staffStack: ItemStack,
+ world: World,
+ user: LivingEntity,
+ hand: Hand
+ ): TypedActionResult {
+ val allowsProjectileRapidFire = staffStack.isEnchantedWith(Enchantments.RAPID_FIRE, world.registryManager)
+ if (!allowsProjectileRapidFire) return TypedActionResult.pass(staffStack)
+
+ user.setCurrentHand(hand)
+ return TypedActionResult.consume(staffStack)
+ }
+
+ override fun usageTick(staffStack: ItemStack, world: World, user: LivingEntity, remainingUseTicks: Int) {
+ val rapidFire = staffStack.getEnchantmentLevel(Enchantments.RAPID_FIRE, world.registryManager)
+ if (remainingUseTicks % getFireRateDenominator(rapidFire) != 0) return
+ if (!tryShootProjectile(staffStack, world, user, ProjectileShootReason.USE)) return
+
+ staffStack.damage(entity = user)
+ (user as? ServerPlayerEntity)?.incrementItemUseStat(staffStack.item)
+ (user as? ServerPlayerEntity)?.incrementStaffItemUseStat(staffStack.itemInStaff!!)
+ }
+
+ override fun attack(staffStack: ItemStack, world: World, attacker: LivingEntity, hand: Hand) {
+ if (tryShootProjectile(staffStack, world, attacker, ProjectileShootReason.ATTACK)) {
+ staffStack.damage(entity = attacker)
+ (attacker as? ServerPlayerEntity)?.incrementItemUseStat(staffStack.item)
+ (attacker as? ServerPlayerEntity)?.incrementStaffItemUseStat(staffStack.itemInStaff!!)
+ }
+ (attacker as? PlayerEntity)?.resetLastAttackedTicks()
+ }
+
+ protected open fun tryShootProjectile(
+ staffStack: ItemStack,
+ world: World,
+ shooter: LivingEntity,
+ reason: ProjectileShootReason
+ ): Boolean {
+ if (world.isClient) return false
+ if (!shooter.canUseStaff) return false
+ if (shooter is PlayerEntity && shooter.isAttackCoolingDown) return false
+
+ return true
+ }
+
+ override fun allowComponentsUpdateAnimation(
+ oldStaffStack: ItemStack,
+ newStaffStack: ItemStack,
+ player: PlayerEntity,
+ hand: Hand
+ ) = false
+
+ override fun allowReequipAnimation(
+ oldStaffStack: ItemStack,
+ newStaffStack: ItemStack,
+ selectedSlotChanged: Boolean
+ ) = selectedSlotChanged
+
+ protected enum class ProjectileShootReason {
+ ATTACK,
+ USE;
+
+ inline val isAttack: Boolean
+ get() = this == ATTACK
+ }
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/AnvilHandler.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/AnvilHandler.kt
index 779376699..99576e7d1 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/AnvilHandler.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/AnvilHandler.kt
@@ -27,6 +27,7 @@ import net.minecraft.entity.player.PlayerEntity
import net.minecraft.item.Item
import net.minecraft.item.ItemStack
import net.minecraft.predicate.entity.EntityPredicates
+import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.ActionResult
import net.minecraft.util.Hand
import net.minecraft.util.Identifier
@@ -75,7 +76,7 @@ internal class AnvilHandler(private val damagedItem: Item?) : StaffHandler() {
val fallDistance = ceil(attacker.fallDistance - 1f)
if (fallDistance <= 0) return ActionResult.FAIL
- aoeAttack(world, attacker, target, fallDistance)
+ val damagedEntities = aoeAttack(world, attacker, target, fallDistance)
world.syncWorldEvent(WorldEvents.SMASH_ATTACK, target.steppingPos, 750)
attacker.fallDistance = 0f
@@ -87,10 +88,14 @@ internal class AnvilHandler(private val damagedItem: Item?) : StaffHandler() {
0
)
+ (attacker as? ServerPlayerEntity)?.incrementItemUseStat(staffStack.item)
+ (attacker as? ServerPlayerEntity)?.incrementStaffItemUseStat(staffStack.itemInStaff!!)
+ staffStack.damage(damagedEntities, attacker, LivingEntity.getSlotForHand(hand))
+
return ActionResult.FAIL
}
- private fun aoeAttack(world: World, attacker: LivingEntity, target: Entity, fallDistance: Float) {
+ private fun aoeAttack(world: World, attacker: LivingEntity, target: Entity, fallDistance: Float): Int {
val cappedFallDistance = floor(fallDistance * IAnvilBlockAccessor.fallingBlockEntityDamageMultiplier())
.coerceAtMost(IAnvilBlockAccessor.fallingBlockEntityMaxDamage().toFloat())
val cooldownProgress =
@@ -103,9 +108,12 @@ internal class AnvilHandler(private val damagedItem: Item?) : StaffHandler() {
.and(EntityPredicates.VALID_LIVING_ENTITY)
.and(EntityPredicates.maxDistance(target.x, target.y, target.z, radius))
- world.getOtherEntities(attacker, box, predicate).forEach { entity ->
+ val entities = world.getOtherEntities(attacker, box, predicate)
+ for (entity in entities) {
entity.damage(world.damageSources.fallingAnvil(attacker), amount / (entity.distanceTo(target) + 1))
}
+
+ return entities.size
}
private fun damageAnvil(staffStack: ItemStack, attacker: LivingEntity, fallDistance: Float): Boolean {
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/BellBlockHandler.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/BellHandler.kt
similarity index 63%
rename from StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/BellBlockHandler.kt
rename to StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/BellHandler.kt
index 2e9fd3c9d..712813cc7 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/BellBlockHandler.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/BellHandler.kt
@@ -18,33 +18,31 @@
package opekope2.avm_staff.internal.staff.handler
-import net.minecraft.client.render.RenderLayer
-import net.minecraft.client.render.VertexConsumerProvider
-import net.minecraft.client.render.block.entity.BellBlockEntityRenderer
-import net.minecraft.client.render.model.json.ModelTransformationMode
-import net.minecraft.client.util.math.MatrixStack
import net.minecraft.component.type.AttributeModifierSlot
import net.minecraft.entity.Entity
import net.minecraft.entity.LivingEntity
+import net.minecraft.entity.ai.brain.MemoryModuleType
import net.minecraft.entity.attribute.EntityAttributes
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.item.ItemStack
+import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.sound.SoundCategory
import net.minecraft.sound.SoundEvents
import net.minecraft.util.ActionResult
import net.minecraft.util.Hand
import net.minecraft.util.TypedActionResult
+import net.minecraft.util.math.Box
import net.minecraft.world.World
-import net.minecraftforge.api.distmarker.Dist
-import net.minecraftforge.api.distmarker.OnlyIn
-import opekope2.avm_staff.api.item.renderer.IStaffItemRenderer
+import net.minecraft.world.event.GameEvent
import opekope2.avm_staff.api.staff.StaffAttributeModifiersComponentBuilder
import opekope2.avm_staff.api.staff.StaffHandler
+import opekope2.avm_staff.mixin.IBellBlockEntityAccessor
import opekope2.avm_staff.util.attackDamage
import opekope2.avm_staff.util.attackSpeed
-import opekope2.avm_staff.util.push
+import opekope2.avm_staff.util.incrementStaffItemUseStat
+import opekope2.avm_staff.util.itemInStaff
-internal class BellBlockHandler : StaffHandler() {
+internal class BellHandler : StaffHandler() {
override val attributeModifiers = StaffAttributeModifiersComponentBuilder()
.add(EntityAttributes.GENERIC_ATTACK_DAMAGE, attackDamage(8.0), AttributeModifierSlot.MAINHAND)
.add(EntityAttributes.GENERIC_ATTACK_SPEED, attackSpeed(1.5), AttributeModifierSlot.MAINHAND)
@@ -55,11 +53,30 @@ internal class BellBlockHandler : StaffHandler() {
override fun use(
staffStack: ItemStack,
world: World,
- user: PlayerEntity,
+ user: LivingEntity,
hand: Hand
): TypedActionResult {
world.playSound(user, user.blockPos, SoundEvents.BLOCK_BELL_USE, SoundCategory.BLOCKS, 2f, 1f)
+ if (!world.isClient) {
+ val box = Box(user.blockPos).expand(48.0)
+ val hearingEntities = world.getNonSpectatingEntities(LivingEntity::class.java, box)
+ var resonate = false
+ for (entity in hearingEntities) {
+ if (entity.isAlive && !entity.isRemoved && user.blockPos.isWithinDistance(entity.pos, 32.0)) {
+ entity.brain.remember(MemoryModuleType.HEARD_BELL_TIME, world.time)
+ }
+ if (IBellBlockEntityAccessor.callIsRaiderEntity(user.blockPos, entity)) {
+ IBellBlockEntityAccessor.callApplyGlowToEntity(entity)
+ resonate = true
+ }
+ }
+ if (resonate) {
+ world.playSound(null, user.blockPos, SoundEvents.BLOCK_BELL_RESONATE, SoundCategory.BLOCKS, 1.0f, 1.0f)
+ }
+ world.emitGameEvent(user, GameEvent.RESONATE_10, user.pos)
+ }
+
return TypedActionResult.success(staffStack)
}
@@ -79,37 +96,8 @@ internal class BellBlockHandler : StaffHandler() {
1f
)
- return ActionResult.PASS
- }
-
- @OnlyIn(Dist.CLIENT)
- class BellStaffItemRenderer : IStaffItemRenderer {
- private val bellModel = BellBlockEntityRenderer.getTexturedModelData().createModel().apply {
- setPivot(-8f, -12f, -8f)
- }
-
- override fun renderItemInStaff(
- staffStack: ItemStack,
- mode: ModelTransformationMode,
- matrices: MatrixStack,
- vertexConsumers: VertexConsumerProvider,
- light: Int,
- overlay: Int
- ) {
- matrices.push {
- scale(16f / 9f, 16f / 9f, 16f / 9f)
- translate(0f, 2f / 9f, 0f)
+ (attacker as? ServerPlayerEntity)?.incrementStaffItemUseStat(staffStack.itemInStaff!!)
- bellModel.render(
- matrices,
- BellBlockEntityRenderer.BELL_BODY_TEXTURE.getVertexConsumer(
- vertexConsumers,
- RenderLayer::getEntitySolid
- ),
- light,
- overlay
- )
- }
- }
+ return ActionResult.PASS
}
}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/BoneBlockHandler.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/BoneBlockHandler.kt
index bd9e327ee..253bcec9c 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/BoneBlockHandler.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/BoneBlockHandler.kt
@@ -19,11 +19,13 @@
package opekope2.avm_staff.internal.staff.handler
import net.minecraft.component.type.AttributeModifierSlot
+import net.minecraft.enchantment.Enchantments
import net.minecraft.entity.LivingEntity
import net.minecraft.entity.attribute.EntityAttributes
import net.minecraft.item.BoneMealItem
import net.minecraft.item.ItemStack
import net.minecraft.item.Items
+import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.ActionResult
import net.minecraft.util.Hand
import net.minecraft.util.math.BlockPos
@@ -33,8 +35,7 @@ import net.minecraft.world.WorldEvents
import net.minecraft.world.event.GameEvent
import opekope2.avm_staff.api.staff.StaffAttributeModifiersComponentBuilder
import opekope2.avm_staff.api.staff.StaffHandler
-import opekope2.avm_staff.util.attackDamage
-import opekope2.avm_staff.util.attackSpeed
+import opekope2.avm_staff.util.*
internal class BoneBlockHandler : StaffHandler() {
override val attributeModifiers = StaffAttributeModifiersComponentBuilder()
@@ -52,27 +53,55 @@ internal class BoneBlockHandler : StaffHandler() {
side: Direction,
hand: Hand
): ActionResult {
- if (BoneMealItem.useOnFertilizable(Items.BONE_MEAL.defaultStack, world, target)) {
- // TODO fertilize area when enchanted
- if (!world.isClient) {
- user.emitGameEvent(GameEvent.ITEM_INTERACT_FINISH)
- world.syncWorldEvent(WorldEvents.BONE_MEAL_USED, target, 15)
- }
+ if (!useOnFertilizable(world, user, target)) {
+ return if (useOnGround(world, user, target, side)) {
+ (user as? ServerPlayerEntity)?.incrementStaffItemUseStat(staffStack.itemInStaff!!)
+ staffStack.damage(1, user, LivingEntity.getSlotForHand(hand))
+ ActionResult.SUCCESS
+ } else ActionResult.PASS
+ }
+
+ val range =
+ 0.5 + staffStack.getEnchantmentLevel(Enchantments.EFFICIENCY, world.registryManager).coerceAtMost(5) / 2.0
+ val rangeSquare = range * range
+ var uses = 0
+
+ for (pos in BlockPos.iterateOutwards(target, range.toInt(), range.toInt(), range.toInt())) {
+ if (pos == target) continue
+ val offset = pos - target
+ if (offset.x * offset.x + offset.y * offset.y + offset.z * offset.z > rangeSquare) continue
+
+ if (useOnFertilizable(world, user, pos)) uses++
+ }
+
+ (user as? ServerPlayerEntity)?.incrementStaffItemUseStat(staffStack.itemInStaff!!)
+ staffStack.damage(uses, user, hand)
+
+ return ActionResult.SUCCESS
+ }
- return ActionResult.SUCCESS
+ private fun useOnFertilizable(world: World, user: LivingEntity, target: BlockPos): Boolean {
+ if (!BoneMealItem.useOnFertilizable(Items.BONE_MEAL.defaultStack, world, target)) return false
+
+ if (!world.isClient) {
+ user.emitGameEvent(GameEvent.ITEM_INTERACT_FINISH)
+ world.syncWorldEvent(WorldEvents.BONE_MEAL_USED, target, 15)
}
+ return true
+ }
+ private fun useOnGround(world: World, user: LivingEntity, target: BlockPos, side: Direction): Boolean {
val targetState = world.getBlockState(target)
- if (!targetState.isSideSolidFullSquare(world, target, side)) return ActionResult.PASS
+ if (!targetState.isSideSolidFullSquare(world, target, side)) return false
val neighborOnUsedSide = target.offset(side)
- if (!BoneMealItem.useOnGround(staffStack.copy(), world, neighborOnUsedSide, side)) return ActionResult.PASS
+ if (!BoneMealItem.useOnGround(Items.BONE_MEAL.defaultStack, world, neighborOnUsedSide, side)) return false
if (!world.isClient) {
user.emitGameEvent(GameEvent.ITEM_INTERACT_FINISH)
world.syncWorldEvent(WorldEvents.BONE_MEAL_USED, neighborOnUsedSide, 15)
}
- return ActionResult.SUCCESS
+ return true
}
}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/CakeHandler.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/CakeHandler.kt
index 103ea5e60..191789567 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/CakeHandler.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/CakeHandler.kt
@@ -19,46 +19,35 @@
package opekope2.avm_staff.internal.staff.handler
import net.minecraft.entity.LivingEntity
-import net.minecraft.entity.player.PlayerEntity
import net.minecraft.item.ItemStack
-import net.minecraft.util.Hand
-import net.minecraft.util.TypedActionResult
import net.minecraft.world.World
-import opekope2.avm_staff.api.cakeEntityType
import opekope2.avm_staff.api.entity.CakeEntity
-import opekope2.avm_staff.api.staff.StaffHandler
-import opekope2.avm_staff.util.*
-
-internal class CakeHandler : StaffHandler() {
- override val maxUseTime: Int
- get() = 72000
-
- override fun use(
+import opekope2.avm_staff.content.EntityTypes
+import opekope2.avm_staff.util.approximateStaffTipPosition
+import opekope2.avm_staff.util.getSpawnPosition
+import opekope2.avm_staff.util.plus
+import opekope2.avm_staff.util.times
+
+internal class CakeHandler : AbstractProjectileShootingStaffHandler() {
+ override fun getFireRateDenominator(rapidFireLevel: Int) = if (rapidFireLevel >= 2) 1 else 2
+
+ private val ProjectileShootReason.velocity: Double
+ get() = when (this) {
+ ProjectileShootReason.ATTACK -> 0.5
+ ProjectileShootReason.USE -> 1.0
+ }
+
+ override fun tryShootProjectile(
staffStack: ItemStack,
world: World,
- user: PlayerEntity,
- hand: Hand
- ): TypedActionResult {
- user.setCurrentHand(hand)
- return TypedActionResult.consume(staffStack)
- }
-
- override fun usageTick(staffStack: ItemStack, world: World, user: LivingEntity, remainingUseTicks: Int) {
- tryThrowCake(world, user, 1.0)
- }
-
- override fun attack(staffStack: ItemStack, world: World, attacker: LivingEntity, hand: Hand) {
- tryThrowCake(world, attacker, 0.5)
- (attacker as? PlayerEntity)?.resetLastAttackedTicks()
- }
-
- private fun tryThrowCake(world: World, user: LivingEntity, velocityMultiplier: Double) {
- if (world.isClient) return
- if (!user.canUseStaff) return
- if (user is PlayerEntity && user.isAttackCoolingDown) return
+ shooter: LivingEntity,
+ reason: ProjectileShootReason
+ ): Boolean {
+ if (!super.tryShootProjectile(staffStack, world, shooter, reason)) return false
- val spawnPos = cakeEntityType.get().getSpawnPosition(world, user.approximateStaffTipPosition) ?: return
+ val spawnPos = EntityTypes.cake.getSpawnPosition(world, shooter.approximateStaffTipPosition) ?: return false
+ CakeEntity.throwCake(world, spawnPos, shooter.rotationVector * reason.velocity + shooter.velocity, shooter)
- CakeEntity.throwCake(world, spawnPos, user.rotationVector * velocityMultiplier + user.velocity, user)
+ return true
}
}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/CampfireHandler.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/CampfireHandler.kt
index db9ee40a3..709bae12f 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/CampfireHandler.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/CampfireHandler.kt
@@ -18,53 +18,35 @@
package opekope2.avm_staff.internal.staff.handler
-import net.minecraft.block.*
-import net.minecraft.client.MinecraftClient
-import net.minecraft.client.option.GraphicsMode
import net.minecraft.entity.Entity
import net.minecraft.entity.LivingEntity
import net.minecraft.entity.player.PlayerEntity
-import net.minecraft.entity.projectile.ProjectileUtil
import net.minecraft.item.ItemStack
import net.minecraft.particle.SimpleParticleType
-import net.minecraft.state.property.Properties.LIT
+import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.ActionResult
import net.minecraft.util.Hand
import net.minecraft.util.TypedActionResult
-import net.minecraft.util.hit.BlockHitResult
-import net.minecraft.util.hit.HitResult
-import net.minecraft.util.math.Box
import net.minecraft.util.math.MathHelper
-import net.minecraft.util.math.Vec3d
-import net.minecraft.util.math.random.Random
-import net.minecraft.world.RaycastContext
import net.minecraft.world.World
-import net.minecraft.world.event.GameEvent
-import net.minecraftforge.api.distmarker.Dist
-import net.minecraftforge.api.distmarker.OnlyIn
-import net.minecraftforge.event.TickEvent
import net.minecraftforge.registries.RegistryObject
-import opekope2.avm_staff.api.rocketModeComponentType
+import opekope2.avm_staff.api.entity.CampfireFlameEntity
import opekope2.avm_staff.api.staff.StaffHandler
-import opekope2.avm_staff.internal.MinecraftUnit
+import opekope2.avm_staff.content.DataComponentTypes
+import opekope2.avm_staff.internal.minecraftUnit
import opekope2.avm_staff.util.*
-import thedarkcolour.kotlinforforge.forge.FORGE_BUS
-internal class CampfireHandler(
- private val particleEffectSupplier: RegistryObject,
- private val properties: Properties
-) : StaffHandler() {
- override val maxUseTime: Int
- get() = 72000
+internal class CampfireHandler(private val parameters: Parameters) : StaffHandler() {
+ override fun getMaxUseTime(staffStack: ItemStack, world: World, user: LivingEntity) = 72000
override fun use(
staffStack: ItemStack,
world: World,
- user: PlayerEntity,
+ user: LivingEntity,
hand: Hand
): TypedActionResult {
if (user.isSneaking && !user.isOnGround) {
- staffStack[rocketModeComponentType.get()] = MinecraftUnit.INSTANCE
+ staffStack[DataComponentTypes.rocketMode] = minecraftUnit
}
user.setCurrentHand(hand)
@@ -76,69 +58,44 @@ internal class CampfireHandler(
val forward = user.rotationVector
val origin = user.approximateStaffTipPosition
- val target = origin + forward * FLAME_MAX_DISTANCE
val relativeRight = user.getRotationVector(0f, MathHelper.wrapDegrees(user.yaw + 90f)).normalize()
val relativeUp = relativeRight.crossProduct(forward).normalize()
+ val rocketMode = DataComponentTypes.rocketMode in staffStack
- if (rocketModeComponentType.get() in staffStack) {
- user.addVelocity(forward * -properties.rocketThrust)
+ if (rocketMode) {
+ user.addVelocity(forward * -parameters.rocketThrust)
user.limitFallDistance()
}
- if (world.isClient) {
- throwFlameParticles(user, target, relativeRight, relativeUp)
- return
- }
-
- for (i in 0 until FLAMETHROWER_CONE_RAYS) {
- for (j in 0 until FLAMETHROWER_CONE_RAYS) {
- val xScale = i / (FLAMETHROWER_CONE_RAYS - 1.0) - 0.5
- val yScale = j / (FLAMETHROWER_CONE_RAYS - 1.0) - 0.5
-
- val offsetTarget = target +
- relativeRight * (xScale * FLAMETHROWER_CONE_END_WIDTH) +
- relativeUp * (yScale * FLAMETHROWER_CONE_END_HEIGHT)
-
- shootFire(
- FirePellet(
- user,
- world,
- origin,
- (offsetTarget - origin).normalize() * FLAME_SPEED,
- FLAME_MAX_AGE
- )
- )
- }
- }
- }
-
- @OnlyIn(Dist.CLIENT)
- fun throwFlameParticles(user: LivingEntity, target: Vec3d, relativeRight: Vec3d, relativeUp: Vec3d) {
- val random = Random.create()
- val particleManager = MinecraftClient.getInstance().particleManager
-
- val origin = user.approximateStaffTipPosition
-
- for (i in 0..flameParticleCount) {
- val xScale = random.nextDouble() - 0.5
- val yScale = random.nextDouble() - 0.5
-
- val offsetTarget = target +
- relativeRight * (xScale * FLAMETHROWER_CONE_END_WIDTH) +
- relativeUp * (yScale * FLAMETHROWER_CONE_END_HEIGHT)
- val targetDirection = (offsetTarget - origin).normalize()
- val particleSpeed = targetDirection.normalize() * FLAME_SPEED * (0.9 + Math.random() * 0.2)
+ if (world.isClient) return
+
+ world.spawnEntity(
+ CampfireFlameEntity(
+ world,
+ CampfireFlameEntity.ServerParameters(
+ origin,
+ forward * FLAMETHROWER_STEP_RESOLUTION.toDouble(),
+ relativeRight * FLAMETHROWER_CONE_END_WIDTH,
+ relativeUp * FLAMETHROWER_CONE_END_HEIGHT,
+ 16,
+ parameters.particleEffectSupplier.key!!,
+ FLAMETHROWER_CONE_RAY_RESOLUTION,
+ parameters.flammableBlockFireChance,
+ parameters.nonFlammableBlockFireChance,
+ parameters.flameFireTicks,
+ !rocketMode
+ ),
+ user
+ )
+ )
- particleManager.addParticle(
- particleEffectSupplier.get(),
- origin.x, origin.y, origin.z,
- particleSpeed.x, particleSpeed.y, particleSpeed.z
- )!!.maxAge = (0.25 * FLAME_MAX_AGE / (Math.random() * 0.8 + 0.2) - 0.05 * FLAME_MAX_AGE).toInt()
- }
+ staffStack.damage(1, user, LivingEntity.getSlotForHand(user.activeHand))
}
override fun onStoppedUsing(staffStack: ItemStack, world: World, user: LivingEntity, remainingUseTicks: Int) {
- staffStack.remove(rocketModeComponentType.get())
+ staffStack.remove(DataComponentTypes.rocketMode)
+ (user as? ServerPlayerEntity)?.incrementItemUseStat(staffStack.item)
+ (user as? ServerPlayerEntity)?.incrementStaffItemUseStat(staffStack.itemInStaff!!)
}
override fun finishUsing(staffStack: ItemStack, world: World, user: LivingEntity): ItemStack {
@@ -153,135 +110,39 @@ internal class CampfireHandler(
target: Entity,
hand: Hand
): ActionResult {
- target.setOnFireFor(properties.attackFireSeconds)
+ target.setOnFireFor(parameters.attackFireSeconds)
+
+ (attacker as? ServerPlayerEntity)?.incrementStaffItemUseStat(staffStack.itemInStaff!!)
+
return ActionResult.PASS
}
- data class Properties(
- val nonFlammableBlockFireChance: Double,
+ override fun allowComponentsUpdateAnimation(
+ oldStaffStack: ItemStack,
+ newStaffStack: ItemStack,
+ player: PlayerEntity,
+ hand: Hand
+ ) = false
+
+ override fun allowReequipAnimation(
+ oldStaffStack: ItemStack,
+ newStaffStack: ItemStack,
+ selectedSlotChanged: Boolean
+ ) = selectedSlotChanged
+
+ data class Parameters(
val flammableBlockFireChance: Double,
+ val nonFlammableBlockFireChance: Double,
val attackFireSeconds: Float,
val flameFireTicks: Int,
- val rocketThrust: Double
+ val rocketThrust: Double,
+ val particleEffectSupplier: RegistryObject
)
- private inner class FirePellet(
- private val shooter: LivingEntity,
- private val world: World,
- private var position: Vec3d,
- private val velocity: Vec3d,
- private val maxAge: Int
- ) {
- private var age = 0
-
- fun tick(attackedEntities: MutableSet): Boolean {
- val newPosition = position + velocity
- val blockHit = world.raycast(
- RaycastContext(
- position,
- newPosition,
- RaycastContext.ShapeType.COLLIDER,
- RaycastContext.FluidHandling.ANY,
- ShapeContext.absent()
- )
- )
- val entityHit = ProjectileUtil.raycast(
- shooter,
- position,
- newPosition,
- Box(position, newPosition),
- { true },
- velocity.lengthSquared()
- )
-
- val blockDistance = blockHit.pos.squaredDistanceTo(position)
- val entityDistance = entityHit?.pos?.squaredDistanceTo(position)
- if (entityHit != null && entityDistance!! < blockDistance) {
- tryBurnEntity(entityHit.entity, attackedEntities)
- } else if (blockHit.type == HitResult.Type.BLOCK) {
- tryCauseFire(blockHit)
- return false
- }
-
- position = newPosition
- return ++age < maxAge
- }
-
- private fun tryBurnEntity(target: Entity, ignoredEntities: MutableSet) {
- if (target in ignoredEntities) return
-
- if (target.isOnFire) {
- // Technically inFire, but use onFire, because it has a more fitting death message
- target.damage(target.damageSources.onFire(), properties.flameFireTicks.toFloat())
- }
- target.fireTicks = target.fireTicks.coerceAtLeast(0) + properties.flameFireTicks + 1
- (target as? LivingEntity)?.attacker = shooter
- ignoredEntities += target
- }
-
- private fun tryCauseFire(blockHit: BlockHitResult) {
- val firePos = blockHit.blockPos.offset(blockHit.side)
- val blockToLight = world.getBlockState(blockHit.blockPos)
- if (CampfireBlock.canBeLit(blockToLight) ||
- CandleBlock.canBeLit(blockToLight) ||
- CandleCakeBlock.canBeLit(blockToLight)
- ) {
- world.setBlockState(blockHit.blockPos, blockToLight.with(LIT, true), Block.NOTIFY_ALL_AND_REDRAW)
- world.emitGameEvent(shooter, GameEvent.BLOCK_CHANGE, firePos)
- return
- }
-
- if (!world.canSetBlock(firePos)) return
- if (!AbstractFireBlock.canPlaceAt(world, firePos, shooter.horizontalFacing)) return
-
- var fireCauseChance =
- if (world.getBlockState(blockHit.blockPos).isBurnable) properties.flammableBlockFireChance
- else properties.nonFlammableBlockFireChance
- fireCauseChance /= FLAMETHROWER_CONE_RAYS_TOTAL
- if (Math.random() >= fireCauseChance) return
-
- world.setBlockState(firePos, AbstractFireBlock.getState(world, firePos), Block.NOTIFY_ALL_AND_REDRAW)
- world.emitGameEvent(shooter, GameEvent.BLOCK_PLACE, firePos)
- }
- }
-
private companion object {
- private const val FLAME_SPEED = 1.0
- private const val FLAME_MAX_AGE = 16
- private const val FLAME_MAX_DISTANCE = FLAME_SPEED * FLAME_MAX_AGE
-
- private const val FLAMETHROWER_CONE_END_WIDTH = 0.25 * FLAME_MAX_DISTANCE
- private const val FLAMETHROWER_CONE_END_HEIGHT = 0.25 * FLAME_MAX_DISTANCE
- private const val FLAMETHROWER_CONE_RAYS = 16
- private const val FLAMETHROWER_CONE_RAYS_TOTAL = FLAMETHROWER_CONE_RAYS * FLAMETHROWER_CONE_RAYS
-
- private val flameParticleCount: Int
- @OnlyIn(Dist.CLIENT)
- get() = when (MinecraftClient.getInstance().options.graphicsMode.value!!) {
- GraphicsMode.FAST -> 4 * 4
- GraphicsMode.FANCY -> 8 * 8
- GraphicsMode.FABULOUS -> 16 * 16
- }
- private val firePellets = mutableListOf()
-
- init {
- FORGE_BUS.addListener(::tick)
- }
-
- private fun shootFire(firePellet: FirePellet) {
- firePellets += firePellet
- }
-
- fun tick(event: TickEvent.ServerTickEvent.Pre) {
- if (!event.server.tickManager.shouldTick()) return
- val damagedEntities = mutableSetOf()
- val iterator = firePellets.iterator()
- while (iterator.hasNext()) {
- val pellet = iterator.next()
- if (!pellet.tick(damagedEntities)) {
- iterator.remove()
- }
- }
- }
+ private const val FLAMETHROWER_STEP_RESOLUTION = 16
+ private const val FLAMETHROWER_CONE_END_WIDTH = 0.25 * FLAMETHROWER_STEP_RESOLUTION
+ private const val FLAMETHROWER_CONE_END_HEIGHT = 0.25 * FLAMETHROWER_STEP_RESOLUTION
+ private const val FLAMETHROWER_CONE_RAY_RESOLUTION = 16
}
}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/DiamondBlockHandler.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/DiamondBlockHandler.kt
index 22b55545e..5dac1fa5d 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/DiamondBlockHandler.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/DiamondBlockHandler.kt
@@ -22,27 +22,19 @@ import net.minecraft.block.Blocks
import net.minecraft.component.type.AttributeModifierSlot
import net.minecraft.entity.LivingEntity
import net.minecraft.entity.attribute.EntityAttributes
-import net.minecraft.entity.player.PlayerEntity
-import net.minecraft.item.ItemStack
-import net.minecraft.server.world.ServerWorld
import net.minecraft.util.ActionResult
-import net.minecraft.util.Hand
import net.minecraft.util.math.BlockPos
-import net.minecraft.util.math.Direction
import net.minecraft.world.World
import opekope2.avm_staff.api.staff.StaffAttributeModifiersComponentBuilder
-import opekope2.avm_staff.api.staff.StaffHandler
import opekope2.avm_staff.util.attackDamage
import opekope2.avm_staff.util.attackSpeed
import opekope2.avm_staff.util.cameraUp
+import opekope2.avm_staff.util.destruction.BlockDestructionPredicate
import opekope2.avm_staff.util.destruction.DiamondBlockStaffShapePredicate
+import opekope2.avm_staff.util.destruction.IShapedBlockDestructionPredicate
import opekope2.avm_staff.util.destruction.MaxHardnessPredicate
-import opekope2.avm_staff.util.destruction.destroyBox
-import opekope2.avm_staff.util.dropcollector.ChunkedBlockDropCollector
-import opekope2.avm_staff.util.dropcollector.NoOpBlockDropCollector
-import opekope2.avm_staff.util.isAttackCoolingDown
-class DiamondBlockHandler : StaffHandler() {
+internal class DiamondBlockHandler : AbstractMassDestructiveStaffHandler() {
override val attributeModifiers = StaffAttributeModifiersComponentBuilder()
.add(EntityAttributes.GENERIC_ATTACK_DAMAGE, attackDamage(18.0), AttributeModifierSlot.MAINHAND)
.add(EntityAttributes.GENERIC_ATTACK_SPEED, attackSpeed(1.0), AttributeModifierSlot.MAINHAND)
@@ -50,41 +42,20 @@ class DiamondBlockHandler : StaffHandler() {
.addDefault(EntityAttributes.PLAYER_BLOCK_INTERACTION_RANGE)
.build()
- override fun attackBlock(
- staffStack: ItemStack,
+ override fun createBlockDestructionShapePredicate(
world: World,
attacker: LivingEntity,
- target: BlockPos,
- side: Direction,
- hand: Hand
- ): ActionResult {
- if (world.isClient) return ActionResult.PASS
- if (attacker is PlayerEntity && attacker.isAttackCoolingDown) return ActionResult.PASS
- require(world is ServerWorld)
-
+ target: BlockPos
+ ): IShapedBlockDestructionPredicate {
val forwardVector = attacker.facing.vector
val upVector = attacker.cameraUp.vector
- val shapePredicate = DiamondBlockStaffShapePredicate(target, forwardVector, upVector, world.random)
- val dropCollector =
- if (attacker is PlayerEntity && attacker.abilities.creativeMode) NoOpBlockDropCollector()
- else ChunkedBlockDropCollector(shapePredicate.volume, MAX_CHUNK_SIZE)
-
- destroyBox(
- world,
- shapePredicate.volume,
- dropCollector,
- attacker,
- staffStack,
- MAX_DIAMOND_HARDNESS.and(shapePredicate)
- )
- dropCollector.dropAll(world)
-
- // "Mismatch in destroy block pos" in server logs if I interrupt on server but not on client side. Nothing bad should happen, right?
- return ActionResult.PASS
+ return DiamondBlockStaffShapePredicate(target, forwardVector, upVector, world.random)
}
+ override fun createDestructionPredicate(shapePredicate: IShapedBlockDestructionPredicate): BlockDestructionPredicate =
+ MAX_DIAMOND_HARDNESS.and(shapePredicate)
+
private companion object {
- private const val MAX_CHUNK_SIZE = 3
private val MAX_DIAMOND_HARDNESS = MaxHardnessPredicate(Blocks.DIAMOND_BLOCK)
}
}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/FurnaceHandler.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/FurnaceHandler.kt
index 9863a3117..8a0d2cc12 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/FurnaceHandler.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/FurnaceHandler.kt
@@ -18,13 +18,6 @@
package opekope2.avm_staff.internal.staff.handler
-import net.minecraft.block.AbstractFurnaceBlock
-import net.minecraft.block.Block
-import net.minecraft.block.BlockState
-import net.minecraft.client.MinecraftClient
-import net.minecraft.client.render.VertexConsumerProvider
-import net.minecraft.client.render.model.json.ModelTransformationMode
-import net.minecraft.client.util.math.MatrixStack
import net.minecraft.component.type.AttributeModifierSlot
import net.minecraft.entity.EntityType
import net.minecraft.entity.ItemEntity
@@ -36,6 +29,7 @@ import net.minecraft.particle.ParticleTypes
import net.minecraft.recipe.AbstractCookingRecipe
import net.minecraft.recipe.RecipeType
import net.minecraft.recipe.input.SingleStackRecipeInput
+import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.server.world.ServerWorld
import net.minecraft.sound.SoundCategory
import net.minecraft.sound.SoundEvent
@@ -47,11 +41,9 @@ import net.minecraft.world.World
import net.minecraftforge.api.distmarker.Dist
import net.minecraftforge.api.distmarker.OnlyIn
import opekope2.avm_staff.api.component.StaffFurnaceDataComponent
-import opekope2.avm_staff.api.item.renderer.BlockStateStaffItemRenderer
-import opekope2.avm_staff.api.item.renderer.IStaffItemRenderer
import opekope2.avm_staff.api.staff.StaffAttributeModifiersComponentBuilder
import opekope2.avm_staff.api.staff.StaffHandler
-import opekope2.avm_staff.api.staffFurnaceDataComponentType
+import opekope2.avm_staff.content.DataComponentTypes
import opekope2.avm_staff.mixin.IAbstractFurnaceBlockEntityAccessor
import opekope2.avm_staff.util.*
import kotlin.jvm.optionals.getOrNull
@@ -60,7 +52,7 @@ internal class FurnaceHandler(
private val recipeType: RecipeType,
private val smeltSound: SoundEvent
) : StaffHandler() {
- override val maxUseTime = 72000
+ override fun getMaxUseTime(staffStack: ItemStack, world: World, user: LivingEntity) = 72000
override val attributeModifiers = StaffAttributeModifiersComponentBuilder()
.add(EntityAttributes.GENERIC_ATTACK_DAMAGE, attackDamage(10.0), AttributeModifierSlot.MAINHAND)
@@ -72,13 +64,13 @@ internal class FurnaceHandler(
override fun use(
staffStack: ItemStack,
world: World,
- user: PlayerEntity,
+ user: LivingEntity,
hand: Hand
): TypedActionResult {
- staffStack[staffFurnaceDataComponentType.get()] = StaffFurnaceDataComponent(0)
+ staffStack[DataComponentTypes.furnaceData] = StaffFurnaceDataComponent(0)
user.setCurrentHand(hand)
- return TypedActionResult.consume(staffStack)
+ return TypedActionResult.pass(staffStack)
}
override fun usageTick(staffStack: ItemStack, world: World, user: LivingEntity, remainingUseTicks: Int) {
@@ -92,11 +84,11 @@ internal class FurnaceHandler(
return
}
- val furnaceData = staffStack[staffFurnaceDataComponentType.get()]!!
- furnaceData.serverBurnTicks++
+ val furnaceData = staffStack[DataComponentTypes.furnaceData]!!
+ furnaceData.burnTicks++
val stackToSmelt = itemToSmelt?.stack ?: return
- if (furnaceData.serverBurnTicks < stackToSmelt.count) return
+ if (furnaceData.burnTicks < stackToSmelt.count) return
val recipeInput = SingleStackRecipeInput(itemToSmelt.stack)
val recipe = world.recipeManager.getFirstMatch(recipeType, recipeInput, world).getOrNull()?.value ?: return
@@ -109,7 +101,10 @@ internal class FurnaceHandler(
)
itemToSmelt.discard()
- furnaceData.serverBurnTicks -= stackToSmelt.count
+ furnaceData.burnTicks -= stackToSmelt.count
+
+ staffStack.damage(stackToSmelt.count, user, LivingEntity.getSlotForHand(user.activeHand))
+ (user as? ServerPlayerEntity)?.incrementStaffItemUseStat(staffStack.itemInStaff!!)
}
private fun findItemToSmelt(world: World, smeltingPosition: Vec3d): ItemEntity? {
@@ -129,13 +124,13 @@ internal class FurnaceHandler(
val ry = Math.random() * 0.5
val rz = Math.random() * 0.25 - 0.25 / 2
- val particleManager = MinecraftClient.getInstance().particleManager
particleManager.addParticle(ParticleTypes.FLAME, x + rx, y + ry, z + rz, 0.0, 0.0, 0.0)
particleManager.addParticle(ParticleTypes.SMOKE, x + rx, y + ry, z + rz, 0.0, 0.0, 0.0)
}
override fun onStoppedUsing(staffStack: ItemStack, world: World, user: LivingEntity, remainingUseTicks: Int) {
- staffStack.remove(staffFurnaceDataComponentType.get())
+ staffStack.remove(DataComponentTypes.furnaceData)
+ (user as? ServerPlayerEntity)?.incrementItemUseStat(staffStack.item)
}
override fun finishUsing(staffStack: ItemStack, world: World, user: LivingEntity): ItemStack {
@@ -143,31 +138,18 @@ internal class FurnaceHandler(
return staffStack
}
- @OnlyIn(Dist.CLIENT)
- class FurnaceStaffItemRenderer(unlitState: BlockState, litState: BlockState) : IStaffItemRenderer {
- constructor(furnaceBlock: Block) : this(
- furnaceBlock.defaultState,
- furnaceBlock.defaultState.with(AbstractFurnaceBlock.LIT, true)
- )
+ override fun allowComponentsUpdateAnimation(
+ oldStaffStack: ItemStack,
+ newStaffStack: ItemStack,
+ player: PlayerEntity,
+ hand: Hand
+ ) = false
- private val unlitRenderer = BlockStateStaffItemRenderer(unlitState)
- private val litRenderer = BlockStateStaffItemRenderer(litState)
-
- override fun renderItemInStaff(
- staffStack: ItemStack,
- mode: ModelTransformationMode,
- matrices: MatrixStack,
- vertexConsumers: VertexConsumerProvider,
- light: Int,
- overlay: Int
- ) {
- val renderer =
- if (staffFurnaceDataComponentType.get() in staffStack) litRenderer
- else unlitRenderer
-
- renderer.renderItemInStaff(staffStack, mode, matrices, vertexConsumers, light, overlay)
- }
- }
+ override fun allowReequipAnimation(
+ oldStaffStack: ItemStack,
+ newStaffStack: ItemStack,
+ selectedSlotChanged: Boolean
+ ) = selectedSlotChanged
private companion object {
private val ITEM_DIMENSIONS = EntityType.ITEM.dimensions
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/GoldBlockHandler.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/GoldBlockHandler.kt
index 4562eea52..a3da3ab8e 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/GoldBlockHandler.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/GoldBlockHandler.kt
@@ -22,27 +22,20 @@ import net.minecraft.block.Blocks
import net.minecraft.component.type.AttributeModifierSlot
import net.minecraft.entity.LivingEntity
import net.minecraft.entity.attribute.EntityAttributes
-import net.minecraft.entity.player.PlayerEntity
-import net.minecraft.item.ItemStack
-import net.minecraft.server.world.ServerWorld
import net.minecraft.util.ActionResult
-import net.minecraft.util.Hand
import net.minecraft.util.math.BlockPos
-import net.minecraft.util.math.Direction
import net.minecraft.world.World
import opekope2.avm_staff.api.staff.StaffAttributeModifiersComponentBuilder
-import opekope2.avm_staff.api.staff.StaffHandler
import opekope2.avm_staff.util.attackDamage
import opekope2.avm_staff.util.attackSpeed
import opekope2.avm_staff.util.cameraUp
+import opekope2.avm_staff.util.destruction.BlockDestructionPredicate
import opekope2.avm_staff.util.destruction.GoldBlockStaffShapePredicate
+import opekope2.avm_staff.util.destruction.IShapedBlockDestructionPredicate
import opekope2.avm_staff.util.destruction.MaxHardnessPredicate
-import opekope2.avm_staff.util.destruction.destroyBox
-import opekope2.avm_staff.util.dropcollector.NoOpBlockDropCollector
import opekope2.avm_staff.util.dropcollector.VanillaBlockDropCollector
-import opekope2.avm_staff.util.isAttackCoolingDown
-class GoldBlockHandler : StaffHandler() {
+internal class GoldBlockHandler : AbstractMassDestructiveStaffHandler() {
override val attributeModifiers = StaffAttributeModifiersComponentBuilder()
.add(EntityAttributes.GENERIC_ATTACK_DAMAGE, attackDamage(14.0), AttributeModifierSlot.MAINHAND)
.add(EntityAttributes.GENERIC_ATTACK_SPEED, attackSpeed(1.0), AttributeModifierSlot.MAINHAND)
@@ -50,38 +43,21 @@ class GoldBlockHandler : StaffHandler() {
.addDefault(EntityAttributes.PLAYER_BLOCK_INTERACTION_RANGE)
.build()
- override fun attackBlock(
- staffStack: ItemStack,
+ override fun createBlockDestructionShapePredicate(
world: World,
attacker: LivingEntity,
- target: BlockPos,
- side: Direction,
- hand: Hand
- ): ActionResult {
- if (world.isClient) return ActionResult.PASS
- if (attacker is PlayerEntity && attacker.isAttackCoolingDown) return ActionResult.PASS
- require(world is ServerWorld)
-
+ target: BlockPos
+ ): IShapedBlockDestructionPredicate {
val forwardVector = attacker.facing.vector
val upVector = attacker.cameraUp.vector
- val shapePredicate = GoldBlockStaffShapePredicate(target, forwardVector, upVector)
- val dropCollector =
- if (attacker is PlayerEntity && attacker.abilities.creativeMode) NoOpBlockDropCollector()
- else VanillaBlockDropCollector()
+ return GoldBlockStaffShapePredicate(target, forwardVector, upVector)
+ }
- destroyBox(
- world,
- shapePredicate.volume,
- dropCollector,
- attacker,
- staffStack,
- MAX_OBSIDIAN_HARDNESS.and(shapePredicate)
- )
- dropCollector.dropAll(world)
+ override fun createDestructionPredicate(shapePredicate: IShapedBlockDestructionPredicate): BlockDestructionPredicate =
+ MAX_OBSIDIAN_HARDNESS.and(shapePredicate)
- // "Mismatch in destroy block pos" in server logs if I interrupt on server but not on client side. Nothing bad should happen, right?
- return ActionResult.PASS
- }
+ override fun createBlockDropCollector(shapePredicate: IShapedBlockDestructionPredicate) =
+ VanillaBlockDropCollector()
private companion object {
private val MAX_OBSIDIAN_HARDNESS = MaxHardnessPredicate(Blocks.OBSIDIAN)
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/LightningRodHandler.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/LightningRodHandler.kt
index de26045af..82cb644d1 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/LightningRodHandler.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/LightningRodHandler.kt
@@ -18,10 +18,6 @@
package opekope2.avm_staff.internal.staff.handler
-import net.minecraft.block.Blocks
-import net.minecraft.client.render.VertexConsumerProvider
-import net.minecraft.client.render.model.json.ModelTransformationMode
-import net.minecraft.client.util.math.MatrixStack
import net.minecraft.component.type.AttributeModifierSlot
import net.minecraft.entity.Entity
import net.minecraft.entity.EntityType
@@ -30,21 +26,19 @@ import net.minecraft.entity.attribute.EntityAttributes
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.item.Item
import net.minecraft.item.ItemStack
+import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.ActionResult
import net.minecraft.util.Hand
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.util.math.Vec3d
import net.minecraft.world.World
-import net.minecraftforge.api.distmarker.Dist
-import net.minecraftforge.api.distmarker.OnlyIn
-import opekope2.avm_staff.api.item.renderer.BlockStateStaffItemRenderer
-import opekope2.avm_staff.api.item.renderer.IStaffItemRenderer
import opekope2.avm_staff.api.staff.StaffAttributeModifiersComponentBuilder
import opekope2.avm_staff.api.staff.StaffHandler
+import opekope2.avm_staff.util.incrementStaffItemUseStat
import opekope2.avm_staff.util.interactionRange
import opekope2.avm_staff.util.isItemCoolingDown
-import opekope2.avm_staff.util.push
+import opekope2.avm_staff.util.itemInStaff
internal class LightningRodHandler : StaffHandler() {
override val attributeModifiers = StaffAttributeModifiersComponentBuilder()
@@ -63,7 +57,14 @@ internal class LightningRodHandler : StaffHandler() {
hand: Hand
): ActionResult {
val lightningPos = Vec3d.add(target.offset(side), 0.5, 0.0, 0.5)
- return tryStrike(staffStack, world, user, lightningPos)
+ val result = tryStrike(staffStack, world, user, lightningPos)
+
+ if (result.isAccepted) staffStack.damage(1, user, LivingEntity.getSlotForHand(hand))
+ if (result.shouldIncrementStat()) {
+ (user as? ServerPlayerEntity)?.incrementStaffItemUseStat(staffStack.itemInStaff!!)
+ }
+
+ return result
}
override fun useOnEntity(
@@ -73,7 +74,14 @@ internal class LightningRodHandler : StaffHandler() {
target: LivingEntity,
hand: Hand
): ActionResult {
- return tryStrike(staffStack, world, user, target.pos)
+ val result = tryStrike(staffStack, world, user, target.pos)
+
+ if (result.isAccepted) staffStack.damage(1, user, LivingEntity.getSlotForHand(hand))
+ if (result.shouldIncrementStat()) {
+ (user as? ServerPlayerEntity)?.incrementStaffItemUseStat(staffStack.itemInStaff!!)
+ }
+
+ return result
}
override fun attackEntity(
@@ -113,25 +121,4 @@ internal class LightningRodHandler : StaffHandler() {
return true
}
-
- @OnlyIn(Dist.CLIENT)
- class LightningRodStaffItemRenderer : IStaffItemRenderer {
- private val lightningRodRenderer = BlockStateStaffItemRenderer(Blocks.LIGHTNING_ROD.defaultState)
-
- override fun renderItemInStaff(
- staffStack: ItemStack,
- mode: ModelTransformationMode,
- matrices: MatrixStack,
- vertexConsumers: VertexConsumerProvider,
- light: Int,
- overlay: Int
- ) {
- matrices.push {
- if (mode != ModelTransformationMode.GUI && mode != ModelTransformationMode.FIXED) {
- translate(0f, 22f / 16f, 0f)
- }
- lightningRodRenderer.renderItemInStaff(staffStack, mode, matrices, vertexConsumers, light, overlay)
- }
- }
- }
}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/MagmaBlockHandler.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/MagmaBlockHandler.kt
index 96c597aca..41d66e445 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/MagmaBlockHandler.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/MagmaBlockHandler.kt
@@ -23,21 +23,20 @@ import net.minecraft.entity.Entity
import net.minecraft.entity.EntityType
import net.minecraft.entity.LivingEntity
import net.minecraft.entity.attribute.EntityAttributes
-import net.minecraft.entity.player.PlayerEntity
+import net.minecraft.entity.projectile.AbstractFireballEntity
+import net.minecraft.entity.projectile.FireballEntity
import net.minecraft.entity.projectile.SmallFireballEntity
import net.minecraft.item.ItemStack
+import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.ActionResult
import net.minecraft.util.Hand
-import net.minecraft.util.TypedActionResult
import net.minecraft.world.World
import net.minecraft.world.WorldEvents
import opekope2.avm_staff.api.staff.StaffAttributeModifiersComponentBuilder
-import opekope2.avm_staff.api.staff.StaffHandler
+import opekope2.avm_staff.content.Enchantments
import opekope2.avm_staff.util.*
-internal class MagmaBlockHandler : StaffHandler() {
- override val maxUseTime = 72000
-
+internal class MagmaBlockHandler : AbstractProjectileShootingStaffHandler() {
override val attributeModifiers = StaffAttributeModifiersComponentBuilder()
.add(EntityAttributes.GENERIC_ATTACK_DAMAGE, attackDamage(10.0), AttributeModifierSlot.MAINHAND)
.add(EntityAttributes.GENERIC_ATTACK_SPEED, attackSpeed(1.25), AttributeModifierSlot.MAINHAND)
@@ -45,25 +44,43 @@ internal class MagmaBlockHandler : StaffHandler() {
.addDefault(EntityAttributes.PLAYER_BLOCK_INTERACTION_RANGE)
.build()
- override fun use(
+ override fun getFireRateDenominator(rapidFireLevel: Int) = if (rapidFireLevel >= 2) 2 else 4
+
+ override fun tryShootProjectile(
staffStack: ItemStack,
world: World,
- user: PlayerEntity,
- hand: Hand
- ): TypedActionResult {
- user.setCurrentHand(hand)
- return TypedActionResult.consume(staffStack)
- }
+ shooter: LivingEntity,
+ reason: ProjectileShootReason
+ ): Boolean {
+ if (!super.tryShootProjectile(staffStack, world, shooter, reason)) return false
- override fun usageTick(staffStack: ItemStack, world: World, user: LivingEntity, remainingUseTicks: Int) {
- if ((remainingUseTicks and 1) == 0) {
- tryShootFireball(world, user)
+ return if (reason.isAttack && staffStack.isEnchantedWith(Enchantments.POWER_CHARGE, world.registryManager)) {
+ shootFireball(world, shooter, WorldEvents.GHAST_SHOOTS, EntityType.FIREBALL) {
+ FireballEntity(world, shooter, shooter.rotationVector, 1)
+ }
+ } else {
+ shootFireball(world, shooter, WorldEvents.BLAZE_SHOOTS, EntityType.SMALL_FIREBALL) {
+ SmallFireballEntity(world, shooter, shooter.rotationVector)
+ }
}
}
- override fun attack(staffStack: ItemStack, world: World, attacker: LivingEntity, hand: Hand) {
- tryShootFireball(world, attacker)
- (attacker as? PlayerEntity)?.resetLastAttackedTicks()
+ private inline fun shootFireball(
+ world: World,
+ shooter: LivingEntity,
+ soundWorldEvent: Int,
+ fireballType: EntityType,
+ fireballFactory: () -> T
+ ): Boolean {
+ val spawnPos = fireballType.getSpawnPosition(world, shooter.approximateStaffTipPosition) ?: return false
+ val fireball = fireballFactory()
+ fireball.setPosition(spawnPos)
+ fireball.owner = shooter
+
+ world.spawnEntity(fireball)
+ world.syncWorldEvent(soundWorldEvent, shooter.blockPos, 0)
+
+ return true
}
override fun attackEntity(
@@ -74,19 +91,9 @@ internal class MagmaBlockHandler : StaffHandler() {
hand: Hand
): ActionResult {
target.setOnFireFor(8f)
- return ActionResult.PASS
- }
- private fun tryShootFireball(world: World, shooter: LivingEntity) {
- if (world.isClient) return
- if (!shooter.canUseStaff) return
- if (shooter is PlayerEntity && shooter.isAttackCoolingDown) return
+ (attacker as? ServerPlayerEntity)?.incrementStaffItemUseStat(staffStack.itemInStaff!!)
- val spawnPos = EntityType.SMALL_FIREBALL.getSpawnPosition(world, shooter.approximateStaffTipPosition) ?: return
-
- world.spawnEntity(SmallFireballEntity(world, shooter, shooter.rotationVector).apply {
- setPosition(spawnPos)
- })
- world.syncWorldEvent(WorldEvents.BLAZE_SHOOTS, shooter.blockPos, 0)
+ return ActionResult.PASS
}
}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/NetheriteBlockHandler.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/NetheriteBlockHandler.kt
index a6498ac95..f50846cb1 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/NetheriteBlockHandler.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/NetheriteBlockHandler.kt
@@ -25,26 +25,22 @@ import net.minecraft.entity.LivingEntity
import net.minecraft.entity.attribute.EntityAttributes
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.item.ItemStack
+import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.server.world.ServerWorld
import net.minecraft.util.ActionResult
import net.minecraft.util.Hand
import net.minecraft.util.math.BlockPos
-import net.minecraft.util.math.Direction
import net.minecraft.util.math.MathHelper
import net.minecraft.util.math.Vec2f
import net.minecraft.world.World
import net.minecraft.world.WorldEvents
import opekope2.avm_staff.api.staff.StaffAttributeModifiersComponentBuilder
-import opekope2.avm_staff.api.staff.StaffHandler
import opekope2.avm_staff.util.*
-import opekope2.avm_staff.util.destruction.InTruncatedPyramidPredicate
-import opekope2.avm_staff.util.destruction.MaxHardnessPredicate
-import opekope2.avm_staff.util.destruction.NetheriteBlockStaffShapePredicate
-import opekope2.avm_staff.util.destruction.destroyBox
+import opekope2.avm_staff.util.destruction.*
import opekope2.avm_staff.util.dropcollector.ChunkedBlockDropCollector
import opekope2.avm_staff.util.dropcollector.NoOpBlockDropCollector
-class NetheriteBlockHandler : StaffHandler() {
+internal class NetheriteBlockHandler : AbstractMassDestructiveStaffHandler() {
override val attributeModifiers = StaffAttributeModifiersComponentBuilder()
.add(EntityAttributes.GENERIC_ATTACK_DAMAGE, attackDamage(20.0), AttributeModifierSlot.MAINHAND)
.add(EntityAttributes.GENERIC_ATTACK_SPEED, attackSpeed(1.0), AttributeModifierSlot.MAINHAND)
@@ -99,44 +95,25 @@ class NetheriteBlockHandler : StaffHandler() {
world.syncWorldEvent(WorldEvents.SMASH_ATTACK, target.steppingPos, 750)
+ (attacker as? ServerPlayerEntity)?.incrementStaffItemUseStat(staffStack.itemInStaff!!)
+
return ActionResult.PASS
}
- override fun attackBlock(
- staffStack: ItemStack,
+ override fun createBlockDestructionShapePredicate(
world: World,
attacker: LivingEntity,
- target: BlockPos,
- side: Direction,
- hand: Hand
- ): ActionResult {
- if (world.isClient) return ActionResult.PASS
- if (attacker is PlayerEntity && attacker.isAttackCoolingDown) return ActionResult.PASS
- require(world is ServerWorld)
-
+ target: BlockPos
+ ): IShapedBlockDestructionPredicate {
val forwardVector = attacker.facing.vector
val upVector = attacker.cameraUp.vector
- val shapePredicate = NetheriteBlockStaffShapePredicate(target, forwardVector, upVector)
- val dropCollector =
- if (attacker is PlayerEntity && attacker.abilities.creativeMode) NoOpBlockDropCollector()
- else ChunkedBlockDropCollector(shapePredicate.volume, MAX_CHUNK_SIZE)
-
- destroyBox(
- world,
- shapePredicate.volume,
- dropCollector,
- attacker,
- staffStack,
- MAX_NETHERITE_HARDNESS.and(shapePredicate)
- )
- dropCollector.dropAll(world)
-
- // "Mismatch in destroy block pos" in server logs if I interrupt on server but not on client side. Nothing bad should happen, right?
- return ActionResult.PASS
+ return NetheriteBlockStaffShapePredicate(target, forwardVector, upVector)
}
+ override fun createDestructionPredicate(shapePredicate: IShapedBlockDestructionPredicate): BlockDestructionPredicate =
+ MAX_NETHERITE_HARDNESS.and(shapePredicate)
+
private companion object {
- private const val MAX_CHUNK_SIZE = 3
private val MAX_NETHERITE_HARDNESS = MaxHardnessPredicate(Blocks.NETHERITE_BLOCK)
}
}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/SnowBlockHandler.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/SnowBlockHandler.kt
index 97756fc75..0734a89fe 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/SnowBlockHandler.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/SnowBlockHandler.kt
@@ -20,58 +20,45 @@ package opekope2.avm_staff.internal.staff.handler
import net.minecraft.entity.EntityType
import net.minecraft.entity.LivingEntity
-import net.minecraft.entity.player.PlayerEntity
import net.minecraft.entity.projectile.thrown.SnowballEntity
import net.minecraft.item.ItemStack
import net.minecraft.sound.SoundEvents
-import net.minecraft.util.Hand
-import net.minecraft.util.TypedActionResult
import net.minecraft.world.World
-import opekope2.avm_staff.api.staff.StaffHandler
import opekope2.avm_staff.util.*
-internal class SnowBlockHandler : StaffHandler() {
- override val maxUseTime = 72000
+internal class SnowBlockHandler : AbstractProjectileShootingStaffHandler() {
+ private val ProjectileShootReason.velocity: Float
+ get() = when (this) {
+ ProjectileShootReason.ATTACK -> 1.5f
+ ProjectileShootReason.USE -> 3f
+ }
- override fun use(
+ override fun getFireRateDenominator(rapidFireLevel: Int) = if (rapidFireLevel >= 2) 1 else 2
+
+ override fun tryShootProjectile(
staffStack: ItemStack,
world: World,
- user: PlayerEntity,
- hand: Hand
- ): TypedActionResult {
- user.setCurrentHand(hand)
- return TypedActionResult.consume(staffStack)
- }
-
- override fun usageTick(staffStack: ItemStack, world: World, user: LivingEntity, remainingUseTicks: Int) {
- tryThrowSnowball(world, user)
- }
+ shooter: LivingEntity,
+ reason: ProjectileShootReason
+ ): Boolean {
+ if (!super.tryShootProjectile(staffStack, world, shooter, reason)) return false
- override fun attack(staffStack: ItemStack, world: World, attacker: LivingEntity, hand: Hand) {
- tryThrowSnowball(world, attacker)
- (attacker as? PlayerEntity)?.resetLastAttackedTicks()
- }
-
- private fun tryThrowSnowball(world: World, thrower: LivingEntity) {
- if (world.isClient) return
- if (!thrower.canUseStaff) return
- if (thrower is PlayerEntity && thrower.isAttackCoolingDown) return
-
- val spawnPos = EntityType.SNOWBALL.getSpawnPosition(world, thrower.approximateStaffTipPosition) ?: return
+ val spawnPos = EntityType.SNOWBALL.getSpawnPosition(world, shooter.approximateStaffTipPosition) ?: return false
val (x, y, z) = spawnPos
world.spawnEntity(SnowballEntity(world, x, y, z).apply {
- owner = thrower
- // TODO speed
- setVelocity(thrower, thrower.pitch, thrower.yaw, 0f, 4f, 1f)
+ owner = shooter
+ setVelocity(shooter, shooter.pitch, shooter.yaw, 0f, reason.velocity, 1f)
})
world.playSound(
null,
- thrower.blockPos,
+ shooter.blockPos,
SoundEvents.ENTITY_SNOWBALL_THROW,
- thrower.soundCategory,
- 0.5f,
- 0.4f / (world.getRandom().nextFloat() * 0.4f + 0.8f)
+ shooter.soundCategory,
+ .5f,
+ .4f / (world.random.nextFloat() * .4f + .8f)
)
+
+ return true
}
}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/TntHandler.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/TntHandler.kt
index c65c9c870..9677c6edb 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/TntHandler.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/TntHandler.kt
@@ -21,32 +21,105 @@ package opekope2.avm_staff.internal.staff.handler
import net.minecraft.entity.LivingEntity
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.item.ItemStack
+import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.sound.SoundCategory
import net.minecraft.sound.SoundEvents
import net.minecraft.util.Hand
+import net.minecraft.util.TypedActionResult
import net.minecraft.world.World
import net.minecraft.world.event.GameEvent
+import opekope2.avm_staff.api.component.StaffTntDataComponent
import opekope2.avm_staff.api.entity.ImpactTntEntity
-import opekope2.avm_staff.api.impactTntEntityType
import opekope2.avm_staff.api.staff.StaffHandler
+import opekope2.avm_staff.content.DataComponentTypes
+import opekope2.avm_staff.content.Enchantments
+import opekope2.avm_staff.content.EntityTypes
import opekope2.avm_staff.util.*
internal class TntHandler : StaffHandler() {
+ override fun getMaxUseTime(staffStack: ItemStack, world: World, user: LivingEntity) = 4 * 20
+
+ override fun use(
+ staffStack: ItemStack,
+ world: World,
+ user: LivingEntity,
+ hand: Hand
+ ): TypedActionResult {
+ if (!staffStack.isEnchantedWith(Enchantments.DISTANT_DETONATION, world.registryManager))
+ return TypedActionResult.pass(staffStack)
+ val tnt = tryShootTnt(world, user) ?: return TypedActionResult.pass(staffStack)
+
+ staffStack[DataComponentTypes.tntData] = StaffTntDataComponent(tnt)
+
+ user.setCurrentHand(hand)
+ return TypedActionResult.consume(staffStack)
+ }
+
+ override fun usageTick(staffStack: ItemStack, world: World, user: LivingEntity, remainingUseTicks: Int) {
+ if (!world.isClient && staffStack[DataComponentTypes.tntData]?.tnt?.isRemoved == true) {
+ user.stopUsingItem() // TODO reset last attacked ticks on client
+ }
+ }
+
+ override fun onStoppedUsing(staffStack: ItemStack, world: World, user: LivingEntity, remainingUseTicks: Int) {
+ val tntData = staffStack.remove(DataComponentTypes.tntData)
+ if (!world.isClient && tntData?.tnt?.isAlive == true) {
+ tntData.tnt.explodeLater()
+ }
+
+ (user as? ServerPlayerEntity)?.incrementItemUseStat(staffStack.item)
+ (user as? ServerPlayerEntity)?.incrementStaffItemUseStat(staffStack.itemInStaff!!)
+ (user as? PlayerEntity)?.resetLastAttackedTicks()
+ }
+
+ override fun finishUsing(staffStack: ItemStack, world: World, user: LivingEntity): ItemStack {
+ onStoppedUsing(staffStack, world, user, 0)
+ return staffStack
+ }
+
override fun attack(staffStack: ItemStack, world: World, attacker: LivingEntity, hand: Hand) {
- tryShootTnt(world, attacker)
+ if (tryShootTnt(world, attacker) != null) {
+ staffStack.damage(1, attacker, LivingEntity.getSlotForHand(hand))
+ (attacker as? ServerPlayerEntity)?.incrementItemUseStat(staffStack.item)
+ (attacker as? ServerPlayerEntity)?.incrementStaffItemUseStat(staffStack.itemInStaff!!)
+ }
(attacker as? PlayerEntity)?.resetLastAttackedTicks()
}
- private fun tryShootTnt(world: World, shooter: LivingEntity) {
- if (world.isClient) return
- if (!shooter.canUseStaff) return
- if (shooter is PlayerEntity && shooter.isAttackCoolingDown) return
+ override fun allowComponentsUpdateAnimation(
+ oldStaffStack: ItemStack,
+ newStaffStack: ItemStack,
+ player: PlayerEntity,
+ hand: Hand
+ ) = false
- val spawnPos = impactTntEntityType.get().getSpawnPosition(world, shooter.approximateStaffTipPosition) ?: return
+ override fun allowReequipAnimation(
+ oldStaffStack: ItemStack,
+ newStaffStack: ItemStack,
+ selectedSlotChanged: Boolean
+ ) = selectedSlotChanged
+
+ private fun tryShootTnt(world: World, shooter: LivingEntity): ImpactTntEntity? {
+ if (world.isClient) return null
+ if (!shooter.canUseStaff) return null
+ if (shooter is PlayerEntity && shooter.isAttackCoolingDown) return null
+
+ val spawnPos =
+ EntityTypes.impactTnt.getSpawnPosition(world, shooter.approximateStaffTipPosition) ?: return null
val (x, y, z) = spawnPos
- world.spawnEntity(ImpactTntEntity(world, x, y, z, shooter.rotationVector + shooter.velocity, shooter))
+ val tnt = ImpactTntEntity(world, x, y, z, shooter.rotationVector + shooter.velocity, shooter)
+ // TODO Power Charge enchantment: increase explosion power
+ world.spawnEntity(tnt)
+ world.playSound(
+ null,
+ x, y, z,
+ SoundEvents.ITEM_FLINTANDSTEEL_USE, SoundCategory.BLOCKS,
+ 1f, world.random.nextFloat() * 0.4f + 0.8f
+ )
world.playSound(null, x, y, z, SoundEvents.ENTITY_TNT_PRIMED, SoundCategory.BLOCKS, 1f, 1f)
world.emitGameEvent(shooter, GameEvent.PRIME_FUSE, spawnPos)
+
+ return tnt
}
}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/VanillaStaffHandlers.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/VanillaStaffHandlers.kt
index ea1ba4058..ae75399ac 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/VanillaStaffHandlers.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/VanillaStaffHandlers.kt
@@ -18,159 +18,146 @@
package opekope2.avm_staff.internal.staff.handler
-import net.minecraft.block.Block
import net.minecraft.block.Blocks
import net.minecraft.item.BlockItem
-import net.minecraft.item.Item
import net.minecraft.item.Items.*
import net.minecraft.recipe.RecipeType
-import net.minecraft.registry.Registries
import net.minecraft.sound.SoundEvents
import net.minecraftforge.api.distmarker.Dist
import net.minecraftforge.api.distmarker.OnlyIn
-import opekope2.avm_staff.api.flamethrowerParticleType
import opekope2.avm_staff.api.item.renderer.BlockStateStaffItemRenderer
-import opekope2.avm_staff.api.item.renderer.IStaffItemRenderer
-import opekope2.avm_staff.api.soulFlamethrowerParticleType
+import opekope2.avm_staff.api.item.renderer.StaffItemRenderer
import opekope2.avm_staff.api.staff.StaffHandler
-
-private fun Item.registerHandler(handler: StaffHandler) {
- StaffHandler.register(Registries.ITEM.getId(this), handler)
-}
+import opekope2.avm_staff.content.ParticleTypes
+import opekope2.avm_staff.internal.staff.item_renderer.BellStaffItemRenderer
+import opekope2.avm_staff.internal.staff.item_renderer.FurnaceStaffItemRenderer
+import opekope2.avm_staff.internal.staff.item_renderer.LightningRodStaffItemRenderer
+import opekope2.avm_staff.internal.staff.item_renderer.WitherSkeletonSkullStaffItemRenderer
fun registerVanillaStaffHandlers() {
- ANVIL.registerHandler(AnvilHandler(CHIPPED_ANVIL))
- CHIPPED_ANVIL.registerHandler(AnvilHandler(DAMAGED_ANVIL))
- DAMAGED_ANVIL.registerHandler(AnvilHandler(null))
+ StaffHandler.register(ANVIL, AnvilHandler(CHIPPED_ANVIL))
+ StaffHandler.register(CHIPPED_ANVIL, AnvilHandler(DAMAGED_ANVIL))
+ StaffHandler.register(DAMAGED_ANVIL, AnvilHandler(null))
- BELL.registerHandler(BellBlockHandler())
+ StaffHandler.register(BELL, BellHandler())
- BONE_BLOCK.registerHandler(BoneBlockHandler())
+ StaffHandler.register(BONE_BLOCK, BoneBlockHandler())
- CAKE.registerHandler(CakeHandler())
+ StaffHandler.register(CAKE, CakeHandler())
- CAMPFIRE.registerHandler(
+ StaffHandler.register(
+ CAMPFIRE,
CampfireHandler(
- flamethrowerParticleType,
- CampfireHandler.Properties(1 / 20.0, 5 / 20.0, 4f, 1, 0.1)
+ CampfireHandler.Parameters(5 / 20.0, 1 / 20.0, 4f, 1, 0.1, ParticleTypes.FLAME)
)
)
- SOUL_CAMPFIRE.registerHandler(
+ StaffHandler.register(
+ SOUL_CAMPFIRE,
CampfireHandler(
- soulFlamethrowerParticleType,
- CampfireHandler.Properties(2 / 20.0, 10 / 20.0, 6f, 2, 0.12)
+ CampfireHandler.Parameters(10 / 20.0, 2 / 20.0, 6f, 2, 0.12, ParticleTypes.SOUL_FIRE_FLAME)
)
)
- // TODO command block
+ StaffHandler.register(COMMAND_BLOCK, StaffHandler.Fallback) // TODO
- DIAMOND_BLOCK.registerHandler(DiamondBlockHandler())
+ StaffHandler.register(DIAMOND_BLOCK, DiamondBlockHandler())
- FURNACE.registerHandler(
+ StaffHandler.register(
+ FURNACE,
FurnaceHandler(RecipeType.SMELTING, SoundEvents.BLOCK_FURNACE_FIRE_CRACKLE)
)
- BLAST_FURNACE.registerHandler(
+ StaffHandler.register(
+ BLAST_FURNACE,
FurnaceHandler(RecipeType.BLASTING, SoundEvents.BLOCK_BLASTFURNACE_FIRE_CRACKLE)
)
- SMOKER.registerHandler(
+ StaffHandler.register(
+ SMOKER,
FurnaceHandler(RecipeType.SMOKING, SoundEvents.BLOCK_SMOKER_SMOKE)
)
- GOLD_BLOCK.registerHandler(GoldBlockHandler())
+ StaffHandler.register(GOLD_BLOCK, GoldBlockHandler())
- LIGHTNING_ROD.registerHandler(LightningRodHandler())
+ StaffHandler.register(LIGHTNING_ROD, LightningRodHandler())
- MAGMA_BLOCK.registerHandler(MagmaBlockHandler())
+ StaffHandler.register(MAGMA_BLOCK, MagmaBlockHandler())
- NETHERITE_BLOCK.registerHandler(NetheriteBlockHandler())
+ StaffHandler.register(NETHERITE_BLOCK, NetheriteBlockHandler())
- SNOW_BLOCK.registerHandler(SnowBlockHandler())
+ StaffHandler.register(SNOW_BLOCK, SnowBlockHandler())
- TNT.registerHandler(TntHandler())
+ StaffHandler.register(TNT, TntHandler())
- WITHER_SKELETON_SKULL.registerHandler(
- WitherSkeletonSkullHandler()
- )
-
- WHITE_WOOL.registerHandler(WoolHandler(WHITE_WOOL as BlockItem, WHITE_CARPET as BlockItem))
- ORANGE_WOOL.registerHandler(WoolHandler(ORANGE_WOOL as BlockItem, ORANGE_CARPET as BlockItem))
- MAGENTA_WOOL.registerHandler(WoolHandler(MAGENTA_WOOL as BlockItem, MAGENTA_CARPET as BlockItem))
- LIGHT_BLUE_WOOL.registerHandler(WoolHandler(LIGHT_BLUE_WOOL as BlockItem, LIGHT_BLUE_CARPET as BlockItem))
- YELLOW_WOOL.registerHandler(WoolHandler(YELLOW_WOOL as BlockItem, YELLOW_CARPET as BlockItem))
- LIME_WOOL.registerHandler(WoolHandler(LIME_WOOL as BlockItem, LIME_CARPET as BlockItem))
- PINK_WOOL.registerHandler(WoolHandler(PINK_WOOL as BlockItem, PINK_CARPET as BlockItem))
- GRAY_WOOL.registerHandler(WoolHandler(GRAY_WOOL as BlockItem, GRAY_CARPET as BlockItem))
- LIGHT_GRAY_WOOL.registerHandler(WoolHandler(LIGHT_GRAY_WOOL as BlockItem, LIGHT_GRAY_CARPET as BlockItem))
- CYAN_WOOL.registerHandler(WoolHandler(CYAN_WOOL as BlockItem, CYAN_CARPET as BlockItem))
- PURPLE_WOOL.registerHandler(WoolHandler(PURPLE_WOOL as BlockItem, PURPLE_CARPET as BlockItem))
- BLUE_WOOL.registerHandler(WoolHandler(BLUE_WOOL as BlockItem, BLUE_CARPET as BlockItem))
- BROWN_WOOL.registerHandler(WoolHandler(BROWN_WOOL as BlockItem, BROWN_CARPET as BlockItem))
- GREEN_WOOL.registerHandler(WoolHandler(GREEN_WOOL as BlockItem, GREEN_CARPET as BlockItem))
- RED_WOOL.registerHandler(WoolHandler(RED_WOOL as BlockItem, RED_CARPET as BlockItem))
- BLACK_WOOL.registerHandler(WoolHandler(BLACK_WOOL as BlockItem, BLACK_CARPET as BlockItem))
-}
+ StaffHandler.register(WITHER_SKELETON_SKULL, WitherSkeletonSkullHandler())
-@OnlyIn(Dist.CLIENT)
-private fun Item.registerStaffItemRenderer(renderer: IStaffItemRenderer) {
- IStaffItemRenderer.register(Registries.ITEM.getId(this), renderer)
-}
-
-@OnlyIn(Dist.CLIENT)
-private fun Item.registerStaffItemRenderer(staffItem: Block) {
- registerStaffItemRenderer(BlockStateStaffItemRenderer(staffItem.defaultState))
+ StaffHandler.register(WHITE_WOOL, WoolHandler(WHITE_WOOL as BlockItem, WHITE_CARPET as BlockItem))
+ StaffHandler.register(ORANGE_WOOL, WoolHandler(ORANGE_WOOL as BlockItem, ORANGE_CARPET as BlockItem))
+ StaffHandler.register(MAGENTA_WOOL, WoolHandler(MAGENTA_WOOL as BlockItem, MAGENTA_CARPET as BlockItem))
+ StaffHandler.register(LIGHT_BLUE_WOOL, WoolHandler(LIGHT_BLUE_WOOL as BlockItem, LIGHT_BLUE_CARPET as BlockItem))
+ StaffHandler.register(YELLOW_WOOL, WoolHandler(YELLOW_WOOL as BlockItem, YELLOW_CARPET as BlockItem))
+ StaffHandler.register(LIME_WOOL, WoolHandler(LIME_WOOL as BlockItem, LIME_CARPET as BlockItem))
+ StaffHandler.register(PINK_WOOL, WoolHandler(PINK_WOOL as BlockItem, PINK_CARPET as BlockItem))
+ StaffHandler.register(GRAY_WOOL, WoolHandler(GRAY_WOOL as BlockItem, GRAY_CARPET as BlockItem))
+ StaffHandler.register(LIGHT_GRAY_WOOL, WoolHandler(LIGHT_GRAY_WOOL as BlockItem, LIGHT_GRAY_CARPET as BlockItem))
+ StaffHandler.register(CYAN_WOOL, WoolHandler(CYAN_WOOL as BlockItem, CYAN_CARPET as BlockItem))
+ StaffHandler.register(PURPLE_WOOL, WoolHandler(PURPLE_WOOL as BlockItem, PURPLE_CARPET as BlockItem))
+ StaffHandler.register(BLUE_WOOL, WoolHandler(BLUE_WOOL as BlockItem, BLUE_CARPET as BlockItem))
+ StaffHandler.register(BROWN_WOOL, WoolHandler(BROWN_WOOL as BlockItem, BROWN_CARPET as BlockItem))
+ StaffHandler.register(GREEN_WOOL, WoolHandler(GREEN_WOOL as BlockItem, GREEN_CARPET as BlockItem))
+ StaffHandler.register(RED_WOOL, WoolHandler(RED_WOOL as BlockItem, RED_CARPET as BlockItem))
+ StaffHandler.register(BLACK_WOOL, WoolHandler(BLACK_WOOL as BlockItem, BLACK_CARPET as BlockItem))
}
@OnlyIn(Dist.CLIENT)
fun registerVanillaStaffItemRenderers() {
- ANVIL.registerStaffItemRenderer(Blocks.ANVIL)
- CHIPPED_ANVIL.registerStaffItemRenderer(Blocks.CHIPPED_ANVIL)
- DAMAGED_ANVIL.registerStaffItemRenderer(Blocks.DAMAGED_ANVIL)
+ StaffItemRenderer.register(ANVIL, BlockStateStaffItemRenderer(Blocks.ANVIL))
+ StaffItemRenderer.register(CHIPPED_ANVIL, BlockStateStaffItemRenderer(Blocks.CHIPPED_ANVIL))
+ StaffItemRenderer.register(DAMAGED_ANVIL, BlockStateStaffItemRenderer(Blocks.DAMAGED_ANVIL))
- BELL.registerStaffItemRenderer(BellBlockHandler.BellStaffItemRenderer())
+ StaffItemRenderer.register(BELL, BellStaffItemRenderer())
- BONE_BLOCK.registerStaffItemRenderer(Blocks.BONE_BLOCK)
+ StaffItemRenderer.register(BONE_BLOCK, BlockStateStaffItemRenderer(Blocks.BONE_BLOCK))
- CAKE.registerStaffItemRenderer(Blocks.CAKE)
+ StaffItemRenderer.register(CAKE, BlockStateStaffItemRenderer(Blocks.CAKE))
- CAMPFIRE.registerStaffItemRenderer(Blocks.CAMPFIRE)
- SOUL_CAMPFIRE.registerStaffItemRenderer(Blocks.SOUL_CAMPFIRE)
+ StaffItemRenderer.register(CAMPFIRE, BlockStateStaffItemRenderer(Blocks.CAMPFIRE))
+ StaffItemRenderer.register(SOUL_CAMPFIRE, BlockStateStaffItemRenderer(Blocks.SOUL_CAMPFIRE))
- COMMAND_BLOCK.registerStaffItemRenderer(Blocks.COMMAND_BLOCK)
+ StaffItemRenderer.register(COMMAND_BLOCK, BlockStateStaffItemRenderer(Blocks.COMMAND_BLOCK))
- DIAMOND_BLOCK.registerStaffItemRenderer(Blocks.DIAMOND_BLOCK)
+ StaffItemRenderer.register(DIAMOND_BLOCK, BlockStateStaffItemRenderer(Blocks.DIAMOND_BLOCK))
- FURNACE.registerStaffItemRenderer(FurnaceHandler.FurnaceStaffItemRenderer(Blocks.FURNACE))
- BLAST_FURNACE.registerStaffItemRenderer(FurnaceHandler.FurnaceStaffItemRenderer(Blocks.BLAST_FURNACE))
- SMOKER.registerStaffItemRenderer(FurnaceHandler.FurnaceStaffItemRenderer(Blocks.SMOKER))
+ StaffItemRenderer.register(FURNACE, FurnaceStaffItemRenderer(Blocks.FURNACE))
+ StaffItemRenderer.register(BLAST_FURNACE, FurnaceStaffItemRenderer(Blocks.BLAST_FURNACE))
+ StaffItemRenderer.register(SMOKER, FurnaceStaffItemRenderer(Blocks.SMOKER))
- GOLD_BLOCK.registerStaffItemRenderer(Blocks.GOLD_BLOCK)
+ StaffItemRenderer.register(GOLD_BLOCK, BlockStateStaffItemRenderer(Blocks.GOLD_BLOCK))
- LIGHTNING_ROD.registerStaffItemRenderer(LightningRodHandler.LightningRodStaffItemRenderer())
+ StaffItemRenderer.register(LIGHTNING_ROD, LightningRodStaffItemRenderer())
- MAGMA_BLOCK.registerStaffItemRenderer(Blocks.MAGMA_BLOCK)
+ StaffItemRenderer.register(MAGMA_BLOCK, BlockStateStaffItemRenderer(Blocks.MAGMA_BLOCK))
- NETHERITE_BLOCK.registerStaffItemRenderer(Blocks.NETHERITE_BLOCK)
+ StaffItemRenderer.register(NETHERITE_BLOCK, BlockStateStaffItemRenderer(Blocks.NETHERITE_BLOCK))
- SNOW_BLOCK.registerStaffItemRenderer(Blocks.SNOW_BLOCK)
+ StaffItemRenderer.register(SNOW_BLOCK, BlockStateStaffItemRenderer(Blocks.SNOW_BLOCK))
- TNT.registerStaffItemRenderer(Blocks.TNT)
+ StaffItemRenderer.register(TNT, BlockStateStaffItemRenderer(Blocks.TNT))
- WITHER_SKELETON_SKULL.registerStaffItemRenderer(WitherSkeletonSkullHandler.WitherSkeletonSkullStaffItemRenderer())
+ StaffItemRenderer.register(WITHER_SKELETON_SKULL, WitherSkeletonSkullStaffItemRenderer())
- WHITE_WOOL.registerStaffItemRenderer(Blocks.WHITE_WOOL)
- ORANGE_WOOL.registerStaffItemRenderer(Blocks.ORANGE_WOOL)
- MAGENTA_WOOL.registerStaffItemRenderer(Blocks.MAGENTA_WOOL)
- LIGHT_BLUE_WOOL.registerStaffItemRenderer(Blocks.LIGHT_BLUE_WOOL)
- YELLOW_WOOL.registerStaffItemRenderer(Blocks.YELLOW_WOOL)
- LIME_WOOL.registerStaffItemRenderer(Blocks.LIME_WOOL)
- PINK_WOOL.registerStaffItemRenderer(Blocks.PINK_WOOL)
- GRAY_WOOL.registerStaffItemRenderer(Blocks.GRAY_WOOL)
- LIGHT_GRAY_WOOL.registerStaffItemRenderer(Blocks.LIGHT_GRAY_WOOL)
- CYAN_WOOL.registerStaffItemRenderer(Blocks.CYAN_WOOL)
- PURPLE_WOOL.registerStaffItemRenderer(Blocks.PURPLE_WOOL)
- BLUE_WOOL.registerStaffItemRenderer(Blocks.BLUE_WOOL)
- BROWN_WOOL.registerStaffItemRenderer(Blocks.BROWN_WOOL)
- GREEN_WOOL.registerStaffItemRenderer(Blocks.GREEN_WOOL)
- RED_WOOL.registerStaffItemRenderer(Blocks.RED_WOOL)
- BLACK_WOOL.registerStaffItemRenderer(Blocks.BLACK_WOOL)
+ StaffItemRenderer.register(WHITE_WOOL, BlockStateStaffItemRenderer(Blocks.WHITE_WOOL))
+ StaffItemRenderer.register(ORANGE_WOOL, BlockStateStaffItemRenderer(Blocks.ORANGE_WOOL))
+ StaffItemRenderer.register(MAGENTA_WOOL, BlockStateStaffItemRenderer(Blocks.MAGENTA_WOOL))
+ StaffItemRenderer.register(LIGHT_BLUE_WOOL, BlockStateStaffItemRenderer(Blocks.LIGHT_BLUE_WOOL))
+ StaffItemRenderer.register(YELLOW_WOOL, BlockStateStaffItemRenderer(Blocks.YELLOW_WOOL))
+ StaffItemRenderer.register(LIME_WOOL, BlockStateStaffItemRenderer(Blocks.LIME_WOOL))
+ StaffItemRenderer.register(PINK_WOOL, BlockStateStaffItemRenderer(Blocks.PINK_WOOL))
+ StaffItemRenderer.register(GRAY_WOOL, BlockStateStaffItemRenderer(Blocks.GRAY_WOOL))
+ StaffItemRenderer.register(LIGHT_GRAY_WOOL, BlockStateStaffItemRenderer(Blocks.LIGHT_GRAY_WOOL))
+ StaffItemRenderer.register(CYAN_WOOL, BlockStateStaffItemRenderer(Blocks.CYAN_WOOL))
+ StaffItemRenderer.register(PURPLE_WOOL, BlockStateStaffItemRenderer(Blocks.PURPLE_WOOL))
+ StaffItemRenderer.register(BLUE_WOOL, BlockStateStaffItemRenderer(Blocks.BLUE_WOOL))
+ StaffItemRenderer.register(BROWN_WOOL, BlockStateStaffItemRenderer(Blocks.BROWN_WOOL))
+ StaffItemRenderer.register(GREEN_WOOL, BlockStateStaffItemRenderer(Blocks.GREEN_WOOL))
+ StaffItemRenderer.register(RED_WOOL, BlockStateStaffItemRenderer(Blocks.RED_WOOL))
+ StaffItemRenderer.register(BLACK_WOOL, BlockStateStaffItemRenderer(Blocks.BLACK_WOOL))
}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/WitherSkeletonSkullHandler.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/WitherSkeletonSkullHandler.kt
index 606031d23..581e162ae 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/WitherSkeletonSkullHandler.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/WitherSkeletonSkullHandler.kt
@@ -18,13 +18,6 @@
package opekope2.avm_staff.internal.staff.handler
-import net.minecraft.block.AbstractSkullBlock
-import net.minecraft.block.Blocks
-import net.minecraft.client.render.VertexConsumerProvider
-import net.minecraft.client.render.block.entity.SkullBlockEntityRenderer
-import net.minecraft.client.render.entity.model.SkullEntityModel
-import net.minecraft.client.render.model.json.ModelTransformationMode
-import net.minecraft.client.util.math.MatrixStack
import net.minecraft.entity.Entity
import net.minecraft.entity.EntityType
import net.minecraft.entity.LivingEntity
@@ -33,56 +26,47 @@ import net.minecraft.entity.effect.StatusEffects
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.entity.projectile.WitherSkullEntity
import net.minecraft.item.ItemStack
+import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.ActionResult
import net.minecraft.util.Hand
-import net.minecraft.util.TypedActionResult
import net.minecraft.world.Difficulty
import net.minecraft.world.World
import net.minecraft.world.WorldEvents
import net.minecraftforge.api.distmarker.Dist
import net.minecraftforge.api.distmarker.OnlyIn
-import opekope2.avm_staff.api.item.renderer.IStaffItemRenderer
-import opekope2.avm_staff.api.staff.StaffHandler
+import opekope2.avm_staff.content.Enchantments
import opekope2.avm_staff.util.*
-internal class WitherSkeletonSkullHandler : StaffHandler() {
- override val maxUseTime = 20
+internal class WitherSkeletonSkullHandler : AbstractProjectileShootingStaffHandler() {
+ override fun getFireRateDenominator(rapidFireLevel: Int) = if (rapidFireLevel >= 2) 2 else 4
- override fun use(
- staffStack: ItemStack,
- world: World,
- user: PlayerEntity,
- hand: Hand
- ): TypedActionResult {
- user.setCurrentHand(hand)
- return TypedActionResult.consume(staffStack)
- }
-
- override fun usageTick(staffStack: ItemStack, world: World, user: LivingEntity, remainingUseTicks: Int) {
- if ((remainingUseTicks and 1) == 0) {
- tryShootSkull(world, user, Math.random() < 0.1f) // TODO ratio
- }
- }
+ override fun getMaxUseTime(staffStack: ItemStack, world: World, user: LivingEntity) = 20
override fun attack(staffStack: ItemStack, world: World, attacker: LivingEntity, hand: Hand) {
if (attacker is PlayerEntity && attacker.itemCooldownManager.isCoolingDown(staffStack.item)) return
-
- tryShootSkull(world, attacker, false)
- (attacker as? PlayerEntity)?.resetLastAttackedTicks()
+ super.attack(staffStack, world, attacker, hand)
+ if (attacker is PlayerEntity) addCooldown(staffStack, world, attacker, 0)
}
- private fun tryShootSkull(world: World, user: LivingEntity, charged: Boolean) {
- if (world.isClient) return
- if (!user.canUseStaff) return
- if (user is PlayerEntity && user.isAttackCoolingDown) return
+ override fun tryShootProjectile(
+ staffStack: ItemStack,
+ world: World,
+ shooter: LivingEntity,
+ reason: ProjectileShootReason
+ ): Boolean {
+ if (!super.tryShootProjectile(staffStack, world, shooter, reason)) return false
- val spawnPos = EntityType.WITHER_SKULL.getSpawnPosition(world, user.approximateStaffTipPosition) ?: return
+ val spawnPos = EntityType.WITHER_SKULL.getSpawnPosition(world, shooter.approximateStaffTipPosition)
+ ?: return false
- world.spawnEntity(WitherSkullEntity(world, user, user.rotationVector).apply {
- isCharged = charged
+ world.spawnEntity(WitherSkullEntity(world, shooter, shooter.rotationVector).apply {
+ owner = shooter
+ isCharged = reason.isAttack && staffStack.isEnchantedWith(Enchantments.POWER_CHARGE, world.registryManager)
setPosition(spawnPos)
})
- world.syncWorldEvent(WorldEvents.WITHER_SHOOTS, user.blockPos, 0)
+ world.syncWorldEvent(WorldEvents.WITHER_SHOOTS, shooter.blockPos, 0)
+
+ return true
}
override fun attackEntity(
@@ -96,13 +80,14 @@ internal class WitherSkeletonSkullHandler : StaffHandler() {
if (target is LivingEntity && !target.isInvulnerableTo(world.damageSources.wither())) {
val amplifier = if (world.difficulty == Difficulty.HARD) 1 else 0
target.addStatusEffect(StatusEffectInstance(StatusEffects.WITHER, 10 * 20, amplifier))
+ (attacker as? ServerPlayerEntity)?.incrementStaffItemUseStat(staffStack.itemInStaff!!)
}
return ActionResult.PASS
}
override fun onStoppedUsing(staffStack: ItemStack, world: World, user: LivingEntity, remainingUseTicks: Int) {
- (user as? PlayerEntity)?.itemCooldownManager?.set(staffStack.item, 4 * (maxUseTime - remainingUseTicks))
+ if (user is PlayerEntity) addCooldown(staffStack, world, user, remainingUseTicks)
}
override fun finishUsing(staffStack: ItemStack, world: World, user: LivingEntity): ItemStack {
@@ -110,33 +95,13 @@ internal class WitherSkeletonSkullHandler : StaffHandler() {
return staffStack
}
- @OnlyIn(Dist.CLIENT)
- class WitherSkeletonSkullStaffItemRenderer : IStaffItemRenderer {
- private val skullModel = SkullEntityModel.getSkullTexturedModelData().createModel()
+ private fun addCooldown(staffStack: ItemStack, world: World, player: PlayerEntity, remainingUseTicks: Int) {
+ if (player.abilities.creativeMode) return
- override fun renderItemInStaff(
- staffStack: ItemStack,
- mode: ModelTransformationMode,
- matrices: MatrixStack,
- vertexConsumers: VertexConsumerProvider,
- light: Int,
- overlay: Int
- ) {
- matrices.push {
- scale(-1f, -1f, 1f)
- translate(0f, 8f / 16f, 0f)
- scale(2f, 2f, 2f)
- skullModel.render(
- matrices,
- vertexConsumers.getBuffer(
- SkullBlockEntityRenderer.getRenderLayer(
- (Blocks.WITHER_SKELETON_SKULL as AbstractSkullBlock).skullType, null
- )
- ),
- light,
- overlay
- )
- }
- }
+ val quickDraw = staffStack.getEnchantmentLevel(Enchantments.QUICK_DRAW, world.registryManager) + 1
+ player.itemCooldownManager.set(
+ staffStack.item,
+ 4 * (getMaxUseTime(staffStack, world, player) - remainingUseTicks) / quickDraw
+ )
}
}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/WoolHandler.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/WoolHandler.kt
index 387554fae..325816379 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/WoolHandler.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/handler/WoolHandler.kt
@@ -28,6 +28,7 @@ import net.minecraft.item.BlockItem
import net.minecraft.item.ItemPlacementContext
import net.minecraft.item.ItemStack
import net.minecraft.registry.tag.BlockTags
+import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.ActionResult
import net.minecraft.util.Hand
import net.minecraft.util.hit.BlockHitResult
@@ -37,9 +38,7 @@ import net.minecraft.world.World
import opekope2.avm_staff.api.staff.StaffAttributeModifiersComponentBuilder
import opekope2.avm_staff.api.staff.StaffHandler
import opekope2.avm_staff.mixin.IMinecraftClientAccessor
-import opekope2.avm_staff.util.attackDamage
-import opekope2.avm_staff.util.attackSpeed
-import opekope2.avm_staff.util.mutableItemStackInStaff
+import opekope2.avm_staff.util.*
internal class WoolHandler(private val woolItem: BlockItem, private val carpetItem: BlockItem) : StaffHandler() {
override val attributeModifiers = StaffAttributeModifiersComponentBuilder()
@@ -73,7 +72,15 @@ internal class WoolHandler(private val woolItem: BlockItem, private val carpetIt
staffStack.mutableItemStackInStaff!!,
BlockHitResult(target.toCenterPos(), side, target, false)
)
- return itemToPlace.place(woolPlaceContext)
+
+ val result = itemToPlace.place(woolPlaceContext)
+
+ if (result.isAccepted) staffStack.damage(1, user, LivingEntity.getSlotForHand(hand))
+ if (result.shouldIncrementStat()) {
+ (user as? ServerPlayerEntity)?.incrementStaffItemUseStat(staffStack.itemInStaff!!)
+ }
+
+ return result
}
private class WoolPlacementContext(
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/item_renderer/BellStaffItemRenderer.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/item_renderer/BellStaffItemRenderer.kt
new file mode 100644
index 000000000..5ff75ecdf
--- /dev/null
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/item_renderer/BellStaffItemRenderer.kt
@@ -0,0 +1,61 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.internal.staff.item_renderer
+
+import net.minecraft.client.render.RenderLayer
+import net.minecraft.client.render.VertexConsumerProvider
+import net.minecraft.client.render.block.entity.BellBlockEntityRenderer
+import net.minecraft.client.render.model.json.ModelTransformationMode
+import net.minecraft.client.util.math.MatrixStack
+import net.minecraft.item.ItemStack
+import net.minecraftforge.api.distmarker.Dist
+import net.minecraftforge.api.distmarker.OnlyIn
+import opekope2.avm_staff.api.item.renderer.StaffItemRenderer
+import opekope2.avm_staff.util.push
+
+@OnlyIn(Dist.CLIENT)
+class BellStaffItemRenderer : StaffItemRenderer() {
+ private val bellModel = BellBlockEntityRenderer.getTexturedModelData().createModel().apply {
+ setPivot(-8f, -12f, -8f)
+ }
+
+ override fun renderItemInStaff(
+ staffStack: ItemStack,
+ mode: ModelTransformationMode,
+ matrices: MatrixStack,
+ vertexConsumers: VertexConsumerProvider,
+ light: Int,
+ overlay: Int
+ ) {
+ matrices.push {
+ scale(16f / 9f, 16f / 9f, 16f / 9f)
+ translate(0f, 2f / 9f, 0f)
+
+ bellModel.render(
+ matrices,
+ BellBlockEntityRenderer.BELL_BODY_TEXTURE.getVertexConsumer(
+ vertexConsumers,
+ RenderLayer::getEntitySolid
+ ),
+ light,
+ overlay
+ )
+ }
+ }
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/item_renderer/FurnaceStaffItemRenderer.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/item_renderer/FurnaceStaffItemRenderer.kt
new file mode 100644
index 000000000..059d075cb
--- /dev/null
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/item_renderer/FurnaceStaffItemRenderer.kt
@@ -0,0 +1,58 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.internal.staff.item_renderer
+
+import net.minecraft.block.AbstractFurnaceBlock
+import net.minecraft.block.Block
+import net.minecraft.block.BlockState
+import net.minecraft.client.render.VertexConsumerProvider
+import net.minecraft.client.render.model.json.ModelTransformationMode
+import net.minecraft.client.util.math.MatrixStack
+import net.minecraft.item.ItemStack
+import net.minecraftforge.api.distmarker.Dist
+import net.minecraftforge.api.distmarker.OnlyIn
+import opekope2.avm_staff.api.item.renderer.BlockStateStaffItemRenderer
+import opekope2.avm_staff.api.item.renderer.StaffItemRenderer
+import opekope2.avm_staff.content.DataComponentTypes
+
+@OnlyIn(Dist.CLIENT)
+class FurnaceStaffItemRenderer(unlitState: BlockState, litState: BlockState) : StaffItemRenderer() {
+ constructor(furnaceBlock: Block) : this(
+ furnaceBlock.defaultState,
+ furnaceBlock.defaultState.with(AbstractFurnaceBlock.LIT, true)
+ )
+
+ private val unlitRenderer = BlockStateStaffItemRenderer(unlitState)
+ private val litRenderer = BlockStateStaffItemRenderer(litState)
+
+ override fun renderItemInStaff(
+ staffStack: ItemStack,
+ mode: ModelTransformationMode,
+ matrices: MatrixStack,
+ vertexConsumers: VertexConsumerProvider,
+ light: Int,
+ overlay: Int
+ ) {
+ val renderer =
+ if (DataComponentTypes.furnaceData in staffStack) litRenderer
+ else unlitRenderer
+
+ renderer.renderItemInStaff(staffStack, mode, matrices, vertexConsumers, light, overlay)
+ }
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/item_renderer/LightningRodStaffItemRenderer.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/item_renderer/LightningRodStaffItemRenderer.kt
new file mode 100644
index 000000000..951f61ec4
--- /dev/null
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/item_renderer/LightningRodStaffItemRenderer.kt
@@ -0,0 +1,51 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.internal.staff.item_renderer
+
+import net.minecraft.block.Blocks
+import net.minecraft.client.render.VertexConsumerProvider
+import net.minecraft.client.render.model.json.ModelTransformationMode
+import net.minecraft.client.util.math.MatrixStack
+import net.minecraft.item.ItemStack
+import net.minecraftforge.api.distmarker.Dist
+import net.minecraftforge.api.distmarker.OnlyIn
+import opekope2.avm_staff.api.item.renderer.BlockStateStaffItemRenderer
+import opekope2.avm_staff.api.item.renderer.StaffItemRenderer
+import opekope2.avm_staff.util.push
+
+@OnlyIn(Dist.CLIENT)
+class LightningRodStaffItemRenderer : StaffItemRenderer() {
+ private val lightningRodRenderer = BlockStateStaffItemRenderer(Blocks.LIGHTNING_ROD.defaultState)
+
+ override fun renderItemInStaff(
+ staffStack: ItemStack,
+ mode: ModelTransformationMode,
+ matrices: MatrixStack,
+ vertexConsumers: VertexConsumerProvider,
+ light: Int,
+ overlay: Int
+ ) {
+ matrices.push {
+ if (mode != ModelTransformationMode.GUI && mode != ModelTransformationMode.FIXED) {
+ translate(0f, 22f / 16f, 0f)
+ }
+ lightningRodRenderer.renderItemInStaff(staffStack, mode, matrices, vertexConsumers, light, overlay)
+ }
+ }
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/item_renderer/WitherSkeletonSkullStaffItemRenderer.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/item_renderer/WitherSkeletonSkullStaffItemRenderer.kt
new file mode 100644
index 000000000..91532d089
--- /dev/null
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/internal/staff/item_renderer/WitherSkeletonSkullStaffItemRenderer.kt
@@ -0,0 +1,62 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.internal.staff.item_renderer
+
+import net.minecraft.block.AbstractSkullBlock
+import net.minecraft.block.Blocks
+import net.minecraft.client.render.VertexConsumerProvider
+import net.minecraft.client.render.block.entity.SkullBlockEntityRenderer
+import net.minecraft.client.render.entity.model.SkullEntityModel
+import net.minecraft.client.render.model.json.ModelTransformationMode
+import net.minecraft.client.util.math.MatrixStack
+import net.minecraft.item.ItemStack
+import net.minecraftforge.api.distmarker.Dist
+import net.minecraftforge.api.distmarker.OnlyIn
+import opekope2.avm_staff.api.item.renderer.StaffItemRenderer
+import opekope2.avm_staff.util.push
+
+@OnlyIn(Dist.CLIENT)
+class WitherSkeletonSkullStaffItemRenderer : StaffItemRenderer() {
+ private val skullModel = SkullEntityModel.getSkullTexturedModelData().createModel()
+
+ override fun renderItemInStaff(
+ staffStack: ItemStack,
+ mode: ModelTransformationMode,
+ matrices: MatrixStack,
+ vertexConsumers: VertexConsumerProvider,
+ light: Int,
+ overlay: Int
+ ) {
+ matrices.push {
+ scale(-1f, -1f, 1f)
+ translate(0f, 8f / 16f, 0f)
+ scale(2f, 2f, 2f)
+ skullModel.render(
+ matrices,
+ vertexConsumers.getBuffer(
+ SkullBlockEntityRenderer.getRenderLayer(
+ (Blocks.WITHER_SKELETON_SKULL as AbstractSkullBlock).skullType, null
+ )
+ ),
+ light,
+ overlay
+ )
+ }
+ }
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/util/EnchantmentUtil.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/util/EnchantmentUtil.kt
new file mode 100644
index 000000000..c10881797
--- /dev/null
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/util/EnchantmentUtil.kt
@@ -0,0 +1,48 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+@file: JvmName("EnchantmentUtil")
+
+package opekope2.avm_staff.util
+
+import net.minecraft.enchantment.Enchantment
+import net.minecraft.enchantment.EnchantmentHelper
+import net.minecraft.item.ItemStack
+import net.minecraft.registry.DynamicRegistryManager
+import net.minecraft.registry.RegistryKey
+import opekope2.avm_staff.content.Enchantments.getEntry
+
+/**
+ * Gets an enchantment level on an item
+ *
+ * @param enchantment The registry key of the enchantment to check
+ * @param registryManager The registry manager of a world
+ * @see EnchantmentHelper.getLevel
+ */
+fun ItemStack.getEnchantmentLevel(enchantment: RegistryKey, registryManager: DynamicRegistryManager) =
+ EnchantmentHelper.getLevel(enchantment.getEntry(registryManager), this)
+
+/**
+ * Checks is an item is enchanted with [enchantment]
+ *
+ * @param enchantment The registry key of the enchantment to check
+ * @param registryManager The registry manager of a world
+ * @see EnchantmentHelper.getLevel
+ */
+fun ItemStack.isEnchantedWith(enchantment: RegistryKey, registryManager: DynamicRegistryManager) =
+ getEnchantmentLevel(enchantment, registryManager) > 0
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/util/EntityUtil.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/util/EntityUtil.kt
index c92f0f17c..31c3f9187 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/util/EntityUtil.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/util/EntityUtil.kt
@@ -23,8 +23,10 @@ package opekope2.avm_staff.util
import net.minecraft.entity.Entity
import net.minecraft.entity.EntityType
import net.minecraft.util.math.Direction
+import net.minecraft.util.math.MathHelper
import net.minecraft.util.math.Vec3d
import net.minecraft.world.World
+import kotlin.math.sqrt
/**
* Calculates the camera's upward direction based on [Entity.getFacing] and [Entity.getHorizontalFacing].
@@ -48,3 +50,13 @@ fun EntityType.getSpawnPosition(world: World, center: Vec3d): Vec3d?
return if (world.isSpaceEmpty(getSpawnBox(spawnPos.x, spawnPos.y, spawnPos.z))) spawnPos
else null
}
+
+/**
+ * Sets the [yaw][Entity.yaw] and [pitch][Entity.pitch] of the entity to look [forward][Entity.velocity].
+ */
+fun Entity.lookForward() {
+ val (vx, vy, vz) = velocity.normalize()
+ val horizontalSpeed = sqrt(vx * vx + vz * vz)
+ yaw = MathHelper.atan2(vx, vz).toFloat()
+ pitch = MathHelper.atan2(horizontalSpeed, vy).toFloat()
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/util/ItemStackUtil.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/util/ItemStackUtil.kt
index 67b883367..1d3ac5288 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/util/ItemStackUtil.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/util/ItemStackUtil.kt
@@ -20,7 +20,9 @@
package opekope2.avm_staff.util
+import net.minecraft.entity.LivingEntity
import net.minecraft.item.ItemStack
+import net.minecraft.util.Hand
import opekope2.avm_staff.api.item.StaffItem
/**
@@ -28,3 +30,10 @@ import opekope2.avm_staff.api.item.StaffItem
*/
inline val ItemStack.isStaff
get() = item is StaffItem
+
+/**
+ * @see ItemStack.damage
+ */
+fun ItemStack.damage(amount: Int = 1, entity: LivingEntity, hand: Hand = entity.activeHand) {
+ damage(amount, entity, LivingEntity.getSlotForHand(hand))
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/util/MinecraftClientUtil.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/util/MinecraftClientUtil.kt
new file mode 100644
index 000000000..3c9929b6b
--- /dev/null
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/util/MinecraftClientUtil.kt
@@ -0,0 +1,68 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+@file: JvmName("MinecraftClientUtil")
+@file: OnlyIn(Dist.CLIENT)
+
+package opekope2.avm_staff.util
+
+import net.minecraft.client.MinecraftClient
+import net.minecraft.client.option.GameOptions
+import net.minecraft.client.particle.ParticleManager
+import net.minecraft.client.render.block.entity.BlockEntityRenderDispatcher
+import net.minecraft.client.render.entity.model.EntityModelLoader
+import net.minecraft.client.render.item.ItemRenderer
+import net.minecraft.client.render.model.BakedModelManager
+import net.minecraftforge.api.distmarker.Dist
+import net.minecraftforge.api.distmarker.OnlyIn
+
+/**
+ * @see MinecraftClient.bakedModelManager
+ */
+inline val bakedModelManager: BakedModelManager
+ get() = MinecraftClient.getInstance().bakedModelManager
+
+/**
+ * @see MinecraftClient.blockEntityRenderDispatcher
+ */
+inline val blockEntityRenderDispatcher: BlockEntityRenderDispatcher
+ get() = MinecraftClient.getInstance().blockEntityRenderDispatcher
+
+/**
+ * @see MinecraftClient.options
+ */
+inline val clientOptions: GameOptions
+ get() = MinecraftClient.getInstance().options
+
+/**
+ * @see MinecraftClient.entityModelLoader
+ */
+inline val entityModelLoader: EntityModelLoader
+ get() = MinecraftClient.getInstance().entityModelLoader
+
+/**
+ * @see MinecraftClient.itemRenderer
+ */
+inline val itemRenderer: ItemRenderer
+ get() = MinecraftClient.getInstance().itemRenderer
+
+/**
+ * @see MinecraftClient.particleManager
+ */
+inline val particleManager: ParticleManager
+ get() = MinecraftClient.getInstance().particleManager
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/util/RegistryKeyUtil.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/util/RegistryKeyUtil.kt
new file mode 100644
index 000000000..7ee96941a
--- /dev/null
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/util/RegistryKeyUtil.kt
@@ -0,0 +1,64 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.util
+
+import net.minecraft.registry.DynamicRegistryManager
+import net.minecraft.registry.Registry
+import net.minecraft.registry.RegistryKey
+import net.minecraft.util.Identifier
+import kotlin.jvm.optionals.getOrNull
+
+/**
+ * Utility class to create [Identifier]s and [RegistryKey]s using a specified [namespace][Identifier.namespace] and
+ * registry.
+ *
+ * @param TContent The type of the content to register
+ * @param modId The [namespace][Identifier.namespace] of the content to register.
+ * @param registry The registry to register the content in
+ */
+open class RegistryKeyUtil(
+ protected val modId: String,
+ protected val registry: RegistryKey>
+) {
+ init {
+ require(Identifier.isNamespaceValid(modId)) { "Mod ID is not a valid namespace" }
+ }
+
+ /**
+ * Creates an [Identifier] from the namespace specified in the constructor and a given path.
+ *
+ * @param path The path of the [Identifier] to create
+ */
+ fun id(path: String): Identifier = Identifier.of(modId, path)
+
+ /**
+ * Creates a [RegistryKey] from the registry and namespace specified in the constructor and a given path.
+ *
+ * @param path The path of the [Identifier] to create a registry key from
+ */
+ fun registryKey(path: String): RegistryKey = RegistryKey.of(registry, id(path))
+
+ /**
+ * Gets the registry entry of the given registry key from the given registry manager or `null`, if it's not found.
+ *
+ * @param registryManager The registry manager of a world
+ */
+ fun RegistryKey.getEntry(registryManager: DynamicRegistryManager) =
+ registryManager.get(this@RegistryKeyUtil.registry).getEntry(this).getOrNull()
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/util/RegistryUtil.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/util/RegistryUtil.kt
new file mode 100644
index 000000000..7d9171b8d
--- /dev/null
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/util/RegistryUtil.kt
@@ -0,0 +1,73 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.util
+
+import net.minecraft.registry.Registry
+import net.minecraft.registry.RegistryKey
+import net.minecraft.util.Identifier
+import net.minecraftforge.registries.DeferredRegister
+import net.minecraftforge.registries.IForgeRegistry
+import net.minecraftforge.registries.RegistryObject
+import thedarkcolour.kotlinforforge.forge.MOD_BUS
+
+/**
+ * Utility class to register content to Minecraft registries.
+ *
+ * @param TContent The type of the content to register
+ */
+abstract class RegistryUtil : RegistryKeyUtil {
+ /**
+ * Creates a new [RegistryUtil] instance.
+ *
+ * @param TContent The type of the content to register
+ * @param modId The [namespace][Identifier.namespace] of the content to register.
+ * @param registry The registry to register the content in
+ */
+ protected constructor(modId: String, registry: RegistryKey>) : super(modId, registry) {
+ this.deferredRegister = DeferredRegister.create(registry, modId)
+ }
+
+ /**
+ * Creates a new [RegistryUtil] instance.
+ *
+ * @param TContent The type of the content to register
+ * @param modId The [namespace][Identifier.namespace] of the content to register.
+ * @param registry The registry to register the content in
+ */
+ protected constructor(modId: String, registry: IForgeRegistry) : super(modId, registry.registryKey) {
+ this.deferredRegister = DeferredRegister.create(registry, modId)
+ }
+
+ private val deferredRegister: DeferredRegister
+
+ /**
+ * Adds a content to be registered in a Minecraft registry using Architectury API.
+ *
+ * @param path The [path][Identifier.path] of the identifier of the content to register
+ * @param factory The function creating
+ */
+ protected fun register(path: String, factory: (RegistryKey) -> T): RegistryObject =
+ deferredRegister.register(path) { factory(registryKey(path)) }
+
+ /**
+ * @suppress
+ */
+ @JvmSynthetic
+ internal open fun register() = deferredRegister.register(MOD_BUS)
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/util/StaffUtil.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/util/StaffUtil.kt
index 4af883e34..4ca01c70e 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/util/StaffUtil.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/util/StaffUtil.kt
@@ -24,26 +24,25 @@ import net.minecraft.component.ComponentChanges
import net.minecraft.entity.Entity
import net.minecraft.item.Item
import net.minecraft.item.ItemStack
-import net.minecraft.registry.Registries
import net.minecraft.util.hit.HitResult
import net.minecraft.util.math.Vec3d
import net.minecraft.world.RaycastContext
import opekope2.avm_staff.api.component.StaffItemComponent
import opekope2.avm_staff.api.staff.StaffHandler
-import opekope2.avm_staff.api.staffItemComponentType
+import opekope2.avm_staff.content.DataComponentTypes
/**
* Checks if an item is added the given staff item stack.
*/
val ItemStack.isItemInStaff: Boolean
@JvmName("isItemInStaff")
- get() = staffItemComponentType.get() in this
+ get() = DataComponentTypes.staffItem in this
/**
* Gets the item inserted into the given staff item stack.
*/
val ItemStack.itemInStaff: Item?
- get() = getOrDefault(staffItemComponentType.get(), null)?.item?.item
+ get() = getOrDefault(DataComponentTypes.staffItem, null)?.item?.item
/**
* Gets the item stack inserted into the given staff item stack.
@@ -52,7 +51,7 @@ val ItemStack.itemInStaff: Item?
* @see mutableItemStackInStaff
*/
val ItemStack.itemStackInStaff: ItemStack?
- get() = getOrDefault(staffItemComponentType.get(), null)?.item
+ get() = getOrDefault(DataComponentTypes.staffItem, null)?.item
/**
* Gets or sets a copy of the item stack inserted into the given staff item stack. The value returned or passed in can
@@ -66,9 +65,9 @@ var ItemStack.mutableItemStackInStaff: ItemStack?
val changes = ComponentChanges.builder()
if (value == null || value.isEmpty) {
- changes.remove(staffItemComponentType.get())
+ changes.remove(DataComponentTypes.staffItem)
} else {
- changes.add(staffItemComponentType.get(), StaffItemComponent(value.copy()))
+ changes.add(DataComponentTypes.staffItem, StaffItemComponent(value.copy()))
}
applyChanges(changes.build())
@@ -79,25 +78,23 @@ var ItemStack.mutableItemStackInStaff: ItemStack?
*/
val Item.hasStaffHandler: Boolean
@JvmName("hasStaffHandler")
- get() {
- val itemId = Registries.ITEM.getId(this)
- return itemId in StaffHandler
- }
+ get() = this in StaffHandler.Registry
/**
* Returns the registered staff handler of the given item if available.
*/
-val Item.staffHandler: StaffHandler?
- get() {
- val itemId = Registries.ITEM.getId(this)
- return StaffHandler[itemId]
+val Item?.staffHandler: StaffHandler?
+ get() = when {
+ this == null -> StaffHandler.Empty
+ !hasStaffHandler -> null
+ else -> StaffHandler.Registry[this]
}
/**
- * Returns the registered staff handler of the given item if available, [StaffHandler.Default] otherwise.
+ * Returns the registered staff handler of the given item if available, [StaffHandler.Fallback] otherwise.
*/
-val Item?.staffHandlerOrDefault: StaffHandler
- get() = this?.staffHandler ?: StaffHandler.Default
+val Item?.staffHandlerOrFallback: StaffHandler
+ get() = staffHandler ?: StaffHandler.Fallback
private const val STAFF_MODEL_LENGTH = 40.0 / 16.0
private const val STAFF_MODEL_ITEM_POSITION_CENTER = 33.5 / 16.0
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/util/StatUtil.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/util/StatUtil.kt
new file mode 100644
index 000000000..b52ae53ec
--- /dev/null
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/util/StatUtil.kt
@@ -0,0 +1,40 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+@file: JvmName("StatUtil")
+
+package opekope2.avm_staff.util
+
+import net.minecraft.item.Item
+import net.minecraft.server.network.ServerPlayerEntity
+import net.minecraft.stat.Stats
+import opekope2.avm_staff.content.StatTypes
+
+/**
+ * Increments [Stats.USED] of [item] by 1.
+ */
+fun ServerPlayerEntity.incrementItemUseStat(item: Item) {
+ incrementStat(Stats.USED.getOrCreateStat(item))
+}
+
+/**
+ * Increments [StatTypes.usedItemInStaff] of [item] by 1.
+ */
+fun ServerPlayerEntity.incrementStaffItemUseStat(item: Item) {
+ incrementStat(StatTypes.usedItemInStaff.getOrCreateStat(item))
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/util/TagKeyUtil.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/util/TagKeyUtil.kt
new file mode 100644
index 000000000..bc49aa60e
--- /dev/null
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/util/TagKeyUtil.kt
@@ -0,0 +1,54 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.util
+
+import net.minecraft.registry.Registry
+import net.minecraft.registry.RegistryKey
+import net.minecraft.registry.tag.TagKey
+import net.minecraft.util.Identifier
+
+/**
+ * Utility class to create [Identifier]s and [TagKey]s using a specified [namespace][Identifier.namespace] and registry.
+ *
+ * @param TContent The type of the content to register
+ * @param modId The [namespace][Identifier.namespace] of the content to register.
+ * @param registry The registry the content is registered in
+ */
+open class TagKeyUtil(
+ protected val modId: String,
+ protected val registry: RegistryKey>
+) {
+ init {
+ require(Identifier.isNamespaceValid(modId)) { "Mod ID is not a valid namespace" }
+ }
+
+ /**
+ * Creates an [Identifier] from the namespace specified in the constructor and a given path.
+ *
+ * @param path The path of the [Identifier] to create
+ */
+ fun id(path: String): Identifier = Identifier.of(modId, path)
+
+ /**
+ * Creates a [TagKey] from the registry and namespace specified in the constructor and a given path.
+ *
+ * @param path The path of the [Identifier] to create a tag key from
+ */
+ fun tagKey(path: String): TagKey = TagKey.of(registry, id(path))
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/util/destruction/DestructionUtil.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/util/destruction/DestructionUtil.kt
index 85a5ba0b1..220f94b4d 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/util/destruction/DestructionUtil.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/util/destruction/DestructionUtil.kt
@@ -141,7 +141,7 @@ private fun destroyBlock(
world.emitGameEvent(GameEvent.BLOCK_DESTROY, pos, GameEvent.Emitter.of(destroyer, state))
}
- tool.postMine(world, breakState, pos, destroyer)
+ tool.item.postMine(tool, world, breakState, pos, destroyer)
if (!broke) return false
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/util/destruction/DiamondBlockStaffShapePredicate.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/util/destruction/DiamondBlockStaffShapePredicate.kt
index bd0d1c33e..da0fe6c67 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/util/destruction/DiamondBlockStaffShapePredicate.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/util/destruction/DiamondBlockStaffShapePredicate.kt
@@ -39,7 +39,7 @@ import kotlin.math.abs
* @param rng Random number generator deciding the pattern of destruction
*/
class DiamondBlockStaffShapePredicate(origin: BlockPos, forwardVector: Vec3i, upVector: Vec3i, rng: Random) :
- BlockDestructionPredicate {
+ IShapedBlockDestructionPredicate {
private val rightVector: Vec3i = forwardVector.crossProduct(upVector)
private val farBottomLeft = origin + forwardVector * 8 + upVector * -1 + rightVector * -4
@@ -48,10 +48,7 @@ class DiamondBlockStaffShapePredicate(origin: BlockPos, forwardVector: Vec3i, up
private val nonDestroyablePositions: Set
- /**
- * The bounding volume of the destroyable blocks.
- */
- val volume = encompassPositions(farBottomLeft, nearTopRight)!!
+ override val volume = encompassPositions(farBottomLeft, nearTopRight)!!
init {
nonDestroyablePositions = mutableSetOf()
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/util/destruction/GoldBlockStaffShapePredicate.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/util/destruction/GoldBlockStaffShapePredicate.kt
index b22d92588..3094aece7 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/util/destruction/GoldBlockStaffShapePredicate.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/util/destruction/GoldBlockStaffShapePredicate.kt
@@ -33,13 +33,10 @@ import opekope2.avm_staff.util.times
* @param upVector Vector pointing "upward" relative to the block destroyer's POV
*/
class GoldBlockStaffShapePredicate(private val origin: BlockPos, forwardVector: Vec3i, upVector: Vec3i) :
- BlockDestructionPredicate {
+ IShapedBlockDestructionPredicate {
private val rightVector = forwardVector.crossProduct(upVector)
- /**
- * The bounding volume of the destroyable blocks.
- */
- val volume = encompassPositions(
+ override val volume = encompassPositions(
origin + forwardVector + upVector + rightVector * -1,
origin + upVector * -1 + rightVector
)!!
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/util/destruction/IShapedBlockDestructionPredicate.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/util/destruction/IShapedBlockDestructionPredicate.kt
new file mode 100644
index 000000000..c9a66f791
--- /dev/null
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/util/destruction/IShapedBlockDestructionPredicate.kt
@@ -0,0 +1,31 @@
+/*
+ * AvM Staff Mod
+ * Copyright (c) 2024 opekope2
+ *
+ * This mod is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This mod is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this mod. If not, see .
+ */
+
+package opekope2.avm_staff.util.destruction
+
+import net.minecraft.util.math.BlockBox
+
+/**
+ * A [BlockDestructionPredicate], which can only break blocks in its [volume], this should be tested in [test].
+ */
+interface IShapedBlockDestructionPredicate : BlockDestructionPredicate {
+ /**
+ * The bounding volume of the destroyable blocks.
+ */
+ val volume: BlockBox
+}
diff --git a/StaffMod/src/main/kotlin/opekope2/avm_staff/util/destruction/NetheriteBlockStaffShapePredicate.kt b/StaffMod/src/main/kotlin/opekope2/avm_staff/util/destruction/NetheriteBlockStaffShapePredicate.kt
index e0e550572..b7c7caf55 100644
--- a/StaffMod/src/main/kotlin/opekope2/avm_staff/util/destruction/NetheriteBlockStaffShapePredicate.kt
+++ b/StaffMod/src/main/kotlin/opekope2/avm_staff/util/destruction/NetheriteBlockStaffShapePredicate.kt
@@ -33,17 +33,14 @@ import opekope2.avm_staff.util.times
* @param upVector Vector pointing "upward" relative to the block destroyer's POV
*/
class NetheriteBlockStaffShapePredicate(origin: BlockPos, forwardVector: Vec3i, upVector: Vec3i) :
- BlockDestructionPredicate {
+ IShapedBlockDestructionPredicate {
private val rightVector = forwardVector.crossProduct(upVector)
private val farBottomLeft = origin + forwardVector * 10 + upVector * -3 + rightVector * -6
private val furtherBottomLeft = origin + forwardVector * 11 + upVector * -3 + rightVector * -6
private val nearTopRight = origin + upVector * 9 + rightVector * 6
- /**
- * The bounding volume of the destroyable blocks.
- */
- val volume = encompassPositions(furtherBottomLeft, nearTopRight)!!
+ override val volume = encompassPositions(furtherBottomLeft, nearTopRight)!!
override fun test(world: ServerWorld, pos: BlockPos): Boolean {
if (pos !in volume) return false
diff --git a/StaffMod/src/main/resources/assets/avm_staff/lang/en_us.json b/StaffMod/src/main/resources/assets/avm_staff/lang/en_us.json
index 56aa2936d..745114325 100644
--- a/StaffMod/src/main/resources/assets/avm_staff/lang/en_us.json
+++ b/StaffMod/src/main/resources/assets/avm_staff/lang/en_us.json
@@ -1,9 +1,46 @@
{
+ "advancements.avm_staff.bazeboze.description": "Hit %s using %s",
+ "advancements.avm_staff.bazeboze.title": "Bazeboze",
+ "advancements.avm_staff.bulldozer.description": "Cause mass destruction using %s",
+ "advancements.avm_staff.bulldozer.title": "Bulldozer",
+ "advancements.avm_staff.coming_down.description": "...is the hardest thing",
+ "advancements.avm_staff.coming_down.title": "Coming down",
+ "advancements.avm_staff.coronavirus.description": "Die while wearing %s cursed with %s",
+ "advancements.avm_staff.coronavirus.title": "Coronavirus",
+ "advancements.avm_staff.engineered_attack.description": "Use %s to launch %s at someone",
+ "advancements.avm_staff.engineered_attack.title": "Trust me, I'm an engineer!",
+ "advancements.avm_staff.flying_into_space_like_a_rocket.description": "Use %s to fly above build limit",
+ "advancements.avm_staff.flying_into_space_like_a_rocket.title": "Flying into space like a rocket",
+ "advancements.avm_staff.get_pranked_lol.description": "Get thrown with %s",
+ "advancements.avm_staff.get_pranked_lol.title": "Get pranked LOL",
+ "advancements.avm_staff.infinite_power.description": "Obtain %s",
+ "advancements.avm_staff.infinite_power.title": "Infinite Power!",
+ "advancements.avm_staff.juggler.description": "Hit %s 3 times",
+ "advancements.avm_staff.juggler.title": "Juggler",
+ "advancements.avm_staff.learning_to_fly.description": "Use %s to fly",
+ "advancements.avm_staff.learning_to_fly.title": "Learning to fly",
+ "advancements.avm_staff.meet_the_engineer.description": "Get thrown with %s launched by %s",
+ "advancements.avm_staff.meet_the_engineer.title": "Meet the Engineer",
+ "advancements.avm_staff.meteor.description": "Reach Mach 0.5 while falling down",
+ "advancements.avm_staff.meteor.title": "Meteor",
+ "advancements.avm_staff.prankster.description": "Throw %s at someone",
+ "advancements.avm_staff.prankster.title": "Prankster",
"block.avm_staff.crown_of_king_orange": "Crown of King Orange",
"death.attack.avm_staff.pranked": "%s was pranked",
"death.attack.avm_staff.pranked.player": "%s was pranked by %s",
"death.attack.avm_staff.pranked.player.item": "%s was pranked by %s using %s",
+ "enchantment.avm_staff.distant_detonation": "Distant Detonation",
+ "enchantment.avm_staff.distant_detonation.desc": "Hold the use key to throw a TNT from a Staff. Release the key to detonate the TNT",
+ "enchantment.avm_staff.power_charge": "Power Charge",
+ "enchantment.avm_staff.power_charge.desc": "Fire more powerful projectiles from a Staff",
+ "enchantment.avm_staff.quick_draw": "Quick Draw",
+ "enchantment.avm_staff.quick_draw.desc": "Reduce the cooldown of a Staff",
+ "enchantment.avm_staff.rapid_fire": "Rapid Fire",
+ "enchantment.avm_staff.rapid_fire.desc": "Fire projectiles quicker from a Staff",
+ "enchantment.avm_staff.spectre": "Spectre",
+ "enchantment.avm_staff.spectre.desc": "Inflict Glowing to the attacked entity when attacking using a Staff with Bell",
"entity.avm_staff.cake": "Cake",
+ "entity.avm_staff.campfire_flame": "Campfire flame",
"entity.avm_staff.impact_tnt": "Impact TNT",
"gamerule.throwableCakes": "Throwable cakes",
"gamerule.throwableCakes.description": "Enable throwing cakes by players and dispensers",
@@ -13,6 +50,8 @@
"item.avm_staff.royal_staff": "Royal staff",
"item.avm_staff.royal_staff.with_item": "Royal staff with %s",
"item.avm_staff.royal_staff_ingredient": "Royal staff ingredient",
+ "item.avm_staff.staff": "Staff",
+ "item.avm_staff.staff.with_item": "Staff with %s",
"item.avm_staff.staff_infusion_smithing_template": "Smithing Template",
"item.avm_staff.staff_infusion_smithing_template.applies_to": "Faint staffs",
"item.avm_staff.staff_infusion_smithing_template.base_slot_description": "Add faint staff",
@@ -20,6 +59,7 @@
"itemGroup.avm_staff_items": "Staff Mod",
"key.avm_staff.add_remove_staff_item": "Add/remove staff item",
"key.categories.avm_staff": "Staff Mod",
+ "stat_type.avm_staff.used_item_in_staff": "Used in Staff",
"subtitles.entity.avm_staff.cake.splash": "Cake splashes",
"subtitles.entity.avm_staff.cake.throw": "Cake flies",
"tag.item.avm_staff.staffs": "Staffs"
diff --git a/StaffMod/src/main/resources/avm_staff.mixins.json b/StaffMod/src/main/resources/avm_staff.mixins.json
index 536daea1b..6672c255c 100644
--- a/StaffMod/src/main/resources/avm_staff.mixins.json
+++ b/StaffMod/src/main/resources/avm_staff.mixins.json
@@ -13,13 +13,16 @@
"IParticleMixin"
],
"mixins": [
+ "AbstractFurnaceBlockEntityMixin",
"BeehiveBlockMixin",
"IAbstractFurnaceBlockEntityAccessor",
"IAnvilBlockAccessor",
"IBeehiveBlockEntityAccessor",
+ "IBellBlockEntityAccessor",
"ICakeBlockAccessor",
"IPiglinBrainAccessor",
"ISmithingTemplateItemAccessor",
+ "SculkShriekerBlockEntityMixin",
"TurtleEggBlockMixin"
]
}
diff --git a/StaffMod/src/main/resources/data/avm_staff/advancement/bazeboze.json b/StaffMod/src/main/resources/data/avm_staff/advancement/bazeboze.json
new file mode 100644
index 000000000..16fb83ba6
--- /dev/null
+++ b/StaffMod/src/main/resources/data/avm_staff/advancement/bazeboze.json
@@ -0,0 +1,64 @@
+{
+ "parent": "avm_staff:bulldozer",
+ "display": {
+ "icon": {
+ "id": "avm_staff:royal_staff",
+ "components": {
+ "avm_staff:staff_item": {
+ "item": {
+ "id": "minecraft:netherite_block"
+ }
+ }
+ }
+ },
+ "title": {
+ "translate": "advancements.avm_staff.bazeboze.title"
+ },
+ "description": {
+ "translate": "advancements.avm_staff.bazeboze.description",
+ "with": [
+ [
+ "a ",
+ {
+ "translate": "entity.minecraft.bat"
+ }
+ ],
+ [
+ "a ",
+ {
+ "translate": "item.avm_staff.staff.with_item",
+ "with": [
+ {
+ "translate": "block.minecraft.netherite_block"
+ }
+ ]
+ }
+ ]
+ ]
+ }
+ },
+ "criteria": {
+ "hit_bat": {
+ "trigger": "minecraft:player_hurt_entity",
+ "conditions": {
+ "player": {
+ "equipment": {
+ "mainhand": {
+ "items": "#avm_staff:staffs",
+ "components": {
+ "avm_staff:staff_item": {
+ "item": {
+ "id": "minecraft:netherite_block"
+ }
+ }
+ }
+ }
+ }
+ },
+ "entity": {
+ "type": "minecraft:bat"
+ }
+ }
+ }
+ }
+}
diff --git a/StaffMod/src/main/resources/data/avm_staff/advancement/bulldozer.json b/StaffMod/src/main/resources/data/avm_staff/advancement/bulldozer.json
new file mode 100644
index 000000000..10c829093
--- /dev/null
+++ b/StaffMod/src/main/resources/data/avm_staff/advancement/bulldozer.json
@@ -0,0 +1,75 @@
+{
+ "parent": "avm_staff:infinite_power",
+ "display": {
+ "icon": {
+ "id": "avm_staff:royal_staff",
+ "components": {
+ "avm_staff:staff_item": {
+ "item": {
+ "id": "minecraft:diamond_block"
+ }
+ }
+ }
+ },
+ "title": {
+ "translate": "advancements.avm_staff.bulldozer.title"
+ },
+ "description": {
+ "translate": "advancements.avm_staff.bulldozer.description",
+ "with": [
+ [
+ "a ",
+ {
+ "translate": "item.avm_staff.staff"
+ }
+ ]
+ ]
+ }
+ },
+ "criteria": {
+ "use_diamond_block_staff": {
+ "trigger": "avm_staff:destroy_block_with_staff",
+ "conditions": {
+ "player": {
+ "equipment": {
+ "mainhand": {
+ "items": "#avm_staff:staffs",
+ "components": {
+ "avm_staff:staff_item": {
+ "item": {
+ "id": "minecraft:diamond_block"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "use_netherite_block_staff": {
+ "trigger": "avm_staff:destroy_block_with_staff",
+ "conditions": {
+ "player": {
+ "equipment": {
+ "mainhand": {
+ "items": "#avm_staff:staffs",
+ "components": {
+ "avm_staff:staff_item": {
+ "item": {
+ "id": "minecraft:netherite_block"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "requirements": [
+ [
+ "use_diamond_block_staff",
+ "use_netherite_block_staff"
+ ]
+ ]
+}
diff --git a/StaffMod/src/main/resources/data/avm_staff/advancement/coming_down.json b/StaffMod/src/main/resources/data/avm_staff/advancement/coming_down.json
new file mode 100644
index 000000000..e292bbcc7
--- /dev/null
+++ b/StaffMod/src/main/resources/data/avm_staff/advancement/coming_down.json
@@ -0,0 +1,68 @@
+{
+ "parent": "avm_staff:learning_to_fly",
+ "display": {
+ "icon": {
+ "id": "minecraft:damaged_anvil"
+ },
+ "title": {
+ "translate": "advancements.avm_staff.coming_down.title"
+ },
+ "description": {
+ "translate": "advancements.avm_staff.coming_down.description"
+ }
+ },
+ "criteria": {
+ "using_campfire_staff": {
+ "trigger": "avm_staff:get_hurt_while_using_item",
+ "conditions": {
+ "item": {
+ "items": "#avm_staff:staffs",
+ "components": {
+ "avm_staff:staff_item": {
+ "item": {
+ "id": "minecraft:campfire"
+ }
+ }
+ }
+ },
+ "damage_type": {
+ "tags": [
+ {
+ "id": "minecraft:is_fall",
+ "expected": true
+ }
+ ]
+ }
+ }
+ },
+ "using_soul_campfire_staff": {
+ "trigger": "avm_staff:get_hurt_while_using_item",
+ "conditions": {
+ "item": {
+ "items": "#avm_staff:staffs",
+ "components": {
+ "avm_staff:staff_item": {
+ "item": {
+ "id": "minecraft:soul_campfire"
+ }
+ }
+ }
+ },
+ "damage_type": {
+ "tags": [
+ {
+ "id": "minecraft:is_fall",
+ "expected": true
+ }
+ ]
+ }
+ }
+ }
+ },
+ "requirements": [
+ [
+ "using_campfire_staff",
+ "using_soul_campfire_staff"
+ ]
+ ]
+}
diff --git a/StaffMod/src/main/resources/data/avm_staff/advancement/coronavirus.json b/StaffMod/src/main/resources/data/avm_staff/advancement/coronavirus.json
new file mode 100644
index 000000000..a2bf12fcd
--- /dev/null
+++ b/StaffMod/src/main/resources/data/avm_staff/advancement/coronavirus.json
@@ -0,0 +1,69 @@
+{
+ "parent": "avm_staff:infinite_power",
+ "display": {
+ "icon": {
+ "id": "avm_staff:crown_of_king_orange"
+ },
+ "title": {
+ "translate": "advancements.avm_staff.coronavirus.title"
+ },
+ "description": {
+ "translate": "advancements.avm_staff.coronavirus.description",
+ "with": [
+ {
+ "translate": "block.avm_staff.crown_of_king_orange"
+ },
+ {
+ "translate": "enchantment.minecraft.binding_curse"
+ }
+ ]
+ }
+ },
+ "criteria": {
+ "die": {
+ "trigger": "minecraft:entity_hurt_player",
+ "conditions": {
+ "player": {
+ "nbt": "{Health:0f}",
+ "equipment": {
+ "head": {
+ "items": "avm_staff:crown_of_king_orange",
+ "predicates": {
+ "minecraft:enchantments": [
+ {
+ "enchantments": "minecraft:binding_curse"
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+ },
+ "survive_with_totem": {
+ "trigger": "minecraft:used_totem",
+ "conditions": {
+ "player": {
+ "equipment": {
+ "head": {
+ "items": "avm_staff:crown_of_king_orange",
+ "predicates": {
+ "minecraft:enchantments": [
+ {
+ "enchantments": "minecraft:binding_curse"
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "requirements": [
+ [
+ "die",
+ "survive_with_totem"
+ ]
+ ]
+}
diff --git a/StaffMod/src/main/resources/data/avm_staff/advancement/flying_into_space_like_a_rocket.json b/StaffMod/src/main/resources/data/avm_staff/advancement/flying_into_space_like_a_rocket.json
new file mode 100644
index 000000000..0cb8912fd
--- /dev/null
+++ b/StaffMod/src/main/resources/data/avm_staff/advancement/flying_into_space_like_a_rocket.json
@@ -0,0 +1,92 @@
+{
+ "parent": "avm_staff:learning_to_fly",
+ "display": {
+ "icon": {
+ "id": "avm_staff:royal_staff",
+ "components": {
+ "avm_staff:staff_item": {
+ "item": {
+ "id": "minecraft:soul_campfire"
+ }
+ }
+ }
+ },
+ "title": {
+ "translate": "advancements.avm_staff.flying_into_space_like_a_rocket.title"
+ },
+ "description": {
+ "translate": "advancements.avm_staff.flying_into_space_like_a_rocket.description",
+ "with": [
+ [
+ "a ",
+ {
+ "translate": "item.avm_staff.staff.with_item",
+ "with": [
+ {
+ "translate": "block.minecraft.campfire"
+ }
+ ]
+ }
+ ]
+ ]
+ }
+ },
+ "criteria": {
+ "flying_above_build_limit_using_campfire_staff": {
+ "trigger": "minecraft:using_item",
+ "conditions": {
+ "player": {
+ "location": {
+ "position": {
+ "y": {
+ "min": 320
+ }
+ }
+ }
+ },
+ "item": {
+ "items": "#avm_staff:staffs",
+ "components": {
+ "avm_staff:rocket_mode": {},
+ "avm_staff:staff_item": {
+ "item": {
+ "id": "minecraft:campfire"
+ }
+ }
+ }
+ }
+ }
+ },
+ "flying_above_build_limit_using_soul_campfire_staff": {
+ "trigger": "minecraft:using_item",
+ "conditions": {
+ "player": {
+ "location": {
+ "position": {
+ "y": {
+ "min": 320
+ }
+ }
+ }
+ },
+ "item": {
+ "items": "#avm_staff:staffs",
+ "components": {
+ "avm_staff:rocket_mode": {},
+ "avm_staff:staff_item": {
+ "item": {
+ "id": "minecraft:soul_campfire"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "requirements": [
+ [
+ "flying_above_build_limit_using_campfire_staff",
+ "flying_above_build_limit_using_soul_campfire_staff"
+ ]
+ ]
+}
diff --git a/StaffMod/src/main/resources/data/avm_staff/advancement/get_pranked_lol.json b/StaffMod/src/main/resources/data/avm_staff/advancement/get_pranked_lol.json
new file mode 100644
index 000000000..75f0c2986
--- /dev/null
+++ b/StaffMod/src/main/resources/data/avm_staff/advancement/get_pranked_lol.json
@@ -0,0 +1,42 @@
+{
+ "parent": "avm_staff:infinite_power",
+ "display": {
+ "icon": {
+ "id": "minecraft:cake"
+ },
+ "title": {
+ "translate": "advancements.avm_staff.get_pranked_lol.title"
+ },
+ "description": {
+ "translate": "advancements.avm_staff.get_pranked_lol.description",
+ "with": [
+ [
+ "a ",
+ {
+ "translate": "block.minecraft.cake"
+ }
+ ]
+ ]
+ }
+ },
+ "criteria": {
+ "get_pranked": {
+ "trigger": "minecraft:entity_hurt_player",
+ "conditions": {
+ "damage": {
+ "type": {
+ "tags": [
+ {
+ "id": "avm_staff:is_prank",
+ "expected": true
+ }
+ ],
+ "direct_entity": {
+ "type": "avm_staff:cake"
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/StaffMod/src/main/resources/data/avm_staff/advancement/infinite_power.json b/StaffMod/src/main/resources/data/avm_staff/advancement/infinite_power.json
new file mode 100644
index 000000000..6b17db508
--- /dev/null
+++ b/StaffMod/src/main/resources/data/avm_staff/advancement/infinite_power.json
@@ -0,0 +1,42 @@
+{
+ "display": {
+ "icon": {
+ "id": "avm_staff:royal_staff",
+ "components": {
+ "avm_staff:staff_item": {
+ "item": {
+ "id": "minecraft:command_block"
+ }
+ }
+ }
+ },
+ "title": {
+ "translate": "advancements.avm_staff.infinite_power.title"
+ },
+ "description": {
+ "translate": "advancements.avm_staff.infinite_power.description",
+ "with": [
+ [
+ "a ",
+ {
+ "translate": "item.avm_staff.staff"
+ }
+ ]
+ ]
+ },
+ "frame": "goal",
+ "background": "minecraft:textures/block/smithing_table_top.png"
+ },
+ "criteria": {
+ "obtain_staff": {
+ "trigger": "minecraft:inventory_changed",
+ "conditions": {
+ "items": [
+ {
+ "items": "#avm_staff:staffs"
+ }
+ ]
+ }
+ }
+ }
+}
diff --git a/StaffMod/src/main/resources/data/avm_staff/advancement/juggler.json b/StaffMod/src/main/resources/data/avm_staff/advancement/juggler.json
new file mode 100644
index 000000000..dc4df4054
--- /dev/null
+++ b/StaffMod/src/main/resources/data/avm_staff/advancement/juggler.json
@@ -0,0 +1,41 @@
+{
+ "parent": "avm_staff:infinite_power",
+ "display": {
+ "icon": {
+ "id": "avm_staff:royal_staff",
+ "components": {
+ "avm_staff:staff_item": {
+ "item": {
+ "id": "minecraft:tnt"
+ }
+ }
+ }
+ },
+ "title": {
+ "translate": "advancements.avm_staff.juggler.title"
+ },
+ "description": {
+ "translate": "advancements.avm_staff.juggler.description",
+ "with": [
+ [
+ "an ",
+ {
+ "translate": "entity.avm_staff.impact_tnt"
+ }
+ ]
+ ]
+ },
+ "frame": "challenge"
+ },
+ "criteria": {
+ "juggler": {
+ "trigger": "minecraft:player_hurt_entity",
+ "conditions": {
+ "entity": {
+ "type": "avm_staff:impact_tnt",
+ "nbt": "{Juggles:3}"
+ }
+ }
+ }
+ }
+}
diff --git a/StaffMod/src/main/resources/data/avm_staff/advancement/learning_to_fly.json b/StaffMod/src/main/resources/data/avm_staff/advancement/learning_to_fly.json
new file mode 100644
index 000000000..3c8ecba8b
--- /dev/null
+++ b/StaffMod/src/main/resources/data/avm_staff/advancement/learning_to_fly.json
@@ -0,0 +1,88 @@
+{
+ "parent": "avm_staff:infinite_power",
+ "display": {
+ "icon": {
+ "id": "avm_staff:royal_staff",
+ "components": {
+ "avm_staff:staff_item": {
+ "item": {
+ "id": "minecraft:campfire"
+ }
+ }
+ }
+ },
+ "title": {
+ "translate": "advancements.avm_staff.learning_to_fly.title"
+ },
+ "description": {
+ "translate": "advancements.avm_staff.learning_to_fly.description",
+ "with": [
+ [
+ "a ",
+ {
+ "translate": "item.avm_staff.staff.with_item",
+ "with": [
+ {
+ "translate": "block.minecraft.campfire"
+ }
+ ]
+ }
+ ]
+ ]
+ }
+ },
+ "criteria": {
+ "flying_using_campfire_staff": {
+ "trigger": "minecraft:using_item",
+ "conditions": {
+ "player": {
+ "movement": {
+ "y": {
+ "min": 5
+ }
+ }
+ },
+ "item": {
+ "items": "#avm_staff:staffs",
+ "components": {
+ "avm_staff:rocket_mode": {},
+ "avm_staff:staff_item": {
+ "item": {
+ "id": "minecraft:campfire"
+ }
+ }
+ }
+ }
+ }
+ },
+ "flying_using_soul_campfire_staff": {
+ "trigger": "minecraft:using_item",
+ "conditions": {
+ "player": {
+ "movement": {
+ "y": {
+ "min": 5
+ }
+ }
+ },
+ "item": {
+ "items": "#avm_staff:staffs",
+ "components": {
+ "avm_staff:rocket_mode": {},
+ "avm_staff:staff_item": {
+ "item": {
+ "id": "minecraft:soul_campfire"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "requirements": [
+ [
+ "flying_using_campfire_staff",
+ "flying_using_soul_campfire_staff"
+ ]
+ ]
+}
diff --git a/StaffMod/src/main/resources/data/avm_staff/advancement/meet_the_engineer.json b/StaffMod/src/main/resources/data/avm_staff/advancement/meet_the_engineer.json
new file mode 100644
index 000000000..d301de4d3
--- /dev/null
+++ b/StaffMod/src/main/resources/data/avm_staff/advancement/meet_the_engineer.json
@@ -0,0 +1,57 @@
+{
+ "parent": "avm_staff:get_pranked_lol",
+ "display": {
+ "icon": {
+ "id": "avm_staff:royal_staff",
+ "components": {
+ "avm_staff:staff_item": {
+ "item": {
+ "id": "minecraft:cake"
+ }
+ }
+ }
+ },
+ "title": {
+ "translate": "advancements.avm_staff.meet_the_engineer.title"
+ },
+ "description": {
+ "translate": "advancements.avm_staff.meet_the_engineer.description",
+ "with": [
+ [
+ "a ",
+ {
+ "translate": "block.minecraft.cake"
+ }
+ ],
+ [
+ "an ",
+ {
+ "translate": "entity.avm_staff.impact_tnt"
+ }
+ ]
+ ]
+ },
+ "hidden": true
+ },
+ "criteria": {
+ "meet_the_engineer": {
+ "trigger": "minecraft:entity_hurt_player",
+ "conditions": {
+ "damage": {
+ "type": {
+ "tags": [
+ {
+ "id": "avm_staff:is_prank",
+ "expected": true
+ }
+ ],
+ "direct_entity": {
+ "type": "avm_staff:cake",
+ "nbt": "{EngineeredAttack:1b}"
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/StaffMod/src/main/resources/data/avm_staff/advancement/meteor.json b/StaffMod/src/main/resources/data/avm_staff/advancement/meteor.json
new file mode 100644
index 000000000..33effd62f
--- /dev/null
+++ b/StaffMod/src/main/resources/data/avm_staff/advancement/meteor.json
@@ -0,0 +1,28 @@
+{
+ "parent": "avm_staff:flying_into_space_like_a_rocket",
+ "display": {
+ "icon": {
+ "id": "minecraft:fire_charge"
+ },
+ "title": {
+ "translate": "advancements.avm_staff.meteor.title"
+ },
+ "description": {
+ "translate": "advancements.avm_staff.meteor.description"
+ }
+ },
+ "criteria": {
+ "half_mach": {
+ "trigger": "minecraft:tick",
+ "conditions": {
+ "player": {
+ "movement": {
+ "y": {
+ "max": -171.5
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/StaffMod/src/main/resources/data/avm_staff/advancement/prankster.json b/StaffMod/src/main/resources/data/avm_staff/advancement/prankster.json
new file mode 100644
index 000000000..b6f2cd839
--- /dev/null
+++ b/StaffMod/src/main/resources/data/avm_staff/advancement/prankster.json
@@ -0,0 +1,45 @@
+{
+ "parent": "avm_staff:infinite_power",
+ "display": {
+ "icon": {
+ "id": "minecraft:cake"
+ },
+ "title": {
+ "translate": "advancements.avm_staff.prankster.title"
+ },
+ "description": {
+ "translate": "advancements.avm_staff.prankster.description",
+ "with": [
+ [
+ "a ",
+ {
+ "translate": "block.minecraft.cake"
+ }
+ ]
+ ]
+ }
+ },
+ "criteria": {
+ "prankster": {
+ "trigger": "minecraft:player_hurt_entity",
+ "conditions": {
+ "entity": {
+ "type": "player"
+ },
+ "damage": {
+ "type": {
+ "tags": [
+ {
+ "id": "avm_staff:is_prank",
+ "expected": true
+ }
+ ],
+ "direct_entity": {
+ "type": "avm_staff:cake"
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/StaffMod/src/main/resources/data/avm_staff/advancement/trust_me_im_an_engineer.json b/StaffMod/src/main/resources/data/avm_staff/advancement/trust_me_im_an_engineer.json
new file mode 100644
index 000000000..476e01ad0
--- /dev/null
+++ b/StaffMod/src/main/resources/data/avm_staff/advancement/trust_me_im_an_engineer.json
@@ -0,0 +1,64 @@
+{
+ "parent": "avm_staff:prankster",
+ "display": {
+ "icon": {
+ "id": "avm_staff:royal_staff",
+ "components": {
+ "avm_staff:staff_item": {
+ "item": {
+ "id": "minecraft:cake"
+ }
+ }
+ }
+ },
+ "title": {
+ "translate": "advancements.avm_staff.engineered_attack.title"
+ },
+ "description": {
+ "translate": "advancements.avm_staff.engineered_attack.description",
+ "with": [
+ [
+ "an ",
+ {
+ "translate": "entity.avm_staff.impact_tnt"
+ }
+ ],
+ [
+ "a ",
+ {
+ "translate": "block.minecraft.cake"
+ }
+ ]
+ ]
+ },
+ "frame": "challenge",
+ "hidden": true
+ },
+ "criteria": {
+ "engineered_attack": {
+ "trigger": "minecraft:player_hurt_entity",
+ "conditions": {
+ "entity": {
+ "type": "player"
+ },
+ "damage": {
+ "type": {
+ "tags": [
+ {
+ "id": "avm_staff:is_prank",
+ "expected": true
+ }
+ ],
+ "direct_entity": {
+ "type": "avm_staff:cake",
+ "nbt": "{EngineeredAttack:1b}"
+ }
+ }
+ }
+ }
+ }
+ },
+ "rewards": {
+ "experience": 50
+ }
+}
diff --git a/StaffMod/src/main/resources/data/avm_staff/enchantment/distant_detonation.json b/StaffMod/src/main/resources/data/avm_staff/enchantment/distant_detonation.json
new file mode 100644
index 000000000..f9531efea
--- /dev/null
+++ b/StaffMod/src/main/resources/data/avm_staff/enchantment/distant_detonation.json
@@ -0,0 +1,18 @@
+{
+ "description": {
+ "translate": "enchantment.avm_staff.distant_detonation"
+ },
+ "supported_items": "#avm_staff:staffs",
+ "weight": 2,
+ "max_level": 1,
+ "min_cost": {
+ "base": 20,
+ "per_level_above_first": 0
+ },
+ "max_cost": {
+ "base": 50,
+ "per_level_above_first": 0
+ },
+ "anvil_cost": 4,
+ "slots": []
+}
diff --git a/StaffMod/src/main/resources/data/avm_staff/enchantment/power_charge.json b/StaffMod/src/main/resources/data/avm_staff/enchantment/power_charge.json
new file mode 100644
index 000000000..76a1e84dc
--- /dev/null
+++ b/StaffMod/src/main/resources/data/avm_staff/enchantment/power_charge.json
@@ -0,0 +1,18 @@
+{
+ "description": {
+ "translate": "enchantment.avm_staff.power_charge"
+ },
+ "supported_items": "#avm_staff:staffs",
+ "weight": 2,
+ "max_level": 1,
+ "min_cost": {
+ "base": 20,
+ "per_level_above_first": 0
+ },
+ "max_cost": {
+ "base": 50,
+ "per_level_above_first": 0
+ },
+ "anvil_cost": 4,
+ "slots": []
+}
diff --git a/StaffMod/src/main/resources/data/avm_staff/enchantment/quick_draw.json b/StaffMod/src/main/resources/data/avm_staff/enchantment/quick_draw.json
new file mode 100644
index 000000000..c20004299
--- /dev/null
+++ b/StaffMod/src/main/resources/data/avm_staff/enchantment/quick_draw.json
@@ -0,0 +1,18 @@
+{
+ "description": {
+ "translate": "enchantment.avm_staff.quick_draw"
+ },
+ "supported_items": "#avm_staff:staffs",
+ "weight": 2,
+ "max_level": 4,
+ "min_cost": {
+ "base": 15,
+ "per_level_above_first": 9
+ },
+ "max_cost": {
+ "base": 65,
+ "per_level_above_first": 9
+ },
+ "anvil_cost": 8,
+ "slots": []
+}
diff --git a/StaffMod/src/main/resources/data/avm_staff/enchantment/rapid_fire.json b/StaffMod/src/main/resources/data/avm_staff/enchantment/rapid_fire.json
new file mode 100644
index 000000000..b93829ce9
--- /dev/null
+++ b/StaffMod/src/main/resources/data/avm_staff/enchantment/rapid_fire.json
@@ -0,0 +1,18 @@
+{
+ "description": {
+ "translate": "enchantment.avm_staff.rapid_fire"
+ },
+ "supported_items": "#avm_staff:staffs",
+ "weight": 2,
+ "max_level": 2,
+ "min_cost": {
+ "base": 10,
+ "per_level_above_first": 20
+ },
+ "max_cost": {
+ "base": 60,
+ "per_level_above_first": 20
+ },
+ "anvil_cost": 8,
+ "slots": []
+}
diff --git a/StaffMod/src/main/resources/data/avm_staff/enchantment/spectre.json b/StaffMod/src/main/resources/data/avm_staff/enchantment/spectre.json
new file mode 100644
index 000000000..9204cbe6a
--- /dev/null
+++ b/StaffMod/src/main/resources/data/avm_staff/enchantment/spectre.json
@@ -0,0 +1,62 @@
+{
+ "description": {
+ "translate": "enchantment.avm_staff.spectre"
+ },
+ "supported_items": "#avm_staff:staffs",
+ "weight": 1,
+ "max_level": 3,
+ "min_cost": {
+ "base": 10,
+ "per_level_above_first": 10
+ },
+ "max_cost": {
+ "base": 25,
+ "per_level_above_first": 10
+ },
+ "anvil_cost": 4,
+ "slots": [
+ "mainhand"
+ ],
+ "effects": {
+ "minecraft:post_attack": [
+ {
+ "enchanted": "attacker",
+ "affected": "victim",
+ "effect": {
+ "type": "minecraft:apply_mob_effect",
+ "to_apply": "minecraft:glowing",
+ "min_duration": {
+ "type": "minecraft:linear",
+ "base": 10,
+ "per_level_above_first": 10
+ },
+ "max_duration": {
+ "type": "minecraft:linear",
+ "base": 10,
+ "per_level_above_first": 10
+ },
+ "min_amplifier": 0,
+ "max_amplifier": 0
+ },
+ "requirements": {
+ "condition": "minecraft:entity_properties",
+ "entity": "attacker",
+ "predicate": {
+ "equipment": {
+ "mainhand": {
+ "items": "#avm_staff:staffs",
+ "components": {
+ "avm_staff:staff_item": {
+ "item": {
+ "id": "minecraft:bell"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ ]
+ }
+}
diff --git a/StaffMod/src/main/resources/data/avm_staff/loot_table/add_loot_pool/chests/ancient_city.json b/StaffMod/src/main/resources/data/avm_staff/loot_table/add_loot_pool/chests/ancient_city.json
new file mode 100644
index 000000000..07132058a
--- /dev/null
+++ b/StaffMod/src/main/resources/data/avm_staff/loot_table/add_loot_pool/chests/ancient_city.json
@@ -0,0 +1,35 @@
+{
+ "pools": [
+ {
+ "rolls": 1,
+ "entries": [
+ {
+ "type": "minecraft:empty",
+ "weight": 80
+ },
+ {
+ "type": "minecraft:item",
+ "name": "minecraft:book",
+ "weight": 3,
+ "functions": [
+ {
+ "function": "minecraft:enchant_randomly",
+ "options": "avm_staff:quick_draw"
+ }
+ ]
+ },
+ {
+ "type": "minecraft:item",
+ "name": "minecraft:book",
+ "weight": 3,
+ "functions": [
+ {
+ "function": "minecraft:enchant_randomly",
+ "options": "avm_staff:rapid_fire"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/StaffMod/src/main/resources/data/avm_staff/loot_table/add_loot_pool/chests/bastion_other.json b/StaffMod/src/main/resources/data/avm_staff/loot_table/add_loot_pool/chests/bastion_other.json
new file mode 100644
index 000000000..64c30b007
--- /dev/null
+++ b/StaffMod/src/main/resources/data/avm_staff/loot_table/add_loot_pool/chests/bastion_other.json
@@ -0,0 +1,24 @@
+{
+ "pools": [
+ {
+ "rolls": 1,
+ "entries": [
+ {
+ "type": "minecraft:empty",
+ "weight": 79
+ },
+ {
+ "type": "minecraft:item",
+ "name": "minecraft:book",
+ "weight": 10,
+ "functions": [
+ {
+ "function": "minecraft:enchant_randomly",
+ "options": "avm_staff:spectre"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/StaffMod/src/main/resources/data/avm_staff/tags/damage_type/is_prank.json b/StaffMod/src/main/resources/data/avm_staff/tags/damage_type/is_prank.json
new file mode 100644
index 000000000..cdcf70815
--- /dev/null
+++ b/StaffMod/src/main/resources/data/avm_staff/tags/damage_type/is_prank.json
@@ -0,0 +1,7 @@
+{
+ "replace": false,
+ "values": [
+ "avm_staff:pranked",
+ "avm_staff:pranked_by_player"
+ ]
+}
diff --git a/StaffMod/src/main/resources/data/avm_staff/tags/enchantment/redirects_impact_tnt.json b/StaffMod/src/main/resources/data/avm_staff/tags/enchantment/redirects_impact_tnt.json
new file mode 100644
index 000000000..fc88a55b6
--- /dev/null
+++ b/StaffMod/src/main/resources/data/avm_staff/tags/enchantment/redirects_impact_tnt.json
@@ -0,0 +1,6 @@
+{
+ "replace": false,
+ "values": [
+ "minecraft:silk_touch"
+ ]
+}
diff --git a/StaffMod/src/main/resources/data/avm_staff/tags/item/staffs.json b/StaffMod/src/main/resources/data/avm_staff/tags/item/staffs.json
new file mode 100644
index 000000000..d252465a6
--- /dev/null
+++ b/StaffMod/src/main/resources/data/avm_staff/tags/item/staffs.json
@@ -0,0 +1,6 @@
+{
+ "replace": false,
+ "values": [
+ "avm_staff:royal_staff"
+ ]
+}
diff --git a/StaffMod/src/main/resources/data/minecraft/tags/enchantment/non_treasure.json b/StaffMod/src/main/resources/data/minecraft/tags/enchantment/non_treasure.json
new file mode 100644
index 000000000..e2cf9ba4d
--- /dev/null
+++ b/StaffMod/src/main/resources/data/minecraft/tags/enchantment/non_treasure.json
@@ -0,0 +1,7 @@
+{
+ "replace": false,
+ "values": [
+ "avm_staff:distant_detonation",
+ "avm_staff:power_charge"
+ ]
+}
diff --git a/StaffMod/src/main/resources/data/minecraft/tags/enchantment/treasure.json b/StaffMod/src/main/resources/data/minecraft/tags/enchantment/treasure.json
new file mode 100644
index 000000000..9111ba3b8
--- /dev/null
+++ b/StaffMod/src/main/resources/data/minecraft/tags/enchantment/treasure.json
@@ -0,0 +1,8 @@
+{
+ "replace": false,
+ "values": [
+ "avm_staff:quick_draw",
+ "avm_staff:rapid_fire",
+ "avm_staff:spectre"
+ ]
+}
diff --git a/StaffMod/src/main/resources/data/minecraft/tags/item/enchantable/durability.json b/StaffMod/src/main/resources/data/minecraft/tags/item/enchantable/durability.json
new file mode 100644
index 000000000..641e9fc93
--- /dev/null
+++ b/StaffMod/src/main/resources/data/minecraft/tags/item/enchantable/durability.json
@@ -0,0 +1,6 @@
+{
+ "replace": false,
+ "values": [
+ "#avm_staff:staffs"
+ ]
+}
diff --git a/StaffMod/src/main/resources/data/minecraft/tags/items/piglin_loved.json b/StaffMod/src/main/resources/data/minecraft/tags/item/enchantable/equippable.json
similarity index 100%
rename from StaffMod/src/main/resources/data/minecraft/tags/items/piglin_loved.json
rename to StaffMod/src/main/resources/data/minecraft/tags/item/enchantable/equippable.json
diff --git a/StaffMod/src/main/resources/data/minecraft/tags/item/enchantable/mining.json b/StaffMod/src/main/resources/data/minecraft/tags/item/enchantable/mining.json
new file mode 100644
index 000000000..641e9fc93
--- /dev/null
+++ b/StaffMod/src/main/resources/data/minecraft/tags/item/enchantable/mining.json
@@ -0,0 +1,6 @@
+{
+ "replace": false,
+ "values": [
+ "#avm_staff:staffs"
+ ]
+}
diff --git a/StaffMod/src/main/resources/data/minecraft/tags/item/enchantable/mining_loot.json b/StaffMod/src/main/resources/data/minecraft/tags/item/enchantable/mining_loot.json
new file mode 100644
index 000000000..641e9fc93
--- /dev/null
+++ b/StaffMod/src/main/resources/data/minecraft/tags/item/enchantable/mining_loot.json
@@ -0,0 +1,6 @@
+{
+ "replace": false,
+ "values": [
+ "#avm_staff:staffs"
+ ]
+}
diff --git a/StaffMod/src/main/resources/data/minecraft/tags/item/enchantable/vanishing.json b/StaffMod/src/main/resources/data/minecraft/tags/item/enchantable/vanishing.json
new file mode 100644
index 000000000..ca6d02fee
--- /dev/null
+++ b/StaffMod/src/main/resources/data/minecraft/tags/item/enchantable/vanishing.json
@@ -0,0 +1,7 @@
+{
+ "replace": false,
+ "values": [
+ "#avm_staff:staffs",
+ "avm_staff:crown_of_king_orange"
+ ]
+}
diff --git a/StaffMod/src/main/resources/data/minecraft/tags/item/piglin_loved.json b/StaffMod/src/main/resources/data/minecraft/tags/item/piglin_loved.json
new file mode 100644
index 000000000..fca333cf3
--- /dev/null
+++ b/StaffMod/src/main/resources/data/minecraft/tags/item/piglin_loved.json
@@ -0,0 +1,6 @@
+{
+ "replace": false,
+ "values": [
+ "avm_staff:crown_of_king_orange"
+ ]
+}
diff --git a/build.gradle.kts b/build.gradle.kts
index faadd5316..3fa0d1ecb 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -54,12 +54,14 @@ allprojects {
apply(plugin = "architectury-plugin")
val javaVersion = rootProject.libs.versions.java.get()
+ val minecraftVersion = rootProject.libs.versions.minecraft.get()
base {
archivesName = "staff-mod"
}
- version = rootProject.libs.versions.staff.mod.get()
+ val loaderSuffix = if (extra.has("loom.platform")) "${extra["loom.platform"]}." else ""
+ version = rootProject.libs.versions.staff.mod.get() + "-" + loaderSuffix + minecraftVersion
group = "opekope2.avm_staff"
repositories {
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index ca6621f06..13be163fc 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,13 +1,12 @@
[versions]
java = "21" # Don't forget to update *.mixins.json
kotlin = "2.0.0"
-staff-mod = "0.19.3-forge"
+staff-mod = "0.20.3"
architectury-plugin = "3.4-SNAPSHOT"
architectury-loom = "1.7-SNAPSHOT"
yarn = "1.21+build.9"
shadow = "8.1.1"
minecraft = "1.21"
-fabric-loader = "0.15.11"
forge = "1.21-51.0.33"
kotlinforforge = "5.4.0"
mixin-extras = "0.4.0"
@@ -17,7 +16,6 @@ dokka = "1.9.20"
minecraft = { group = "com.mojang", name = "minecraft", version.ref = "minecraft" }
yarn = { group = "net.fabricmc", name = "yarn", version.ref = "yarn" }
kotlin-stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib", version.ref = "kotlin" }
-fabric-loader = { group = "net.fabricmc", name = "fabric-loader", version.ref = "fabric-loader" }
forge = { group = "net.minecraftforge", name = "forge", version.ref = "forge" }
kotlinforforge = { group = "thedarkcolour", name = "kotlinforforge", version.ref = "kotlinforforge" }
mixinextras-common = { group = "io.github.llamalad7", name = "mixinextras-common", version.ref = "mixin-extras" }