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" }