From ea4f696d7c9196f6eab971e1f46808bb8b2b9cdc Mon Sep 17 00:00:00 2001 From: TheDeathlyCow Date: Thu, 2 Jan 2025 00:32:12 -1000 Subject: [PATCH 1/3] bump required thermoo version --- gradle.properties | 2 +- src/main/resources/fabric.mod.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 7ee597e2..f95ed311 100644 --- a/gradle.properties +++ b/gradle.properties @@ -21,7 +21,7 @@ mod_menu_version=11.0.3 cloth_config_version=15.0.140 # Thermoo -thermoo_version=4.2.3 +thermoo_version=4.2.5 # Colorful Hearts (10.3.8) colorful_hearts_version=r2mnc8w2 diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index cddd9087..59bb51fc 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -50,7 +50,7 @@ "fabricloader": ">=0.16.9", "fabric-api": ">=0.108.0", "minecraft": "1.21.1", - "thermoo": ">=4.2.3", + "thermoo": ">=4.2.5", "java": ">=21" }, "suggests": { From fe892cbe6c826c191ffe908b4305cd6dfbb92113 Mon Sep 17 00:00:00 2001 From: TheDeathlyCow <53499406+TheDeathlyCow@users.noreply.github.com> Date: Thu, 2 Jan 2025 01:19:49 -1000 Subject: [PATCH 2/3] Frostologer rebalance (#138) * remove biter summon goal and require temperature to be high for destroy heat sources * move frostologer class to own package * move frost wand attack to own package * move frost wand cast goal to own package * dont turn water logged blocks to ice * adjust frostologer fight freezing mechanics - frostologer gains freezing when they hit root their target - frostologer is warmed each tick they are channeling their blizzard spell - frostologer no longer has passive freezing - frostologer equipment can be enchanted - frostologer has a max temp of 0, even with scorchful installed * use heat drain config value for warming * make frostologer icicle spell cooldown longer * make frost wand hit cooling configurable * start rendering frost layers earlier * freeze damage no longer breaks root * scale frostologer enchantment power by local difficulty * add enervation to frostologer spawn enchantment provider * dispense a normal vault reward from boss vault * update destroy heat sources tests * fix frost layer rendering * fix issues found in self review --- .../client/model/FrostologerEntityModel.java | 2 +- .../entity/FrostologerEntityRenderer.java | 2 +- .../client/render/feature/FrostLayers.java | 6 +- .../FrostologerCloakFeatureRenderer.java | 2 +- .../FrostologerEyesFeatureRenderer.java | 2 +- .../FrostologerFrostFeatureRenderer.java | 2 +- .../thedeathlycow/frostiful/Frostiful.java | 5 + .../config/group/CombatConfigGroup.java | 24 +- .../frostiful/entity/ChillagerEntity.java | 1 + .../frostiful/entity/RootedEntity.java | 18 +- .../frostologer/FrostWandAttackGoal.java | 19 ++ .../entity/frostologer/FrostWandCastGoal.java | 57 ++++ .../{ => frostologer}/FrostologerEntity.java | 280 ++++-------------- .../entity/root/RootedEntityImplMixin.java | 12 +- .../registry/FEnchantmentProviders.java | 19 ++ .../frostiful/registry/FEntityTypes.java | 1 + .../registry/tag/FDamageTypeTags.java | 5 +- .../assets/frostiful/lang/en_us.json | 4 +- .../frostologer_spawn_frost_wand.json | 10 + .../ominous/reward_boss.json | 15 +- .../regular/reward_boss.json | 15 +- .../tags/damage_type/does_not_break_root.json | 7 + .../FrostologerDestroyHeatSourcesTests.java | 50 ++-- 23 files changed, 241 insertions(+), 317 deletions(-) create mode 100644 src/main/java/com/github/thedeathlycow/frostiful/entity/frostologer/FrostWandAttackGoal.java create mode 100644 src/main/java/com/github/thedeathlycow/frostiful/entity/frostologer/FrostWandCastGoal.java rename src/main/java/com/github/thedeathlycow/frostiful/entity/{ => frostologer}/FrostologerEntity.java (70%) create mode 100644 src/main/java/com/github/thedeathlycow/frostiful/registry/FEnchantmentProviders.java create mode 100644 src/main/resources/data/frostiful/enchantment_provider/frostologer_spawn_frost_wand.json create mode 100644 src/main/resources/data/frostiful/tags/damage_type/does_not_break_root.json diff --git a/src/client/java/com/github/thedeathlycow/frostiful/client/model/FrostologerEntityModel.java b/src/client/java/com/github/thedeathlycow/frostiful/client/model/FrostologerEntityModel.java index cdbbbee7..f4171ab6 100644 --- a/src/client/java/com/github/thedeathlycow/frostiful/client/model/FrostologerEntityModel.java +++ b/src/client/java/com/github/thedeathlycow/frostiful/client/model/FrostologerEntityModel.java @@ -1,6 +1,6 @@ package com.github.thedeathlycow.frostiful.client.model; -import com.github.thedeathlycow.frostiful.entity.FrostologerEntity; +import com.github.thedeathlycow.frostiful.entity.frostologer.FrostologerEntity; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.model.*; diff --git a/src/client/java/com/github/thedeathlycow/frostiful/client/render/entity/FrostologerEntityRenderer.java b/src/client/java/com/github/thedeathlycow/frostiful/client/render/entity/FrostologerEntityRenderer.java index 1ff40db3..2caac14e 100644 --- a/src/client/java/com/github/thedeathlycow/frostiful/client/render/entity/FrostologerEntityRenderer.java +++ b/src/client/java/com/github/thedeathlycow/frostiful/client/render/entity/FrostologerEntityRenderer.java @@ -6,7 +6,7 @@ import com.github.thedeathlycow.frostiful.client.render.feature.FrostologerCloakFeatureRenderer; import com.github.thedeathlycow.frostiful.client.render.feature.FrostologerEyesFeatureRenderer; import com.github.thedeathlycow.frostiful.client.render.feature.FrostologerFrostFeatureRenderer; -import com.github.thedeathlycow.frostiful.entity.FrostologerEntity; +import com.github.thedeathlycow.frostiful.entity.frostologer.FrostologerEntity; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.render.entity.EntityRendererFactory; diff --git a/src/client/java/com/github/thedeathlycow/frostiful/client/render/feature/FrostLayers.java b/src/client/java/com/github/thedeathlycow/frostiful/client/render/feature/FrostLayers.java index a6da3481..66cd3e36 100644 --- a/src/client/java/com/github/thedeathlycow/frostiful/client/render/feature/FrostLayers.java +++ b/src/client/java/com/github/thedeathlycow/frostiful/client/render/feature/FrostLayers.java @@ -1,7 +1,7 @@ package com.github.thedeathlycow.frostiful.client.render.feature; import com.github.thedeathlycow.frostiful.Frostiful; -import com.github.thedeathlycow.frostiful.entity.FrostologerEntity; +import com.github.thedeathlycow.frostiful.entity.frostologer.FrostologerEntity; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.util.Identifier; @@ -13,8 +13,8 @@ public enum FrostLayers { NONE(0.0f, null), - LOW(-0.5f, Frostiful.id("textures/entity/illager/frostologer/low_frost.png")), - MEDIUM(-0.75f, Frostiful.id("textures/entity/illager/frostologer/medium_frost.png")), + LOW(-0.25f, Frostiful.id("textures/entity/illager/frostologer/low_frost.png")), + MEDIUM(-0.5f, Frostiful.id("textures/entity/illager/frostologer/medium_frost.png")), HIGH(FrostologerEntity.MAX_POWER_SCALE_START, Frostiful.id("textures/entity/illager/frostologer/high_frost.png")); public static final FrostLayers[] LAYERS_WITHOUT_NONE = Stream.of(FrostLayers.values()) diff --git a/src/client/java/com/github/thedeathlycow/frostiful/client/render/feature/FrostologerCloakFeatureRenderer.java b/src/client/java/com/github/thedeathlycow/frostiful/client/render/feature/FrostologerCloakFeatureRenderer.java index 32156012..4cab74ff 100644 --- a/src/client/java/com/github/thedeathlycow/frostiful/client/render/feature/FrostologerCloakFeatureRenderer.java +++ b/src/client/java/com/github/thedeathlycow/frostiful/client/render/feature/FrostologerCloakFeatureRenderer.java @@ -1,7 +1,7 @@ package com.github.thedeathlycow.frostiful.client.render.feature; import com.github.thedeathlycow.frostiful.client.model.FrostologerEntityModel; -import com.github.thedeathlycow.frostiful.entity.FrostologerEntity; +import com.github.thedeathlycow.frostiful.entity.frostologer.FrostologerEntity; import com.github.thedeathlycow.frostiful.item.cloak.AbstractFrostologyCloakItem; import com.github.thedeathlycow.frostiful.registry.FItems; import net.fabricmc.api.EnvType; diff --git a/src/client/java/com/github/thedeathlycow/frostiful/client/render/feature/FrostologerEyesFeatureRenderer.java b/src/client/java/com/github/thedeathlycow/frostiful/client/render/feature/FrostologerEyesFeatureRenderer.java index 34f499fc..a9d6bdb9 100644 --- a/src/client/java/com/github/thedeathlycow/frostiful/client/render/feature/FrostologerEyesFeatureRenderer.java +++ b/src/client/java/com/github/thedeathlycow/frostiful/client/render/feature/FrostologerEyesFeatureRenderer.java @@ -1,7 +1,7 @@ package com.github.thedeathlycow.frostiful.client.render.feature; import com.github.thedeathlycow.frostiful.client.model.FrostologerEntityModel; -import com.github.thedeathlycow.frostiful.entity.FrostologerEntity; +import com.github.thedeathlycow.frostiful.entity.frostologer.FrostologerEntity; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.render.OverlayTexture; diff --git a/src/client/java/com/github/thedeathlycow/frostiful/client/render/feature/FrostologerFrostFeatureRenderer.java b/src/client/java/com/github/thedeathlycow/frostiful/client/render/feature/FrostologerFrostFeatureRenderer.java index beeb1550..c5bb2f97 100644 --- a/src/client/java/com/github/thedeathlycow/frostiful/client/render/feature/FrostologerFrostFeatureRenderer.java +++ b/src/client/java/com/github/thedeathlycow/frostiful/client/render/feature/FrostologerFrostFeatureRenderer.java @@ -1,7 +1,7 @@ package com.github.thedeathlycow.frostiful.client.render.feature; import com.github.thedeathlycow.frostiful.client.model.FrostologerEntityModel; -import com.github.thedeathlycow.frostiful.entity.FrostologerEntity; +import com.github.thedeathlycow.frostiful.entity.frostologer.FrostologerEntity; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.render.VertexConsumerProvider; diff --git a/src/main/java/com/github/thedeathlycow/frostiful/Frostiful.java b/src/main/java/com/github/thedeathlycow/frostiful/Frostiful.java index 1af8702c..656daf86 100644 --- a/src/main/java/com/github/thedeathlycow/frostiful/Frostiful.java +++ b/src/main/java/com/github/thedeathlycow/frostiful/Frostiful.java @@ -2,6 +2,7 @@ import com.github.thedeathlycow.frostiful.compat.FrostifulIntegrations; import com.github.thedeathlycow.frostiful.config.FrostifulConfig; +import com.github.thedeathlycow.frostiful.entity.RootedEntity; import com.github.thedeathlycow.frostiful.entity.loot.StrayLootTableModifier; import com.github.thedeathlycow.frostiful.item.FrostedBanner; import com.github.thedeathlycow.frostiful.item.cloak.AbstractFrostologyCloakItem; @@ -19,6 +20,8 @@ import me.shedaniel.autoconfig.serializer.GsonConfigSerializer; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; +import net.fabricmc.fabric.api.entity.event.v1.ServerEntityCombatEvents; +import net.fabricmc.fabric.api.entity.event.v1.ServerLivingEntityEvents; import net.fabricmc.fabric.api.loot.v3.LootTableEvents; import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry; import net.fabricmc.fabric.api.util.TriState; @@ -75,6 +78,8 @@ public void onInitialize() { FFeatures.initialize(); FPlacedFeatures.initialize(); + ServerLivingEntityEvents.AFTER_DAMAGE.register(RootedEntity::afterDamage); + this.registerThermooEventListeners(); PayloadTypeRegistry.playS2C().register( PointWindSpawnPacket.PACKET_ID, diff --git a/src/main/java/com/github/thedeathlycow/frostiful/config/group/CombatConfigGroup.java b/src/main/java/com/github/thedeathlycow/frostiful/config/group/CombatConfigGroup.java index a3f2305e..a7319203 100644 --- a/src/main/java/com/github/thedeathlycow/frostiful/config/group/CombatConfigGroup.java +++ b/src/main/java/com/github/thedeathlycow/frostiful/config/group/CombatConfigGroup.java @@ -22,22 +22,18 @@ public class CombatConfigGroup implements ConfigData { int frostologerHeatDrainPerTick = 30; + int frostologerCoolingFromFrostWandHit = 6300 / 6; + int packedSnowballFreezeAmount = 500; float packedSnowballDamage = 2.0f; float packedSnowballVulnerableTypesDamage = 5.0f; - int frostologerPassiveFreezingPerTick = 2; - - float frostologerMaxPassiveFreezing = 0.5f; - int biterFrostBiteMaxAmplifier = 2; float chillagerFireDamageMultiplier = 1.5f; - int frostologerIntolerableHeat = 9; - @ConfigEntry.Gui.RequiresRestart float furUpgradeTemplateGenerateChance = 0.5f; @@ -76,6 +72,10 @@ public int getFrostologerHeatDrainPerTick() { return 2 * frostologerHeatDrainPerTick; } + public int getFrostologerCoolingFromFrostWandHit() { + return frostologerCoolingFromFrostWandHit; + } + public int getPackedSnowballFreezeAmount() { return packedSnowballFreezeAmount; } @@ -88,14 +88,6 @@ public float getPackedSnowballVulnerableTypesDamage() { return packedSnowballVulnerableTypesDamage; } - public int getFrostologerPassiveFreezingPerTick() { - return frostologerPassiveFreezingPerTick; - } - - public float getFrostologerMaxPassiveFreezing() { - return frostologerMaxPassiveFreezing; - } - public int getBiterFrostBiteMaxAmplifier() { return Math.max(0, this.biterFrostBiteMaxAmplifier); } @@ -104,10 +96,6 @@ public float getChillagerFireDamageMultiplier() { return chillagerFireDamageMultiplier; } - public int getFrostologerIntolerableHeat() { - return frostologerIntolerableHeat; - } - public float getFurUpgradeTemplateGenerateChance() { return MathHelper.clamp(furUpgradeTemplateGenerateChance, 0f, 1f); } diff --git a/src/main/java/com/github/thedeathlycow/frostiful/entity/ChillagerEntity.java b/src/main/java/com/github/thedeathlycow/frostiful/entity/ChillagerEntity.java index fe01f54c..f39a3376 100644 --- a/src/main/java/com/github/thedeathlycow/frostiful/entity/ChillagerEntity.java +++ b/src/main/java/com/github/thedeathlycow/frostiful/entity/ChillagerEntity.java @@ -2,6 +2,7 @@ import com.github.thedeathlycow.frostiful.Frostiful; import com.github.thedeathlycow.frostiful.config.FrostifulConfig; +import com.github.thedeathlycow.frostiful.entity.frostologer.FrostologerEntity; import com.github.thedeathlycow.frostiful.registry.FEntityTypes; import com.github.thedeathlycow.frostiful.registry.FItems; import com.github.thedeathlycow.frostiful.registry.FSoundEvents; diff --git a/src/main/java/com/github/thedeathlycow/frostiful/entity/RootedEntity.java b/src/main/java/com/github/thedeathlycow/frostiful/entity/RootedEntity.java index 00408108..30715083 100644 --- a/src/main/java/com/github/thedeathlycow/frostiful/entity/RootedEntity.java +++ b/src/main/java/com/github/thedeathlycow/frostiful/entity/RootedEntity.java @@ -2,13 +2,17 @@ import com.github.thedeathlycow.frostiful.Frostiful; import com.github.thedeathlycow.frostiful.config.FrostifulConfig; +import com.github.thedeathlycow.frostiful.entity.damage.FDamageTypes; +import com.github.thedeathlycow.frostiful.registry.tag.FDamageTypeTags; import net.minecraft.block.Blocks; import net.minecraft.entity.Entity; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.MovementType; +import net.minecraft.entity.damage.DamageSource; import net.minecraft.particle.BlockStateParticleEffect; import net.minecraft.particle.ParticleEffect; import net.minecraft.particle.ParticleTypes; +import net.minecraft.registry.tag.DamageTypeTags; import net.minecraft.server.world.ServerWorld; import net.minecraft.sound.SoundCategory; import net.minecraft.sound.SoundEvents; @@ -54,6 +58,18 @@ static Vec3d getMovementWhenRooted(MovementType type, Vec3d movement, Entity ent }; } + static void afterDamage(LivingEntity entity, DamageSource source, float baseDamageTaken, float damageTaken, boolean blocked) { + boolean breakRoot = !blocked + && damageTaken > 0f + && !source.isIn(FDamageTypeTags.DOES_NOT_BREAK_ROOT) + && entity instanceof RootedEntity rooted + && rooted.frostiful$isRooted(); + + if (breakRoot) { + RootedEntity.breakRootOnEntity(entity); + } + } + static void breakRootOnEntity(LivingEntity victim) { RootedEntity rootedEntity = (RootedEntity) victim; World world = victim.getWorld(); @@ -82,6 +98,4 @@ static void spawnShatterParticlesAndSound(LivingEntity victim, ServerWorld serve 1.0f, 0.75f ); } - - } diff --git a/src/main/java/com/github/thedeathlycow/frostiful/entity/frostologer/FrostWandAttackGoal.java b/src/main/java/com/github/thedeathlycow/frostiful/entity/frostologer/FrostWandAttackGoal.java new file mode 100644 index 00000000..e48b7779 --- /dev/null +++ b/src/main/java/com/github/thedeathlycow/frostiful/entity/frostologer/FrostWandAttackGoal.java @@ -0,0 +1,19 @@ +package com.github.thedeathlycow.frostiful.entity.frostologer; + +import net.minecraft.entity.ai.goal.AttackGoal; + +class FrostWandAttackGoal extends AttackGoal { + private final FrostologerEntity frostologerEntity; + + public FrostWandAttackGoal(FrostologerEntity frostologerEntity) { + super(frostologerEntity); + this.frostologerEntity = frostologerEntity; + } + + @Override + public boolean canStart() { + return frostologerEntity.isTargetRooted() + && super.canStart(); + } + +} \ No newline at end of file diff --git a/src/main/java/com/github/thedeathlycow/frostiful/entity/frostologer/FrostWandCastGoal.java b/src/main/java/com/github/thedeathlycow/frostiful/entity/frostologer/FrostWandCastGoal.java new file mode 100644 index 00000000..256d5d71 --- /dev/null +++ b/src/main/java/com/github/thedeathlycow/frostiful/entity/frostologer/FrostWandCastGoal.java @@ -0,0 +1,57 @@ +package com.github.thedeathlycow.frostiful.entity.frostologer; + +import com.github.thedeathlycow.frostiful.Frostiful; +import com.github.thedeathlycow.frostiful.registry.FItems; +import com.github.thedeathlycow.frostiful.registry.FSoundEvents; +import net.minecraft.entity.ai.goal.ProjectileAttackGoal; +import net.minecraft.util.Hand; + +class FrostWandCastGoal extends ProjectileAttackGoal { + + private final FrostologerEntity frostologerEntity; + + public FrostWandCastGoal(FrostologerEntity frostologer, double mobSpeed, int intervalTicks, float maxShootRange) { + super(frostologer, mobSpeed, intervalTicks, maxShootRange); + this.frostologerEntity = frostologer; + } + + @Override + public boolean canStart() { + return super.canStart() + && frostologerEntity.hasTarget() + && !frostologerEntity.isTargetRooted() + && frostologerEntity.getMainHandStack().isOf(FItems.FROST_WAND); + } + + @Override + public void start() { + super.start(); + frostologerEntity.setAttacking(true); + frostologerEntity.setCurrentHand(Hand.MAIN_HAND); + this.startUsingFrostWand(); + } + + @Override + public void stop() { + super.stop(); + frostologerEntity.setAttacking(false); + frostologerEntity.clearActiveItem(); + this.stopUsingFrostWand(); + if (frostologerEntity.isTargetRooted()) { + int cooling = -Frostiful.getConfig().combatConfig.getFrostologerCoolingFromFrostWandHit(); + frostologerEntity.thermoo$addTemperature(cooling); + } + } + + private void startUsingFrostWand() { + frostologerEntity.playSound( + FSoundEvents.ITEM_FROST_WAND_PREPARE_CAST, + 1.0f, 1.0f + ); + frostologerEntity.getDataTracker().set(FrostologerEntity.IS_USING_FROST_WAND, true); + } + + private void stopUsingFrostWand() { + frostologerEntity.getDataTracker().set(FrostologerEntity.IS_USING_FROST_WAND, false); + } +} \ No newline at end of file diff --git a/src/main/java/com/github/thedeathlycow/frostiful/entity/FrostologerEntity.java b/src/main/java/com/github/thedeathlycow/frostiful/entity/frostologer/FrostologerEntity.java similarity index 70% rename from src/main/java/com/github/thedeathlycow/frostiful/entity/FrostologerEntity.java rename to src/main/java/com/github/thedeathlycow/frostiful/entity/frostologer/FrostologerEntity.java index 0fd08d14..dece2f4e 100644 --- a/src/main/java/com/github/thedeathlycow/frostiful/entity/FrostologerEntity.java +++ b/src/main/java/com/github/thedeathlycow/frostiful/entity/frostologer/FrostologerEntity.java @@ -1,18 +1,19 @@ -package com.github.thedeathlycow.frostiful.entity; +package com.github.thedeathlycow.frostiful.entity.frostologer; import com.github.thedeathlycow.frostiful.Frostiful; import com.github.thedeathlycow.frostiful.block.FrozenTorchBlock; -import com.github.thedeathlycow.frostiful.config.FrostifulConfig; -import com.github.thedeathlycow.frostiful.config.group.CombatConfigGroup; +import com.github.thedeathlycow.frostiful.entity.BiterEntity; +import com.github.thedeathlycow.frostiful.entity.RootedEntity; +import com.github.thedeathlycow.frostiful.entity.ThrownIcicleEntity; import com.github.thedeathlycow.frostiful.item.FrostWandItem; import com.github.thedeathlycow.frostiful.item.enchantment.HeatDrainEnchantmentEffect; +import com.github.thedeathlycow.frostiful.registry.FEnchantmentProviders; import com.github.thedeathlycow.frostiful.registry.FEntityTypes; import com.github.thedeathlycow.frostiful.registry.FItems; import com.github.thedeathlycow.frostiful.registry.FSoundEvents; import com.github.thedeathlycow.frostiful.registry.tag.FBlockTags; import com.github.thedeathlycow.frostiful.registry.tag.FDamageTypeTags; import com.github.thedeathlycow.thermoo.api.ThermooAttributes; -import com.github.thedeathlycow.thermoo.api.temperature.EnvironmentController; import com.github.thedeathlycow.thermoo.api.temperature.EnvironmentManager; import com.github.thedeathlycow.thermoo.api.temperature.HeatingModes; import net.fabricmc.api.EnvType; @@ -21,9 +22,10 @@ import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.block.Blocks; +import net.minecraft.enchantment.EnchantmentHelper; +import net.minecraft.enchantment.provider.EnchantmentProviders; import net.minecraft.entity.*; import net.minecraft.entity.ai.RangedAttackMob; -import net.minecraft.entity.ai.TargetPredicate; import net.minecraft.entity.ai.goal.*; import net.minecraft.entity.attribute.DefaultAttributeContainer; import net.minecraft.entity.attribute.EntityAttributes; @@ -46,12 +48,10 @@ import net.minecraft.particle.ParticleTypes; import net.minecraft.registry.tag.DamageTypeTags; import net.minecraft.registry.tag.EntityTypeTags; -import net.minecraft.registry.tag.FluidTags; import net.minecraft.server.world.ServerWorld; import net.minecraft.sound.SoundCategory; import net.minecraft.sound.SoundEvent; import net.minecraft.sound.SoundEvents; -import net.minecraft.state.property.Properties; import net.minecraft.util.Hand; import net.minecraft.util.math.*; import net.minecraft.util.math.intprovider.IntProvider; @@ -74,13 +74,13 @@ */ public class FrostologerEntity extends SpellcastingIllagerEntity implements RangedAttackMob { - private static final TrackedData IS_USING_FROST_WAND = DataTracker.registerData( + static final TrackedData IS_USING_FROST_WAND = DataTracker.registerData( FrostologerEntity.class, TrackedDataHandlerRegistry.BOOLEAN ); - public static final float MAX_POWER_SCALE_START = -0.95f; + public static final float MAX_POWER_SCALE_START = -0.75f; private static final int NUM_POWER_PARTICLES = 2; - private static final float START_PLACING_SNOW_TEMP = -0.8f; + private static final float START_PLACING_SNOW_TEMP = -0.75f; public float prevStrideDistance; @@ -105,18 +105,8 @@ public static DefaultAttributeContainer.Builder createFrostologerAttributes() { .add(EntityAttributes.GENERIC_MOVEMENT_SPEED, 0.5) .add(EntityAttributes.GENERIC_FOLLOW_RANGE, 32.0) .add(EntityAttributes.GENERIC_MAX_HEALTH, 150.0) - .add(ThermooAttributes.MIN_TEMPERATURE, 5.0); - } - - public boolean isInHeatedArea() { - World world = this.getWorld(); - EnvironmentController controller = EnvironmentManager.INSTANCE.getController(); - - CombatConfigGroup config = Frostiful.getConfig().combatConfig; - int intolerableHeat = config.getFrostologerIntolerableHeat(); - - return world.getGameRules().getBoolean(GameRules.DO_MOB_GRIEFING) - && controller.getHeatAtLocation(world, this.getBlockPos()) > intolerableHeat; + .add(ThermooAttributes.MIN_TEMPERATURE, 5.0) + .add(ThermooAttributes.MAX_TEMPERATURE, 0.0); } public boolean isAtMaxPower() { @@ -147,11 +137,7 @@ public void destroyHeatSource(ServerWorld world, BlockState state, BlockPos bloc if (state.isIn(FBlockTags.FROSTOLOGER_CANNOT_FREEZE)) { frozenState = state; } else if (blockPos.equals(this.getBlockPos())) { - if (fluidState.isIn(FluidTags.WATER)) { - frozenState = Blocks.WATER.getDefaultState(); - } else { - frozenState = Blocks.AIR.getDefaultState(); - } + frozenState = Blocks.AIR.getDefaultState(); } else if (state.isIn(FBlockTags.HOT_FLOOR)) { frozenState = Blocks.COBBLESTONE.getDefaultState(); } else if (state.isFullCube(world, blockPos)) { @@ -161,8 +147,6 @@ public void destroyHeatSource(ServerWorld world, BlockState state, BlockPos bloc } else if (heatedBlock instanceof AbstractTorchBlock) { BlockState torch = FrozenTorchBlock.freezeTorch(state); frozenState = torch != null ? torch : Blocks.AIR.getDefaultState(); - } else if (state.contains(Properties.WATERLOGGED) && Boolean.TRUE.equals(state.get(Properties.WATERLOGGED))) { - frozenState = Blocks.ICE.getDefaultState(); } else { frozenState = Blocks.AIR.getDefaultState(); } @@ -213,9 +197,8 @@ protected void initGoals() { this.goalSelector.add(2, new FleeEntityGoal<>(this, IronGolemEntity.class, 8.0F, 1.2, 1.5)); - this.goalSelector.add(4, new FrostWandAttackGoal()); - this.goalSelector.add(4, new SummonMinionsGoal()); - this.goalSelector.add(4, new IcicleAttackGoal(UniformIntProvider.create(20, 30))); + this.goalSelector.add(3, new IcicleAttackGoal(UniformIntProvider.create(20, 30), UniformIntProvider.create(15, 25))); + this.goalSelector.add(4, new FrostWandAttackGoal(this)); this.goalSelector.add(6, new DestroyHeatSourcesGoal(15)); @@ -253,15 +236,29 @@ public EntityData initialize( @Nullable EntityData entityData ) { this.initEquipment(world.getRandom(), difficulty); + this.updateEnchantments(world, random, difficulty); return super.initialize(world, difficulty, spawnReason, entityData); } @Override - protected void initEquipment(Random random, LocalDifficulty difficulty) { + protected void enchantMainHandItem(ServerWorldAccess world, Random random, LocalDifficulty localDifficulty) { + ItemStack stack = this.getEquippedStack(EquipmentSlot.MAINHAND); + if (!stack.isEmpty()) { + EnchantmentHelper.applyEnchantmentProvider( + stack, + world.getRegistryManager(), + FEnchantmentProviders.FROSTOLOGER_SPAWN_FROST_WAND, + localDifficulty, + random + ); + this.equipStack(EquipmentSlot.MAINHAND, stack); + } + } + + @Override + public void initEquipment(Random random, LocalDifficulty difficulty) { this.setStackInHand(Hand.MAIN_HAND, new ItemStack(FItems.FROST_WAND)); this.equipStack(EquipmentSlot.CHEST, new ItemStack(FItems.FROSTOLOGY_CLOAK)); - // TODO: figure out enchanting equipment - // this.enchantMainHandItem(this.getWorld(), random, difficulty.getClampedLocalDifficulty()); // equipment drops handled with loot table this.setEquipmentDropChance(EquipmentSlot.MAINHAND, 0.0f); @@ -283,14 +280,6 @@ public void tick() { if (this.getWorld().isClient && this.isAtMaxPower()) { this.spawnPowerParticles(); } - - FrostifulConfig config = Frostiful.getConfig(); - if (this.isTargetPlayer() && this.thermoo$getTemperatureScale() > -config.combatConfig.getFrostologerMaxPassiveFreezing()) { - this.thermoo$addTemperature( - -config.combatConfig.getFrostologerPassiveFreezingPerTick(), - HeatingModes.PASSIVE - ); - } } @Override @@ -335,7 +324,6 @@ public void tickMovement() { stepPositionsPool[0] = frostologerPos; stepPositionsPool[1] = frostologerPos.down(); for (BlockPos blockPos : stepPositionsPool) { - BlockState blockState = world.getBlockState(blockPos); if (EnvironmentManager.INSTANCE.getController().isHeatSource(blockState)) { this.destroyHeatSource(serverWorld, blockState, blockPos); @@ -519,62 +507,6 @@ public void writeCustomDataToNbt(NbtCompound nbt) { nbt.putBoolean("IsUsingFrostWand", this.dataTracker.get(IS_USING_FROST_WAND)); } - protected class FrostWandAttackGoal extends AttackGoal { - public FrostWandAttackGoal() { - super(FrostologerEntity.this); - } - - @Override - public boolean canStart() { - return FrostologerEntity.this.isTargetRooted() - && super.canStart(); - } - - } - - protected class FrostWandCastGoal extends ProjectileAttackGoal { - - public FrostWandCastGoal(RangedAttackMob mob, double mobSpeed, int intervalTicks, float maxShootRange) { - super(mob, mobSpeed, intervalTicks, maxShootRange); - } - - @Override - public boolean canStart() { - return super.canStart() - && FrostologerEntity.this.hasTarget() - && !FrostologerEntity.this.isTargetRooted() - && FrostologerEntity.this.getMainHandStack().isOf(FItems.FROST_WAND); - } - - @Override - public void start() { - super.start(); - FrostologerEntity.this.setAttacking(true); - FrostologerEntity.this.setCurrentHand(Hand.MAIN_HAND); - this.startUsingFrostWand(); - } - - @Override - public void stop() { - super.stop(); - FrostologerEntity.this.setAttacking(false); - FrostologerEntity.this.clearActiveItem(); - this.stopUsingFrostWand(); - } - - private void startUsingFrostWand() { - FrostologerEntity.this.playSound( - FSoundEvents.ITEM_FROST_WAND_PREPARE_CAST, - 1.0f, 1.0f - ); - FrostologerEntity.this.dataTracker.set(IS_USING_FROST_WAND, true); - } - - private void stopUsingFrostWand() { - FrostologerEntity.this.dataTracker.set(IS_USING_FROST_WAND, false); - } - } - protected class DestroyHeatSourcesGoal extends SpellcastingIllagerEntity.CastSpellGoal { private final int range; @@ -596,49 +528,31 @@ public void start() { @Override public boolean canStart() { - // no super call as that requires a target to be selected - if (FrostologerEntity.this.isSpellcasting()) { - return false; - } else if (FrostologerEntity.this.age < this.startTime) { - return false; - } else if (!FrostologerEntity.this.hasTarget()) { - return false; - } else { - return FrostologerEntity.this.isOnFire() - || FrostologerEntity.this.isInHeatedArea(); - } - } - - @Override - public boolean shouldContinue() { - return this.spellCooldown > 0; + FrostologerEntity frostologer = FrostologerEntity.this; + return super.canStart() && frostologer.thermoo$getTemperature() <= frostologer.thermoo$getMinTemperature(); } @Override public void tick() { + FrostologerEntity frostologer = FrostologerEntity.this; - Box box = FrostologerEntity.this.getBoundingBox().expand(this.range); + Box box = frostologer.getBoundingBox().expand(this.range); - @Nullable - ServerWorld serverWorld = null; - World world = FrostologerEntity.this.getWorld(); - if (!world.isClient) { - serverWorld = (ServerWorld) world; + if (frostologer.isOnFire()) { + frostologer.extinguish(); + frostologer.playExtinguishSound(); } - if (FrostologerEntity.this.isOnFire()) { - FrostologerEntity.this.extinguish(); - FrostologerEntity.this.playExtinguishSound(); - } + World world = frostologer.getWorld(); int heatDrain = Frostiful.getConfig().combatConfig.getFrostologerHeatDrainPerTick(); - for (LivingEntity victim : world.getEntitiesByClass(LivingEntity.class, box, entity -> true)) { + frostologer.thermoo$addTemperature(heatDrain); + + for (LivingEntity victim : world.getEntitiesByClass(LivingEntity.class, box, entity -> entity != frostologer)) { victim.thermoo$addTemperature(-heatDrain, HeatingModes.ACTIVE); - if (serverWorld != null) { - HeatDrainEnchantmentEffect.addHeatDrainParticles( - serverWorld, victim, FrostologerEntity.this, 5, 0.08 - ); + if (world instanceof ServerWorld serverWorld) { + HeatDrainEnchantmentEffect.addHeatDrainParticles(serverWorld, victim, frostologer, 5, 0.08); } } @@ -647,7 +561,6 @@ public void tick() { @Override protected void castSpell() { - World world = getWorld(); if (!world.getGameRules().getBoolean(GameRules.DO_MOB_GRIEFING)) { return; @@ -655,6 +568,7 @@ protected void castSpell() { BlockPos origin = FrostologerEntity.this.getBlockPos(); Vec3i distance = new Vec3i(this.range, this.range, this.range); + for (BlockPos pos : BlockPos.iterate(origin.subtract(distance), origin.add(distance))) { BlockState state = world.getBlockState(pos); if (EnvironmentManager.INSTANCE.getController().isHeatSource(state) && world instanceof ServerWorld serverWorld) { @@ -693,109 +607,17 @@ protected Spell getSpell() { } - protected class SummonMinionsGoal extends SpellcastingIllagerEntity.CastSpellGoal { - private static final TargetPredicate IS_MINION = TargetPredicate.createNonAttackable() - .setBaseMaxDistance(16.0) - .ignoreVisibility() - .ignoreDistanceScalingFactor(); - - // I could make this configurable, but it's for the sake of stopping biter spam and not really a significant mechanic - private static final int SUMMON_BITER_COOL_DOWN = 5 * 20; - - private int nextStartTime = -1; - - @Override - public void start() { - super.start(); - if (FrostologerEntity.this.isOnFire()) { - FrostologerEntity.this.extinguish(); - FrostologerEntity.this.playExtinguishSound(); - } - } - - @Override - public boolean canStart() { - if (FrostologerEntity.this.age <= nextStartTime) { - return false; - } else if (FrostologerEntity.this.random.nextInt(2) == 0) { - return false; - } else if (!super.canStart()) { - return false; - } else if (!FrostologerEntity.this.isTargetRooted()) { - return false; - } else { - int numNearbyMinions = getWorld().getTargets( - BiterEntity.class, - IS_MINION, - FrostologerEntity.this, - FrostologerEntity.this.getBoundingBox().expand(16.0) - ).size(); - - return FrostologerEntity.this.random.nextInt(8) + 1 > numNearbyMinions; - } - } - - @Override - protected void castSpell() { - ServerWorld serverWorld = (ServerWorld) getWorld(); - nextStartTime = FrostologerEntity.this.age + SUMMON_BITER_COOL_DOWN; - - for (int i = 0; i < 3; ++i) { - BlockPos blockPos = FrostologerEntity.this.getBlockPos(); - - // use vex entity as placeholder for custom minions - BiterEntity minionEntity = FEntityTypes.BITER.create(serverWorld); - - if (minionEntity == null) { - return; - } - - minionEntity.refreshPositionAndAngles(blockPos, 0.0F, 0.0F); - - minionEntity.initialize( - serverWorld, - serverWorld.getLocalDifficulty(blockPos), - SpawnReason.MOB_SUMMONED, - null - ); - minionEntity.setOwner(FrostologerEntity.this); - - serverWorld.spawnEntityAndPassengers(minionEntity); - } - } - - @Override - protected int getSpellTicks() { - return 20; - } - - @Override - protected int startTimeDelay() { - return 20; - } - - @Nullable - @Override - protected SoundEvent getSoundPrepare() { - return SoundEvents.ENTITY_EVOKER_PREPARE_SUMMON; - } - - @Override - protected Spell getSpell() { - return Spell.SUMMON_VEX; - } - } - protected class IcicleAttackGoal extends SpellcastingIllagerEntity.CastSpellGoal { private final IntProvider numIciclesProvider; - private static final int ICICLE_ATTACK_COOL_DOWN = 20; + private final IntProvider cooldownProvider; private int nextStartTime = -1; - public IcicleAttackGoal(IntProvider numIciclesProvider) { + public IcicleAttackGoal(IntProvider numIciclesProvider, IntProvider cooldownProvider) { this.numIciclesProvider = numIciclesProvider; + this.cooldownProvider = cooldownProvider; } @Override @@ -811,8 +633,6 @@ public void start() { public boolean canStart() { if (FrostologerEntity.this.age <= nextStartTime) { return false; - } else if (FrostologerEntity.this.random.nextInt(2) == 0) { - return false; } else if (!super.canStart()) { return false; } else { @@ -825,7 +645,7 @@ protected void castSpell() { ServerWorld serverWorld = (ServerWorld) getWorld(); int numIcicles = this.numIciclesProvider.get(random); - nextStartTime = FrostologerEntity.this.age + ICICLE_ATTACK_COOL_DOWN; + nextStartTime = FrostologerEntity.this.age + cooldownProvider.get(random) * 20; for (int i = 0; i < numIcicles; ++i) { BlockPos blockPos = FrostologerEntity.this.getBlockPos() .add( diff --git a/src/main/java/com/github/thedeathlycow/frostiful/mixins/entity/root/RootedEntityImplMixin.java b/src/main/java/com/github/thedeathlycow/frostiful/mixins/entity/root/RootedEntityImplMixin.java index 1d8b796f..06281e35 100644 --- a/src/main/java/com/github/thedeathlycow/frostiful/mixins/entity/root/RootedEntityImplMixin.java +++ b/src/main/java/com/github/thedeathlycow/frostiful/mixins/entity/root/RootedEntityImplMixin.java @@ -9,6 +9,7 @@ import net.minecraft.entity.LivingEntity; import net.minecraft.entity.damage.DamageSource; import net.minecraft.particle.ParticleTypes; +import net.minecraft.registry.tag.DamageTypeTags; import net.minecraft.server.world.ServerWorld; import net.minecraft.util.profiler.Profiler; import net.minecraft.world.World; @@ -61,17 +62,6 @@ public RootedEntityImplMixin(EntityType type, World world) { return instance.thermoo$canFreeze(); } - @Inject( - method = "damage", - at = @At("RETURN") - ) - private void shatterIceOnDamaged(DamageSource source, float amount, CallbackInfoReturnable cir) { - if (!cir.getReturnValue() || source.isOf(FDamageTypes.BROKEN_ICE) || !this.frostiful$isRooted()) { - return; - } - RootedEntity.breakRootOnEntity((LivingEntity) (Object) this); - } - @Inject( method = "tickMovement", at = @At( diff --git a/src/main/java/com/github/thedeathlycow/frostiful/registry/FEnchantmentProviders.java b/src/main/java/com/github/thedeathlycow/frostiful/registry/FEnchantmentProviders.java new file mode 100644 index 00000000..9af61168 --- /dev/null +++ b/src/main/java/com/github/thedeathlycow/frostiful/registry/FEnchantmentProviders.java @@ -0,0 +1,19 @@ +package com.github.thedeathlycow.frostiful.registry; + +import com.github.thedeathlycow.frostiful.Frostiful; +import net.minecraft.enchantment.provider.EnchantmentProvider; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryKeys; + +public final class FEnchantmentProviders { + + public static final RegistryKey FROSTOLOGER_SPAWN_FROST_WAND = key("frostologer_spawn_frost_wand"); + + private static RegistryKey key(String id) { + return RegistryKey.of(RegistryKeys.ENCHANTMENT_PROVIDER, Frostiful.id(id)); + } + + private FEnchantmentProviders() { + + } +} \ No newline at end of file diff --git a/src/main/java/com/github/thedeathlycow/frostiful/registry/FEntityTypes.java b/src/main/java/com/github/thedeathlycow/frostiful/registry/FEntityTypes.java index cef42281..b950ed36 100644 --- a/src/main/java/com/github/thedeathlycow/frostiful/registry/FEntityTypes.java +++ b/src/main/java/com/github/thedeathlycow/frostiful/registry/FEntityTypes.java @@ -2,6 +2,7 @@ import com.github.thedeathlycow.frostiful.Frostiful; import com.github.thedeathlycow.frostiful.entity.*; +import com.github.thedeathlycow.frostiful.entity.frostologer.FrostologerEntity; import net.fabricmc.fabric.api.entity.event.v1.ServerEntityCombatEvents; import net.fabricmc.fabric.api.object.builder.v1.entity.FabricDefaultAttributeRegistry; import net.minecraft.entity.Entity; diff --git a/src/main/java/com/github/thedeathlycow/frostiful/registry/tag/FDamageTypeTags.java b/src/main/java/com/github/thedeathlycow/frostiful/registry/tag/FDamageTypeTags.java index 6fae99e9..7977cb14 100644 --- a/src/main/java/com/github/thedeathlycow/frostiful/registry/tag/FDamageTypeTags.java +++ b/src/main/java/com/github/thedeathlycow/frostiful/registry/tag/FDamageTypeTags.java @@ -5,13 +5,16 @@ import net.minecraft.registry.RegistryKeys; import net.minecraft.registry.tag.TagKey; -public class FDamageTypeTags { +public final class FDamageTypeTags { public static final TagKey IS_ICICLE = register("is_icicle"); + public static final TagKey DOES_NOT_BREAK_ROOT = register("does_not_break_root"); private static TagKey register(String id) { return TagKey.of(RegistryKeys.DAMAGE_TYPE, Frostiful.id(id)); } + private FDamageTypeTags() { + } } diff --git a/src/main/resources/assets/frostiful/lang/en_us.json b/src/main/resources/assets/frostiful/lang/en_us.json index 565acff0..e63cffbe 100644 --- a/src/main/resources/assets/frostiful/lang/en_us.json +++ b/src/main/resources/assets/frostiful/lang/en_us.json @@ -228,14 +228,12 @@ "text.autoconfig.frostiful.option.combatConfig.frostWandCooldown": "Frost Wand cool down (ticks)", "text.autoconfig.frostiful.option.combatConfig.frostWandRootTime": "Frost Wand root time (ticks)", "text.autoconfig.frostiful.option.combatConfig.frostologerHeatDrainPerTick": "Frostologer heat drain per tick", + "text.autoconfig.frostiful.option.combatConfig.frostologerCoolingFromFrostWandHit": "Frostologer Cooling from Frost Wand hit", "text.autoconfig.frostiful.option.combatConfig.packedSnowballFreezeAmount": "Packed snowball freeze amount", "text.autoconfig.frostiful.option.combatConfig.packedSnowballDamage": "Packed snowball damage", "text.autoconfig.frostiful.option.combatConfig.packedSnowballVulnerableTypesDamage": "Packed snowball vulnerable types damage", - "text.autoconfig.frostiful.option.combatConfig.frostologerPassiveFreezingPerTick": "Frostologer passive freezing per tick", - "text.autoconfig.frostiful.option.combatConfig.frostologerMaxPassiveFreezing": "Frostologer max passive freezing percent (0-1)", "text.autoconfig.frostiful.option.combatConfig.biterFrostBiteMaxAmplifier": "Max Frost Bite Max Amplifier (inclusive)", "text.autoconfig.frostiful.option.combatConfig.chillagerFireDamageMultiplier": "Chillager fire damage multiplier", - "text.autoconfig.frostiful.option.combatConfig.frostologerIntolerableHeat": "Frostologer intolerable heat", "text.autoconfig.frostiful.option.combatConfig.furUpgradeTemplateGenerateChance": "Fur upgrade generate chance (0-1)", "text.autoconfig.frostiful.option.combatConfig.skateUpgradeTemplateGenerateChance": "Ice Skate upgrade generate chance (0-1)", "text.autoconfig.frostiful.option.combatConfig.veryProtectiveFrostResistanceMultiplier": "Very protective Frost Resistance multiplier", diff --git a/src/main/resources/data/frostiful/enchantment_provider/frostologer_spawn_frost_wand.json b/src/main/resources/data/frostiful/enchantment_provider/frostologer_spawn_frost_wand.json new file mode 100644 index 00000000..7b65684f --- /dev/null +++ b/src/main/resources/data/frostiful/enchantment_provider/frostologer_spawn_frost_wand.json @@ -0,0 +1,10 @@ +{ + "type": "minecraft:by_cost_with_difficulty", + "enchantments": [ + "frostiful:enervation", + "frostiful:frozen_touch_curse", + "frostiful:ice_breaker" + ], + "max_cost_span": 20, + "min_cost": 5 +} \ No newline at end of file diff --git a/src/main/resources/data/frostiful/loot_table/chests/frostologer_castle/ominous/reward_boss.json b/src/main/resources/data/frostiful/loot_table/chests/frostologer_castle/ominous/reward_boss.json index 4b99934b..233b6231 100644 --- a/src/main/resources/data/frostiful/loot_table/chests/frostologer_castle/ominous/reward_boss.json +++ b/src/main/resources/data/frostiful/loot_table/chests/frostologer_castle/ominous/reward_boss.json @@ -2,24 +2,15 @@ "type": "minecraft:chest", "pools": [ { + "rolls": 1, "bonus_rolls": 0, "entries": [ { "type": "minecraft:loot_table", - "value": "frostiful:chests/frostologer_castle/ominous/reward_rare", + "value": "frostiful:chests/frostologer_castle/ominous/reward", "weight": 2 - }, - { - "type": "minecraft:loot_table", - "value": "frostiful:chests/frostologer_castle/ominous/reward_common", - "weight": 1 } - ], - "rolls": { - "type": "minecraft:uniform", - "max": 3, - "min": 1 - } + ] }, { "bonus_rolls": 0, diff --git a/src/main/resources/data/frostiful/loot_table/chests/frostologer_castle/regular/reward_boss.json b/src/main/resources/data/frostiful/loot_table/chests/frostologer_castle/regular/reward_boss.json index 7469875d..7d7c815d 100644 --- a/src/main/resources/data/frostiful/loot_table/chests/frostologer_castle/regular/reward_boss.json +++ b/src/main/resources/data/frostiful/loot_table/chests/frostologer_castle/regular/reward_boss.json @@ -2,24 +2,15 @@ "type": "minecraft:chest", "pools": [ { + "rolls": 1, "bonus_rolls": 0, "entries": [ { "type": "minecraft:loot_table", - "value": "frostiful:chests/frostologer_castle/regular/reward_rare", + "value": "frostiful:chests/frostologer_castle/regular/reward", "weight": 2 - }, - { - "type": "minecraft:loot_table", - "value": "frostiful:chests/frostologer_castle/regular/reward_common", - "weight": 1 } - ], - "rolls": { - "type": "minecraft:uniform", - "max": 3, - "min": 1 - } + ] }, { "bonus_rolls": 0, diff --git a/src/main/resources/data/frostiful/tags/damage_type/does_not_break_root.json b/src/main/resources/data/frostiful/tags/damage_type/does_not_break_root.json new file mode 100644 index 00000000..890b1ef0 --- /dev/null +++ b/src/main/resources/data/frostiful/tags/damage_type/does_not_break_root.json @@ -0,0 +1,7 @@ +{ + "replace": false, + "values": [ + "minecraft:freeze", + "frostiful:broken_ice" + ] +} \ No newline at end of file diff --git a/src/testmod/java/com/github/thedeathlycow/frostiful/test/frostologer/FrostologerDestroyHeatSourcesTests.java b/src/testmod/java/com/github/thedeathlycow/frostiful/test/frostologer/FrostologerDestroyHeatSourcesTests.java index 8194c983..54350157 100644 --- a/src/testmod/java/com/github/thedeathlycow/frostiful/test/frostologer/FrostologerDestroyHeatSourcesTests.java +++ b/src/testmod/java/com/github/thedeathlycow/frostiful/test/frostologer/FrostologerDestroyHeatSourcesTests.java @@ -1,6 +1,6 @@ package com.github.thedeathlycow.frostiful.test.frostologer; -import com.github.thedeathlycow.frostiful.entity.FrostologerEntity; +import com.github.thedeathlycow.frostiful.entity.frostologer.FrostologerEntity; import com.github.thedeathlycow.frostiful.registry.FBlocks; import com.github.thedeathlycow.frostiful.registry.FEntityTypes; import net.minecraft.block.*; @@ -74,11 +74,6 @@ public void shroomlight_becomes_ice(TestContext context) { runDestroyHeatSourceTest(context, Blocks.SHROOMLIGHT.getDefaultState(), Blocks.ICE); } - @GameTest(templateName = "frostiful-test:frostologer_heat_source_test_template") - public void sea_lantern_becomes_ice(TestContext context) { - runDestroyHeatSourceTest(context, Blocks.SEA_LANTERN.getDefaultState(), Blocks.ICE); - } - @GameTest(templateName = "frostiful-test:frostologer_heat_source_test_template") public void redstone_lamp_becomes_ice(TestContext context) { runDestroyHeatSourceTest( @@ -89,6 +84,11 @@ public void redstone_lamp_becomes_ice(TestContext context) { ); } + @GameTest(templateName = "frostiful-test:frostologer_heat_source_test_template") + public void sea_lantern_becomes_ice(TestContext context) { + runDestroyHeatSourceTest(context, Blocks.SEA_LANTERN.getDefaultState(), Blocks.ICE); + } + @GameTest(templateName = "frostiful-test:frostologer_heat_source_test_template") public void furnace_becomes_ice(TestContext context) { runDestroyHeatSourceTest( @@ -152,82 +152,92 @@ public void beacon_is_unaffected_by_frostologer(TestContext context) { runDestroyHeatSourceTest(context, Blocks.BEACON.getDefaultState(), Blocks.BEACON); } + @GameTest(templateName = "frostiful-test:frostologer_heat_source_test_template") + public void vault_is_unaffected_by_frostologer(TestContext context) { + runDestroyHeatSourceTest(context, Blocks.VAULT.getDefaultState(), Blocks.VAULT); + } + + @GameTest(templateName = "frostiful-test:frostologer_heat_source_test_template") + public void trial_spawner_is_unaffected_by_frostologer(TestContext context) { + runDestroyHeatSourceTest(context, Blocks.TRIAL_SPAWNER.getDefaultState(), Blocks.TRIAL_SPAWNER); + } + //endregion //region water logged tests @GameTest(templateName = "frostiful-test:frostologer_heat_source_test_template") - public void waterlogged_sea_pickle_becomes_ice(TestContext context) { + public void waterlogged_sea_pickle_becomes_air(TestContext context) { runDestroyHeatSourceTest( context, Blocks.SEA_PICKLE.getDefaultState() .with(SeaPickleBlock.WATERLOGGED, true) .with(SeaPickleBlock.PICKLES, 4), - Blocks.ICE + Blocks.AIR ); } @GameTest(templateName = "frostiful-test:frostologer_heat_source_test_template") - public void waterlogged_ender_chest_becomes_ice(TestContext context) { + public void waterlogged_ender_chest_becomes_air(TestContext context) { runDestroyHeatSourceTest( context, Blocks.ENDER_CHEST.getDefaultState() .with(EnderChestBlock.WATERLOGGED, true), - Blocks.ICE + Blocks.AIR ); } @GameTest(templateName = "frostiful-test:frostologer_heat_source_test_template") - public void waterlogged_lantern_becomes_ice(TestContext context) { + public void waterlogged_lantern_becomes_air(TestContext context) { runDestroyHeatSourceTest( context, Blocks.LANTERN.getDefaultState() .with(EnderChestBlock.WATERLOGGED, true), - Blocks.ICE + Blocks.AIR ); } @GameTest(templateName = "frostiful-test:frostologer_heat_source_test_template") - public void waterlogged_amethyst_cluster_becomes_ice(TestContext context) { + public void waterlogged_amethyst_cluster_becomes_air(TestContext context) { runDestroyHeatSourceTest( context, Blocks.AMETHYST_CLUSTER.getDefaultState() .with(AmethystClusterBlock.WATERLOGGED, true), - Blocks.ICE + Blocks.AIR ); } // yes this is technically a possible block state @GameTest(templateName = "frostiful-test:frostologer_heat_source_test_template") - public void waterlogged_lit_candle_becomes_ice(TestContext context) { + public void waterlogged_lit_candle_becomes_air(TestContext context) { runDestroyHeatSourceTest( context, Blocks.CANDLE.getDefaultState() .with(CandleBlock.WATERLOGGED, true) .with(CandleBlock.LIT, true), - Blocks.ICE + Blocks.AIR ); } // yes this is technically a possible block state @GameTest(templateName = "frostiful-test:frostologer_heat_source_test_template") - public void waterlogged_lit_campfire_becomes_ice(TestContext context) { + public void waterlogged_lit_campfire_becomes_air(TestContext context) { runDestroyHeatSourceTest( context, Blocks.CAMPFIRE.getDefaultState() .with(CampfireBlock.WATERLOGGED, true) .with(CampfireBlock.LIT, true), - Blocks.ICE + Blocks.AIR ); } @GameTest(templateName = "frostiful-test:frostologer_heat_source_test_template") - public void waterlogged_hot_sun_lichen_becomes_ice(TestContext context) { + public void waterlogged_hot_sun_lichen_becomes_air(TestContext context) { runDestroyHeatSourceTest( context, FBlocks.HOT_SUN_LICHEN.getDefaultState() .with(AmethystClusterBlock.WATERLOGGED, true), - Blocks.ICE + Blocks.AIR ); } From 0004622afaa69be8f4c0d6c22f4536ecace9f5cd Mon Sep 17 00:00:00 2001 From: TheDeathlyCow <53499406+TheDeathlyCow@users.noreply.github.com> Date: Thu, 2 Jan 2025 03:45:08 -1000 Subject: [PATCH 3/3] Refactor rooted interface to better use components (#139) * rewrite rooted duck interface in component * update overlay renderer * update loot condition logic * add optionality to rooted component nbt * Update RootedEffectRenderer.java * Update FrostSpellEntity.java * Update RootedTests.java * Update BiterEntity.java * Update FrostWandRootComponent.java * Update FrostologerEntity.java * Update FrostWandItem.java * update movement block * update break effects * remove rooted impl mixin * update root command * remove rooted entity class * register frost wand root in fabric.mod.json * sync rooted ticks * sync rooted component each tick --- .../client/gui/RootedOverlayRenderer.java | 17 +- .../entity_renderer/RootedEffectRenderer.java | 10 +- .../client/mixin/gui/InGameHudMixin.java | 3 +- .../thedeathlycow/frostiful/Frostiful.java | 5 +- .../frostiful/entity/BiterEntity.java | 16 +- .../frostiful/entity/FrostSpellEntity.java | 5 +- .../frostiful/entity/RootedEntity.java | 101 ---------- .../component/FrostWandRootComponent.java | 189 ++++++++++++++++++ .../component/LivingEntityComponents.java | 22 +- .../entity/frostologer/FrostologerEntity.java | 9 +- .../entity/loot/RootedLootCondition.java | 8 +- .../frostiful/item/FrostWandItem.java | 5 +- .../mixins/entity/EntityInvoker.java | 3 + .../entity/root/EntityMovementBlockMixin.java | 4 +- .../entity/root/PlayerMovementBlockMixin.java | 4 +- .../entity/root/RootedEntityImplMixin.java | 110 ---------- .../frostiful/registry/FComponents.java | 11 + .../frostiful/server/command/RootCommand.java | 9 +- src/main/resources/fabric.mod.json | 3 +- src/main/resources/frostiful.mixins.json | 1 - .../frostiful/test/effects/RootedTests.java | 21 +- 21 files changed, 268 insertions(+), 288 deletions(-) delete mode 100644 src/main/java/com/github/thedeathlycow/frostiful/entity/RootedEntity.java create mode 100644 src/main/java/com/github/thedeathlycow/frostiful/entity/component/FrostWandRootComponent.java delete mode 100644 src/main/java/com/github/thedeathlycow/frostiful/mixins/entity/root/RootedEntityImplMixin.java diff --git a/src/client/java/com/github/thedeathlycow/frostiful/client/gui/RootedOverlayRenderer.java b/src/client/java/com/github/thedeathlycow/frostiful/client/gui/RootedOverlayRenderer.java index 14ea028e..9caeff1b 100644 --- a/src/client/java/com/github/thedeathlycow/frostiful/client/gui/RootedOverlayRenderer.java +++ b/src/client/java/com/github/thedeathlycow/frostiful/client/gui/RootedOverlayRenderer.java @@ -1,10 +1,10 @@ package com.github.thedeathlycow.frostiful.client.gui; -import com.github.thedeathlycow.frostiful.Frostiful; -import com.github.thedeathlycow.frostiful.config.FrostifulConfig; -import com.github.thedeathlycow.frostiful.entity.RootedEntity; +import com.github.thedeathlycow.frostiful.entity.component.FrostWandRootComponent; +import com.github.thedeathlycow.frostiful.registry.FComponents; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.render.RenderTickCounter; +import net.minecraft.entity.LivingEntity; import net.minecraft.util.Identifier; public class RootedOverlayRenderer { @@ -13,19 +13,18 @@ public class RootedOverlayRenderer { private static final Identifier FROSTIFUL_ROOTED_OVERLAY = Identifier.ofVanilla("textures/block/ice.png"); public static void render( - RootedEntity rooted, + LivingEntity entity, DrawContext context, RenderTickCounter tickCounter, OverlayRenderCallback callback ) { - if (rooted.frostiful$isRooted()) { - FrostifulConfig config = Frostiful.getConfig(); - float opacity = ((float) rooted.frostiful$getRootedTicks()) / config.combatConfig.getFrostWandRootTime(); - callback.renderOverlay(context, FROSTIFUL_ROOTED_OVERLAY, opacity); + FrostWandRootComponent component = FComponents.FROST_WAND_ROOT_COMPONENT.get(entity); + if (component.isRooted()) { + callback.renderOverlay(context, FROSTIFUL_ROOTED_OVERLAY, component.getRootProgress()); } } private RootedOverlayRenderer() { } - + } diff --git a/src/client/java/com/github/thedeathlycow/frostiful/client/mixin/entity_renderer/RootedEffectRenderer.java b/src/client/java/com/github/thedeathlycow/frostiful/client/mixin/entity_renderer/RootedEffectRenderer.java index fdb1e140..a29246c3 100644 --- a/src/client/java/com/github/thedeathlycow/frostiful/client/mixin/entity_renderer/RootedEffectRenderer.java +++ b/src/client/java/com/github/thedeathlycow/frostiful/client/mixin/entity_renderer/RootedEffectRenderer.java @@ -1,6 +1,6 @@ package com.github.thedeathlycow.frostiful.client.mixin.entity_renderer; -import com.github.thedeathlycow.frostiful.entity.RootedEntity; +import com.github.thedeathlycow.frostiful.registry.FComponents; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.block.BlockState; @@ -45,15 +45,15 @@ private void initAddon(EntityRendererFactory.Context ctx, M model, float shadowR ) ) private void renderIceOnEntity(T livingEntity, float f, float g, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i, CallbackInfo ci) { - if (livingEntity instanceof RootedEntity rootedEntity && rootedEntity.frostiful$isRooted()) { + if (FComponents.FROST_WAND_ROOT_COMPONENT.get(livingEntity).isRooted()) { matrixStack.push(); float blockSize = 1.75f; Box boundingBox = livingEntity.getBoundingBox(); BlockPos blockPos = BlockPos.ofFloored(livingEntity.getX(), boundingBox.minY, livingEntity.getZ()); matrixStack.scale( - blockSize * (float)boundingBox.getLengthX(), - blockSize * (float)boundingBox.getLengthY(), - blockSize * (float)boundingBox.getLengthZ() + blockSize * (float) boundingBox.getLengthX(), + blockSize * (float) boundingBox.getLengthY(), + blockSize * (float) boundingBox.getLengthZ() ); matrixStack.translate(-0.5, -0.3, -0.5); this.frostiful$renderBlock(livingEntity, matrixStack, vertexConsumerProvider, blockPos); diff --git a/src/client/java/com/github/thedeathlycow/frostiful/client/mixin/gui/InGameHudMixin.java b/src/client/java/com/github/thedeathlycow/frostiful/client/mixin/gui/InGameHudMixin.java index d5b1139a..20f17810 100644 --- a/src/client/java/com/github/thedeathlycow/frostiful/client/mixin/gui/InGameHudMixin.java +++ b/src/client/java/com/github/thedeathlycow/frostiful/client/mixin/gui/InGameHudMixin.java @@ -2,7 +2,6 @@ import com.github.thedeathlycow.frostiful.client.gui.FrostOverlayRenderer; import com.github.thedeathlycow.frostiful.client.gui.RootedOverlayRenderer; -import com.github.thedeathlycow.frostiful.entity.RootedEntity; import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; @@ -57,7 +56,7 @@ private boolean blockVanillaFrozenOverlayRender( private void renderRootedOverlay(DrawContext context, RenderTickCounter tickCounter, CallbackInfo ci) { if (this.client.player != null) { RootedOverlayRenderer.render( - (RootedEntity) this.client.player, + this.client.player, context, tickCounter, this::renderOverlay ); diff --git a/src/main/java/com/github/thedeathlycow/frostiful/Frostiful.java b/src/main/java/com/github/thedeathlycow/frostiful/Frostiful.java index 656daf86..b521b5c4 100644 --- a/src/main/java/com/github/thedeathlycow/frostiful/Frostiful.java +++ b/src/main/java/com/github/thedeathlycow/frostiful/Frostiful.java @@ -2,7 +2,7 @@ import com.github.thedeathlycow.frostiful.compat.FrostifulIntegrations; import com.github.thedeathlycow.frostiful.config.FrostifulConfig; -import com.github.thedeathlycow.frostiful.entity.RootedEntity; +import com.github.thedeathlycow.frostiful.entity.component.FrostWandRootComponent; import com.github.thedeathlycow.frostiful.entity.loot.StrayLootTableModifier; import com.github.thedeathlycow.frostiful.item.FrostedBanner; import com.github.thedeathlycow.frostiful.item.cloak.AbstractFrostologyCloakItem; @@ -20,7 +20,6 @@ import me.shedaniel.autoconfig.serializer.GsonConfigSerializer; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; -import net.fabricmc.fabric.api.entity.event.v1.ServerEntityCombatEvents; import net.fabricmc.fabric.api.entity.event.v1.ServerLivingEntityEvents; import net.fabricmc.fabric.api.loot.v3.LootTableEvents; import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry; @@ -78,7 +77,7 @@ public void onInitialize() { FFeatures.initialize(); FPlacedFeatures.initialize(); - ServerLivingEntityEvents.AFTER_DAMAGE.register(RootedEntity::afterDamage); + ServerLivingEntityEvents.AFTER_DAMAGE.register(FrostWandRootComponent::afterDamage); this.registerThermooEventListeners(); PayloadTypeRegistry.playS2C().register( diff --git a/src/main/java/com/github/thedeathlycow/frostiful/entity/BiterEntity.java b/src/main/java/com/github/thedeathlycow/frostiful/entity/BiterEntity.java index ae0f3df3..1ed3554a 100644 --- a/src/main/java/com/github/thedeathlycow/frostiful/entity/BiterEntity.java +++ b/src/main/java/com/github/thedeathlycow/frostiful/entity/BiterEntity.java @@ -1,6 +1,7 @@ package com.github.thedeathlycow.frostiful.entity; import com.github.thedeathlycow.frostiful.Frostiful; +import com.github.thedeathlycow.frostiful.registry.FComponents; import com.github.thedeathlycow.frostiful.registry.FSoundEvents; import com.github.thedeathlycow.frostiful.registry.FStatusEffects; import com.github.thedeathlycow.thermoo.api.ThermooAttributes; @@ -64,16 +65,21 @@ public void tickMovement() { } } + @Override public boolean tryAttack(Entity target) { this.attackTicks = ATTACK_TIME; this.getWorld().sendEntityStatus(this, EntityStatuses.PLAY_ATTACK_SOUND); this.playAttackSound(); - if (target instanceof LivingEntity livingTarget && ((RootedEntity) livingTarget).frostiful$isRooted()) { - + if (target instanceof LivingEntity livingTarget && FComponents.FROST_WAND_ROOT_COMPONENT.get(livingTarget).isRooted()) { int maxAmplifier = Frostiful.getConfig().combatConfig.getBiterFrostBiteMaxAmplifier() + 1; livingTarget.addStatusEffect( - new StatusEffectInstance(FStatusEffects.FROST_BITE, 20 * 15, this.random.nextInt(maxAmplifier)), this + new StatusEffectInstance( + FStatusEffects.FROST_BITE, + 20 * 15, + this.random.nextInt(maxAmplifier) + ), + this ); } return super.tryAttack(target); @@ -82,7 +88,7 @@ public boolean tryAttack(Entity target) { @Override protected void initDataTracker(DataTracker.Builder builder) { super.initDataTracker(builder); - builder.add(ICE_GOLEM_FLAGS, (byte)0); + builder.add(ICE_GOLEM_FLAGS, (byte) 0); } protected void initGoals() { @@ -169,7 +175,7 @@ private void setIceGolemFlag(int mask, boolean value) { flags &= ~mask; } - this.dataTracker.set(ICE_GOLEM_FLAGS, (byte)(flags & 0xff)); + this.dataTracker.set(ICE_GOLEM_FLAGS, (byte) (flags & 0xff)); } public void playAttackSound() { diff --git a/src/main/java/com/github/thedeathlycow/frostiful/entity/FrostSpellEntity.java b/src/main/java/com/github/thedeathlycow/frostiful/entity/FrostSpellEntity.java index 1229703d..d9177c24 100644 --- a/src/main/java/com/github/thedeathlycow/frostiful/entity/FrostSpellEntity.java +++ b/src/main/java/com/github/thedeathlycow/frostiful/entity/FrostSpellEntity.java @@ -1,5 +1,6 @@ package com.github.thedeathlycow.frostiful.entity; +import com.github.thedeathlycow.frostiful.registry.FComponents; import com.github.thedeathlycow.frostiful.registry.FEntityTypes; import com.github.thedeathlycow.frostiful.registry.FSoundEvents; import net.minecraft.entity.Entity; @@ -70,8 +71,8 @@ protected void applyEffectCloud() { @Override protected void applySingleTargetEffect(Entity target) { World world = target.getWorld(); - if (!world.isClient && target instanceof RootedEntity rootedEntity) { - if (rootedEntity.frostiful$root(this.getOwner())) { + if (!world.isClient) { + if (FComponents.FROST_WAND_ROOT_COMPONENT.get(target).tryRootFromFrostWand(this.getOwner())) { world.playSound( null, target.getX(), target.getY(), target.getZ(), diff --git a/src/main/java/com/github/thedeathlycow/frostiful/entity/RootedEntity.java b/src/main/java/com/github/thedeathlycow/frostiful/entity/RootedEntity.java deleted file mode 100644 index 30715083..00000000 --- a/src/main/java/com/github/thedeathlycow/frostiful/entity/RootedEntity.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.github.thedeathlycow.frostiful.entity; - -import com.github.thedeathlycow.frostiful.Frostiful; -import com.github.thedeathlycow.frostiful.config.FrostifulConfig; -import com.github.thedeathlycow.frostiful.entity.damage.FDamageTypes; -import com.github.thedeathlycow.frostiful.registry.tag.FDamageTypeTags; -import net.minecraft.block.Blocks; -import net.minecraft.entity.Entity; -import net.minecraft.entity.LivingEntity; -import net.minecraft.entity.MovementType; -import net.minecraft.entity.damage.DamageSource; -import net.minecraft.particle.BlockStateParticleEffect; -import net.minecraft.particle.ParticleEffect; -import net.minecraft.particle.ParticleTypes; -import net.minecraft.registry.tag.DamageTypeTags; -import net.minecraft.server.world.ServerWorld; -import net.minecraft.sound.SoundCategory; -import net.minecraft.sound.SoundEvents; -import net.minecraft.util.math.Vec3d; -import net.minecraft.world.World; -import org.jetbrains.annotations.Nullable; - -public interface RootedEntity { - - int frostiful$getRootedTicks(); - - void frostiful$setRootedTicks(int amount); - - boolean frostiful$canRoot(@Nullable Entity attacker); - - default boolean frostiful$root(@Nullable Entity attacker) { - if (this.frostiful$canRoot(attacker)) { - FrostifulConfig config = Frostiful.getConfig(); - this.frostiful$setRootedTicks(config.combatConfig.getFrostWandRootTime()); - return true; - } - return false; - } - - default void frostiful$breakRoot() { - this.frostiful$setRootedTicks(1); - } - - default boolean frostiful$isRooted() { - return this.frostiful$getRootedTicks() > 0; - } - - @Nullable - static Vec3d getMovementWhenRooted(MovementType type, Vec3d movement, Entity entity) { - - if (!(entity instanceof RootedEntity rootedEntity && rootedEntity.frostiful$isRooted())) { - return null; - } - - return switch (type) { - case SELF, PLAYER -> Vec3d.ZERO.add(0, movement.y < 0 && !entity.hasNoGravity() ? movement.y : 0, 0); - default -> null; - }; - } - - static void afterDamage(LivingEntity entity, DamageSource source, float baseDamageTaken, float damageTaken, boolean blocked) { - boolean breakRoot = !blocked - && damageTaken > 0f - && !source.isIn(FDamageTypeTags.DOES_NOT_BREAK_ROOT) - && entity instanceof RootedEntity rooted - && rooted.frostiful$isRooted(); - - if (breakRoot) { - RootedEntity.breakRootOnEntity(entity); - } - } - - static void breakRootOnEntity(LivingEntity victim) { - RootedEntity rootedEntity = (RootedEntity) victim; - World world = victim.getWorld(); - if (!world.isClient && rootedEntity.frostiful$isRooted()) { - rootedEntity.frostiful$breakRoot(); - spawnShatterParticlesAndSound(victim, (ServerWorld) world); - } - } - - static void spawnShatterParticlesAndSound(LivingEntity victim, ServerWorld serverWorld) { - ParticleEffect shatteredIce = new BlockStateParticleEffect(ParticleTypes.BLOCK, Blocks.BLUE_ICE.getDefaultState()); - - serverWorld.spawnParticles( - shatteredIce, - victim.getX(), victim.getY(), victim.getZ(), - 500, - 0.5, 1.0, 0.5, - 1.0 - ); - - victim.getWorld().playSound( - null, - victim.getBlockPos(), - SoundEvents.BLOCK_GLASS_BREAK, - SoundCategory.AMBIENT, - 1.0f, 0.75f - ); - } -} diff --git a/src/main/java/com/github/thedeathlycow/frostiful/entity/component/FrostWandRootComponent.java b/src/main/java/com/github/thedeathlycow/frostiful/entity/component/FrostWandRootComponent.java new file mode 100644 index 00000000..18ffa7ad --- /dev/null +++ b/src/main/java/com/github/thedeathlycow/frostiful/entity/component/FrostWandRootComponent.java @@ -0,0 +1,189 @@ +package com.github.thedeathlycow.frostiful.entity.component; + +import com.github.thedeathlycow.frostiful.Frostiful; +import com.github.thedeathlycow.frostiful.mixins.entity.EntityInvoker; +import com.github.thedeathlycow.frostiful.registry.FComponents; +import com.github.thedeathlycow.frostiful.registry.tag.FDamageTypeTags; +import com.github.thedeathlycow.frostiful.registry.tag.FEntityTypeTags; +import net.minecraft.block.Blocks; +import net.minecraft.entity.Entity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.MovementType; +import net.minecraft.entity.damage.DamageSource; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; +import net.minecraft.network.RegistryByteBuf; +import net.minecraft.particle.BlockStateParticleEffect; +import net.minecraft.particle.ParticleEffect; +import net.minecraft.particle.ParticleTypes; +import net.minecraft.registry.RegistryWrapper; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.sound.SoundCategory; +import net.minecraft.sound.SoundEvents; +import net.minecraft.util.math.Vec3d; +import org.jetbrains.annotations.Nullable; +import org.ladysnake.cca.api.v3.component.Component; +import org.ladysnake.cca.api.v3.component.sync.AutoSyncedComponent; +import org.ladysnake.cca.api.v3.component.tick.ServerTickingComponent; + +public class FrostWandRootComponent implements Component, AutoSyncedComponent, ServerTickingComponent { + + private static final String ROOTED_TICKS_KEY = "rooted_ticks"; + + private final LivingEntity provider; + + private int rootedTicks; + + public FrostWandRootComponent(LivingEntity provider) { + this.provider = provider; + } + + public static void afterDamage( + LivingEntity entity, + DamageSource source, + float baseDamageTaken, float damageTaken, + boolean blocked + ) { + FrostWandRootComponent component = FComponents.FROST_WAND_ROOT_COMPONENT.get(entity); + boolean breakRoot = !blocked + && damageTaken > 0f + && !source.isIn(FDamageTypeTags.DOES_NOT_BREAK_ROOT) + && component.isRooted(); + + if (breakRoot) { + component.breakRoot(); + } + } + + @Nullable + public static Vec3d adjustMovementForRoot(MovementType type, Vec3d movement, Entity entity) { + if (entity instanceof LivingEntity livingEntity) { + FrostWandRootComponent component = FComponents.FROST_WAND_ROOT_COMPONENT.get(entity); + return component.adjustMovementForRoot(type, movement); + } + + return null; + } + + @Override + public void serverTick() { + if (provider.isSpectator()) { + this.setRootedTicks(0); + } else if (this.isRooted()) { + this.setRootedTicks(this.getRootedTicks() - 1); + + if (provider.isOnFire()) { + this.breakRoot(); + provider.extinguish(); + ((EntityInvoker) provider).frostiful$invokePlayExtinguishSound(); + } + } + } + + public float getRootProgress() { + return (float) this.rootedTicks / Frostiful.getConfig().combatConfig.getFrostWandRootTime(); + } + + public void breakRoot() { + if (this.isRooted() && provider.getWorld() instanceof ServerWorld serverWorld) { + this.setRootedTicks(1); // set to 1 so the icebreaker enchantment can detect it + spawnShatterParticlesAndSound(provider, serverWorld); + } + } + + public boolean tryRootFromFrostWand(@Nullable Entity originalCaster) { + if (this.canBeRootedBy(originalCaster)) { + this.setRootedTicks(Frostiful.getConfig().combatConfig.getFrostWandRootTime()); + return true; + } + return false; + } + + @Override + public void writeSyncPacket(RegistryByteBuf buf, ServerPlayerEntity recipient) { + buf.writeVarInt(this.rootedTicks); + } + + @Override + public void applySyncPacket(RegistryByteBuf buf) { + this.rootedTicks = buf.readVarInt(); + } + + @Override + public void readFromNbt(NbtCompound tag, RegistryWrapper.WrapperLookup registryLookup) { + this.rootedTicks = tag.contains(ROOTED_TICKS_KEY, NbtElement.INT_TYPE) + ? tag.getInt(ROOTED_TICKS_KEY) + : 0; + } + + @Override + public void writeToNbt(NbtCompound tag, RegistryWrapper.WrapperLookup registryLookup) { + if (this.rootedTicks != 0) { + tag.putInt(ROOTED_TICKS_KEY, this.rootedTicks); + } + } + + public boolean isRooted() { + return this.getRootedTicks() > 0; + } + + public int getRootedTicks() { + return rootedTicks; + } + + public void setRootedTicks(int rootedTicks) { + if (this.rootedTicks != rootedTicks) { + this.rootedTicks = rootedTicks; + FComponents.FROST_WAND_ROOT_COMPONENT.sync(this.provider); + } + } + + private boolean canBeRootedBy(@Nullable Entity originalCaster) { + if (this.isRooted()) { + return false; + } + + if (provider.getType().isIn(FEntityTypeTags.ROOT_IMMUNE)) { + return false; + } + + if (originalCaster != null && provider.isTeammate(originalCaster)) { + return false; + } + + return provider.thermoo$canFreeze(); + } + + @Nullable + private Vec3d adjustMovementForRoot(MovementType type, Vec3d movement) { + if (!this.isRooted()) { + return null; + } + + return switch (type) { + case SELF, PLAYER -> Vec3d.ZERO.add(0, movement.y < 0 && !provider.hasNoGravity() ? movement.y : 0, 0); + default -> null; + }; + } + + private static void spawnShatterParticlesAndSound(LivingEntity victim, ServerWorld serverWorld) { + ParticleEffect shatteredIce = new BlockStateParticleEffect(ParticleTypes.BLOCK, Blocks.BLUE_ICE.getDefaultState()); + + serverWorld.spawnParticles( + shatteredIce, + victim.getX(), victim.getY(), victim.getZ(), + 500, + 0.5, 1.0, 0.5, + 1.0 + ); + + victim.getWorld().playSound( + null, + victim.getBlockPos(), + SoundEvents.BLOCK_GLASS_BREAK, + SoundCategory.AMBIENT, + 1.0f, 0.75f + ); + } +} \ No newline at end of file diff --git a/src/main/java/com/github/thedeathlycow/frostiful/entity/component/LivingEntityComponents.java b/src/main/java/com/github/thedeathlycow/frostiful/entity/component/LivingEntityComponents.java index 3bfaadb7..2a9c083f 100644 --- a/src/main/java/com/github/thedeathlycow/frostiful/entity/component/LivingEntityComponents.java +++ b/src/main/java/com/github/thedeathlycow/frostiful/entity/component/LivingEntityComponents.java @@ -10,10 +10,6 @@ import org.ladysnake.cca.api.v3.component.sync.AutoSyncedComponent; public class LivingEntityComponents implements Component, AutoSyncedComponent { - - private static final String ROOTED_TICKS_KEY = "rooted_ticks"; - - private int rootedTicks; private byte skateFlags; private final LivingEntity provider; @@ -22,18 +18,6 @@ public LivingEntityComponents(LivingEntity provider) { this.provider = provider; } - - public int getRootedTicks() { - return rootedTicks; - } - - public void setRootedTicks(int rootedTicks) { - if (this.rootedTicks != rootedTicks) { - this.rootedTicks = rootedTicks; - FComponents.ENTITY_COMPONENTS.sync(this.provider); - } - } - public byte getSkateFlags() { return skateFlags; } @@ -47,23 +31,21 @@ public void setSkateFlags(byte skateFlags) { @Override public void writeSyncPacket(RegistryByteBuf buf, ServerPlayerEntity recipient) { - buf.writeVarInt(this.rootedTicks); buf.writeByte(this.skateFlags); } @Override public void applySyncPacket(RegistryByteBuf buf) { - this.rootedTicks = buf.readVarInt(); this.skateFlags = buf.readByte(); } @Override public void readFromNbt(NbtCompound tag, RegistryWrapper.WrapperLookup registryLookup) { - this.rootedTicks = tag.getInt(ROOTED_TICKS_KEY); + // nothing to save } @Override public void writeToNbt(NbtCompound tag, RegistryWrapper.WrapperLookup registryLookup) { - tag.putInt(ROOTED_TICKS_KEY, this.rootedTicks); + // nothing to save } } diff --git a/src/main/java/com/github/thedeathlycow/frostiful/entity/frostologer/FrostologerEntity.java b/src/main/java/com/github/thedeathlycow/frostiful/entity/frostologer/FrostologerEntity.java index dece2f4e..657f1f42 100644 --- a/src/main/java/com/github/thedeathlycow/frostiful/entity/frostologer/FrostologerEntity.java +++ b/src/main/java/com/github/thedeathlycow/frostiful/entity/frostologer/FrostologerEntity.java @@ -3,14 +3,10 @@ import com.github.thedeathlycow.frostiful.Frostiful; import com.github.thedeathlycow.frostiful.block.FrozenTorchBlock; import com.github.thedeathlycow.frostiful.entity.BiterEntity; -import com.github.thedeathlycow.frostiful.entity.RootedEntity; import com.github.thedeathlycow.frostiful.entity.ThrownIcicleEntity; import com.github.thedeathlycow.frostiful.item.FrostWandItem; import com.github.thedeathlycow.frostiful.item.enchantment.HeatDrainEnchantmentEffect; -import com.github.thedeathlycow.frostiful.registry.FEnchantmentProviders; -import com.github.thedeathlycow.frostiful.registry.FEntityTypes; -import com.github.thedeathlycow.frostiful.registry.FItems; -import com.github.thedeathlycow.frostiful.registry.FSoundEvents; +import com.github.thedeathlycow.frostiful.registry.*; import com.github.thedeathlycow.frostiful.registry.tag.FBlockTags; import com.github.thedeathlycow.frostiful.registry.tag.FDamageTypeTags; import com.github.thedeathlycow.thermoo.api.ThermooAttributes; @@ -23,7 +19,6 @@ import net.minecraft.block.BlockState; import net.minecraft.block.Blocks; import net.minecraft.enchantment.EnchantmentHelper; -import net.minecraft.enchantment.provider.EnchantmentProviders; import net.minecraft.entity.*; import net.minecraft.entity.ai.RangedAttackMob; import net.minecraft.entity.ai.goal.*; @@ -394,7 +389,7 @@ public boolean isTargetPlayer() { public boolean isTargetRooted() { LivingEntity target = this.getTarget(); return target != null - && ((RootedEntity) target).frostiful$isRooted(); + && FComponents.FROST_WAND_ROOT_COMPONENT.get(target).isRooted(); } public boolean isUsingFrostWand() { diff --git a/src/main/java/com/github/thedeathlycow/frostiful/entity/loot/RootedLootCondition.java b/src/main/java/com/github/thedeathlycow/frostiful/entity/loot/RootedLootCondition.java index c1a256b9..2eaf2a0d 100644 --- a/src/main/java/com/github/thedeathlycow/frostiful/entity/loot/RootedLootCondition.java +++ b/src/main/java/com/github/thedeathlycow/frostiful/entity/loot/RootedLootCondition.java @@ -1,6 +1,7 @@ package com.github.thedeathlycow.frostiful.entity.loot; -import com.github.thedeathlycow.frostiful.entity.RootedEntity; +import com.github.thedeathlycow.frostiful.entity.component.FrostWandRootComponent; +import com.github.thedeathlycow.frostiful.registry.FComponents; import com.github.thedeathlycow.frostiful.registry.FLootConditionTypes; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; @@ -31,8 +32,9 @@ public LootConditionType getType() { @Override public boolean test(LootContext lootContext) { Entity entity = lootContext.get(LootContextParameters.THIS_ENTITY); - if (entity instanceof RootedEntity rootable) { - return this.rootTicksRemaining.test(rootable.frostiful$getRootedTicks()); + if (entity != null) { + FrostWandRootComponent component = FComponents.FROST_WAND_ROOT_COMPONENT.get(entity); + return this.rootTicksRemaining.test(component.getRootedTicks()); } return false; diff --git a/src/main/java/com/github/thedeathlycow/frostiful/item/FrostWandItem.java b/src/main/java/com/github/thedeathlycow/frostiful/item/FrostWandItem.java index e6ee2538..42926603 100644 --- a/src/main/java/com/github/thedeathlycow/frostiful/item/FrostWandItem.java +++ b/src/main/java/com/github/thedeathlycow/frostiful/item/FrostWandItem.java @@ -3,7 +3,7 @@ import com.github.thedeathlycow.frostiful.Frostiful; import com.github.thedeathlycow.frostiful.config.FrostifulConfig; import com.github.thedeathlycow.frostiful.entity.FrostSpellEntity; -import com.github.thedeathlycow.frostiful.entity.RootedEntity; +import com.github.thedeathlycow.frostiful.registry.FComponents; import com.github.thedeathlycow.frostiful.registry.FItems; import com.github.thedeathlycow.frostiful.registry.FSoundEvents; import net.minecraft.block.BlockState; @@ -82,7 +82,8 @@ public boolean canRepair(ItemStack stack, ItemStack ingredient) { @Override public float getBonusAttackDamage(Entity target, float baseAttackDamage, DamageSource damageSource) { Entity attacker = damageSource.getAttacker(); - boolean resetCooldown = target instanceof RootedEntity rooted && rooted.frostiful$isRooted(); + boolean resetCooldown = target instanceof LivingEntity livingEntity + && FComponents.FROST_WAND_ROOT_COMPONENT.get(livingEntity).isRooted(); if (attacker instanceof PlayerEntity player && resetCooldown) { player.getItemCooldownManager().set(this, 0); } diff --git a/src/main/java/com/github/thedeathlycow/frostiful/mixins/entity/EntityInvoker.java b/src/main/java/com/github/thedeathlycow/frostiful/mixins/entity/EntityInvoker.java index 2527dca6..2e9c632d 100644 --- a/src/main/java/com/github/thedeathlycow/frostiful/mixins/entity/EntityInvoker.java +++ b/src/main/java/com/github/thedeathlycow/frostiful/mixins/entity/EntityInvoker.java @@ -13,4 +13,7 @@ public interface EntityInvoker { @Invoker("isInsideBubbleColumn") boolean frostiful$invokeIsInsideBubbleColumn(); + @Invoker("playExtinguishSound") + void frostiful$invokePlayExtinguishSound(); + } diff --git a/src/main/java/com/github/thedeathlycow/frostiful/mixins/entity/root/EntityMovementBlockMixin.java b/src/main/java/com/github/thedeathlycow/frostiful/mixins/entity/root/EntityMovementBlockMixin.java index 628ddcb7..7cc2792e 100644 --- a/src/main/java/com/github/thedeathlycow/frostiful/mixins/entity/root/EntityMovementBlockMixin.java +++ b/src/main/java/com/github/thedeathlycow/frostiful/mixins/entity/root/EntityMovementBlockMixin.java @@ -1,6 +1,6 @@ package com.github.thedeathlycow.frostiful.mixins.entity.root; -import com.github.thedeathlycow.frostiful.entity.RootedEntity; +import com.github.thedeathlycow.frostiful.entity.component.FrostWandRootComponent; import net.minecraft.entity.Entity; import net.minecraft.entity.MovementType; import net.minecraft.util.math.Vec3d; @@ -20,7 +20,7 @@ public class EntityMovementBlockMixin { private void blockMovementForRootedEntities(Vec3d movement, MovementType type, CallbackInfoReturnable cir) { Entity instance = (Entity) (Object) this; - Vec3d adjustedMovement = RootedEntity.getMovementWhenRooted(type, movement, instance); + Vec3d adjustedMovement = FrostWandRootComponent.adjustMovementForRoot(type, movement, instance); if (adjustedMovement != null) { cir.setReturnValue(adjustedMovement); } diff --git a/src/main/java/com/github/thedeathlycow/frostiful/mixins/entity/root/PlayerMovementBlockMixin.java b/src/main/java/com/github/thedeathlycow/frostiful/mixins/entity/root/PlayerMovementBlockMixin.java index dc823db1..d277fe94 100644 --- a/src/main/java/com/github/thedeathlycow/frostiful/mixins/entity/root/PlayerMovementBlockMixin.java +++ b/src/main/java/com/github/thedeathlycow/frostiful/mixins/entity/root/PlayerMovementBlockMixin.java @@ -1,6 +1,6 @@ package com.github.thedeathlycow.frostiful.mixins.entity.root; -import com.github.thedeathlycow.frostiful.entity.RootedEntity; +import com.github.thedeathlycow.frostiful.entity.component.FrostWandRootComponent; import net.minecraft.entity.MovementType; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.util.math.Vec3d; @@ -23,7 +23,7 @@ public class PlayerMovementBlockMixin { private void blockMovementForRootedEntities(Vec3d movement, MovementType type, CallbackInfoReturnable cir) { PlayerEntity instance = (PlayerEntity) (Object) this; - Vec3d adjustedMovement = RootedEntity.getMovementWhenRooted(type, movement, instance); + Vec3d adjustedMovement = FrostWandRootComponent.adjustMovementForRoot(type, movement, instance); if (adjustedMovement != null) { cir.setReturnValue(adjustedMovement); } diff --git a/src/main/java/com/github/thedeathlycow/frostiful/mixins/entity/root/RootedEntityImplMixin.java b/src/main/java/com/github/thedeathlycow/frostiful/mixins/entity/root/RootedEntityImplMixin.java deleted file mode 100644 index 06281e35..00000000 --- a/src/main/java/com/github/thedeathlycow/frostiful/mixins/entity/root/RootedEntityImplMixin.java +++ /dev/null @@ -1,110 +0,0 @@ -package com.github.thedeathlycow.frostiful.mixins.entity.root; - -import com.github.thedeathlycow.frostiful.entity.RootedEntity; -import com.github.thedeathlycow.frostiful.entity.damage.FDamageTypes; -import com.github.thedeathlycow.frostiful.registry.FComponents; -import com.github.thedeathlycow.frostiful.registry.tag.FEntityTypeTags; -import net.minecraft.entity.Entity; -import net.minecraft.entity.EntityType; -import net.minecraft.entity.LivingEntity; -import net.minecraft.entity.damage.DamageSource; -import net.minecraft.particle.ParticleTypes; -import net.minecraft.registry.tag.DamageTypeTags; -import net.minecraft.server.world.ServerWorld; -import net.minecraft.util.profiler.Profiler; -import net.minecraft.world.World; -import org.jetbrains.annotations.Nullable; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.Slice; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -@Mixin(LivingEntity.class) -public abstract class RootedEntityImplMixin extends Entity implements RootedEntity { - - @Shadow - public abstract boolean damage(DamageSource source, float amount); - - - public RootedEntityImplMixin(EntityType type, World world) { - super(type, world); - } - - @Override - public int frostiful$getRootedTicks() { - return FComponents.ENTITY_COMPONENTS.get(this).getRootedTicks(); - } - - @Override - public void frostiful$setRootedTicks(int amount) { - FComponents.ENTITY_COMPONENTS.get(this).setRootedTicks(amount); - } - - @Override - public boolean frostiful$canRoot(@Nullable Entity attacker) { - if (this.frostiful$isRooted()) { - return false; - } - - final LivingEntity instance = (LivingEntity) (Object) this; - - if (instance.getType().isIn(FEntityTypeTags.ROOT_IMMUNE)) { - return false; - } - - if (attacker != null && this.isTeammate(attacker)) { - return false; - } - - return instance.thermoo$canFreeze(); - } - - @Inject( - method = "tickMovement", - at = @At( - value = "INVOKE", - target = "Lnet/minecraft/util/profiler/Profiler;pop()V", - ordinal = 1 - ), - slice = @Slice( - from = @At( - value = "INVOKE", - target = "Lnet/minecraft/entity/LivingEntity;setVelocity(DDD)V" - ) - ) - ) - private void tickRoot(CallbackInfo ci) { - World world = getWorld(); - Profiler profiler = world.getProfiler(); - profiler.push("frostiful.rooted_tick"); - if (this.isSpectator()) { - this.frostiful$setRootedTicks(0); - } else if (this.frostiful$isRooted()) { - // remove tick - int ticksLeft = this.frostiful$getRootedTicks(); - this.frostiful$setRootedTicks(--ticksLeft); - - if (!world.isClient && this.isOnFire()) { - this.frostiful$breakRoot(); - this.extinguish(); - ServerWorld serverWorld = (ServerWorld) world; - serverWorld.spawnParticles( - ParticleTypes.POOF, - this.getX(), this.getY(), this.getZ(), - 100, - 0.5, 0.5, 0.5, - 0.1f - ); - this.playExtinguishSound(); - } - } - - profiler.pop(); - } - -} - - diff --git a/src/main/java/com/github/thedeathlycow/frostiful/registry/FComponents.java b/src/main/java/com/github/thedeathlycow/frostiful/registry/FComponents.java index 8f8e1a7a..2de75418 100644 --- a/src/main/java/com/github/thedeathlycow/frostiful/registry/FComponents.java +++ b/src/main/java/com/github/thedeathlycow/frostiful/registry/FComponents.java @@ -3,6 +3,7 @@ import com.github.thedeathlycow.frostiful.Frostiful; import com.github.thedeathlycow.frostiful.entity.component.BrushableComponent; import com.github.thedeathlycow.frostiful.entity.component.LivingEntityComponents; +import com.github.thedeathlycow.frostiful.entity.component.FrostWandRootComponent; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.passive.OcelotEntity; import net.minecraft.entity.passive.PolarBearEntity; @@ -19,6 +20,11 @@ public class FComponents implements EntityComponentInitializer { LivingEntityComponents.class ); + public static final ComponentKey FROST_WAND_ROOT_COMPONENT = ComponentRegistry.getOrCreate( + Frostiful.id("frost_wand_root"), + FrostWandRootComponent.class + ); + public static final ComponentKey BRUSHABLE_COMPONENT = ComponentRegistry.getOrCreate( Frostiful.id("brushable"), BrushableComponent.class @@ -31,6 +37,11 @@ public void registerEntityComponentFactories(EntityComponentFactoryRegistry regi ENTITY_COMPONENTS, LivingEntityComponents::new ); + registry.registerFor( + LivingEntity.class, + FROST_WAND_ROOT_COMPONENT, + FrostWandRootComponent::new + ); registry.registerFor( PolarBearEntity.class, BRUSHABLE_COMPONENT, diff --git a/src/main/java/com/github/thedeathlycow/frostiful/server/command/RootCommand.java b/src/main/java/com/github/thedeathlycow/frostiful/server/command/RootCommand.java index 3596ae55..41a8f889 100644 --- a/src/main/java/com/github/thedeathlycow/frostiful/server/command/RootCommand.java +++ b/src/main/java/com/github/thedeathlycow/frostiful/server/command/RootCommand.java @@ -1,11 +1,13 @@ package com.github.thedeathlycow.frostiful.server.command; -import com.github.thedeathlycow.frostiful.entity.RootedEntity; +import com.github.thedeathlycow.frostiful.entity.component.FrostWandRootComponent; +import com.github.thedeathlycow.frostiful.registry.FComponents; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.exceptions.CommandSyntaxException; import net.minecraft.command.argument.EntityArgumentType; import net.minecraft.entity.Entity; +import net.minecraft.entity.LivingEntity; import net.minecraft.server.command.ServerCommandSource; import net.minecraft.text.Text; @@ -44,8 +46,9 @@ private static int runRoot(ServerCommandSource source, Collection { - context.assertTrue(rootedEntity.frostiful$isRooted(), "Villager is not rooted for re-apply"); + context.assertTrue(rootComponent.isRooted(), "Villager is not rooted for re-apply"); // root again, before root is expired - rootedEntity.frostiful$root(null); + rootComponent.tryRootFromFrostWand(null); - int newRootTicks = rootedEntity.frostiful$getRootedTicks(); + int newRootTicks = rootComponent.getRootedTicks(); context.assertFalse( newRootTicks >= initialRootTicks, "Villager root ticks were not reset"