From 3d3fa566c746116f897a4d93624bfb638321f2f5 Mon Sep 17 00:00:00 2001 From: Tropical <42101043+tropicaaal@users.noreply.github.com> Date: Sat, 21 Mar 2026 16:29:02 -0500 Subject: [PATCH] TU19: merge Minecraft.World/Entities --- .../Entities/AbsoptionMobEffect.cpp | 24 + Minecraft.World/Entities/AbsoptionMobEffect.h | 15 + .../Entities/AttackDamageMobEffect.cpp | 16 + .../Entities/AttackDamageMobEffect.h | 13 + Minecraft.World/Entities/BossMobPart.cpp | 31 - Minecraft.World/Entities/BossMobPart.h | 27 - Minecraft.World/Entities/Enemy.cpp | 10 +- Minecraft.World/Entities/Enemy.h | 20 +- Minecraft.World/Entities/Entity.cpp | 653 +++--- Minecraft.World/Entities/Entity.h | 78 +- .../Entities/EntityDamageSource.cpp | 54 +- Minecraft.World/Entities/EntityDamageSource.h | 5 +- Minecraft.World/Entities/EntitySelector.cpp | 42 + Minecraft.World/Entities/EntitySelector.h | 28 + .../Entities/FireworksRocketEntity.cpp | 158 ++ .../Entities/FireworksRocketEntity.h | 40 + Minecraft.World/Entities/FlyingMob.cpp | 5 + Minecraft.World/Entities/FlyingMob.h | 1 + Minecraft.World/Entities/HangingEntity.cpp | 43 +- Minecraft.World/Entities/HangingEntity.h | 14 +- .../Entities/HealthBoostMobEffect.cpp | 16 + .../Entities/HealthBoostMobEffect.h | 14 + .../Entities/IndirectEntityDamageSource.cpp | 36 +- .../Entities/IndirectEntityDamageSource.h | 5 +- Minecraft.World/Entities/ItemEntity.cpp | 22 +- Minecraft.World/Entities/ItemEntity.h | 4 +- Minecraft.World/Entities/LargeFireball.cpp | 47 + Minecraft.World/Entities/LargeFireball.h | 25 + .../Entities/LeashFenceKnotEntity.cpp | 140 ++ .../Entities/LeashFenceKnotEntity.h | 38 + Minecraft.World/Entities/LivingEntity.cpp | 1735 +++++++++++++++ Minecraft.World/Entities/LivingEntity.h | 330 +++ Minecraft.World/Entities/MinecartChest.cpp | 36 + Minecraft.World/Entities/MinecartChest.h | 22 + .../Entities/MinecartContainer.cpp | 213 ++ Minecraft.World/Entities/MinecartContainer.h | 47 + Minecraft.World/Entities/MinecartFurnace.cpp | 148 ++ Minecraft.World/Entities/MinecartFurnace.h | 51 + Minecraft.World/Entities/MinecartHopper.cpp | 119 ++ Minecraft.World/Entities/MinecartHopper.h | 81 + Minecraft.World/Entities/MinecartRideable.cpp | 32 + Minecraft.World/Entities/MinecartRideable.h | 16 + Minecraft.World/Entities/MinecartSpawner.cpp | 74 + Minecraft.World/Entities/MinecartSpawner.h | 43 + Minecraft.World/Entities/MinecartTNT.cpp | 136 ++ Minecraft.World/Entities/MinecartTNT.h | 44 + Minecraft.World/Entities/Mob.cpp | 1851 +++++------------ Minecraft.World/Entities/Mob.h | 348 +--- Minecraft.World/Entities/MobCategory.cpp | 45 +- Minecraft.World/Entities/MobCategory.h | 11 +- Minecraft.World/Entities/MobEffect.cpp | 397 ++-- Minecraft.World/Entities/MobEffect.h | 50 +- .../Entities/MobEffectInstance.cpp | 72 +- Minecraft.World/Entities/MobEffectInstance.h | 18 +- Minecraft.World/Entities/Mobs/AgeableMob.cpp | 47 +- Minecraft.World/Entities/Mobs/AgeableMob.h | 7 +- .../Entities/Mobs/AmbientCreature.cpp | 11 + .../Entities/Mobs/AmbientCreature.h | 14 + Minecraft.World/Entities/Mobs/Animal.cpp | 93 +- Minecraft.World/Entities/Mobs/Animal.h | 8 +- Minecraft.World/Entities/Mobs/Arrow.cpp | 127 +- Minecraft.World/Entities/Mobs/Arrow.h | 14 +- Minecraft.World/Entities/Mobs/Bat.cpp | 208 ++ Minecraft.World/Entities/Mobs/Bat.h | 57 + Minecraft.World/Entities/Mobs/Blaze.cpp | 19 +- Minecraft.World/Entities/Mobs/Blaze.h | 2 +- Minecraft.World/Entities/Mobs/Boat.cpp | 70 +- Minecraft.World/Entities/Mobs/Boat.h | 6 +- Minecraft.World/Entities/Mobs/BossMob.cpp | 24 - Minecraft.World/Entities/Mobs/BossMob.h | 22 +- Minecraft.World/Entities/Mobs/CaveSpider.cpp | 20 +- Minecraft.World/Entities/Mobs/CaveSpider.h | 10 +- Minecraft.World/Entities/Mobs/Chicken.cpp | 41 +- Minecraft.World/Entities/Mobs/Chicken.h | 7 +- Minecraft.World/Entities/Mobs/Cow.cpp | 48 +- Minecraft.World/Entities/Mobs/Cow.h | 5 +- Minecraft.World/Entities/Mobs/Creeper.cpp | 83 +- Minecraft.World/Entities/Mobs/Creeper.h | 14 +- .../Entities/Mobs/DragonFireball.cpp | 19 +- .../Entities/Mobs/DragonFireball.h | 11 +- .../Entities/Mobs/EnderCrystal.cpp | 12 +- Minecraft.World/Entities/Mobs/EnderCrystal.h | 2 +- Minecraft.World/Entities/Mobs/EnderDragon.cpp | 222 +- Minecraft.World/Entities/Mobs/EnderDragon.h | 52 +- Minecraft.World/Entities/Mobs/EnderMan.cpp | 158 +- Minecraft.World/Entities/Mobs/EnderMan.h | 10 +- Minecraft.World/Entities/Mobs/EntityHorse.cpp | 1520 ++++++++++++++ Minecraft.World/Entities/Mobs/EntityHorse.h | 343 +++ .../Entities/Mobs/ExperienceOrb.cpp | 11 +- Minecraft.World/Entities/Mobs/ExperienceOrb.h | 2 +- .../Entities/Mobs/EyeOfEnderSignal.cpp | 8 +- Minecraft.World/Entities/Mobs/Fireball.cpp | 70 +- Minecraft.World/Entities/Mobs/Fireball.h | 18 +- Minecraft.World/Entities/Mobs/FishingHook.cpp | 38 +- Minecraft.World/Entities/Mobs/Ghast.cpp | 59 +- Minecraft.World/Entities/Mobs/Ghast.h | 14 +- Minecraft.World/Entities/Mobs/Giant.cpp | 22 +- Minecraft.World/Entities/Mobs/Giant.h | 5 +- Minecraft.World/Entities/Mobs/ItemFrame.cpp | 51 +- Minecraft.World/Entities/Mobs/ItemFrame.h | 10 +- Minecraft.World/Entities/Mobs/LavaSlime.cpp | 27 +- Minecraft.World/Entities/Mobs/LavaSlime.h | 4 + .../Entities/Mobs/LightningBolt.cpp | 101 +- Minecraft.World/Entities/Mobs/Minecart.cpp | 990 ++++----- Minecraft.World/Entities/Mobs/Minecart.h | 95 +- Minecraft.World/Entities/Mobs/MobGroupData.h | 7 + .../Entities/Mobs/MultiEntityMob.h | 10 + .../Entities/Mobs/MultiEntityMobPart.cpp | 34 + .../Entities/Mobs/MultiEntityMobPart.h | 27 + Minecraft.World/Entities/Mobs/MushroomCow.cpp | 16 +- Minecraft.World/Entities/Mobs/MushroomCow.h | 2 +- Minecraft.World/Entities/Mobs/Ocelot.cpp | 188 +- Minecraft.World/Entities/Mobs/Ocelot.h | 50 +- Minecraft.World/Entities/Mobs/Painting.cpp | 44 +- Minecraft.World/Entities/Mobs/Painting.h | 13 +- Minecraft.World/Entities/Mobs/Pig.cpp | 47 +- Minecraft.World/Entities/Mobs/Pig.h | 10 +- Minecraft.World/Entities/Mobs/PigZombie.cpp | 102 +- Minecraft.World/Entities/Mobs/PigZombie.h | 26 +- Minecraft.World/Entities/Mobs/PrimedTnt.cpp | 13 +- Minecraft.World/Entities/Mobs/PrimedTnt.h | 5 +- .../Entities/Mobs/RangedAttackMob.h | 7 + .../Entities/Mobs/SharedMonsterAttributes.cpp | 111 + .../Entities/Mobs/SharedMonsterAttributes.h | 26 + Minecraft.World/Entities/Mobs/Sheep.cpp | 68 +- Minecraft.World/Entities/Mobs/Sheep.h | 12 +- Minecraft.World/Entities/Mobs/Silverfish.cpp | 68 +- Minecraft.World/Entities/Mobs/Silverfish.h | 5 +- Minecraft.World/Entities/Mobs/Skeleton.cpp | 279 ++- Minecraft.World/Entities/Mobs/Skeleton.h | 50 +- Minecraft.World/Entities/Mobs/Slime.cpp | 77 +- Minecraft.World/Entities/Mobs/Slime.h | 3 +- .../Entities/Mobs/SmallFireball.cpp | 8 +- Minecraft.World/Entities/Mobs/SmallFireball.h | 6 +- Minecraft.World/Entities/Mobs/SnowMan.cpp | 43 +- Minecraft.World/Entities/Mobs/SnowMan.h | 12 +- Minecraft.World/Entities/Mobs/Snowball.cpp | 6 +- Minecraft.World/Entities/Mobs/Snowball.h | 2 +- Minecraft.World/Entities/Mobs/Spider.cpp | 85 +- Minecraft.World/Entities/Mobs/Spider.h | 22 +- Minecraft.World/Entities/Mobs/Squid.cpp | 29 +- Minecraft.World/Entities/Mobs/Squid.h | 3 +- Minecraft.World/Entities/Mobs/ThrownEgg.cpp | 2 +- Minecraft.World/Entities/Mobs/ThrownEgg.h | 2 +- .../Entities/Mobs/ThrownEnderPearl.cpp | 29 +- .../Entities/Mobs/ThrownEnderPearl.h | 2 +- .../Entities/Mobs/ThrownExpBottle.cpp | 3 +- .../Entities/Mobs/ThrownExpBottle.h | 2 +- .../Entities/Mobs/ThrownPotion.cpp | 64 +- Minecraft.World/Entities/Mobs/ThrownPotion.h | 9 +- Minecraft.World/Entities/Mobs/Villager.cpp | 138 +- Minecraft.World/Entities/Mobs/Villager.h | 23 +- .../Entities/Mobs/VillagerGolem.cpp | 66 +- Minecraft.World/Entities/Mobs/VillagerGolem.h | 8 +- Minecraft.World/Entities/Mobs/Witch.cpp | 215 ++ Minecraft.World/Entities/Mobs/Witch.h | 49 + Minecraft.World/Entities/Mobs/WitherBoss.cpp | 509 +++++ Minecraft.World/Entities/Mobs/WitherBoss.h | 110 + Minecraft.World/Entities/Mobs/Wolf.cpp | 178 +- Minecraft.World/Entities/Mobs/Wolf.h | 29 +- Minecraft.World/Entities/Mobs/Zombie.cpp | 351 +++- Minecraft.World/Entities/Mobs/Zombie.h | 68 +- Minecraft.World/Entities/Monster.cpp | 81 +- Minecraft.World/Entities/Monster.h | 13 +- Minecraft.World/Entities/OwnableEntity.h | 7 + Minecraft.World/Entities/PathfinderMob.cpp | 119 +- Minecraft.World/Entities/PathfinderMob.h | 30 +- Minecraft.World/Entities/Projectile.h | 7 + Minecraft.World/Entities/SyncedEntityData.cpp | 157 +- Minecraft.World/Entities/SyncedEntityData.h | 18 +- Minecraft.World/Entities/TamableAnimal.cpp | 35 +- Minecraft.World/Entities/TamableAnimal.h | 9 +- Minecraft.World/Entities/Throwable.cpp | 54 +- Minecraft.World/Entities/Throwable.h | 10 +- Minecraft.World/Entities/WaterAnimal.cpp | 17 + Minecraft.World/Entities/WaterAnimal.h | 3 + Minecraft.World/Entities/WitherSkull.cpp | 108 + Minecraft.World/Entities/WitherSkull.h | 45 + 178 files changed, 12204 insertions(+), 4256 deletions(-) create mode 100644 Minecraft.World/Entities/AbsoptionMobEffect.cpp create mode 100644 Minecraft.World/Entities/AbsoptionMobEffect.h create mode 100644 Minecraft.World/Entities/AttackDamageMobEffect.cpp create mode 100644 Minecraft.World/Entities/AttackDamageMobEffect.h delete mode 100644 Minecraft.World/Entities/BossMobPart.cpp delete mode 100644 Minecraft.World/Entities/BossMobPart.h create mode 100644 Minecraft.World/Entities/EntitySelector.cpp create mode 100644 Minecraft.World/Entities/EntitySelector.h create mode 100644 Minecraft.World/Entities/FireworksRocketEntity.cpp create mode 100644 Minecraft.World/Entities/FireworksRocketEntity.h create mode 100644 Minecraft.World/Entities/HealthBoostMobEffect.cpp create mode 100644 Minecraft.World/Entities/HealthBoostMobEffect.h create mode 100644 Minecraft.World/Entities/LargeFireball.cpp create mode 100644 Minecraft.World/Entities/LargeFireball.h create mode 100644 Minecraft.World/Entities/LeashFenceKnotEntity.cpp create mode 100644 Minecraft.World/Entities/LeashFenceKnotEntity.h create mode 100644 Minecraft.World/Entities/LivingEntity.cpp create mode 100644 Minecraft.World/Entities/LivingEntity.h create mode 100644 Minecraft.World/Entities/MinecartChest.cpp create mode 100644 Minecraft.World/Entities/MinecartChest.h create mode 100644 Minecraft.World/Entities/MinecartContainer.cpp create mode 100644 Minecraft.World/Entities/MinecartContainer.h create mode 100644 Minecraft.World/Entities/MinecartFurnace.cpp create mode 100644 Minecraft.World/Entities/MinecartFurnace.h create mode 100644 Minecraft.World/Entities/MinecartHopper.cpp create mode 100644 Minecraft.World/Entities/MinecartHopper.h create mode 100644 Minecraft.World/Entities/MinecartRideable.cpp create mode 100644 Minecraft.World/Entities/MinecartRideable.h create mode 100644 Minecraft.World/Entities/MinecartSpawner.cpp create mode 100644 Minecraft.World/Entities/MinecartSpawner.h create mode 100644 Minecraft.World/Entities/MinecartTNT.cpp create mode 100644 Minecraft.World/Entities/MinecartTNT.h create mode 100644 Minecraft.World/Entities/Mobs/AmbientCreature.cpp create mode 100644 Minecraft.World/Entities/Mobs/AmbientCreature.h create mode 100644 Minecraft.World/Entities/Mobs/Bat.cpp create mode 100644 Minecraft.World/Entities/Mobs/Bat.h delete mode 100644 Minecraft.World/Entities/Mobs/BossMob.cpp create mode 100644 Minecraft.World/Entities/Mobs/EntityHorse.cpp create mode 100644 Minecraft.World/Entities/Mobs/EntityHorse.h create mode 100644 Minecraft.World/Entities/Mobs/MobGroupData.h create mode 100644 Minecraft.World/Entities/Mobs/MultiEntityMob.h create mode 100644 Minecraft.World/Entities/Mobs/MultiEntityMobPart.cpp create mode 100644 Minecraft.World/Entities/Mobs/MultiEntityMobPart.h create mode 100644 Minecraft.World/Entities/Mobs/RangedAttackMob.h create mode 100644 Minecraft.World/Entities/Mobs/SharedMonsterAttributes.cpp create mode 100644 Minecraft.World/Entities/Mobs/SharedMonsterAttributes.h create mode 100644 Minecraft.World/Entities/Mobs/Witch.cpp create mode 100644 Minecraft.World/Entities/Mobs/Witch.h create mode 100644 Minecraft.World/Entities/Mobs/WitherBoss.cpp create mode 100644 Minecraft.World/Entities/Mobs/WitherBoss.h create mode 100644 Minecraft.World/Entities/OwnableEntity.h create mode 100644 Minecraft.World/Entities/Projectile.h create mode 100644 Minecraft.World/Entities/WitherSkull.cpp create mode 100644 Minecraft.World/Entities/WitherSkull.h diff --git a/Minecraft.World/Entities/AbsoptionMobEffect.cpp b/Minecraft.World/Entities/AbsoptionMobEffect.cpp new file mode 100644 index 000000000..56a1812a6 --- /dev/null +++ b/Minecraft.World/Entities/AbsoptionMobEffect.cpp @@ -0,0 +1,24 @@ +#include "../Platform/stdafx.h" +#include "../Headers/net.minecraft.world.entity.h" +#include "../Headers/net.minecraft.world.effect.h" +#include "AbsoptionMobEffect.h" + +AbsoptionMobEffect::AbsoptionMobEffect(int id, bool isHarmful, + eMinecraftColour color) + : MobEffect(id, isHarmful, color) {} + +void AbsoptionMobEffect::removeAttributeModifiers( + std::shared_ptr entity, BaseAttributeMap* attributes, + int amplifier) { + entity->setAbsorptionAmount(entity->getAbsorptionAmount() - + 4 * (amplifier + 1)); + MobEffect::removeAttributeModifiers(entity, attributes, amplifier); +} + +void AbsoptionMobEffect::addAttributeModifiers( + std::shared_ptr entity, BaseAttributeMap* attributes, + int amplifier) { + entity->setAbsorptionAmount(entity->getAbsorptionAmount() + + 4 * (amplifier + 1)); + MobEffect::addAttributeModifiers(entity, attributes, amplifier); +} diff --git a/Minecraft.World/Entities/AbsoptionMobEffect.h b/Minecraft.World/Entities/AbsoptionMobEffect.h new file mode 100644 index 000000000..4fbb1180e --- /dev/null +++ b/Minecraft.World/Entities/AbsoptionMobEffect.h @@ -0,0 +1,15 @@ +#pragma once + +class LivingEntity; + +#include "MobEffect.h" + +class AbsoptionMobEffect : public MobEffect { +public: + AbsoptionMobEffect(int id, bool isHarmful, eMinecraftColour color); + + void removeAttributeModifiers(std::shared_ptr entity, + BaseAttributeMap* attributes, int amplifier); + void addAttributeModifiers(std::shared_ptr entity, + BaseAttributeMap* attributes, int amplifier); +}; \ No newline at end of file diff --git a/Minecraft.World/Entities/AttackDamageMobEffect.cpp b/Minecraft.World/Entities/AttackDamageMobEffect.cpp new file mode 100644 index 000000000..d79a5454e --- /dev/null +++ b/Minecraft.World/Entities/AttackDamageMobEffect.cpp @@ -0,0 +1,16 @@ +#include "../Platform/stdafx.h" + +#include "AttackDamageMobEffect.h" + +AttackDamageMobEffect::AttackDamageMobEffect(int id, bool isHarmful, + eMinecraftColour color) + : MobEffect(id, isHarmful, color) {} + +double AttackDamageMobEffect::getAttributeModifierValue( + int amplifier, AttributeModifier* original) { + if (id == MobEffect::weakness->id) { + return -0.5f * (amplifier + 1); + } else { + return 1.3 * (amplifier + 1); + } +} \ No newline at end of file diff --git a/Minecraft.World/Entities/AttackDamageMobEffect.h b/Minecraft.World/Entities/AttackDamageMobEffect.h new file mode 100644 index 000000000..a19051716 --- /dev/null +++ b/Minecraft.World/Entities/AttackDamageMobEffect.h @@ -0,0 +1,13 @@ +#pragma once + +#include "MobEffect.h" + +class AttributeModifier; + +class AttackDamageMobEffect : public MobEffect { +public: + AttackDamageMobEffect(int id, bool isHarmful, eMinecraftColour color); + + double getAttributeModifierValue(int amplifier, + AttributeModifier* original); +}; \ No newline at end of file diff --git a/Minecraft.World/Entities/BossMobPart.cpp b/Minecraft.World/Entities/BossMobPart.cpp deleted file mode 100644 index bff38bceb..000000000 --- a/Minecraft.World/Entities/BossMobPart.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "../Platform/stdafx.h" -#include "Mobs/BossMob.h" -#include "BossMobPart.h" - -BossMobPart::BossMobPart(BossMob* bossMob, const std::wstring& id, float w, - float h) - : Entity(bossMob->level), bossMob(bossMob), id(id) { - // 4J Stu - This function call had to be moved here from the Entity ctor to - // ensure that the derived version of the function is called - this->defineSynchedData(); - - setSize(w, h); -} - -void BossMobPart::defineSynchedData() {} - -void BossMobPart::readAdditionalSaveData(CompoundTag* tag) {} - -void BossMobPart::addAdditonalSaveData(CompoundTag* tag) {} - -bool BossMobPart::isPickable() { return true; } - -bool BossMobPart::hurt(DamageSource* source, int damage) { - return bossMob->hurt( - std::dynamic_pointer_cast(shared_from_this()), source, - damage); -} - -bool BossMobPart::is(std::shared_ptr other) { - return shared_from_this() == other || bossMob == other.get(); -} \ No newline at end of file diff --git a/Minecraft.World/Entities/BossMobPart.h b/Minecraft.World/Entities/BossMobPart.h deleted file mode 100644 index 87952955c..000000000 --- a/Minecraft.World/Entities/BossMobPart.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include "Entity.h" - -class Level; -class BossMob; - -class BossMobPart : public Entity { -public: - eINSTANCEOF GetType() { return eTYPE_BOSS_MOB_PART; }; - -public: - BossMob* bossMob; - const std::wstring id; - - BossMobPart(BossMob* bossMob, const std::wstring& id, float w, float h); - -protected: - virtual void defineSynchedData(); - virtual void readAdditionalSaveData(CompoundTag* tag); - virtual void addAdditonalSaveData(CompoundTag* tag); - -public: - virtual bool isPickable(); - virtual bool hurt(DamageSource* source, int damage); - virtual bool is(std::shared_ptr other); -}; \ No newline at end of file diff --git a/Minecraft.World/Entities/Enemy.cpp b/Minecraft.World/Entities/Enemy.cpp index 536121f1f..ec2c5bcdc 100644 --- a/Minecraft.World/Entities/Enemy.cpp +++ b/Minecraft.World/Entities/Enemy.cpp @@ -1,8 +1,8 @@ #include "../Platform/stdafx.h" #include "Enemy.h" -const int Enemy::XP_REWARD_NONE = 0; -const int Enemy::XP_REWARD_SMALL = 3; -const int Enemy::XP_REWARD_MEDIUM = 5; -const int Enemy::XP_REWARD_LARGE = 10; -const int Enemy::XP_REWARD_HUGE = 20; \ No newline at end of file +EntitySelector* Enemy::ENEMY_SELECTOR = new Enemy::EnemyEntitySelector(); + +bool Enemy::EnemyEntitySelector::matches(std::shared_ptr entity) const { + return (entity != NULL) && entity->instanceof(eTYPE_ENEMY); +} \ No newline at end of file diff --git a/Minecraft.World/Entities/Enemy.h b/Minecraft.World/Entities/Enemy.h index 15eef2a35..87d72edf2 100644 --- a/Minecraft.World/Entities/Enemy.h +++ b/Minecraft.World/Entities/Enemy.h @@ -1,13 +1,19 @@ #pragma once #include "Mobs/Creature.h" - -class Level; +#include "EntitySelector.h" class Enemy : public Creature { public: - static const int XP_REWARD_NONE; - static const int XP_REWARD_SMALL; - static const int XP_REWARD_MEDIUM; - static const int XP_REWARD_LARGE; - static const int XP_REWARD_HUGE; + class EnemyEntitySelector : public EntitySelector { + bool matches(std::shared_ptr entity) const; + }; + + static const int XP_REWARD_NONE = 0; + static const int XP_REWARD_SMALL = 3; + static const int XP_REWARD_MEDIUM = 5; + static const int XP_REWARD_LARGE = 10; + static const int XP_REWARD_HUGE = 20; + static const int XP_REWARD_BOSS = 50; + + static EntitySelector* ENEMY_SELECTOR; }; diff --git a/Minecraft.World/Entities/Entity.cpp b/Minecraft.World/Entities/Entity.cpp index 2ca869552..552bf572f 100644 --- a/Minecraft.World/Entities/Entity.cpp +++ b/Minecraft.World/Entities/Entity.cpp @@ -3,6 +3,7 @@ #include "../Headers/net.minecraft.world.item.h" #include "../Headers/net.minecraft.world.item.enchantment.h" #include "../Headers/net.minecraft.world.level.h" +#include "../Headers/net.minecraft.world.level.dimension.h" #include "../Headers/net.minecraft.world.level.tile.h" #include "../Headers/net.minecraft.world.phys.h" #include "../Headers/net.minecraft.world.entity.item.h" @@ -22,6 +23,8 @@ #include "../../Minecraft.Client/Level/MultiPlayerLevel.h" #include "../../Minecraft.Client/Player/MultiPlayerLocalPlayer.h" #include +#include "../../Minecraft.Client/Level/ServerLevel.h" +#include "../../Minecraft.Client/Network/PlayerList.h" namespace { #if defined(_WIN32) @@ -91,7 +94,7 @@ int Entity::getSmallId() { // Has set up the entityIdRemovingFlags vector in this case, so // we should check against this when allocating new ids // app.DebugPrintf("getSmallId: - //Removed entities found\n"); + // Removed entities found\n"); puiRemovedFlags = entityIdRemovingFlags; } } @@ -109,7 +112,8 @@ int Entity::getSmallId() { if (puiRemovedFlags) { if (puiRemovedFlags[i] & uiMask) { // app.DebugPrintf("Avoiding - //using ID %d (0x%x)\n", i * 32 + j,puiRemovedFlags[i]); + // using ID %d (0x%x)\n", i * 32 + + // j,puiRemovedFlags[i]); uiMask >>= 1; continue; } @@ -266,7 +270,7 @@ void Entity::tickExtraWandering() { // 4J - added for common ctor code // Do all the default initialisations done in the java class -void Entity::_init(bool useSmallId) { +void Entity::_init(bool useSmallId, Level* level) { // 4J - changed to assign two different types of ids. A range from 0-2047 is // used for things that we'll be wanting to identify over the network, so we // should only need 11 bits rather than 32 to uniquely identify them. The @@ -286,6 +290,7 @@ void Entity::_init(bool useSmallId) { blocksBuilding = false; rider = std::weak_ptr(); riding = nullptr; + forcedLoading = false; // level = NULL; // Level is assigned to in the original c_tor code xo = yo = zo = 0.0; @@ -309,6 +314,7 @@ void Entity::_init(bool useSmallId) { walkDistO = 0; walkDist = 0; + moveDist = 0.0f; fallDistance = 0; @@ -332,13 +338,15 @@ void Entity::_init(bool useSmallId) { firstTick = true; - customTextureUrl = L""; - customTextureUrl2 = L""; - fireImmune = false; // values that need to be sent to clients in SMP - entityData = std::shared_ptr(new SynchedEntityData()); + if (useSmallId) { + entityData = + std::shared_ptr(new SynchedEntityData()); + } else { + entityData = nullptr; + } xRideRotA = yRideRotA = 0.0; inChunk = false; @@ -349,29 +357,46 @@ void Entity::_init(bool useSmallId) { hasImpulse = false; + changingDimensionDelay = 0; + isInsidePortal = false; + portalTime = 0; + dimension = 0; + portalEntranceDir = 0; + invulnerable = false; + if (useSmallId) { + uuid = L"ent" + Mth::createInsecureUUID(random); + } + // 4J Added m_ignoreVerticalCollisions = false; m_uiAnimOverrideBitmask = 0L; + m_ignorePortal = false; } Entity::Entity(Level* level, bool useSmallId) // 4J - added useSmallId parameter { MemSect(16); - _init(useSmallId); + _init(useSmallId, level); MemSect(0); this->level = level; // resetPos(); setPos(0, 0, 0); - entityData->define(DATA_SHARED_FLAGS_ID, (uint8_t)0); - entityData->define( - DATA_AIR_SUPPLY_ID, - TOTAL_AIR_SUPPLY); // 4J Stu - Brought forward from 1.2.3 to fix 38654 - // - Gameplay: Player will take damage when air - // bubbles are present if resuming game from - // load/autosave underwater. + if (level != NULL) { + dimension = level->dimension->id; + } + + if (entityData) { + entityData->define(DATA_SHARED_FLAGS_ID, (uint8_t)0); + entityData->define( + DATA_AIR_SUPPLY_ID, + TOTAL_AIR_SUPPLY); // 4J Stu - Brought forward from 1.2.3 to fix + // 38654 - Gameplay: Player will take damage + // when air bubbles are present if resuming game + // from load/autosave underwater. + } // 4J Stu - We cannot call virtual functions in ctors, as at this point the // object is of type Entity and not a derived class @@ -498,9 +523,10 @@ void Entity::baseTick() { // 4J Stu - Not needed // util.Timer.push("entityBaseTick"); - if (riding != NULL && riding->removed) riding = nullptr; + if (riding != NULL && riding->removed) { + riding = nullptr; + } - tickCount++; walkDistO = walkDist; xo = x; yo = y; @@ -508,9 +534,47 @@ void Entity::baseTick() { xRotO = xRot; yRotO = yRot; + if (!level->isClientSide) // 4J Stu - Don't need this && level instanceof + // ServerLevel) + { + if (!m_ignorePortal) // 4J Added + { + MinecraftServer* server = + dynamic_cast(level)->getServer(); + int waitTime = getPortalWaitTime(); + + if (isInsidePortal) { + if (server->isNetherEnabled()) { + if (riding == NULL) { + if (portalTime++ >= waitTime) { + portalTime = waitTime; + changingDimensionDelay = + getDimensionChangingDelay(); + + int targetDimension; + + if (level->dimension->id == -1) { + targetDimension = 0; + } else { + targetDimension = -1; + } + + changeDimension(targetDimension); + } + } + isInsidePortal = false; + } + } else { + if (portalTime > 0) portalTime -= 4; + if (portalTime < 0) portalTime = 0; + } + if (changingDimensionDelay > 0) changingDimensionDelay--; + } + } + if (isSprinting() && !isInWater() && canCreateParticles()) { int xt = Mth::floor(x); - int yt = Mth::floor(y - 0.2f - this->heightOffset); + int yt = Mth::floor(y - 0.2f - heightOffset); int zt = Mth::floor(z); int t = level->getTile(xt, yt, zt); int d = level->getData(xt, yt, zt); @@ -523,36 +587,7 @@ void Entity::baseTick() { } } - if (updateInWaterState()) { - if (!wasInWater && !firstTick && canCreateParticles()) { - float speed = - Mth::sqrt(xd * xd * 0.2f + yd * yd + zd * zd * 0.2f) * 0.2f; - if (speed > 1) speed = 1; - MemSect(31); - level->playSound( - shared_from_this(), eSoundType_RANDOM_SPLASH, speed, - 1 + (random->nextFloat() - random->nextFloat()) * 0.4f); - MemSect(0); - float yt = (float)Mth::floor(bb->y0); - for (int i = 0; i < 1 + bbWidth * 20; i++) { - float xo = (random->nextFloat() * 2 - 1) * bbWidth; - float zo = (random->nextFloat() * 2 - 1) * bbWidth; - level->addParticle(eParticleType_bubble, x + xo, yt + 1, z + zo, - xd, yd - random->nextFloat() * 0.2f, zd); - } - for (int i = 0; i < 1 + bbWidth * 20; i++) { - float xo = (random->nextFloat() * 2 - 1) * bbWidth; - float zo = (random->nextFloat() * 2 - 1) * bbWidth; - level->addParticle(eParticleType_splash, x + xo, yt + 1, z + zo, - xd, yd, zd); - } - } - fallDistance = 0; - wasInWater = true; - onFire = 0; - } else { - wasInWater = false; - } + updateInWaterState(); if (level->isClientSide) { onFire = 0; @@ -581,7 +616,6 @@ void Entity::baseTick() { if (!level->isClientSide) { setSharedFlag(FLAG_ONFIRE, onFire > 0); - setSharedFlag(FLAG_RIDING, riding != NULL); } firstTick = false; @@ -590,6 +624,8 @@ void Entity::baseTick() { // util.Timer.pop(); } +int Entity::getPortalWaitTime() { return 0; } + void Entity::lavaHurt() { if (fireImmune) { } else { @@ -641,6 +677,7 @@ void Entity::move(double xa, double ya, double za, ySlideOffset *= 0.4f; double xo = x; + double yo = y; double zo = z; if (isStuckInWeb) { @@ -661,8 +698,7 @@ void Entity::move(double xa, double ya, double za, AABB* bbOrg = bb->copy(); bool isPlayerSneaking = - onGround && isSneaking() && - std::dynamic_pointer_cast(shared_from_this()) != NULL; + onGround && isSneaking() && instanceof(eTYPE_PLAYER); if (isPlayerSneaking) { double d = 0.05; @@ -822,11 +858,6 @@ void Entity::move(double xa, double ya, double za, ya = yaN; za = zaN; bb->set(normal); - } else { - double ss = bb->y0 - (int)bb->y0; - if (ss > 0) { - ySlideOffset += (float)(ss + 0.01); - } } } @@ -845,12 +876,12 @@ void Entity::move(double xa, double ya, double za, if (zaOrg != za) zd = 0; double xm = x - xo; + double ym = y - yo; double zm = z - zo; if (makeStepSound() && !isPlayerSneaking && riding == NULL) { - walkDist += (float)(sqrt(xm * xm + zm * zm) * 0.6); int xt = Mth::floor(x); - int yt = Mth::floor(y - 0.2f - this->heightOffset); + int yt = Mth::floor(y - 0.2f - heightOffset); int zt = Mth::floor(z); int t = level->getTile(xt, yt, zt); if (t == 0) { @@ -861,9 +892,24 @@ void Entity::move(double xa, double ya, double za, t = level->getTile(xt, yt - 1, zt); } } + if (t != Tile::ladder_Id) { + ym = 0; + } - if (walkDist > nextStep && t > 0) { - nextStep = (int)walkDist + 1; + walkDist += Mth::sqrt(xm * xm + zm * zm) * 0.6; + moveDist += Mth::sqrt(xm * xm + ym * ym + zm * zm) * 0.6; + + if (moveDist > nextStep && t > 0) { + nextStep = (int)moveDist + 1; + if (isInWater()) { + float speed = + Mth::sqrt(xd * xd * 0.2f + yd * yd + zd * zd * 0.2f) * + 0.35f; + if (speed > 1) speed = 1; + playSound( + eSoundType_LIQUID_SWIM, speed, + 1 + (random->nextFloat() - random->nextFloat()) * 0.4f); + } playStepSound(xt, yt, zt, t); Tile::tiles[t]->stepOn(level, xt, yt, zt, shared_from_this()); } @@ -871,7 +917,7 @@ void Entity::move(double xa, double ya, double za, checkInsideTiles(); - bool water = this->isInWaterOrRain(); + bool water = isInWaterOrRain(); if (level->containsFireTile(bb->shrink(0.001, 0.001, 0.001))) { burn(1); if (!water) { @@ -885,9 +931,8 @@ void Entity::move(double xa, double ya, double za, } if (water && onFire > 0) { - level->playSound( - shared_from_this(), eSoundType_RANDOM_FIZZ, 0.7f, - 1.6f + (random->nextFloat() - random->nextFloat()) * 0.4f); + playSound(eSoundType_RANDOM_FIZZ, 0.7f, + 1.6f + (random->nextFloat() - random->nextFloat()) * 0.4f); onFire = -flameTime; } } @@ -916,6 +961,7 @@ void Entity::checkInsideTiles() { void Entity::playStepSound(int xt, int yt, int zt, int t) { const Tile::SoundType* soundType = Tile::tiles[t]->soundType; MemSect(31); + if (GetType() == eTYPE_PLAYER) { // should we turn off step sounds? unsigned int uiAnimOverrideBitmask = @@ -926,53 +972,21 @@ void Entity::playStepSound(int xt, int yt, int zt, int t) { 0) { return; } - - MultiPlayerLevel* mplevel = (MultiPlayerLevel*)level; - - if (mplevel) { - if (level->getTile(xt, yt + 1, zt) == Tile::topSnow_Id) { - soundType = Tile::topSnow->soundType; - mplevel->playLocalSound( - (double)xt + 0.5, (double)yt, (double)zt + 0.5, - soundType->getStepSound(), soundType->getVolume() * 0.15f, - soundType->getPitch()); - } else if (!Tile::tiles[t]->material->isLiquid()) { - mplevel->playLocalSound( - (double)xt + 0.5, (double)yt, (double)zt + 0.5, - soundType->getStepSound(), soundType->getVolume() * 0.15f, - soundType->getPitch()); - } - } else { - if (level->getTile(xt, yt + 1, zt) == Tile::topSnow_Id) { - soundType = Tile::topSnow->soundType; - level->playLocalSound( - (double)xt + 0.5, (double)yt, (double)zt + 0.5, - soundType->getStepSound(), soundType->getVolume() * 0.15f, - soundType->getPitch()); - } else if (!Tile::tiles[t]->material->isLiquid()) { - level->playLocalSound( - (double)xt + 0.5, (double)yt, (double)zt + 0.5, - soundType->getStepSound(), soundType->getVolume() * 0.15f, - soundType->getPitch()); - } - } - } else { - if (level->getTile(xt, yt + 1, zt) == Tile::topSnow_Id) { - soundType = Tile::topSnow->soundType; - level->playSound(shared_from_this(), soundType->getStepSound(), - soundType->getVolume() * 0.15f, - soundType->getPitch()); - } else if (!Tile::tiles[t]->material->isLiquid()) { - level->playSound(shared_from_this(), soundType->getStepSound(), - soundType->getVolume() * 0.15f, - soundType->getPitch()); - } } + if (level->getTile(xt, yt + 1, zt) == Tile::topSnow_Id) { + soundType = Tile::topSnow->soundType; + playSound(soundType->getStepSound(), soundType->getVolume() * 0.15f, + soundType->getPitch()); + } else if (!Tile::tiles[t]->material->isLiquid()) { + playSound(soundType->getStepSound(), soundType->getVolume() * 0.15f, + soundType->getPitch()); + } + MemSect(0); } void Entity::playSound(int iSound, float volume, float pitch) { - level->playSound(shared_from_this(), iSound, volume, pitch); + level->playEntitySound(shared_from_this(), iSound, volume, pitch); } bool Entity::makeStepSound() { return true; } @@ -980,21 +994,6 @@ bool Entity::makeStepSound() { return true; } void Entity::checkFallDamage(double ya, bool onGround) { if (onGround) { if (fallDistance > 0) { - if (std::dynamic_pointer_cast(shared_from_this()) != NULL) { - int xt = Mth::floor(x); - int yt = Mth::floor(y - 0.2f - this->heightOffset); - int zt = Mth::floor(z); - int t = level->getTile(xt, yt, zt); - if (t == 0 && - level->getTile(xt, yt - 1, zt) == Tile::fence_Id) { - t = level->getTile(xt, yt - 1, zt); - } - - if (t > 0) { - Tile::tiles[t]->fallOn(level, xt, yt, zt, - shared_from_this(), fallDistance); - } - } causeFallDamage(fallDistance); fallDistance = 0; } @@ -1019,15 +1018,46 @@ void Entity::causeFallDamage(float distance) { bool Entity::isInWaterOrRain() { return wasInWater || - (level->isRainingAt(Mth::floor(x), Mth::floor(y), Mth::floor(z))); + (level->isRainingAt(Mth::floor(x), Mth::floor(y), Mth::floor(z)) || + level->isRainingAt(Mth::floor(x), Mth::floor(y + bbHeight), + Mth::floor(z))); } bool Entity::isInWater() { return wasInWater; } bool Entity::updateInWaterState() { - return level->checkAndHandleWater( - bb->grow(0, -0.4f, 0)->shrink(0.001, 0.001, 0.001), Material::water, - shared_from_this()); + if (level->checkAndHandleWater( + bb->grow(0, -0.4f, 0)->shrink(0.001, 0.001, 0.001), Material::water, + shared_from_this())) { + if (!wasInWater && !firstTick && canCreateParticles()) { + float speed = + Mth::sqrt(xd * xd * 0.2f + yd * yd + zd * zd * 0.2f) * 0.2f; + if (speed > 1) speed = 1; + MemSect(31); + playSound(eSoundType_RANDOM_SPLASH, speed, + 1 + (random->nextFloat() - random->nextFloat()) * 0.4f); + MemSect(0); + float yt = (float)Mth::floor(bb->y0); + for (int i = 0; i < 1 + bbWidth * 20; i++) { + float xo = (random->nextFloat() * 2 - 1) * bbWidth; + float zo = (random->nextFloat() * 2 - 1) * bbWidth; + level->addParticle(eParticleType_bubble, x + xo, yt + 1, z + zo, + xd, yd - random->nextFloat() * 0.2f, zd); + } + for (int i = 0; i < 1 + bbWidth * 20; i++) { + float xo = (random->nextFloat() * 2 - 1) * bbWidth; + float zo = (random->nextFloat() * 2 - 1) * bbWidth; + level->addParticle(eParticleType_splash, x + xo, yt + 1, z + zo, + xd, yd, zd); + } + } + fallDistance = 0; + wasInWater = true; + onFire = 0; + } else { + wasInWater = false; + } + return wasInWater; } bool Entity::isUnderLiquid(Material* material) { @@ -1076,7 +1106,7 @@ int Entity::getLightColor(float a) { if (level->hasChunkAt(xTile, 0, zTile)) { double hh = (bb->y1 - bb->y0) * 0.66; - int yTile = Mth::floor(y - this->heightOffset + hh); + int yTile = Mth::floor(y - heightOffset + hh); return level->getLightColor(xTile, yTile, zTile, 0); } return 0; @@ -1088,7 +1118,7 @@ float Entity::getBrightness(float a) { int zTile = Mth::floor(z); if (level->hasChunkAt(xTile, 0, zTile)) { double hh = (bb->y1 - bb->y0) * 0.66; - int yTile = Mth::floor(y - this->heightOffset + hh); + int yTile = Mth::floor(y - heightOffset + hh); return level->getBrightness(xTile, yTile, zTile); } return 0; @@ -1097,27 +1127,27 @@ float Entity::getBrightness(float a) { void Entity::setLevel(Level* level) { this->level = level; } void Entity::absMoveTo(double x, double y, double z, float yRot, float xRot) { - this->xo = this->x = x; - this->yo = this->y = y; - this->zo = this->z = z; - this->yRotO = this->yRot = yRot; - this->xRotO = this->xRot = xRot; + xo = this->x = x; + yo = this->y = y; + zo = this->z = z; + yRotO = this->yRot = yRot; + xRotO = this->xRot = xRot; ySlideOffset = 0; double yRotDiff = yRotO - yRot; if (yRotDiff < -180) yRotO += 360; if (yRotDiff >= 180) yRotO -= 360; - this->setPos(this->x, this->y, this->z); - this->setRot(yRot, xRot); + setPos(this->x, this->y, this->z); + setRot(yRot, xRot); } void Entity::moveTo(double x, double y, double z, float yRot, float xRot) { - this->xOld = this->xo = this->x = x; - this->yOld = this->yo = this->y = y + heightOffset; - this->zOld = this->zo = this->z = z; + xOld = xo = this->x = x; + yOld = yo = this->y = y + heightOffset; + zOld = zo = this->z = z; this->yRot = yRot; this->xRot = xRot; - this->setPos(this->x, this->y, this->z); + setPos(this->x, this->y, this->z); } float Entity::distanceTo(std::shared_ptr e) { @@ -1174,7 +1204,7 @@ void Entity::push(std::shared_ptr e) { xa *= 1 - pushthrough; za *= 1 - pushthrough; - this->push(-xa, 0, -za); + push(-xa, 0, -za); e->push(xa, 0, za); } } @@ -1183,12 +1213,13 @@ void Entity::push(double xa, double ya, double za) { xd += xa; yd += ya; zd += za; - this->hasImpulse = true; + hasImpulse = true; } -void Entity::markHurt() { this->hurtMarked = true; } +void Entity::markHurt() { hurtMarked = true; } -bool Entity::hurt(DamageSource* source, int damage) { +bool Entity::hurt(DamageSource* source, float damage) { + if (isInvulnerable()) return false; markHurt(); return false; } @@ -1220,14 +1251,22 @@ bool Entity::shouldRenderAtSqrDistance(double distance) { return distance < size * size; } -// 4J - used to be std::wstring return type, returning L"" -int Entity::getTexture() { return -1; } - bool Entity::isCreativeModeAllowed() { return false; } +bool Entity::saveAsMount(CompoundTag* entityTag) { + std::wstring id = getEncodeId(); + if (removed || id.empty()) { + return false; + } + // TODO Is this fine to be casting to a non-const char pointer? + entityTag->putString(L"id", id); + saveWithoutId(entityTag); + return true; +} + bool Entity::save(CompoundTag* entityTag) { std::wstring id = getEncodeId(); - if (removed || id.empty()) { + if (removed || id.empty() || (rider.lock() != NULL)) { return false; } // TODO Is this fine to be casting to a non-const char pointer? @@ -1245,20 +1284,30 @@ void Entity::saveWithoutId(CompoundTag* entityTag) { entityTag->putShort(L"Fire", (short)onFire); entityTag->putShort(L"Air", (short)getAirSupply()); entityTag->putBoolean(L"OnGround", onGround); + entityTag->putInt(L"Dimension", dimension); + entityTag->putBoolean(L"Invulnerable", invulnerable); + entityTag->putInt(L"PortalCooldown", changingDimensionDelay); + + entityTag->putString(L"UUID", uuid); addAdditonalSaveData(entityTag); + + if (riding != NULL) { + CompoundTag* ridingTag = new CompoundTag(RIDING_TAG); + if (riding->saveAsMount(ridingTag)) { + entityTag->put(L"Riding", ridingTag); + } + } } void Entity::load(CompoundTag* tag) { - // 4jcraft changed c style cast of templated class - // to getting the actual type and casting when needed - ListTag* pos = tag->getList(L"Pos"); - ListTag* motion = tag->getList(L"Motion"); - ListTag* rotation = tag->getList(L"Rotation"); + ListTag* pos = (ListTag*)tag->getList(L"Pos"); + ListTag* motion = (ListTag*)tag->getList(L"Motion"); + ListTag* rotation = (ListTag*)tag->getList(L"Rotation"); - xd = ((DoubleTag*)motion->get(0))->data; - yd = ((DoubleTag*)motion->get(1))->data; - zd = ((DoubleTag*)motion->get(2))->data; + xd = motion->get(0)->data; + yd = motion->get(1)->data; + zd = motion->get(2)->data; if (abs(xd) > 10.0) { xd = 0; @@ -1270,28 +1319,46 @@ void Entity::load(CompoundTag* tag) { zd = 0; } - xo = xOld = x = ((DoubleTag*)pos->get(0))->data; - yo = yOld = y = ((DoubleTag*)pos->get(1))->data; - zo = zOld = z = ((DoubleTag*)pos->get(2))->data; + xo = xOld = x = pos->get(0)->data; + yo = yOld = y = pos->get(1)->data; + zo = zOld = z = pos->get(2)->data; - yRotO = yRot = ((FloatTag*)rotation->get(0))->data; - xRotO = xRot = ((FloatTag*)rotation->get(1))->data; + yRotO = yRot = rotation->get(0)->data; + xRotO = xRot = rotation->get(1)->data; fallDistance = tag->getFloat(L"FallDistance"); onFire = tag->getShort(L"Fire"); setAirSupply(tag->getShort(L"Air")); onGround = tag->getBoolean(L"OnGround"); + dimension = tag->getInt(L"Dimension"); + invulnerable = tag->getBoolean(L"Invulnerable"); + changingDimensionDelay = tag->getInt(L"PortalCooldown"); + + if (tag->contains(L"UUID")) { + uuid = tag->getString(L"UUID"); + } setPos(x, y, z); setRot(yRot, xRot); readAdditionalSaveData(tag); + + // set position again because bb size may have changed + if (repositionEntityAfterLoad()) setPos(x, y, z); } +bool Entity::repositionEntityAfterLoad() { return true; } + const std::wstring Entity::getEncodeId() { return EntityIO::getEncodeId(shared_from_this()); } +/** + * Called after load() has finished and the entity has been added to the + * world + */ +void Entity::onLoadedFromSave() {} + ListTag* Entity::newDoubleList(unsigned int number, double firstValue, ...) { ListTag* res = new ListTag(); @@ -1357,6 +1424,9 @@ std::shared_ptr Entity::spawnAtLocation(int resource, int count, std::shared_ptr Entity::spawnAtLocation( std::shared_ptr itemInstance, float yOffs) { + if (itemInstance->count == 0) { + return nullptr; + } std::shared_ptr ie = std::shared_ptr( new ItemEntity(level, x, y + yOffs, z, itemInstance)); ie->throwTime = 10; @@ -1372,7 +1442,7 @@ bool Entity::isInWall() { float yo = ((i >> 1) % 2 - 0.5f) * 0.1f; float zo = ((i >> 2) % 2 - 0.5f) * bbWidth * 0.8f; int xt = Mth::floor(x + xo); - int yt = Mth::floor(y + this->getHeadHeight() + yo); + int yt = Mth::floor(y + getHeadHeight() + yo); int zt = Mth::floor(z + zo); if (level->isSolidBlockingTile(xt, yt, zt)) { return true; @@ -1423,24 +1493,21 @@ void Entity::rideTick() { yRideRotA -= yra; xRideRotA -= xra; - yRot += (float)yra; - xRot += (float)xra; + // jeb: This caused the crosshair to "drift" while riding horses. For now + // I've just disabled it, + // because I can't figure out what it's needed for. Riding boats and + // minecarts seem unaffected... + // yRot += yra; + // xRot += xra; } void Entity::positionRider() { std::shared_ptr lockedRider = rider.lock(); - if (lockedRider) { - std::shared_ptr player = - std::dynamic_pointer_cast(lockedRider); - if (!(player && player->isLocalPlayer())) { - lockedRider->xOld = xOld; - lockedRider->yOld = - yOld + getRideHeight() + lockedRider->getRidingHeight(); - lockedRider->zOld = zOld; - } - lockedRider->setPos( - x, y + getRideHeight() + lockedRider->getRidingHeight(), z); + if (lockedRider == NULL) { + return; } + lockedRider->setPos(x, y + getRideHeight() + lockedRider->getRidingHeight(), + z); } double Entity::getRidingHeight() { return heightOffset; } @@ -1454,7 +1521,7 @@ void Entity::ride(std::shared_ptr e) { if (e == NULL) { if (riding != NULL) { // 4J Stu - Position should already be updated before the - // SetRidingPacket comes in + // SetEntityLinkPacket comes in if (!level->isClientSide) moveTo(riding->x, riding->bb->y0 + riding->bbHeight, riding->z, yRot, xRot); @@ -1470,53 +1537,6 @@ void Entity::ride(std::shared_ptr e) { e->rider = shared_from_this(); } -// 4J Stu - Brought forward from 12w36 to fix #46282 - TU5: Gameplay: Exiting -// the minecart in a tight corridor damages the player -void Entity::findStandUpPosition(std::shared_ptr vehicle) { - AABB* boundingBox; - double fallbackX = vehicle->x; - double fallbackY = vehicle->bb->y0 + vehicle->bbHeight; - double fallbackZ = vehicle->z; - - for (double xDiff = -1.5; xDiff < 2; xDiff += 1.5) { - for (double zDiff = -1.5; zDiff < 2; zDiff += 1.5) { - if (xDiff == 0 && zDiff == 0) { - continue; - } - - int xToInt = (int)(this->x + xDiff); - int zToInt = (int)(this->z + zDiff); - - // 4J Stu - Added loop over y to restaring the bb into 2 block high - // spaces if required (eg the track block plus 1 air block above it - // for minecarts) - for (double yDiff = 1.0; yDiff >= 0; yDiff -= 0.5) { - boundingBox = this->bb->cloneMove(xDiff, yDiff, zDiff); - - if (level->getTileCubes(boundingBox, true)->size() == 0) { - if (level->isTopSolidBlocking( - xToInt, (int)(y - (1 - yDiff)), zToInt)) { - this->moveTo(this->x + xDiff, this->y + yDiff, - this->z + zDiff, yRot, xRot); - return; - } else if (level->isTopSolidBlocking( - xToInt, (int)(y - (1 - yDiff)) - 1, - zToInt) || - level->getMaterial(xToInt, - (int)(y - (1 - yDiff)) - 1, - zToInt) == Material::water) { - fallbackX = x + xDiff; - fallbackY = y + yDiff; - fallbackZ = z + zDiff; - } - } - } - } - } - - this->moveTo(fallbackX, fallbackY, fallbackZ, yRot, xRot); -} - void Entity::lerpTo(double x, double y, double z, float yRot, float xRot, int steps) { setPos(x, y, z); @@ -1546,7 +1566,25 @@ float Entity::getPickRadius() { return 0.1f; } Vec3* Entity::getLookAngle() { return NULL; } -void Entity::handleInsidePortal() {} +void Entity::handleInsidePortal() { + if (changingDimensionDelay > 0) { + changingDimensionDelay = getDimensionChangingDelay(); + return; + } + + double xd = xo - x; + double zd = zo - z; + + if (!level->isClientSide && !isInsidePortal) { + portalEntranceDir = Direction::getDirection(xd, zd); + } + + isInsidePortal = true; +} + +int Entity::getDimensionChangingDelay() { + return SharedConstants::TICKS_PER_SECOND * 45; +} void Entity::lerpMotion(double xd, double yd, double zd) { this->xd = xd; @@ -1558,8 +1596,6 @@ void Entity::handleEntityEvent(uint8_t eventId) {} void Entity::animateHurt() {} -void Entity::prepareCustomTextures() {} - ItemInstanceArray Entity::getEquipmentSlots() // ItemInstance[] { return ItemInstanceArray(); // Default ctor creates NULL internal array @@ -1570,9 +1606,11 @@ ItemInstanceArray Entity::getEquipmentSlots() // ItemInstance[] // other players in online game void Entity::setEquippedSlot(int slot, std::shared_ptr item) {} -bool Entity::isOnFire() { return onFire > 0 || getSharedFlag(FLAG_ONFIRE); } +bool Entity::isOnFire() { + return !fireImmune && (onFire > 0 || getSharedFlag(FLAG_ONFIRE)); +} -bool Entity::isRiding() { return riding != NULL || getSharedFlag(FLAG_RIDING); } +bool Entity::isRiding() { return riding != NULL; } bool Entity::isSneaking() { return getSharedFlag(FLAG_SNEAKING); } @@ -1607,17 +1645,23 @@ void Entity::setUsingItemFlag(bool value) { } bool Entity::getSharedFlag(int flag) { - return (entityData->getByte(DATA_SHARED_FLAGS_ID) & (1 << flag)) != 0; + if (entityData) { + return (entityData->getByte(DATA_SHARED_FLAGS_ID) & (1 << flag)) != 0; + } else { + return false; + } } void Entity::setSharedFlag(int flag, bool value) { - uint8_t currentValue = entityData->getByte(DATA_SHARED_FLAGS_ID); - if (value) { - entityData->set(DATA_SHARED_FLAGS_ID, - (uint8_t)(currentValue | (1 << flag))); - } else { - entityData->set(DATA_SHARED_FLAGS_ID, - (uint8_t)(currentValue & ~(1 << flag))); + if (entityData) { + uint8_t currentValue = entityData->getByte(DATA_SHARED_FLAGS_ID); + if (value) { + entityData->set(DATA_SHARED_FLAGS_ID, + (uint8_t)(currentValue | (1 << flag))); + } else { + entityData->set(DATA_SHARED_FLAGS_ID, + (uint8_t)(currentValue & ~(1 << flag))); + } } } @@ -1639,7 +1683,7 @@ void Entity::thunderHit(const LightningBolt* lightningBolt) { if (onFire == 0) setOnFire(8); } -void Entity::killed(std::shared_ptr mob) {} +void Entity::killed(std::shared_ptr mob) {} bool Entity::checkInTile(double x, double y, double z) { int xTile = Mth::floor(x); @@ -1650,15 +1694,17 @@ bool Entity::checkInTile(double x, double y, double z) { double yd = y - (yTile); double zd = z - (zTile); - if (level->isSolidBlockingTile(xTile, yTile, zTile)) { - bool west = !level->isSolidBlockingTile(xTile - 1, yTile, zTile); - bool east = !level->isSolidBlockingTile(xTile + 1, yTile, zTile); - bool up = !level->isSolidBlockingTile(xTile, yTile - 1, zTile); - bool down = !level->isSolidBlockingTile(xTile, yTile + 1, zTile); - bool north = !level->isSolidBlockingTile(xTile, yTile, zTile - 1); - bool south = !level->isSolidBlockingTile(xTile, yTile, zTile + 1); + std::vector* cubes = level->getTileCubes(bb); + if ((cubes && !cubes->empty()) || + level->isFullAABBTile(xTile, yTile, zTile)) { + bool west = !level->isFullAABBTile(xTile - 1, yTile, zTile); + bool east = !level->isFullAABBTile(xTile + 1, yTile, zTile); + bool down = !level->isFullAABBTile(xTile, yTile - 1, zTile); + bool up = !level->isFullAABBTile(xTile, yTile + 1, zTile); + bool north = !level->isFullAABBTile(xTile, yTile, zTile - 1); + bool south = !level->isFullAABBTile(xTile, yTile, zTile + 1); - int dir = -1; + int dir = 3; double closest = 9999; if (west && xd < closest) { closest = xd; @@ -1668,11 +1714,7 @@ bool Entity::checkInTile(double x, double y, double z) { closest = 1 - xd; dir = 1; } - if (up && yd < closest) { - closest = yd; - dir = 2; - } - if (down && 1 - yd < closest) { + if (up && 1 - yd < closest) { closest = 1 - yd; dir = 3; } @@ -1694,9 +1736,9 @@ bool Entity::checkInTile(double x, double y, double z) { if (dir == 4) this->zd = -speed; if (dir == 5) this->zd = +speed; + return true; } - return false; } @@ -1706,10 +1748,13 @@ void Entity::makeStuckInWeb() { } std::wstring Entity::getAName() { +#ifdef _DEBUG std::wstring id = EntityIO::getEncodeId(shared_from_this()); if (id.empty()) id = L"generic"; return L"entity." + id + _toString(entityId); - // return I18n.get("entity." + id + ".name"); +#else + return L""; +#endif } std::vector >* Entity::getSubEntities() { return NULL; } @@ -1724,12 +1769,116 @@ void Entity::setYHeadRot(float yHeadRot) {} bool Entity::isAttackable() { return true; } -bool Entity::isInvulnerable() { return false; } +bool Entity::skipAttackInteraction(std::shared_ptr source) { + return false; +} + +bool Entity::isInvulnerable() { return invulnerable; } void Entity::copyPosition(std::shared_ptr target) { moveTo(target->x, target->y, target->z, target->yRot, target->xRot); } +void Entity::restoreFrom(std::shared_ptr oldEntity, bool teleporting) { + CompoundTag* tag = new CompoundTag(); + oldEntity->saveWithoutId(tag); + load(tag); + delete tag; + changingDimensionDelay = oldEntity->changingDimensionDelay; + portalEntranceDir = oldEntity->portalEntranceDir; +} + +void Entity::changeDimension(int i) { + if (level->isClientSide || removed) return; + + MinecraftServer* server = MinecraftServer::getInstance(); + int lastDimension = dimension; + ServerLevel* oldLevel = server->getLevel(lastDimension); + ServerLevel* newLevel = server->getLevel(i); + + if (lastDimension == 1 && i == 1) { + newLevel = server->getLevel(0); + } + + // 4J: Restrictions on what can go through + { + // 4J: Some things should just be destroyed when they hit a portal + if (instanceof(eTYPE_FALLINGTILE)) { + removed = true; + return; + } + + // 4J: Check server level entity limit (arrows, item entities, + // experience orbs, etc) + if (newLevel->atEntityLimit(shared_from_this())) return; + + // 4J: Check level limit on living entities, minecarts and boats + if (!instanceof(eTYPE_PLAYER) && + !newLevel->canCreateMore(GetType(), Level::eSpawnType_Portal)) + return; + } + + // 4J: Definitely sending, set dimension now + dimension = newLevel->dimension->id; + + level->removeEntity(shared_from_this()); + removed = false; + + server->getPlayers()->repositionAcrossDimension( + shared_from_this(), lastDimension, oldLevel, newLevel); + std::shared_ptr newEntity = EntityIO::newEntity( + EntityIO::getEncodeId(shared_from_this()), newLevel); + + if (newEntity != NULL) { + newEntity->restoreFrom(shared_from_this(), true); + + if (lastDimension == 1 && i == 1) { + Pos* spawnPos = newLevel->getSharedSpawnPos(); + spawnPos->y = level->getTopSolidBlock(spawnPos->x, spawnPos->z); + newEntity->moveTo(spawnPos->x, spawnPos->y, spawnPos->z, + newEntity->yRot, newEntity->xRot); + delete spawnPos; + } + + newLevel->addEntity(newEntity); + } + + removed = true; + + oldLevel->resetEmptyTime(); + newLevel->resetEmptyTime(); +} + +float Entity::getTileExplosionResistance(Explosion* explosion, Level* level, + int x, int y, int z, Tile* tile) { + return tile->getExplosionResistance(shared_from_this()); +} + +bool Entity::shouldTileExplode(Explosion* explosion, Level* level, int x, int y, + int z, int id, float power) { + return true; +} + +int Entity::getMaxFallDistance() { return 3; } + +int Entity::getPortalEntranceDir() { return portalEntranceDir; } + +bool Entity::isIgnoringTileTriggers() { return false; } + +bool Entity::displayFireAnimation() { return isOnFire(); } + +void Entity::setUUID(const std::wstring& UUID) { uuid = UUID; } + +std::wstring Entity::getUUID() { return uuid; } + +bool Entity::isPushedByWater() { return true; } + +std::wstring Entity::getDisplayName() { return getAName(); } + +// 4J: Added to retrieve name that should be sent in ChatPackets (important on +// Xbox One for players) +std::wstring Entity::getNetworkName() { return getDisplayName(); } + void Entity::setAnimOverrideBitmask(unsigned int uiBitmask) { m_uiAnimOverrideBitmask = uiBitmask; app.DebugPrintf("!!! Setting anim override bitmask to %d\n", uiBitmask); @@ -1756,4 +1905,4 @@ unsigned int Entity::getAnimOverrideBitmask() { } return m_uiAnimOverrideBitmask; -} +} \ No newline at end of file diff --git a/Minecraft.World/Entities/Entity.h b/Minecraft.World/Entities/Entity.h index b8468b8b5..3fceb64b6 100644 --- a/Minecraft.World/Entities/Entity.h +++ b/Minecraft.World/Entities/Entity.h @@ -10,7 +10,7 @@ #include #endif -class Mob; +class LivingEntity; class LightningBolt; class ItemEntity; class EntityPos; @@ -21,6 +21,7 @@ class Random; class Level; class CompoundTag; class DamageSource; +class Explosion; // 4J Stu Added this mainly to allow is to record telemetry for player deaths enum EEntityDamageType { @@ -48,7 +49,15 @@ public: // 4J-PB - added to replace (e instanceof Type), avoiding dynamic casts virtual eINSTANCEOF GetType() = 0; + inline bool instanceof(eINSTANCEOF super) { + return eTYPE_DERIVED_FROM(super, GetType()); + } + inline static bool instanceof(eINSTANCEOF type, eINSTANCEOF super) { + return eTYPE_DERIVED_FROM(super, type); + } + public: + static const std::wstring RIDING_TAG; static const short TOTAL_AIR_SUPPLY = 20 * 15; private: @@ -63,6 +72,7 @@ public: std::weak_ptr rider; // Changed to weak to avoid circular // dependency between rider/riding entity std::shared_ptr riding; + bool forcedLoading; Level* level; double xo, yo, zo; @@ -89,6 +99,7 @@ public: float walkDistO; float walkDist; + float moveDist; float fallDistance; private: @@ -120,10 +131,6 @@ public: private: bool firstTick; -public: - std::wstring customTextureUrl; - std::wstring customTextureUrl2; - protected: bool fireImmune; @@ -135,7 +142,7 @@ private: static const int DATA_SHARED_FLAGS_ID = 0; static const int FLAG_ONFIRE = 0; static const int FLAG_SNEAKING = 1; - static const int FLAG_RIDING = 2; + // static const int FLAG_ = 2; static const int FLAG_SPRINTING = 3; static const int FLAG_USING_ITEM = 4; static const int FLAG_INVISIBLE = 5; @@ -153,12 +160,29 @@ public: int xp, yp, zp, xRotp, yRotp; bool noCulling; bool hasImpulse; + int changingDimensionDelay; + +protected: + bool isInsidePortal; + int portalTime; + +public: + int dimension; + +protected: + int portalEntranceDir; + +private: + bool invulnerable; + std::wstring uuid; protected: // 4J Added so that client side simulations on the host are not affected by // zero-lag bool m_ignoreVerticalCollisions; + bool m_ignorePortal; + public: Entity(Level* level, bool useSmallId = true); // 4J - added useSmallId parameter @@ -166,7 +190,7 @@ public: protected: // 4J - added for common ctor code - void _init(bool useSmallId); + void _init(bool useSmallId, Level* level); protected: virtual void defineSynchedData() = 0; @@ -204,6 +228,7 @@ public: void interpolateTurn(float xo, float yo); virtual void tick(); virtual void baseTick(); + virtual int getPortalWaitTime(); protected: void lavaHurt(); @@ -272,7 +297,7 @@ protected: public: // 4J Added damageSource param to enable telemetry on player deaths - virtual bool hurt(DamageSource* source, int damage); + virtual bool hurt(DamageSource* source, float damage); bool intersects(double x0, double y0, double z0, double x1, double y1, double z1); virtual bool isPickable(); @@ -281,18 +306,24 @@ public: virtual void awardKillScore(std::shared_ptr victim, int score); virtual bool shouldRender(Vec3* c); virtual bool shouldRenderAtSqrDistance(double distance); - virtual int getTexture(); // 4J - changed from std::wstring to int virtual bool isCreativeModeAllowed(); + bool saveAsMount(CompoundTag* entityTag); bool save(CompoundTag* entityTag); void saveWithoutId(CompoundTag* entityTag); virtual void load(CompoundTag* tag); protected: + virtual bool repositionEntityAfterLoad(); const std::wstring getEncodeId(); public: virtual void readAdditionalSaveData(CompoundTag* tag) = 0; virtual void addAdditonalSaveData(CompoundTag* tag) = 0; + /** + * Called after load() has finished and the entity has been added to the + * world + */ + virtual void onLoadedFromSave(); protected: ListTag* newDoubleList(unsigned int number, double firstValue, @@ -317,20 +348,15 @@ public: virtual double getRidingHeight(); virtual double getRideHeight(); virtual void ride(std::shared_ptr e); - virtual void findStandUpPosition( - std::shared_ptr - vehicle); // 4J Stu - Brought forward from 12w36 to fix #46282 - - // TU5: Gameplay: Exiting the minecart in a tight - // corridor damages the player virtual void lerpTo(double x, double y, double z, float yRot, float xRot, int steps); virtual float getPickRadius(); virtual Vec3* getLookAngle(); virtual void handleInsidePortal(); + virtual int getDimensionChangingDelay(); virtual void lerpMotion(double xd, double yd, double zd); virtual void handleEntityEvent(uint8_t eventId); virtual void animateHurt(); - virtual void prepareCustomTextures(); virtual ItemInstanceArray getEquipmentSlots(); // ItemInstance[] virtual void setEquippedSlot( int slot, std::shared_ptr @@ -369,7 +395,7 @@ public: void setAirSupply(int supply); virtual void thunderHit(const LightningBolt* lightningBolt); - virtual void killed(std::shared_ptr mob); + virtual void killed(std::shared_ptr mob); protected: bool checkInTile(double x, double y, double z); @@ -379,9 +405,6 @@ public: virtual std::wstring getAName(); - // TU9 - bool skipAttackInteraction(std::shared_ptr source) { return false; } - // 4J - added to manage allocation of small ids private: // Things also added here to be able to manage the concept of a number of @@ -423,8 +446,25 @@ public: virtual float getYHeadRot(); virtual void setYHeadRot(float yHeadRot); virtual bool isAttackable(); + virtual bool skipAttackInteraction(std::shared_ptr source); virtual bool isInvulnerable(); virtual void copyPosition(std::shared_ptr target); + virtual void restoreFrom(std::shared_ptr oldEntity, + bool teleporting); + virtual void changeDimension(int i); + virtual float getTileExplosionResistance(Explosion* explosion, Level* level, + int x, int y, int z, Tile* tile); + virtual bool shouldTileExplode(Explosion* explosion, Level* level, int x, + int y, int z, int id, float power); + virtual int getMaxFallDistance(); + virtual int getPortalEntranceDir(); + virtual bool isIgnoringTileTriggers(); + virtual bool displayFireAnimation(); + virtual void setUUID(const std::wstring& UUID); + virtual std::wstring getUUID(); + virtual bool isPushedByWater(); + virtual std::wstring getDisplayName(); + virtual std::wstring getNetworkName(); // 4J: Added private: unsigned int m_uiAnimOverrideBitmask; diff --git a/Minecraft.World/Entities/EntityDamageSource.cpp b/Minecraft.World/Entities/EntityDamageSource.cpp index 288838962..a455a4a59 100644 --- a/Minecraft.World/Entities/EntityDamageSource.cpp +++ b/Minecraft.World/Entities/EntityDamageSource.cpp @@ -4,36 +4,58 @@ #include "../Headers/net.minecraft.world.damagesource.h" #include "../Headers/net.minecraft.network.packet.h" -// EntityDamageSource::EntityDamageSource(const std::wstring &msgId, -// std::shared_ptr entity) : DamageSource(msgId) -EntityDamageSource::EntityDamageSource(ChatPacket::EChatPacketMessage msgId, - std::shared_ptr entity) - : DamageSource(msgId) { +// EntityDamageSource::EntityDamageSource(const wstring &msgId, +// shared_ptr entity) : DamageSource(msgId) +EntityDamageSource::EntityDamageSource( + ChatPacket::EChatPacketMessage msgId, + ChatPacket::EChatPacketMessage msgWithItemId, + std::shared_ptr entity) + : DamageSource(msgId, msgWithItemId) { this->entity = entity; } std::shared_ptr EntityDamageSource::getEntity() { return entity; } -// std::wstring -// EntityDamageSource::getLocalizedDeathMessage(std::shared_ptr player) +// wstring EntityDamageSource::getLocalizedDeathMessage(shared_ptr +// player) //{ // return L"death." + msgId + player->name + entity->getAName(); // //return I18n.get("death." + msgId, player.name, entity.getAName()); // } std::shared_ptr EntityDamageSource::getDeathMessagePacket( - std::shared_ptr player) { + std::shared_ptr player) { + std::shared_ptr held = + (entity != NULL) && entity->instanceof(eTYPE_LIVINGENTITY) + ? std::dynamic_pointer_cast(entity)->getCarriedItem() + : nullptr; std::wstring additional = L""; - if (entity->GetType() == eTYPE_SERVERPLAYER) { - std::shared_ptr sourcePlayer = - std::dynamic_pointer_cast(entity); - if (sourcePlayer != NULL) additional = sourcePlayer->name; + + if (entity->instanceof(eTYPE_SERVERPLAYER)) { + additional = std::dynamic_pointer_cast(entity)->name; + } else if (entity->instanceof(eTYPE_MOB)) { + std::shared_ptr mob = std::dynamic_pointer_cast(entity); + if (mob->hasCustomName()) { + additional = mob->getCustomName(); + } + } + + if ((held != NULL) && held->hasCustomHoverName()) { + return std::shared_ptr(new ChatPacket( + player->getNetworkName(), m_msgWithItemId, entity->GetType(), + additional, held->getHoverName())); + } else { + return std::shared_ptr(new ChatPacket( + player->getNetworkName(), m_msgId, entity->GetType(), additional)); } - return std::shared_ptr( - new ChatPacket(player->name, m_msgId, entity->GetType(), additional)); } bool EntityDamageSource::scalesWithDifficulty() { - return entity != NULL && std::dynamic_pointer_cast(entity) && - !(std::dynamic_pointer_cast(entity)); + return (entity != NULL) && entity->instanceof(eTYPE_LIVINGENTITY) && + !entity->instanceof(eTYPE_PLAYER); +} + +// 4J: Copy function +DamageSource* EntityDamageSource::copy() { + return new EntityDamageSource(*this); } \ No newline at end of file diff --git a/Minecraft.World/Entities/EntityDamageSource.h b/Minecraft.World/Entities/EntityDamageSource.h index 04c4ecadc..9e4b2cbec 100644 --- a/Minecraft.World/Entities/EntityDamageSource.h +++ b/Minecraft.World/Entities/EntityDamageSource.h @@ -13,6 +13,7 @@ public: // EntityDamageSource(const std::wstring &msgId, std::shared_ptr // entity); EntityDamageSource(ChatPacket::EChatPacketMessage msgId, + ChatPacket::EChatPacketMessage msgWithItemId, std::shared_ptr entity); virtual ~EntityDamageSource() {} @@ -22,7 +23,9 @@ public: // virtual std::wstring getLocalizedDeathMessage(std::shared_ptr // player); virtual std::shared_ptr getDeathMessagePacket( - std::shared_ptr player); + std::shared_ptr player); virtual bool scalesWithDifficulty(); + + virtual DamageSource* copy(); }; \ No newline at end of file diff --git a/Minecraft.World/Entities/EntitySelector.cpp b/Minecraft.World/Entities/EntitySelector.cpp new file mode 100644 index 000000000..18ee52af4 --- /dev/null +++ b/Minecraft.World/Entities/EntitySelector.cpp @@ -0,0 +1,42 @@ +#include "../Platform/stdafx.h" +#include "../Containers/Container.h" +#include "EntitySelector.h" + +const EntitySelector* EntitySelector::ENTITY_STILL_ALIVE = + new AliveEntitySelector(); +const EntitySelector* EntitySelector::CONTAINER_ENTITY_SELECTOR = + new ContainerEntitySelector(); + +bool AliveEntitySelector::matches(std::shared_ptr entity) const { + return entity->isAlive(); +} + +bool ContainerEntitySelector::matches(std::shared_ptr entity) const { + return (std::dynamic_pointer_cast(entity) != NULL) && + entity->isAlive(); +} + +MobCanWearArmourEntitySelector::MobCanWearArmourEntitySelector( + std::shared_ptr item) { + this->item = item; +} + +bool MobCanWearArmourEntitySelector::matches( + std::shared_ptr entity) const { + if (!entity->isAlive()) return false; + if (!entity->instanceof(eTYPE_LIVINGENTITY)) return false; + + std::shared_ptr mob = + std::dynamic_pointer_cast(entity); + + if (mob->getCarried(Mob::getEquipmentSlotForItem(item)) != NULL) + return false; + + if (mob->instanceof(eTYPE_MOB)) { + return std::dynamic_pointer_cast(mob)->canPickUpLoot(); + } else if (mob->instanceof(eTYPE_PLAYER)) { + return true; + } + + return false; +} \ No newline at end of file diff --git a/Minecraft.World/Entities/EntitySelector.h b/Minecraft.World/Entities/EntitySelector.h new file mode 100644 index 000000000..fa952ab5b --- /dev/null +++ b/Minecraft.World/Entities/EntitySelector.h @@ -0,0 +1,28 @@ +#pragma once + +class EntitySelector { +public: + static const EntitySelector* ENTITY_STILL_ALIVE; + static const EntitySelector* CONTAINER_ENTITY_SELECTOR; + + virtual bool matches(std::shared_ptr entity) const = 0; +}; + +class AliveEntitySelector : public EntitySelector { +public: + bool matches(std::shared_ptr entity) const; +}; + +class ContainerEntitySelector : public EntitySelector { +public: + bool matches(std::shared_ptr entity) const; +}; + +class MobCanWearArmourEntitySelector : public EntitySelector { +private: + std::shared_ptr item; + +public: + MobCanWearArmourEntitySelector(std::shared_ptr item); + bool matches(std::shared_ptr entity) const; +}; \ No newline at end of file diff --git a/Minecraft.World/Entities/FireworksRocketEntity.cpp b/Minecraft.World/Entities/FireworksRocketEntity.cpp new file mode 100644 index 000000000..44f118599 --- /dev/null +++ b/Minecraft.World/Entities/FireworksRocketEntity.cpp @@ -0,0 +1,158 @@ +#include "../Platform/stdafx.h" +#include "../Headers/net.minecraft.world.entity.h" +#include "../Headers/net.minecraft.world.item.h" +#include "../Headers/net.minecraft.world.level.h" +#include "FireworksRocketEntity.h" + +FireworksRocketEntity::FireworksRocketEntity(Level* level) : Entity(level) { + defineSynchedData(); + + life = 0; + lifetime = 0; + setSize(0.25f, 0.25f); +} + +void FireworksRocketEntity::defineSynchedData() { + entityData->defineNULL(DATA_ID_FIREWORKS_ITEM, NULL); +} + +bool FireworksRocketEntity::shouldRenderAtSqrDistance(double distance) { + return distance < 64 * 64; +} + +FireworksRocketEntity::FireworksRocketEntity( + Level* level, double x, double y, double z, + std::shared_ptr sourceItem) + : Entity(level) { + defineSynchedData(); + + life = 0; + + setSize(0.25f, 0.25f); + + setPos(x, y, z); + heightOffset = 0; + + int flightCount = 1; + if (sourceItem != NULL && sourceItem->hasTag()) { + entityData->set(DATA_ID_FIREWORKS_ITEM, sourceItem); + + CompoundTag* tag = sourceItem->getTag(); + CompoundTag* compound = tag->getCompound(FireworksItem::TAG_FIREWORKS); + if (compound != NULL) { + flightCount += compound->getByte(FireworksItem::TAG_FLIGHT); + } + } + xd = random->nextGaussian() * .001; + zd = random->nextGaussian() * .001; + yd = 0.05; + + lifetime = (SharedConstants::TICKS_PER_SECOND / 2) * flightCount + + random->nextInt(6) + random->nextInt(7); +} + +void FireworksRocketEntity::lerpMotion(double xd, double yd, double zd) { + xd = xd; + yd = yd; + zd = zd; + if (xRotO == 0 && yRotO == 0) { + double sd = Mth::sqrt(xd * xd + zd * zd); + yRotO = yRot = (float)(atan2(xd, zd) * 180 / PI); + xRotO = xRot = (float)(atan2(yd, sd) * 180 / PI); + } +} + +void FireworksRocketEntity::tick() { + xOld = x; + yOld = y; + zOld = z; + Entity::tick(); + + xd *= 1.15; + zd *= 1.15; + yd += .04; + move(xd, yd, zd); + + double sd = Mth::sqrt(xd * xd + zd * zd); + yRot = (float)(atan2(xd, zd) * 180 / PI); + xRot = (float)(atan2(yd, sd) * 180 / PI); + + while (xRot - xRotO < -180) xRotO -= 360; + while (xRot - xRotO >= 180) xRotO += 360; + + while (yRot - yRotO < -180) yRotO -= 360; + while (yRot - yRotO >= 180) yRotO += 360; + + xRot = xRotO + (xRot - xRotO) * 0.2f; + yRot = yRotO + (yRot - yRotO) * 0.2f; + + if (!level->isClientSide) { + if (life == 0) { + level->playEntitySound(shared_from_this(), + eSoundType_FIREWORKS_LAUNCH, 3, 1); + } + } + + life++; + if (level->isClientSide && (life % 2) < 2) { + level->addParticle(eParticleType_fireworksspark, x, y - .3, z, + random->nextGaussian() * .05, -yd * .5, + random->nextGaussian() * .05); + } + if (!level->isClientSide && life > lifetime) { + level->broadcastEntityEvent(shared_from_this(), + EntityEvent::FIREWORKS_EXPLODE); + remove(); + } +} + +void FireworksRocketEntity::handleEntityEvent(uint8_t eventId) { + if (eventId == EntityEvent::FIREWORKS_EXPLODE && level->isClientSide) { + std::shared_ptr sourceItem = + entityData->getItemInstance(DATA_ID_FIREWORKS_ITEM); + CompoundTag* tag = NULL; + if (sourceItem != NULL && sourceItem->hasTag()) { + tag = + sourceItem->getTag()->getCompound(FireworksItem::TAG_FIREWORKS); + } + level->createFireworks(x, y, z, xd, yd, zd, tag); + } + Entity::handleEntityEvent(eventId); +} + +void FireworksRocketEntity::addAdditonalSaveData(CompoundTag* tag) { + tag->putInt(L"Life", life); + tag->putInt(L"LifeTime", lifetime); + std::shared_ptr itemInstance = + entityData->getItemInstance(DATA_ID_FIREWORKS_ITEM); + if (itemInstance != NULL) { + CompoundTag* itemTag = new CompoundTag(); + itemInstance->save(itemTag); + tag->putCompound(L"FireworksItem", itemTag); + } +} + +void FireworksRocketEntity::readAdditionalSaveData(CompoundTag* tag) { + life = tag->getInt(L"Life"); + lifetime = tag->getInt(L"LifeTime"); + + CompoundTag* itemTag = tag->getCompound(L"FireworksItem"); + if (itemTag != NULL) { + std::shared_ptr fromTag = ItemInstance::fromTag(itemTag); + if (fromTag != NULL) { + entityData->set(DATA_ID_FIREWORKS_ITEM, fromTag); + } + } +} + +float FireworksRocketEntity::getShadowHeightOffs() { return 0; } + +float FireworksRocketEntity::getBrightness(float a) { + return Entity::getBrightness(a); +} + +int FireworksRocketEntity::getLightColor(float a) { + return Entity::getLightColor(a); +} + +bool FireworksRocketEntity::isAttackable() { return false; } \ No newline at end of file diff --git a/Minecraft.World/Entities/FireworksRocketEntity.h b/Minecraft.World/Entities/FireworksRocketEntity.h new file mode 100644 index 000000000..21dc74a17 --- /dev/null +++ b/Minecraft.World/Entities/FireworksRocketEntity.h @@ -0,0 +1,40 @@ +#pragma once + +#include "Entity.h" + +class FireworksRocketEntity : public Entity { +public: + eINSTANCEOF GetType() { return eTYPE_FIREWORKS_ROCKET; } + static Entity* create(Level* level) { + return new FireworksRocketEntity(level); + } + +private: + static const int DATA_ID_FIREWORKS_ITEM = 8; + + int life; + int lifetime; + + // constructor needed for level loader +public: + FireworksRocketEntity(Level* level); + +protected: + virtual void defineSynchedData(); + +public: + virtual bool shouldRenderAtSqrDistance(double distance); + + FireworksRocketEntity(Level* level, double x, double y, double z, + std::shared_ptr sourceItem); + + virtual void lerpMotion(double xd, double yd, double zd); + virtual void tick(); + virtual void handleEntityEvent(uint8_t eventId); + virtual void addAdditonalSaveData(CompoundTag* tag); + virtual void readAdditionalSaveData(CompoundTag* tag); + virtual float getShadowHeightOffs(); + virtual float getBrightness(float a); + virtual int getLightColor(float a); + virtual bool isAttackable(); +}; \ No newline at end of file diff --git a/Minecraft.World/Entities/FlyingMob.cpp b/Minecraft.World/Entities/FlyingMob.cpp index e3dddf6b3..752c68047 100644 --- a/Minecraft.World/Entities/FlyingMob.cpp +++ b/Minecraft.World/Entities/FlyingMob.cpp @@ -11,6 +11,11 @@ void FlyingMob::causeFallDamage(float distance) { // not trigger the "fallOn" tile calls (such as trampling crops) } +void FlyingMob::checkFallDamage(double ya, bool onGround) { + // this method is empty because flying creatures should + // not trigger the "fallOn" tile calls (such as trampling crops) +} + void FlyingMob::travel(float xa, float ya) { if (isInWater()) { moveRelative(xa, ya, 0.02f); diff --git a/Minecraft.World/Entities/FlyingMob.h b/Minecraft.World/Entities/FlyingMob.h index 8169997e5..323dd9a35 100644 --- a/Minecraft.World/Entities/FlyingMob.h +++ b/Minecraft.World/Entities/FlyingMob.h @@ -10,6 +10,7 @@ public: protected: virtual void causeFallDamage(float distance); + virtual void checkFallDamage(double ya, bool onGround); public: virtual void travel(float xa, float ya); diff --git a/Minecraft.World/Entities/HangingEntity.cpp b/Minecraft.World/Entities/HangingEntity.cpp index d78258a30..d67329209 100644 --- a/Minecraft.World/Entities/HangingEntity.cpp +++ b/Minecraft.World/Entities/HangingEntity.cpp @@ -11,22 +11,16 @@ void HangingEntity::_init(Level* level) { checkInterval = 0; dir = 0; xTile = yTile = zTile = 0; -} - -HangingEntity::HangingEntity(Level* level) : Entity(level) { - _init(level); - this->heightOffset = 0; this->setSize(0.5f, 0.5f); } +HangingEntity::HangingEntity(Level* level) : Entity(level) { _init(level); } + HangingEntity::HangingEntity(Level* level, int xTile, int yTile, int zTile, int dir) : Entity(level) { _init(level); - // motive = NULL; - this->heightOffset = 0; - this->setSize(0.5f, 0.5f); this->xTile = xTile; this->yTile = yTile; this->zTile = zTile; @@ -34,7 +28,7 @@ HangingEntity::HangingEntity(Level* level, int xTile, int yTile, int zTile, void HangingEntity::setDir(int dir) { this->dir = dir; - this->yRotO = this->yRot = (float)(dir * 90); + yRotO = yRot = (float)(dir * 90); float w = (float)getWidth(); float h = (float)getHeight(); @@ -68,7 +62,7 @@ void HangingEntity::setDir(int dir) { if (dir == Direction::EAST) z -= offs(getWidth()); y += offs(getHeight()); - this->setPos(x, y, z); + setPos(x, y, z); float ss = -(0.5f / 16.0f); @@ -91,12 +85,14 @@ float HangingEntity::offs(int w) { } void HangingEntity::tick() { - if (checkInterval++ == 20 * 5 && !level->isClientSide) // isClientSide) - { + xo = x; + yo = y; + zo = z; + if (checkInterval++ == 20 * 5 && !level->isClientSide) { checkInterval = 0; if (!removed && !survives()) { remove(); - dropItem(); + dropItem(nullptr); } } } @@ -138,7 +134,7 @@ bool HangingEntity::survives() { AUTO_VAR(itEnd, entities->end()); for (AUTO_VAR(it, entities->begin()); it != itEnd; it++) { std::shared_ptr e = (*it); - if (std::dynamic_pointer_cast(e) != NULL) { + if (e->instanceof(eTYPE_HANGING_ENTITY)) { return false; } } @@ -159,12 +155,14 @@ bool HangingEntity::skipAttackInteraction(std::shared_ptr source) { return false; } -bool HangingEntity::hurt(DamageSource* source, int damage) { +bool HangingEntity::hurt(DamageSource* source, float damage) { + if (isInvulnerable()) return false; if (!removed && !level->isClientSide) { if (dynamic_cast(source) != NULL) { std::shared_ptr sourceEntity = source->getDirectEntity(); - if (std::dynamic_pointer_cast(sourceEntity) != NULL && + if ((sourceEntity != NULL) && + sourceEntity->instanceof(eTYPE_PLAYER) && !std::dynamic_pointer_cast(sourceEntity) ->isAllowedToHurtEntity(shared_from_this())) { return false; @@ -176,8 +174,9 @@ bool HangingEntity::hurt(DamageSource* source, int damage) { std::shared_ptr player = nullptr; std::shared_ptr e = source->getEntity(); - if (e != NULL && ((e->GetType() & eTYPE_PLAYER) != - 0)) // check if it's serverplayer or player + if ((e != NULL) && + e->instanceof( + eTYPE_PLAYER)) // check if it's serverplayer or player { player = std::dynamic_pointer_cast(e); } @@ -186,7 +185,7 @@ bool HangingEntity::hurt(DamageSource* source, int damage) { return true; } - dropItem(); + dropItem(nullptr); } return true; } @@ -195,14 +194,14 @@ bool HangingEntity::hurt(DamageSource* source, int damage) { void HangingEntity::move(double xa, double ya, double za, bool noEntityCubes) { if (!level->isClientSide && !removed && (xa * xa + ya * ya + za * za) > 0) { remove(); - dropItem(); + dropItem(nullptr); } } void HangingEntity::push(double xa, double ya, double za) { if (!level->isClientSide && !removed && (xa * xa + ya * ya + za * za) > 0) { remove(); - dropItem(); + dropItem(nullptr); } } @@ -253,3 +252,5 @@ void HangingEntity::readAdditionalSaveData(CompoundTag* tag) { zTile = tag->getInt(L"TileZ"); setDir(dir); } + +bool HangingEntity::repositionEntityAfterLoad() { return false; } \ No newline at end of file diff --git a/Minecraft.World/Entities/HangingEntity.h b/Minecraft.World/Entities/HangingEntity.h index 622148519..845e0e947 100644 --- a/Minecraft.World/Entities/HangingEntity.h +++ b/Minecraft.World/Entities/HangingEntity.h @@ -9,7 +9,6 @@ public: private: void _init(Level* level); - float offs(int w); int checkInterval; // eINSTANCEOF eType; @@ -24,12 +23,16 @@ public: HangingEntity(Level* level); HangingEntity(Level* level, int xTile, int yTile, int zTile, int dir); void setDir(int dir); - bool survives(); + virtual bool survives(); +private: + float offs(int w); + +public: virtual void tick(); virtual bool isPickable(); virtual bool skipAttackInteraction(std::shared_ptr source); - virtual bool hurt(DamageSource* source, int damage); + virtual bool hurt(DamageSource* source, float damage); virtual void move( double xa, double ya, double za, bool noEntityCubes = false); // 4J - added noEntityCubes parameter @@ -39,5 +42,8 @@ public: virtual int getWidth() = 0; virtual int getHeight() = 0; - virtual void dropItem() = 0; + virtual void dropItem(std::shared_ptr causedBy) = 0; + +protected: + virtual bool repositionEntityAfterLoad(); }; diff --git a/Minecraft.World/Entities/HealthBoostMobEffect.cpp b/Minecraft.World/Entities/HealthBoostMobEffect.cpp new file mode 100644 index 000000000..09c30f8f9 --- /dev/null +++ b/Minecraft.World/Entities/HealthBoostMobEffect.cpp @@ -0,0 +1,16 @@ +#include "../Platform/stdafx.h" +#include "../Headers/net.minecraft.world.entity.h" +#include "HealthBoostMobEffect.h" + +HealthBoostMobEffect::HealthBoostMobEffect(int id, bool isHarmful, + eMinecraftColour color) + : MobEffect(id, isHarmful, color) {} + +void HealthBoostMobEffect::removeAttributeModifiers( + std::shared_ptr entity, BaseAttributeMap* attributes, + int amplifier) { + MobEffect::removeAttributeModifiers(entity, attributes, amplifier); + if (entity->getHealth() > entity->getMaxHealth()) { + entity->setHealth(entity->getMaxHealth()); + } +} diff --git a/Minecraft.World/Entities/HealthBoostMobEffect.h b/Minecraft.World/Entities/HealthBoostMobEffect.h new file mode 100644 index 000000000..84f424b4f --- /dev/null +++ b/Minecraft.World/Entities/HealthBoostMobEffect.h @@ -0,0 +1,14 @@ +#pragma once + +#include "MobEffect.h" + +class LivingEntity; +class BaseAttributeMap; + +class HealthBoostMobEffect : public MobEffect { +public: + HealthBoostMobEffect(int id, bool isHarmful, eMinecraftColour color); + + void removeAttributeModifiers(std::shared_ptr entity, + BaseAttributeMap* attributes, int amplifier); +}; \ No newline at end of file diff --git a/Minecraft.World/Entities/IndirectEntityDamageSource.cpp b/Minecraft.World/Entities/IndirectEntityDamageSource.cpp index 73646db66..326ed1757 100644 --- a/Minecraft.World/Entities/IndirectEntityDamageSource.cpp +++ b/Minecraft.World/Entities/IndirectEntityDamageSource.cpp @@ -4,13 +4,14 @@ #include "../Headers/net.minecraft.world.damagesource.h" #include "../Headers/net.minecraft.network.packet.h" -// IndirectEntityDamageSource::IndirectEntityDamageSource(const std::wstring -// &msgId, std::shared_ptr entity, std::shared_ptr owner) : +// IndirectEntityDamageSource::IndirectEntityDamageSource(const wstring &msgId, +// shared_ptr entity, shared_ptr owner) : // EntityDamageSource(msgId, entity) IndirectEntityDamageSource::IndirectEntityDamageSource( - ChatPacket::EChatPacketMessage msgId, std::shared_ptr entity, - std::shared_ptr owner) - : EntityDamageSource(msgId, entity) { + ChatPacket::EChatPacketMessage msgId, + ChatPacket::EChatPacketMessage msgWithItemId, + std::shared_ptr entity, std::shared_ptr owner) + : EntityDamageSource(msgId, msgWithItemId, entity) { this->owner = owner; } @@ -23,8 +24,8 @@ std::shared_ptr IndirectEntityDamageSource::getEntity() { return owner; } -// std::wstring -// IndirectEntityDamageSource::getLocalizedDeathMessage(std::shared_ptr +// wstring +// IndirectEntityDamageSource::getLocalizedDeathMessage(shared_ptr // player) //{ // return L"death." + msgId + player->name + owner->getAName(); @@ -32,7 +33,11 @@ std::shared_ptr IndirectEntityDamageSource::getEntity() { // } std::shared_ptr IndirectEntityDamageSource::getDeathMessagePacket( - std::shared_ptr player) { + std::shared_ptr player) { + std::shared_ptr held = + entity->instanceof(eTYPE_LIVINGENTITY) + ? std::dynamic_pointer_cast(entity)->getCarriedItem() + : nullptr; std::wstring additional = L""; int type; if (owner != NULL) { @@ -45,6 +50,17 @@ std::shared_ptr IndirectEntityDamageSource::getDeathMessagePacket( } else { type = entity->GetType(); } - return std::shared_ptr( - new ChatPacket(player->name, m_msgId, type, additional)); + if (held != NULL && held->hasCustomHoverName()) { + return std::shared_ptr( + new ChatPacket(player->getNetworkName(), m_msgWithItemId, type, + additional, held->getHoverName())); + } else { + return std::shared_ptr(new ChatPacket( + player->getNetworkName(), m_msgId, type, additional)); + } +} + +// 4J: Copy function +DamageSource* IndirectEntityDamageSource::copy() { + return new IndirectEntityDamageSource(*this); } \ No newline at end of file diff --git a/Minecraft.World/Entities/IndirectEntityDamageSource.h b/Minecraft.World/Entities/IndirectEntityDamageSource.h index ae7ffd16a..5ec5f768c 100644 --- a/Minecraft.World/Entities/IndirectEntityDamageSource.h +++ b/Minecraft.World/Entities/IndirectEntityDamageSource.h @@ -13,6 +13,7 @@ public: // IndirectEntityDamageSource(const std::wstring &msgId, // std::shared_ptr entity, std::shared_ptr owner); IndirectEntityDamageSource(ChatPacket::EChatPacketMessage msgId, + ChatPacket::EChatPacketMessage msgWithItemId, std::shared_ptr entity, std::shared_ptr owner); virtual ~IndirectEntityDamageSource() {} @@ -25,5 +26,7 @@ public: // virtual std::wstring getLocalizedDeathMessage(std::shared_ptr // player); virtual std::shared_ptr getDeathMessagePacket( - std::shared_ptr player); + std::shared_ptr player); + + virtual DamageSource* copy(); }; \ No newline at end of file diff --git a/Minecraft.World/Entities/ItemEntity.cpp b/Minecraft.World/Entities/ItemEntity.cpp index 6b9824ea1..8725cbe5f 100644 --- a/Minecraft.World/Entities/ItemEntity.cpp +++ b/Minecraft.World/Entities/ItemEntity.cpp @@ -83,8 +83,8 @@ void ItemEntity::tick() { xd = (random->nextFloat() - random->nextFloat()) * 0.2f; zd = (random->nextFloat() - random->nextFloat()) * 0.2f; MemSect(31); - level->playSound(shared_from_this(), eSoundType_RANDOM_FIZZ, 0.4f, - 2.0f + random->nextFloat() * 0.4f); + playSound(eSoundType_RANDOM_FIZZ, 0.4f, + 2.0f + random->nextFloat() * 0.4f); MemSect(0); } @@ -168,7 +168,7 @@ bool ItemEntity::updateInWaterState() { void ItemEntity::burn(int dmg) { hurt(DamageSource::inFire, dmg); } -bool ItemEntity::hurt(DamageSource* source, int damage) { +bool ItemEntity::hurt(DamageSource* source, float damage) { // 4J - added next line: found whilst debugging an issue with item entities // getting into a bad state when being created by a cactus, since entities // insides cactuses get hurt and therefore depending on the timing of things @@ -177,6 +177,10 @@ bool ItemEntity::hurt(DamageSource* source, int damage) { // hurt? if (level->isClientSide) return false; + if (isInvulnerable()) return false; + if (getItem() != NULL && getItem()->id == Item::netherStar_Id && + source->isExplosion()) + return false; markHurt(); health -= damage; if (health <= 0) { @@ -238,8 +242,8 @@ void ItemEntity::playerTouch(std::shared_ptr player) { player->awardStat(GenericStats::blazeRod(), GenericStats::param_blazeRod()); - level->playSound( - shared_from_this(), eSoundType_RANDOM_POP, 0.2f, + playSound( + eSoundType_RANDOM_POP, 0.2f, ((random->nextFloat() - random->nextFloat()) * 0.7f + 1.0f) * 2.0f); player->take(shared_from_this(), orgCount); // System.out.println(item.count + ", " + orgCount); @@ -252,6 +256,12 @@ std::wstring ItemEntity::getAName() { // return I18n.get("item." + item.getDescriptionId()); } +void ItemEntity::changeDimension(int i) { + Entity::changeDimension(i); + + if (!level->isClientSide) mergeWithNeighbours(); +} + std::shared_ptr ItemEntity::getItem() { std::shared_ptr result = getEntityData()->getItemInstance(DATA_ITEM); @@ -262,7 +272,7 @@ std::shared_ptr ItemEntity::getItem() { // level.getLogger().severe("Item entity " + entityId + " has no // item?!"); } - return std::shared_ptr(new ItemInstance(Tile::rock)); + return std::shared_ptr(new ItemInstance(Tile::stone)); } return result; diff --git a/Minecraft.World/Entities/ItemEntity.h b/Minecraft.World/Entities/ItemEntity.h index 08d779d23..c5e8a3ae4 100644 --- a/Minecraft.World/Entities/ItemEntity.h +++ b/Minecraft.World/Entities/ItemEntity.h @@ -59,13 +59,13 @@ protected: virtual void burn(int dmg); public: - virtual bool hurt(DamageSource* source, int damage); + virtual bool hurt(DamageSource* source, float damage); virtual void addAdditonalSaveData(CompoundTag* entityTag); virtual void readAdditionalSaveData(CompoundTag* tag); virtual void playerTouch(std::shared_ptr player); virtual std::wstring getAName(); - + virtual void changeDimension(int i); std::shared_ptr getItem(); void setItem(std::shared_ptr item); virtual bool isAttackable(); diff --git a/Minecraft.World/Entities/LargeFireball.cpp b/Minecraft.World/Entities/LargeFireball.cpp new file mode 100644 index 000000000..81677498d --- /dev/null +++ b/Minecraft.World/Entities/LargeFireball.cpp @@ -0,0 +1,47 @@ +#include "../Platform/stdafx.h" +#include "../Headers/net.minecraft.world.damagesource.h" +#include "../Headers/net.minecraft.world.level.h" +#include "../Headers/net.minecraft.world.phys.h" +#include "LargeFireball.h" + +LargeFireball::LargeFireball(Level* level) : Fireball(level) { + explosionPower = 1; +} + +LargeFireball::LargeFireball(Level* level, double x, double y, double z, + double xa, double ya, double za) + : Fireball(level, x, y, z, xa, ya, za) { + explosionPower = 1; +} + +LargeFireball::LargeFireball(Level* level, std::shared_ptr mob, + double xa, double ya, double za) + : Fireball(level, mob, xa, ya, za) { + explosionPower = 1; +} + +void LargeFireball::onHit(HitResult* res) { + if (!level->isClientSide) { + if (res->entity != NULL) { + DamageSource* damageSource = DamageSource::fireball( + std::dynamic_pointer_cast(shared_from_this()), owner); + res->entity->hurt(damageSource, 6); + delete damageSource; + } + level->explode( + nullptr, x, y, z, explosionPower, true, + level->getGameRules()->getBoolean(GameRules::RULE_MOBGRIEFING)); + remove(); + } +} + +void LargeFireball::addAdditonalSaveData(CompoundTag* tag) { + Fireball::addAdditonalSaveData(tag); + tag->putInt(L"ExplosionPower", explosionPower); +} + +void LargeFireball::readAdditionalSaveData(CompoundTag* tag) { + Fireball::readAdditionalSaveData(tag); + if (tag->contains(L"ExplosionPower")) + explosionPower = tag->getInt(L"ExplosionPower"); +} \ No newline at end of file diff --git a/Minecraft.World/Entities/LargeFireball.h b/Minecraft.World/Entities/LargeFireball.h new file mode 100644 index 000000000..6c525e8e1 --- /dev/null +++ b/Minecraft.World/Entities/LargeFireball.h @@ -0,0 +1,25 @@ +#pragma once + +#include "Mobs/Fireball.h" + +class LargeFireball : public Fireball { +public: + eINSTANCEOF GetType() { return eTYPE_LARGE_FIREBALL; } + static Entity* create(Level* level) { return new LargeFireball(level); } + +public: + int explosionPower; + + LargeFireball(Level* level); + LargeFireball(Level* level, double x, double y, double z, double xa, + double ya, double za); + LargeFireball(Level* level, std::shared_ptr mob, double xa, + double ya, double za); + +protected: + void onHit(HitResult* res); + +public: + void addAdditonalSaveData(CompoundTag* tag); + void readAdditionalSaveData(CompoundTag* tag); +}; \ No newline at end of file diff --git a/Minecraft.World/Entities/LeashFenceKnotEntity.cpp b/Minecraft.World/Entities/LeashFenceKnotEntity.cpp new file mode 100644 index 000000000..5a4ccd45f --- /dev/null +++ b/Minecraft.World/Entities/LeashFenceKnotEntity.cpp @@ -0,0 +1,140 @@ +#include "../Platform/stdafx.h" +#include "../Headers/net.minecraft.world.entity.player.h" +#include "../Headers/net.minecraft.world.item.h" +#include "../Headers/net.minecraft.world.level.h" +#include "../Headers/net.minecraft.world.phys.h" +#include "LeashFenceKnotEntity.h" + +void LeashFenceKnotEntity::_init() { defineSynchedData(); } + +LeashFenceKnotEntity::LeashFenceKnotEntity(Level* level) + : HangingEntity(level) { + _init(); +} + +LeashFenceKnotEntity::LeashFenceKnotEntity(Level* level, int xTile, int yTile, + int zTile) + : HangingEntity(level, xTile, yTile, zTile, 0) { + _init(); + setPos(xTile + .5, yTile + .5, zTile + .5); +} + +void LeashFenceKnotEntity::defineSynchedData() { + HangingEntity::defineSynchedData(); +} + +void LeashFenceKnotEntity::setDir(int dir) { + // override to do nothing, knots don't have directions +} + +int LeashFenceKnotEntity::getWidth() { return 9; } + +int LeashFenceKnotEntity::getHeight() { return 9; } + +bool LeashFenceKnotEntity::shouldRenderAtSqrDistance(double distance) { + return distance < 32 * 32; +} + +void LeashFenceKnotEntity::dropItem(std::shared_ptr causedBy) {} + +bool LeashFenceKnotEntity::save(CompoundTag* entityTag) { + // knots are not saved, they are recreated by the entities that are tied + return false; +} + +void LeashFenceKnotEntity::addAdditonalSaveData(CompoundTag* tag) {} + +void LeashFenceKnotEntity::readAdditionalSaveData(CompoundTag* tag) {} + +bool LeashFenceKnotEntity::interact(std::shared_ptr player) { + std::shared_ptr item = player->getCarriedItem(); + + bool attachedMob = false; + if (item != NULL && item->id == Item::lead_Id) { + if (!level->isClientSide) { + // look for entities that can be attached to the fence + double range = 7; + std::vector >* mobs = + level->getEntitiesOfClass( + typeid(Mob), + AABB::newTemp(x - range, y - range, z - range, x + range, + y + range, z + range)); + if (mobs != NULL) { + for (AUTO_VAR(it, mobs->begin()); it != mobs->end(); ++it) { + std::shared_ptr mob = + std::dynamic_pointer_cast(*it); + if (mob->isLeashed() && mob->getLeashHolder() == player) { + mob->setLeashedTo(shared_from_this(), true); + attachedMob = true; + } + } + delete mobs; + } + } + } + if (!level->isClientSide && !attachedMob) { + remove(); + + if (player->abilities.instabuild) { + // if the player is in creative mode, attempt to remove all leashed + // mobs without dropping additional items + double range = 7; + std::vector >* mobs = + level->getEntitiesOfClass( + typeid(Mob), + AABB::newTemp(x - range, y - range, z - range, x + range, + y + range, z + range)); + if (mobs != NULL) { + for (AUTO_VAR(it, mobs->begin()); it != mobs->end(); ++it) { + std::shared_ptr mob = + std::dynamic_pointer_cast(*it); + if (mob->isLeashed() && + mob->getLeashHolder() == shared_from_this()) { + mob->dropLeash(true, false); + } + } + delete mobs; + } + } + } + return true; +} + +bool LeashFenceKnotEntity::survives() { + // knots are placed on top of fence tiles + int tile = level->getTile(xTile, yTile, zTile); + if (Tile::tiles[tile] != NULL && + Tile::tiles[tile]->getRenderShape() == Tile::SHAPE_FENCE) { + return true; + } + return false; +} + +std::shared_ptr LeashFenceKnotEntity::createAndAddKnot( + Level* level, int x, int y, int z) { + std::shared_ptr knot = + std::shared_ptr( + new LeashFenceKnotEntity(level, x, y, z)); + knot->forcedLoading = true; + level->addEntity(knot); + return knot; +} + +std::shared_ptr LeashFenceKnotEntity::findKnotAt( + Level* level, int x, int y, int z) { + std::vector >* knots = level->getEntitiesOfClass( + typeid(LeashFenceKnotEntity), + AABB::newTemp(x - 1.0, y - 1.0, z - 1.0, x + 1.0, y + 1.0, z + 1.0)); + if (knots != NULL) { + for (AUTO_VAR(it, knots->begin()); it != knots->end(); ++it) { + std::shared_ptr knot = + std::dynamic_pointer_cast(*it); + if (knot->xTile == x && knot->yTile == y && knot->zTile == z) { + delete knots; + return knot; + } + } + delete knots; + } + return nullptr; +} \ No newline at end of file diff --git a/Minecraft.World/Entities/LeashFenceKnotEntity.h b/Minecraft.World/Entities/LeashFenceKnotEntity.h new file mode 100644 index 000000000..e8b9b411a --- /dev/null +++ b/Minecraft.World/Entities/LeashFenceKnotEntity.h @@ -0,0 +1,38 @@ +#pragma once + +#include "HangingEntity.h" + +class LeashFenceKnotEntity : public HangingEntity { +public: + eINSTANCEOF GetType() { return eTYPE_LEASHFENCEKNOT; }; + static Entity* create(Level* level) { + return new LeashFenceKnotEntity(level); + } + +private: + void _init(); + +public: + LeashFenceKnotEntity(Level* level); + LeashFenceKnotEntity(Level* level, int xTile, int yTile, int zTile); + +protected: + void defineSynchedData(); + +public: + void setDir(int dir); + int getWidth(); + int getHeight(); + bool shouldRenderAtSqrDistance(double distance); + void dropItem(std::shared_ptr causedBy); + bool save(CompoundTag* entityTag); + void addAdditonalSaveData(CompoundTag* tag); + void readAdditionalSaveData(CompoundTag* tag); + bool interact(std::shared_ptr player); + virtual bool survives(); + static std::shared_ptr createAndAddKnot(Level* level, + int x, int y, + int z); + static std::shared_ptr findKnotAt(Level* level, int x, + int y, int z); +}; \ No newline at end of file diff --git a/Minecraft.World/Entities/LivingEntity.cpp b/Minecraft.World/Entities/LivingEntity.cpp new file mode 100644 index 000000000..18e5369f3 --- /dev/null +++ b/Minecraft.World/Entities/LivingEntity.cpp @@ -0,0 +1,1735 @@ +#include "../Platform/stdafx.h" +#include "../Util/JavaMath.h" +#include "../Util/Mth.h" +#include "../Headers/net.minecraft.network.packet.h" +#include "../Headers/net.minecraft.world.level.h" +#include "../Headers/net.minecraft.world.level.tile.h" +#include "../Headers/net.minecraft.world.phys.h" +#include "../Headers/net.minecraft.world.entity.h" +#include "../Headers/net.minecraft.world.entity.ai.attributes.h" +#include "../Headers/net.minecraft.world.entity.ai.control.h" +#include "../Headers/net.minecraft.world.entity.ai.navigation.h" +#include "../Headers/net.minecraft.world.entity.ai.sensing.h" +#include "../Headers/net.minecraft.world.entity.player.h" +#include "../Headers/net.minecraft.world.entity.animal.h" +#include "../Headers/net.minecraft.world.entity.monster.h" +#include "../Headers/net.minecraft.world.item.h" +#include "../Headers/net.minecraft.world.level.h" +#include "../Headers/net.minecraft.world.level.chunk.h" +#include "../Headers/net.minecraft.world.level.material.h" +#include "../Headers/net.minecraft.world.damagesource.h" +#include "../Headers/net.minecraft.world.effect.h" +#include "../Headers/net.minecraft.world.item.alchemy.h" +#include "../Headers/net.minecraft.world.item.enchantment.h" +#include "../Headers/net.minecraft.world.scores.h" +#include "../Headers/com.mojang.nbt.h" +#include "LivingEntity.h" +#include "../../Minecraft.Client/Textures/Textures.h" +#include "../../Minecraft.Client/Level/ServerLevel.h" +#include "../../Minecraft.Client/Player/EntityTracker.h" +#include "../Util/SoundTypes.h" +#include "../Util/BasicTypeContainers.h" +#include "../Util/ParticleTypes.h" +#include "../Stats/GenericStats.h" +#include "ItemEntity.h" + +const double LivingEntity::MIN_MOVEMENT_DISTANCE = 0.005; + +AttributeModifier* LivingEntity::SPEED_MODIFIER_SPRINTING = + (new AttributeModifier(eModifierId_MOB_SPRINTING, 0.3f, + AttributeModifier::OPERATION_MULTIPLY_TOTAL)) + ->setSerialize(false); + +void LivingEntity::_init() { + attributes = NULL; + combatTracker = new CombatTracker(this); + lastEquipment = ItemInstanceArray(5); + + swinging = false; + swingTime = 0; + removeArrowTime = 0; + lastHealth = 0.0f; + + hurtTime = 0; + hurtDuration = 0; + hurtDir = 0.0f; + deathTime = 0; + attackTime = 0; + oAttackAnim = attackAnim = 0.0f; + + walkAnimSpeedO = 0.0f; + walkAnimSpeed = 0.0f; + walkAnimPos = 0.0f; + invulnerableDuration = 20; + oTilt = tilt = 0.0f; + timeOffs = 0.0f; + rotA = 0.0f; + yBodyRot = yBodyRotO = 0.0f; + yHeadRot = yHeadRotO = 0.0f; + flyingSpeed = 0.02f; + + lastHurtByPlayer = nullptr; + lastHurtByPlayerTime = 0; + dead = false; + noActionTime = 0; + oRun = run = 0.0f; + animStep = animStepO = 0.0f; + rotOffs = 0.0f; + deathScore = 0; + lastHurt = 0.0f; + jumping = false; + + xxa = 0.0f; + yya = 0.0f; + yRotA = 0.0f; + lSteps = 0; + lx = ly = lz = lyr = lxr = 0.0; + + effectsDirty = false; + + lastHurtByMob = nullptr; + lastHurtByMobTimestamp = 0; + lastHurtMob = nullptr; + lastHurtMobTimestamp = 0; + + speed = 0.0f; + noJumpDelay = 0; + absorptionAmount = 0.0f; +} + +LivingEntity::LivingEntity(Level* level) : Entity(level) { + MemSect(56); + _init(); + MemSect(0); + + // 4J Stu - This will not call the correct derived function, so moving to + // each derived class + // setHealth(0); + // registerAttributes(); + + blocksBuilding = true; + + rotA = (float)(Math::random() + 1) * 0.01f; + setPos(x, y, z); + timeOffs = (float)Math::random() * 12398; + yRot = (float)(Math::random() * PI * 2); + yHeadRot = yRot; + + footSize = 0.5f; +} + +LivingEntity::~LivingEntity() { + for (AUTO_VAR(it, activeEffects.begin()); it != activeEffects.end(); ++it) { + delete it->second; + } + + delete attributes; + delete combatTracker; + + if (lastEquipment.data != NULL) delete[] lastEquipment.data; +} + +void LivingEntity::defineSynchedData() { + entityData->define(DATA_EFFECT_COLOR_ID, 0); + entityData->define(DATA_EFFECT_AMBIENCE_ID, (uint8_t)0); + entityData->define(DATA_ARROW_COUNT_ID, (uint8_t)0); + entityData->define(DATA_HEALTH_ID, 1.0f); +} + +void LivingEntity::registerAttributes() { + getAttributes()->registerAttribute(SharedMonsterAttributes::MAX_HEALTH); + getAttributes()->registerAttribute( + SharedMonsterAttributes::KNOCKBACK_RESISTANCE); + getAttributes()->registerAttribute(SharedMonsterAttributes::MOVEMENT_SPEED); + + if (!useNewAi()) { + getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED) + ->setBaseValue(0.1f); + } +} + +void LivingEntity::checkFallDamage(double ya, bool onGround) { + if (!isInWater()) { + // double-check if we've reached water in this move tick + updateInWaterState(); + } + + if (onGround && fallDistance > 0) { + int xt = Mth::floor(x); + int yt = Mth::floor(y - 0.2f - heightOffset); + int zt = Mth::floor(z); + int t = level->getTile(xt, yt, zt); + if (t == 0) { + int renderShape = level->getTileRenderShape(xt, yt - 1, zt); + if (renderShape == Tile::SHAPE_FENCE || + renderShape == Tile::SHAPE_WALL || + renderShape == Tile::SHAPE_FENCE_GATE) { + t = level->getTile(xt, yt - 1, zt); + } + } + + if (t > 0) { + Tile::tiles[t]->fallOn(level, xt, yt, zt, shared_from_this(), + fallDistance); + } + } + + Entity::checkFallDamage(ya, onGround); +} + +bool LivingEntity::isWaterMob() { return false; } + +void LivingEntity::baseTick() { + oAttackAnim = attackAnim; + Entity::baseTick(); + + if (isAlive() && isInWall()) { + hurt(DamageSource::inWall, 1); + } + + if (isFireImmune() || level->isClientSide) clearFire(); + std::shared_ptr thisPlayer = + std::dynamic_pointer_cast(shared_from_this()); + bool isInvulnerable = + (thisPlayer != NULL && thisPlayer->abilities.invulnerable); + + if (isAlive() && isUnderLiquid(Material::water)) { + if (!isWaterMob() && !hasEffect(MobEffect::waterBreathing->id) && + !isInvulnerable) { + setAirSupply(decreaseAirSupply(getAirSupply())); + if (getAirSupply() == -20) { + setAirSupply(0); + if (canCreateParticles()) { + for (int i = 0; i < 8; i++) { + float xo = random->nextFloat() - random->nextFloat(); + float yo = random->nextFloat() - random->nextFloat(); + float zo = random->nextFloat() - random->nextFloat(); + level->addParticle(eParticleType_bubble, x + xo, y + yo, + z + zo, xd, yd, zd); + } + } + hurt(DamageSource::drown, 2); + } + } + + clearFire(); + if (!level->isClientSide && isRiding() && + riding->instanceof(eTYPE_LIVINGENTITY)) { + ride(nullptr); + } + } else { + setAirSupply(TOTAL_AIR_SUPPLY); + } + + oTilt = tilt; + + if (attackTime > 0) attackTime--; + if (hurtTime > 0) hurtTime--; + if (invulnerableTime > 0) invulnerableTime--; + if (getHealth() <= 0) { + tickDeath(); + } + + if (lastHurtByPlayerTime > 0) + lastHurtByPlayerTime--; + else { + // Note - this used to just set to nullptr, but that has to create a new + // std::shared_ptr and free an old one, when generally this won't be + // doing anything at all. This is the lightweight but ugly alternative + if (lastHurtByPlayer) { + lastHurtByPlayer.reset(); + } + } + if (lastHurtMob != NULL && !lastHurtMob->isAlive()) { + lastHurtMob = nullptr; + } + + // If lastHurtByMob is dead, remove it + if (lastHurtByMob != NULL && !lastHurtByMob->isAlive()) { + setLastHurtByMob(nullptr); + } + + // Update effects + tickEffects(); + + animStepO = animStep; + + yBodyRotO = yBodyRot; + yHeadRotO = yHeadRot; + yRotO = yRot; + xRotO = xRot; +} + +bool LivingEntity::isBaby() { return false; } + +void LivingEntity::tickDeath() { + deathTime++; + if (deathTime == 20) { + // 4J Stu - Added level->isClientSide check from 1.2 to fix XP orbs + // being created client side + if (!level->isClientSide && + (lastHurtByPlayerTime > 0 || isAlwaysExperienceDropper())) { + if (!isBaby() && + level->getGameRules()->getBoolean(GameRules::RULE_DOMOBLOOT)) { + int xpCount = this->getExperienceReward(lastHurtByPlayer); + while (xpCount > 0) { + int newCount = ExperienceOrb::getExperienceValue(xpCount); + xpCount -= newCount; + level->addEntity(std::shared_ptr( + new ExperienceOrb(level, x, y, z, newCount))); + } + } + } + + remove(); + for (int i = 0; i < 20; i++) { + double xa = random->nextGaussian() * 0.02; + double ya = random->nextGaussian() * 0.02; + double za = random->nextGaussian() * 0.02; + level->addParticle(eParticleType_explode, + x + random->nextFloat() * bbWidth * 2 - bbWidth, + y + random->nextFloat() * bbHeight, + z + random->nextFloat() * bbWidth * 2 - bbWidth, + xa, ya, za); + } + } +} + +int LivingEntity::decreaseAirSupply(int currentSupply) { + int oxygenBonus = EnchantmentHelper::getOxygenBonus( + std::dynamic_pointer_cast(shared_from_this())); + if (oxygenBonus > 0) { + if (random->nextInt(oxygenBonus + 1) > 0) { + // the oxygen bonus prevents us from drowning + return currentSupply; + } + } + if (instanceof(eTYPE_PLAYER)) { + app.DebugPrintf("++++++++++ %s: Player decreasing air supply to %d\n", + level->isClientSide ? "CLIENT" : "SERVER", + currentSupply - 1); + } + return currentSupply - 1; +} + +int LivingEntity::getExperienceReward(std::shared_ptr killedBy) { + return 0; +} + +bool LivingEntity::isAlwaysExperienceDropper() { return false; } + +Random* LivingEntity::getRandom() { return random; } + +std::shared_ptr LivingEntity::getLastHurtByMob() { + return lastHurtByMob; +} + +int LivingEntity::getLastHurtByMobTimestamp() { return lastHurtByMobTimestamp; } + +void LivingEntity::setLastHurtByMob(std::shared_ptr target) { + lastHurtByMob = target; + lastHurtByMobTimestamp = tickCount; +} + +std::shared_ptr LivingEntity::getLastHurtMob() { + return lastHurtMob; +} + +int LivingEntity::getLastHurtMobTimestamp() { return lastHurtMobTimestamp; } + +void LivingEntity::setLastHurtMob(std::shared_ptr target) { + if (target->instanceof(eTYPE_LIVINGENTITY)) { + lastHurtMob = std::dynamic_pointer_cast(target); + } else { + lastHurtMob = nullptr; + } + lastHurtMobTimestamp = tickCount; +} + +int LivingEntity::getNoActionTime() { return noActionTime; } + +void LivingEntity::addAdditonalSaveData(CompoundTag* entityTag) { + entityTag->putFloat(L"HealF", getHealth()); + entityTag->putShort(L"Health", (short)ceil(getHealth())); + entityTag->putShort(L"HurtTime", (short)hurtTime); + entityTag->putShort(L"DeathTime", (short)deathTime); + entityTag->putShort(L"AttackTime", (short)attackTime); + entityTag->putFloat(L"AbsorptionAmount", getAbsorptionAmount()); + + ItemInstanceArray items = getEquipmentSlots(); + for (unsigned int i = 0; i < items.length; ++i) { + std::shared_ptr item = items[i]; + if (item != NULL) { + attributes->removeItemModifiers(item); + } + } + + entityTag->put(L"Attributes", + SharedMonsterAttributes::saveAttributes(getAttributes())); + + for (unsigned int i = 0; i < items.length; ++i) { + std::shared_ptr item = items[i]; + if (item != NULL) { + attributes->addItemModifiers(item); + } + } + + if (!activeEffects.empty()) { + ListTag* listTag = new ListTag(); + + for (AUTO_VAR(it, activeEffects.begin()); it != activeEffects.end(); + ++it) { + MobEffectInstance* effect = it->second; + listTag->add(effect->save(new CompoundTag())); + } + entityTag->put(L"ActiveEffects", listTag); + } +} + +void LivingEntity::readAdditionalSaveData(CompoundTag* tag) { + setAbsorptionAmount(tag->getFloat(L"AbsorptionAmount")); + + if (tag->contains(L"Attributes") && level != NULL && !level->isClientSide) { + SharedMonsterAttributes::loadAttributes( + getAttributes(), + (ListTag*)tag->getList(L"Attributes")); + } + + if (tag->contains(L"ActiveEffects")) { + ListTag* effects = + (ListTag*)tag->getList(L"ActiveEffects"); + for (int i = 0; i < effects->size(); i++) { + CompoundTag* effectTag = effects->get(i); + MobEffectInstance* effect = MobEffectInstance::load(effectTag); + activeEffects.insert( + std::unordered_map::value_type( + effect->getId(), effect)); + } + } + + if (tag->contains(L"HealF")) { + setHealth(tag->getFloat(L"HealF")); + } else { + Tag* healthTag = tag->get(L"Health"); + if (healthTag == NULL) { + setHealth(getMaxHealth()); + } else if (healthTag->getId() == Tag::TAG_Float) { + setHealth(((FloatTag*)healthTag)->data); + } else if (healthTag->getId() == Tag::TAG_Short) { + // pre-1.6 health + setHealth((float)((ShortTag*)healthTag)->data); + } + } + + hurtTime = tag->getShort(L"HurtTime"); + deathTime = tag->getShort(L"DeathTime"); + attackTime = tag->getShort(L"AttackTime"); +} + +void LivingEntity::tickEffects() { + bool removed = false; + for (AUTO_VAR(it, activeEffects.begin()); it != activeEffects.end();) { + MobEffectInstance* effect = it->second; + removed = false; + if (!effect->tick( + std::dynamic_pointer_cast(shared_from_this()))) { + if (!level->isClientSide) { + it = activeEffects.erase(it); + onEffectRemoved(effect); + delete effect; + removed = true; + } + } else if (effect->getDuration() % + (SharedConstants::TICKS_PER_SECOND * 30) == + 0) { + // update effects every 30 seconds to synchronize client-side + // timer + onEffectUpdated(effect, false); + } + if (!removed) { + ++it; + } + } + if (effectsDirty) { + if (!level->isClientSide) { + if (activeEffects.empty()) { + entityData->set(DATA_EFFECT_AMBIENCE_ID, (uint8_t)0); + entityData->set(DATA_EFFECT_COLOR_ID, 0); + setInvisible(false); + setWeakened(false); + } else { + std::vector values; + for (AUTO_VAR(it, activeEffects.begin()); + it != activeEffects.end(); ++it) { + values.push_back(it->second); + } + int colorValue = PotionBrewing::getColorValue(&values); + entityData->set(DATA_EFFECT_AMBIENCE_ID, + PotionBrewing::areAllEffectsAmbient(&values) + ? (uint8_t)1 + : (uint8_t)0); + values.clear(); + entityData->set(DATA_EFFECT_COLOR_ID, colorValue); + setInvisible(hasEffect(MobEffect::invisibility->id)); + setWeakened(hasEffect(MobEffect::weakness->id)); + } + } + effectsDirty = false; + } + int colorValue = entityData->getInteger(DATA_EFFECT_COLOR_ID); + bool ambient = entityData->getByte(DATA_EFFECT_AMBIENCE_ID) > 0; + + if (colorValue > 0) { + boolean doParticle = false; + + if (!isInvisible()) { + doParticle = random->nextBoolean(); + } else { + // much fewer particles when invisible + doParticle = random->nextInt(15) == 0; + } + + if (ambient) doParticle &= random->nextInt(5) == 0; + + if (doParticle) { + // int colorValue = + // entityData.getInteger(DATA_EFFECT_COLOR_ID); + if (colorValue > 0) { + double red = (double)((colorValue >> 16) & 0xff) / 255.0; + double green = (double)((colorValue >> 8) & 0xff) / 255.0; + double blue = (double)((colorValue >> 0) & 0xff) / 255.0; + + level->addParticle( + ambient ? eParticleType_mobSpellAmbient + : eParticleType_mobSpell, + x + (random->nextDouble() - 0.5) * bbWidth, + y + random->nextDouble() * bbHeight - heightOffset, + z + (random->nextDouble() - 0.5) * bbWidth, red, green, + blue); + } + } + } +} + +void LivingEntity::removeAllEffects() { + // Iterator effectIdIterator = + // activeEffects.keySet().iterator(); while + // (effectIdIterator.hasNext()) + for (AUTO_VAR(it, activeEffects.begin()); it != activeEffects.end();) { + // Integer effectId = effectIdIterator.next(); + MobEffectInstance* effect = it->second; // activeEffects.get(effectId); + + if (!level->isClientSide) { + // effectIdIterator.remove(); + it = activeEffects.erase(it); + onEffectRemoved(effect); + delete effect; + } else { + ++it; + } + } +} + +std::vector* LivingEntity::getActiveEffects() { + std::vector* active = + new std::vector(); + + for (AUTO_VAR(it, activeEffects.begin()); it != activeEffects.end(); ++it) { + active->push_back(it->second); + } + + return active; +} + +bool LivingEntity::hasEffect(int id) { + return activeEffects.find(id) != activeEffects.end(); + ; +} + +bool LivingEntity::hasEffect(MobEffect* effect) { + return activeEffects.find(effect->id) != activeEffects.end(); +} + +MobEffectInstance* LivingEntity::getEffect(MobEffect* effect) { + MobEffectInstance* effectInst = NULL; + + AUTO_VAR(it, activeEffects.find(effect->id)); + if (it != activeEffects.end()) effectInst = it->second; + + return effectInst; +} + +void LivingEntity::addEffect(MobEffectInstance* newEffect) { + if (!canBeAffected(newEffect)) { + return; + } + + if (activeEffects.find(newEffect->getId()) != activeEffects.end()) { + // replace effect and update + MobEffectInstance* effectInst = + activeEffects.find(newEffect->getId())->second; + effectInst->update(newEffect); + onEffectUpdated(effectInst, true); + } else { + activeEffects.insert( + std::unordered_map::value_type( + newEffect->getId(), newEffect)); + onEffectAdded(newEffect); + } +} + +// 4J Added +void LivingEntity::addEffectNoUpdate(MobEffectInstance* newEffect) { + if (!canBeAffected(newEffect)) { + return; + } + + if (activeEffects.find(newEffect->getId()) != activeEffects.end()) { + // replace effect and update + MobEffectInstance* effectInst = + activeEffects.find(newEffect->getId())->second; + effectInst->update(newEffect); + } else { + activeEffects.insert( + std::unordered_map::value_type( + newEffect->getId(), newEffect)); + } +} + +bool LivingEntity::canBeAffected(MobEffectInstance* newEffect) { + if (getMobType() == UNDEAD) { + int id = newEffect->getId(); + if (id == MobEffect::regeneration->id || id == MobEffect::poison->id) { + return false; + } + } + + return true; +} + +bool LivingEntity::isInvertedHealAndHarm() { return getMobType() == UNDEAD; } + +void LivingEntity::removeEffectNoUpdate(int effectId) { + AUTO_VAR(it, activeEffects.find(effectId)); + if (it != activeEffects.end()) { + MobEffectInstance* effect = it->second; + if (effect != NULL) { + delete effect; + } + activeEffects.erase(it); + } +} + +void LivingEntity::removeEffect(int effectId) { + AUTO_VAR(it, activeEffects.find(effectId)); + if (it != activeEffects.end()) { + MobEffectInstance* effect = it->second; + if (effect != NULL) { + onEffectRemoved(effect); + delete effect; + } + activeEffects.erase(it); + } +} + +void LivingEntity::onEffectAdded(MobEffectInstance* effect) { + effectsDirty = true; + if (!level->isClientSide) + MobEffect::effects[effect->getId()]->addAttributeModifiers( + std::dynamic_pointer_cast(shared_from_this()), + getAttributes(), effect->getAmplifier()); +} + +void LivingEntity::onEffectUpdated(MobEffectInstance* effect, + bool doRefreshAttributes) { + effectsDirty = true; + if (doRefreshAttributes && !level->isClientSide) { + MobEffect::effects[effect->getId()]->removeAttributeModifiers( + std::dynamic_pointer_cast(shared_from_this()), + getAttributes(), effect->getAmplifier()); + MobEffect::effects[effect->getId()]->addAttributeModifiers( + std::dynamic_pointer_cast(shared_from_this()), + getAttributes(), effect->getAmplifier()); + } +} + +void LivingEntity::onEffectRemoved(MobEffectInstance* effect) { + effectsDirty = true; + if (!level->isClientSide) + MobEffect::effects[effect->getId()]->removeAttributeModifiers( + std::dynamic_pointer_cast(shared_from_this()), + getAttributes(), effect->getAmplifier()); +} + +void LivingEntity::heal(float heal) { + float health = getHealth(); + if (health > 0) { + setHealth(health + heal); + } +} + +float LivingEntity::getHealth() { return entityData->getFloat(DATA_HEALTH_ID); } + +void LivingEntity::setHealth(float health) { + entityData->set(DATA_HEALTH_ID, Mth::clamp(health, 0.0f, getMaxHealth())); +} + +bool LivingEntity::hurt(DamageSource* source, float dmg) { + if (isInvulnerable()) return false; + + // 4J Stu - Reworked this function a bit to show hurt damage on the client + // before the server responds. Fix for #8823 - Gameplay: Confirmation that a + // monster or animal has taken damage from an attack is highly delayed 4J + // Stu - Change to the fix to only show damage when attacked, rather than + // collision damage Fix for #10299 - When in corners, passive mobs may show + // that they are taking damage. 4J Stu - Change to the fix for TU6, as + // source is never NULL due to changes in 1.8.2 to what source actually is + if (level->isClientSide && + dynamic_cast(source) == NULL) + return false; + noActionTime = 0; + if (getHealth() <= 0) return false; + + if (source->isFire() && hasEffect(MobEffect::fireResistance)) { + // 4J-JEV, for new achievement Stayin'Frosty, TODO merge with Java + // version. + if (this->instanceof(eTYPE_PLAYER) && + (source == + DamageSource::lava)) // Only award when in lava (not any fire). + { + std::shared_ptr plr = + std::dynamic_pointer_cast(shared_from_this()); + plr->awardStat(GenericStats::stayinFrosty(), + GenericStats::param_stayinFrosty()); + } + return false; + } + + if ((source == DamageSource::anvil || + source == DamageSource::fallingBlock) && + getCarried(SLOT_HELM) != NULL) { + getCarried(SLOT_HELM)->hurtAndBreak( + (int)(dmg * 4 + random->nextFloat() * dmg * 2.0f), + std::dynamic_pointer_cast(shared_from_this())); + dmg *= 0.75f; + } + + walkAnimSpeed = 1.5f; + + bool sound = true; + if (invulnerableTime > invulnerableDuration / 2.0f) { + if (dmg <= lastHurt) return false; + if (!level->isClientSide) actuallyHurt(source, dmg - lastHurt); + lastHurt = dmg; + sound = false; + } else { + lastHurt = dmg; + lastHealth = getHealth(); + invulnerableTime = invulnerableDuration; + if (!level->isClientSide) actuallyHurt(source, dmg); + hurtTime = hurtDuration = 10; + } + + hurtDir = 0; + + std::shared_ptr sourceEntity = source->getEntity(); + if (sourceEntity != NULL) { + if (sourceEntity->instanceof(eTYPE_LIVINGENTITY)) { + setLastHurtByMob( + std::dynamic_pointer_cast(sourceEntity)); + } + + if (sourceEntity->instanceof(eTYPE_PLAYER)) { + lastHurtByPlayerTime = PLAYER_HURT_EXPERIENCE_TIME; + lastHurtByPlayer = std::dynamic_pointer_cast(sourceEntity); + } else if (sourceEntity->instanceof(eTYPE_WOLF)) { + std::shared_ptr w = + std::dynamic_pointer_cast(sourceEntity); + if (w->isTame()) { + lastHurtByPlayerTime = PLAYER_HURT_EXPERIENCE_TIME; + lastHurtByPlayer = nullptr; + } + } + } + + if (sound && level->isClientSide) { + return false; + } + + if (sound) { + level->broadcastEntityEvent(shared_from_this(), EntityEvent::HURT); + if (source != DamageSource::drown) markHurt(); + if (sourceEntity != NULL) { + double xd = sourceEntity->x - x; + double zd = sourceEntity->z - z; + while (xd * xd + zd * zd < 0.0001) { + xd = (Math::random() - Math::random()) * 0.01; + zd = (Math::random() - Math::random()) * 0.01; + } + hurtDir = (float)(atan2(zd, xd) * 180 / PI) - yRot; + knockback(sourceEntity, dmg, xd, zd); + } else { + hurtDir = (float)(int)((Math::random() * 2) * + 180); // 4J This cast is the same as Java + } + } + + MemSect(31); + if (getHealth() <= 0) { + if (sound) + playSound(getDeathSound(), getSoundVolume(), getVoicePitch()); + die(source); + } else { + if (sound) playSound(getHurtSound(), getSoundVolume(), getVoicePitch()); + } + MemSect(0); + + return true; +} + +void LivingEntity::breakItem(std::shared_ptr itemInstance) { + playSound(eSoundType_RANDOM_BREAK, 0.8f, + 0.8f + level->random->nextFloat() * 0.4f); + + for (int i = 0; i < 5; i++) { + Vec3* d = Vec3::newTemp((random->nextFloat() - 0.5) * 0.1, + Math::random() * 0.1 + 0.1, 0); + d->xRot(-xRot * PI / 180); + d->yRot(-yRot * PI / 180); + + Vec3* p = Vec3::newTemp((random->nextFloat() - 0.5) * 0.3, + -random->nextFloat() * 0.6 - 0.3, 0.6); + p->xRot(-xRot * PI / 180); + p->yRot(-yRot * PI / 180); + p = p->add(x, y + getHeadHeight(), z); + level->addParticle(PARTICLE_ICONCRACK(itemInstance->getItem()->id, 0), + p->x, p->y, p->z, d->x, d->y + 0.05, d->z); + } +} + +void LivingEntity::die(DamageSource* source) { + std::shared_ptr sourceEntity = source->getEntity(); + std::shared_ptr killer = getKillCredit(); + if (deathScore >= 0 && killer != NULL) + killer->awardKillScore(shared_from_this(), deathScore); + + if (sourceEntity != NULL) + sourceEntity->killed( + std::dynamic_pointer_cast(shared_from_this())); + + dead = true; + + if (!level->isClientSide) { + int playerBonus = 0; + + std::shared_ptr player = nullptr; + if ((sourceEntity != NULL) && sourceEntity->instanceof(eTYPE_PLAYER)) { + player = std::dynamic_pointer_cast(sourceEntity); + playerBonus = EnchantmentHelper::getKillingLootBonus( + std::dynamic_pointer_cast(player)); + } + + if (!isBaby() && + level->getGameRules()->getBoolean(GameRules::RULE_DOMOBLOOT)) { + dropDeathLoot(lastHurtByPlayerTime > 0, playerBonus); + dropEquipment(lastHurtByPlayerTime > 0, playerBonus); + if (lastHurtByPlayerTime > 0) { + int rareLoot = random->nextInt(200) - playerBonus; + if (rareLoot < 5) { + dropRareDeathLoot((rareLoot <= 0) ? 1 : 0); + } + } + } + + // 4J-JEV, hook for Durango mobKill event. + if (player != NULL) { + player->awardStat( + GenericStats::killMob(), + GenericStats::param_mobKill( + player, std::dynamic_pointer_cast(shared_from_this()), + source)); + } + } + + level->broadcastEntityEvent(shared_from_this(), EntityEvent::DEATH); +} + +void LivingEntity::dropEquipment(bool byPlayer, int playerBonusLevel) {} + +void LivingEntity::knockback(std::shared_ptr source, float dmg, + double xd, double zd) { + if (random->nextDouble() < + getAttribute(SharedMonsterAttributes::KNOCKBACK_RESISTANCE) + ->getValue()) { + return; + } + + hasImpulse = true; + float dd = Mth::sqrt(xd * xd + zd * zd); + float pow = 0.4f; + + this->xd /= 2; + yd /= 2; + this->zd /= 2; + + this->xd -= xd / dd * pow; + yd += pow; + this->zd -= zd / dd * pow; + + if (yd > 0.4f) yd = 0.4f; +} + +int LivingEntity::getHurtSound() { return eSoundType_DAMAGE_HURT; } + +int LivingEntity::getDeathSound() { return eSoundType_DAMAGE_HURT; } + +/** + * Drop extra rare loot. Only occurs roughly 5% of the time, rareRootLevel + * is set to 1 (otherwise 0) 1% of the time. + * + * @param rareLootLevel + */ +void LivingEntity::dropRareDeathLoot(int rareLootLevel) {} + +void LivingEntity::dropDeathLoot(bool wasKilledByPlayer, int playerBonusLevel) { +} + +bool LivingEntity::onLadder() { + int xt = Mth::floor(x); + int yt = Mth::floor(bb->y0); + int zt = Mth::floor(z); + + // 4J-PB - TU9 - add climbable vines + int iTile = level->getTile(xt, yt, zt); + return (iTile == Tile::ladder_Id) || (iTile == Tile::vine_Id); +} + +bool LivingEntity::isShootable() { return true; } + +bool LivingEntity::isAlive() { return !removed && getHealth() > 0; } + +void LivingEntity::causeFallDamage(float distance) { + Entity::causeFallDamage(distance); + MobEffectInstance* jumpBoost = getEffect(MobEffect::jump); + float padding = jumpBoost != NULL ? jumpBoost->getAmplifier() + 1 : 0; + + int dmg = (int)ceil(distance - 3 - padding); + if (dmg > 0) { + // 4J - new sounds here brought forward from 1.2.3 + if (dmg > 4) { + playSound(eSoundType_DAMAGE_FALL_BIG, 1, 1); + } else { + playSound(eSoundType_DAMAGE_FALL_SMALL, 1, 1); + } + hurt(DamageSource::fall, dmg); + + int t = level->getTile(Mth::floor(x), + Mth::floor(y - 0.2f - this->heightOffset), + Mth::floor(z)); + if (t > 0) { + const Tile::SoundType* soundType = Tile::tiles[t]->soundType; + MemSect(31); + playSound(soundType->getStepSound(), soundType->getVolume() * 0.5f, + soundType->getPitch() * 0.75f); + MemSect(0); + } + } +} + +void LivingEntity::animateHurt() { + hurtTime = hurtDuration = 10; + hurtDir = 0; +} + +/** + * Fetches the mob's armor value, from 0 (no armor) to 20 (full armor) + * + * @return + */ +int LivingEntity::getArmorValue() { + int val = 0; + ItemInstanceArray items = getEquipmentSlots(); + for (unsigned int i = 0; i < items.length; ++i) { + std::shared_ptr item = items[i]; + if (item != NULL && dynamic_cast(item->getItem()) != NULL) { + int baseProtection = ((ArmorItem*)item->getItem())->defense; + val += baseProtection; + } + } + return val; +} + +void LivingEntity::hurtArmor(float damage) {} + +float LivingEntity::getDamageAfterArmorAbsorb(DamageSource* damageSource, + float damage) { + if (!damageSource->isBypassArmor()) { + int absorb = 25 - getArmorValue(); + float v = (damage)*absorb; + hurtArmor(damage); + damage = v / 25; + } + return damage; +} + +float LivingEntity::getDamageAfterMagicAbsorb(DamageSource* damageSource, + float damage) { + // [EB]: Stupid hack :( + if (this->instanceof(eTYPE_ZOMBIE)) { + damage = damage; + } + if (hasEffect(MobEffect::damageResistance) && + damageSource != DamageSource::outOfWorld) { + int absorbValue = + (getEffect(MobEffect::damageResistance)->getAmplifier() + 1) * 5; + int absorb = 25 - absorbValue; + float v = (damage)*absorb; + damage = v / 25; + } + + if (damage <= 0) return 0; + + int enchantmentArmor = EnchantmentHelper::getDamageProtection( + getEquipmentSlots(), damageSource); + if (enchantmentArmor > 20) { + enchantmentArmor = 20; + } + if (enchantmentArmor > 0 && enchantmentArmor <= 20) { + int absorb = 25 - enchantmentArmor; + float v = damage * absorb; + damage = v / 25; + } + + return damage; +} + +void LivingEntity::actuallyHurt(DamageSource* source, float dmg) { + if (isInvulnerable()) return; + dmg = getDamageAfterArmorAbsorb(source, dmg); + dmg = getDamageAfterMagicAbsorb(source, dmg); + + float originalDamage = dmg; + dmg = std::max(dmg - getAbsorptionAmount(), 0.0f); + setAbsorptionAmount(getAbsorptionAmount() - (originalDamage - dmg)); + if (dmg == 0) return; + + float oldHealth = getHealth(); + setHealth(oldHealth - dmg); + getCombatTracker()->recordDamage(source, oldHealth, dmg); + setAbsorptionAmount(getAbsorptionAmount() - dmg); +} + +CombatTracker* LivingEntity::getCombatTracker() { return combatTracker; } + +std::shared_ptr LivingEntity::getKillCredit() { + if (combatTracker->getKiller() != NULL) return combatTracker->getKiller(); + if (lastHurtByPlayer != NULL) return lastHurtByPlayer; + if (lastHurtByMob != NULL) return lastHurtByMob; + return nullptr; +} + +float LivingEntity::getMaxHealth() { + return (float)getAttribute(SharedMonsterAttributes::MAX_HEALTH)->getValue(); +} + +int LivingEntity::getArrowCount() { + return entityData->getByte(DATA_ARROW_COUNT_ID); +} + +void LivingEntity::setArrowCount(int count) { + entityData->set(DATA_ARROW_COUNT_ID, (uint8_t)count); +} + +int LivingEntity::getCurrentSwingDuration() { + if (hasEffect(MobEffect::digSpeed)) { + return SWING_DURATION - + (1 + getEffect(MobEffect::digSpeed)->getAmplifier()) * 1; + } + if (hasEffect(MobEffect::digSlowdown)) { + return SWING_DURATION + + (1 + getEffect(MobEffect::digSlowdown)->getAmplifier()) * 2; + } + return SWING_DURATION; +} + +void LivingEntity::swing() { + if (!swinging || swingTime >= getCurrentSwingDuration() / 2 || + swingTime < 0) { + swingTime = -1; + swinging = true; + + if (dynamic_cast(level) != NULL) { + ((ServerLevel*)level) + ->getTracker() + ->broadcast(shared_from_this(), + std::shared_ptr(new AnimatePacket( + shared_from_this(), AnimatePacket::SWING))); + } + } +} + +void LivingEntity::handleEntityEvent(uint8_t id) { + if (id == EntityEvent::HURT) { + walkAnimSpeed = 1.5f; + + invulnerableTime = invulnerableDuration; + hurtTime = hurtDuration = 10; + hurtDir = 0; + + MemSect(31); + // 4J-PB -added because villagers have no sounds + int iHurtSound = getHurtSound(); + if (iHurtSound != -1) { + playSound( + iHurtSound, getSoundVolume(), + (random->nextFloat() - random->nextFloat()) * 0.2f + 1.0f); + } + MemSect(0); + hurt(DamageSource::genericSource, 0); + } else if (id == EntityEvent::DEATH) { + MemSect(31); + // 4J-PB -added because villagers have no sounds + int iDeathSound = getDeathSound(); + if (iDeathSound != -1) { + playSound( + iDeathSound, getSoundVolume(), + (random->nextFloat() - random->nextFloat()) * 0.2f + 1.0f); + } + MemSect(0); + setHealth(0); + die(DamageSource::genericSource); + } else { + Entity::handleEntityEvent(id); + } +} + +void LivingEntity::outOfWorld() { hurt(DamageSource::outOfWorld, 4); } + +void LivingEntity::updateSwingTime() { + int currentSwingDuration = getCurrentSwingDuration(); + if (swinging) { + swingTime++; + if (swingTime >= currentSwingDuration) { + swingTime = 0; + swinging = false; + } + } else { + swingTime = 0; + } + + attackAnim = swingTime / (float)currentSwingDuration; +} + +AttributeInstance* LivingEntity::getAttribute(Attribute* attribute) { + return getAttributes()->getInstance(attribute); +} + +BaseAttributeMap* LivingEntity::getAttributes() { + if (attributes == NULL) { + attributes = new ServersideAttributeMap(); + } + + return attributes; +} + +MobType LivingEntity::getMobType() { return UNDEFINED; } + +void LivingEntity::setSprinting(bool value) { + Entity::setSprinting(value); + + AttributeInstance* speed = + getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED); + if (speed->getModifier(eModifierId_MOB_SPRINTING) != NULL) { + speed->removeModifier(eModifierId_MOB_SPRINTING); + } + if (value) { + speed->addModifier(new AttributeModifier(*SPEED_MODIFIER_SPRINTING)); + } +} + +float LivingEntity::getSoundVolume() { return 1; } + +float LivingEntity::getVoicePitch() { + if (isBaby()) { + return (random->nextFloat() - random->nextFloat()) * 0.2f + 1.5f; + } + return (random->nextFloat() - random->nextFloat()) * 0.2f + 1.0f; +} + +bool LivingEntity::isImmobile() { return getHealth() <= 0; } + +void LivingEntity::teleportTo(double x, double y, double z) { + moveTo(x, y, z, yRot, xRot); +} + +void LivingEntity::findStandUpPosition(std::shared_ptr vehicle) { + AABB* boundingBox; + double fallbackX = vehicle->x; + double fallbackY = vehicle->bb->y0 + vehicle->bbHeight; + double fallbackZ = vehicle->z; + + for (double xDiff = -1.5; xDiff < 2; xDiff += 1.5) { + for (double zDiff = -1.5; zDiff < 2; zDiff += 1.5) { + if (xDiff == 0 && zDiff == 0) { + continue; + } + + int xToInt = (int)(x + xDiff); + int zToInt = (int)(z + zDiff); + boundingBox = bb->cloneMove(xDiff, 1, zDiff); + + if (level->getTileCubes(boundingBox, true)->empty()) { + if (level->isTopSolidBlocking(xToInt, (int)y, zToInt)) { + teleportTo(x + xDiff, y + 1, z + zDiff); + return; + } else if (level->isTopSolidBlocking(xToInt, (int)y - 1, + zToInt) || + level->getMaterial(xToInt, (int)y - 1, zToInt) == + Material::water) { + fallbackX = x + xDiff; + fallbackY = y + 1; + fallbackZ = z + zDiff; + } + } + } + } + + teleportTo(fallbackX, fallbackY, fallbackZ); +} + +bool LivingEntity::shouldShowName() { return false; } + +Icon* LivingEntity::getItemInHandIcon(std::shared_ptr item, + int layer) { + return item->getIcon(); +} + +void LivingEntity::jumpFromGround() { + yd = 0.42f; + if (hasEffect(MobEffect::jump)) { + yd += (getEffect(MobEffect::jump)->getAmplifier() + 1) * .1f; + } + if (isSprinting()) { + float rr = yRot * Mth::RAD_TO_GRAD; + + xd -= Mth::sin(rr) * 0.2f; + zd += Mth::cos(rr) * 0.2f; + } + this->hasImpulse = true; +} + +void LivingEntity::travel(float xa, float ya) { +#ifdef __PSVITA__ + // AP - dynamic_pointer_cast is a non-trivial call + Player* thisPlayer = NULL; + if (this->instanceof(eTYPE_PLAYER)) { + thisPlayer = (Player*)this; + } +#else + std::shared_ptr thisPlayer = + std::dynamic_pointer_cast(shared_from_this()); +#endif + if (isInWater() && !(thisPlayer && thisPlayer->abilities.flying)) { + double yo = y; + moveRelative(xa, ya, useNewAi() ? 0.04f : 0.02f); + move(xd, yd, zd); + + xd *= 0.80f; + yd *= 0.80f; + zd *= 0.80f; + yd -= 0.02; + + if (horizontalCollision && isFree(xd, yd + 0.6f - y + yo, zd)) { + yd = 0.3f; + } + } else if (isInLava() && !(thisPlayer && thisPlayer->abilities.flying)) { + double yo = y; + moveRelative(xa, ya, 0.02f); + move(xd, yd, zd); + xd *= 0.50f; + yd *= 0.50f; + zd *= 0.50f; + yd -= 0.02; + + if (horizontalCollision && isFree(xd, yd + 0.6f - y + yo, zd)) { + yd = 0.3f; + } + } else { + float friction = 0.91f; + if (onGround) { + friction = 0.6f * 0.91f; + int t = level->getTile(Mth::floor(x), Mth::floor(bb->y0) - 1, + Mth::floor(z)); + if (t > 0) { + friction = Tile::tiles[t]->friction * 0.91f; + } + } + + float friction2 = (0.6f * 0.6f * 0.91f * 0.91f * 0.6f * 0.91f) / + (friction * friction * friction); + + float speed; + if (onGround) { + speed = getSpeed() * friction2; + } else { + speed = flyingSpeed; + } + + moveRelative(xa, ya, speed); + + friction = 0.91f; + if (onGround) { + friction = 0.6f * 0.91f; + int t = level->getTile(Mth::floor(x), Mth::floor(bb->y0) - 1, + Mth::floor(z)); + if (t > 0) { + friction = Tile::tiles[t]->friction * 0.91f; + } + } + if (onLadder()) { + float max = 0.15f; + if (xd < -max) xd = -max; + if (xd > max) xd = max; + if (zd < -max) zd = -max; + if (zd > max) zd = max; + fallDistance = 0; + if (yd < -0.15) yd = -0.15; + bool playerSneaking = + isSneaking() && this->instanceof(eTYPE_PLAYER); + if (playerSneaking && yd < 0) yd = 0; + } + + move(xd, yd, zd); + + if (horizontalCollision && onLadder()) { + yd = 0.2; + } + + if (!level->isClientSide || + (level->hasChunkAt((int)x, 0, (int)z) && + level->getChunkAt((int)x, (int)z)->loaded)) { + yd -= 0.08; + } else if (y > 0) { + yd = -0.1; + } else { + yd = 0; + } + + yd *= 0.98f; + xd *= friction; + zd *= friction; + } + + walkAnimSpeedO = walkAnimSpeed; + double xxd = x - xo; + double zzd = z - zo; + float wst = Mth::sqrt(xxd * xxd + zzd * zzd) * 4; + if (wst > 1) wst = 1; + walkAnimSpeed += (wst - walkAnimSpeed) * 0.4f; + walkAnimPos += walkAnimSpeed; +} + +// 4J - added for more accurate lighting of mobs. Takes a weighted average of +// all tiles touched by the bounding volume of the entity - the method in the +// Entity class (which used to be used for mobs too) simply gets a single tile's +// lighting value causing sudden changes of lighting values when entities go in +// and out of lit areas, for example when bobbing in the water. +int LivingEntity::getLightColor(float a) { + float accum[2] = {0, 0}; + float totVol = (bb->x1 - bb->x0) * (bb->y1 - bb->y0) * (bb->z1 - bb->z0); + int xmin = Mth::floor(bb->x0); + int xmax = Mth::floor(bb->x1); + int ymin = Mth::floor(bb->y0); + int ymax = Mth::floor(bb->y1); + int zmin = Mth::floor(bb->z0); + int zmax = Mth::floor(bb->z1); + for (int xt = xmin; xt <= xmax; xt++) + for (int yt = ymin; yt <= ymax; yt++) + for (int zt = zmin; zt <= zmax; zt++) { + float tilexmin = (float)xt; + float tilexmax = (float)(xt + 1); + float tileymin = (float)yt; + float tileymax = (float)(yt + 1); + float tilezmin = (float)zt; + float tilezmax = (float)(zt + 1); + if (tilexmin < bb->x0) tilexmin = bb->x0; + if (tilexmax > bb->x1) tilexmax = bb->x1; + if (tileymin < bb->y0) tileymin = bb->y0; + if (tileymax > bb->y1) tileymax = bb->y1; + if (tilezmin < bb->z0) tilezmin = bb->z0; + if (tilezmax > bb->z1) tilezmax = bb->z1; + float tileVol = (tilexmax - tilexmin) * (tileymax - tileymin) * + (tilezmax - tilezmin); + float frac = tileVol / totVol; + int lc = level->getLightColor(xt, yt, zt, 0); + accum[0] += frac * (float)(lc & 0xffff); + accum[1] += frac * (float)(lc >> 16); + } + + if (accum[0] > 240.0f) accum[0] = 240.0f; + if (accum[1] > 240.0f) accum[1] = 240.0f; + + return (((int)accum[1]) << 16) | ((int)accum[0]); +} + +bool LivingEntity::useNewAi() { return false; } + +float LivingEntity::getSpeed() { + if (useNewAi()) { + return speed; + } else { + return 0.1f; + } +} + +void LivingEntity::setSpeed(float speed) { this->speed = speed; } + +bool LivingEntity::doHurtTarget(std::shared_ptr target) { + setLastHurtMob(target); + return false; +} + +bool LivingEntity::isSleeping() { return false; } + +void LivingEntity::tick() { + Entity::tick(); + + if (!level->isClientSide) { + int arrowCount = getArrowCount(); + if (arrowCount > 0) { + if (removeArrowTime <= 0) { + removeArrowTime = + SharedConstants::TICKS_PER_SECOND * (30 - arrowCount); + } + removeArrowTime--; + if (removeArrowTime <= 0) { + setArrowCount(arrowCount - 1); + } + } + + for (int i = 0; i < 5; i++) { + std::shared_ptr previous = lastEquipment[i]; + std::shared_ptr current = getCarried(i); + + if (!ItemInstance::matches(current, previous)) { + ((ServerLevel*)level) + ->getTracker() + ->broadcast( + shared_from_this(), + std::shared_ptr( + new SetEquippedItemPacket(entityId, i, current))); + if (previous != NULL) attributes->removeItemModifiers(previous); + if (current != NULL) attributes->addItemModifiers(current); + lastEquipment[i] = current == NULL ? nullptr : current->copy(); + } + } + } + + aiStep(); + + double xd = x - xo; + double zd = z - zo; + + float sideDist = xd * xd + zd * zd; + + float yBodyRotT = yBodyRot; + + float walkSpeed = 0; + oRun = run; + float tRun = 0; + if (sideDist > 0.05f * 0.05f) { + tRun = 1; + walkSpeed = sqrt(sideDist) * 3; + yBodyRotT = ((float)atan2(zd, xd) * 180 / (float)PI - 90); + } + if (attackAnim > 0) { + yBodyRotT = yRot; + } + if (!onGround) { + tRun = 0; + } + run = run + (tRun - run) * 0.3f; + + walkSpeed = tickHeadTurn(yBodyRotT, walkSpeed); + + while (yRot - yRotO < -180) yRotO -= 360; + while (yRot - yRotO >= 180) yRotO += 360; + + while (yBodyRot - yBodyRotO < -180) yBodyRotO -= 360; + while (yBodyRot - yBodyRotO >= 180) yBodyRotO += 360; + + while (xRot - xRotO < -180) xRotO -= 360; + while (xRot - xRotO >= 180) xRotO += 360; + + while (yHeadRot - yHeadRotO < -180) yHeadRotO -= 360; + while (yHeadRot - yHeadRotO >= 180) yHeadRotO += 360; + + animStep += walkSpeed; +} + +float LivingEntity::tickHeadTurn(float yBodyRotT, float walkSpeed) { + float yBodyRotD = Mth::wrapDegrees(yBodyRotT - yBodyRot); + yBodyRot += yBodyRotD * 0.3f; + + float headDiff = Mth::wrapDegrees(yRot - yBodyRot); + bool behind = headDiff < -90 || headDiff >= 90; + if (headDiff < -75) headDiff = -75; + if (headDiff >= 75) headDiff = +75; + yBodyRot = yRot - headDiff; + if (headDiff * headDiff > 50 * 50) { + yBodyRot += headDiff * 0.2f; + } + + if (behind) { + walkSpeed *= -1; + } + + return walkSpeed; +} + +void LivingEntity::aiStep() { + if (noJumpDelay > 0) noJumpDelay--; + if (lSteps > 0) { + double xt = x + (lx - x) / lSteps; + double yt = y + (ly - y) / lSteps; + double zt = z + (lz - z) / lSteps; + + double yrd = Mth::wrapDegrees(lyr - yRot); + double xrd = Mth::wrapDegrees(lxr - xRot); + + yRot += (float)((yrd) / lSteps); + xRot += (float)((xrd) / lSteps); + + lSteps--; + setPos(xt, yt, zt); + setRot(yRot, xRot); + + // 4J - this collision is carried out to try and stop the lerping push + // the mob through the floor, in which case gravity can then carry on + // moving the mob because the collision just won't work anymore. BB for + // collision used to be calculated as: bb->shrink(1 / 32.0, 0, 1 / 32.0) + // now using a reduced BB to try and get rid of some issues where mobs + // pop up the sides of walls, undersides of trees etc. + AABB* shrinkbb = bb->shrink(0.1, 0, 0.1); + shrinkbb->y1 = shrinkbb->y0 + 0.1; + AABBList* collisions = level->getCubes(shared_from_this(), shrinkbb); + if (collisions->size() > 0) { + double yTop = 0; + AUTO_VAR(itEnd, collisions->end()); + for (AUTO_VAR(it, collisions->begin()); it != itEnd; it++) { + AABB* ab = *it; // collisions->at(i); + if (ab->y1 > yTop) yTop = ab->y1; + } + + yt += yTop - bb->y0; + setPos(xt, yt, zt); + } + } else if (!isEffectiveAi()) { + // slow down predicted speed, to prevent mobs from sliding through + // walls etc + xd *= .98; + yd *= .98; + zd *= .98; + } + + if (abs(xd) < MIN_MOVEMENT_DISTANCE) xd = 0; + if (abs(yd) < MIN_MOVEMENT_DISTANCE) yd = 0; + if (abs(zd) < MIN_MOVEMENT_DISTANCE) zd = 0; + + if (isImmobile()) { + jumping = false; + xxa = 0; + yya = 0; + yRotA = 0; + } else { + MemSect(25); + if (isEffectiveAi()) { + if (useNewAi()) { + newServerAiStep(); + } else { + serverAiStep(); + yHeadRot = yRot; + } + } + MemSect(0); + } + + if (jumping) { + if (isInWater() || isInLava()) { + yd += 0.04f; + } else if (onGround) { + if (noJumpDelay == 0) { + jumpFromGround(); + noJumpDelay = 10; + } + } + } else { + noJumpDelay = 0; + } + + xxa *= 0.98f; + yya *= 0.98f; + yRotA *= 0.9f; + + travel(xxa, yya); + + if (!level->isClientSide) { + pushEntities(); + } +} + +void LivingEntity::newServerAiStep() {} + +void LivingEntity::pushEntities() { + std::vector >* entities = + level->getEntities(shared_from_this(), this->bb->grow(0.2f, 0, 0.2f)); + if (entities != NULL && !entities->empty()) { + AUTO_VAR(itEnd, entities->end()); + for (AUTO_VAR(it, entities->begin()); it != itEnd; it++) { + std::shared_ptr e = *it; // entities->at(i); + if (e->isPushable()) e->push(shared_from_this()); + } + } +} + +void LivingEntity::doPush(std::shared_ptr e) { + e->push(shared_from_this()); +} + +void LivingEntity::rideTick() { + Entity::rideTick(); + oRun = run; + run = 0; + fallDistance = 0; +} + +void LivingEntity::lerpTo(double x, double y, double z, float yRot, float xRot, + int steps) { + heightOffset = 0; + lx = x; + ly = y; + lz = z; + lyr = yRot; + lxr = xRot; + + lSteps = steps; +} + +void LivingEntity::serverAiMobStep() {} + +void LivingEntity::serverAiStep() { noActionTime++; } + +void LivingEntity::setJumping(bool jump) { jumping = jump; } + +void LivingEntity::take(std::shared_ptr e, int orgCount) { + if (!e->removed && !level->isClientSide) { + EntityTracker* entityTracker = ((ServerLevel*)level)->getTracker(); + if (e->instanceof(eTYPE_ITEMENTITY)) { + entityTracker->broadcast( + e, std::shared_ptr( + new TakeItemEntityPacket(e->entityId, entityId))); + } else if (e->instanceof(eTYPE_ARROW)) { + entityTracker->broadcast( + e, std::shared_ptr( + new TakeItemEntityPacket(e->entityId, entityId))); + } else if (e->instanceof(eTYPE_EXPERIENCEORB)) { + entityTracker->broadcast( + e, std::shared_ptr( + new TakeItemEntityPacket(e->entityId, entityId))); + } + } +} + +bool LivingEntity::canSee(std::shared_ptr target) { + HitResult* hres = level->clip( + Vec3::newTemp(x, y + getHeadHeight(), z), + Vec3::newTemp(target->x, target->y + target->getHeadHeight(), + target->z)); + bool retVal = (hres == NULL); + delete hres; + return retVal; +} + +Vec3* LivingEntity::getLookAngle() { return getViewVector(1); } + +Vec3* LivingEntity::getViewVector(float a) { + if (a == 1) { + float yCos = Mth::cos(-yRot * Mth::RAD_TO_GRAD - PI); + float ySin = Mth::sin(-yRot * Mth::RAD_TO_GRAD - PI); + float xCos = -Mth::cos(-xRot * Mth::RAD_TO_GRAD); + float xSin = Mth::sin(-xRot * Mth::RAD_TO_GRAD); + + return Vec3::newTemp(ySin * xCos, xSin, yCos * xCos); + } + float xRot = xRotO + (this->xRot - xRotO) * a; + float yRot = yRotO + (this->yRot - yRotO) * a; + + float yCos = Mth::cos(-yRot * Mth::RAD_TO_GRAD - PI); + float ySin = Mth::sin(-yRot * Mth::RAD_TO_GRAD - PI); + float xCos = -Mth::cos(-xRot * Mth::RAD_TO_GRAD); + float xSin = Mth::sin(-xRot * Mth::RAD_TO_GRAD); + + return Vec3::newTemp(ySin * xCos, xSin, yCos * xCos); +} + +float LivingEntity::getAttackAnim(float a) { + float diff = attackAnim - oAttackAnim; + if (diff < 0) diff += 1; + return oAttackAnim + diff * a; +} + +Vec3* LivingEntity::getPos(float a) { + if (a == 1) { + return Vec3::newTemp(x, y, z); + } + double x = xo + (this->x - xo) * a; + double y = yo + (this->y - yo) * a; + double z = zo + (this->z - zo) * a; + + return Vec3::newTemp(x, y, z); +} + +HitResult* LivingEntity::pick(double range, float a) { + Vec3* from = getPos(a); + Vec3* b = getViewVector(a); + Vec3* to = from->add(b->x * range, b->y * range, b->z * range); + return level->clip(from, to); +} + +bool LivingEntity::isEffectiveAi() { return !level->isClientSide; } + +bool LivingEntity::isPickable() { return !removed; } + +bool LivingEntity::isPushable() { return !removed; } + +float LivingEntity::getHeadHeight() { return bbHeight * 0.85f; } + +void LivingEntity::markHurt() { + hurtMarked = + random->nextDouble() >= + getAttribute(SharedMonsterAttributes::KNOCKBACK_RESISTANCE)->getValue(); +} + +float LivingEntity::getYHeadRot() { return yHeadRot; } + +void LivingEntity::setYHeadRot(float yHeadRot) { this->yHeadRot = yHeadRot; } + +float LivingEntity::getAbsorptionAmount() { return absorptionAmount; } + +void LivingEntity::setAbsorptionAmount(float absorptionAmount) { + if (absorptionAmount < 0) absorptionAmount = 0; + this->absorptionAmount = absorptionAmount; +} + +Team* LivingEntity::getTeam() { return NULL; } + +bool LivingEntity::isAlliedTo(std::shared_ptr other) { + return isAlliedTo(other->getTeam()); +} + +bool LivingEntity::isAlliedTo(Team* other) { + if (getTeam() != NULL) { + return getTeam()->isAlliedTo(other); + } + return false; +} \ No newline at end of file diff --git a/Minecraft.World/Entities/LivingEntity.h b/Minecraft.World/Entities/LivingEntity.h new file mode 100644 index 000000000..ead873ce3 --- /dev/null +++ b/Minecraft.World/Entities/LivingEntity.h @@ -0,0 +1,330 @@ +#pragma once + +#include "Entity.h" +#include "MobType.h" +#include "../AI/Goals/GoalSelector.h" +#include "../Util/SharedConstants.h" + +class CombatTracker; +class AttributeInstance; +class AttributeModifier; +class MobEffectInstance; +class BaseAttributeMap; +class Team; +class Attribute; +class MobEffect; +class HitResult; +class Vec3; + +class LivingEntity : public Entity { + friend class MobSpawner; + +protected: + // 4J - added for common ctor code + void _init(); + +public: + // 4J-PB - added to replace (e instanceof Type), avoiding dynamic casts + eINSTANCEOF GetType() { return eTYPE_LIVINGENTITY; } + static Entity* create(Level* level) { return NULL; } + +private: + static AttributeModifier* SPEED_MODIFIER_SPRINTING; + +public: + static const int SLOT_WEAPON = 0; + static const int SLOT_BOOTS = 1; + static const int SLOT_LEGGINGS = 2; + static const int SLOT_CHEST = 3; + static const int SLOT_HELM = 4; + + static const int SWING_DURATION = 6; + static const int PLAYER_HURT_EXPERIENCE_TIME = + SharedConstants::TICKS_PER_SECOND * 5; + +private: + static const double MIN_MOVEMENT_DISTANCE; + +public: + static const int DATA_HEALTH_ID = 6; + static const int DATA_EFFECT_COLOR_ID = 7; + static const int DATA_EFFECT_AMBIENCE_ID = 8; + static const int DATA_ARROW_COUNT_ID = 9; + +private: + BaseAttributeMap* attributes; + CombatTracker* combatTracker; + std::unordered_map activeEffects; + ItemInstanceArray lastEquipment; + +public: + bool swinging; + int swingTime; + int removeArrowTime; + float lastHealth; + + int hurtTime; + int hurtDuration; + float hurtDir; + int deathTime; + int attackTime; + float oAttackAnim, attackAnim; + + float walkAnimSpeedO; + float walkAnimSpeed; + float walkAnimPos; + int invulnerableDuration; + float oTilt, tilt; + float timeOffs; + float rotA; + float yBodyRot, yBodyRotO; + float yHeadRot, yHeadRotO; + float flyingSpeed; + +protected: + std::shared_ptr lastHurtByPlayer; + int lastHurtByPlayerTime; + bool dead; + int noActionTime; + float oRun, run; + float animStep, animStepO; + float rotOffs; + int deathScore; + float lastHurt; + bool jumping; + +public: + float xxa; + float yya; + +protected: + float yRotA; + int lSteps; + double lx, ly, lz, lyr, lxr; + +private: + bool effectsDirty; + + std::shared_ptr lastHurtByMob; + int lastHurtByMobTimestamp; + std::shared_ptr lastHurtMob; + int lastHurtMobTimestamp; + + float speed; + +protected: + int noJumpDelay; + +private: + float absorptionAmount; + +public: + LivingEntity(Level* level); + virtual ~LivingEntity(); + +protected: + virtual void defineSynchedData(); + virtual void registerAttributes(); + virtual void checkFallDamage(double ya, bool onGround); + +public: + virtual bool isWaterMob(); + virtual void baseTick(); + virtual bool isBaby(); + +protected: + virtual void tickDeath(); + virtual int decreaseAirSupply(int currentSupply); + virtual int getExperienceReward(std::shared_ptr killedBy); + virtual bool isAlwaysExperienceDropper(); + +public: + virtual Random* getRandom(); + virtual std::shared_ptr getLastHurtByMob(); + virtual int getLastHurtByMobTimestamp(); + virtual void setLastHurtByMob(std::shared_ptr hurtBy); + virtual std::shared_ptr getLastHurtMob(); + virtual int getLastHurtMobTimestamp(); + virtual void setLastHurtMob(std::shared_ptr target); + virtual int getNoActionTime(); + virtual void addAdditonalSaveData(CompoundTag* entityTag); + virtual void readAdditionalSaveData(CompoundTag* tag); + +protected: + virtual void tickEffects(); + +public: + virtual void removeAllEffects(); + virtual std::vector* getActiveEffects(); + virtual bool hasEffect(int id); + virtual bool hasEffect(MobEffect* effect); + virtual MobEffectInstance* getEffect(MobEffect* effect); + virtual void addEffect(MobEffectInstance* newEffect); + virtual void addEffectNoUpdate(MobEffectInstance* newEffect); // 4J added + virtual bool canBeAffected(MobEffectInstance* newEffect); + virtual bool isInvertedHealAndHarm(); + virtual void removeEffectNoUpdate(int effectId); + virtual void removeEffect(int effectId); + +protected: + virtual void onEffectAdded(MobEffectInstance* effect); + virtual void onEffectUpdated(MobEffectInstance* effect, + bool doRefreshAttributes); + virtual void onEffectRemoved(MobEffectInstance* effect); + +public: + virtual void heal(float heal); + virtual float getHealth(); + virtual void setHealth(float health); + virtual bool hurt(DamageSource* source, float dmg); + virtual void breakItem(std::shared_ptr itemInstance); + virtual void die(DamageSource* source); + +protected: + virtual void dropEquipment(bool byPlayer, int playerBonusLevel); + +public: + virtual void knockback(std::shared_ptr source, float dmg, double xd, + double zd); + +protected: + virtual int getHurtSound(); + virtual int getDeathSound(); + +protected: + virtual void dropRareDeathLoot(int rareLootLevel); + virtual void dropDeathLoot(bool wasKilledByPlayer, int playerBonusLevel); + +public: + virtual bool onLadder(); + virtual bool isShootable(); + virtual bool isAlive(); + virtual void causeFallDamage(float distance); + virtual void animateHurt(); + virtual int getArmorValue(); + +protected: + virtual void hurtArmor(float damage); + virtual float getDamageAfterArmorAbsorb(DamageSource* damageSource, + float damage); + virtual float getDamageAfterMagicAbsorb(DamageSource* damageSource, + float damage); + virtual void actuallyHurt(DamageSource* source, float dmg); + +public: + virtual CombatTracker* getCombatTracker(); + virtual std::shared_ptr getKillCredit(); + virtual float getMaxHealth(); + virtual int getArrowCount(); + virtual void setArrowCount(int count); + +private: + int getCurrentSwingDuration(); + +public: + virtual void swing(); + virtual void handleEntityEvent(uint8_t id); + +protected: + virtual void outOfWorld(); + virtual void updateSwingTime(); + +public: + virtual AttributeInstance* getAttribute(Attribute* attribute); + virtual BaseAttributeMap* getAttributes(); + virtual MobType getMobType(); + + virtual std::shared_ptr getCarriedItem() = 0; + virtual std::shared_ptr getCarried(int slot) = 0; + virtual std::shared_ptr getArmor(int pos) = 0; + virtual void setEquippedSlot(int slot, + std::shared_ptr item) = 0; + virtual void setSprinting(bool value); + + virtual ItemInstanceArray getEquipmentSlots() = 0; + + virtual Icon* getItemInHandIcon(std::shared_ptr item, + int layer); + +protected: + virtual float getSoundVolume(); + virtual float getVoicePitch(); + virtual bool isImmobile(); + +public: + virtual void teleportTo(double x, double y, double z); + +protected: + virtual void findStandUpPosition(std::shared_ptr vehicle); + +public: + virtual bool shouldShowName(); + +protected: + virtual void jumpFromGround(); + +public: + virtual void travel(float xa, float ya); + + virtual int getLightColor(float a); // 4J - added + +protected: + virtual bool useNewAi(); + +public: + virtual float getSpeed(); + virtual void setSpeed(float speed); + virtual bool doHurtTarget(std::shared_ptr target); + virtual bool isSleeping(); + virtual void tick(); + +protected: + virtual float tickHeadTurn(float yBodyRotT, float walkSpeed); + +public: + virtual void aiStep(); + +protected: + virtual void newServerAiStep(); + virtual void pushEntities(); + virtual void doPush(std::shared_ptr e); + +public: + virtual void rideTick(); + virtual void lerpTo(double x, double y, double z, float yRot, float xRot, + int steps); + +protected: + virtual void serverAiMobStep(); + virtual void serverAiStep(); + +public: + virtual void setJumping(bool jump); + virtual void take(std::shared_ptr e, int orgCount); + virtual bool canSee(std::shared_ptr target); + +public: + virtual Vec3* getLookAngle(); + virtual Vec3* getViewVector(float a); + virtual float getAttackAnim(float a); + virtual Vec3* getPos(float a); + virtual HitResult* pick(double range, float a); + virtual bool isEffectiveAi(); + + virtual bool isPickable(); + virtual bool isPushable(); + virtual float getHeadHeight(); + +protected: + virtual void markHurt(); + +public: + virtual float getYHeadRot(); + virtual void setYHeadRot(float yHeadRot); + + virtual float getAbsorptionAmount(); + virtual void setAbsorptionAmount(float absorptionAmount); + virtual Team* getTeam(); + virtual bool isAlliedTo(std::shared_ptr other); + virtual bool isAlliedTo(Team* other); +}; diff --git a/Minecraft.World/Entities/MinecartChest.cpp b/Minecraft.World/Entities/MinecartChest.cpp new file mode 100644 index 000000000..8efbf811b --- /dev/null +++ b/Minecraft.World/Entities/MinecartChest.cpp @@ -0,0 +1,36 @@ +#include "../Platform/stdafx.h" +#include "../Headers/net.minecraft.world.level.tile.h" +#include "../Headers/net.minecraft.network.packet.h" +#include "MinecartChest.h" + +MinecartChest::MinecartChest(Level* level) : MinecartContainer(level) { + // 4J Stu - This function call had to be moved here from the Entity ctor to + // ensure that the derived version of the function is called + this->defineSynchedData(); +} + +MinecartChest::MinecartChest(Level* level, double x, double y, double z) + : MinecartContainer(level, x, y, z) { + // 4J Stu - This function call had to be moved here from the Entity ctor to + // ensure that the derived version of the function is called + this->defineSynchedData(); +} + +// 4J Added +int MinecartChest::getContainerType() { + return ContainerOpenPacket::MINECART_CHEST; +} + +void MinecartChest::destroy(DamageSource* source) { + MinecartContainer::destroy(source); + + spawnAtLocation(Tile::chest_Id, 1, 0); +} + +unsigned int MinecartChest::getContainerSize() { return 9 * 3; } + +int MinecartChest::getType() { return TYPE_CHEST; } + +Tile* MinecartChest::getDefaultDisplayTile() { return Tile::chest; } + +int MinecartChest::getDefaultDisplayOffset() { return 8; } \ No newline at end of file diff --git a/Minecraft.World/Entities/MinecartChest.h b/Minecraft.World/Entities/MinecartChest.h new file mode 100644 index 000000000..8bad82273 --- /dev/null +++ b/Minecraft.World/Entities/MinecartChest.h @@ -0,0 +1,22 @@ +#pragma once + +#include "MinecartContainer.h" + +class MinecartChest : public MinecartContainer { +public: + eINSTANCEOF GetType() { return eTYPE_MINECART_CHEST; }; + static Entity* create(Level* level) { return new MinecartChest(level); } + +public: + MinecartChest(Level* level); + MinecartChest(Level* level, double x, double y, double z); + + // 4J added + virtual int getContainerType(); + + virtual void destroy(DamageSource* source); + virtual unsigned int getContainerSize(); + virtual int getType(); + virtual Tile* getDefaultDisplayTile(); + virtual int getDefaultDisplayOffset(); +}; \ No newline at end of file diff --git a/Minecraft.World/Entities/MinecartContainer.cpp b/Minecraft.World/Entities/MinecartContainer.cpp new file mode 100644 index 000000000..63364c434 --- /dev/null +++ b/Minecraft.World/Entities/MinecartContainer.cpp @@ -0,0 +1,213 @@ +#include "../Platform/stdafx.h" +#include "../Headers/net.minecraft.world.entity.item.h" +#include "../Headers/net.minecraft.world.item.h" +#include "../Headers/net.minecraft.world.inventory.h" +#include "../Headers/net.minecraft.world.level.h" +#include "../Headers/net.minecraft.world.level.redstone.h" +#include "MinecartContainer.h" + +void MinecartContainer::_init() { + items = ItemInstanceArray(9 * 4); + dropEquipment = true; + + // 4J Stu - This function call had to be moved here from the Entity ctor to + // ensure that the derived version of the function is called + this->defineSynchedData(); +} + +MinecartContainer::MinecartContainer(Level* level) : Minecart(level) { + _init(); +} + +MinecartContainer::MinecartContainer(Level* level, double x, double y, double z) + : Minecart(level, x, y, z) { + _init(); +} + +void MinecartContainer::destroy(DamageSource* source) { + Minecart::destroy(source); + + for (int i = 0; i < getContainerSize(); i++) { + std::shared_ptr item = getItem(i); + if (item != NULL) { + float xo = random->nextFloat() * 0.8f + 0.1f; + float yo = random->nextFloat() * 0.8f + 0.1f; + float zo = random->nextFloat() * 0.8f + 0.1f; + + while (item->count > 0) { + int count = random->nextInt(21) + 10; + if (count > item->count) count = item->count; + item->count -= count; + + std::shared_ptr itemEntity = + std::shared_ptr(new ItemEntity( + level, x + xo, y + yo, z + zo, + std::shared_ptr(new ItemInstance( + item->id, count, item->getAuxValue())))); + float pow = 0.05f; + itemEntity->xd = (float)random->nextGaussian() * pow; + itemEntity->yd = (float)random->nextGaussian() * pow + 0.2f; + itemEntity->zd = (float)random->nextGaussian() * pow; + level->addEntity(itemEntity); + } + } + } +} + +std::shared_ptr MinecartContainer::getItem(unsigned int slot) { + return items[slot]; +} + +std::shared_ptr MinecartContainer::removeItem(unsigned int slot, + int count) { + if (items[slot] != NULL) { + if (items[slot]->count <= count) { + std::shared_ptr item = items[slot]; + items[slot] = nullptr; + return item; + } else { + std::shared_ptr i = items[slot]->remove(count); + if (items[slot]->count == 0) items[slot] = nullptr; + return i; + } + } + return nullptr; +} + +std::shared_ptr MinecartContainer::removeItemNoUpdate(int slot) { + if (items[slot] != NULL) { + std::shared_ptr item = items[slot]; + items[slot] = nullptr; + return item; + } + return nullptr; +} + +void MinecartContainer::setItem(unsigned int slot, + std::shared_ptr item) { + items[slot] = item; + if (item != NULL && item->count > getMaxStackSize()) + item->count = getMaxStackSize(); +} + +void MinecartContainer::setChanged() {} + +bool MinecartContainer::stillValid(std::shared_ptr player) { + if (removed) return false; + if (player->distanceToSqr(shared_from_this()) > 8 * 8) return false; + return true; +} + +void MinecartContainer::startOpen() {} + +void MinecartContainer::stopOpen() {} + +bool MinecartContainer::canPlaceItem(int slot, + std::shared_ptr item) { + return true; +} + +std::wstring MinecartContainer::getName() { + return hasCustomName() ? getCustomName() + : app.GetString(IDS_CONTAINER_MINECART); +} + +int MinecartContainer::getMaxStackSize() { + return Container::LARGE_MAX_STACK_SIZE; +} + +void MinecartContainer::changeDimension(int i) { + dropEquipment = false; + Minecart::changeDimension(i); +} + +void MinecartContainer::remove() { + if (dropEquipment) { + for (int i = 0; i < getContainerSize(); i++) { + std::shared_ptr item = getItem(i); + if (item != NULL) { + float xo = random->nextFloat() * 0.8f + 0.1f; + float yo = random->nextFloat() * 0.8f + 0.1f; + float zo = random->nextFloat() * 0.8f + 0.1f; + + while (item->count > 0) { + int count = random->nextInt(21) + 10; + if (count > item->count) count = item->count; + item->count -= count; + + std::shared_ptr itemEntity = + std::shared_ptr(new ItemEntity( + level, x + xo, y + yo, z + zo, + std::shared_ptr(new ItemInstance( + item->id, count, item->getAuxValue())))); + + if (item->hasTag()) { + itemEntity->getItem()->setTag( + (CompoundTag*)item->getTag()->copy()); + } + + float pow = 0.05f; + itemEntity->xd = (float)random->nextGaussian() * pow; + itemEntity->yd = (float)random->nextGaussian() * pow + 0.2f; + itemEntity->zd = (float)random->nextGaussian() * pow; + level->addEntity(itemEntity); + } + } + } + } + + Minecart::remove(); +} + +void MinecartContainer::addAdditonalSaveData(CompoundTag* base) { + Minecart::addAdditonalSaveData(base); + + ListTag* listTag = new ListTag(); + + for (int i = 0; i < items.length; i++) { + if (items[i] != NULL) { + CompoundTag* tag = new CompoundTag(); + tag->putByte(L"Slot", (uint8_t)i); + items[i]->save(tag); + listTag->add(tag); + } + } + base->put(L"Items", listTag); +} + +void MinecartContainer::readAdditionalSaveData(CompoundTag* base) { + Minecart::readAdditionalSaveData(base); + + ListTag* inventoryList = + (ListTag*)base->getList(L"Items"); + delete[] items.data; + items = ItemInstanceArray(getContainerSize()); + for (int i = 0; i < inventoryList->size(); i++) { + CompoundTag* tag = inventoryList->get(i); + int slot = tag->getByte(L"Slot") & 0xff; + if (slot >= 0 && slot < items.length) + items[slot] = ItemInstance::fromTag(tag); + } +} + +bool MinecartContainer::interact(std::shared_ptr player) { + if (!level->isClientSide) { + player->openContainer( + std::dynamic_pointer_cast(shared_from_this())); + } + + return true; +} + +void MinecartContainer::applyNaturalSlowdown() { + std::shared_ptr container = + std::dynamic_pointer_cast(shared_from_this()); + int emptiness = + Redstone::SIGNAL_MAX - + AbstractContainerMenu::getRedstoneSignalFromContainer(container); + float keep = 0.98f + (emptiness * 0.001f); + + xd *= keep; + yd *= 0; + zd *= keep; +} \ No newline at end of file diff --git a/Minecraft.World/Entities/MinecartContainer.h b/Minecraft.World/Entities/MinecartContainer.h new file mode 100644 index 000000000..7831cb4c4 --- /dev/null +++ b/Minecraft.World/Entities/MinecartContainer.h @@ -0,0 +1,47 @@ +#pragma once + +#include "Mobs/Minecart.h" +#include "../Containers/Container.h" + +class MinecartContainer : public Minecart, public virtual Container { +private: + ItemInstanceArray items; + bool dropEquipment; + + void _init(); + +public: + MinecartContainer(Level* level); + MinecartContainer(Level* level, double x, double y, double z); + + virtual void destroy(DamageSource* source); + virtual std::shared_ptr getItem(unsigned int slot); + virtual std::shared_ptr removeItem(unsigned int slot, + int count); + virtual std::shared_ptr removeItemNoUpdate(int slot); + virtual void setItem(unsigned int slot, std::shared_ptr item); + virtual void setChanged(); + virtual bool stillValid(std::shared_ptr player); + virtual void startOpen(); + virtual void stopOpen(); + virtual bool canPlaceItem(int slot, std::shared_ptr item); + virtual std::wstring getName(); + virtual int getMaxStackSize(); + virtual void changeDimension(int i); + virtual void remove(); + +protected: + virtual void addAdditonalSaveData(CompoundTag* base); + virtual void readAdditionalSaveData(CompoundTag* base); + +public: + virtual bool interact(std::shared_ptr player); + +protected: + virtual void applyNaturalSlowdown(); + +public: + // 4J Stu - For container + virtual bool hasCustomName() { return Minecart::hasCustomName(); } + virtual std::wstring getCustomName() { return Minecart::getCustomName(); } +}; \ No newline at end of file diff --git a/Minecraft.World/Entities/MinecartFurnace.cpp b/Minecraft.World/Entities/MinecartFurnace.cpp new file mode 100644 index 000000000..87d95a868 --- /dev/null +++ b/Minecraft.World/Entities/MinecartFurnace.cpp @@ -0,0 +1,148 @@ +#include "../Platform/stdafx.h" +#include "../Headers/net.minecraft.world.damagesource.h" +#include "../Headers/net.minecraft.world.entity.h" +#include "../Headers/net.minecraft.world.entity.player.h" +#include "../Headers/net.minecraft.world.level.h" +#include "../Headers/net.minecraft.world.item.h" +#include "../Headers/net.minecraft.network.packet.h" +#include "MinecartFurnace.h" + +MinecartFurnace::MinecartFurnace(Level* level) : Minecart(level) { + defineSynchedData(); + + fuel = 0; + xPush = zPush = 0.0f; +} + +MinecartFurnace::MinecartFurnace(Level* level, double x, double y, double z) + : Minecart(level, x, y, z) { + defineSynchedData(); + + fuel = 0; + xPush = zPush = 0.0f; +} + +// 4J Added +int MinecartFurnace::getContainerType() { + return ContainerOpenPacket::MINECART_HOPPER; +} + +int MinecartFurnace::getType() { return TYPE_FURNACE; } + +void MinecartFurnace::defineSynchedData() { + Minecart::defineSynchedData(); + entityData->define(DATA_ID_FUEL, (uint8_t)0); +} + +void MinecartFurnace::tick() { + Minecart::tick(); + + if (fuel > 0) { + fuel--; + } + if (fuel <= 0) { + xPush = zPush = 0; + } + setHasFuel(fuel > 0); + + if (hasFuel() && random->nextInt(4) == 0) { + level->addParticle(eParticleType_largesmoke, x, y + 0.8, z, 0, 0, 0); + } +} + +void MinecartFurnace::destroy(DamageSource* source) { + Minecart::destroy(source); + + if (!source->isExplosion()) { + spawnAtLocation( + std::shared_ptr(new ItemInstance(Tile::furnace, 1)), + 0); + } +} + +void MinecartFurnace::moveAlongTrack(int xt, int yt, int zt, double maxSpeed, + double slideSpeed, int tile, int data) { + Minecart::moveAlongTrack(xt, yt, zt, maxSpeed, slideSpeed, tile, data); + + double sd = xPush * xPush + zPush * zPush; + if (sd > 0.01 * 0.01 && xd * xd + zd * zd > 0.001) { + sd = Mth::sqrt(sd); + xPush /= sd; + zPush /= sd; + + if (xPush * xd + zPush * zd < 0) { + xPush = 0; + zPush = 0; + } else { + xPush = xd; + zPush = zd; + } + } +} + +void MinecartFurnace::applyNaturalSlowdown() { + double sd = xPush * xPush + zPush * zPush; + + if (sd > 0.01 * 0.01) { + sd = Mth::sqrt(sd); + xPush /= sd; + zPush /= sd; + double speed = 0.05; + xd *= 0.8f; + yd *= 0; + zd *= 0.8f; + xd += xPush * speed; + zd += zPush * speed; + } else { + xd *= 0.98f; + yd *= 0; + zd *= 0.98f; + } + + Minecart::applyNaturalSlowdown(); +} + +bool MinecartFurnace::interact(std::shared_ptr player) { + std::shared_ptr selected = player->inventory->getSelected(); + if (selected != NULL && selected->id == Item::coal_Id) { + if (!player->abilities.instabuild && --selected->count == 0) + player->inventory->setItem(player->inventory->selected, nullptr); + fuel += SharedConstants::TICKS_PER_SECOND * 180; + } + xPush = x - player->x; + zPush = z - player->z; + + return true; +} + +void MinecartFurnace::addAdditonalSaveData(CompoundTag* base) { + Minecart::addAdditonalSaveData(base); + base->putDouble(L"PushX", xPush); + base->putDouble(L"PushZ", zPush); + base->putShort(L"Fuel", (short)fuel); +} + +void MinecartFurnace::readAdditionalSaveData(CompoundTag* base) { + Minecart::readAdditionalSaveData(base); + xPush = base->getDouble(L"PushX"); + zPush = base->getDouble(L"PushZ"); + fuel = base->getShort(L"Fuel"); +} + +bool MinecartFurnace::hasFuel() { + return (entityData->getByte(DATA_ID_FUEL) & 1) != 0; +} + +void MinecartFurnace::setHasFuel(bool fuel) { + if (fuel) { + entityData->set(DATA_ID_FUEL, + (uint8_t)(entityData->getByte(DATA_ID_FUEL) | 1)); + } else { + entityData->set(DATA_ID_FUEL, + (uint8_t)(entityData->getByte(DATA_ID_FUEL) & ~1)); + } +} + +Tile* MinecartFurnace::getDefaultDisplayTile() { return Tile::furnace_lit; } + +int MinecartFurnace::getDefaultDisplayData() { return 2; } \ No newline at end of file diff --git a/Minecraft.World/Entities/MinecartFurnace.h b/Minecraft.World/Entities/MinecartFurnace.h new file mode 100644 index 000000000..25e359bbd --- /dev/null +++ b/Minecraft.World/Entities/MinecartFurnace.h @@ -0,0 +1,51 @@ +#pragma once + +#include "Mobs/Minecart.h" + +class MinecartFurnace : public Minecart { +public: + eINSTANCEOF GetType() { return eTYPE_MINECART_FURNACE; }; + static Entity* create(Level* level) { return new MinecartFurnace(level); } + +private: + static const int DATA_ID_FUEL = 16; + +private: + int fuel; + +public: + double xPush, zPush; + + MinecartFurnace(Level* level); + MinecartFurnace(Level* level, double x, double y, double z); + + // 4J added + virtual int getContainerType(); + + int getType(); + +protected: + void defineSynchedData(); + +public: + void tick(); + void destroy(DamageSource* source); + +protected: + void moveAlongTrack(int xt, int yt, int zt, double maxSpeed, + double slideSpeed, int tile, int data); + void applyNaturalSlowdown(); + +public: + bool interact(std::shared_ptr player); + +protected: + void addAdditonalSaveData(CompoundTag* base); + void readAdditionalSaveData(CompoundTag* base); + bool hasFuel(); + void setHasFuel(bool fuel); + +public: + Tile* getDefaultDisplayTile(); + int getDefaultDisplayData(); +}; \ No newline at end of file diff --git a/Minecraft.World/Entities/MinecartHopper.cpp b/Minecraft.World/Entities/MinecartHopper.cpp new file mode 100644 index 000000000..55b7ed3aa --- /dev/null +++ b/Minecraft.World/Entities/MinecartHopper.cpp @@ -0,0 +1,119 @@ +#include "../Platform/stdafx.h" +#include "../Headers/net.minecraft.world.entity.h" +#include "../Headers/net.minecraft.world.entity.player.h" +#include "../Headers/net.minecraft.world.level.h" +#include "../Headers/net.minecraft.world.level.tile.h" +#include "../Headers/net.minecraft.world.level.tile.entity.h" +#include "../Headers/net.minecraft.world.entity.item.h" +#include "../Headers/net.minecraft.world.phys.h" +#include "MinecartHopper.h" + +const int MinecartHopper::MOVE_ITEM_SPEED = + HopperTileEntity::MOVE_ITEM_SPEED / 2; + +void MinecartHopper::_init() { + enabled = true; + cooldownTime = -1; + + // 4J Stu - This function call had to be moved here from the Entity ctor to + // ensure that the derived version of the function is called + this->defineSynchedData(); +} + +MinecartHopper::MinecartHopper(Level* level) : MinecartContainer(level) { + _init(); +} + +MinecartHopper::MinecartHopper(Level* level, double x, double y, double z) + : MinecartContainer(level, x, y, z) { + _init(); +} + +int MinecartHopper::getType() { return TYPE_HOPPER; } + +Tile* MinecartHopper::getDefaultDisplayTile() { return Tile::hopper; } + +int MinecartHopper::getDefaultDisplayOffset() { return 1; } + +unsigned int MinecartHopper::getContainerSize() { return 5; } + +bool MinecartHopper::interact(std::shared_ptr player) { + if (!level->isClientSide) { + player->openHopper( + std::dynamic_pointer_cast(shared_from_this())); + } + + return true; +} + +void MinecartHopper::activateMinecart(int xt, int yt, int zt, bool state) { + bool newEnabled = !state; + + if (newEnabled != isEnabled()) { + setEnabled(newEnabled); + } +} + +bool MinecartHopper::isEnabled() { return enabled; } + +void MinecartHopper::setEnabled(bool enabled) { this->enabled = enabled; } + +Level* MinecartHopper::getLevel() { return level; } + +double MinecartHopper::getLevelX() { return x; } + +double MinecartHopper::getLevelY() { return y; } + +double MinecartHopper::getLevelZ() { return z; } + +void MinecartHopper::tick() { + MinecartContainer::tick(); + + if (!level->isClientSide && isAlive() && isEnabled()) { + cooldownTime--; + if (!isOnCooldown()) { + setCooldown(0); + + if (suckInItems()) { + setCooldown(MOVE_ITEM_SPEED); + MinecartContainer::setChanged(); + } + } + } +} + +bool MinecartHopper::suckInItems() { + if (HopperTileEntity::suckInItems(this)) return true; + + std::vector >* items = + level->getEntitiesOfClass(typeid(ItemEntity), bb->grow(0.25f, 0, 0.25f), + EntitySelector::ENTITY_STILL_ALIVE); + + if (items->size() > 0) { + HopperTileEntity::addItem( + this, std::dynamic_pointer_cast(items->at(0))); + } + delete items; + + return false; +} + +void MinecartHopper::destroy(DamageSource* source) { + MinecartContainer::destroy(source); + + spawnAtLocation(Tile::hopper_Id, 1, 0); +} + +void MinecartHopper::addAdditonalSaveData(CompoundTag* base) { + MinecartContainer::addAdditonalSaveData(base); + base->putInt(L"TransferCooldown", cooldownTime); +} + +void MinecartHopper::readAdditionalSaveData(CompoundTag* base) { + MinecartContainer::readAdditionalSaveData(base); + cooldownTime = base->getInt(L"TransferCooldown"); +} + +void MinecartHopper::setCooldown(int time) { cooldownTime = time; } + +bool MinecartHopper::isOnCooldown() { return cooldownTime > 0; } \ No newline at end of file diff --git a/Minecraft.World/Entities/MinecartHopper.h b/Minecraft.World/Entities/MinecartHopper.h new file mode 100644 index 000000000..102bb77c8 --- /dev/null +++ b/Minecraft.World/Entities/MinecartHopper.h @@ -0,0 +1,81 @@ +#pragma once + +#include "MinecartContainer.h" +#include "../Blocks/TileEntities/Hopper.h" + +class MinecartHopper : public MinecartContainer, public Hopper { +public: + eINSTANCEOF GetType() { return eTYPE_MINECART_HOPPER; }; + static Entity* create(Level* level) { return new MinecartHopper(level); } + +public: + static const int MOVE_ITEM_SPEED; + +private: + bool enabled; + int cooldownTime; + + void _init(); + +public: + MinecartHopper(Level* level); + MinecartHopper(Level* level, double x, double y, double z); + + virtual int getType(); + virtual Tile* getDefaultDisplayTile(); + virtual int getDefaultDisplayOffset(); + virtual unsigned int getContainerSize(); + virtual bool interact(std::shared_ptr player); + virtual void activateMinecart(int xt, int yt, int zt, bool state); + virtual bool isEnabled(); + virtual void setEnabled(bool enabled); + virtual Level* getLevel(); + virtual double getLevelX(); + virtual double getLevelY(); + virtual double getLevelZ(); + virtual void tick(); + virtual bool suckInItems(); + virtual void destroy(DamageSource* source); + +protected: + virtual void addAdditonalSaveData(CompoundTag* base); + virtual void readAdditionalSaveData(CompoundTag* base); + +public: + void setCooldown(int time); + bool isOnCooldown(); + + // 4J For Hopper + virtual std::shared_ptr getItem(unsigned int slot) { + return MinecartContainer::getItem(slot); + } + virtual std::shared_ptr removeItem(unsigned int slot, + int count) { + return MinecartContainer::removeItem(slot, count); + } + virtual std::shared_ptr removeItemNoUpdate(int slot) { + return MinecartContainer::removeItemNoUpdate(slot); + } + virtual void setItem(unsigned int slot, + std::shared_ptr item) { + MinecartContainer::setItem(slot, item); + } + virtual std::wstring getName() { return MinecartContainer::getName(); } + virtual std::wstring getCustomName() { + return MinecartContainer::getCustomName(); + } + virtual bool hasCustomName() { return MinecartContainer::hasCustomName(); } + virtual int getMaxStackSize() { + return MinecartContainer::getMaxStackSize(); + } + + virtual void setChanged() { MinecartContainer::setChanged(); } + virtual bool stillValid(std::shared_ptr player) { + return MinecartContainer::stillValid(player); + } + virtual void startOpen() { MinecartContainer::startOpen(); } + virtual void stopOpen() { MinecartContainer::stopOpen(); } + virtual bool canPlaceItem(int slot, std::shared_ptr item) { + return MinecartContainer::canPlaceItem(slot, item); + } +}; \ No newline at end of file diff --git a/Minecraft.World/Entities/MinecartRideable.cpp b/Minecraft.World/Entities/MinecartRideable.cpp new file mode 100644 index 000000000..de59c8f4c --- /dev/null +++ b/Minecraft.World/Entities/MinecartRideable.cpp @@ -0,0 +1,32 @@ +#include "../Platform/stdafx.h" +#include "../Headers/net.minecraft.world.entity.h" +#include "../Headers/net.minecraft.world.entity.player.h" +#include "../Headers/net.minecraft.world.level.h" +#include "MinecartRideable.h" + +MinecartRideable::MinecartRideable(Level* level) : Minecart(level) { + // 4J Stu - This function call had to be moved here from the Entity ctor to + // ensure that the derived version of the function is called + this->defineSynchedData(); +} + +MinecartRideable::MinecartRideable(Level* level, double x, double y, double z) + : Minecart(level, x, y, z) { + // 4J Stu - This function call had to be moved here from the Entity ctor to + // ensure that the derived version of the function is called + this->defineSynchedData(); +} + +bool MinecartRideable::interact(std::shared_ptr player) { + if (rider.lock() != NULL && rider.lock()->instanceof(eTYPE_PLAYER) && + rider.lock() != player) + return true; + if (rider.lock() != NULL && rider.lock() != player) return false; + if (!level->isClientSide) { + player->ride(shared_from_this()); + } + + return true; +} + +int MinecartRideable::getType() { return TYPE_RIDEABLE; } \ No newline at end of file diff --git a/Minecraft.World/Entities/MinecartRideable.h b/Minecraft.World/Entities/MinecartRideable.h new file mode 100644 index 000000000..7f2f78a42 --- /dev/null +++ b/Minecraft.World/Entities/MinecartRideable.h @@ -0,0 +1,16 @@ +#pragma once + +#include "Mobs/Minecart.h" + +class MinecartRideable : public Minecart { +public: + eINSTANCEOF GetType() { return eTYPE_MINECART_RIDEABLE; }; + static Entity* create(Level* level) { return new MinecartRideable(level); } + +public: + MinecartRideable(Level* level); + MinecartRideable(Level* level, double x, double y, double z); + + virtual bool interact(std::shared_ptr player); + virtual int getType(); +}; \ No newline at end of file diff --git a/Minecraft.World/Entities/MinecartSpawner.cpp b/Minecraft.World/Entities/MinecartSpawner.cpp new file mode 100644 index 000000000..7b8f4b188 --- /dev/null +++ b/Minecraft.World/Entities/MinecartSpawner.cpp @@ -0,0 +1,74 @@ +#include "../Platform/stdafx.h" +#include "../Headers/net.minecraft.world.level.h" +#include "../Headers/net.minecraft.world.level.tile.h" +#include "MinecartSpawner.h" + +MinecartSpawner::MinecartMobSpawner::MinecartMobSpawner( + MinecartSpawner* parent) { + m_parent = parent; +} + +void MinecartSpawner::MinecartMobSpawner::broadcastEvent(int id) { + m_parent->level->broadcastEntityEvent(m_parent->shared_from_this(), + (uint8_t)id); +} + +Level* MinecartSpawner::MinecartMobSpawner::getLevel() { + return m_parent->level; +} + +int MinecartSpawner::MinecartMobSpawner::getX() { + return Mth::floor(m_parent->x); +} + +int MinecartSpawner::MinecartMobSpawner::getY() { + return Mth::floor(m_parent->y); +} + +int MinecartSpawner::MinecartMobSpawner::getZ() { + return Mth::floor(m_parent->z); +} + +MinecartSpawner::MinecartSpawner(Level* level) : Minecart(level) { + // 4J Stu - This function call had to be moved here from the Entity ctor to + // ensure that the derived version of the function is called + this->defineSynchedData(); + + spawner = new MinecartMobSpawner(this); +} + +MinecartSpawner::MinecartSpawner(Level* level, double x, double y, double z) + : Minecart(level, x, y, z) { + // 4J Stu - This function call had to be moved here from the Entity ctor to + // ensure that the derived version of the function is called + this->defineSynchedData(); + + spawner = new MinecartMobSpawner(this); +} + +MinecartSpawner::~MinecartSpawner() { delete spawner; } + +int MinecartSpawner::getType() { return TYPE_SPAWNER; } + +Tile* MinecartSpawner::getDefaultDisplayTile() { return Tile::mobSpawner; } + +void MinecartSpawner::readAdditionalSaveData(CompoundTag* tag) { + Minecart::readAdditionalSaveData(tag); + spawner->load(tag); +} + +void MinecartSpawner::addAdditonalSaveData(CompoundTag* tag) { + Minecart::addAdditonalSaveData(tag); + spawner->save(tag); +} + +void MinecartSpawner::handleEntityEvent(uint8_t eventId) { + spawner->onEventTriggered(eventId); +} + +void MinecartSpawner::tick() { + Minecart::tick(); + spawner->tick(); +} + +BaseMobSpawner* MinecartSpawner::getSpawner() { return spawner; } \ No newline at end of file diff --git a/Minecraft.World/Entities/MinecartSpawner.h b/Minecraft.World/Entities/MinecartSpawner.h new file mode 100644 index 000000000..34f9a5573 --- /dev/null +++ b/Minecraft.World/Entities/MinecartSpawner.h @@ -0,0 +1,43 @@ +#pragma once + +#include "Mobs/Minecart.h" +#include "../Level/BaseMobSpawner.h" + +class MinecartSpawner : public Minecart { +public: + eINSTANCEOF GetType() { return eTYPE_MINECART_SPAWNER; }; + static Entity* create(Level* level) { return new MinecartSpawner(level); } + +private: + BaseMobSpawner* spawner; + + class MinecartMobSpawner : public BaseMobSpawner { + private: + MinecartSpawner* m_parent; + + public: + MinecartMobSpawner(MinecartSpawner* parent); + void broadcastEvent(int id); + Level* getLevel(); + int getX(); + int getY(); + int getZ(); + }; + +public: + MinecartSpawner(Level* level); + MinecartSpawner(Level* level, double x, double y, double z); + virtual ~MinecartSpawner(); + + virtual int getType(); + virtual Tile* getDefaultDisplayTile(); + +protected: + virtual void readAdditionalSaveData(CompoundTag* tag); + virtual void addAdditonalSaveData(CompoundTag* tag); + +public: + virtual void handleEntityEvent(uint8_t eventId); + virtual void tick(); + virtual BaseMobSpawner* getSpawner(); +}; \ No newline at end of file diff --git a/Minecraft.World/Entities/MinecartTNT.cpp b/Minecraft.World/Entities/MinecartTNT.cpp new file mode 100644 index 000000000..32239b129 --- /dev/null +++ b/Minecraft.World/Entities/MinecartTNT.cpp @@ -0,0 +1,136 @@ +#include "../Platform/stdafx.h" +#include "../Headers/net.minecraft.world.damagesource.h" +#include "../Headers/net.minecraft.world.level.h" +#include "../Headers/net.minecraft.world.level.tile.h" +#include "MinecartTNT.h" + +void MinecartTNT::_init() { + // 4J Stu - This function call had to be moved here from the Entity ctor to + // ensure that the derived version of the function is called + this->defineSynchedData(); + + fuse = -1; +} + +MinecartTNT::MinecartTNT(Level* level) : Minecart(level) { _init(); } + +MinecartTNT::MinecartTNT(Level* level, double x, double y, double z) + : Minecart(level, x, y, z) { + _init(); +} + +int MinecartTNT::getType() { return TYPE_TNT; } + +Tile* MinecartTNT::getDefaultDisplayTile() { return Tile::tnt; } + +void MinecartTNT::tick() { + Minecart::tick(); + + if (fuse > 0) { + fuse--; + level->addParticle(eParticleType_smoke, x, y + 0.5f, z, 0, 0, 0); + } else if (fuse == 0) { + explode(xd * xd + zd * zd); + } + + if (horizontalCollision) { + double speedSqr = xd * xd + zd * zd; + + if (speedSqr >= 0.01f) { + explode(speedSqr); + } + } +} + +void MinecartTNT::destroy(DamageSource* source) { + Minecart::destroy(source); + + double speedSqr = xd * xd + zd * zd; + + if (!source->isExplosion()) { + spawnAtLocation( + std::shared_ptr(new ItemInstance(Tile::tnt, 1)), 0); + } + + if (source->isFire() || source->isExplosion() || speedSqr >= 0.01f) { + explode(speedSqr); + } +} + +void MinecartTNT::explode(double speedSqr) { + if (!level->isClientSide) { + double speed = sqrt(speedSqr); + if (speed > 5) speed = 5; + level->explode(shared_from_this(), x, y, z, + (float)(4 + random->nextDouble() * 1.5f * speed), true); + remove(); + } +} + +void MinecartTNT::causeFallDamage(float distance) { + if (distance >= 3) { + float power = distance / 10; + explode(power * power); + } + + Minecart::causeFallDamage(distance); +} + +void MinecartTNT::activateMinecart(int xt, int yt, int zt, bool state) { + if (state && fuse < 0) { + primeFuse(); + } +} + +void MinecartTNT::handleEntityEvent(uint8_t eventId) { + if (eventId == EVENT_PRIME) { + primeFuse(); + } else { + Minecart::handleEntityEvent(eventId); + } +} + +void MinecartTNT::primeFuse() { + fuse = 80; + + if (!level->isClientSide) { + level->broadcastEntityEvent(shared_from_this(), EVENT_PRIME); + level->playEntitySound(shared_from_this(), eSoundType_RANDOM_FUSE, 1, + 1.0f); + } +} + +int MinecartTNT::getFuse() { return fuse; } + +bool MinecartTNT::isPrimed() { return fuse > -1; } + +float MinecartTNT::getTileExplosionResistance(Explosion* explosion, + Level* level, int x, int y, int z, + Tile* tile) { + if (isPrimed() && (BaseRailTile::isRail(tile->id) || + BaseRailTile::isRail(level, x, y + 1, z))) { + return 0; + } + + return Minecart::getTileExplosionResistance(explosion, level, x, y, z, + tile); +} + +bool MinecartTNT::shouldTileExplode(Explosion* explosion, Level* level, int x, + int y, int z, int id, float power) { + if (isPrimed() && + (BaseRailTile::isRail(id) || BaseRailTile::isRail(level, x, y + 1, z))) + return false; + + return Minecart::shouldTileExplode(explosion, level, x, y, z, id, power); +} + +void MinecartTNT::readAdditionalSaveData(CompoundTag* tag) { + Minecart::readAdditionalSaveData(tag); + if (tag->contains(L"TNTFuse")) fuse = tag->getInt(L"TNTFuse"); +} + +void MinecartTNT::addAdditonalSaveData(CompoundTag* tag) { + Minecart::addAdditonalSaveData(tag); + tag->putInt(L"TNTFuse", fuse); +} \ No newline at end of file diff --git a/Minecraft.World/Entities/MinecartTNT.h b/Minecraft.World/Entities/MinecartTNT.h new file mode 100644 index 000000000..80a51ccbc --- /dev/null +++ b/Minecraft.World/Entities/MinecartTNT.h @@ -0,0 +1,44 @@ +#pragma once + +#include "Mobs/Minecart.h" + +class MinecartTNT : public Minecart { +public: + eINSTANCEOF GetType() { return eTYPE_MINECART_TNT; }; + static Entity* create(Level* level) { return new MinecartTNT(level); } + +private: + static const uint8_t EVENT_PRIME = 10; + + int fuse; + + void _init(); + +public: + MinecartTNT(Level* level); + MinecartTNT(Level* level, double x, double y, double z); + + virtual int getType(); + virtual Tile* getDefaultDisplayTile(); + virtual void tick(); + virtual void destroy(DamageSource* source); + +protected: + virtual void explode(double speedSqr); + virtual void causeFallDamage(float distance); + +public: + virtual void activateMinecart(int xt, int yt, int zt, bool state); + virtual void handleEntityEvent(uint8_t eventId); + virtual void primeFuse(); + virtual int getFuse(); + virtual bool isPrimed(); + virtual float getTileExplosionResistance(Explosion* explosion, Level* level, + int x, int y, int z, Tile* tile); + virtual bool shouldTileExplode(Explosion* explosion, Level* level, int x, + int y, int z, int id, float power); + +protected: + virtual void readAdditionalSaveData(CompoundTag* tag); + virtual void addAdditonalSaveData(CompoundTag* tag); +}; \ No newline at end of file diff --git a/Minecraft.World/Entities/Mob.cpp b/Minecraft.World/Entities/Mob.cpp index c7dc40594..5ad523917 100644 --- a/Minecraft.World/Entities/Mob.cpp +++ b/Minecraft.World/Entities/Mob.cpp @@ -1,8 +1,10 @@ #include "../Platform/stdafx.h" #include "../Util/JavaMath.h" +#include "../Headers/net.minecraft.network.packet.h" #include "../Headers/net.minecraft.world.level.tile.h" #include "../Headers/net.minecraft.world.phys.h" #include "../Headers/net.minecraft.world.entity.h" +#include "../Headers/net.minecraft.world.entity.ai.attributes.h" #include "../Headers/net.minecraft.world.entity.ai.control.h" #include "../Headers/net.minecraft.world.entity.ai.navigation.h" #include "../Headers/net.minecraft.world.entity.ai.sensing.h" @@ -16,6 +18,9 @@ #include "../Headers/net.minecraft.world.effect.h" #include "../Headers/net.minecraft.world.item.alchemy.h" #include "../Headers/net.minecraft.world.item.enchantment.h" +#include "../Headers/net.minecraft.world.h" +#include "../../Minecraft.Client/Level/ServerLevel.h" +#include "../../Minecraft.Client/Player/EntityTracker.h" #include "../Headers/com.mojang.nbt.h" #include "Mob.h" #include "../../Minecraft.Client/Textures/Textures.h" @@ -25,131 +30,59 @@ #include "../Stats/GenericStats.h" #include "ItemEntity.h" -const double Mob::MIN_MOVEMENT_DISTANCE = 0.005; +const float Mob::MAX_WEARING_ARMOR_CHANCE = 0.15f; +const float Mob::MAX_PICKUP_LOOT_CHANCE = 0.55f; +const float Mob::MAX_ENCHANTED_ARMOR_CHANCE = 0.50f; +const float Mob::MAX_ENCHANTED_WEAPON_CHANCE = 0.25f; void Mob::_init() { - invulnerableDuration = 20; - timeOffs = 0.0f; - - yBodyRot = 0; - yBodyRotO = 0; - yHeadRot = 0; - yHeadRotO = 0; - - oRun = 0.0f; - run = 0.0f; - - animStep = 0.0f; - animStepO = 0.0f; - - MemSect(31); - hasHair = true; - textureIdx = TN_MOB_CHAR; // 4J was L"/mob/char.png"; - allowAlpha = true; - rotOffs = 0; - modelName = L""; - bobStrength = 1; - deathScore = 0; - renderOffset = 0; - MemSect(0); - - walkingSpeed = 0.1f; - flyingSpeed = 0.02f; - - oAttackAnim = 0.0f; - attackAnim = 0.0f; - - lastHealth = 0; - dmgSpill = 0; - ambientSoundTime = 0; - - hurtTime = 0; - hurtDuration = 0; - hurtDir = 0; - deathTime = 0; - attackTime = 0; - oTilt = 0; - tilt = 0; - - dead = false; xpReward = 0; - - modelNum = -1; - animSpeed = (float)(Math::random() * 0.9f + 0.1f); - - walkAnimSpeedO = 0.0f; - walkAnimSpeed = 0.0f; - walkAnimPos = 0.0f; - - lastHurtByPlayer = nullptr; - lastHurtByPlayerTime = 0; - lastHurtByMob = nullptr; - lastHurtByMobTime = 0; - lastHurtMob = nullptr; - - arrowCount = 0; - removeArrowTime = 0; - - lSteps = 0; - lx = ly = lz = lyr = lxr = 0.0; - - fallTime = 0.0f; - - lastHurt = 0; - - noActionTime = 0; - xxa = yya = yRotA = 0.0f; - jumping = false; defaultLookAngle = 0.0f; - runSpeed = 0.7f; - noJumpDelay = 0; - lookingAt = nullptr; lookTime = 0; - - effectsDirty = true; - effectColor = 0; - target = nullptr; sensing = NULL; - speed = 0.0f; - restrictCenter = new Pos(0, 0, 0); - restrictRadius = -1.0f; + equipment = ItemInstanceArray(5); + dropChances = floatArray(5); + for (unsigned int i = 0; i < 5; ++i) { + equipment[i] = nullptr; + dropChances[i] = 0.0f; + } + + _canPickUpLoot = false; + persistenceRequired = false; + + _isLeashed = false; + leashHolder = nullptr; + leashInfoTag = NULL; } -Mob::Mob(Level* level) : Entity(level) { +Mob::Mob(Level* level) : LivingEntity(level) { + MemSect(57); _init(); + MemSect(0); - // 4J Stu - This will not call the correct derived function, so moving to - // each derived class - // health = getMaxHealth(); - health = 0; - - blocksBuilding = true; + MemSect(58); + // 4J Stu - We call this again in the derived classes, but need to do it + // here for some internal members + registerAttributes(); + MemSect(0); lookControl = new LookControl(this); moveControl = new MoveControl(this); jumpControl = new JumpControl(this); bodyControl = new BodyControl(this); - navigation = new PathNavigation(this, level, 16); + navigation = new PathNavigation(this, level); sensing = new Sensing(this); - rotA = (float)(Math::random() + 1) * 0.01f; - setPos(x, y, z); - timeOffs = (float)Math::random() * 12398; - yRot = (float)(Math::random() * PI * 2); - yHeadRot = yRot; - - this->footSize = 0.5f; + for (int i = 0; i < 5; i++) { + dropChances[i] = 0.085f; + } } Mob::~Mob() { - for (AUTO_VAR(it, activeEffects.begin()); it != activeEffects.end(); ++it) { - delete it->second; - } - if (lookControl != NULL) delete lookControl; if (moveControl != NULL) delete moveControl; if (jumpControl != NULL) delete jumpControl; @@ -157,7 +90,18 @@ Mob::~Mob() { if (navigation != NULL) delete navigation; if (sensing != NULL) delete sensing; - delete restrictCenter; + if (leashInfoTag != NULL) delete leashInfoTag; + + if (equipment.data != NULL) delete[] equipment.data; + delete[] dropChances.data; +} + +void Mob::registerAttributes() { + LivingEntity::registerAttributes(); + + getAttributes() + ->registerAttribute(SharedMonsterAttributes::FOLLOW_RANGE) + ->setBaseValue(16); } LookControl* Mob::getLookControl() { return lookControl; } @@ -170,39 +114,12 @@ PathNavigation* Mob::getNavigation() { return navigation; } Sensing* Mob::getSensing() { return sensing; } -Random* Mob::getRandom() { return random; } +std::shared_ptr Mob::getTarget() { return target; } -std::shared_ptr Mob::getLastHurtByMob() { return lastHurtByMob; } - -std::shared_ptr Mob::getLastHurtMob() { return lastHurtMob; } - -void Mob::setLastHurtMob(std::shared_ptr target) { - std::shared_ptr mob = std::dynamic_pointer_cast(target); - if (mob != NULL) lastHurtMob = mob; +void Mob::setTarget(std::shared_ptr target) { + this->target = target; } -int Mob::getNoActionTime() { return noActionTime; } - -float Mob::getYHeadRot() { return yHeadRot; } - -void Mob::setYHeadRot(float yHeadRot) { this->yHeadRot = yHeadRot; } - -float Mob::getSpeed() { return speed; } - -void Mob::setSpeed(float speed) { - this->speed = speed; - setYya(speed); -} - -bool Mob::doHurtTarget(std::shared_ptr target) { - setLastHurtMob(target); - return false; -} - -std::shared_ptr Mob::getTarget() { return target; } - -void Mob::setTarget(std::shared_ptr target) { this->target = target; } - bool Mob::canAttackType(eINSTANCEOF targetType) { return !(targetType == eTYPE_CREEPER || targetType == eTYPE_GHAST); } @@ -210,188 +127,49 @@ bool Mob::canAttackType(eINSTANCEOF targetType) { // Called by eatTileGoal void Mob::ate() {} -// might move to navigation, might make area -bool Mob::isWithinRestriction() { - return isWithinRestriction(Mth::floor(x), Mth::floor(y), Mth::floor(z)); -} - -bool Mob::isWithinRestriction(int x, int y, int z) { - if (restrictRadius == -1) return true; - return restrictCenter->distSqr(x, y, z) < restrictRadius * restrictRadius; -} - -void Mob::restrictTo(int x, int y, int z, int radius) { - restrictCenter->set(x, y, z); - restrictRadius = radius; -} - -Pos* Mob::getRestrictCenter() { return restrictCenter; } - -float Mob::getRestrictRadius() { return restrictRadius; } - -void Mob::clearRestriction() { restrictRadius = -1; } - -bool Mob::hasRestriction() { return restrictRadius != -1; } - -void Mob::setLastHurtByMob(std::shared_ptr hurtBy) { - lastHurtByMob = hurtBy; - lastHurtByMobTime = lastHurtByMob != NULL ? PLAYER_HURT_EXPERIENCE_TIME : 0; -} - void Mob::defineSynchedData() { - entityData->define(DATA_EFFECT_COLOR_ID, effectColor); + LivingEntity::defineSynchedData(); + entityData->define(DATA_CUSTOM_NAME_VISIBLE, (uint8_t)0); + entityData->define(DATA_CUSTOM_NAME, L""); } -bool Mob::canSee(std::shared_ptr target) { - HitResult* hres = level->clip( - Vec3::newTemp(x, y + getHeadHeight(), z), - Vec3::newTemp(target->x, target->y + target->getHeadHeight(), - target->z)); - bool retVal = (hres == NULL); - delete hres; - return retVal; -} - -int Mob::getTexture() { return textureIdx; } - -bool Mob::isPickable() { return !removed; } - -bool Mob::isPushable() { return !removed; } - -float Mob::getHeadHeight() { return bbHeight * 0.85f; } - int Mob::getAmbientSoundInterval() { return 20 * 4; } void Mob::playAmbientSound() { MemSect(31); int ambient = getAmbientSound(); if (ambient != -1) { - level->playSound(shared_from_this(), ambient, getSoundVolume(), - getVoicePitch()); + playSound(ambient, getSoundVolume(), getVoicePitch()); } MemSect(0); } void Mob::baseTick() { - oAttackAnim = attackAnim; - Entity::baseTick(); + LivingEntity::baseTick(); if (isAlive() && random->nextInt(1000) < ambientSoundTime++) { ambientSoundTime = -getAmbientSoundInterval(); playAmbientSound(); } - - if (isAlive() && isInWall()) { - hurt(DamageSource::inWall, 1); - } - - if (isFireImmune() || level->isClientSide) clearFire(); - - if (isAlive() && isUnderLiquid(Material::water) && !isWaterMob() && - activeEffects.find(MobEffect::waterBreathing->id) == - activeEffects.end()) { - setAirSupply(decreaseAirSupply(getAirSupply())); - if (getAirSupply() == -20) { - setAirSupply(0); - if (canCreateParticles()) { - for (int i = 0; i < 8; i++) { - float xo = random->nextFloat() - random->nextFloat(); - float yo = random->nextFloat() - random->nextFloat(); - float zo = random->nextFloat() - random->nextFloat(); - level->addParticle(eParticleType_bubble, x + xo, y + yo, - z + zo, xd, yd, zd); - } - } - hurt(DamageSource::drown, 2); - } - - clearFire(); - } else { - setAirSupply(TOTAL_AIR_SUPPLY); - } - - oTilt = tilt; - - if (attackTime > 0) attackTime--; - if (hurtTime > 0) hurtTime--; - if (invulnerableTime > 0) invulnerableTime--; - if (health <= 0) { - tickDeath(); - } - - if (lastHurtByPlayerTime > 0) - lastHurtByPlayerTime--; - else { - // Note - this used to just set to nullptr, but that has to create a new - // std::shared_ptr and free an old one, when generally this won't be - // doing anything at all. This is the lightweight but ugly alternative - if (lastHurtByPlayer) { - lastHurtByPlayer.reset(); - } - } - if (lastHurtMob != NULL && !lastHurtMob->isAlive()) lastHurtMob = nullptr; - - if (lastHurtByMob != NULL) { - if (!lastHurtByMob->isAlive()) - setLastHurtByMob(nullptr); - else if (lastHurtByMobTime > 0) - lastHurtByMobTime--; - else - setLastHurtByMob(nullptr); - } - - // update effects - tickEffects(); - - animStepO = animStep; - - yBodyRotO = yBodyRot; - yHeadRotO = yHeadRot; - yRotO = yRot; - xRotO = xRot; } -void Mob::tickDeath() { - deathTime++; - if (deathTime == 20) { - // 4J Stu - Added level->isClientSide check from 1.2 to fix XP orbs - // being created client side - if (!level->isClientSide && - (lastHurtByPlayerTime > 0 || isAlwaysExperienceDropper())) { - if (!isBaby()) { - int xpCount = this->getExperienceReward(lastHurtByPlayer); - while (xpCount > 0) { - int newCount = ExperienceOrb::getExperienceValue(xpCount); - xpCount -= newCount; - level->addEntity(std::shared_ptr( - new ExperienceOrb(level, x, y, z, newCount))); - } - } - } - - remove(); - for (int i = 0; i < 20; i++) { - double xa = random->nextGaussian() * 0.02; - double ya = random->nextGaussian() * 0.02; - double za = random->nextGaussian() * 0.02; - level->addParticle(eParticleType_explode, - x + random->nextFloat() * bbWidth * 2 - bbWidth, - y + random->nextFloat() * bbHeight, - z + random->nextFloat() * bbWidth * 2 - bbWidth, - xa, ya, za); - } - } -} - -int Mob::decreaseAirSupply(int currentSupply) { return currentSupply - 1; } - int Mob::getExperienceReward(std::shared_ptr killedBy) { - return xpReward; + if (xpReward > 0) { + int result = xpReward; + + ItemInstanceArray slots = getEquipmentSlots(); + for (int i = 0; i < slots.length; i++) { + if (slots[i] != NULL && dropChances[i] <= 1) { + result += 1 + random->nextInt(3); + } + } + + return result; + } else { + return xpReward; + } } - -bool Mob::isAlwaysExperienceDropper() { return false; } - void Mob::spawnAnim() { for (int i = 0; i < 20; i++) { double xa = random->nextGaussian() * 0.02; @@ -407,347 +185,26 @@ void Mob::spawnAnim() { } } -void Mob::rideTick() { - Entity::rideTick(); - oRun = run; - run = 0; - fallDistance = 0; -} - -void Mob::lerpTo(double x, double y, double z, float yRot, float xRot, - int steps) { - heightOffset = 0; - lx = x; - ly = y; - lz = z; - lyr = yRot; - lxr = xRot; - - lSteps = steps; -} - -void Mob::superTick() { Entity::tick(); } - void Mob::tick() { - Entity::tick(); + LivingEntity::tick(); - if (arrowCount > 0) { - if (removeArrowTime <= 0) { - removeArrowTime = 20 * 3; - } - removeArrowTime--; - if (removeArrowTime <= 0) { - arrowCount--; - } + if (!level->isClientSide) { + tickLeash(); } +} - aiStep(); - - double xd = x - xo; - double zd = z - zo; - - float sideDist = xd * xd + zd * zd; - - float yBodyRotT = yBodyRot; - - float walkSpeed = 0; - oRun = run; - float tRun = 0; - if (sideDist <= 0.05f * 0.05f) { - // animStep = 0; - } else { - tRun = 1; - walkSpeed = sqrt(sideDist) * 3; - yBodyRotT = ((float)atan2(zd, xd) * 180 / (float)PI - 90); - } - if (attackAnim > 0) { - yBodyRotT = yRot; - } - if (!onGround) { - tRun = 0; - } - run = run + (tRun - run) * 0.3f; - - /* - * float yBodyRotD = yRot-yBodyRot; while (yBodyRotD < -180) yBodyRotD - * += 360; while (yBodyRotD >= 180) yBodyRotD -= 360; yBodyRot += - * yBodyRotD * 0.1f; - */ - +float Mob::tickHeadTurn(float yBodyRotT, float walkSpeed) { if (useNewAi()) { bodyControl->clientTick(); + return walkSpeed; } else { - float yBodyRotD = Mth::wrapDegrees(yBodyRotT - yBodyRot); - yBodyRot += yBodyRotD * 0.3f; - - float headDiff = Mth::wrapDegrees(yRot - yBodyRot); - bool behind = headDiff < -90 || headDiff >= 90; - if (headDiff < -75) headDiff = -75; - if (headDiff >= 75) headDiff = +75; - yBodyRot = yRot - headDiff; - if (headDiff * headDiff > 50 * 50) { - yBodyRot += headDiff * 0.2f; - } - - if (behind) { - walkSpeed *= -1; - } - } - while (yRot - yRotO < -180) yRotO -= 360; - while (yRot - yRotO >= 180) yRotO += 360; - - while (yBodyRot - yBodyRotO < -180) yBodyRotO -= 360; - while (yBodyRot - yBodyRotO >= 180) yBodyRotO += 360; - - while (xRot - xRotO < -180) xRotO -= 360; - while (xRot - xRotO >= 180) xRotO += 360; - - while (yHeadRot - yHeadRotO < -180) yHeadRotO -= 360; - while (yHeadRot - yHeadRotO >= 180) yHeadRotO += 360; - - animStep += walkSpeed; -} - -void Mob::heal(int heal) { - if (health <= 0) return; - health += heal; - if (health > getMaxHealth()) health = getMaxHealth(); - invulnerableTime = invulnerableDuration / 2; -} - -int Mob::getHealth() { return health; } - -void Mob::setHealth(int health) { - this->health = health; - if (health > getMaxHealth()) { - health = getMaxHealth(); + return LivingEntity::tickHeadTurn(yBodyRotT, walkSpeed); } } -bool Mob::hurt(DamageSource* source, int dmg) { - // 4J Stu - Reworked this function a bit to show hurt damage on the client - // before the server responds. Fix for #8823 - Gameplay: Confirmation that a - // monster or animal has taken damage from an attack is highly delayed 4J - // Stu - Change to the fix to only show damage when attacked, rather than - // collision damage Fix for #10299 - When in corners, passive mobs may show - // that they are taking damage. 4J Stu - Change to the fix for TU6, as - // source is never NULL due to changes in 1.8.2 to what source actually is - if (level->isClientSide && - dynamic_cast(source) == NULL) - return false; - noActionTime = 0; - if (health <= 0) return false; - - if (source->isFire() && hasEffect(MobEffect::fireResistance)) { - // 4J-JEV, for new achievement Stayin'Frosty, TODO merge with Java - // version. - std::shared_ptr plr = - std::dynamic_pointer_cast(shared_from_this()); - if (plr != NULL && - source == - DamageSource::lava) // Only award when in lava (not any fire). - { - plr->awardStat(GenericStats::stayinFrosty(), - GenericStats::param_stayinFrosty()); - } - return false; - } - - this->walkAnimSpeed = 1.5f; - - bool sound = true; - if (invulnerableTime > invulnerableDuration / 2.0f) { - if (dmg <= lastHurt) return false; - if (!level->isClientSide) actuallyHurt(source, dmg - lastHurt); - lastHurt = dmg; - sound = false; - } else { - lastHurt = dmg; - lastHealth = health; - invulnerableTime = invulnerableDuration; - if (!level->isClientSide) actuallyHurt(source, dmg); - hurtTime = hurtDuration = 10; - } - - hurtDir = 0; - - std::shared_ptr sourceEntity = source->getEntity(); - if (sourceEntity != NULL) { - if (std::dynamic_pointer_cast(sourceEntity) != NULL) { - setLastHurtByMob(std::dynamic_pointer_cast(sourceEntity)); - } - if (std::dynamic_pointer_cast(sourceEntity) != NULL) { - lastHurtByPlayerTime = PLAYER_HURT_EXPERIENCE_TIME; - lastHurtByPlayer = std::dynamic_pointer_cast(sourceEntity); - } else if (std::dynamic_pointer_cast(sourceEntity)) { - std::shared_ptr w = - std::dynamic_pointer_cast(sourceEntity); - if (w->isTame()) { - lastHurtByPlayerTime = PLAYER_HURT_EXPERIENCE_TIME; - lastHurtByPlayer = nullptr; - } - } - } - - if (sound && level->isClientSide) { - return false; - } - - if (sound) { - level->broadcastEntityEvent(shared_from_this(), EntityEvent::HURT); - if (source != DamageSource::drown && - source != DamageSource::controlledExplosion) - markHurt(); - if (sourceEntity != NULL) { - double xd = sourceEntity->x - x; - double zd = sourceEntity->z - z; - while (xd * xd + zd * zd < 0.0001) { - xd = (Math::random() - Math::random()) * 0.01; - zd = (Math::random() - Math::random()) * 0.01; - } - hurtDir = (float)(atan2(zd, xd) * 180 / PI) - yRot; - knockback(sourceEntity, dmg, xd, zd); - } else { - hurtDir = (float)(int)((Math::random() * 2) * - 180); // 4J This cast is the same as Java - } - } - - MemSect(31); - if (health <= 0) { - if (sound) - level->playSound(shared_from_this(), getDeathSound(), - getSoundVolume(), getVoicePitch()); - die(source); - } else { - if (sound) - level->playSound(shared_from_this(), getHurtSound(), - getSoundVolume(), getVoicePitch()); - } - MemSect(0); - - return true; -} - -float Mob::getVoicePitch() { - if (isBaby()) { - return (random->nextFloat() - random->nextFloat()) * 0.2f + 1.5f; - } - return (random->nextFloat() - random->nextFloat()) * 0.2f + 1.0f; -} - -void Mob::animateHurt() { - hurtTime = hurtDuration = 10; - hurtDir = 0; -} - -int Mob::getArmorValue() { return 0; } - -void Mob::hurtArmor(int damage) {} - -int Mob::getDamageAfterArmorAbsorb(DamageSource* damageSource, int damage) { - if (!damageSource->isBypassArmor()) { - int absorb = 25 - getArmorValue(); - int v = (damage)*absorb + dmgSpill; - hurtArmor(damage); - damage = v / 25; - dmgSpill = v % 25; - } - return damage; -} - -int Mob::getDamageAfterMagicAbsorb(DamageSource* damageSource, int damage) { - if (hasEffect(MobEffect::damageResistance)) { - int absorbValue = - (getEffect(MobEffect::damageResistance)->getAmplifier() + 1) * 5; - int absorb = 25 - absorbValue; - int v = (damage)*absorb + dmgSpill; - damage = v / 25; - dmgSpill = v % 25; - } - return damage; -} - -void Mob::actuallyHurt(DamageSource* source, int dmg) { - dmg = getDamageAfterArmorAbsorb(source, dmg); - dmg = getDamageAfterMagicAbsorb(source, dmg); - health -= dmg; -} - -float Mob::getSoundVolume() { return 1; } - int Mob::getAmbientSound() { return -1; } -int Mob::getHurtSound() { return eSoundType_DAMAGE_HURT; } - -int Mob::getDeathSound() { return eSoundType_DAMAGE_HURT; } - -void Mob::knockback(std::shared_ptr source, int dmg, double xd, - double zd) { - hasImpulse = true; - float dd = (float)sqrt(xd * xd + zd * zd); - float pow = 0.4f; - - this->xd /= 2; - this->yd /= 2; - this->zd /= 2; - - this->xd -= xd / dd * pow; - this->yd += pow; - this->zd -= zd / dd * pow; - - if (this->yd > 0.4f) this->yd = 0.4f; -} - -void Mob::die(DamageSource* source) { - std::shared_ptr sourceEntity = source->getEntity(); - if (deathScore >= 0 && sourceEntity != NULL) - sourceEntity->awardKillScore(shared_from_this(), deathScore); - - if (sourceEntity != NULL) - sourceEntity->killed( - std::dynamic_pointer_cast(shared_from_this())); - - dead = true; - - if (!level->isClientSide) { - int playerBonus = 0; - std::shared_ptr player = - std::dynamic_pointer_cast(sourceEntity); - if (player != NULL) { - playerBonus = - EnchantmentHelper::getKillingLootBonus(player->inventory); - } - if (!isBaby()) { - dropDeathLoot(lastHurtByPlayerTime > 0, playerBonus); - if (lastHurtByPlayerTime > 0) { - int rareLoot = random->nextInt(200) - playerBonus; - if (rareLoot < 5) { - dropRareDeathLoot((rareLoot <= 0) ? 1 : 0); - } - } - } - - // 4J-JEV, hook for Durango mobKill event. - if (player != NULL) { - player->awardStat( - GenericStats::killMob(), - GenericStats::param_mobKill( - player, std::dynamic_pointer_cast(shared_from_this()), - source)); - } - } - - level->broadcastEntityEvent(shared_from_this(), EntityEvent::DEATH); -} - -/** - * Drop extra rare loot. Only occurs roughly 5% of the time, rareRootLevel - * is set to 1 (otherwise 0) 1% of the time. - * - * @param rareLootLevel - */ -void Mob::dropRareDeathLoot(int rareLootLevel) {} +int Mob::getDeathLoot() { return 0; } void Mob::dropDeathLoot(bool wasKilledByPlayer, int playerBonusLevel) { int loot = getDeathLoot(); @@ -760,372 +217,173 @@ void Mob::dropDeathLoot(bool wasKilledByPlayer, int playerBonusLevel) { } } -int Mob::getDeathLoot() { return 0; } - -void Mob::causeFallDamage(float distance) { - Entity::causeFallDamage(distance); - int dmg = (int)ceil(distance - 3); - if (dmg > 0) { - // 4J - new sounds here brought forward from 1.2.3 - if (dmg > 4) { - level->playSound(shared_from_this(), eSoundType_DAMAGE_FALL_BIG, 1, - 1); - } else { - level->playSound(shared_from_this(), eSoundType_DAMAGE_FALL_SMALL, - 1, 1); - } - hurt(DamageSource::fall, dmg); - - int t = level->getTile(Mth::floor(x), - Mth::floor(y - 0.2f - this->heightOffset), - Mth::floor(z)); - if (t > 0) { - const Tile::SoundType* soundType = Tile::tiles[t]->soundType; - MemSect(31); - level->playSound(shared_from_this(), soundType->getStepSound(), - soundType->getVolume() * 0.5f, - soundType->getPitch() * 0.75f); - MemSect(0); - } - } -} - -void Mob::travel(float xa, float ya) { -#ifdef __PSVITA__ - // AP - std::dynamic_pointer_cast is a non-trivial call - Player* thisPlayer = NULL; - if ((GetType() & eTYPE_PLAYER) == eTYPE_PLAYER) { - thisPlayer = (Player*)this; - } -#else - std::shared_ptr thisPlayer = - std::dynamic_pointer_cast(shared_from_this()); -#endif - if (isInWater() && !(thisPlayer && thisPlayer->abilities.flying)) { - double yo = y; - moveRelative(xa, ya, useNewAi() ? 0.04f : 0.02f); - move(xd, yd, zd); - - xd *= 0.80f; - yd *= 0.80f; - zd *= 0.80f; - yd -= 0.02; - - if (horizontalCollision && isFree(xd, yd + 0.6f - y + yo, zd)) { - yd = 0.3f; - } - } else if (isInLava() && !(thisPlayer && thisPlayer->abilities.flying)) { - double yo = y; - moveRelative(xa, ya, 0.02f); - move(xd, yd, zd); - xd *= 0.50f; - yd *= 0.50f; - zd *= 0.50f; - yd -= 0.02; - - if (horizontalCollision && isFree(xd, yd + 0.6f - y + yo, zd)) { - yd = 0.3f; - } - } else { - float friction = 0.91f; - if (onGround) { - friction = 0.6f * 0.91f; - int t = level->getTile(Mth::floor(x), Mth::floor(bb->y0) - 1, - Mth::floor(z)); - if (t > 0) { - friction = Tile::tiles[t]->friction * 0.91f; - } - } - - float friction2 = (0.6f * 0.6f * 0.91f * 0.91f * 0.6f * 0.91f) / - (friction * friction * friction); - - float speed; - if (onGround) { - if (useNewAi()) - speed = getSpeed(); - else - speed = walkingSpeed; - speed *= friction2; - } else - speed = flyingSpeed; - - moveRelative(xa, ya, speed); - - friction = 0.91f; - if (onGround) { - friction = 0.6f * 0.91f; - int t = level->getTile(Mth::floor(x), Mth::floor(bb->y0) - 1, - Mth::floor(z)); - if (t > 0) { - friction = Tile::tiles[t]->friction * 0.91f; - } - } - if (onLadder()) { - float max = 0.15f; - if (xd < -max) xd = -max; - if (xd > max) xd = max; - if (zd < -max) zd = -max; - if (zd > max) zd = max; - this->fallDistance = 0; - if (yd < -0.15) yd = -0.15; - bool playerSneaking = - isSneaking() && - std::dynamic_pointer_cast(shared_from_this()) != NULL; - if (playerSneaking && yd < 0) yd = 0; - } - - move(xd, yd, zd); - - if (horizontalCollision && onLadder()) { - yd = 0.2; - } - - yd -= 0.08; - yd *= 0.98f; - xd *= friction; - zd *= friction; - } - - walkAnimSpeedO = walkAnimSpeed; - double xxd = x - xo; - double zzd = z - zo; - float wst = Mth::sqrt(xxd * xxd + zzd * zzd) * 4; - if (wst > 1) wst = 1; - walkAnimSpeed += (wst - walkAnimSpeed) * 0.4f; - walkAnimPos += walkAnimSpeed; -} - -bool Mob::onLadder() { - int xt = Mth::floor(x); - int yt = Mth::floor(bb->y0); - int zt = Mth::floor(z); - - // 4J-PB - TU9 - add climbable vines - int iTile = level->getTile(xt, yt, zt); - return (iTile == Tile::ladder_Id) || (iTile == Tile::vine_Id); -} - -bool Mob::isShootable() { return true; } - void Mob::addAdditonalSaveData(CompoundTag* entityTag) { - entityTag->putShort(L"Health", (short)health); - entityTag->putShort(L"HurtTime", (short)hurtTime); - entityTag->putShort(L"DeathTime", (short)deathTime); - entityTag->putShort(L"AttackTime", (short)attackTime); + LivingEntity::addAdditonalSaveData(entityTag); + entityTag->putBoolean(L"CanPickUpLoot", canPickUpLoot()); + entityTag->putBoolean(L"PersistenceRequired", persistenceRequired); - if (!activeEffects.empty()) { - ListTag* listTag = new ListTag(); + ListTag* gear = new ListTag(); + for (int i = 0; i < equipment.length; i++) { + CompoundTag* tag = new CompoundTag(); + if (equipment[i] != NULL) equipment[i]->save(tag); + gear->add(tag); + } + entityTag->put(L"Equipment", gear); - for (AUTO_VAR(it, activeEffects.begin()); it != activeEffects.end(); - ++it) { - MobEffectInstance* effect = it->second; + ListTag* dropChanceList = new ListTag(); + for (int i = 0; i < dropChances.length; i++) { + dropChanceList->add(new FloatTag(_toString(i), dropChances[i])); + } + entityTag->put(L"DropChances", dropChanceList); + entityTag->putString(L"CustomName", getCustomName()); + entityTag->putBoolean(L"CustomNameVisible", isCustomNameVisible()); - CompoundTag* tag = new CompoundTag(); - tag->putByte(L"Id", static_cast(effect->getId())); - tag->putByte(L"Amplifier", (char)effect->getAmplifier()); - tag->putInt(L"Duration", effect->getDuration()); - listTag->add(tag); + // leash info + entityTag->putBoolean(L"Leashed", _isLeashed); + if (leashHolder != NULL) { + CompoundTag* leashTag = new CompoundTag(L"Leash"); + if (leashHolder->instanceof(eTYPE_LIVINGENTITY)) { + // a walking, talking, leash holder + leashTag->putString(L"UUID", leashHolder->getUUID()); + } else if (leashHolder->instanceof(eTYPE_HANGING_ENTITY)) { + // a fixed holder (that doesn't save itself) + std::shared_ptr hangInThere = + std::dynamic_pointer_cast(leashHolder); + leashTag->putInt(L"X", hangInThere->xTile); + leashTag->putInt(L"Y", hangInThere->yTile); + leashTag->putInt(L"Z", hangInThere->zTile); } - entityTag->put(L"ActiveEffects", listTag); + entityTag->put(L"Leash", leashTag); } } void Mob::readAdditionalSaveData(CompoundTag* tag) { - if (health < Short::MIN_VALUE) health = Short::MIN_VALUE; - health = tag->getShort(L"Health"); - if (!tag->contains(L"Health")) health = getMaxHealth(); - hurtTime = tag->getShort(L"HurtTime"); - deathTime = tag->getShort(L"DeathTime"); - attackTime = tag->getShort(L"AttackTime"); + LivingEntity::readAdditionalSaveData(tag); - if (tag->contains(L"ActiveEffects")) { - ListTag* effects = - (ListTag*)tag->getList(L"ActiveEffects"); - for (int i = 0; i < effects->size(); i++) { - CompoundTag* effectTag = effects->get(i); - int id = effectTag->getByte(L"Id"); - int amplifier = effectTag->getByte(L"Amplifier"); - int duration = effectTag->getInt(L"Duration"); + setCanPickUpLoot(tag->getBoolean(L"CanPickUpLoot")); + persistenceRequired = tag->getBoolean(L"PersistenceRequired"); + if (tag->contains(L"CustomName") && + tag->getString(L"CustomName").length() > 0) + setCustomName(tag->getString(L"CustomName")); + setCustomNameVisible(tag->getBoolean(L"CustomNameVisible")); - activeEffects.insert( - std::unordered_map::value_type( - id, new MobEffectInstance(id, duration, amplifier))); + if (tag->contains(L"Equipment")) { + ListTag* gear = + (ListTag*)tag->getList(L"Equipment"); + + for (int i = 0; i < equipment.length; i++) { + equipment[i] = ItemInstance::fromTag(gear->get(i)); } } -} -bool Mob::isAlive() { return !removed && health > 0; } + if (tag->contains(L"DropChances")) { + ListTag* items = + (ListTag*)tag->getList(L"DropChances"); + for (int i = 0; i < items->size(); i++) { + dropChances[i] = items->get(i)->data; + } + } -bool Mob::isWaterMob() { return false; } - -// 4J - added for more accurate lighting of mobs. Takes a weighted average of -// all tiles touched by the bounding volume of the entity - the method in the -// Entity class (which used to be used for mobs too) simply gets a single tile's -// lighting value causing sudden changes of lighting values when entities go in -// and out of lit areas, for example when bobbing in the water. -int Mob::getLightColor(float a) { - float accum[2] = {0, 0}; - float totVol = (bb->x1 - bb->x0) * (bb->y1 - bb->y0) * (bb->z1 - bb->z0); - int xmin = Mth::floor(bb->x0); - int xmax = Mth::floor(bb->x1); - int ymin = Mth::floor(bb->y0); - int ymax = Mth::floor(bb->y1); - int zmin = Mth::floor(bb->z0); - int zmax = Mth::floor(bb->z1); - for (int xt = xmin; xt <= xmax; xt++) - for (int yt = ymin; yt <= ymax; yt++) - for (int zt = zmin; zt <= zmax; zt++) { - float tilexmin = (float)xt; - float tilexmax = (float)(xt + 1); - float tileymin = (float)yt; - float tileymax = (float)(yt + 1); - float tilezmin = (float)zt; - float tilezmax = (float)(zt + 1); - if (tilexmin < bb->x0) tilexmin = bb->x0; - if (tilexmax > bb->x1) tilexmax = bb->x1; - if (tileymin < bb->y0) tileymin = bb->y0; - if (tileymax > bb->y1) tileymax = bb->y1; - if (tilezmin < bb->z0) tilezmin = bb->z0; - if (tilezmax > bb->z1) tilezmax = bb->z1; - float tileVol = (tilexmax - tilexmin) * (tileymax - tileymin) * - (tilezmax - tilezmin); - float frac = tileVol / totVol; - int lc = level->getLightColor(xt, yt, zt, 0); - accum[0] += frac * (float)(lc & 0xffff); - accum[1] += frac * (float)(lc >> 16); - } - - if (accum[0] > 240.0f) accum[0] = 240.0f; - if (accum[1] > 240.0f) accum[1] = 240.0f; - - return (((int)accum[1]) << 16) | ((int)accum[0]); + _isLeashed = tag->getBoolean(L"Leashed"); + if (_isLeashed && tag->contains(L"Leash")) { + leashInfoTag = (CompoundTag*)tag->getCompound(L"Leash")->copy(); + } } void Mob::setYya(float yya) { this->yya = yya; } -void Mob::setJumping(bool jump) { jumping = jump; } +void Mob::setSpeed(float speed) { + LivingEntity::setSpeed(speed); + setYya(speed); +} void Mob::aiStep() { - if (noJumpDelay > 0) noJumpDelay--; - if (lSteps > 0) { - double xt = x + (lx - x) / lSteps; - double yt = y + (ly - y) / lSteps; - double zt = z + (lz - z) / lSteps; + LivingEntity::aiStep(); - double yrd = Mth::wrapDegrees(lyr - yRot); - double xrd = Mth::wrapDegrees(lxr - xRot); + if (!level->isClientSide && canPickUpLoot() && !dead && + level->getGameRules()->getBoolean(GameRules::RULE_MOBGRIEFING)) { + std::vector >* entities = + level->getEntitiesOfClass(typeid(ItemEntity), bb->grow(1, 0, 1)); + for (AUTO_VAR(it, entities->begin()); it != entities->end(); ++it) { + std::shared_ptr entity = + std::dynamic_pointer_cast(*it); + if (entity->removed || entity->getItem() == NULL) continue; + std::shared_ptr item = entity->getItem(); + int slot = getEquipmentSlotForItem(item); - yRot += (float)((yrd) / lSteps); - xRot += (float)((xrd) / lSteps); + if (slot > -1) { + bool replace = true; + std::shared_ptr current = getCarried(slot); - lSteps--; - this->setPos(xt, yt, zt); - this->setRot(yRot, xRot); + if (current != NULL) { + if (slot == SLOT_WEAPON) { + WeaponItem* newWeapon = + dynamic_cast(item->getItem()); + WeaponItem* oldWeapon = + dynamic_cast(current->getItem()); + if (newWeapon != NULL && oldWeapon == NULL) { + replace = true; + } else if (newWeapon != NULL && oldWeapon != NULL) { + if (newWeapon->getTierDamage() == + oldWeapon->getTierDamage()) { + replace = item->getAuxValue() > + current->getAuxValue() || + item->hasTag() && !current->hasTag(); + } else { + replace = newWeapon->getTierDamage() > + oldWeapon->getTierDamage(); + } + } else { + replace = false; + } + } else { + ArmorItem* newArmor = + dynamic_cast(item->getItem()); + ArmorItem* oldArmor = + dynamic_cast(current->getItem()); + if (newArmor != NULL && oldArmor == NULL) { + replace = true; + } else if (newArmor != NULL && oldArmor != NULL) { + if (newArmor->defense == oldArmor->defense) { + replace = item->getAuxValue() > + current->getAuxValue() || + item->hasTag() && !current->hasTag(); + } else { + replace = newArmor->defense > oldArmor->defense; + } + } else { + replace = false; + } + } + } - // 4J - this collision is carried out to try and stop the lerping push - // the mob through the floor, in which case gravity can then carry on - // moving the mob because the collision just won't work anymore. BB for - // collision used to be calculated as: bb->shrink(1 / 32.0, 0, 1 / 32.0) - // now using a reduced BB to try and get rid of some issues where mobs - // pop up the sides of walls, undersides of trees etc. - AABB* shrinkbb = bb->shrink(0.1, 0, 0.1); - shrinkbb->y1 = shrinkbb->y0 + 0.1; - AABBList* collisions = level->getCubes(shared_from_this(), shrinkbb); - if (collisions->size() > 0) { - double yTop = 0; - AUTO_VAR(itEnd, collisions->end()); - for (AUTO_VAR(it, collisions->begin()); it != itEnd; it++) { - AABB* ab = *it; // collisions->at(i); - if (ab->y1 > yTop) yTop = ab->y1; - } + if (replace) { + if (current != NULL && + random->nextFloat() - 0.1f < dropChances[slot]) { + spawnAtLocation(current, 0); + } - yt += yTop - bb->y0; - setPos(xt, yt, zt); - } - if (abs(xd) < MIN_MOVEMENT_DISTANCE) xd = 0; - if (abs(yd) < MIN_MOVEMENT_DISTANCE) yd = 0; - if (abs(zd) < MIN_MOVEMENT_DISTANCE) zd = 0; - } - - if (isImmobile()) { - jumping = false; - xxa = 0; - yya = 0; - yRotA = 0; - } else { - MemSect(25); - if (isEffectiveAI()) { - if (useNewAi()) { - newServerAiStep(); - } else { - serverAiStep(); - yHeadRot = yRot; - } - } - MemSect(0); - } - - if (jumping) { - if (isInWater() || isInLava()) { - yd += 0.04f; - } else if (onGround) { - if (noJumpDelay == 0) { - jumpFromGround(); - noJumpDelay = 10; - } - } - } else { - noJumpDelay = 0; - } - - xxa *= 0.98f; - yya *= 0.98f; - yRotA *= 0.9f; - - float normalSpeed = walkingSpeed; - walkingSpeed *= getWalkingSpeedModifier(); - travel(xxa, yya); - walkingSpeed = normalSpeed; - - if (!level->isClientSide) { - std::vector >* entities = level->getEntities( - shared_from_this(), this->bb->grow(0.2f, 0, 0.2f)); - if (entities != NULL && !entities->empty()) { - AUTO_VAR(itEnd, entities->end()); - for (AUTO_VAR(it, entities->begin()); it != itEnd; it++) { - std::shared_ptr e = *it; // entities->at(i); - if (e->isPushable()) e->push(shared_from_this()); + setEquippedSlot(slot, item); + dropChances[slot] = 2; + persistenceRequired = true; + take(entity, 1); + entity->remove(); + } } } + delete entities; } } bool Mob::useNewAi() { return false; } -bool Mob::isEffectiveAI() { return !level->isClientSide; } - -bool Mob::isImmobile() { return health <= 0; } - -bool Mob::isBlocking() { return false; } - -void Mob::jumpFromGround() { - yd = 0.42f; - if (hasEffect(MobEffect::jump)) { - yd += (getEffect(MobEffect::jump)->getAmplifier() + 1) * .1f; - } - if (isSprinting()) { - float rr = yRot * Mth::RAD_TO_GRAD; - - xd -= Mth::sin(rr) * 0.2f; - zd += Mth::cos(rr) * 0.2f; - } - this->hasImpulse = true; -} - bool Mob::removeWhenFarAway() { return true; } void Mob::checkDespawn() { + if (persistenceRequired) { + noActionTime = 0; + return; + } std::shared_ptr player = level->getNearestPlayer(shared_from_this(), -1); if (player != NULL) { @@ -1148,35 +406,55 @@ void Mob::checkDespawn() { } void Mob::newServerAiStep() { + PIXBeginNamedEvent(0, "Tick target selector for %d", GetType()); MemSect(51); noActionTime++; + PIXBeginNamedEvent(0, "Check despawn"); checkDespawn(); + PIXEndNamedEvent(); + PIXBeginNamedEvent(0, "Tick sensing"); sensing->tick(); + PIXEndNamedEvent(); + PIXBeginNamedEvent(0, "Tick target selector"); targetSelector.tick(); + PIXEndNamedEvent(); + PIXBeginNamedEvent(0, "Tick goal selectors"); goalSelector.tick(); + PIXEndNamedEvent(); + PIXBeginNamedEvent(0, "Tick navigation"); navigation->tick(); + PIXEndNamedEvent(); + PIXBeginNamedEvent(0, "Tick server ai mob step"); serverAiMobStep(); + PIXEndNamedEvent(); + PIXBeginNamedEvent(0, "Tick move"); moveControl->tick(); + PIXEndNamedEvent(); + PIXBeginNamedEvent(0, "Tick look"); lookControl->tick(); + PIXEndNamedEvent(); + PIXBeginNamedEvent(0, "Tick jump"); jumpControl->tick(); + PIXEndNamedEvent(); // Consider this for extra strolling if it is protected against despawning. // We aren't interested in ones that aren't protected as the whole point of // this extra wandering is to potentially transition from protected to not // protected. + PIXBeginNamedEvent(0, "Consider extra wandering"); considerForExtraWandering(isDespawnProtected()); + PIXEndNamedEvent(); MemSect(0); + PIXEndNamedEvent(); } -void Mob::serverAiMobStep() {} - void Mob::serverAiStep() { - noActionTime++; - - checkDespawn(); + LivingEntity::serverAiStep(); xxa = 0; yya = 0; + checkDespawn(); + float lookDistance = 8; if (random->nextFloat() < 0.02f) { std::shared_ptr player = @@ -1216,9 +494,10 @@ void Mob::lookAt(std::shared_ptr e, float yMax, float xMax) { double yd; double zd = e->z - z; - std::shared_ptr mob = std::dynamic_pointer_cast(e); - if (mob != NULL) { - yd = (y + getHeadHeight()) - (mob->y + mob->getHeadHeight()); + if (e->instanceof(eTYPE_LIVINGENTITY)) { + std::shared_ptr mob = + std::dynamic_pointer_cast(e); + yd = (mob->y + mob->getHeadHeight()) - (y + getHeadHeight()); } else { yd = (e->bb->y0 + e->bb->y1) / 2 - (y + getHeadHeight()); } @@ -1227,7 +506,7 @@ void Mob::lookAt(std::shared_ptr e, float yMax, float xMax) { float yRotD = (float)(atan2(zd, xd) * 180 / PI) - 90; float xRotD = (float)-(atan2(yd, sd) * 180 / PI); - xRot = -rotlerp(xRot, xRotD, xMax); + xRot = rotlerp(xRot, xRotD, xMax); yRot = rotlerp(yRot, yRotD, yMax); } @@ -1253,107 +532,356 @@ bool Mob::canSpawn() { !level->containsAnyLiquid_NoLoad(bb); } -void Mob::outOfWorld() { hurt(DamageSource::outOfWorld, 4); } - -float Mob::getAttackAnim(float a) { - float diff = attackAnim - oAttackAnim; - if (diff < 0) diff += 1; - return oAttackAnim + diff * a; -} - -Vec3* Mob::getPos(float a) { - if (a == 1) { - return Vec3::newTemp(x, y, z); - } - double x = xo + (this->x - xo) * a; - double y = yo + (this->y - yo) * a; - double z = zo + (this->z - zo) * a; - - return Vec3::newTemp(x, y, z); -} - -Vec3* Mob::getLookAngle() { return getViewVector(1); } - -Vec3* Mob::getViewVector(float a) { - if (a == 1) { - float yCos = Mth::cos(-yRot * Mth::RAD_TO_GRAD - PI); - float ySin = Mth::sin(-yRot * Mth::RAD_TO_GRAD - PI); - float xCos = -Mth::cos(-xRot * Mth::RAD_TO_GRAD); - float xSin = Mth::sin(-xRot * Mth::RAD_TO_GRAD); - - return Vec3::newTemp(ySin * xCos, xSin, yCos * xCos); - } - float xRot = xRotO + (this->xRot - xRotO) * a; - float yRot = yRotO + (this->yRot - yRotO) * a; - - float yCos = Mth::cos(-yRot * Mth::RAD_TO_GRAD - PI); - float ySin = Mth::sin(-yRot * Mth::RAD_TO_GRAD - PI); - float xCos = -Mth::cos(-xRot * Mth::RAD_TO_GRAD); - float xSin = Mth::sin(-xRot * Mth::RAD_TO_GRAD); - - return Vec3::newTemp(ySin * xCos, xSin, yCos * xCos); -} - float Mob::getSizeScale() { return 1.0f; } float Mob::getHeadSizeScale() { return 1.0f; } -HitResult* Mob::pick(double range, float a) { - Vec3* from = getPos(a); - Vec3* b = getViewVector(a); - Vec3* to = from->add(b->x * range, b->y * range, b->z * range); - return level->clip(from, to); -} - int Mob::getMaxSpawnClusterSize() { return 4; } -std::shared_ptr Mob::getCarriedItem() { return nullptr; } - -std::shared_ptr Mob::getArmor(int pos) { - // 4J Stu - Not implemented yet - return nullptr; - // return equipment[pos + 1]; +int Mob::getMaxFallDistance() { + if (getTarget() == NULL) return 3; + int sacrifice = (int)(getHealth() - (getMaxHealth() * 0.33f)); + sacrifice -= (3 - level->difficulty) * 4; + if (sacrifice < 0) sacrifice = 0; + return sacrifice + 3; } -void Mob::handleEntityEvent(uint8_t id) { - if (id == EntityEvent::HURT) { - this->walkAnimSpeed = 1.5f; +std::shared_ptr Mob::getCarriedItem() { + return equipment[SLOT_WEAPON]; +} - invulnerableTime = invulnerableDuration; - hurtTime = hurtDuration = 10; - hurtDir = 0; +std::shared_ptr Mob::getCarried(int slot) { + return equipment[slot]; +} - MemSect(31); - // 4J-PB -added because villagers have no sounds - int iHurtSound = getHurtSound(); - if (iHurtSound != -1) { - level->playSound( - shared_from_this(), iHurtSound, getSoundVolume(), - (random->nextFloat() - random->nextFloat()) * 0.2f + 1.0f); +std::shared_ptr Mob::getArmor(int pos) { + return equipment[pos + 1]; +} + +void Mob::setEquippedSlot(int slot, std::shared_ptr item) { + equipment[slot] = item; +} + +ItemInstanceArray Mob::getEquipmentSlots() { return equipment; } + +void Mob::dropEquipment(bool byPlayer, int playerBonusLevel) { + for (int slot = 0; slot < getEquipmentSlots().length; slot++) { + std::shared_ptr item = getCarried(slot); + bool preserve = dropChances[slot] > 1; + + if (item != NULL && (byPlayer || preserve) && + random->nextFloat() - playerBonusLevel * 0.01f < + dropChances[slot]) { + if (!preserve && item->isDamageableItem()) { + int _max = std::max(item->getMaxDamage() - 25, 1); + int damage = item->getMaxDamage() - + random->nextInt(random->nextInt(_max) + 1); + if (damage > _max) damage = _max; + if (damage < 1) damage = 1; + item->setAuxValue(damage); + } + spawnAtLocation(item, 0); } - MemSect(0); - hurt(DamageSource::genericSource, 0); - } else if (id == EntityEvent::DEATH) { - MemSect(31); - // 4J-PB -added because villagers have no sounds - int iDeathSound = getDeathSound(); - if (iDeathSound != -1) { - level->playSound( - shared_from_this(), iDeathSound, getSoundVolume(), - (random->nextFloat() - random->nextFloat()) * 0.2f + 1.0f); - } - MemSect(0); - health = 0; - die(DamageSource::genericSource); - } else { - Entity::handleEntityEvent(id); } } -bool Mob::isSleeping() { return false; } +void Mob::populateDefaultEquipmentSlots() { + if (random->nextFloat() < + MAX_WEARING_ARMOR_CHANCE * level->getDifficulty(x, y, z)) { + int armorType = random->nextInt(2); + float partialChance = + level->difficulty == Difficulty::HARD ? 0.1f : 0.25f; + if (random->nextFloat() < 0.095f) armorType++; + if (random->nextFloat() < 0.095f) armorType++; + if (random->nextFloat() < 0.095f) armorType++; -Icon* Mob::getItemInHandIcon(std::shared_ptr item, int layer) { - return item->getIcon(); + for (int i = 3; i >= 0; i--) { + std::shared_ptr item = getArmor(i); + if (i < 3 && random->nextFloat() < partialChance) break; + if (item == NULL) { + Item* equip = getEquipmentForSlot(i + 1, armorType); + if (equip != NULL) + setEquippedSlot(i + 1, std::shared_ptr( + new ItemInstance(equip))); + } + } + } +} + +int Mob::getEquipmentSlotForItem(std::shared_ptr item) { + if (item->id == Tile::pumpkin_Id || item->id == Item::skull_Id) { + return SLOT_HELM; + } + + ArmorItem* armorItem = dynamic_cast(item->getItem()); + if (armorItem != NULL) { + switch (armorItem->slot) { + case ArmorItem::SLOT_FEET: + return SLOT_BOOTS; + case ArmorItem::SLOT_LEGS: + return SLOT_LEGGINGS; + case ArmorItem::SLOT_TORSO: + return SLOT_CHEST; + case ArmorItem::SLOT_HEAD: + return SLOT_HELM; + } + } + + return SLOT_WEAPON; +} + +Item* Mob::getEquipmentForSlot(int slot, int type) { + switch (slot) { + case SLOT_HELM: + if (type == 0) return Item::helmet_leather; + if (type == 1) return Item::helmet_gold; + if (type == 2) return Item::helmet_chain; + if (type == 3) return Item::helmet_iron; + if (type == 4) return Item::helmet_diamond; + case SLOT_CHEST: + if (type == 0) return Item::chestplate_leather; + if (type == 1) return Item::chestplate_gold; + if (type == 2) return Item::chestplate_chain; + if (type == 3) return Item::chestplate_iron; + if (type == 4) return Item::chestplate_diamond; + case SLOT_LEGGINGS: + if (type == 0) return Item::leggings_leather; + if (type == 1) return Item::leggings_gold; + if (type == 2) return Item::leggings_chain; + if (type == 3) return Item::leggings_iron; + if (type == 4) return Item::leggings_diamond; + case SLOT_BOOTS: + if (type == 0) return Item::boots_leather; + if (type == 1) return Item::boots_gold; + if (type == 2) return Item::boots_chain; + if (type == 3) return Item::boots_iron; + if (type == 4) return Item::boots_diamond; + } + + return NULL; +} + +void Mob::populateDefaultEquipmentEnchantments() { + float difficulty = level->getDifficulty(x, y, z); + + if (getCarriedItem() != NULL && + random->nextFloat() < MAX_ENCHANTED_WEAPON_CHANCE * difficulty) { + EnchantmentHelper::enchantItem( + random, getCarriedItem(), + (int)(5 + difficulty * random->nextInt(18))); + } + + for (int i = 0; i < 4; i++) { + std::shared_ptr item = getArmor(i); + if (item != NULL && + random->nextFloat() < MAX_ENCHANTED_ARMOR_CHANCE * difficulty) { + EnchantmentHelper::enchantItem( + random, item, (int)(5 + difficulty * random->nextInt(18))); + } + } +} + +/** + * Added this method so mobs can handle their own spawn settings instead of + * hacking MobSpawner.java + * + * @param groupData + * TODO + * @return TODO + */ +MobGroupData* Mob::finalizeMobSpawn( + MobGroupData* groupData, int extraData /*= 0*/) // 4J Added extraData param +{ + // 4J Stu - Take this out, it's not great and nobody will notice. Also not + // great for performance. + // getAttribute(SharedMonsterAttributes::FOLLOW_RANGE)->addModifier(new + // AttributeModifier(random->nextGaussian() * 0.05, + // AttributeModifier::OPERATION_MULTIPLY_BASE)); + + return groupData; +} + +void Mob::finalizeSpawnEggSpawn(int extraData) {} + +bool Mob::canBeControlledByRider() { return false; } + +std::wstring Mob::getAName() { + if (hasCustomName()) return getCustomName(); + return LivingEntity::getAName(); +} + +void Mob::setPersistenceRequired() { persistenceRequired = true; } + +void Mob::setCustomName(const std::wstring& name) { + entityData->set(DATA_CUSTOM_NAME, name); +} + +std::wstring Mob::getCustomName() { + return entityData->getString(DATA_CUSTOM_NAME); +} + +bool Mob::hasCustomName() { + return entityData->getString(DATA_CUSTOM_NAME).length() > 0; +} + +void Mob::setCustomNameVisible(bool visible) { + entityData->set(DATA_CUSTOM_NAME_VISIBLE, + visible ? (uint8_t)1 : (uint8_t)0); +} + +bool Mob::isCustomNameVisible() { + return entityData->getByte(DATA_CUSTOM_NAME_VISIBLE) == 1; +} + +bool Mob::shouldShowName() { return isCustomNameVisible(); } + +void Mob::setDropChance(int slot, float pct) { dropChances[slot] = pct; } + +bool Mob::canPickUpLoot() { return _canPickUpLoot; } + +void Mob::setCanPickUpLoot(bool canPickUpLoot) { + _canPickUpLoot = canPickUpLoot; +} + +bool Mob::isPersistenceRequired() { return persistenceRequired; } + +bool Mob::interact(std::shared_ptr player) { + if (isLeashed() && getLeashHolder() == player) { + dropLeash(true, !player->abilities.instabuild); + return true; + } + + std::shared_ptr itemstack = player->inventory->getSelected(); + if (itemstack != NULL) { + // it's inconvenient to have the leash code here, but it's because + // the mob.interact(player) method has priority over + // item.interact(mob) + if (itemstack->id == Item::lead_Id) { + if (canBeLeashed()) { + std::shared_ptr tamableAnimal = nullptr; + if (shared_from_this()->instanceof(eTYPE_TAMABLE_ANIMAL) && + (tamableAnimal = std::dynamic_pointer_cast( + shared_from_this())) + ->isTame()) // 4J-JEV: excuse the assignment operator + // in here, don't want to dyn-cast if it's + // avoidable. + { + if (player->getUUID().compare( + tamableAnimal->getOwnerUUID()) == 0) { + setLeashedTo(player, true); + itemstack->count--; + return true; + } + } else { + setLeashedTo(player, true); + itemstack->count--; + return true; + } + } + } + } + + if (mobInteract(player)) { + return true; + } + + return LivingEntity::interact(player); +} + +bool Mob::mobInteract(std::shared_ptr player) { return false; } + +void Mob::tickLeash() { + if (leashInfoTag != NULL) { + restoreLeashFromSave(); + } + if (!_isLeashed) { + return; + } + + if (leashHolder == NULL || leashHolder->removed) { + dropLeash(true, true); + return; + } +} + +void Mob::dropLeash(bool synch, bool createItemDrop) { + if (_isLeashed) { + _isLeashed = false; + leashHolder = nullptr; + if (!level->isClientSide && createItemDrop) { + spawnAtLocation(Item::lead_Id, 1); + } + + ServerLevel* serverLevel = dynamic_cast(level); + if (!level->isClientSide && synch && serverLevel != NULL) { + serverLevel->getTracker()->broadcast( + shared_from_this(), + std::shared_ptr(new SetEntityLinkPacket( + SetEntityLinkPacket::LEASH, shared_from_this(), nullptr))); + } + } +} + +bool Mob::canBeLeashed() { + return !isLeashed() && !shared_from_this()->instanceof(eTYPE_ENEMY); +} + +bool Mob::isLeashed() { return _isLeashed; } + +std::shared_ptr Mob::getLeashHolder() { return leashHolder; } + +void Mob::setLeashedTo(std::shared_ptr holder, bool synch) { + _isLeashed = true; + leashHolder = holder; + + ServerLevel* serverLevel = dynamic_cast(level); + if (!level->isClientSide && synch && serverLevel) { + serverLevel->getTracker()->broadcast( + shared_from_this(), + std::shared_ptr(new SetEntityLinkPacket( + SetEntityLinkPacket::LEASH, shared_from_this(), leashHolder))); + } +} + +void Mob::restoreLeashFromSave() { + // after being added to the world, attempt to recreate leash bond + if (_isLeashed && leashInfoTag != NULL) { + if (leashInfoTag->contains(L"UUID")) { + std::wstring leashUuid = leashInfoTag->getString(L"UUID"); + std::vector >* livingEnts = + level->getEntitiesOfClass(typeid(LivingEntity), + bb->grow(10, 10, 10)); + for (AUTO_VAR(it, livingEnts->begin()); it != livingEnts->end(); + ++it) { + std::shared_ptr le = + std::dynamic_pointer_cast(*it); + if (le->getUUID().compare(leashUuid) == 0) { + leashHolder = le; + setLeashedTo(leashHolder, true); + break; + } + } + delete livingEnts; + } else if (leashInfoTag->contains(L"X") && + leashInfoTag->contains(L"Y") && + leashInfoTag->contains(L"Z")) { + int x = leashInfoTag->getInt(L"X"); + int y = leashInfoTag->getInt(L"Y"); + int z = leashInfoTag->getInt(L"Z"); + + std::shared_ptr activeKnot = + LeashFenceKnotEntity::findKnotAt(level, x, y, z); + if (activeKnot == NULL) { + activeKnot = + LeashFenceKnotEntity::createAndAddKnot(level, x, y, z); + } + leashHolder = activeKnot; + setLeashedTo(leashHolder, true); + } else { + dropLeash(false, true); + } + } + leashInfoTag = NULL; } // 4J added so we can not render mobs before their chunks are loaded - to @@ -1368,242 +896,9 @@ bool Mob::shouldRender(Vec3* c) { return Entity::shouldRender(c); } -void Mob::tickEffects() { - bool removed = false; - for (AUTO_VAR(it, activeEffects.begin()); it != activeEffects.end();) { - MobEffectInstance* effect = it->second; - removed = false; - if (!effect->tick(std::dynamic_pointer_cast(shared_from_this()))) { - if (!level->isClientSide) { - it = activeEffects.erase(it); - onEffectRemoved(effect); - delete effect; - removed = true; - } - } - if (!removed) { - ++it; - } - } - if (effectsDirty) { - if (!level->isClientSide) { - if (activeEffects.empty()) { - entityData->set(DATA_EFFECT_COLOR_ID, (int)0); - setInvisible(false); - setWeakened(false); - } else { - std::vector values; - for (AUTO_VAR(it, activeEffects.begin()); - it != activeEffects.end(); ++it) { - values.push_back(it->second); - } - int colorValue = PotionBrewing::getColorValue(&values); - values.clear(); - entityData->set(DATA_EFFECT_COLOR_ID, colorValue); - setInvisible(hasEffect(MobEffect::invisibility->id)); - setWeakened(hasEffect(MobEffect::weakness->id)); - } - } - effectsDirty = false; - } - if (random->nextBoolean()) { - int colorValue = entityData->getInteger(DATA_EFFECT_COLOR_ID); - if (colorValue > 0) { - double red = (double)((colorValue >> 16) & 0xff) / 255.0; - double green = (double)((colorValue >> 8) & 0xff) / 255.0; - double blue = (double)((colorValue >> 0) & 0xff) / 255.0; - - level->addParticle( - eParticleType_mobSpell, - x + (random->nextDouble() - 0.5) * bbWidth, - y + random->nextDouble() * bbHeight - heightOffset, - z + (random->nextDouble() - 0.5) * bbWidth, red, green, blue); - } - } -} - -void Mob::removeAllEffects() { - // Iterator effectIdIterator = activeEffects.keySet().iterator(); - // while (effectIdIterator.hasNext()) - for (AUTO_VAR(it, activeEffects.begin()); it != activeEffects.end();) { - // Integer effectId = effectIdIterator.next(); - MobEffectInstance* effect = it->second; // activeEffects.get(effectId); - - if (!level->isClientSide) { - // effectIdIterator.remove(); - it = activeEffects.erase(it); - onEffectRemoved(effect); - delete effect; - } else { - ++it; - } - } -} - -std::vector* Mob::getActiveEffects() { - std::vector* active = - new std::vector(); - - for (AUTO_VAR(it, activeEffects.begin()); it != activeEffects.end(); ++it) { - active->push_back(it->second); - } - - return active; -} - -bool Mob::hasEffect(int id) { - return activeEffects.find(id) != activeEffects.end(); - ; -} - -bool Mob::hasEffect(MobEffect* effect) { - return activeEffects.find(effect->id) != activeEffects.end(); -} - -MobEffectInstance* Mob::getEffect(MobEffect* effect) { - MobEffectInstance* effectInst = NULL; - - AUTO_VAR(it, activeEffects.find(effect->id)); - if (it != activeEffects.end()) effectInst = it->second; - - return effectInst; -} - -void Mob::addEffect(MobEffectInstance* newEffect) { - if (!canBeAffected(newEffect)) { - return; - } - - if (activeEffects.find(newEffect->getId()) != activeEffects.end()) { - // replace effect and update - MobEffectInstance* effectInst = - activeEffects.find(newEffect->getId())->second; - effectInst->update(newEffect); - onEffectUpdated(effectInst); - } else { - activeEffects.insert( - std::unordered_map::value_type( - newEffect->getId(), newEffect)); - onEffectAdded(newEffect); - } -} - -// 4J Added -void Mob::addEffectNoUpdate(MobEffectInstance* newEffect) { - if (!canBeAffected(newEffect)) { - return; - } - - if (activeEffects.find(newEffect->getId()) != activeEffects.end()) { - // replace effect and update - MobEffectInstance* effectInst = - activeEffects.find(newEffect->getId())->second; - effectInst->update(newEffect); - } else { - activeEffects.insert( - std::unordered_map::value_type( - newEffect->getId(), newEffect)); - } -} - -bool Mob::canBeAffected(MobEffectInstance* newEffect) { - if (getMobType() == UNDEAD) { - int id = newEffect->getId(); - if (id == MobEffect::regeneration->id || id == MobEffect::poison->id) { - return false; - } - } - - return true; -} - -bool Mob::isInvertedHealAndHarm() { return getMobType() == UNDEAD; } - -void Mob::removeEffectNoUpdate(int effectId) { - AUTO_VAR(it, activeEffects.find(effectId)); - if (it != activeEffects.end()) { - MobEffectInstance* effect = it->second; - if (effect != NULL) { - delete effect; - } - activeEffects.erase(it); - } -} - -void Mob::removeEffect(int effectId) { - AUTO_VAR(it, activeEffects.find(effectId)); - if (it != activeEffects.end()) { - MobEffectInstance* effect = it->second; - if (effect != NULL) { - onEffectRemoved(effect); - delete effect; - } - activeEffects.erase(it); - } -} - -void Mob::onEffectAdded(MobEffectInstance* effect) { effectsDirty = true; } - -void Mob::onEffectUpdated(MobEffectInstance* effect) { effectsDirty = true; } - -void Mob::onEffectRemoved(MobEffectInstance* effect) { effectsDirty = true; } - -float Mob::getWalkingSpeedModifier() { - float speed = 1.0f; - if (hasEffect(MobEffect::movementSpeed)) { - speed *= - 1.0f + - .2f * (getEffect(MobEffect::movementSpeed)->getAmplifier() + 1); - } - if (hasEffect(MobEffect::movementSlowdown)) { - speed *= - 1.0f - - .15f * (getEffect(MobEffect::movementSlowdown)->getAmplifier() + 1); - } - return speed; -} - -void Mob::teleportTo(double x, double y, double z) { - moveTo(x, y, z, yRot, xRot); -} - -bool Mob::isBaby() { return false; } - -MobType Mob::getMobType() { return UNDEFINED; } - -void Mob::breakItem(std::shared_ptr itemInstance) { - level->playSound(shared_from_this(), eSoundType_RANDOM_BREAK, 0.8f, - 0.8f + level->random->nextFloat() * 0.4f); - - for (int i = 0; i < 5; i++) { - Vec3* d = Vec3::newTemp((random->nextFloat() - 0.5) * 0.1, - Math::random() * 0.1 + 0.1, 0); - d->xRot(-xRot * PI / 180); - d->yRot(-yRot * PI / 180); - - Vec3* p = Vec3::newTemp((random->nextFloat() - 0.5) * 0.3, - -random->nextFloat() * 0.6 - 0.3, 0.6); - p->xRot(-xRot * PI / 180); - p->yRot(-yRot * PI / 180); - p = p->add(x, y + getHeadHeight(), z); - level->addParticle(PARTICLE_ICONCRACK(itemInstance->getItem()->id, 0), - p->x, p->y, p->z, d->x, d->y + 0.05, d->z); - } -} - -bool Mob::isInvulnerable() { - // 4J-JEV: I have no idea what was going on here (it gets changed in a later - // java version). - return invulnerableTime > 0; // invulnerableTime <= invulnerableTime / 2; -} - void Mob::setLevel(Level* level) { Entity::setLevel(level); navigation->setLevel(level); goalSelector.setLevel(level); targetSelector.setLevel(level); -} - -void Mob::finalizeMobSpawn() {} - -bool Mob::canBeControlledByRider() { return false; } +} \ No newline at end of file diff --git a/Minecraft.World/Entities/Mob.h b/Minecraft.World/Entities/Mob.h index de1f12ef1..1b64d6a18 100644 --- a/Minecraft.World/Entities/Mob.h +++ b/Minecraft.World/Entities/Mob.h @@ -1,6 +1,6 @@ #pragma once -#include "Entity.h" +#include "LivingEntity.h" #include "MobType.h" #include "../AI/Goals/GoalSelector.h" @@ -18,106 +18,33 @@ class PathNavigation; class Sensing; class Icon; class Pos; +class MobGroupData; -class Mob : public Entity { +class Mob : public LivingEntity { friend class MobSpawner; -protected: - // 4J - added for common ctor code - void _init(); - public: - Mob(Level* level); - virtual ~Mob(); - // 4J-PB - added to replace (e instanceof Type), avoiding dynamic casts eINSTANCEOF GetType() { return eTYPE_MOB; } static Entity* create(Level* level) { return NULL; } public: - static const int ATTACK_DURATION = 5; - static const int PLAYER_HURT_EXPERIENCE_TIME = 20 * 3; - -public: // 4J Stu - Made public - static const int DATA_EFFECT_COLOR_ID = 8; + static const float MAX_WEARING_ARMOR_CHANCE; + static const float MAX_PICKUP_LOOT_CHANCE; + static const float MAX_ENCHANTED_ARMOR_CHANCE; + static const float MAX_ENCHANTED_WEAPON_CHANCE; private: - static const double MIN_MOVEMENT_DISTANCE; - -public: - int invulnerableDuration; - float timeOffs; - float rotA; - float yBodyRot, yBodyRotO; - float yHeadRot, yHeadRotO; - -protected: - float oRun, run; - float animStep, animStepO; - bool hasHair; - // std::wstring textureName; - int textureIdx; // 4J changed from std::wstring textureName - bool allowAlpha; - float rotOffs; - std::wstring modelName; - float bobStrength; - int deathScore; - float renderOffset; - -public: - float walkingSpeed; - float flyingSpeed; - float oAttackAnim, attackAnim; - -protected: - int health; - -public: - int lastHealth; - -protected: - int dmgSpill; + static const int DATA_CUSTOM_NAME = 10; + static const int DATA_CUSTOM_NAME_VISIBLE = 11; public: int ambientSoundTime; - int hurtTime; - int hurtDuration; - float hurtDir; - int deathTime; - int attackTime; - float oTilt, tilt; protected: - bool dead; int xpReward; -public: - int modelNum; - float animSpeed; - float walkAnimSpeedO; - float walkAnimSpeed; - float walkAnimPos; - -protected: - std::shared_ptr lastHurtByPlayer; - int lastHurtByPlayerTime; - private: - std::shared_ptr lastHurtByMob; - int lastHurtByMobTime; - std::shared_ptr lastHurtMob; - -public: - int arrowCount; - int removeArrowTime; - -protected: - std::map activeEffects; - -private: - bool effectsDirty; - int effectColor; - LookControl* lookControl; MoveControl* moveControl; JumpControl* jumpControl; @@ -129,12 +56,28 @@ protected: GoalSelector targetSelector; private: - std::shared_ptr target; + std::shared_ptr target; Sensing* sensing; - float speed; - Pos* restrictCenter; - float restrictRadius; + ItemInstanceArray equipment; + +protected: + floatArray dropChances; + +private: + bool _canPickUpLoot; + bool persistenceRequired; + +protected: + // 4J - added for common ctor code + void _init(); + +public: + Mob(Level* level); + virtual ~Mob(); + +protected: + void registerAttributes(); public: virtual LookControl* getLookControl(); @@ -142,152 +85,46 @@ public: virtual JumpControl* getJumpControl(); virtual PathNavigation* getNavigation(); virtual Sensing* getSensing(); - virtual Random* getRandom(); - virtual std::shared_ptr getLastHurtByMob(); - virtual std::shared_ptr getLastHurtMob(); - void setLastHurtMob(std::shared_ptr target); - virtual int getNoActionTime(); - float getYHeadRot(); - void setYHeadRot(float yHeadRot); - float getSpeed(); - void setSpeed(float speed); - virtual bool doHurtTarget(std::shared_ptr target); - std::shared_ptr getTarget(); - virtual void setTarget(std::shared_ptr target); + std::shared_ptr getTarget(); + virtual void setTarget(std::shared_ptr target); virtual bool canAttackType(eINSTANCEOF targetType); virtual void ate(); - bool isWithinRestriction(); - bool isWithinRestriction(int x, int y, int z); - void restrictTo(int x, int y, int z, int radius); - Pos* getRestrictCenter(); - float getRestrictRadius(); - void clearRestriction(); - bool hasRestriction(); - - virtual void setLastHurtByMob(std::shared_ptr hurtBy); - protected: virtual void defineSynchedData(); public: - bool canSee(std::shared_ptr target); - virtual int getTexture(); // 4J - changed from std::wstring to int - virtual bool isPickable(); - virtual bool isPushable(); - virtual float getHeadHeight(); virtual int getAmbientSoundInterval(); void playAmbientSound(); virtual void baseTick(); protected: - virtual void tickDeath(); - virtual int decreaseAirSupply(int currentSupply); virtual int getExperienceReward(std::shared_ptr killedBy); - virtual bool isAlwaysExperienceDropper(); public: - void spawnAnim(); - virtual void rideTick(); - -protected: - int lSteps; - double lx, ly, lz, lyr, lxr; - -public: - virtual void lerpTo(double x, double y, double z, float yRot, float xRot, - int steps); - -private: - float fallTime; - -public: - void superTick(); + virtual void spawnAnim(); virtual void tick(); - virtual void heal(int heal); - virtual int getMaxHealth() = 0; - virtual int getHealth(); - virtual void setHealth(int health); protected: - int lastHurt; - -public: - virtual bool hurt(DamageSource* source, int dmg); - -protected: - float getVoicePitch(); - -public: - virtual void animateHurt(); - - /** - * Fetches the mob's armor value, from 0 (no armor) to 20 (full armor) - * - * @return - */ - virtual int getArmorValue(); - -protected: - virtual void hurtArmor(int damage); - virtual int getDamageAfterArmorAbsorb(DamageSource* damageSource, - int damage); - virtual int getDamageAfterMagicAbsorb(DamageSource* damageSource, - int damage); - - virtual void actuallyHurt(DamageSource* source, int dmg); - virtual float getSoundVolume(); + virtual float tickHeadTurn(float yBodyRotT, float walkSpeed); virtual int getAmbientSound(); - virtual int getHurtSound(); - virtual int getDeathSound(); - -public: - void knockback(std::shared_ptr source, int dmg, double xd, - double zd); - virtual void die(DamageSource* source); - -protected: - virtual void dropRareDeathLoot(int rareLootLevel); - virtual void dropDeathLoot(bool wasKilledByPlayer, int playerBonusLevel); virtual int getDeathLoot(); - virtual void causeFallDamage(float distance); + virtual void dropDeathLoot(bool wasKilledByPlayer, int playerBonusLevel); public: - virtual void travel(float xa, float ya); - virtual bool onLadder(); - virtual bool isShootable(); virtual void addAdditonalSaveData(CompoundTag* entityTag); virtual void readAdditionalSaveData(CompoundTag* tag); - virtual bool isAlive(); - virtual bool isWaterMob(); - virtual int getLightColor(float a); // 4J - added protected: - int noActionTime; - float xxa, yya, yRotA; - bool jumping; float defaultLookAngle; - float runSpeed; - -protected: - int noJumpDelay; public: virtual void setYya(float yya); - virtual void setJumping(bool jump); - + virtual void setSpeed(float speed); virtual void aiStep(); protected: virtual bool useNewAi(); - virtual bool isEffectiveAI(); - virtual bool isImmobile(); - -public: - virtual bool isBlocking(); - -protected: - virtual void jumpFromGround(); virtual bool removeWhenFarAway(); private: @@ -298,7 +135,6 @@ protected: virtual void checkDespawn(); virtual void newServerAiStep(); - virtual void serverAiMobStep(); virtual void serverAiStep(); public: @@ -314,62 +150,80 @@ private: public: virtual bool canSpawn(); - -protected: - virtual void outOfWorld(); - -public: - float getAttackAnim(float a); - virtual Vec3* getPos(float a); - virtual Vec3* getLookAngle(); - Vec3* getViewVector(float a); virtual float getSizeScale(); virtual float getHeadSizeScale(); - HitResult* pick(double range, float a); virtual int getMaxSpawnClusterSize(); + virtual int getMaxFallDistance(); virtual std::shared_ptr getCarriedItem(); + virtual std::shared_ptr getCarried(int slot); virtual std::shared_ptr getArmor(int pos); - virtual void handleEntityEvent(uint8_t id); - virtual bool isSleeping(); - virtual Icon* getItemInHandIcon(std::shared_ptr item, - int layer); + virtual void setEquippedSlot(int slot, std::shared_ptr item); + virtual ItemInstanceArray getEquipmentSlots(); + +protected: + virtual void dropEquipment(bool byPlayer, int playerBonusLevel); + virtual void populateDefaultEquipmentSlots(); + +public: + static int getEquipmentSlotForItem(std::shared_ptr item); + static Item* getEquipmentForSlot(int slot, int type); + +protected: + virtual void populateDefaultEquipmentEnchantments(); + +public: + /** + * Added this method so mobs can handle their own spawn settings instead of + * hacking MobSpawner.java + * + * @param groupData + * TODO + * @return TODO + */ + virtual MobGroupData* finalizeMobSpawn( + MobGroupData* groupData, + int extraData = 0); // 4J Added extraData param + virtual void finalizeSpawnEggSpawn(int extraData); // 4J Added + virtual bool canBeControlledByRider(); + virtual std::wstring getAName(); + virtual void setPersistenceRequired(); + virtual void setCustomName(const std::wstring& name); + virtual std::wstring getCustomName(); + virtual bool hasCustomName(); + virtual void setCustomNameVisible(bool visible); + virtual bool isCustomNameVisible(); + virtual bool shouldShowName(); + virtual void setDropChance(int slot, float pct); + virtual bool canPickUpLoot(); + virtual void setCanPickUpLoot(bool canPickUpLoot); + virtual bool isPersistenceRequired(); + virtual bool interact(std::shared_ptr player); + +protected: + virtual bool mobInteract(std::shared_ptr player); + + // roper / leash methods + +private: + bool _isLeashed; + std::shared_ptr leashHolder; + CompoundTag* leashInfoTag; + +protected: + virtual void tickLeash(); + +public: + virtual void dropLeash(bool synch, bool createItemDrop); + virtual bool canBeLeashed(); + virtual bool isLeashed(); + virtual std::shared_ptr getLeashHolder(); + virtual void setLeashedTo(std::shared_ptr holder, bool synch); + +private: + virtual void restoreLeashFromSave(); virtual bool shouldRender(Vec3* c); -protected: - void tickEffects(); - public: - void removeAllEffects(); - std::vector* getActiveEffects(); - bool hasEffect(int id); - bool hasEffect(MobEffect* effect); - MobEffectInstance* getEffect(MobEffect* effect); - void addEffect(MobEffectInstance* newEffect); - void addEffectNoUpdate(MobEffectInstance* newEffect); // 4J Added - virtual bool canBeAffected(MobEffectInstance* newEffect); - virtual bool isInvertedHealAndHarm(); - void removeEffectNoUpdate(int effectId); - void removeEffect(int effectId); - -protected: - virtual void onEffectAdded(MobEffectInstance* effect); - virtual void onEffectUpdated(MobEffectInstance* effect); - virtual void onEffectRemoved(MobEffectInstance* effect); - -public: - virtual float getWalkingSpeedModifier(); - - // 4J-Pb added (from 1.2.3) - virtual void teleportTo(double x, double y, double z); - virtual bool isBaby(); - virtual MobType getMobType(); - virtual void breakItem(std::shared_ptr itemInstance); - - virtual bool isInvulnerable(); - - virtual void finalizeMobSpawn(); - virtual bool canBeControlledByRider(); - // 4J Added override to update ai elements when loading entity from // schematics virtual void setLevel(Level* level); diff --git a/Minecraft.World/Entities/MobCategory.cpp b/Minecraft.World/Entities/MobCategory.cpp index ac39cceb7..b55f6c21a 100644 --- a/Minecraft.World/Entities/MobCategory.cpp +++ b/Minecraft.World/Entities/MobCategory.cpp @@ -7,47 +7,54 @@ MobCategory* MobCategory::monster = NULL; MobCategory* MobCategory::creature = NULL; +MobCategory* MobCategory::ambient = NULL; MobCategory* MobCategory::waterCreature = NULL; // 4J - added these extra categories MobCategory* MobCategory::creature_wolf = NULL; MobCategory* MobCategory::creature_chicken = NULL; MobCategory* MobCategory::creature_mushroomcow = NULL; -MobCategoryArray MobCategory::values = MobCategoryArray(6); +MobCategoryArray MobCategory::values = MobCategoryArray(7); void MobCategory::staticCtor() { // 4J - adjusted the max levels here for the xbox version, which now // represent the max levels in the whole world - monster = new MobCategory(70, Material::air, false, eTYPE_MONSTER, false, - CONSOLE_MONSTERS_HARD_LIMIT); - creature = new MobCategory(10, Material::air, true, + monster = new MobCategory(70, Material::air, false, false, eTYPE_MONSTER, + false, CONSOLE_MONSTERS_HARD_LIMIT); + creature = new MobCategory(10, Material::air, true, true, eTYPE_ANIMALS_SPAWN_LIMIT_CHECK, false, CONSOLE_ANIMALS_HARD_LIMIT); - waterCreature = new MobCategory(5, Material::water, true, eTYPE_WATERANIMAL, - false, CONSOLE_SQUID_HARD_LIMIT); + ambient = new MobCategory(15, Material::air, true, false, eTYPE_AMBIENT, + false, CONSOLE_AMBIENT_HARD_LIMIT), + waterCreature = + new MobCategory(5, Material::water, true, false, eTYPE_WATERANIMAL, + false, CONSOLE_SQUID_HARD_LIMIT); values[0] = monster; values[1] = creature; - values[2] = waterCreature; + values[2] = ambient; + values[3] = waterCreature; // 4J - added 2 new categories to give us better control over spawning // wolves & chickens - creature_wolf = new MobCategory(3, Material::air, true, eTYPE_WOLF, true, - MAX_XBOX_WOLVES); - creature_chicken = new MobCategory(2, Material::air, true, eTYPE_CHICKEN, - true, MAX_XBOX_CHICKENS); - creature_mushroomcow = new MobCategory( - 2, Material::air, true, eTYPE_MUSHROOMCOW, true, MAX_XBOX_MUSHROOMCOWS); - values[3] = creature_wolf; - values[4] = creature_chicken; - values[5] = creature_mushroomcow; + creature_wolf = new MobCategory(3, Material::air, true, true, eTYPE_WOLF, + true, MAX_XBOX_WOLVES); + creature_chicken = new MobCategory(2, Material::air, true, true, + eTYPE_CHICKEN, true, MAX_XBOX_CHICKENS); + creature_mushroomcow = + new MobCategory(2, Material::air, true, true, eTYPE_MUSHROOMCOW, true, + MAX_XBOX_MUSHROOMCOWS); + values[4] = creature_wolf; + values[5] = creature_chicken; + values[6] = creature_mushroomcow; } MobCategory::MobCategory(int maxVar, Material* spawnPositionMaterial, - bool isFriendly, eINSTANCEOF eBase, bool isSingleType, - int maxPerLevel) + bool isFriendly, bool isPersistent, eINSTANCEOF eBase, + bool isSingleType, int maxPerLevel) : m_max(maxVar), spawnPositionMaterial(spawnPositionMaterial), m_isFriendly(isFriendly), + m_isPersistent(isPersistent), m_eBase(eBase), m_isSingleType(isSingleType), m_maxPerLevel(maxPerLevel) {} @@ -69,3 +76,5 @@ Material* MobCategory::getSpawnPositionMaterial() { bool MobCategory::isFriendly() { return m_isFriendly; } bool MobCategory::isSingleType() { return m_isSingleType; } + +bool MobCategory::isPersistent() { return m_isPersistent; } diff --git a/Minecraft.World/Entities/MobCategory.h b/Minecraft.World/Entities/MobCategory.h index 5aadbb954..9a53c87b6 100644 --- a/Minecraft.World/Entities/MobCategory.h +++ b/Minecraft.World/Entities/MobCategory.h @@ -12,6 +12,7 @@ public: static const int CONSOLE_ANIMALS_HARD_LIMIT = 50; // Max number of animals (cows, sheep, pigs) that the mob spawner // will produce + static const int CONSOLE_AMBIENT_HARD_LIMIT = 20; // Ambient mobs static const int MAX_XBOX_CHICKENS = 8; // Max number of chickens that the mob spawner will produce @@ -26,6 +27,8 @@ public: 16; // Max number of iron golems that can be created by placing blocks // - 4J-PB increased limit due to player requests static const int CONSOLE_SQUID_HARD_LIMIT = 5; + static const int MAX_CONSOLE_BOSS = + 1; // Max number of bosses (enderdragon/wither) static const int MAX_XBOX_ANIMALS_WITH_BREEDING = CONSOLE_ANIMALS_HARD_LIMIT + 20; // Max number of animals that we can @@ -56,6 +59,8 @@ public: MAX_XBOX_MUSHROOMCOWS_WITH_BREEDING + 8; static const int MAX_XBOX_SQUIDS_WITH_SPAWN_EGG = CONSOLE_SQUID_HARD_LIMIT + 8; + static const int MAX_AMBIENT_WITH_SPAWN_EGG = + CONSOLE_AMBIENT_HARD_LIMIT + 8; /* Maximum animals = 50 + 20 + 20 = 90 @@ -74,6 +79,7 @@ public: static MobCategory* monster; static MobCategory* creature; + static MobCategory* ambient; static MobCategory* waterCreature; // 4J added extra categories, to break these out of general creatures & give // us more control of levels @@ -91,11 +97,13 @@ private: const int m_maxPerLevel; const Material* spawnPositionMaterial; const bool m_isFriendly; + const bool m_isPersistent; const bool m_isSingleType; // 4J Added const eINSTANCEOF m_eBase; // 4J added MobCategory(int maxVar, Material* spawnPositionMaterial, bool isFriendly, - eINSTANCEOF eBase, bool isSingleType, int maxPerLevel); + bool isPersistent, eINSTANCEOF eBase, bool isSingleType, + int maxPerLevel); public: const std::type_info getBaseClass(); @@ -105,6 +113,7 @@ public: Material* getSpawnPositionMaterial(); bool isFriendly(); bool isSingleType(); + bool isPersistent(); public: static void staticCtor(); diff --git a/Minecraft.World/Entities/MobEffect.cpp b/Minecraft.World/Entities/MobEffect.cpp index 568ce679a..a0822934f 100644 --- a/Minecraft.World/Entities/MobEffect.cpp +++ b/Minecraft.World/Entities/MobEffect.cpp @@ -1,5 +1,9 @@ #include "../Platform/stdafx.h" +#include "../Headers/net.minecraft.world.entity.ai.attributes.h" #include "../Headers/net.minecraft.world.entity.player.h" +#include "../Headers/net.minecraft.world.entity.monster.h" +#include "../Headers/net.minecraft.world.entity.h" +#include "../Headers/net.minecraft.world.level.h" #include "../Headers/net.minecraft.world.damagesource.h" #include "../Headers/net.minecraft.world.food.h" #include "../Headers/net.minecraft.world.effect.h" @@ -7,119 +11,195 @@ MobEffect* MobEffect::effects[NUM_EFFECTS]; -MobEffect* MobEffect::voidEffect = NULL; -MobEffect* MobEffect::movementSpeed = - (new MobEffect(1, false, eMinecraftColour_Effect_MovementSpeed)) - ->setDescriptionId(IDS_POTION_MOVESPEED) - ->setPostfixDescriptionId(IDS_POTION_MOVESPEED_POSTFIX) - ->setIcon(MobEffect::e_MobEffectIcon_Speed); // setIcon(0, 0); -MobEffect* MobEffect::movementSlowdown = - (new MobEffect(2, true, eMinecraftColour_Effect_MovementSlowDown)) - ->setDescriptionId(IDS_POTION_MOVESLOWDOWN) - ->setPostfixDescriptionId(IDS_POTION_MOVESLOWDOWN_POSTFIX) - ->setIcon(MobEffect::e_MobEffectIcon_Slowness); //->setIcon(1, 0); -MobEffect* MobEffect::digSpeed = - (new MobEffect(3, false, eMinecraftColour_Effect_DigSpeed)) - ->setDescriptionId(IDS_POTION_DIGSPEED) - ->setPostfixDescriptionId(IDS_POTION_DIGSPEED_POSTFIX) - ->setDurationModifier(1.5) - ->setIcon(MobEffect::e_MobEffectIcon_Haste); //->setIcon(2, 0); -MobEffect* MobEffect::digSlowdown = - (new MobEffect(4, true, eMinecraftColour_Effect_DigSlowdown)) - ->setDescriptionId(IDS_POTION_DIGSLOWDOWN) - ->setPostfixDescriptionId(IDS_POTION_DIGSLOWDOWN_POSTFIX) - ->setIcon(MobEffect::e_MobEffectIcon_MiningFatigue); //->setIcon(3, 0); -MobEffect* MobEffect::damageBoost = - (new MobEffect(5, false, eMinecraftColour_Effect_DamageBoost)) - ->setDescriptionId(IDS_POTION_DAMAGEBOOST) - ->setPostfixDescriptionId(IDS_POTION_DAMAGEBOOST_POSTFIX) - ->setIcon(MobEffect::e_MobEffectIcon_Strength); //->setIcon(4, 0); -MobEffect* MobEffect::heal = - (new InstantenousMobEffect(6, false, eMinecraftColour_Effect_Heal)) - ->setDescriptionId(IDS_POTION_HEAL) - ->setPostfixDescriptionId(IDS_POTION_HEAL_POSTFIX); -MobEffect* MobEffect::harm = - (new InstantenousMobEffect(7, true, eMinecraftColour_Effect_Harm)) - ->setDescriptionId(IDS_POTION_HARM) - ->setPostfixDescriptionId(IDS_POTION_HARM_POSTFIX); -MobEffect* MobEffect::jump = - (new MobEffect(8, false, eMinecraftColour_Effect_Jump)) - ->setDescriptionId(IDS_POTION_JUMP) - ->setPostfixDescriptionId(IDS_POTION_JUMP_POSTFIX) - ->setIcon(MobEffect::e_MobEffectIcon_JumpBoost); //->setIcon(2, 1); -MobEffect* MobEffect::confusion = - (new MobEffect(9, true, eMinecraftColour_Effect_Confusion)) - ->setDescriptionId(IDS_POTION_CONFUSION) - ->setPostfixDescriptionId(IDS_POTION_CONFUSION_POSTFIX) - ->setDurationModifier(.25) - ->setIcon(MobEffect::e_MobEffectIcon_Nausea); //->setIcon(3, 1); -MobEffect* MobEffect::regeneration = - (new MobEffect(10, false, eMinecraftColour_Effect_Regeneration)) - ->setDescriptionId(IDS_POTION_REGENERATION) - ->setPostfixDescriptionId(IDS_POTION_REGENERATION_POSTFIX) - ->setDurationModifier(.25) - ->setIcon(MobEffect::e_MobEffectIcon_Regeneration); //->setIcon(7, 0); -MobEffect* MobEffect::damageResistance = - (new MobEffect(11, false, eMinecraftColour_Effect_DamageResistance)) - ->setDescriptionId(IDS_POTION_RESISTANCE) - ->setPostfixDescriptionId(IDS_POTION_RESISTANCE_POSTFIX) - ->setIcon(MobEffect::e_MobEffectIcon_Resistance); //->setIcon(6, 1); -MobEffect* MobEffect::fireResistance = - (new MobEffect(12, false, eMinecraftColour_Effect_FireResistance)) - ->setDescriptionId(IDS_POTION_FIRERESISTANCE) - ->setPostfixDescriptionId(IDS_POTION_FIRERESISTANCE_POSTFIX) - ->setIcon( - MobEffect::e_MobEffectIcon_FireResistance); //->setIcon(7, 1); -MobEffect* MobEffect::waterBreathing = - (new MobEffect(13, false, eMinecraftColour_Effect_WaterBreathing)) - ->setDescriptionId(IDS_POTION_WATERBREATHING) - ->setPostfixDescriptionId(IDS_POTION_WATERBREATHING_POSTFIX) - ->setIcon( - MobEffect::e_MobEffectIcon_WaterBreathing); //->setIcon(0, 2); -MobEffect* MobEffect::invisibility = - (new MobEffect(14, false, eMinecraftColour_Effect_Invisiblity)) - ->setDescriptionId(IDS_POTION_INVISIBILITY) - ->setPostfixDescriptionId(IDS_POTION_INVISIBILITY_POSTFIX) - ->setIcon(MobEffect::e_MobEffectIcon_Invisiblity); //->setIcon(0, 1); -MobEffect* MobEffect::blindness = - (new MobEffect(15, true, eMinecraftColour_Effect_Blindness)) - ->setDescriptionId(IDS_POTION_BLINDNESS) - ->setPostfixDescriptionId(IDS_POTION_BLINDNESS_POSTFIX) - ->setDurationModifier(.25) - ->setIcon(MobEffect::e_MobEffectIcon_Blindness); //->setIcon(5, 1); -MobEffect* MobEffect::nightVision = - (new MobEffect(16, false, eMinecraftColour_Effect_NightVision)) - ->setDescriptionId(IDS_POTION_NIGHTVISION) - ->setPostfixDescriptionId(IDS_POTION_NIGHTVISION_POSTFIX) - ->setIcon(MobEffect::e_MobEffectIcon_NightVision); //->setIcon(4, 1); -MobEffect* MobEffect::hunger = - (new MobEffect(17, true, eMinecraftColour_Effect_Hunger)) - ->setDescriptionId(IDS_POTION_HUNGER) - ->setPostfixDescriptionId(IDS_POTION_HUNGER_POSTFIX) - ->setIcon(MobEffect::e_MobEffectIcon_Hunger); //->setIcon(1, 1); -MobEffect* MobEffect::weakness = - (new MobEffect(18, true, eMinecraftColour_Effect_Weakness)) - ->setDescriptionId(IDS_POTION_WEAKNESS) - ->setPostfixDescriptionId(IDS_POTION_WEAKNESS_POSTFIX) - ->setIcon(MobEffect::e_MobEffectIcon_Weakness); //->setIcon(5, 0); -MobEffect* MobEffect::poison = - (new MobEffect(19, true, eMinecraftColour_Effect_Poison)) - ->setDescriptionId(IDS_POTION_POISON) - ->setPostfixDescriptionId(IDS_POTION_POISON_POSTFIX) - ->setDurationModifier(.25) - ->setIcon(MobEffect::e_MobEffectIcon_Poison); //->setIcon(6, 0); -MobEffect* MobEffect::reserved_20 = NULL; -MobEffect* MobEffect::reserved_21 = NULL; -MobEffect* MobEffect::reserved_22 = NULL; -MobEffect* MobEffect::reserved_23 = NULL; -MobEffect* MobEffect::reserved_24 = NULL; -MobEffect* MobEffect::reserved_25 = NULL; -MobEffect* MobEffect::reserved_26 = NULL; -MobEffect* MobEffect::reserved_27 = NULL; -MobEffect* MobEffect::reserved_28 = NULL; -MobEffect* MobEffect::reserved_29 = NULL; -MobEffect* MobEffect::reserved_30 = NULL; -MobEffect* MobEffect::reserved_31 = NULL; +MobEffect* MobEffect::voidEffect; +MobEffect* MobEffect::movementSpeed; +MobEffect* MobEffect::movementSlowdown; +MobEffect* MobEffect::digSpeed; +MobEffect* MobEffect::digSlowdown; +MobEffect* MobEffect::damageBoost; +MobEffect* MobEffect::heal; +MobEffect* MobEffect::harm; +MobEffect* MobEffect::jump; +MobEffect* MobEffect::confusion; +MobEffect* MobEffect::regeneration; +MobEffect* MobEffect::damageResistance; +MobEffect* MobEffect::fireResistance; +MobEffect* MobEffect::waterBreathing; +MobEffect* MobEffect::invisibility; +MobEffect* MobEffect::blindness; +MobEffect* MobEffect::nightVision; +MobEffect* MobEffect::hunger; +MobEffect* MobEffect::weakness; +MobEffect* MobEffect::poison; +MobEffect* MobEffect::wither; +MobEffect* MobEffect::healthBoost; +MobEffect* MobEffect::absorption; +MobEffect* MobEffect::saturation; +MobEffect* MobEffect::reserved_24; +MobEffect* MobEffect::reserved_25; +MobEffect* MobEffect::reserved_26; +MobEffect* MobEffect::reserved_27; +MobEffect* MobEffect::reserved_28; +MobEffect* MobEffect::reserved_29; +MobEffect* MobEffect::reserved_30; +MobEffect* MobEffect::reserved_31; + +void MobEffect::staticCtor() { + voidEffect = NULL; + movementSpeed = + (new MobEffect(1, false, eMinecraftColour_Effect_MovementSpeed)) + ->setDescriptionId(IDS_POTION_MOVESPEED) + ->setPostfixDescriptionId(IDS_POTION_MOVESPEED_POSTFIX) + ->setIcon(MobEffect::e_MobEffectIcon_Speed) + ->addAttributeModifier( + SharedMonsterAttributes::MOVEMENT_SPEED, + eModifierId_POTION_MOVESPEED, 0.2f, + AttributeModifier::OPERATION_MULTIPLY_TOTAL); // setIcon(0, 0); + movementSlowdown = + (new MobEffect(2, true, eMinecraftColour_Effect_MovementSlowDown)) + ->setDescriptionId(IDS_POTION_MOVESLOWDOWN) + ->setPostfixDescriptionId(IDS_POTION_MOVESLOWDOWN_POSTFIX) + ->setIcon(MobEffect::e_MobEffectIcon_Slowness) + ->addAttributeModifier( + SharedMonsterAttributes::MOVEMENT_SPEED, + eModifierId_POTION_MOVESLOWDOWN, -0.15f, + AttributeModifier::OPERATION_MULTIPLY_TOTAL); //->setIcon(1, + // 0); + digSpeed = + (new MobEffect(3, false, eMinecraftColour_Effect_DigSpeed)) + ->setDescriptionId(IDS_POTION_DIGSPEED) + ->setPostfixDescriptionId(IDS_POTION_DIGSPEED_POSTFIX) + ->setDurationModifier(1.5) + ->setIcon(MobEffect::e_MobEffectIcon_Haste); //->setIcon(2, 0); + digSlowdown = + (new MobEffect(4, true, eMinecraftColour_Effect_DigSlowdown)) + ->setDescriptionId(IDS_POTION_DIGSLOWDOWN) + ->setPostfixDescriptionId(IDS_POTION_DIGSLOWDOWN_POSTFIX) + ->setIcon( + MobEffect::e_MobEffectIcon_MiningFatigue); //->setIcon(3, 0); + damageBoost = + (new AttackDamageMobEffect(5, false, + eMinecraftColour_Effect_DamageBoost)) + ->setDescriptionId(IDS_POTION_DAMAGEBOOST) + ->setPostfixDescriptionId(IDS_POTION_DAMAGEBOOST_POSTFIX) + ->setIcon(MobEffect::e_MobEffectIcon_Strength) + ->addAttributeModifier( + SharedMonsterAttributes::ATTACK_DAMAGE, + eModifierId_POTION_DAMAGEBOOST, 3, + AttributeModifier::OPERATION_MULTIPLY_TOTAL); //->setIcon(4, + // 0); + heal = (new InstantenousMobEffect(6, false, eMinecraftColour_Effect_Heal)) + ->setDescriptionId(IDS_POTION_HEAL) + ->setPostfixDescriptionId(IDS_POTION_HEAL_POSTFIX); + harm = (new InstantenousMobEffect(7, true, eMinecraftColour_Effect_Harm)) + ->setDescriptionId(IDS_POTION_HARM) + ->setPostfixDescriptionId(IDS_POTION_HARM_POSTFIX); + jump = + (new MobEffect(8, false, eMinecraftColour_Effect_Jump)) + ->setDescriptionId(IDS_POTION_JUMP) + ->setPostfixDescriptionId(IDS_POTION_JUMP_POSTFIX) + ->setIcon(MobEffect::e_MobEffectIcon_JumpBoost); //->setIcon(2, 1); + confusion = + (new MobEffect(9, true, eMinecraftColour_Effect_Confusion)) + ->setDescriptionId(IDS_POTION_CONFUSION) + ->setPostfixDescriptionId(IDS_POTION_CONFUSION_POSTFIX) + ->setDurationModifier(.25) + ->setIcon(MobEffect::e_MobEffectIcon_Nausea); //->setIcon(3, 1); + regeneration = + (new MobEffect(10, false, eMinecraftColour_Effect_Regeneration)) + ->setDescriptionId(IDS_POTION_REGENERATION) + ->setPostfixDescriptionId(IDS_POTION_REGENERATION_POSTFIX) + ->setDurationModifier(.25) + ->setIcon( + MobEffect::e_MobEffectIcon_Regeneration); //->setIcon(7, 0); + damageResistance = + (new MobEffect(11, false, eMinecraftColour_Effect_DamageResistance)) + ->setDescriptionId(IDS_POTION_RESISTANCE) + ->setPostfixDescriptionId(IDS_POTION_RESISTANCE_POSTFIX) + ->setIcon( + MobEffect::e_MobEffectIcon_Resistance); //->setIcon(6, 1); + fireResistance = + (new MobEffect(12, false, eMinecraftColour_Effect_FireResistance)) + ->setDescriptionId(IDS_POTION_FIRERESISTANCE) + ->setPostfixDescriptionId(IDS_POTION_FIRERESISTANCE_POSTFIX) + ->setIcon( + MobEffect::e_MobEffectIcon_FireResistance); //->setIcon(7, 1); + waterBreathing = + (new MobEffect(13, false, eMinecraftColour_Effect_WaterBreathing)) + ->setDescriptionId(IDS_POTION_WATERBREATHING) + ->setPostfixDescriptionId(IDS_POTION_WATERBREATHING_POSTFIX) + ->setIcon( + MobEffect::e_MobEffectIcon_WaterBreathing); //->setIcon(0, 2); + invisibility = + (new MobEffect(14, false, eMinecraftColour_Effect_Invisiblity)) + ->setDescriptionId(IDS_POTION_INVISIBILITY) + ->setPostfixDescriptionId(IDS_POTION_INVISIBILITY_POSTFIX) + ->setIcon( + MobEffect::e_MobEffectIcon_Invisiblity); //->setIcon(0, 1); + blindness = + (new MobEffect(15, true, eMinecraftColour_Effect_Blindness)) + ->setDescriptionId(IDS_POTION_BLINDNESS) + ->setPostfixDescriptionId(IDS_POTION_BLINDNESS_POSTFIX) + ->setDurationModifier(.25) + ->setIcon(MobEffect::e_MobEffectIcon_Blindness); //->setIcon(5, 1); + nightVision = + (new MobEffect(16, false, eMinecraftColour_Effect_NightVision)) + ->setDescriptionId(IDS_POTION_NIGHTVISION) + ->setPostfixDescriptionId(IDS_POTION_NIGHTVISION_POSTFIX) + ->setIcon( + MobEffect::e_MobEffectIcon_NightVision); //->setIcon(4, 1); + hunger = + (new MobEffect(17, true, eMinecraftColour_Effect_Hunger)) + ->setDescriptionId(IDS_POTION_HUNGER) + ->setPostfixDescriptionId(IDS_POTION_HUNGER_POSTFIX) + ->setIcon(MobEffect::e_MobEffectIcon_Hunger); //->setIcon(1, 1); + weakness = + (new AttackDamageMobEffect(18, true, eMinecraftColour_Effect_Weakness)) + ->setDescriptionId(IDS_POTION_WEAKNESS) + ->setPostfixDescriptionId(IDS_POTION_WEAKNESS_POSTFIX) + ->setIcon(MobEffect::e_MobEffectIcon_Weakness) + ->addAttributeModifier( + SharedMonsterAttributes::ATTACK_DAMAGE, + eModifierId_POTION_WEAKNESS, 2, + AttributeModifier::OPERATION_ADDITION); //->setIcon(5, 0); + poison = + (new MobEffect(19, true, eMinecraftColour_Effect_Poison)) + ->setDescriptionId(IDS_POTION_POISON) + ->setPostfixDescriptionId(IDS_POTION_POISON_POSTFIX) + ->setDurationModifier(.25) + ->setIcon(MobEffect::e_MobEffectIcon_Poison); //->setIcon(6, 0); + wither = (new MobEffect(20, true, eMinecraftColour_Effect_Wither)) + ->setDescriptionId(IDS_POTION_WITHER) + ->setPostfixDescriptionId(IDS_POTION_WITHER_POSTFIX) + ->setIcon(MobEffect::e_MobEffectIcon_Wither) + ->setDurationModifier(.25); + healthBoost = + (new HealthBoostMobEffect(21, false, + eMinecraftColour_Effect_HealthBoost)) + ->setDescriptionId(IDS_POTION_HEALTHBOOST) + ->setPostfixDescriptionId(IDS_POTION_HEALTHBOOST_POSTFIX) + ->setIcon(MobEffect::e_MobEffectIcon_HealthBoost) + ->addAttributeModifier(SharedMonsterAttributes::MAX_HEALTH, + eModifierId_POTION_HEALTHBOOST, 4, + AttributeModifier::OPERATION_ADDITION); + absorption = + (new AbsoptionMobEffect(22, false, eMinecraftColour_Effect_Absoprtion)) + ->setDescriptionId(IDS_POTION_ABSORPTION) + ->setPostfixDescriptionId(IDS_POTION_ABSORPTION_POSTFIX) + ->setIcon(MobEffect::e_MobEffectIcon_Absorption); + saturation = (new InstantenousMobEffect(23, false, + eMinecraftColour_Effect_Saturation)) + ->setDescriptionId(IDS_POTION_SATURATION) + ->setPostfixDescriptionId(IDS_POTION_SATURATION_POSTFIX); + reserved_24 = NULL; + reserved_25 = NULL; + reserved_26 = NULL; + reserved_27 = NULL; + reserved_28 = NULL; + reserved_29 = NULL; + reserved_30 = NULL; + reserved_31 = NULL; +} MobEffect::MobEffect(int id, bool isHarmful, eMinecraftColour color) : id(id), _isHarmful(isHarmful), color(color) { @@ -159,7 +239,8 @@ int MobEffect::getId() { return id; } * @param mob * @param amplification */ -void MobEffect::applyEffectTick(std::shared_ptr mob, int amplification) { +void MobEffect::applyEffectTick(std::shared_ptr mob, + int amplification) { // Maybe move this to separate class implementations in the future? if (id == regeneration->id) { if (mob->getHealth() < mob->getMaxHealth()) { @@ -169,27 +250,33 @@ void MobEffect::applyEffectTick(std::shared_ptr mob, int amplification) { if (mob->getHealth() > 1) { mob->hurt(DamageSource::magic, 1); } - } else if (id == hunger->id && - std::dynamic_pointer_cast(mob) != NULL) { + } else if (id == wither->id) { + mob->hurt(DamageSource::wither, 1); + } else if ((id == hunger->id) && mob->instanceof(eTYPE_PLAYER)) { // every tick, cause the same amount of exhaustion as when removing // a block, times amplification std::dynamic_pointer_cast(mob)->causeFoodExhaustion( FoodConstants::EXHAUSTION_MINE * (amplification + 1)); + } else if ((id == saturation->id) && mob->instanceof(eTYPE_PLAYER)) { + if (!mob->level->isClientSide) { + std::dynamic_pointer_cast(mob)->getFoodData()->eat( + amplification + 1, FoodConstants::FOOD_SATURATION_MAX); + } } else if ((id == heal->id && !mob->isInvertedHealAndHarm()) || (id == harm->id && mob->isInvertedHealAndHarm())) { - mob->heal(6 << amplification); + mob->heal(std::max(4 << amplification, 0)); } else if ((id == harm->id && !mob->isInvertedHealAndHarm()) || (id == heal->id && mob->isInvertedHealAndHarm())) { mob->hurt(DamageSource::magic, 6 << amplification); } } -void MobEffect::applyInstantenousEffect(std::shared_ptr source, - std::shared_ptr mob, +void MobEffect::applyInstantenousEffect(std::shared_ptr source, + std::shared_ptr mob, int amplification, double scale) { if ((id == heal->id && !mob->isInvertedHealAndHarm()) || (id == harm->id && mob->isInvertedHealAndHarm())) { - int amount = (int)(scale * (double)(6 << amplification) + .5); + int amount = (int)(scale * (double)(4 << amplification) + .5); mob->heal(amount); } else if ((id == harm->id && !mob->isInvertedHealAndHarm()) || (id == heal->id && mob->isInvertedHealAndHarm())) { @@ -219,13 +306,26 @@ bool MobEffect::isInstantenous() { return false; } */ bool MobEffect::isDurationEffectTick(int remainingDuration, int amplification) { // Maybe move this to separate class implementations in the future? - if (id == regeneration->id || id == poison->id) { + if (id == regeneration->id) { + // tick intervals are 50, 25, 12, 6.. + int interval = 50 >> amplification; + if (interval > 0) { + return (remainingDuration % interval) == 0; + } + return true; + } else if (id == poison->id) { // tick intervals are 25, 12, 6.. int interval = 25 >> amplification; if (interval > 0) { return (remainingDuration % interval) == 0; } return true; + } else if (id == wither->id) { + int interval = 40 >> amplification; + if (interval > 0) { + return (remainingDuration % interval) == 0; + } + return true; } else if (id == hunger->id) { return true; } @@ -256,6 +356,9 @@ MobEffect::EMobEffectIcon MobEffect::getIcon() { return icon; } bool MobEffect::isHarmful() { return _isHarmful; } std::wstring MobEffect::formatDuration(MobEffectInstance* instance) { + if (instance->isNoCounter()) { + return L"**:**"; + } int duration = instance->getDuration(); int seconds = duration / SharedConstants::TICKS_PER_SECOND; @@ -284,10 +387,60 @@ MobEffect* MobEffect::setDurationModifier(double durationModifier) { double MobEffect::getDurationModifier() { return durationModifier; } MobEffect* MobEffect::setDisabled() { - this->_isDisabled = true; + _isDisabled = true; return this; } bool MobEffect::isDisabled() { return _isDisabled; } eMinecraftColour MobEffect::getColor() { return color; } + +MobEffect* MobEffect::addAttributeModifier(Attribute* attribute, + eMODIFIER_ID id, double amount, + int operation) { + AttributeModifier* effect = new AttributeModifier(id, amount, operation); + attributeModifiers.insert( + std::pair(attribute, effect)); + return this; +} + +std::unordered_map* +MobEffect::getAttributeModifiers() { + return &attributeModifiers; +} + +void MobEffect::removeAttributeModifiers(std::shared_ptr entity, + BaseAttributeMap* attributes, + int amplifier) { + for (AUTO_VAR(it, attributeModifiers.begin()); + it != attributeModifiers.end(); ++it) { + AttributeInstance* attribute = attributes->getInstance(it->first); + + if (attribute != NULL) { + attribute->removeModifier(it->second); + } + } +} + +void MobEffect::addAttributeModifiers(std::shared_ptr entity, + BaseAttributeMap* attributes, + int amplifier) { + for (AUTO_VAR(it, attributeModifiers.begin()); + it != attributeModifiers.end(); ++it) { + AttributeInstance* attribute = attributes->getInstance(it->first); + + if (attribute != NULL) { + AttributeModifier* original = it->second; + attribute->removeModifier(original); + attribute->addModifier(new AttributeModifier( + original->getId(), + getAttributeModifierValue(amplifier, original), + original->getOperation())); + } + } +} + +double MobEffect::getAttributeModifierValue(int amplifier, + AttributeModifier* original) { + return original->getAmount() * (amplifier + 1); +} \ No newline at end of file diff --git a/Minecraft.World/Entities/MobEffect.h b/Minecraft.World/Entities/MobEffect.h index 3138fc66a..8e7ccce17 100644 --- a/Minecraft.World/Entities/MobEffect.h +++ b/Minecraft.World/Entities/MobEffect.h @@ -1,7 +1,10 @@ #pragma once +#include "../AI/Attributes/AttributeModifier.h" + class Mob; class MobEffectInstance; +class Attribute; class MobEffect { public: @@ -24,6 +27,9 @@ public: e_MobEffectIcon_Strength, e_MobEffectIcon_WaterBreathing, e_MobEffectIcon_Weakness, + e_MobEffectIcon_Wither, + e_MobEffectIcon_HealthBoost, + e_MobEffectIcon_Absorption, e_MobEffectIcon_COUNT, }; @@ -51,10 +57,10 @@ public: static MobEffect* hunger; static MobEffect* weakness; static MobEffect* poison; - static MobEffect* reserved_20; - static MobEffect* reserved_21; - static MobEffect* reserved_22; - static MobEffect* reserved_23; + static MobEffect* wither; + static MobEffect* healthBoost; + static MobEffect* absorption; + static MobEffect* saturation; static MobEffect* reserved_24; static MobEffect* reserved_25; static MobEffect* reserved_26; @@ -66,7 +72,10 @@ public: const int id; + static void staticCtor(); + private: + std::unordered_map attributeModifiers; int descriptionId; int m_postfixDescriptionId; // 4J added EMobEffectIcon icon; // 4J changed type @@ -82,11 +91,12 @@ protected: MobEffect* setIcon(EMobEffectIcon icon); public: - int getId(); - void applyEffectTick(std::shared_ptr mob, int amplification); - void applyInstantenousEffect(std::shared_ptr source, - std::shared_ptr mob, int amplification, - double scale); + virtual int getId(); + virtual void applyEffectTick(std::shared_ptr mob, + int amplification); + virtual void applyInstantenousEffect(std::shared_ptr source, + std::shared_ptr mob, + int amplification, double scale); virtual bool isInstantenous(); virtual bool isDurationEffectTick(int remainingDuration, int amplification); @@ -106,8 +116,22 @@ protected: MobEffect* setDurationModifier(double durationModifier); public: - double getDurationModifier(); - MobEffect* setDisabled(); - bool isDisabled(); - eMinecraftColour getColor(); + virtual double getDurationModifier(); + virtual MobEffect* setDisabled(); + virtual bool isDisabled(); + virtual eMinecraftColour getColor(); + + virtual MobEffect* addAttributeModifier(Attribute* attribute, + eMODIFIER_ID id, double amount, + int operation); + virtual std::unordered_map* + getAttributeModifiers(); + virtual void removeAttributeModifiers(std::shared_ptr entity, + BaseAttributeMap* attributes, + int amplifier); + virtual void addAttributeModifiers(std::shared_ptr entity, + BaseAttributeMap* attributes, + int amplifier); + virtual double getAttributeModifierValue(int amplifier, + AttributeModifier* original); }; \ No newline at end of file diff --git a/Minecraft.World/Entities/MobEffectInstance.cpp b/Minecraft.World/Entities/MobEffectInstance.cpp index e30393e12..c9689c415 100644 --- a/Minecraft.World/Entities/MobEffectInstance.cpp +++ b/Minecraft.World/Entities/MobEffectInstance.cpp @@ -5,6 +5,10 @@ void MobEffectInstance::_init(int id, int duration, int amplifier) { this->id = id; this->duration = duration; this->amplifier = amplifier; + + splash = false; + ambient = false; + noCounter = false; } MobEffectInstance::MobEffectInstance(int id) { _init(id, 0, 0); } @@ -17,23 +21,34 @@ MobEffectInstance::MobEffectInstance(int id, int duration, int amplifier) { _init(id, duration, amplifier); } +MobEffectInstance::MobEffectInstance(int id, int duration, int amplifier, + bool ambient) { + _init(id, duration, amplifier); + this->ambient = ambient; +} + MobEffectInstance::MobEffectInstance(MobEffectInstance* copy) { this->id = copy->id; this->duration = copy->duration; this->amplifier = copy->amplifier; + this->splash = copy->splash; + this->ambient = copy->ambient; + this->noCounter = copy->noCounter; } void MobEffectInstance::update(MobEffectInstance* takeOver) { - if (this->id != takeOver->id) { + if (id != takeOver->id) { app.DebugPrintf( "This method should only be called for matching effects!"); } - if (takeOver->amplifier > this->amplifier) { - this->amplifier = takeOver->amplifier; - this->duration = takeOver->duration; - } else if (takeOver->amplifier == this->amplifier && - this->duration < takeOver->duration) { - this->duration = takeOver->duration; + if (takeOver->amplifier > amplifier) { + amplifier = takeOver->amplifier; + duration = takeOver->duration; + } else if (takeOver->amplifier == amplifier && + duration < takeOver->duration) { + duration = takeOver->duration; + } else if (!takeOver->ambient && ambient) { + ambient = takeOver->ambient; } } @@ -43,13 +58,19 @@ int MobEffectInstance::getDuration() { return duration; } int MobEffectInstance::getAmplifier() { return amplifier; } +bool MobEffectInstance::isSplash() { return splash; } + +void MobEffectInstance::setSplash(bool splash) { this->splash = splash; } + +bool MobEffectInstance::isAmbient() { return ambient; } + /** * Runs the effect on a Mob target. * * @param target * @return True if the effect is still active. */ -bool MobEffectInstance::tick(std::shared_ptr target) { +bool MobEffectInstance::tick(std::shared_ptr target) { if (duration > 0) { if (MobEffect::effects[id]->isDurationEffectTick(duration, amplifier)) { applyEffect(target); @@ -61,7 +82,7 @@ bool MobEffectInstance::tick(std::shared_ptr target) { int MobEffectInstance::tickDownDuration() { return --duration; } -void MobEffectInstance::applyEffect(std::shared_ptr mob) { +void MobEffectInstance::applyEffect(std::shared_ptr mob) { if (duration > 0) { MobEffect::effects[id]->applyEffectTick(mob, amplifier); } @@ -88,11 +109,11 @@ int MobEffectInstance::hashCode() { std::wstring MobEffectInstance::toString() { std::wstring result = L"MobEffectInstance::toString - NON IMPLEMENTED OR LOCALISED FUNCTION"; - // std::wstring result = ""; + // wstring result = ""; // if (getAmplifier() > 0) //{ // result = getDescriptionId() + " x " + (getAmplifier() + 1) + ", - //Duration: " + getDuration(); + // Duration: " + getDuration(); // } // else //{ @@ -106,7 +127,30 @@ std::wstring MobEffectInstance::toString() { } // Was bool equals(Object obj) -bool MobEffectInstance::equals(MobEffectInstance* obj) { - return this->id == obj->id && this->amplifier == obj->amplifier && - this->duration == obj->duration; +bool MobEffectInstance::equals(MobEffectInstance* instance) { + return id == instance->id && amplifier == instance->amplifier && + duration == instance->duration && splash == instance->splash && + ambient == instance->ambient; } + +CompoundTag* MobEffectInstance::save(CompoundTag* tag) { + tag->putByte(L"Id", (uint8_t)getId()); + tag->putByte(L"Amplifier", (uint8_t)getAmplifier()); + tag->putInt(L"Duration", getDuration()); + tag->putBoolean(L"Ambient", isAmbient()); + return tag; +} + +MobEffectInstance* MobEffectInstance::load(CompoundTag* tag) { + int id = tag->getByte(L"Id"); + int amplifier = tag->getByte(L"Amplifier"); + int duration = tag->getInt(L"Duration"); + boolean ambient = tag->getBoolean(L"Ambient"); + return new MobEffectInstance(id, duration, amplifier, ambient); +} + +void MobEffectInstance::setNoCounter(bool noCounter) { + this->noCounter = noCounter; +} + +bool MobEffectInstance::isNoCounter() { return noCounter; } \ No newline at end of file diff --git a/Minecraft.World/Entities/MobEffectInstance.h b/Minecraft.World/Entities/MobEffectInstance.h index eb8dd2a27..b37f8711e 100644 --- a/Minecraft.World/Entities/MobEffectInstance.h +++ b/Minecraft.World/Entities/MobEffectInstance.h @@ -10,6 +10,9 @@ private: int duration; // sent as byte int amplifier; + bool splash; + bool ambient; + bool noCounter; void _init(int id, int duration, int amplifier); @@ -17,19 +20,25 @@ public: MobEffectInstance(int id); MobEffectInstance(int id, int duration); MobEffectInstance(int id, int duration, int amplifier); + MobEffectInstance(int id, int duration, int amplifier, bool ambient); MobEffectInstance(MobEffectInstance* copy); void update(MobEffectInstance* takeOver); int getId(); int getDuration(); int getAmplifier(); - bool tick(std::shared_ptr target); + + bool isSplash(); + void setSplash(bool splash); + bool isAmbient(); + + bool tick(std::shared_ptr target); private: int tickDownDuration(); public: - void applyEffect(std::shared_ptr mob); + void applyEffect(std::shared_ptr mob); int getDescriptionId(); int getPostfixDescriptionId(); // 4J Added int hashCode(); @@ -38,4 +47,9 @@ public: // Was bool equals(Object obj) bool equals(MobEffectInstance* obj); + + CompoundTag* save(CompoundTag* tag); + static MobEffectInstance* load(CompoundTag* tag); + void setNoCounter(bool noCounter); + bool isNoCounter(); }; \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/AgeableMob.cpp b/Minecraft.World/Entities/Mobs/AgeableMob.cpp index bd236aa6d..ff04e017f 100644 --- a/Minecraft.World/Entities/Mobs/AgeableMob.cpp +++ b/Minecraft.World/Entities/Mobs/AgeableMob.cpp @@ -12,10 +12,10 @@ AgableMob::AgableMob(Level* level) : PathfinderMob(level) { registeredBBHeight = 0; } -bool AgableMob::interact(std::shared_ptr player) { +bool AgableMob::mobInteract(std::shared_ptr player) { std::shared_ptr item = player->inventory->getSelected(); - if (item != NULL && item->id == Item::monsterPlacer_Id) { + if (item != NULL && item->id == Item::spawnEgg_Id) { if (!level->isClientSide) { eINSTANCEOF classToSpawn = EntityIO::getClass(item->getAuxValue()); if (classToSpawn != eTYPE_NOTSET && @@ -23,28 +23,38 @@ bool AgableMob::interact(std::shared_ptr player) { classToSpawn == GetType()) // 4J Added GetType() check to only // spawn same type { - std::shared_ptr offspring = getBreedOffspring( - std::dynamic_pointer_cast(shared_from_this())); - if (offspring != NULL) { - offspring->setAge(-20 * 60 * 20); - offspring->moveTo(x, y, z, 0, 0); + int error; + std::shared_ptr result = + SpawnEggItem::canSpawn(item->getAuxValue(), level, &error); - level->addEntity(offspring); + if (result != NULL) { + std::shared_ptr offspring = + getBreedOffspring(std::dynamic_pointer_cast( + shared_from_this())); + if (offspring != NULL) { + offspring->setAge(BABY_START_AGE); + offspring->moveTo(x, y, z, 0, 0); - if (!player->abilities.instabuild) { - item->count--; + level->addEntity(offspring); - if (item->count <= 0) { - player->inventory->setItem( - player->inventory->selected, nullptr); + if (!player->abilities.instabuild) { + item->count--; + + if (item->count <= 0) { + player->inventory->setItem( + player->inventory->selected, nullptr); + } } } + } else { + SpawnEggItem::DisplaySpawnError(player, error); } } } + return true; } - return PathfinderMob::interact(player); + return false; } void AgableMob::defineSynchedData() { @@ -54,6 +64,15 @@ void AgableMob::defineSynchedData() { int AgableMob::getAge() { return entityData->getInteger(DATA_AGE_ID); } +void AgableMob::ageUp(int seconds) { + int age = getAge(); + age += seconds * SharedConstants::TICKS_PER_SECOND; + if (age > 0) { + age = 0; + } + setAge(age); +} + void AgableMob::setAge(int age) { entityData->set(DATA_AGE_ID, age); updateSize(isBaby()); diff --git a/Minecraft.World/Entities/Mobs/AgeableMob.h b/Minecraft.World/Entities/Mobs/AgeableMob.h index 933dc349f..5815a2347 100644 --- a/Minecraft.World/Entities/Mobs/AgeableMob.h +++ b/Minecraft.World/Entities/Mobs/AgeableMob.h @@ -6,13 +6,17 @@ class AgableMob : public PathfinderMob { private: static const int DATA_AGE_ID = 12; +public: + static const int BABY_START_AGE = -20 * 60 * 20; + +private: float registeredBBWidth; float registeredBBHeight; public: AgableMob(Level* level); - virtual bool interact(std::shared_ptr player); + virtual bool mobInteract(std::shared_ptr player); protected: virtual void defineSynchedData(); @@ -21,6 +25,7 @@ public: virtual std::shared_ptr getBreedOffspring( std::shared_ptr target) = 0; virtual int getAge(); + virtual void ageUp(int seconds); virtual void setAge(int age); virtual void addAdditonalSaveData(CompoundTag* tag); virtual void readAdditionalSaveData(CompoundTag* tag); diff --git a/Minecraft.World/Entities/Mobs/AmbientCreature.cpp b/Minecraft.World/Entities/Mobs/AmbientCreature.cpp new file mode 100644 index 000000000..6022dd7db --- /dev/null +++ b/Minecraft.World/Entities/Mobs/AmbientCreature.cpp @@ -0,0 +1,11 @@ +#include "../../Platform/stdafx.h" + +#include "AmbientCreature.h" + +AmbientCreature::AmbientCreature(Level* level) : Mob(level) {} + +bool AmbientCreature::canBeLeashed() { return false; } + +bool AmbientCreature::mobInteract(std::shared_ptr player) { + return false; +} \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/AmbientCreature.h b/Minecraft.World/Entities/Mobs/AmbientCreature.h new file mode 100644 index 000000000..59e059841 --- /dev/null +++ b/Minecraft.World/Entities/Mobs/AmbientCreature.h @@ -0,0 +1,14 @@ +#pragma once + +#include "../Mob.h" +#include "Creature.h" + +class AmbientCreature : public Mob, public Creature { +public: + AmbientCreature(Level* level); + + virtual bool canBeLeashed(); + +protected: + virtual bool mobInteract(std::shared_ptr player); +}; \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/Animal.cpp b/Minecraft.World/Entities/Mobs/Animal.cpp index c1be81155..7c72ea23b 100644 --- a/Minecraft.World/Entities/Mobs/Animal.cpp +++ b/Minecraft.World/Entities/Mobs/Animal.cpp @@ -1,4 +1,5 @@ #include "../../Platform/stdafx.h" + #include "../../Headers/com.mojang.nbt.h" #include "../../Headers/net.minecraft.world.level.tile.h" #include "../../Headers/net.minecraft.world.item.h" @@ -9,6 +10,8 @@ #include "../../Headers/net.minecraft.world.entity.h" #include "../../Headers/net.minecraft.world.entity.projectile.h" #include "../../Headers/net.minecraft.world.damagesource.h" +#include "../../Headers/net.minecraft.world.entity.monster.h" +#include "../../Headers/net.minecraft.world.entity.ai.attributes.h" #include "../../Util/Random.h" #include "Animal.h" @@ -57,7 +60,8 @@ void Animal::aiStep() { } void Animal::checkHurtTarget(std::shared_ptr target, float d) { - if (std::dynamic_pointer_cast(target) != NULL) { + // 4J-JEV: Changed from dynamic cast to use eINSTANCEOF + if (target->instanceof(eTYPE_PLAYER)) { if (d < 3) { double xd = target->x - x; double zd = target->z - z; @@ -67,13 +71,13 @@ void Animal::checkHurtTarget(std::shared_ptr target, float d) { } std::shared_ptr p = std::dynamic_pointer_cast(target); - if (p->getSelectedItem() != NULL && - this->isFood(p->getSelectedItem())) { - } else { + if (p->getSelectedItem() == NULL || !isFood(p->getSelectedItem())) { attackTarget = nullptr; } - } else if (std::dynamic_pointer_cast(target) != NULL) { + } + // 4J-JEV: Changed from dynamic cast to use eINSTANCEOF + else if (target->instanceof(eTYPE_ANIMAL)) { std::shared_ptr a = std::dynamic_pointer_cast(target); if (getAge() > 0 && a->getAge() < 0) { if (d < 2.5) { @@ -152,20 +156,26 @@ float Animal::getWalkTargetValue(int x, int y, int z) { return level->getBrightness(x, y, z) - 0.5f; } -bool Animal::hurt(DamageSource* dmgSource, int dmg) { +bool Animal::hurt(DamageSource* dmgSource, float dmg) { + if (isInvulnerable()) return false; if (dynamic_cast(dmgSource) != NULL) { std::shared_ptr source = dmgSource->getDirectEntity(); - if (std::dynamic_pointer_cast(source) != NULL && + // 4J-JEV: Changed from dynamic cast to use eINSTANCEOF + if (source->instanceof(eTYPE_PLAYER) && !std::dynamic_pointer_cast(source) ->isAllowedToAttackAnimals()) { return false; } - if (source != NULL && source->GetType() == eTYPE_ARROW) { + if ((source != NULL) && source->instanceof(eTYPE_ARROW)) { std::shared_ptr arrow = std::dynamic_pointer_cast(source); - if (std::dynamic_pointer_cast(arrow->owner) != NULL && + + // 4J: Check that the arrow's owner can attack animals (dispenser + // arrows are not owned) + if (arrow->owner != NULL && + arrow->owner->instanceof(eTYPE_PLAYER) && !std::dynamic_pointer_cast(arrow->owner) ->isAllowedToAttackAnimals()) { return false; @@ -174,6 +184,16 @@ bool Animal::hurt(DamageSource* dmgSource, int dmg) { } fleeTime = 20 * 3; + + if (!useNewAi()) { + AttributeInstance* speed = + getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED); + if (speed->getModifier(eModifierId_MOB_FLEEING) == NULL) { + speed->addModifier( + new AttributeModifier(*Animal::SPEED_MODIFIER_FLEEING)); + } + } + attackTarget = nullptr; setInLoveValue(0); @@ -265,9 +285,10 @@ bool Animal::isFood(std::shared_ptr itemInstance) { return itemInstance->id == Item::wheat_Id; } -bool Animal::interact(std::shared_ptr player) { +bool Animal::mobInteract(std::shared_ptr player) { std::shared_ptr item = player->inventory->getSelected(); - if (item != NULL && isFood(item) && getAge() == 0) { + if (item != NULL && isFood(item) && getAge() == 0 && + getInLoveValue() <= 0) { if (!player->abilities.instabuild) { item->count--; if (item->count <= 0) { @@ -311,28 +332,17 @@ bool Animal::interact(std::shared_ptr player) { return false; } - } else if ((GetType() & eTYPE_MONSTER) == eTYPE_MONSTER) { + } else if (instanceof(eTYPE_MONSTER)) { } break; } setInLove(player); } - - attackTarget = nullptr; - for (int i = 0; i < 7; i++) { - double xa = random->nextGaussian() * 0.02; - double ya = random->nextGaussian() * 0.02; - double za = random->nextGaussian() * 0.02; - level->addParticle(eParticleType_heart, - x + random->nextFloat() * bbWidth * 2 - bbWidth, - y + .5f + random->nextFloat() * bbHeight, - z + random->nextFloat() * bbWidth * 2 - bbWidth, - xa, ya, za); - } + setInLove(); return true; } - return AgableMob::interact(player); + return AgableMob::mobInteract(player); } // 4J added @@ -348,18 +358,41 @@ void Animal::setInLove(std::shared_ptr player) { std::shared_ptr Animal::getLoveCause() { return loveCause.lock(); } +void Animal::setInLove() { + entityData->set(DATA_IN_LOVE, 20 * 30); + + attackTarget = nullptr; + level->broadcastEntityEvent(shared_from_this(), + EntityEvent::IN_LOVE_HEARTS); +} + bool Animal::isInLove() { return entityData->getInteger(DATA_IN_LOVE) > 0; } void Animal::resetLove() { entityData->set(DATA_IN_LOVE, 0); } bool Animal::canMate(std::shared_ptr partner) { if (partner == shared_from_this()) return false; - Animal* partnerPtr = partner.get(); - if (partnerPtr == NULL || typeid(*partnerPtr) != typeid(*this)) - return false; + if (typeid(*partner) != typeid(*this)) return false; return isInLove() && partner->isInLove(); } +void Animal::handleEntityEvent(uint8_t id) { + if (id == EntityEvent::IN_LOVE_HEARTS) { + for (int i = 0; i < 7; i++) { + double xa = random->nextGaussian() * 0.02; + double ya = random->nextGaussian() * 0.02; + double za = random->nextGaussian() * 0.02; + level->addParticle(eParticleType_heart, + x + random->nextFloat() * bbWidth * 2 - bbWidth, + y + .5f + random->nextFloat() * bbHeight, + z + random->nextFloat() * bbWidth * 2 - bbWidth, + xa, ya, za); + } + } else { + AgableMob::handleEntityEvent(id); + } +} + void Animal::updateDespawnProtectedState() { if (level->isClientSide) return; @@ -375,7 +408,7 @@ void Animal::updateDespawnProtectedState() { if (((m_maxWanderX - m_minWanderX) > MAX_WANDER_DISTANCE) || ((m_maxWanderZ - m_minWanderZ) > MAX_WANDER_DISTANCE)) { // printf("Unprotecting : %d to %d, %d to %d\n", - //m_minWanderX, m_maxWanderX, m_minWanderZ, m_maxWanderZ ); + // m_minWanderX, m_maxWanderX, m_minWanderZ, m_maxWanderZ ); m_isDespawnProtected = false; } @@ -403,4 +436,4 @@ void Animal::setDespawnProtected() { m_maxWanderZ = zt; m_isDespawnProtected = true; -} +} \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/Animal.h b/Minecraft.World/Entities/Mobs/Animal.h index f9a366b3c..f7882212e 100644 --- a/Minecraft.World/Entities/Mobs/Animal.h +++ b/Minecraft.World/Entities/Mobs/Animal.h @@ -11,7 +11,7 @@ private: static const int DATA_IN_LOVE = 13; // 4J added // int inLove; // 4J - //removed - now synched data + // removed - now synched data int loveTime; std::weak_ptr loveCause; @@ -35,7 +35,7 @@ public: virtual float getWalkTargetValue(int x, int y, int z); public: - virtual bool hurt(DamageSource* source, int dmg); + virtual bool hurt(DamageSource* source, float dmg); virtual void addAdditonalSaveData(CompoundTag* tag); virtual void readAdditionalSaveData(CompoundTag* tag); @@ -52,7 +52,7 @@ protected: public: virtual bool isFood(std::shared_ptr itemInstance); - virtual bool interact(std::shared_ptr player); + virtual bool mobInteract(std::shared_ptr player); protected: int getInLoveValue(); // 4J added @@ -62,10 +62,12 @@ public: void setInLove(std::shared_ptr player); // 4J added, then modified to match latest Java // for XboxOne achievements + virtual void setInLove(); std::shared_ptr getLoveCause(); bool isInLove(); void resetLove(); virtual bool canMate(std::shared_ptr partner); + virtual void handleEntityEvent(uint8_t id); // 4J added for determining whether animals are enclosed or not private: diff --git a/Minecraft.World/Entities/Mobs/Arrow.cpp b/Minecraft.World/Entities/Mobs/Arrow.cpp index f57bb4845..fc6562f26 100644 --- a/Minecraft.World/Entities/Mobs/Arrow.cpp +++ b/Minecraft.World/Entities/Mobs/Arrow.cpp @@ -7,6 +7,9 @@ #include "../../Headers/net.minecraft.world.item.h" #include "../../Headers/net.minecraft.world.damagesource.h" #include "../../Headers/net.minecraft.world.item.enchantment.h" +#include "../../Headers/net.minecraft.network.packet.h" +#include "../../../Minecraft.Client/Player/ServerPlayer.h" +#include "../../../Minecraft.Client/Network/PlayerConnection.h" #include "../../Headers/com.mojang.nbt.h" #include "Arrow.h" @@ -44,16 +47,19 @@ void Arrow::_init() { Arrow::Arrow(Level* level) : Entity(level) { _init(); - this->setSize(0.5f, 0.5f); + viewScale = 10; + setSize(0.5f, 0.5f); } -Arrow::Arrow(Level* level, std::shared_ptr mob, - std::shared_ptr target, float power, float uncertainty) +Arrow::Arrow(Level* level, std::shared_ptr mob, + std::shared_ptr target, float power, + float uncertainty) : Entity(level) { _init(); - this->owner = mob; - if (std::dynamic_pointer_cast(mob) != NULL) pickup = PICKUP_ALLOWED; + viewScale = 10; + owner = mob; + if (mob->instanceof(eTYPE_PLAYER)) pickup = PICKUP_ALLOWED; y = mob->y + mob->getHeadHeight() - 0.1f; @@ -78,29 +84,30 @@ Arrow::Arrow(Level* level, std::shared_ptr mob, Arrow::Arrow(Level* level, double x, double y, double z) : Entity(level) { _init(); - this->setSize(0.5f, 0.5f); + viewScale = 10; + setSize(0.5f, 0.5f); - this->setPos(x, y, z); - this->heightOffset = 0; + setPos(x, y, z); + heightOffset = 0; } -Arrow::Arrow(Level* level, std::shared_ptr mob, float power) +Arrow::Arrow(Level* level, std::shared_ptr mob, float power) : Entity(level) { _init(); - this->owner = mob; - if (std::dynamic_pointer_cast(mob) != NULL) pickup = PICKUP_ALLOWED; + viewScale = 10; + owner = mob; + if (mob->instanceof(eTYPE_PLAYER)) pickup = PICKUP_ALLOWED; setSize(0.5f, 0.5f); - this->moveTo(mob->x, mob->y + mob->getHeadHeight(), mob->z, mob->yRot, - mob->xRot); + moveTo(mob->x, mob->y + mob->getHeadHeight(), mob->z, mob->yRot, mob->xRot); x -= Mth::cos(yRot / 180 * PI) * 0.16f; y -= 0.1f; z -= Mth::sin(yRot / 180 * PI) * 0.16f; - this->setPos(x, y, z); - this->heightOffset = 0; + setPos(x, y, z); + heightOffset = 0; xd = -Mth::sin(yRot / 180 * PI) * Mth::cos(xRot / 180 * PI); zd = Mth::cos(yRot / 180 * PI) * Mth::cos(xRot / 180 * PI); @@ -119,9 +126,12 @@ void Arrow::shoot(double xd, double yd, double zd, float pow, yd /= dist; zd /= dist; - xd += (random->nextGaussian()) * 0.0075f * uncertainty; - yd += (random->nextGaussian()) * 0.0075f * uncertainty; - zd += (random->nextGaussian()) * 0.0075f * uncertainty; + xd += (random->nextGaussian() * (random->nextBoolean() ? -1 : 1)) * + 0.0075f * uncertainty; + yd += (random->nextGaussian() * (random->nextBoolean() ? -1 : 1)) * + 0.0075f * uncertainty; + zd += (random->nextGaussian() * (random->nextBoolean() ? -1 : 1)) * + 0.0075f * uncertainty; xd *= pow; yd *= pow; @@ -133,8 +143,8 @@ void Arrow::shoot(double xd, double yd, double zd, float pow, double sd = sqrt(xd * xd + zd * zd); - yRotO = this->yRot = (float)(atan2(xd, zd) * 180 / PI); - xRotO = this->xRot = (float)(atan2(yd, sd) * 180 / PI); + yRotO = yRot = (float)(atan2(xd, zd) * 180 / PI); + xRotO = xRot = (float)(atan2(yd, sd) * 180 / PI); life = 0; } @@ -150,8 +160,8 @@ void Arrow::lerpMotion(double xd, double yd, double zd) { this->zd = zd; if (xRotO == 0 && yRotO == 0) { double sd = sqrt(xd * xd + zd * zd); - yRotO = this->yRot = (float)(atan2(xd, zd) * 180 / PI); - xRotO = this->xRot = (float)(atan2(yd, sd) * 180 / PI); + yRotO = yRot = (float)(atan2(xd, zd) * 180 / PI); + xRotO = xRot = (float)(atan2(yd, sd) * 180 / PI); xRotO = xRot; yRotO = yRot; app.DebugPrintf("%f %f : 0x%x\n", xRot, yRot, &yRot); @@ -165,8 +175,8 @@ void Arrow::tick() { if (xRotO == 0 && yRotO == 0) { double sd = sqrt(xd * xd + zd * zd); - yRotO = this->yRot = (float)(atan2(xd, zd) * 180 / PI); - xRotO = this->xRot = (float)(atan2(yd, sd) * 180 / PI); + yRotO = yRot = (float)(atan2(xd, zd) * 180 / PI); + xRotO = xRot = (float)(atan2(yd, sd) * 180 / PI); } { @@ -243,6 +253,20 @@ void Arrow::tick() { res = new HitResult(hitEntity); } + if ((res != NULL) && (res->entity != NULL) && + res->entity->instanceof(eTYPE_PLAYER)) { + std::shared_ptr player = + std::dynamic_pointer_cast(res->entity); + // 4J: Check for owner being null + if (player->abilities.invulnerable || + ((owner != NULL) && + (owner->instanceof(eTYPE_PLAYER) && + !std::dynamic_pointer_cast(owner)->canHarmPlayer( + player)))) { + res = NULL; + } + } + if (res != NULL) { if (res->entity != NULL) { float pow = Mth::sqrt(xd * xd + yd * yd + zd * zd); @@ -268,14 +292,17 @@ void Arrow::tick() { // unless we can cause some damage (this doesn't necessarily // mean that the arrow hit lowered their health) set targets on // fire first because we want cooked pork/chicken/steak - if (this->isOnFire()) { + if (isOnFire() && res->entity->GetType() != eTYPE_ENDERMAN) { res->entity->setOnFire(5); } - std::shared_ptr mob = - std::dynamic_pointer_cast(res->entity); - if (mob != NULL) { - mob->arrowCount++; + if (res->entity->instanceof(eTYPE_LIVINGENTITY)) { + std::shared_ptr mob = + std::dynamic_pointer_cast(res->entity); + + if (!level->isClientSide) { + mob->setArrowCount(mob->getArrowCount() + 1); + } if (knockback > 0) { float pushLen = sqrt(xd * xd + zd * zd); if (pushLen > 0) { @@ -289,15 +316,23 @@ void Arrow::tick() { ThornsEnchantment::doThornsAfterAttack(owner, mob, random); } + + if (owner != NULL && res->entity != owner && + owner->GetType() == eTYPE_SERVERPLAYER) { + std::dynamic_pointer_cast(owner) + ->connection->send(std::shared_ptr( + new GameEventPacket( + GameEventPacket::SUCCESSFUL_BOW_HIT, 0))); + } } // 4J : WESTY : For award, need to track if creeper was killed // by arrow from the player. - if ((std::dynamic_pointer_cast(owner) != - NULL) && // arrow owner is a player - (res->entity->isAlive() == false) && // target is now dead - (std::dynamic_pointer_cast(res->entity) != - NULL)) // target is a creeper + if (owner != NULL && + owner->instanceof(eTYPE_PLAYER) // arrow owner is a player + && !res->entity->isAlive() // target is now dead + && (res->entity->GetType() == + eTYPE_CREEPER)) // target is a creeper { std::dynamic_pointer_cast(owner)->awardStat( @@ -305,11 +340,9 @@ void Arrow::tick() { GenericStats::param_arrowKillCreeper()); } - // 4J - sound change brought forward from 1.2.3 - level->playSound(shared_from_this(), eSoundType_RANDOM_BOW_HIT, - 1.0f, - 1.2f / (random->nextFloat() * 0.2f + 0.9f)); - remove(); + playSound(eSoundType_RANDOM_BOW_HIT, 1.0f, + 1.2f / (random->nextFloat() * 0.2f + 0.9f)); + if (res->entity->GetType() != eTYPE_ENDERDRAGON) remove(); } else { xd *= -0.1f; yd *= -0.1f; @@ -337,12 +370,16 @@ void Arrow::tick() { z -= (zd / dd) * 0.05f; } - // 4J - sound change brought forward from 1.2.3 - level->playSound(shared_from_this(), eSoundType_RANDOM_BOW_HIT, - 1.0f, 1.2f / (random->nextFloat() * 0.2f + 0.9f)); + playSound(eSoundType_RANDOM_BOW_HIT, 1.0f, + 1.2f / (random->nextFloat() * 0.2f + 0.9f)); inGround = true; shakeTime = 7; setCritArrow(false); + + if (lastTile != 0) { + Tile::tiles[lastTile]->entityInside(level, xTile, yTile, zTile, + shared_from_this()); + } } } delete res; @@ -441,14 +478,16 @@ void Arrow::playerTouch(std::shared_ptr player) { } if (bRemove) { - level->playSound( - shared_from_this(), eSoundType_RANDOM_POP, 0.2f, + playSound( + eSoundType_RANDOM_POP, 0.2f, ((random->nextFloat() - random->nextFloat()) * 0.7f + 1.0f) * 2.0f); player->take(shared_from_this(), 1); remove(); } } +bool Arrow::makeStepSound() { return false; } + float Arrow::getShadowHeightOffs() { return 0; } void Arrow::setBaseDamage(double baseDamage) { this->baseDamage = baseDamage; } diff --git a/Minecraft.World/Entities/Mobs/Arrow.h b/Minecraft.World/Entities/Mobs/Arrow.h index cf85a4ac2..919df0c04 100644 --- a/Minecraft.World/Entities/Mobs/Arrow.h +++ b/Minecraft.World/Entities/Mobs/Arrow.h @@ -1,11 +1,12 @@ #pragma once #include "../Entity.h" +#include "../Projectile.h" class Level; class CompoundTag; -class Arrow : public Entity { +class Arrow : public Entity, public Projectile { public: eINSTANCEOF GetType() { return eTYPE_ARROW; } static Entity* create(Level* level) { return new Arrow(level); } @@ -50,10 +51,10 @@ private: public: Arrow(Level* level); - Arrow(Level* level, std::shared_ptr mob, std::shared_ptr target, - float power, float uncertainty); + Arrow(Level* level, std::shared_ptr mob, + std::shared_ptr target, float power, float uncertainty); Arrow(Level* level, double x, double y, double z); - Arrow(Level* level, std::shared_ptr mob, float power); + Arrow(Level* level, std::shared_ptr mob, float power); protected: virtual void defineSynchedData(); @@ -67,6 +68,11 @@ public: virtual void addAdditonalSaveData(CompoundTag* tag); virtual void readAdditionalSaveData(CompoundTag* tag); virtual void playerTouch(std::shared_ptr player); + +protected: + virtual bool makeStepSound(); + +public: virtual float getShadowHeightOffs(); void setBaseDamage(double baseDamage); diff --git a/Minecraft.World/Entities/Mobs/Bat.cpp b/Minecraft.World/Entities/Mobs/Bat.cpp new file mode 100644 index 000000000..43c180678 --- /dev/null +++ b/Minecraft.World/Entities/Mobs/Bat.cpp @@ -0,0 +1,208 @@ +#include "../../Platform/stdafx.h" +#include "../../Headers/net.minecraft.world.entity.h" +#include "../../Headers/net.minecraft.world.entity.ai.attributes.h" +#include "../../Headers/net.minecraft.world.entity.monster.h" +#include "../../Headers/net.minecraft.world.level.h" +#include "../../Headers/net.minecraft.world.level.tile.h" +#include "../../Headers/net.minecraft.world.phys.h" +#include "Bat.h" + +Bat::Bat(Level* level) : AmbientCreature(level) { + // 4J Stu - This function call had to be moved here from the Entity ctor to + // ensure that the derived version of the function is called + this->defineSynchedData(); + registerAttributes(); + setHealth(getMaxHealth()); + + targetPosition = NULL; + + setSize(.5f, .9f); + setResting(true); +} + +void Bat::defineSynchedData() { + AmbientCreature::defineSynchedData(); + + entityData->define(DATA_ID_FLAGS, (char)0); +} + +float Bat::getSoundVolume() { return 0.1f; } + +float Bat::getVoicePitch() { return AmbientCreature::getVoicePitch() * .95f; } + +int Bat::getAmbientSound() { + if (isResting() && random->nextInt(4) != 0) { + return -1; + } + return eSoundType_MOB_BAT_IDLE; //"mob.bat.idle"; +} + +int Bat::getHurtSound() { + return eSoundType_MOB_BAT_HURT; //"mob.bat.hurt"; +} + +int Bat::getDeathSound() { + return eSoundType_MOB_BAT_DEATH; //"mob.bat.death"; +} + +bool Bat::isPushable() { + // bats can't be pushed by other mobs + return false; +} + +void Bat::doPush(std::shared_ptr e) { + // bats don't push other mobs +} + +void Bat::pushEntities() { + // bats don't push other mobs +} + +void Bat::registerAttributes() { + AmbientCreature::registerAttributes(); + + getAttribute(SharedMonsterAttributes::MAX_HEALTH)->setBaseValue(6); +} + +bool Bat::isResting() { + return (entityData->getByte(DATA_ID_FLAGS) & FLAG_RESTING) != 0; +} + +void Bat::setResting(bool value) { + char current = entityData->getByte(DATA_ID_FLAGS); + if (value) { + entityData->set(DATA_ID_FLAGS, (char)(current | FLAG_RESTING)); + } else { + entityData->set(DATA_ID_FLAGS, (char)(current & ~FLAG_RESTING)); + } +} + +bool Bat::useNewAi() { return true; } + +void Bat::tick() { + AmbientCreature::tick(); + + if (isResting()) { + xd = yd = zd = 0; + y = Mth::floor(y) + 1.0 - bbHeight; + } else { + yd *= .6f; + } +} + +inline int signum(double x) { return (x > 0) - (x < 0); } + +void Bat::newServerAiStep() { + AmbientCreature::newServerAiStep(); + + if (isResting()) { + if (!level->isSolidBlockingTile(Mth::floor(x), (int)y + 1, + Mth::floor(z))) { + setResting(false); + level->levelEvent(nullptr, LevelEvent::SOUND_BAT_LIFTOFF, (int)x, + (int)y, (int)z, 0); + } else { + if (random->nextInt(200) == 0) { + yHeadRot = random->nextInt(360); + } + + if (level->getNearestPlayer(shared_from_this(), 4.0f) != NULL) { + setResting(false); + level->levelEvent(nullptr, LevelEvent::SOUND_BAT_LIFTOFF, + (int)x, (int)y, (int)z, 0); + } + } + } else { + if (targetPosition != NULL && + (!level->isEmptyTile(targetPosition->x, targetPosition->y, + targetPosition->z) || + targetPosition->y < 1)) { + delete targetPosition; + targetPosition = NULL; + } + if (targetPosition == NULL || random->nextInt(30) == 0 || + targetPosition->distSqr((int)x, (int)y, (int)z) < 4) { + delete targetPosition; + targetPosition = + new Pos((int)x + random->nextInt(7) - random->nextInt(7), + (int)y + random->nextInt(6) - 2, + (int)z + random->nextInt(7) - random->nextInt(7)); + } + + double dx = (targetPosition->x + .5) - x; + double dy = (targetPosition->y + .1) - y; + double dz = (targetPosition->z + .5) - z; + + xd = xd + (signum(dx) * .5f - xd) * .1f; + yd = yd + (signum(dy) * .7f - yd) * .1f; + zd = zd + (signum(dz) * .5f - zd) * .1f; + + float yRotD = (float)(atan2(zd, xd) * 180 / PI) - 90; + float rotDiff = Mth::wrapDegrees(yRotD - yRot); + yya = .5f; + yRot += rotDiff; + + if (random->nextInt(100) == 0 && + level->isSolidBlockingTile(Mth::floor(x), (int)y + 1, + Mth::floor(z))) { + setResting(true); + } + } +} + +bool Bat::makeStepSound() { return false; } + +void Bat::causeFallDamage(float distance) {} + +void Bat::checkFallDamage(double ya, bool onGround) { + // this method is empty because flying creatures should + // not trigger the "fallOn" tile calls (such as trampling crops) +} + +bool Bat::isIgnoringTileTriggers() { return true; } + +bool Bat::hurt(DamageSource* source, float dmg) { + if (isInvulnerable()) return false; + if (!level->isClientSide) { + if (isResting()) { + setResting(false); + } + } + + return AmbientCreature::hurt(source, dmg); +} + +void Bat::readAdditionalSaveData(CompoundTag* tag) { + AmbientCreature::readAdditionalSaveData(tag); + + entityData->set(DATA_ID_FLAGS, tag->getByte(L"BatFlags")); +} + +void Bat::addAdditonalSaveData(CompoundTag* entityTag) { + AmbientCreature::addAdditonalSaveData(entityTag); + + entityTag->putByte(L"BatFlags", entityData->getByte(DATA_ID_FLAGS)); +} + +bool Bat::canSpawn() { + int yt = Mth::floor(bb->y0); + if (yt >= level->seaLevel) return false; + + int xt = Mth::floor(x); + int zt = Mth::floor(z); + + int br = level->getRawBrightness(xt, yt, zt); + int maxLight = 4; + + if ((Calendar::GetDayOfMonth() + 1 == 10 && + Calendar::GetDayOfMonth() >= 20) || + (Calendar::GetMonth() + 1 == 11 && Calendar::GetMonth() <= 3)) { + maxLight = 7; + } else if (random->nextBoolean()) { + return false; + } + + if (br > random->nextInt(maxLight)) return false; + + return AmbientCreature::canSpawn(); +} \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/Bat.h b/Minecraft.World/Entities/Mobs/Bat.h new file mode 100644 index 000000000..a7eedff8f --- /dev/null +++ b/Minecraft.World/Entities/Mobs/Bat.h @@ -0,0 +1,57 @@ +#pragma once + +#include "AmbientCreature.h" + +class Bat : public AmbientCreature { +public: + eINSTANCEOF GetType() { return eTYPE_BAT; } + static Entity* create(Level* level) { return new Bat(level); } + +private: + static const int DATA_ID_FLAGS = 16; + static const int FLAG_RESTING = 1; + + Pos* targetPosition; + +public: + Bat(Level* level); + +protected: + virtual void defineSynchedData(); + virtual float getSoundVolume(); + virtual float getVoicePitch(); + virtual int getAmbientSound(); + virtual int getHurtSound(); + virtual int getDeathSound(); + +public: + virtual bool isPushable(); + +protected: + virtual void doPush(std::shared_ptr e); + virtual void pushEntities(); + virtual void registerAttributes(); + +public: + virtual bool isResting(); + virtual void setResting(bool value); + +protected: + virtual bool useNewAi(); + +public: + virtual void tick(); + +protected: + virtual void newServerAiStep(); + virtual bool makeStepSound(); + virtual void causeFallDamage(float distance); + virtual void checkFallDamage(double ya, bool onGround); + virtual bool isIgnoringTileTriggers(); + +public: + virtual bool hurt(DamageSource* source, float dmg); + virtual void readAdditionalSaveData(CompoundTag* tag); + virtual void addAdditonalSaveData(CompoundTag* entityTag); + virtual bool canSpawn(); +}; \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/Blaze.cpp b/Minecraft.World/Entities/Mobs/Blaze.cpp index 24f479585..2ac264dbd 100644 --- a/Minecraft.World/Entities/Mobs/Blaze.cpp +++ b/Minecraft.World/Entities/Mobs/Blaze.cpp @@ -5,6 +5,8 @@ #include "../../Headers/net.minecraft.world.phys.h" #include "../../Headers/net.minecraft.world.item.h" #include "../../Headers/net.minecraft.world.entity.h" +#include "../../Headers/net.minecraft.world.entity.ai.attributes.h" +#include "../../Headers/net.minecraft.world.entity.monster.h" #include "../../Headers/net.minecraft.world.entity.projectile.h" #include "../../Util/SharedConstants.h" #include "../../../Minecraft.Client/Textures/Textures.h" @@ -15,17 +17,11 @@ Blaze::Blaze(Level* level) : Monster(level) { // 4J Stu - This function call had to be moved here from the Entity ctor to // ensure that the derived version of the function is called this->defineSynchedData(); - - // 4J Stu - This function call had to be moved here from the Entity ctor to - // ensure that the derived version of the function is called - health = getMaxHealth(); - - this->textureIdx = TN_MOB_BLAZE; // 4J Was "/mob/fire.png"; + registerAttributes(); + setHealth(getMaxHealth()); fireImmune = true; - attackDamage = 6; xpReward = XP_REWARD_LARGE; - // this.setSize(1.2f, 1.8f); // 4J Default inits allowedHeightOffset = 0.5f; @@ -33,7 +29,10 @@ Blaze::Blaze(Level* level) : Monster(level) { attackCounter = 0; } -int Blaze::getMaxHealth() { return 20; } +void Blaze::registerAttributes() { + Monster::registerAttributes(); + getAttribute(SharedMonsterAttributes::ATTACK_DAMAGE)->setBaseValue(6); +} void Blaze::defineSynchedData() { Monster::defineSynchedData(); @@ -67,7 +66,7 @@ void Blaze::aiStep() { if (getAttackTarget() != NULL && (getAttackTarget()->y + getAttackTarget()->getHeadHeight()) > - (this->y + getHeadHeight() + allowedHeightOffset)) { + (y + getHeadHeight() + allowedHeightOffset)) { yd = yd + (.3f - yd) * .3f; } } diff --git a/Minecraft.World/Entities/Mobs/Blaze.h b/Minecraft.World/Entities/Mobs/Blaze.h index 3770bd87e..54c6b11cf 100644 --- a/Minecraft.World/Entities/Mobs/Blaze.h +++ b/Minecraft.World/Entities/Mobs/Blaze.h @@ -17,9 +17,9 @@ private: public: Blaze(Level* level); - virtual int getMaxHealth(); protected: + virtual void registerAttributes(); virtual void defineSynchedData(); virtual int getAmbientSound(); virtual int getHurtSound(); diff --git a/Minecraft.World/Entities/Mobs/Boat.cpp b/Minecraft.World/Entities/Mobs/Boat.cpp index 127e88e3e..31ac18a8e 100644 --- a/Minecraft.World/Entities/Mobs/Boat.cpp +++ b/Minecraft.World/Entities/Mobs/Boat.cpp @@ -40,7 +40,7 @@ bool Boat::makeStepSound() { return false; } void Boat::defineSynchedData() { entityData->define(DATA_ID_HURT, 0); entityData->define(DATA_ID_HURTDIR, 1); - entityData->define(DATA_ID_DAMAGE, 0); + entityData->define(DATA_ID_DAMAGE, 0.0f); } AABB* Boat::getCollideAgainstBox(std::shared_ptr entity) { @@ -66,7 +66,8 @@ Boat::Boat(Level* level, double x, double y, double z) : Entity(level) { double Boat::getRideHeight() { return bbHeight * 0.0f - 0.3f; } -bool Boat::hurt(DamageSource* source, int hurtDamage) { +bool Boat::hurt(DamageSource* source, float hurtDamage) { + if (isInvulnerable()) return false; if (level->isClientSide || removed) return true; // 4J-JEV: Fix for #88212, @@ -74,10 +75,11 @@ bool Boat::hurt(DamageSource* source, int hurtDamage) { if (dynamic_cast(source) != NULL) { std::shared_ptr attacker = source->getDirectEntity(); - if (std::dynamic_pointer_cast(attacker) != NULL && + if (attacker->instanceof(eTYPE_PLAYER) && !std::dynamic_pointer_cast(attacker)->isAllowedToHurtEntity( - shared_from_this())) + shared_from_this())) { return false; + } } setHurtDir(-getHurtDir()); @@ -95,14 +97,16 @@ bool Boat::hurt(DamageSource* source, int hurtDamage) { // 4J Stu - Brought froward from 12w36 to fix #46611 - TU5: Gameplay: // Minecarts and boat requires more hits than one to be destroyed in - // creative mode - std::shared_ptr player = - std::dynamic_pointer_cast(source->getEntity()); - if (player != NULL && player->abilities.instabuild) setDamage(100); + // creative mode 4J-PB - Fix for XB1 #175735 - [CRASH] [Multi-Plat]: Code: + // Gameplay: Placing a boat on harmful surfaces causes the game to crash + bool creativePlayer = (source->getEntity() != NULL) && + source->getEntity()->instanceof(eTYPE_PLAYER) && + std::dynamic_pointer_cast(source->getEntity()) + ->abilities.instabuild; - if (getDamage() > 20 * 2) { + if (creativePlayer || getDamage() > 20 * 2) { if (rider.lock() != NULL) rider.lock()->ride(shared_from_this()); - spawnAtLocation(Item::boat_Id, 1, 0); + if (!creativePlayer) spawnAtLocation(Item::boat_Id, 1, 0); remove(); } return true; @@ -139,9 +143,9 @@ void Boat::lerpTo(double x, double y, double z, float yRot, float xRot, lyr = yRot; lxr = xRot; - this->xd = lxd; - this->yd = lyd; - this->zd = lzd; + xd = lxd; + yd = lyd; + zd = lzd; } void Boat::lerpMotion(double xd, double yd, double zd) { @@ -204,8 +208,8 @@ void Boat::tick() { xRot += (float)((lxr - xRot) / lSteps); lSteps--; - this->setPos(xt, yt, zt); - this->setRot(yRot, xRot); + setPos(xt, yt, zt); + setRot(yRot, xRot); } else { #if 1 // Original @@ -216,7 +220,7 @@ void Boat::tick() { // 4J Stu - Fix for various boat bugs, ensure that we check // collision on client-side movement - this->move(xd, yd, zd); + move(xd, yd, zd); if (onGround) { xd *= 0.5f; @@ -269,9 +273,17 @@ void Boat::tick() { yd += 0.007f; } - if (rider.lock() != NULL) { - xd += rider.lock()->xd * acceleration; - zd += rider.lock()->zd * acceleration; + if (rider.lock() != NULL && rider.lock()->instanceof(eTYPE_LIVINGENTITY)) { + std::shared_ptr livingRider = + std::dynamic_pointer_cast(rider.lock()); + double std::forward = livingRider->yya; + + if (std::forward > 0) { + double riderXd = -sin(livingRider->yRot * PI / 180); + double riderZd = cos(livingRider->yRot * PI / 180); + xd += riderXd * acceleration * 0.05f; + zd += riderZd * acceleration * 0.05f; + } } double curSpeed = sqrt(xd * xd + zd * zd); @@ -300,7 +312,7 @@ void Boat::tick() { move(xd, yd, zd); if ((horizontalCollision && lastSpeed > 0.20)) { - if (!level->isClientSide) { + if (!level->isClientSide && !removed) { remove(); for (int i = 0; i < 3; i++) { spawnAtLocation(Tile::wood_Id, 1, 0); @@ -334,7 +346,7 @@ void Boat::tick() { if (level->isClientSide) return; std::vector >* entities = - level->getEntities(shared_from_this(), this->bb->grow(0.2f, 0, 0.2f)); + level->getEntities(shared_from_this(), bb->grow(0.2f, 0, 0.2f)); if (entities != NULL && !entities->empty()) { AUTO_VAR(itEnd, entities->end()); for (AUTO_VAR(it, entities->begin()); it != itEnd; it++) { @@ -353,14 +365,11 @@ void Boat::tick() { for (int j = 0; j < 2; j++) { int yy = Mth::floor(y) + j; int tile = level->getTile(xx, yy, zz); - int data = level->getData(xx, yy, zz); if (tile == Tile::topSnow_Id) { - level->setTile(xx, yy, zz, 0); + level->removeTile(xx, yy, zz); } else if (tile == Tile::waterLily_Id) { - Tile::waterLily->spawnResources(level, xx, yy, zz, data, 0.3f, - 0); - level->setTile(xx, yy, zz, 0); + level->destroyTile(xx, yy, zz, true); } } } @@ -388,9 +397,8 @@ float Boat::getShadowHeightOffs() { return 0; } std::wstring Boat::getName() { return L"Boat"; } bool Boat::interact(std::shared_ptr player) { - if (rider.lock() != NULL && - std::dynamic_pointer_cast(rider.lock()) != NULL && - rider.lock() != player) + if ((rider.lock() != NULL) && rider.lock()->instanceof(eTYPE_PLAYER) && + (rider.lock() != player)) return true; if (!level->isClientSide) { // 4J HEG - Fixed issue with player not being able to dismount boat @@ -400,9 +408,9 @@ bool Boat::interact(std::shared_ptr player) { return true; } -void Boat::setDamage(int damage) { entityData->set(DATA_ID_DAMAGE, damage); } +void Boat::setDamage(float damage) { entityData->set(DATA_ID_DAMAGE, damage); } -int Boat::getDamage() { return entityData->getInteger(DATA_ID_DAMAGE); } +float Boat::getDamage() { return entityData->getFloat(DATA_ID_DAMAGE); } void Boat::setHurtTime(int hurtTime) { entityData->set(DATA_ID_HURT, hurtTime); diff --git a/Minecraft.World/Entities/Mobs/Boat.h b/Minecraft.World/Entities/Mobs/Boat.h index 14f61751d..0a6ca3b5e 100644 --- a/Minecraft.World/Entities/Mobs/Boat.h +++ b/Minecraft.World/Entities/Mobs/Boat.h @@ -46,7 +46,7 @@ public: Boat(Level* level, double x, double y, double z); virtual double getRideHeight(); - virtual bool hurt(DamageSource* source, int damage); + virtual bool hurt(DamageSource* source, float damage); virtual void animateHurt(); virtual bool isPickable(); @@ -71,8 +71,8 @@ public: std::wstring getName(); virtual bool interact(std::shared_ptr player); - virtual void setDamage(int damage); - virtual int getDamage(); + virtual void setDamage(float damage); + virtual float getDamage(); virtual void setHurtTime(int hurtTime); virtual int getHurtTime(); virtual void setHurtDir(int hurtDir); diff --git a/Minecraft.World/Entities/Mobs/BossMob.cpp b/Minecraft.World/Entities/Mobs/BossMob.cpp deleted file mode 100644 index a93129cf3..000000000 --- a/Minecraft.World/Entities/Mobs/BossMob.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include "../../Platform/stdafx.h" -#include "../BossMobPart.h" -#include "BossMob.h" - -BossMob::BossMob(Level* level) : Mob(level) { - maxHealth = 100; - - // 4J Stu - This function call had to be moved here from the Entity ctor to - // ensure that the derived version of the function is called - health = getMaxHealth(); -} - -int BossMob::getMaxHealth() { return maxHealth; } - -bool BossMob::hurt(std::shared_ptr bossMobPart, - DamageSource* source, int damage) { - return hurt(source, damage); -} - -bool BossMob::hurt(DamageSource* source, int damage) { return false; } - -bool BossMob::reallyHurt(DamageSource* source, int damage) { - return Mob::hurt(source, damage); -} \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/BossMob.h b/Minecraft.World/Entities/Mobs/BossMob.h index b53d92021..c7ee39293 100644 --- a/Minecraft.World/Entities/Mobs/BossMob.h +++ b/Minecraft.World/Entities/Mobs/BossMob.h @@ -1,22 +1,8 @@ #pragma once -#include "../Mob.h" - -class Level; -class BossMobPart; - -class BossMob : public Mob { -protected: - int maxHealth; - +class BossMob { public: - BossMob(Level* level); - - virtual int getMaxHealth(); - virtual bool hurt(std::shared_ptr bossMobPart, - DamageSource* source, int damage); - virtual bool hurt(DamageSource* source, int damage); - -protected: - virtual bool reallyHurt(DamageSource* source, int damage); + virtual float getMaxHealth() = 0; + virtual float getHealth() = 0; + virtual std::wstring getAName() = 0; }; \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/CaveSpider.cpp b/Minecraft.World/Entities/Mobs/CaveSpider.cpp index 68d87fb52..3e3a9ebfa 100644 --- a/Minecraft.World/Entities/Mobs/CaveSpider.cpp +++ b/Minecraft.World/Entities/Mobs/CaveSpider.cpp @@ -1,5 +1,7 @@ #include "../../Platform/stdafx.h" #include "../../Util/SharedConstants.h" +#include "../../Headers/net.minecraft.world.entity.ai.attributes.h" +#include "../../Headers/net.minecraft.world.entity.monster.h" #include "../../Headers/net.minecraft.world.effect.h" #include "../../Headers/net.minecraft.world.level.h" #include "../../Headers/net.minecraft.world.h" @@ -9,19 +11,20 @@ CaveSpider::CaveSpider(Level* level) : Spider(level) { // 4J Stu - This function call had to be moved here from the Entity ctor to // ensure that the derived version of the function is called - health = getMaxHealth(); + registerAttributes(); - this->textureIdx = TN_MOB_CAVE_SPIDER; // 4J was "/mob/cavespider.png"; this->setSize(0.7f, 0.5f); } -int CaveSpider::getMaxHealth() { return 12; } +void CaveSpider::registerAttributes() { + Spider::registerAttributes(); -float CaveSpider::getModelScale() { return .7f; } + getAttribute(SharedMonsterAttributes::MAX_HEALTH)->setBaseValue(12); +} bool CaveSpider::doHurtTarget(std::shared_ptr target) { if (Spider::doHurtTarget(target)) { - if (std::dynamic_pointer_cast(target) != NULL) { + if (target->instanceof(eTYPE_LIVINGENTITY)) { int poisonTime = 0; if (level->difficulty <= Difficulty::EASY) { // No poison! @@ -32,7 +35,7 @@ bool CaveSpider::doHurtTarget(std::shared_ptr target) { } if (poisonTime > 0) { - std::dynamic_pointer_cast(target)->addEffect( + std::dynamic_pointer_cast(target)->addEffect( new MobEffectInstance( MobEffect::poison->id, poisonTime * SharedConstants::TICKS_PER_SECOND, 0)); @@ -44,6 +47,9 @@ bool CaveSpider::doHurtTarget(std::shared_ptr target) { return false; } -void CaveSpider::finalizeMobSpawn() { +MobGroupData* CaveSpider::finalizeMobSpawn( + MobGroupData* groupData, int extraData /*= 0*/) // 4J Added extraData param +{ // do nothing + return groupData; } \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/CaveSpider.h b/Minecraft.World/Entities/Mobs/CaveSpider.h index 9bda83e3c..9df4510d7 100644 --- a/Minecraft.World/Entities/Mobs/CaveSpider.h +++ b/Minecraft.World/Entities/Mobs/CaveSpider.h @@ -10,8 +10,12 @@ public: public: CaveSpider(Level* level); - virtual int getMaxHealth(); - virtual float getModelScale(); +protected: + void registerAttributes(); + +public: virtual bool doHurtTarget(std::shared_ptr target); - void finalizeMobSpawn(); + MobGroupData* finalizeMobSpawn( + MobGroupData* groupData, + int extraData = 0); // 4J Added extraData param }; \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/Chicken.cpp b/Minecraft.World/Entities/Mobs/Chicken.cpp index be8cf5c60..dc8449eb7 100644 --- a/Minecraft.World/Entities/Mobs/Chicken.cpp +++ b/Minecraft.World/Entities/Mobs/Chicken.cpp @@ -1,6 +1,8 @@ #include "../../Platform/stdafx.h" #include "../../Headers/com.mojang.nbt.h" +#include "../../Headers/net.minecraft.world.entity.ai.attributes.h" #include "../../Headers/net.minecraft.world.entity.ai.goal.h" +#include "../../Headers/net.minecraft.world.entity.monster.h" #include "../../Headers/net.minecraft.world.level.tile.h" #include "../../Headers/net.minecraft.world.phys.h" #include "../../Headers/net.minecraft.world.level.h" @@ -23,31 +25,32 @@ Chicken::Chicken(Level* level) : Animal(level) { // 4J Stu - This function call had to be moved here from the Entity ctor to // ensure that the derived version of the function is called this->defineSynchedData(); - - // 4J Stu - This function call had to be moved here from the Entity ctor to - // ensure that the derived version of the function is called - health = getMaxHealth(); + registerAttributes(); + setHealth(getMaxHealth()); _init(); - this->textureIdx = TN_MOB_CHICKEN; // 4J - was L"/mob/chicken.png"; - this->setSize(0.3f, 0.7f); // 4J Changed from 0.4 to 0.7 in 1.8.2 + setSize(0.3f, 0.7f); // 4J Changed from 0.4 to 0.7 in 1.8.2 eggTime = random->nextInt(20 * 60 * 5) + 20 * 60 * 5; - float walkSpeed = 0.25f; goalSelector.addGoal(0, new FloatGoal(this)); - goalSelector.addGoal(1, new PanicGoal(this, 0.38f)); - goalSelector.addGoal(2, new BreedGoal(this, walkSpeed)); - goalSelector.addGoal( - 3, new TemptGoal(this, 0.25f, Item::seeds_wheat_Id, false)); - goalSelector.addGoal(4, new FollowParentGoal(this, 0.28f)); - goalSelector.addGoal(5, new RandomStrollGoal(this, walkSpeed)); + goalSelector.addGoal(1, new PanicGoal(this, 1.4)); + goalSelector.addGoal(2, new BreedGoal(this, 1.0)); + goalSelector.addGoal(3, + new TemptGoal(this, 1.0, Item::seeds_wheat_Id, false)); + goalSelector.addGoal(4, new FollowParentGoal(this, 1.1)); + goalSelector.addGoal(5, new RandomStrollGoal(this, 1.0)); goalSelector.addGoal(6, new LookAtPlayerGoal(this, typeid(Player), 6)); goalSelector.addGoal(7, new RandomLookAroundGoal(this)); } bool Chicken::useNewAi() { return true; } -int Chicken::getMaxHealth() { return 4; } +void Chicken::registerAttributes() { + Animal::registerAttributes(); + + getAttribute(SharedMonsterAttributes::MAX_HEALTH)->setBaseValue(4); + getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED)->setBaseValue(0.25f); +} void Chicken::aiStep() { Animal::aiStep(); @@ -70,8 +73,8 @@ void Chicken::aiStep() { if (!isBaby()) { if (!level->isClientSide && --eggTime <= 0) { - level->playSound( - shared_from_this(), eSoundType_MOB_CHICKENPLOP, 1.0f, + playSound( + eSoundType_MOB_CHICKENPLOP, 1.0f, (random->nextFloat() - random->nextFloat()) * 0.2f + 1.0f); spawnAtLocation(Item::egg->id, 1); eggTime = random->nextInt(20 * 60 * 5) + 20 * 60 * 5; @@ -87,6 +90,10 @@ int Chicken::getHurtSound() { return eSoundType_MOB_CHICKEN_HURT; } int Chicken::getDeathSound() { return eSoundType_MOB_CHICKEN_HURT; } +void Chicken::playStepSound(int xt, int yt, int zt, int t) { + playSound(eSoundType_MOB_CHICKEN_STEP, 0.15f, 1); +} + int Chicken::getDeathLoot() { return Item::feather->id; } void Chicken::dropDeathLoot(bool wasKilledByPlayer, int playerBonusLevel) { @@ -115,7 +122,7 @@ std::shared_ptr Chicken::getBreedOffspring( bool Chicken::isFood(std::shared_ptr itemInstance) { return (itemInstance->id == Item::seeds_wheat_Id) || - (itemInstance->id == Item::netherStalkSeeds_Id) || + (itemInstance->id == Item::netherwart_seeds_Id) || (itemInstance->id == Item::seeds_melon_Id) || (itemInstance->id == Item::seeds_pumpkin_Id); } diff --git a/Minecraft.World/Entities/Mobs/Chicken.h b/Minecraft.World/Entities/Mobs/Chicken.h index 83b8a44f9..cf97c207c 100644 --- a/Minecraft.World/Entities/Mobs/Chicken.h +++ b/Minecraft.World/Entities/Mobs/Chicken.h @@ -22,7 +22,11 @@ private: public: Chicken(Level* level); virtual bool useNewAi(); - virtual int getMaxHealth(); + +protected: + void registerAttributes(); + +public: virtual void aiStep(); protected: @@ -30,6 +34,7 @@ protected: virtual int getAmbientSound(); virtual int getHurtSound(); virtual int getDeathSound(); + virtual void playStepSound(int xt, int yt, int zt, int t); virtual int getDeathLoot(); virtual void dropDeathLoot(bool wasKilledByPlayer, int playerBonusLevel); diff --git a/Minecraft.World/Entities/Mobs/Cow.cpp b/Minecraft.World/Entities/Mobs/Cow.cpp index b759cb3fd..217570612 100644 --- a/Minecraft.World/Entities/Mobs/Cow.cpp +++ b/Minecraft.World/Entities/Mobs/Cow.cpp @@ -1,5 +1,6 @@ #include "../../Platform/stdafx.h" #include "../../Headers/com.mojang.nbt.h" +#include "../../Headers/net.minecraft.world.entity.ai.attributes.h" #include "../../Headers/net.minecraft.world.entity.ai.navigation.h" #include "../../Headers/net.minecraft.world.entity.ai.goal.h" #include "../../Headers/net.minecraft.world.level.tile.h" @@ -7,6 +8,7 @@ #include "../../Headers/net.minecraft.world.level.h" #include "../../Headers/net.minecraft.world.item.h" #include "../../Headers/net.minecraft.world.entity.player.h" +#include "../../Headers/net.minecraft.world.entity.monster.h" #include "../../Headers/net.minecraft.stats.h" #include "Cow.h" #include "../../../Minecraft.Client/Textures/Textures.h" @@ -16,28 +18,30 @@ Cow::Cow(Level* level) : Animal(level) { // 4J Stu - This function call had to be moved here from the Entity ctor to // ensure that the derived version of the function is called this->defineSynchedData(); + registerAttributes(); + setHealth(getMaxHealth()); - // 4J Stu - This function call had to be moved here from the Entity ctor to - // ensure that the derived version of the function is called - health = getMaxHealth(); - - this->textureIdx = TN_MOB_COW; // 4J was L"/mob/cow.png"; this->setSize(0.9f, 1.3f); getNavigation()->setAvoidWater(true); goalSelector.addGoal(0, new FloatGoal(this)); - goalSelector.addGoal(1, new PanicGoal(this, 0.38f)); - goalSelector.addGoal(2, new BreedGoal(this, 0.2f)); - goalSelector.addGoal(3, new TemptGoal(this, 0.25f, Item::wheat_Id, false)); - goalSelector.addGoal(4, new FollowParentGoal(this, 0.25f)); - goalSelector.addGoal(5, new RandomStrollGoal(this, 0.2f)); + goalSelector.addGoal(1, new PanicGoal(this, 2.0f)); + goalSelector.addGoal(2, new BreedGoal(this, 1.0f)); + goalSelector.addGoal(3, new TemptGoal(this, 1.25f, Item::wheat_Id, false)); + goalSelector.addGoal(4, new FollowParentGoal(this, 1.25f)); + goalSelector.addGoal(5, new RandomStrollGoal(this, 1.0f)); goalSelector.addGoal(6, new LookAtPlayerGoal(this, typeid(Player), 6)); goalSelector.addGoal(7, new RandomLookAroundGoal(this)); } bool Cow::useNewAi() { return true; } -int Cow::getMaxHealth() { return 10; } +void Cow::registerAttributes() { + Animal::registerAttributes(); + + getAttribute(SharedMonsterAttributes::MAX_HEALTH)->setBaseValue(10); + getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED)->setBaseValue(0.2f); +} int Cow::getAmbientSound() { return eSoundType_MOB_COW_AMBIENT; } @@ -45,6 +49,10 @@ int Cow::getHurtSound() { return eSoundType_MOB_COW_HURT; } int Cow::getDeathSound() { return eSoundType_MOB_COW_HURT; } +void Cow::playStepSound(int xt, int yt, int zt, int t) { + playSound(eSoundType_MOB_COW_STEP, 0.15f, 1); +} + float Cow::getSoundVolume() { return 0.4f; } int Cow::getDeathLoot() { return Item::leather->id; } @@ -66,25 +74,27 @@ void Cow::dropDeathLoot(bool wasKilledByPlayer, int playerBonusLevel) { } } -bool Cow::interact(std::shared_ptr player) { +bool Cow::mobInteract(std::shared_ptr player) { std::shared_ptr item = player->inventory->getSelected(); - if (item != NULL && item->id == Item::bucket_empty->id) { + if (item != NULL && item->id == Item::bucket_empty->id && + !player->abilities.instabuild) { player->awardStat(GenericStats::cowsMilked(), GenericStats::param_cowsMilked()); - if (--item->count <= 0) { + if (item->count-- == 0) { player->inventory->setItem( player->inventory->selected, - std::shared_ptr(new ItemInstance(Item::milk))); + std::shared_ptr( + new ItemInstance(Item::bucket_milk))); } else if (!player->inventory->add(std::shared_ptr( - new ItemInstance(Item::milk)))) { - player->drop( - std::shared_ptr(new ItemInstance(Item::milk))); + new ItemInstance(Item::bucket_milk)))) { + player->drop(std::shared_ptr( + new ItemInstance(Item::bucket_milk))); } return true; } - return Animal::interact(player); + return Animal::mobInteract(player); } std::shared_ptr Cow::getBreedOffspring( diff --git a/Minecraft.World/Entities/Mobs/Cow.h b/Minecraft.World/Entities/Mobs/Cow.h index f7e1f4b42..55a3a3806 100644 --- a/Minecraft.World/Entities/Mobs/Cow.h +++ b/Minecraft.World/Entities/Mobs/Cow.h @@ -13,18 +13,19 @@ public: public: Cow(Level* level); virtual bool useNewAi(); - virtual int getMaxHealth(); protected: + virtual void registerAttributes(); virtual int getAmbientSound(); virtual int getHurtSound(); virtual int getDeathSound(); virtual float getSoundVolume(); virtual int getDeathLoot(); + virtual void playStepSound(int xt, int yt, int zt, int t); virtual void dropDeathLoot(bool wasKilledByPlayer, int playerBonusLevel); public: - virtual bool interact(std::shared_ptr player); + virtual bool mobInteract(std::shared_ptr player); virtual std::shared_ptr getBreedOffspring( std::shared_ptr target); }; diff --git a/Minecraft.World/Entities/Mobs/Creeper.cpp b/Minecraft.World/Entities/Mobs/Creeper.cpp index 9cda242ab..404ae4a41 100644 --- a/Minecraft.World/Entities/Mobs/Creeper.cpp +++ b/Minecraft.World/Entities/Mobs/Creeper.cpp @@ -3,11 +3,13 @@ #include "../../Headers/net.minecraft.world.level.tile.h" #include "../../Headers/net.minecraft.world.item.h" #include "../../Headers/net.minecraft.world.entity.h" +#include "../../Headers/net.minecraft.world.entity.ai.attributes.h" #include "../../Headers/net.minecraft.world.entity.ai.goal.h" #include "../../Headers/net.minecraft.world.entity.ai.goal.target.h" #include "../../Headers/net.minecraft.world.entity.ai.navigation.h" #include "../../Headers/net.minecraft.world.entity.animal.h" #include "../../Headers/net.minecraft.world.entity.player.h" +#include "../../Headers/net.minecraft.world.entity.monster.h" #include "../../Headers/net.minecraft.world.damagesource.h" #include "../../Stats/GeneralStat.h" #include "Skeleton.h" @@ -19,38 +21,53 @@ void Creeper::_init() { swell = 0; oldSwell = 0; + maxSwell = 30; + explosionRadius = 3; } Creeper::Creeper(Level* level) : Monster(level) { // 4J Stu - This function call had to be moved here from the Entity ctor to // ensure that the derived version of the function is called this->defineSynchedData(); - - // 4J Stu - This function call had to be moved here from the Entity ctor to - // ensure that the derived version of the function is called - health = getMaxHealth(); + registerAttributes(); + setHealth(getMaxHealth()); _init(); - this->textureIdx = TN_MOB_CREEPER; // 4J was L"/mob/creeper.png"; - goalSelector.addGoal(1, new FloatGoal(this)); goalSelector.addGoal(2, new SwellGoal(this)); goalSelector.addGoal( - 3, new AvoidPlayerGoal(this, typeid(Ozelot), 6, 0.25f, 0.30f)); - goalSelector.addGoal(4, new MeleeAttackGoal(this, 0.25f, false)); - goalSelector.addGoal(5, new RandomStrollGoal(this, 0.20f)); + 3, new AvoidPlayerGoal(this, typeid(Ocelot), 6, 1.0, 1.2)); + goalSelector.addGoal(4, new MeleeAttackGoal(this, 1.0, false)); + goalSelector.addGoal(5, new RandomStrollGoal(this, 0.8)); goalSelector.addGoal(6, new LookAtPlayerGoal(this, typeid(Player), 8)); goalSelector.addGoal(6, new RandomLookAroundGoal(this)); targetSelector.addGoal( - 1, new NearestAttackableTargetGoal(this, typeid(Player), 16, 0, true)); + 1, new NearestAttackableTargetGoal(this, typeid(Player), 0, true)); targetSelector.addGoal(2, new HurtByTargetGoal(this, false)); } +void Creeper::registerAttributes() { + Monster::registerAttributes(); + + getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED)->setBaseValue(0.25f); +} + bool Creeper::useNewAi() { return true; } -int Creeper::getMaxHealth() { return 20; } +int Creeper::getMaxFallDistance() { + if (getTarget() == NULL) return 3; + // As long as they survive the fall they should try. + return 3 + (int)(getHealth() - 1); +} + +void Creeper::causeFallDamage(float distance) { + Monster::causeFallDamage(distance); + + swell += distance * 1.5f; + if (swell > maxSwell - 5) swell = maxSwell - 5; +} void Creeper::defineSynchedData() { Monster::defineSynchedData(); @@ -63,12 +80,17 @@ void Creeper::addAdditonalSaveData(CompoundTag* entityTag) { Monster::addAdditonalSaveData(entityTag); if (entityData->getByte(DATA_IS_POWERED) == 1) entityTag->putBoolean(L"powered", true); + entityTag->putShort(L"Fuse", (short)maxSwell); + entityTag->putByte(L"ExplosionRadius", (uint8_t)explosionRadius); } void Creeper::readAdditionalSaveData(CompoundTag* tag) { Monster::readAdditionalSaveData(tag); entityData->set(DATA_IS_POWERED, (uint8_t)(tag->getBoolean(L"powered") ? 1 : 0)); + if (tag->contains(L"Fuse")) maxSwell = tag->getShort(L"Fuse"); + if (tag->contains(L"ExplosionRadius")) + explosionRadius = tag->getByte(L"ExplosionRadius"); } void Creeper::tick() { @@ -76,21 +98,20 @@ void Creeper::tick() { if (isAlive()) { int swellDir = getSwellDir(); if (swellDir > 0 && swell == 0) { - level->playSound(shared_from_this(), eSoundType_RANDOM_FUSE, 1, - 0.5f); + playSound(eSoundType_RANDOM_FUSE, 1, 0.5f); } swell += swellDir; if (swell < 0) swell = 0; - if (swell >= MAX_SWELL) { - swell = MAX_SWELL; + if (swell >= maxSwell) { + swell = maxSwell; if (!level->isClientSide) { - bool destroyBlocks = - true; // level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); + bool destroyBlocks = level->getGameRules()->getBoolean( + GameRules::RULE_MOBGRIEFING); if (isPowered()) - level->explode(shared_from_this(), x, y, z, 6, - destroyBlocks); + level->explode(shared_from_this(), x, y, z, + explosionRadius * 2, destroyBlocks); else - level->explode(shared_from_this(), x, y, z, 3, + level->explode(shared_from_this(), x, y, z, explosionRadius, destroyBlocks); remove(); } @@ -106,14 +127,20 @@ int Creeper::getDeathSound() { return eSoundType_MOB_CREEPER_DEATH; } void Creeper::die(DamageSource* source) { Monster::die(source); - if (std::dynamic_pointer_cast(source->getEntity()) != NULL) { - spawnAtLocation(Item::record_01_Id + random->nextInt(12), 1); + if (source->getEntity() != NULL && + source->getEntity()->instanceof(eTYPE_SKELETON)) { + int recordId = + Item::record_01_Id + + random->nextInt(Item::record_12_Id - Item::record_01_Id + 1); + spawnAtLocation(recordId, 1); } - std::shared_ptr player = - std::dynamic_pointer_cast(source->getEntity()); - if ((std::dynamic_pointer_cast(source->getDirectEntity()) != NULL) && - (player != NULL)) { + if (source->getDirectEntity() != NULL && + source->getDirectEntity()->instanceof(eTYPE_ARROW) && + source->getEntity() != NULL && + source->getEntity()->instanceof(eTYPE_PLAYER)) { + std::shared_ptr player = + std::dynamic_pointer_cast(source->getEntity()); player->awardStat(GenericStats::archer(), GenericStats::param_archer()); } } @@ -123,10 +150,10 @@ bool Creeper::doHurtTarget(std::shared_ptr target) { return true; } bool Creeper::isPowered() { return entityData->getByte(DATA_IS_POWERED) == 1; } float Creeper::getSwelling(float a) { - return (oldSwell + (swell - oldSwell) * a) / (MAX_SWELL - 2); + return (oldSwell + (swell - oldSwell) * a) / (maxSwell - 2); } -int Creeper::getDeathLoot() { return Item::sulphur->id; } +int Creeper::getDeathLoot() { return Item::gunpowder_Id; } int Creeper::getSwellDir() { return (int)(char)entityData->getByte(DATA_SWELL_DIR); diff --git a/Minecraft.World/Entities/Mobs/Creeper.h b/Minecraft.World/Entities/Mobs/Creeper.h index 1762f65e9..d071431c3 100644 --- a/Minecraft.World/Entities/Mobs/Creeper.h +++ b/Minecraft.World/Entities/Mobs/Creeper.h @@ -14,20 +14,26 @@ private: static const int DATA_SWELL_DIR = 16; static const int DATA_IS_POWERED = 17; - int swell; int oldSwell; - - static const int MAX_SWELL = 30; + int swell; + int maxSwell; + int explosionRadius; void _init(); public: Creeper(Level* level); +protected: + void registerAttributes(); + +public: virtual bool useNewAi(); - virtual int getMaxHealth(); + + virtual int getMaxFallDistance(); protected: + virtual void causeFallDamage(float distance); virtual void defineSynchedData(); public: diff --git a/Minecraft.World/Entities/Mobs/DragonFireball.cpp b/Minecraft.World/Entities/Mobs/DragonFireball.cpp index b352f5246..29fa582d0 100644 --- a/Minecraft.World/Entities/Mobs/DragonFireball.cpp +++ b/Minecraft.World/Entities/Mobs/DragonFireball.cpp @@ -15,7 +15,7 @@ DragonFireball::DragonFireball(Level* level) : Fireball(level) { setSize(5 / 16.0f, 5 / 16.0f); } -DragonFireball::DragonFireball(Level* level, std::shared_ptr mob, +DragonFireball::DragonFireball(Level* level, std::shared_ptr mob, double xa, double ya, double za) : Fireball(level, mob, xa, ya, za) { setSize(5 / 16.0f, 5 / 16.0f); @@ -31,14 +31,15 @@ void DragonFireball::onHit(HitResult* res) { if (!level->isClientSide) { AABB* aoe = bb->grow(SPLASH_RANGE, SPLASH_RANGE / 2, SPLASH_RANGE); std::vector >* entitiesOfClass = - level->getEntitiesOfClass(typeid(Mob), aoe); + level->getEntitiesOfClass(typeid(LivingEntity), aoe); if (entitiesOfClass != NULL && !entitiesOfClass->empty()) { // for (Entity e : entitiesOfClass) for (AUTO_VAR(it, entitiesOfClass->begin()); it != entitiesOfClass->end(); ++it) { - // std::shared_ptr e = *it; - std::shared_ptr e = std::dynamic_pointer_cast(*it); + // shared_ptr e = *it; + std::shared_ptr e = + std::dynamic_pointer_cast(*it); double dist = distanceToSqr(e); if (dist < SPLASH_RANGE_SQ) { double scale = 1.0 - (sqrt(dist) / SPLASH_RANGE); @@ -60,12 +61,10 @@ void DragonFireball::onHit(HitResult* res) { bool DragonFireball::isPickable() { return false; } -bool DragonFireball::hurt(DamageSource* source, int damage) { return false; } - -bool DragonFireball::shouldBurn() { return false; } - -int DragonFireball::getIcon() { return 15 + 14 * 16; } +bool DragonFireball::hurt(DamageSource* source, float damage) { return false; } ePARTICLE_TYPE DragonFireball::getTrailParticleType() { return eParticleType_dragonbreath; -} \ No newline at end of file +} + +bool DragonFireball::shouldBurn() { return false; } \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/DragonFireball.h b/Minecraft.World/Entities/Mobs/DragonFireball.h index 6d2ad5d2d..7adeeddbb 100644 --- a/Minecraft.World/Entities/Mobs/DragonFireball.h +++ b/Minecraft.World/Entities/Mobs/DragonFireball.h @@ -17,8 +17,8 @@ private: public: DragonFireball(Level* level); - DragonFireball(Level* level, std::shared_ptr mob, double xa, double ya, - double za); + DragonFireball(Level* level, std::shared_ptr mob, double xa, + double ya, double za); DragonFireball(Level* level, double x, double y, double z, double xa, double ya, double za); @@ -27,12 +27,11 @@ protected: public: virtual bool isPickable(); - virtual bool hurt(DamageSource* source, int damage); - - virtual bool shouldBurn(); - virtual int getIcon(); + virtual bool hurt(DamageSource* source, float damage); protected: // 4J Added TU9 virtual ePARTICLE_TYPE getTrailParticleType(); + + virtual bool shouldBurn(); }; \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/EnderCrystal.cpp b/Minecraft.World/Entities/Mobs/EnderCrystal.cpp index d0458f079..45924f472 100644 --- a/Minecraft.World/Entities/Mobs/EnderCrystal.cpp +++ b/Minecraft.World/Entities/Mobs/EnderCrystal.cpp @@ -50,7 +50,7 @@ void EnderCrystal::tick() { int yt = Mth::floor(y); int zt = Mth::floor(z); if (level->getTile(xt, yt, zt) != Tile::fire_Id) { - level->setTile(xt, yt, zt, Tile::fire_Id); + level->setTileAndUpdate(xt, yt, zt, Tile::fire_Id); } } } @@ -63,13 +63,13 @@ float EnderCrystal::getShadowHeightOffs() { return 0; } bool EnderCrystal::isPickable() { return true; } -bool EnderCrystal::hurt(DamageSource* source, int damage) { +bool EnderCrystal::hurt(DamageSource* source, float damage) { + if (isInvulnerable()) return false; + // 4J-PB - if the owner of the source is the enderdragon, then ignore it // (where the dragon's fireball hits an endercrystal) - std::shared_ptr sourceIsDragon = - std::dynamic_pointer_cast(source->getEntity()); - - if (sourceIsDragon != NULL) { + if (source->getEntity() != NULL && + source->getEntity()->instanceof(eTYPE_ENDERDRAGON)) { return false; } diff --git a/Minecraft.World/Entities/Mobs/EnderCrystal.h b/Minecraft.World/Entities/Mobs/EnderCrystal.h index 402b583fc..844b08706 100644 --- a/Minecraft.World/Entities/Mobs/EnderCrystal.h +++ b/Minecraft.World/Entities/Mobs/EnderCrystal.h @@ -38,5 +38,5 @@ protected: public: virtual float getShadowHeightOffs(); virtual bool isPickable(); - virtual bool hurt(DamageSource* source, int damage); + virtual bool hurt(DamageSource* source, float damage); }; \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/EnderDragon.cpp b/Minecraft.World/Entities/Mobs/EnderDragon.cpp index 81d6ec4c1..920c5c75d 100644 --- a/Minecraft.World/Entities/Mobs/EnderDragon.cpp +++ b/Minecraft.World/Entities/Mobs/EnderDragon.cpp @@ -2,7 +2,9 @@ #include "../../Headers/net.minecraft.world.level.h" #include "../../Headers/net.minecraft.world.level.tile.h" #include "../../Headers/net.minecraft.world.entity.h" +#include "../../Headers/net.minecraft.world.entity.ai.attributes.h" #include "../../Headers/net.minecraft.world.entity.boss.h" +#include "../../Headers/net.minecraft.world.entity.monster.h" #include "../../Headers/net.minecraft.world.entity.projectile.h" #include "../../Headers/net.minecraft.world.phys.h" #include "../../Headers/net.minecraft.world.damagesource.h" @@ -48,10 +50,8 @@ void EnderDragon::_init() { // 4J Stu - This function call had to be moved here from the Entity ctor to // ensure that the derived version of the function is called this->defineSynchedData(); - - // 4J Stu - This function call had to be moved here from the Entity ctor to - // ensure that the derived version of the function is called - health = getMaxHealth(); + registerAttributes(); + setHealth(getMaxHealth()); xTarget = yTarget = zTarget = 0.0; posPointer = -1; @@ -86,33 +86,9 @@ void EnderDragon::_init() { m_currentPath = NULL; } -EnderDragon::EnderDragon(Level* level) : BossMob(level) { +EnderDragon::EnderDragon(Level* level) : Mob(level) { _init(); - head = std::shared_ptr(new BossMobPart(this, L"head", 6, 6)); - neck = std::shared_ptr( - new BossMobPart(this, L"neck", 6, 6)); // 4J Added - body = std::shared_ptr(new BossMobPart(this, L"body", 8, 8)); - tail1 = std::shared_ptr(new BossMobPart(this, L"tail", 4, 4)); - tail2 = std::shared_ptr(new BossMobPart(this, L"tail", 4, 4)); - tail3 = std::shared_ptr(new BossMobPart(this, L"tail", 4, 4)); - wing1 = std::shared_ptr(new BossMobPart(this, L"wing", 4, 4)); - wing2 = std::shared_ptr(new BossMobPart(this, L"wing", 4, 4)); - - subEntities.push_back(head); - subEntities.push_back(neck); // 4J Added - subEntities.push_back(body); - subEntities.push_back(tail1); - subEntities.push_back(tail2); - subEntities.push_back(tail3); - subEntities.push_back(wing1); - subEntities.push_back(wing2); - - maxHealth = 200; - setHealth(maxHealth); - - this->textureIdx = - TN_MOB_ENDERDRAGON; // 4J was "/mob/enderdragon/ender.png"; setSize(16, 8); noPhysics = true; @@ -125,6 +101,44 @@ EnderDragon::EnderDragon(Level* level) : BossMob(level) { noCulling = true; } +// 4J - split off from ctor so we can use shared_from_this() +void EnderDragon::AddParts() { + head = std::shared_ptr(new MultiEntityMobPart( + std::dynamic_pointer_cast(shared_from_this()), L"head", + 6, 6)); + neck = std::shared_ptr(new MultiEntityMobPart( + std::dynamic_pointer_cast(shared_from_this()), L"neck", + 6, + 6)); // 4J Added + body = std::shared_ptr(new MultiEntityMobPart( + std::dynamic_pointer_cast(shared_from_this()), L"body", + 8, 8)); + tail1 = std::shared_ptr(new MultiEntityMobPart( + std::dynamic_pointer_cast(shared_from_this()), L"tail", + 4, 4)); + tail2 = std::shared_ptr(new MultiEntityMobPart( + std::dynamic_pointer_cast(shared_from_this()), L"tail", + 4, 4)); + tail3 = std::shared_ptr(new MultiEntityMobPart( + std::dynamic_pointer_cast(shared_from_this()), L"tail", + 4, 4)); + wing1 = std::shared_ptr(new MultiEntityMobPart( + std::dynamic_pointer_cast(shared_from_this()), L"wing", + 4, 4)); + wing2 = std::shared_ptr(new MultiEntityMobPart( + std::dynamic_pointer_cast(shared_from_this()), L"wing", + 4, 4)); + + subEntities.push_back(head); + subEntities.push_back(neck); // 4J Added + subEntities.push_back(body); + subEntities.push_back(tail1); + subEntities.push_back(tail2); + subEntities.push_back(tail3); + subEntities.push_back(wing1); + subEntities.push_back(wing2); +} + EnderDragon::~EnderDragon() { if (m_nodes->data != NULL) { for (unsigned int i = 0; i < m_nodes->length; ++i) { @@ -136,10 +150,14 @@ EnderDragon::~EnderDragon() { if (m_currentPath != NULL) delete m_currentPath; } -void EnderDragon::defineSynchedData() { - BossMob::defineSynchedData(); +void EnderDragon::registerAttributes() { + Mob::registerAttributes(); - entityData->define(DATA_ID_SYNCHED_HEALTH, maxHealth); + getAttribute(SharedMonsterAttributes::MAX_HEALTH)->setBaseValue(200); +} + +void EnderDragon::defineSynchedData() { + Mob::defineSynchedData(); // 4J Added for new dragon behaviour entityData->define(DATA_ID_SYNCHED_ACTION, @@ -147,7 +165,7 @@ void EnderDragon::defineSynchedData() { } void EnderDragon::getLatencyPos(doubleArray result, int step, float a) { - if (health <= 0) { + if (getHealth() <= 0) { a = 0; } @@ -173,21 +191,20 @@ void EnderDragon::getLatencyPos(doubleArray result, int step, float a) { } void EnderDragon::aiStep() { - if (!level->isClientSide) { - entityData->set(DATA_ID_SYNCHED_HEALTH, health); - } else { + if (level->isClientSide) { // 4J Stu - If saved when dead we need to make sure that the actual // health is updated correctly on the client Fix for TU9: Content: // Gameplay: Enderdragon respawns after loading game which was // previously saved at point of hes death - health = getSynchedHealth(); + setHealth(getHealth()); float flap = Mth::cos(flapTime * PI * 2); float oldFlap = Mth::cos(oFlapTime * PI * 2); if (oldFlap <= -0.3f && flap >= -0.3f) { level->playLocalSound(x, y, z, eSoundType_MOB_ENDERDRAGON_MOVE, 1, - 0.8f + random->nextFloat() * .3f, 100.0f); + 0.8f + random->nextFloat() * .3f, false, + 100.0f); } // play a growl every now and then if (!(getSynchedAction() == e_EnderdragonAction_Sitting_Flaming || @@ -197,7 +214,7 @@ void EnderDragon::aiStep() { if (m_iGrowlTimer < 0) { level->playLocalSound(x, y, z, eSoundType_MOB_ENDERDRAGON_GROWL, 0.5f, 0.8f + random->nextFloat() * .3f, - 100.0f); + false, 100.0f); m_iGrowlTimer = 200 + (random->nextInt(200)); } } @@ -205,7 +222,7 @@ void EnderDragon::aiStep() { oFlapTime = flapTime; - if (health <= 0) { + if (getHealth() <= 0) { // level.addParticle("explode", x + random.nextFloat() * // bbWidth * 2 - bbWidth, y + random.nextFloat() * bbHeight, // z + random.nextFloat() * bbWidth * 2 - bbWidth, 0, 0, 0); @@ -352,7 +369,7 @@ void EnderDragon::aiStep() { // AP - changed this to use playLocalSound because no sound could be // heard with playSound (cos it's a stub function) level->playLocalSound(x, y, z, eSoundType_MOB_ENDERDRAGON_GROWL, - 0.5f, 0.8f + random->nextFloat() * .3f, + 0.5f, 0.8f + random->nextFloat() * .3f, false, 100.0f); } } else { @@ -447,10 +464,10 @@ void EnderDragon::aiStep() { for (AUTO_VAR(it, targets->begin()); it != targets->end(); ++it) { - std::shared_ptr e = - std::dynamic_pointer_cast(*it); - if (e != NULL) { + if ((*it)->instanceof(eTYPE_LIVINGENTITY)) { // app.DebugPrintf("Attacking entity with acid\n"); + std::shared_ptr e = + std::dynamic_pointer_cast(*it); e->hurt(DamageSource::dragonbreath, 2); } } @@ -685,7 +702,7 @@ void EnderDragon::aiStep() { // Curls/straightens the tail for (int i = 0; i < 3; i++) { - std::shared_ptr part = nullptr; + std::shared_ptr part = nullptr; if (i == 0) part = tail1; if (i == 1) part = tail2; @@ -782,12 +799,12 @@ void EnderDragon::checkCrystals() { if (nearestCrystal != NULL) { if (nearestCrystal->removed) { if (!level->isClientSide) { - hurt(head, DamageSource::explosion, 10); + hurt(head, DamageSource::explosion(NULL), 10); } nearestCrystal = nullptr; } else if (tickCount % 10 == 0) { - if (health < maxHealth) health++; + if (getHealth() < getMaxHealth()) setHealth(getHealth() + 1); } } @@ -836,9 +853,10 @@ void EnderDragon::knockBack(std::vector >* entities) { // for (Entity e : entities) for (AUTO_VAR(it, entities->begin()); it != entities->end(); ++it) { - std::shared_ptr e = std::dynamic_pointer_cast(*it); - if (e != NULL) //(e instanceof Mob) + if ((*it)->instanceof(eTYPE_LIVINGENTITY)) //(e instanceof Mob) { + std::shared_ptr e = + std::dynamic_pointer_cast(*it); double xd = e->x - xm; double zd = e->z - zm; double dd = xd * xd + zd * zd; @@ -850,12 +868,13 @@ void EnderDragon::knockBack(std::vector >* entities) { void EnderDragon::hurt(std::vector >* entities) { // for (int i = 0; i < entities->size(); i++) for (AUTO_VAR(it, entities->begin()); it != entities->end(); ++it) { - std::shared_ptr e = - std::dynamic_pointer_cast(*it); // entities.get(i); - if (e != NULL) //(e instanceof Mob) + if ((*it)->instanceof(eTYPE_LIVINGENTITY)) //(e instanceof Mob) { + std::shared_ptr e = + std::dynamic_pointer_cast( + *it); // entities.get(i); DamageSource* damageSource = DamageSource::mobAttack( - std::dynamic_pointer_cast(shared_from_this())); + std::dynamic_pointer_cast(shared_from_this())); e->hurt(damageSource, 10); delete damageSource; } @@ -919,7 +938,8 @@ void EnderDragon::findNewTarget() { // setSynchedAction(e_EnderdragonAction_Sitting_Flaming); // #if PRINT_DRAGON_STATE_CHANGE_MESSAGES // app.DebugPrintf("Dragon action is now: - //SittingFlaming\n"); #endif m_actionTicks = FLAME_TICKS; + // SittingFlaming\n"); #endif m_actionTicks = + // FLAME_TICKS; m_flameAttacks = 0; setSynchedAction(e_EnderdragonAction_Sitting_Scanning); @@ -1066,12 +1086,13 @@ bool EnderDragon::checkWalls(AABB* bb) { int t = level->getTile(x, y, z); // 4J Stu - Don't remove fire if (t == 0 || t == Tile::fire_Id) { - } else if (t == Tile::obsidian_Id || t == Tile::whiteStone_Id || - t == Tile::unbreakable_Id) { + } else if (t == Tile::obsidian_Id || t == Tile::endStone_Id || + t == Tile::unbreakable_Id || + !level->getGameRules()->getBoolean( + GameRules::RULE_MOBGRIEFING)) { hitWall = true; } else { - destroyedTile = true; - level->setTile(x, y, z, 0); + destroyedTile = level->removeTile(x, y, z) || destroyedTile; } } } @@ -1087,9 +1108,9 @@ bool EnderDragon::checkWalls(AABB* bb) { return hitWall; } -bool EnderDragon::hurt(std::shared_ptr bossMobPart, - DamageSource* source, int damage) { - if (bossMobPart != head) { +bool EnderDragon::hurt(std::shared_ptr MultiEntityMobPart, + DamageSource* source, float damage) { + if (MultiEntityMobPart != head) { damage = damage / 4 + 1; } @@ -1102,18 +1123,19 @@ bool EnderDragon::hurt(std::shared_ptr bossMobPart, // zTarget = z - cc1 * 5 + (random->nextFloat() - 0.5f) * 2; // attackTarget = NULL; - if (source == DamageSource::explosion || - (std::dynamic_pointer_cast(source->getEntity()) != NULL)) { - int healthBefore = health; + if (source->getEntity() != NULL && + source->getEntity()->instanceof(eTYPE_PLAYER) || + source->isExplosion()) { + int healthBefore = getHealth(); reallyHurt(source, damage); // if(!level->isClientSide) app.DebugPrintf("Health is now %d\n", // health); - if (health <= 0 && + if (getHealth() <= 0 && !(getSynchedAction() == e_EnderdragonAction_Sitting_Flaming || getSynchedAction() == e_EnderdragonAction_Sitting_Scanning || getSynchedAction() == e_EnderdragonAction_Sitting_Attacking)) { - health = 1; + setHealth(1); if (setSynchedAction(e_EnderdragonAction_LandingApproach)) { if (m_currentPath != NULL) { @@ -1132,7 +1154,7 @@ bool EnderDragon::hurt(std::shared_ptr bossMobPart, if (getSynchedAction() == e_EnderdragonAction_Sitting_Flaming || getSynchedAction() == e_EnderdragonAction_Sitting_Scanning || getSynchedAction() == e_EnderdragonAction_Sitting_Attacking) { - m_sittingDamageReceived += healthBefore - health; + m_sittingDamageReceived += healthBefore - getHealth(); if (m_sittingDamageReceived > (SITTING_ALLOWED_DAMAGE_PERCENTAGE * getMaxHealth())) { @@ -1148,11 +1170,17 @@ bool EnderDragon::hurt(std::shared_ptr bossMobPart, return true; } +bool EnderDragon::hurt(DamageSource* source, float damage) { return false; } + +bool EnderDragon::reallyHurt(DamageSource* source, float damage) { + return Mob::hurt(source, damage); +} + void EnderDragon::tickDeath() { if (getSynchedAction() != e_EnderdragonAction_Sitting_Flaming && getSynchedAction() != e_EnderdragonAction_Sitting_Scanning && getSynchedAction() != e_EnderdragonAction_Sitting_Attacking) { - if (!level->isClientSide) health = 1; + if (!level->isClientSide) setHealth(1); return; } @@ -1175,10 +1203,8 @@ void EnderDragon::tickDeath() { } } if (dragonDeathTime == 1) { - // level->globalLevelEvent(LevelEvent::SOUND_DRAGON_DEATH, (int) x, - // (int) y, (int) z, 0); - level->levelEvent(LevelEvent::SOUND_DRAGON_DEATH, (int)x, (int)y, - (int)z, 0); + level->globalLevelEvent(LevelEvent::SOUND_DRAGON_DEATH, (int)x, + (int)y, (int)z, 0); } } move(0, 0.1f, 0); @@ -1224,15 +1250,18 @@ void EnderDragon::spawnExitPortal(int x, int z) { if (yy < y) { if (d > r - 1 - 0.5) { } else { - level->setTile(xx, yy, zz, Tile::unbreakable_Id); + level->setTileAndUpdate(xx, yy, zz, + Tile::unbreakable_Id); } } else if (yy > y) { - level->setTile(xx, yy, zz, 0); + level->setTileAndUpdate(xx, yy, zz, 0); } else { if (d > r - 1 - 0.5) { - level->setTile(xx, yy, zz, Tile::unbreakable_Id); + level->setTileAndUpdate(xx, yy, zz, + Tile::unbreakable_Id); } else { - level->setTile(xx, yy, zz, Tile::endPortalTile_Id); + level->setTileAndUpdate(xx, yy, zz, + Tile::endPortalTile_Id); } } } @@ -1240,15 +1269,15 @@ void EnderDragon::spawnExitPortal(int x, int z) { } } - level->setTile(x, y + 0, z, Tile::unbreakable_Id); - level->setTile(x, y + 1, z, Tile::unbreakable_Id); - level->setTile(x, y + 2, z, Tile::unbreakable_Id); - level->setTile(x - 1, y + 2, z, Tile::torch_Id); - level->setTile(x + 1, y + 2, z, Tile::torch_Id); - level->setTile(x, y + 2, z - 1, Tile::torch_Id); - level->setTile(x, y + 2, z + 1, Tile::torch_Id); - level->setTile(x, y + 3, z, Tile::unbreakable_Id); - level->setTile(x, y + 4, z, Tile::dragonEgg_Id); + level->setTileAndUpdate(x, y + 0, z, Tile::unbreakable_Id); + level->setTileAndUpdate(x, y + 1, z, Tile::unbreakable_Id); + level->setTileAndUpdate(x, y + 2, z, Tile::unbreakable_Id); + level->setTileAndUpdate(x - 1, y + 2, z, Tile::torch_Id); + level->setTileAndUpdate(x + 1, y + 2, z, Tile::torch_Id); + level->setTileAndUpdate(x, y + 2, z - 1, Tile::torch_Id); + level->setTileAndUpdate(x, y + 2, z + 1, Tile::torch_Id); + level->setTileAndUpdate(x, y + 3, z, Tile::unbreakable_Id); + level->setTileAndUpdate(x, y + 4, z, Tile::dragonEgg_Id); // 4J-PB - The podium can be floating with nothing under it, so put some // whiteStone under it if this is the case @@ -1256,7 +1285,7 @@ void EnderDragon::spawnExitPortal(int x, int z) { for (int xx = x - (r - 1); xx <= x + (r - 1); xx++) { for (int zz = z - (r - 1); zz <= z + (r - 1); zz++) { if (level->isEmptyTile(xx, yy, zz)) { - level->setTile(xx, yy, zz, Tile::whiteStone_Id); + level->setTileAndUpdate(xx, yy, zz, Tile::endStone_Id); } } } @@ -1273,14 +1302,18 @@ std::vector >* EnderDragon::getSubEntities() { bool EnderDragon::isPickable() { return false; } -// Fix for TU9 Enderdragon sound hits being the player sound hits - moved this -// forward from later version -int EnderDragon::getHurtSound() { return eSoundType_MOB_ENDERDRAGON_HIT; } +Level* EnderDragon::getLevel() { return level; } -int EnderDragon::getSynchedHealth() { - return entityData->getInteger(DATA_ID_SYNCHED_HEALTH); +int EnderDragon::getAmbientSound() { + return eSoundType_MOB_ENDERDRAGON_GROWL; //"mob.enderdragon.growl"; } +int EnderDragon::getHurtSound() { + return eSoundType_MOB_ENDERDRAGON_HIT; //"mob.enderdragon.hit"; +} + +float EnderDragon::getSoundVolume() { return 5; } + // 4J Added for new dragon behaviour bool EnderDragon::setSynchedAction(EEnderdragonAction action, bool force /*= false*/) { @@ -1411,7 +1444,8 @@ void EnderDragon::handleCrystalDestroyed(DamageSource* source) { app.DebugPrintf("Dragon action is now: LandingApproach\n"); #endif } - } else if (std::dynamic_pointer_cast(source->getEntity()) != NULL) { + } else if (source->getEntity() != NULL && + source->getEntity()->instanceof(eTYPE_PLAYER)) { if (setSynchedAction(e_EnderdragonAction_StrafePlayer)) { attackTarget = std::dynamic_pointer_cast(source->getEntity()); @@ -1696,7 +1730,7 @@ void EnderDragon::addAdditonalSaveData(CompoundTag* entityTag) { entityTag->putShort(L"RemainingCrystals", m_remainingCrystalsCount); entityTag->putInt(L"DragonState", (int)getSynchedAction()); - BossMob::addAdditonalSaveData(entityTag); + Mob::addAdditonalSaveData(entityTag); } void EnderDragon::readAdditionalSaveData(CompoundTag* tag) { @@ -1708,7 +1742,7 @@ void EnderDragon::readAdditionalSaveData(CompoundTag* tag) { if (tag->contains(L"DragonState")) setSynchedAction((EEnderdragonAction)tag->getInt(L"DragonState"), true); - BossMob::readAdditionalSaveData(tag); + Mob::readAdditionalSaveData(tag); } float EnderDragon::getTilt(float a) { diff --git a/Minecraft.World/Entities/Mobs/EnderDragon.h b/Minecraft.World/Entities/Mobs/EnderDragon.h index 1d9a0516e..90056ce63 100644 --- a/Minecraft.World/Entities/Mobs/EnderDragon.h +++ b/Minecraft.World/Entities/Mobs/EnderDragon.h @@ -1,21 +1,23 @@ #pragma once - #include "BossMob.h" +#include "MultiEntityMob.h" +#include "../Enemy.h" -class BossMobPart; +class MultiEntityMobPart; class EnderCrystal; class Node; class BinaryHeap; class Path; -class EnderDragon : public BossMob { +class EnderDragon : public Mob, + public BossMob, + public MultiEntityMob, + public Enemy { public: eINSTANCEOF GetType() { return eTYPE_ENDERDRAGON; }; static Entity* create(Level* level) { return new EnderDragon(level); } private: - static const int DATA_ID_SYNCHED_HEALTH = 16; - // 4J Added for new behaviours static const int DATA_ID_SYNCHED_ACTION = 17; @@ -28,16 +30,16 @@ public: double positions[positionsLength][3]; int posPointer; - // BossMobPart[] subEntities; + // MultiEntityMobPart[] subEntities; std::vector > subEntities; - std::shared_ptr head; - std::shared_ptr neck; // 4J Added - std::shared_ptr body; - std::shared_ptr tail1; - std::shared_ptr tail2; - std::shared_ptr tail3; - std::shared_ptr wing1; - std::shared_ptr wing2; + std::shared_ptr head; + std::shared_ptr neck; // 4J Added + std::shared_ptr body; + std::shared_ptr tail1; + std::shared_ptr tail2; + std::shared_ptr tail3; + std::shared_ptr wing1; + std::shared_ptr wing2; float oFlapTime; float flapTime; @@ -113,9 +115,11 @@ private: public: EnderDragon(Level* level); + void AddParts(); virtual ~EnderDragon(); protected: + virtual void registerAttributes(); virtual void defineSynchedData(); public: @@ -123,7 +127,7 @@ public: virtual void aiStep(); private: - using BossMob::hurt; + using MultiEntityMob::hurt; void checkCrystals(); void checkAttack(); @@ -134,10 +138,12 @@ private: bool checkWalls(AABB* bb); public: - virtual bool hurt(std::shared_ptr bossMobPart, - DamageSource* source, int damage); + virtual bool hurt(std::shared_ptr MultiEntityMobPart, + DamageSource* source, float damage); + virtual bool hurt(DamageSource* source, float damage); protected: + virtual bool reallyHurt(DamageSource* source, float damage); virtual void tickDeath(); private: @@ -145,12 +151,16 @@ private: protected: virtual void checkDespawn(); - virtual int getHurtSound(); public: virtual std::vector >* getSubEntities(); virtual bool isPickable(); - virtual int getSynchedHealth(); + Level* getLevel(); + +protected: + int getAmbientSound(); + int getHurtSound(); + float getSoundVolume(); private: // 4J added for new dragon behaviour @@ -179,4 +189,8 @@ public: double getHeadPartYRotDiff(int partIndex, doubleArray bodyPos, doubleArray partPos); Vec3* getHeadLookVector(float a); + + virtual std::wstring getAName() { return app.GetString(IDS_ENDERDRAGON); }; + virtual float getHealth() { return LivingEntity::getHealth(); }; + virtual float getMaxHealth() { return LivingEntity::getMaxHealth(); }; }; diff --git a/Minecraft.World/Entities/Mobs/EnderMan.cpp b/Minecraft.World/Entities/Mobs/EnderMan.cpp index 38480874e..986143ab9 100644 --- a/Minecraft.World/Entities/Mobs/EnderMan.cpp +++ b/Minecraft.World/Entities/Mobs/EnderMan.cpp @@ -1,6 +1,8 @@ #include "../../Platform/stdafx.h" #include "../../Headers/net.minecraft.world.entity.player.h" #include "../../Headers/net.minecraft.world.entity.h" +#include "../../Headers/net.minecraft.world.entity.ai.attributes.h" +#include "../../Headers/net.minecraft.world.entity.monster.h" #include "../../Headers/net.minecraft.world.item.h" #include "../../Headers/net.minecraft.world.level.h" #include "../../Headers/net.minecraft.world.level.tile.h" @@ -10,6 +12,11 @@ #include "../../../Minecraft.Client/Textures/Textures.h" #include "EnderMan.h" +AttributeModifier* EnderMan::SPEED_MODIFIER_ATTACKING = + (new AttributeModifier(eModifierId_MOB_ENDERMAN_ATTACKSPEED, 6.2f, + AttributeModifier::OPERATION_ADDITION)) + ->setSerialize(false); + bool EnderMan::MAY_TAKE[256]; void EnderMan::staticCtor() { @@ -20,8 +27,8 @@ void EnderMan::staticCtor() { MAY_TAKE[Tile::gravel_Id] = true; MAY_TAKE[Tile::flower_Id] = true; MAY_TAKE[Tile::rose_Id] = true; - MAY_TAKE[Tile::mushroom1_Id] = true; - MAY_TAKE[Tile::mushroom2_Id] = true; + MAY_TAKE[Tile::mushroom_brown_Id] = true; + MAY_TAKE[Tile::mushroom_red_Id] = true; MAY_TAKE[Tile::tnt_Id] = true; MAY_TAKE[Tile::cactus_Id] = true; MAY_TAKE[Tile::clay_Id] = true; @@ -35,25 +42,27 @@ EnderMan::EnderMan(Level* level) : Monster(level) { // ensure that the derived version of the function is called Brought forward // from 1.2.3 this->defineSynchedData(); - - // 4J Stu - This function call had to be moved here from the Entity ctor to - // ensure that the derived version of the function is called - health = getMaxHealth(); + registerAttributes(); + setHealth(getMaxHealth()); // 4J initialisors teleportTime = 0; aggroTime = 0; + lastAttackTarget = nullptr; + aggroedByPlayer = false; - this->textureIdx = TN_MOB_ENDERMAN; // 4J was "/mob/enderman.png"; - runSpeed = 0.2f; - attackDamage = 7; setSize(0.6f, 2.9f); footSize = 1; } -int EnderMan::getMaxHealth() { return 40; } +void EnderMan::registerAttributes() { + Monster::registerAttributes(); + + getAttribute(SharedMonsterAttributes::MAX_HEALTH)->setBaseValue(40); + getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED)->setBaseValue(0.3f); + getAttribute(SharedMonsterAttributes::ATTACK_DAMAGE)->setBaseValue(7); +} -// Brought forward from 1.2.3 void EnderMan::defineSynchedData() { Monster::defineSynchedData(); @@ -85,6 +94,10 @@ std::shared_ptr EnderMan::findAttackTarget() { level->getNearestAttackablePlayer(shared_from_this(), 64); if (player != NULL) { if (isLookingAtMe(player)) { + aggroedByPlayer = true; + if (aggroTime == 0) + level->playEntitySound(player, eSoundType_MOB_ENDERMAN_STARE, 1, + 1); if (aggroTime++ == 5) { aggroTime = 0; setCreepy(true); @@ -116,42 +129,53 @@ bool EnderMan::isLookingAtMe(std::shared_ptr player) { } void EnderMan::aiStep() { - if (this->isInWaterOrRain()) hurt(DamageSource::drown, 1); + if (isInWaterOrRain()) hurt(DamageSource::drown, 1); - runSpeed = attackTarget != NULL ? 6.5f : 0.3f; + if (lastAttackTarget != attackTarget) { + AttributeInstance* speed = + getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED); + speed->removeModifier(SPEED_MODIFIER_ATTACKING); + + if (attackTarget != NULL) { + speed->addModifier( + new AttributeModifier(*SPEED_MODIFIER_ATTACKING)); + } + } + + lastAttackTarget = attackTarget; if (!level->isClientSide) { - if (getCarryingTile() == 0) { - if (random->nextInt(20) == 0) { - int xt = Mth::floor(x - 2 + random->nextDouble() * 4); - int yt = Mth::floor(y + random->nextDouble() * 3); - int zt = Mth::floor(z - 2 + random->nextDouble() * 4); - int t = level->getTile(xt, yt, zt); - // if (t > 0 && Tile::tiles[t]->isCubeShaped()) - if (EnderMan::MAY_TAKE[t]) // 4J - Brought forward from 1.2.3 - { - setCarryingTile(level->getTile(xt, yt, zt)); - setCarryingData(level->getData(xt, yt, zt)); - level->setTile(xt, yt, zt, 0); + if (level->getGameRules()->getBoolean(GameRules::RULE_MOBGRIEFING)) { + if (getCarryingTile() == 0) { + if (random->nextInt(20) == 0) { + int xt = Mth::floor(x - 2 + random->nextDouble() * 4); + int yt = Mth::floor(y + random->nextDouble() * 3); + int zt = Mth::floor(z - 2 + random->nextDouble() * 4); + int t = level->getTile(xt, yt, zt); + if (MAY_TAKE[t]) { + setCarryingTile(level->getTile(xt, yt, zt)); + setCarryingData(level->getData(xt, yt, zt)); + level->setTileAndUpdate(xt, yt, zt, 0); + } } - } - } else { - if (random->nextInt(2000) == 0) { - int xt = Mth::floor(x - 1 + random->nextDouble() * 2); - int yt = Mth::floor(y + random->nextDouble() * 2); - int zt = Mth::floor(z - 1 + random->nextDouble() * 2); - int t = level->getTile(xt, yt, zt); - int bt = level->getTile(xt, yt - 1, zt); - if (t == 0 && bt > 0 && Tile::tiles[bt]->isCubeShaped()) { - level->setTileAndData(xt, yt, zt, getCarryingTile(), - getCarryingData()); - setCarryingTile(0); + } else { + if (random->nextInt(2000) == 0) { + int xt = Mth::floor(x - 1 + random->nextDouble() * 2); + int yt = Mth::floor(y + random->nextDouble() * 2); + int zt = Mth::floor(z - 1 + random->nextDouble() * 2); + int t = level->getTile(xt, yt, zt); + int bt = level->getTile(xt, yt - 1, zt); + if (t == 0 && bt > 0 && Tile::tiles[bt]->isCubeShaped()) { + level->setTileAndData(xt, yt, zt, getCarryingTile(), + getCarryingData(), + Tile::UPDATE_ALL); + setCarryingTile(0); + } } } } } - // 4J - Brought forward particles from 1.2.3 for (int i = 0; i < 2; i++) { level->addParticle( eParticleType_ender, x + (random->nextDouble() - 0.5) * bbWidth, @@ -164,34 +188,38 @@ void EnderMan::aiStep() { if (level->isDay() && !level->isClientSide) { float br = getBrightness(1); if (br > 0.5f) { - if (level->canSeeSky(Mth::floor(x), Mth::floor(y), Mth::floor(z)) && + if (level->canSeeSky(Mth::floor(x), (int)floor(y + 0.5), + Mth::floor(z)) && random->nextFloat() * 30 < (br - 0.4f) * 2) { - // 4J - Brought forward behaviour change from 1.2.3 - // onFire = 20 * 15; attackTarget = nullptr; setCreepy(false); + aggroedByPlayer = false; teleport(); } } } - // 4J Brought forward behaviour change from 1.2.3 + if (isInWaterOrRain() || isOnFire()) { attackTarget = nullptr; setCreepy(false); + aggroedByPlayer = false; teleport(); } + + if (isCreepy() && !aggroedByPlayer && random->nextInt(100) == 0) { + setCreepy(false); + } + jumping = false; if (attackTarget != NULL) { - this->lookAt(attackTarget, 100, 100); + lookAt(attackTarget, 100, 100); } if (!level->isClientSide && isAlive()) { if (attackTarget != NULL) { - if (std::dynamic_pointer_cast(attackTarget) != NULL && + if (attackTarget->instanceof(eTYPE_PLAYER) && isLookingAtMe( std::dynamic_pointer_cast(attackTarget))) { - xxa = yya = 0; - runSpeed = 0; if (attackTarget->distanceToSqr(shared_from_this()) < 4 * 4) { teleport(); } @@ -278,15 +306,10 @@ bool EnderMan::teleport(double xx, double yy, double zz) { double _z = zo + (z - zo) * d + (random->nextDouble() - 0.5) * bbWidth * 2; - // 4J - Brought forward particle change from 1.2.3 - // level->addParticle(eParticleType_largesmoke, _x, _y, _z, xa, ya, - // za); level->addParticle(eParticleType_ender, _x, _y, _z, xa, ya, za); } - // 4J - moved sounds forward from 1.2.3 level->playSound(xo, yo, zo, eSoundType_MOB_ENDERMEN_PORTAL, 1, 1); - level->playSound(shared_from_this(), eSoundType_MOB_ENDERMEN_PORTAL, 1, - 1); + playSound(eSoundType_MOB_ENDERMEN_PORTAL, 1, 1); return true; } else { setPos(xo, yo, zo); @@ -295,19 +318,13 @@ bool EnderMan::teleport(double xx, double yy, double zz) { } int EnderMan::getAmbientSound() { - // 4J - brought sound change forward from 1.2.3 - return eSoundType_MOB_ENDERMEN_IDLE; + return isCreepy() ? eSoundType_MOB_ENDERMAN_SCREAM + : eSoundType_MOB_ENDERMEN_IDLE; } -int EnderMan::getHurtSound() { - // 4J - brought sound change forward from 1.2.3 - return eSoundType_MOB_ENDERMEN_HIT; -} +int EnderMan::getHurtSound() { return eSoundType_MOB_ENDERMEN_HIT; } -int EnderMan::getDeathSound() { - // 4J - brought sound change forward from 1.2.3 - return eSoundType_MOB_ENDERMEN_DEATH; -} +int EnderMan::getDeathSound() { return eSoundType_MOB_ENDERMEN_DEATH; } int EnderMan::getDeathLoot() { return Item::enderPearl_Id; } @@ -336,10 +353,21 @@ int EnderMan::getCarryingData() { return entityData->getByte(DATA_CARRY_ITEM_DATA); } -bool EnderMan::hurt(DamageSource* source, int damage) { +bool EnderMan::hurt(DamageSource* source, float damage) { + if (isInvulnerable()) return false; + setCreepy(true); + + if (dynamic_cast(source) != NULL && + source->getEntity()->instanceof(eTYPE_PLAYER)) { + aggroedByPlayer = true; + } + if (dynamic_cast(source) != NULL) { + aggroedByPlayer = false; for (int i = 0; i < 64; i++) { - if (teleport()) return true; + if (teleport()) { + return true; + } } return false; } @@ -350,4 +378,4 @@ bool EnderMan::isCreepy() { return entityData->getByte(DATA_CREEPY) > 0; } void EnderMan::setCreepy(bool creepy) { entityData->set(DATA_CREEPY, (uint8_t)(creepy ? 1 : 0)); -} +} \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/EnderMan.h b/Minecraft.World/Entities/Mobs/EnderMan.h index 795d93e02..5acd6ab9b 100644 --- a/Minecraft.World/Entities/Mobs/EnderMan.h +++ b/Minecraft.World/Entities/Mobs/EnderMan.h @@ -11,6 +11,8 @@ public: static void staticCtor(); private: + static AttributeModifier* SPEED_MODIFIER_ATTACKING; + static bool MAY_TAKE[256]; static const int DATA_CARRY_ITEM_ID = 16; @@ -20,13 +22,14 @@ private: private: int teleportTime; int aggroTime; + std::shared_ptr lastAttackTarget; + bool aggroedByPlayer; public: EnderMan(Level* level); - virtual int getMaxHealth(); - protected: + virtual void registerAttributes(); virtual void defineSynchedData(); public: @@ -54,12 +57,11 @@ protected: virtual void dropDeathLoot(bool wasKilledByPlayer, int playerBonusLevel); public: - // 4J Brought forward from 1.2.3 to help fix Enderman behaviour void setCarryingTile(int carryingTile); int getCarryingTile(); void setCarryingData(int carryingData); int getCarryingData(); - virtual bool hurt(DamageSource* source, int damage); + virtual bool hurt(DamageSource* source, float damage); bool isCreepy(); void setCreepy(bool creepy); }; \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/EntityHorse.cpp b/Minecraft.World/Entities/Mobs/EntityHorse.cpp new file mode 100644 index 000000000..288933779 --- /dev/null +++ b/Minecraft.World/Entities/Mobs/EntityHorse.cpp @@ -0,0 +1,1520 @@ +#include "../../Platform/stdafx.h" +#include "../../Headers/net.minecraft.world.entity.h" +#include "../../Headers/net.minecraft.world.entity.monster.h" +#include "../../Headers/net.minecraft.world.entity.ai.attributes.h" +#include "../../Headers/net.minecraft.world.entity.ai.goal.h" +#include "../../Headers/net.minecraft.world.entity.ai.navigation.h" +#include "../../Headers/net.minecraft.world.entity.player.h" +#include "../../Headers/net.minecraft.world.entity.monster.h" +#include "../../Headers/net.minecraft.world.effect.h" +#include "../../Headers/net.minecraft.world.damagesource.h" +#include "../../Headers/net.minecraft.world.item.h" +#include "../../Headers/net.minecraft.world.level.h" +#include "../../Headers/net.minecraft.world.level.tile.h" +#include "../../Headers/net.minecraft.world.inventory.h" +#include "../../Headers/net.minecraft.world.phys.h" +#include "../../../Minecraft.Client/Textures/Textures.h" +#include "../../../Minecraft.Client/Minecraft.h" +#include "../../Util/BasicTypeContainers.h" +#include "EntityHorse.h" + +const std::wstring EntityHorse::TEX_FOLDER = L"mob/horse/"; + +const EntitySelector* EntityHorse::PARENT_HORSE_SELECTOR = + new HorseEntitySelector(); + +Attribute* EntityHorse::JUMP_STRENGTH = + (new RangedAttribute(eAttributeId_HORSE_JUMPSTRENGTH, .7, 0, 2.0)) + ->setSyncable(true); + +std::wstring EntityHorse::ARMOR_TEXTURES[EntityHorse::ARMORS] = { + L"", L"armor/horse_armor_iron.png", L"armor/horse_armor_gold.png", + L"armor/horse_armor_diamond.png"}; +int EntityHorse::ARMOR_TEXTURES_ID[EntityHorse::ARMORS] = { + -1, TN_MOB_HORSE_ARMOR_IRON, TN_MOB_HORSE_ARMOR_GOLD, + TN_MOB_HORSE_ARMOR_DIAMOND}; +std::wstring EntityHorse::ARMOR_HASHES[EntityHorse::ARMORS] = {L"", L"meo", + L"goo", L"dio"}; +int EntityHorse::ARMOR_PROTECTION[EntityHorse::ARMORS] = {0, 5, 7, 11}; + +std::wstring EntityHorse::VARIANT_TEXTURES[EntityHorse::VARIANTS] = { + L"horse_white.png", L"horse_creamy.png", L"horse_chestnut.png", + L"horse_brown.png", L"horse_black.png", L"horse_gray.png", + L"horse_darkbrown.png"}; +int EntityHorse::VARIANT_TEXTURES_ID[EntityHorse::VARIANTS] = { + TN_MOB_HORSE_WHITE, TN_MOB_HORSE_CREAMY, TN_MOB_HORSE_CHESTNUT, + TN_MOB_HORSE_BROWN, TN_MOB_HORSE_BLACK, TN_MOB_HORSE_GRAY, + TN_MOB_HORSE_DARKBROWN}; + +std::wstring EntityHorse::VARIANT_HASHES[EntityHorse::VARIANTS] = { + L"hwh", L"hcr", L"hch", L"hbr", L"hbl", L"hgr", L"hdb"}; + +std::wstring EntityHorse::MARKING_TEXTURES[EntityHorse::MARKINGS] = { + L"", L"horse_markings_white.png", L"horse_markings_whitefield.png", + L"horse_markings_whitedots.png", L"horse_markings_blackdots.png"}; +int EntityHorse::MARKING_TEXTURES_ID[EntityHorse::MARKINGS] = { + -1, TN_MOB_HORSE_MARKINGS_WHITE, TN_MOB_HORSE_MARKINGS_WHITEFIELD, + TN_MOB_HORSE_MARKINGS_WHITEDOTS, TN_MOB_HORSE_MARKINGS_BLACKDOTS}; +std::wstring EntityHorse::MARKING_HASHES[EntityHorse::MARKINGS] = { + L"", L"wo_", L"wmo", L"wdo", L"bdo"}; + +bool HorseEntitySelector::matches(std::shared_ptr entity) const { + return entity->instanceof(eTYPE_HORSE) && + std::dynamic_pointer_cast(entity)->isBred(); +} + +EntityHorse::EntityHorse(Level* level) : Animal(level) { + // 4J Stu - This function call had to be moved here from the Entity ctor to + // ensure that the derived version of the function is called + this->defineSynchedData(); + registerAttributes(); + setHealth(getMaxHealth()); + + countEating = 0; + mouthCounter = 0; + standCounter = 0; + tailCounter = 0; + sprintCounter = 0; + isEntityJumping = false; + inventory = nullptr; + hasReproduced = false; + temper = 0; + playerJumpPendingScale = 0.0f; + allowStandSliding = false; + eatAnim = eatAnimO = 0.0f; + standAnim = standAnimO = 0.0f; + mouthAnim = mouthAnimO = 0.0f; + gallopSoundCounter = 0; + + layerTextureHashName = L""; + + layerTextureLayers = intArray(3); + for (unsigned int i = 0; i < 3; ++i) { + layerTextureLayers[i] = -1; + } + + setSize(1.4f, 1.6f); + fireImmune = false; + setChestedHorse(false); + + getNavigation()->setAvoidWater(true); + goalSelector.addGoal(0, new FloatGoal(this)); + goalSelector.addGoal(1, new PanicGoal(this, 1.2)); + goalSelector.addGoal(1, new RunAroundLikeCrazyGoal(this, 1.2)); + goalSelector.addGoal(2, new BreedGoal(this, 1.0)); + goalSelector.addGoal(4, new FollowParentGoal(this, 1.0)); + goalSelector.addGoal(6, new RandomStrollGoal(this, .7)); + goalSelector.addGoal(7, new LookAtPlayerGoal(this, typeid(Player), 6)); + goalSelector.addGoal(8, new RandomLookAroundGoal(this)); + + createInventory(); +} + +EntityHorse::~EntityHorse() { delete[] layerTextureLayers.data; } + +void EntityHorse::defineSynchedData() { + Animal::defineSynchedData(); + entityData->define(DATA_ID_HORSE_FLAGS, 0); + entityData->define(DATA_ID_TYPE, (uint8_t)0); + entityData->define(DATA_ID_TYPE_VARIANT, 0); + entityData->define(DATA_ID_OWNER_NAME, L""); + entityData->define(DATA_ID_ARMOR, 0); +} + +void EntityHorse::setType(int i) { + entityData->set(DATA_ID_TYPE, (uint8_t)i); + clearLayeredTextureInfo(); +} + +int EntityHorse::getType() { return entityData->getByte(DATA_ID_TYPE); } + +void EntityHorse::setVariant(int i) { + entityData->set(DATA_ID_TYPE_VARIANT, i); + clearLayeredTextureInfo(); +} + +int EntityHorse::getVariant() { + return entityData->getInteger(DATA_ID_TYPE_VARIANT); +} + +std::wstring EntityHorse::getAName() { + if (hasCustomName()) return getCustomName(); +#ifdef _DEBUG + int type = getType(); + switch (type) { + default: + case TYPE_HORSE: + return L"entity.horse.name"; + case TYPE_DONKEY: + return L"entity.donkey.name"; + case TYPE_MULE: + return L"entity.mule.name"; + case TYPE_SKELETON: + return L"entity.skeletonhorse.name"; + case TYPE_UNDEAD: + return L"entity.zombiehorse.name"; + } +#else + return L""; +#endif +} + +bool EntityHorse::getHorseFlag(int flag) { + return (entityData->getInteger(DATA_ID_HORSE_FLAGS) & flag) != 0; +} + +void EntityHorse::setHorseFlag(int flag, bool value) { + int current = entityData->getInteger(DATA_ID_HORSE_FLAGS); + if (value) { + entityData->set(DATA_ID_HORSE_FLAGS, current | flag); + } else { + entityData->set(DATA_ID_HORSE_FLAGS, current & ~flag); + } +} + +bool EntityHorse::isAdult() { return !isBaby(); } + +bool EntityHorse::isTamed() { return getHorseFlag(FLAG_TAME); } + +bool EntityHorse::isRidable() { return isAdult(); } + +std::wstring EntityHorse::getOwnerName() { + return entityData->getString(DATA_ID_OWNER_NAME); +} + +void EntityHorse::setOwner(const std::wstring& par1Str) { + entityData->set(DATA_ID_OWNER_NAME, par1Str); +} + +float EntityHorse::getFoalScale() { + int age = getAge(); + if (age >= 0) { + return 1.0f; + } + return .5f + (float)(BABY_START_AGE - age) / (float)BABY_START_AGE * .5f; +} + +void EntityHorse::updateSize(bool isBaby) { + if (isBaby) { + internalSetSize(getFoalScale()); + } else { + internalSetSize(1.0f); + } +} + +bool EntityHorse::getIsJumping() { return isEntityJumping; } + +void EntityHorse::setTamed(bool flag) { setHorseFlag(FLAG_TAME, flag); } + +void EntityHorse::setIsJumping(bool flag) { isEntityJumping = flag; } + +bool EntityHorse::canBeLeashed() { + return !isUndead() && Animal::canBeLeashed(); +} + +void EntityHorse::onLeashDistance(float distanceToLeashHolder) { + if (distanceToLeashHolder > 6 && isEating()) { + setEating(false); + } +} + +bool EntityHorse::isChestedHorse() { return getHorseFlag(FLAG_CHESTED); } + +int EntityHorse::getArmorType() { + return entityData->getInteger(DATA_ID_ARMOR); +} + +int EntityHorse::getArmorTypeForItem(std::shared_ptr armorItem) { + if (armorItem == NULL) { + return ARMOR_NONE; + } + if (armorItem->id == Item::horseArmorMetal_Id) { + return ARMOR_IRON; + } else if (armorItem->id == Item::horseArmorGold_Id) { + return ARMOR_GOLD; + } else if (armorItem->id == Item::horseArmorDiamond_Id) { + return ARMOR_DIAMOND; + } + return ARMOR_NONE; +} + +bool EntityHorse::isEating() { return getHorseFlag(FLAG_EATING); } + +bool EntityHorse::isStanding() { return getHorseFlag(FLAG_STANDING); } + +bool EntityHorse::isBred() { return getHorseFlag(FLAG_BRED); } + +bool EntityHorse::getHasReproduced() { return hasReproduced; } + +void EntityHorse::setArmorType(int i) { + entityData->set(DATA_ID_ARMOR, i); + clearLayeredTextureInfo(); +} + +void EntityHorse::setBred(bool flag) { setHorseFlag(FLAG_BRED, flag); } + +void EntityHorse::setChestedHorse(bool flag) { + setHorseFlag(FLAG_CHESTED, flag); +} + +void EntityHorse::setReproduced(bool flag) { hasReproduced = flag; } + +void EntityHorse::setSaddled(bool flag) { setHorseFlag(FLAG_SADDLE, flag); } + +int EntityHorse::getTemper() { return temper; } + +void EntityHorse::setTemper(int temper) { this->temper = temper; } + +int EntityHorse::modifyTemper(int amount) { + int temper = Mth::clamp(getTemper() + amount, 0, getMaxTemper()); + + setTemper(temper); + return temper; +} + +bool EntityHorse::hurt(DamageSource* damagesource, float dmg) { + // 4J: Protect owned horses from untrusted players + if (isTamed()) { + std::shared_ptr entity = damagesource->getDirectEntity(); + if (entity != NULL && entity->instanceof(eTYPE_PLAYER)) { + std::shared_ptr attacker = + std::dynamic_pointer_cast(entity); + attacker->canHarmPlayer(getOwnerName()); + } + } + + std::shared_ptr attacker = damagesource->getEntity(); + if (rider.lock() != NULL && (rider.lock() == (attacker))) { + return false; + } + + return Animal::hurt(damagesource, dmg); +} + +int EntityHorse::getArmorValue() { return ARMOR_PROTECTION[getArmorType()]; } + +bool EntityHorse::isPushable() { return rider.lock() == NULL; } + +// TODO: [EB]: Explain why this is being done - what side effect does getBiome +// have? +bool EntityHorse::checkSpawningBiome() { + int x = Mth::floor(this->x); + int z = Mth::floor(this->z); + + level->getBiome(x, z); + return true; +} + +/** + * Drops a chest block if the horse is bagged + */ +void EntityHorse::dropBags() { + if (level->isClientSide || !isChestedHorse()) { + return; + } + + spawnAtLocation(Tile::chest_Id, 1); + setChestedHorse(false); +} + +void EntityHorse::eatingHorse() { + openMouth(); + level->playEntitySound( + shared_from_this(), eSoundType_EATING, 1.0f, + 1.0f + (random->nextFloat() - random->nextFloat()) * 0.2f); +} + +/** + * Changed to adjust fall damage for riders + */ +void EntityHorse::causeFallDamage(float fallDistance) { + if (fallDistance > 1) { + playSound(eSoundType_MOB_HORSE_LAND, .4f, 1); + } + + int dmg = Mth::ceil(fallDistance * .5f - 3.0f); + if (dmg <= 0) return; + + hurt(DamageSource::fall, dmg); + + if (rider.lock() != NULL) { + rider.lock()->hurt(DamageSource::fall, dmg); + } + + int id = level->getTile(Mth::floor(x), Mth::floor(y - 0.2 - yRotO), + Mth::floor(z)); + if (id > 0) { + const Tile::SoundType* stepsound = Tile::tiles[id]->soundType; + level->playEntitySound(shared_from_this(), stepsound->getStepSound(), + stepsound->getVolume() * 0.5f, + stepsound->getPitch() * 0.75f); + } +} + +/** + * Different inventory sizes depending on the kind of horse + * + * @return + */ +int EntityHorse::getInventorySize() { + int type = getType(); + if (isChestedHorse() && (type == TYPE_DONKEY || type == TYPE_MULE)) { + return INV_BASE_COUNT + INV_DONKEY_CHEST_COUNT; + } + return INV_BASE_COUNT; +} + +void EntityHorse::createInventory() { + std::shared_ptr old = inventory; + inventory = std::shared_ptr( + new AnimalChest(L"HorseChest", getInventorySize())); + inventory->setCustomName(getAName()); + if (old != NULL) { + old->removeListener(this); + + int max = + std::min(old->getContainerSize(), inventory->getContainerSize()); + for (int slot = 0; slot < max; slot++) { + std::shared_ptr item = old->getItem(slot); + if (item != NULL) { + inventory->setItem(slot, item->copy()); + } + } + old = nullptr; + } + inventory->addListener(this); + updateEquipment(); +} + +void EntityHorse::updateEquipment() { + if (!level->isClientSide) { + setSaddled(inventory->getItem(INV_SLOT_SADDLE) != NULL); + if (canWearArmor()) { + setArmorType( + getArmorTypeForItem(inventory->getItem(INV_SLOT_ARMOR))); + } + } +} + +void EntityHorse::containerChanged() { + int armorType = getArmorType(); + bool saddled = isSaddled(); + updateEquipment(); + if (tickCount > 20) { + if (armorType == ARMOR_NONE && armorType != getArmorType()) { + playSound(eSoundType_MOB_HORSE_ARMOR, .5f, 1); + } + if (!saddled && isSaddled()) { + playSound(eSoundType_MOB_HORSE_LEATHER, .5f, 1); + } + } +} + +bool EntityHorse::canSpawn() { + checkSpawningBiome(); + return Animal::canSpawn(); +} + +std::shared_ptr EntityHorse::getClosestMommy( + std::shared_ptr baby, double searchRadius) { + double closestDistance = Double::MAX_VALUE; + + std::shared_ptr mommy = nullptr; + std::vector >* list = level->getEntities( + baby, baby->bb->expand(searchRadius, searchRadius, searchRadius), + PARENT_HORSE_SELECTOR); + + for (AUTO_VAR(it, list->begin()); it != list->end(); ++it) { + std::shared_ptr horse = *it; + double distanceSquared = + horse->distanceToSqr(baby->x, baby->y, baby->z); + + if (distanceSquared < closestDistance) { + mommy = horse; + closestDistance = distanceSquared; + } + } + delete list; + + return std::dynamic_pointer_cast(mommy); +} + +double EntityHorse::getCustomJump() { + return getAttribute(JUMP_STRENGTH)->getValue(); +} + +int EntityHorse::getDeathSound() { + openMouth(); + int type = getType(); + if (type == TYPE_UNDEAD) { + return eSoundType_MOB_HORSE_ZOMBIE_DEATH; //"mob.horse.zombie.death"; + } + if (type == TYPE_SKELETON) { + return eSoundType_MOB_HORSE_SKELETON_DEATH; //"mob.horse.skeleton.death"; + } + if (type == TYPE_DONKEY || type == TYPE_MULE) { + return eSoundType_MOB_HORSE_DONKEY_DEATH; //"mob.horse.donkey.death"; + } + return eSoundType_MOB_HORSE_DEATH; //"mob.horse.death"; +} + +int EntityHorse::getDeathLoot() { + bool flag = random->nextInt(4) == 0; + + int type = getType(); + if (type == TYPE_SKELETON) { + return Item::bone_Id; + } + if (type == TYPE_UNDEAD) { + if (flag) { + return 0; + } + return Item::rotten_flesh_Id; + } + + return Item::leather_Id; +} + +int EntityHorse::getHurtSound() { + openMouth(); + { + if (random->nextInt(3) == 0) { + stand(); + } + } + int type = getType(); + if (type == TYPE_UNDEAD) { + return eSoundType_MOB_HORSE_ZOMBIE_HIT; //"mob.horse.zombie.hit"; + } + if (type == TYPE_SKELETON) { + return eSoundType_MOB_HORSE_SKELETON_HIT; //"mob.horse.skeleton.hit"; + } + if (type == TYPE_DONKEY || type == TYPE_MULE) { + return eSoundType_MOB_HORSE_DONKEY_HIT; //"mob.horse.donkey.hit"; + } + return eSoundType_MOB_HORSE_HIT; //"mob.horse.hit"; +} + +bool EntityHorse::isSaddled() { return getHorseFlag(FLAG_SADDLE); } + +int EntityHorse::getAmbientSound() { + openMouth(); + if (random->nextInt(10) == 0 && !isImmobile()) { + stand(); + } + int type = getType(); + if (type == TYPE_UNDEAD) { + return eSoundType_MOB_HORSE_ZOMBIE_IDLE; //"mob.horse.zombie.idle"; + } + if (type == TYPE_SKELETON) { + return eSoundType_MOB_HORSE_SKELETON_IDLE; //"mob.horse.skeleton.idle"; + } + if (type == TYPE_DONKEY || type == TYPE_MULE) { + return eSoundType_MOB_HORSE_DONKEY_IDLE; //"mob.horse.donkey.idle"; + } + return eSoundType_MOB_HORSE_IDLE; //"mob.horse.idle"; +} + +/** + * sound played when an untamed mount buckles rider + */ +int EntityHorse::getMadSound() { + openMouth(); + stand(); + int type = getType(); + if (type == TYPE_UNDEAD || type == TYPE_SKELETON) { + return -1; + } + if (type == TYPE_DONKEY || type == TYPE_MULE) { + return eSoundType_MOB_HORSE_DONKEY_ANGRY; //"mob.horse.donkey.angry"; + } + return eSoundType_MOB_HORSE_ANGRY; //"mob.horse.angry"; +} + +void EntityHorse::playStepSound(int xt, int yt, int zt, int t) { + const Tile::SoundType* soundType = Tile::tiles[t]->soundType; + if (level->getTile(xt, yt + 1, zt) == Tile::topSnow_Id) { + soundType = Tile::topSnow->soundType; + } + if (!Tile::tiles[t]->material->isLiquid()) { + int type = getType(); + if (rider.lock() != NULL && type != TYPE_DONKEY && type != TYPE_MULE) { + gallopSoundCounter++; + if (gallopSoundCounter > 5 && gallopSoundCounter % 3 == 0) { + playSound(eSoundType_MOB_HORSE_GALLOP, + soundType->getVolume() * 0.15f, + soundType->getPitch()); + if (type == TYPE_HORSE && random->nextInt(10) == 0) { + playSound(eSoundType_MOB_HORSE_BREATHE, + soundType->getVolume() * 0.6f, + soundType->getPitch()); + } + } else if (gallopSoundCounter <= 5) { + playSound(eSoundType_MOB_HORSE_WOOD, + soundType->getVolume() * 0.15f, + soundType->getPitch()); + } + } else if (soundType == Tile::SOUND_WOOD) { + playSound(eSoundType_MOB_HORSE_SOFT, soundType->getVolume() * 0.15f, + soundType->getPitch()); + } else { + playSound(eSoundType_MOB_HORSE_WOOD, soundType->getVolume() * 0.15f, + soundType->getPitch()); + } + } +} + +void EntityHorse::registerAttributes() { + Animal::registerAttributes(); + + getAttributes()->registerAttribute(JUMP_STRENGTH); + + getAttribute(SharedMonsterAttributes::MAX_HEALTH)->setBaseValue(53); + getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED)->setBaseValue(0.225f); +} + +int EntityHorse::getMaxSpawnClusterSize() { return 6; } + +/** + * How difficult is the creature to be tamed? the Higher the number, the + * more difficult + */ +int EntityHorse::getMaxTemper() { return 100; } + +float EntityHorse::getSoundVolume() { return 0.8f; } + +int EntityHorse::getAmbientSoundInterval() { return 400; } + +bool EntityHorse::hasLayeredTextures() { + return getType() == TYPE_HORSE || getArmorType() > 0; +} + +void EntityHorse::clearLayeredTextureInfo() { layerTextureHashName = L""; } + +void EntityHorse::rebuildLayeredTextureInfo() { + layerTextureHashName = L"horse/"; + layerTextureLayers[0] = -1; + layerTextureLayers[1] = -1; + layerTextureLayers[2] = -1; + + int type = getType(); + int variant = getVariant(); + int armorIndex = 2; + if (type == TYPE_HORSE) { + int skin = variant & 0xFF; + int markings = (variant & 0xFF00) >> 8; + layerTextureLayers[0] = VARIANT_TEXTURES_ID[skin]; + layerTextureHashName += VARIANT_HASHES[skin]; + + layerTextureLayers[1] = MARKING_TEXTURES_ID[markings]; + layerTextureHashName += MARKING_HASHES[markings]; + + if (layerTextureLayers[1] == -1) { + armorIndex = 1; + } + } else { + layerTextureLayers[0] = -1; + layerTextureHashName += L"_" + _toString(type) + L"_"; + armorIndex = 1; + } + + int armor = getArmorType(); + layerTextureLayers[armorIndex] = ARMOR_TEXTURES_ID[armor]; + layerTextureHashName += ARMOR_HASHES[armor]; +} + +std::wstring EntityHorse::getLayeredTextureHashName() { + if (layerTextureHashName.empty()) { + rebuildLayeredTextureInfo(); + } + return layerTextureHashName; +} + +intArray EntityHorse::getLayeredTextureLayers() { + if (layerTextureHashName.empty()) { + rebuildLayeredTextureInfo(); + } + return layerTextureLayers; +} + +void EntityHorse::openInventory(std::shared_ptr player) { + if (!level->isClientSide && + (rider.lock() == NULL || rider.lock() == player) && isTamed()) { + inventory->setCustomName(getAName()); + player->openHorseInventory( + std::dynamic_pointer_cast(shared_from_this()), + inventory); + } +} + +bool EntityHorse::mobInteract(std::shared_ptr player) { + std::shared_ptr itemstack = player->inventory->getSelected(); + + if (itemstack != NULL && itemstack->id == Item::spawnEgg_Id) { + return Animal::mobInteract(player); + } + + if (!isTamed()) { + if (isUndead()) { + return false; + } + } + + if (isTamed() && isAdult() && player->isSneaking()) { + openInventory(player); + return true; + } + + if (isRidable() && rider.lock() != NULL) { + return Animal::mobInteract(player); + } + + // consumables + if (itemstack != NULL) { + bool itemUsed = false; + + if (canWearArmor()) { + int armorType = -1; + + if (itemstack->id == Item::horseArmorMetal_Id) { + armorType = ARMOR_IRON; + } else if (itemstack->id == Item::horseArmorGold_Id) { + armorType = ARMOR_GOLD; + } else if (itemstack->id == Item::horseArmorDiamond_Id) { + armorType = ARMOR_DIAMOND; + } + + if (armorType >= 0) { + if (!isTamed()) { + makeMad(); + return true; + } + openInventory(player); + return true; + } + } + + if (!itemUsed && !isUndead()) { + float _heal = 0; + int _ageUp = 0; + int temper = 0; + + if (itemstack->id == Item::wheat_Id) { + _heal = 2; + _ageUp = 60; + temper = 3; + } else if (itemstack->id == Item::sugar_Id) { + _heal = 1; + _ageUp = 30; + temper = 3; + } else if (itemstack->id == Item::bread_Id) { + _heal = 7; + _ageUp = 180; + temper = 3; + } else if (itemstack->id == Tile::hayBlock_Id) { + _heal = 20; + _ageUp = 180; + } else if (itemstack->id == Item::apple_Id) { + _heal = 3; + _ageUp = 60; + temper = 3; + } else if (itemstack->id == Item::carrotGolden_Id) { + _heal = 4; + _ageUp = 60; + temper = 5; + if (isTamed() && getAge() == 0) { + itemUsed = true; + setInLove(); + } + } else if (itemstack->id == Item::apple_gold_Id) { + _heal = 10; + _ageUp = 240; + temper = 10; + if (isTamed() && getAge() == 0) { + itemUsed = true; + setInLove(); + } + } + if (getHealth() < getMaxHealth() && _heal > 0) { + heal(_heal); + itemUsed = true; + } + if (!isAdult() && _ageUp > 0) { + ageUp(_ageUp); + itemUsed = true; + } + if (temper > 0 && (itemUsed || !isTamed()) && + temper < getMaxTemper()) { + itemUsed = true; + modifyTemper(temper); + } + if (itemUsed) { + eatingHorse(); + } + } + + if (!isTamed() && !itemUsed) { + if (itemstack != NULL && + itemstack->interactEnemy( + player, std::dynamic_pointer_cast( + shared_from_this()))) { + return true; + } + makeMad(); + return true; + } + + if (!itemUsed && canWearBags() && !isChestedHorse()) { + if (itemstack->id == Tile::chest_Id) { + setChestedHorse(true); + playSound( + eSoundType_MOB_CHICKENPLOP, 1.0f, + (random->nextFloat() - random->nextFloat()) * 0.2f + 1.0f); + itemUsed = true; + createInventory(); + } + } + + if (!itemUsed && isRidable() && !isSaddled()) { + if (itemstack->id == Item::saddle_Id) { + openInventory(player); + return true; + } + } + + if (itemUsed) { + if (!player->abilities.instabuild) { + if (--itemstack->count == 0) { + player->inventory->setItem(player->inventory->selected, + nullptr); + } + } + return true; + } + } + + if (isRidable() && rider.lock() == NULL) { + // for name tag items and such, we must call the item's interaction + // method before riding + if (itemstack != NULL && + itemstack->interactEnemy( + player, + std::dynamic_pointer_cast(shared_from_this()))) { + return true; + } + doPlayerRide(player); + + app.DebugPrintf( + " Horse speed: %f\n", + (float)(getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED) + ->getValue())); + + return true; + } else { + return Animal::mobInteract(player); + } +} + +void EntityHorse::doPlayerRide(std::shared_ptr player) { + player->yRot = yRot; + player->xRot = xRot; + setEating(false); + setStanding(false); + if (!level->isClientSide) { + player->ride(shared_from_this()); + } +} + +/** + * Can this horse be trapped in an amulet? + */ +bool EntityHorse::isAmuletHorse() { return getType() == TYPE_SKELETON; } + +/** + * Can wear regular armor + */ +bool EntityHorse::canWearArmor() { return getType() == TYPE_HORSE; } + +/** + * able to carry bags + * + * @return + */ +bool EntityHorse::canWearBags() { + int type = getType(); + return type == TYPE_MULE || type == TYPE_DONKEY; +} + +bool EntityHorse::isImmobile() { + if (rider.lock() != NULL && isSaddled()) { + return true; + } + return isEating() || isStanding(); +} + +/** + * Rare horse that can be transformed into Nightmares or Bathorses or give + * ghost horses on dead + */ +bool EntityHorse::isPureBreed() { return getType() > 10 && getType() < 21; } + +/** + * Is this an Undead Horse? + * + * @return + */ +bool EntityHorse::isUndead() { + int type = getType(); + return type == TYPE_UNDEAD || type == TYPE_SKELETON; +} + +bool EntityHorse::isSterile() { return isUndead() || getType() == TYPE_MULE; } + +bool EntityHorse::isFood(std::shared_ptr itemInstance) { + // horses have their own food behaviors in mobInterract + return false; +} + +void EntityHorse::moveTail() { tailCounter = 1; } + +int EntityHorse::nameYOffset() { + if (isAdult()) { + return -80; + } else { + return (int)(-5 - getFoalScale() * 80.0f); + } +} + +void EntityHorse::die(DamageSource* damagesource) { + Animal::die(damagesource); + if (!level->isClientSide) { + dropMyStuff(); + } +} + +void EntityHorse::aiStep() { + if (random->nextInt(200) == 0) { + moveTail(); + } + + Animal::aiStep(); + + if (!level->isClientSide) { + if (random->nextInt(900) == 0 && deathTime == 0) { + heal(1); + } + + if (!isEating() && rider.lock() == NULL && random->nextInt(300) == 0) { + if (level->getTile(Mth::floor(x), Mth::floor(y) - 1, + Mth::floor(z)) == Tile::grass_Id) { + setEating(true); + } + } + + if (isEating() && ++countEating > 50) { + countEating = 0; + setEating(false); + } + + if (isBred() && !isAdult() && !isEating()) { + std::shared_ptr mommy = + getClosestMommy(shared_from_this(), 16); + if (mommy != NULL && distanceToSqr(mommy) > 4.0) { + Path* pathentity = level->findPath( + shared_from_this(), mommy, 16.0f, true, false, false, true); + setPath(pathentity); + } + } + } +} + +void EntityHorse::tick() { + Animal::tick(); + + // if client-side data values have changed, rebuild texture info + if (level->isClientSide && entityData->isDirty()) { + entityData->clearDirty(); + clearLayeredTextureInfo(); + } + + if (mouthCounter > 0 && ++mouthCounter > 30) { + mouthCounter = 0; + setHorseFlag(FLAG_OPEN_MOUTH, false); + } + + if (!level->isClientSide) { + if (standCounter > 0 && ++standCounter > 20) { + standCounter = 0; + setStanding(false); + } + } + + if (tailCounter > 0 && ++tailCounter > 8) { + tailCounter = 0; + } + + if (sprintCounter > 0) { + ++sprintCounter; + + if (sprintCounter > 300) { + sprintCounter = 0; + } + } + + eatAnimO = eatAnim; + if (isEating()) { + eatAnim += (1.0f - eatAnim) * .4f + .05f; + if (eatAnim > 1) { + eatAnim = 1; + } + } else { + eatAnim += (.0f - eatAnim) * .4f - .05f; + if (eatAnim < 0) { + eatAnim = 0; + } + } + standAnimO = standAnim; + if (isStanding()) { + // standing is incompatible with eating, so lock eat anim + eatAnimO = eatAnim = 0; + standAnim += (1.0f - standAnim) * .4f + .05f; + if (standAnim > 1) { + standAnim = 1; + } + } else { + allowStandSliding = false; + // the animation falling back to ground is slower in the beginning + standAnim += + (.8f * standAnim * standAnim * standAnim - standAnim) * .6f - .05f; + if (standAnim < 0) { + standAnim = 0; + } + } + mouthAnimO = mouthAnim; + if (getHorseFlag(FLAG_OPEN_MOUTH)) { + mouthAnim += (1.0f - mouthAnim) * .7f + .05f; + if (mouthAnim > 1) { + mouthAnim = 1; + } + } else { + mouthAnim += (.0f - mouthAnim) * .7f - .05f; + if (mouthAnim < 0) { + mouthAnim = 0; + } + } +} + +void EntityHorse::openMouth() { + if (!level->isClientSide) { + mouthCounter = 1; + setHorseFlag(FLAG_OPEN_MOUTH, true); + } +} + +bool EntityHorse::isReadyForParenting() { + return rider.lock() == NULL && riding == NULL && isTamed() && isAdult() && + !isSterile() && getHealth() >= getMaxHealth(); +} + +bool EntityHorse::renderName() { + return hasCustomName() && rider.lock() == NULL; +} + +bool EntityHorse::rideableEntity() { return true; } + +void EntityHorse::setUsingItemFlag(bool flag) { + setHorseFlag(FLAG_EATING, flag); +} + +void EntityHorse::setEating(bool state) { setUsingItemFlag(state); } + +void EntityHorse::setStanding(bool state) { + if (state) { + setEating(false); + } + setHorseFlag(FLAG_STANDING, state); +} + +void EntityHorse::stand() { + if (!level->isClientSide) { + standCounter = 1; + setStanding(true); + } +} + +void EntityHorse::makeMad() { + stand(); + int ambient = getMadSound(); + playSound(ambient, getSoundVolume(), getVoicePitch()); +} + +void EntityHorse::dropMyStuff() { + dropInventory(shared_from_this(), inventory); + dropBags(); +} + +void EntityHorse::dropInventory(std::shared_ptr entity, + std::shared_ptr animalchest) { + if (animalchest == NULL || level->isClientSide) return; + + for (int i = 0; i < animalchest->getContainerSize(); i++) { + std::shared_ptr itemstack = animalchest->getItem(i); + if (itemstack == NULL) { + continue; + } + spawnAtLocation(itemstack, 0); + } +} + +bool EntityHorse::tameWithName(std::shared_ptr player) { + setOwner(player->getName()); + setTamed(true); + return true; +} + +/** + * Overridden method to add control to mounts, should be moved to + * EntityLiving + */ +void EntityHorse::travel(float xa, float ya) { + // If the entity is not ridden by Player, then execute the normal + // Entityliving code + if (rider.lock() == NULL || !isSaddled()) { + footSize = .5f; + flyingSpeed = .02f; + Animal::travel(xa, ya); + return; + } + + yRotO = yRot = rider.lock()->yRot; + xRot = rider.lock()->xRot * 0.5f; + setRot(yRot, xRot); + yHeadRot = yBodyRot = yRot; + + std::shared_ptr livingRider = + std::dynamic_pointer_cast(rider.lock()); + xa = livingRider->xxa * .5f; + ya = livingRider->yya; + + // move much slower backwards + if (ya <= 0) { + ya *= .25f; + gallopSoundCounter = 0; + } + + if (onGround && playerJumpPendingScale == 0 && isStanding() && + !allowStandSliding) { + xa = 0; + ya = 0; + } + + if (playerJumpPendingScale > 0 && !getIsJumping() && onGround) { + yd = getCustomJump() * playerJumpPendingScale; + if (hasEffect(MobEffect::jump)) { + yd += (getEffect(MobEffect::jump)->getAmplifier() + 1) * .1f; + } + + setIsJumping(true); + hasImpulse = true; + + if (ya > 0) { + float sin = Mth::sin(yRot * PI / 180); + float cos = Mth::cos(yRot * PI / 180); + + xd += -0.4f * sin * playerJumpPendingScale; + zd += 0.4f * cos * playerJumpPendingScale; + + playSound(eSoundType_MOB_HORSE_JUMP, .4f, 1); + } + playerJumpPendingScale = 0; + } + + footSize = 1; + flyingSpeed = getSpeed() * .1f; + if (!level->isClientSide) { + setSpeed((float)(getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED) + ->getValue())); + Animal::travel(xa, ya); + } + + if (onGround) { + // blood - fixes jump bug + playerJumpPendingScale = 0; + setIsJumping(false); + } + walkAnimSpeedO = walkAnimSpeed; + double dx = x - xo; + double dz = z - zo; + float wst = Mth::sqrt(dx * dx + dz * dz) * 4.0f; + if (wst > 1.0f) { + wst = 1.0f; + } + + walkAnimSpeed += (wst - walkAnimSpeed) * 0.4f; + walkAnimPos += walkAnimSpeed; +} + +void EntityHorse::addAdditonalSaveData(CompoundTag* tag) { + Animal::addAdditonalSaveData(tag); + + tag->putBoolean(L"EatingHaystack", isEating()); + tag->putBoolean(L"ChestedHorse", isChestedHorse()); + tag->putBoolean(L"HasReproduced", getHasReproduced()); + tag->putBoolean(L"Bred", isBred()); + tag->putInt(L"Type", getType()); + tag->putInt(L"Variant", getVariant()); + tag->putInt(L"Temper", getTemper()); + tag->putBoolean(L"Tame", isTamed()); + tag->putString(L"OwnerName", getOwnerName()); + + if (isChestedHorse()) { + ListTag* listTag = new ListTag(); + + for (int i = INV_BASE_COUNT; i < inventory->getContainerSize(); i++) { + std::shared_ptr stack = inventory->getItem(i); + + if (stack != NULL) { + CompoundTag* compoundTag = new CompoundTag(); + + compoundTag->putByte(L"Slot", (uint8_t)i); + + stack->save(compoundTag); + listTag->add(compoundTag); + } + } + tag->put(L"Items", listTag); + } + + if (inventory->getItem(INV_SLOT_ARMOR) != NULL) { + tag->put(L"ArmorItem", inventory->getItem(INV_SLOT_ARMOR) + ->save(new CompoundTag(L"ArmorItem"))); + } + if (inventory->getItem(INV_SLOT_SADDLE) != NULL) { + tag->put(L"SaddleItem", inventory->getItem(INV_SLOT_SADDLE) + ->save(new CompoundTag(L"SaddleItem"))); + } +} + +void EntityHorse::readAdditionalSaveData(CompoundTag* tag) { + Animal::readAdditionalSaveData(tag); + setEating(tag->getBoolean(L"EatingHaystack")); + setBred(tag->getBoolean(L"Bred")); + setChestedHorse(tag->getBoolean(L"ChestedHorse")); + setReproduced(tag->getBoolean(L"HasReproduced")); + setType(tag->getInt(L"Type")); + setVariant(tag->getInt(L"Variant")); + setTemper(tag->getInt(L"Temper")); + setTamed(tag->getBoolean(L"Tame")); + if (tag->contains(L"OwnerName")) { + setOwner(tag->getString(L"OwnerName")); + } + + // 4J: This is for handling old save data, not needed on console + /*AttributeInstance *oldSpeedAttribute = + getAttributes()->getInstance(SharedMonsterAttributes::MOVEMENT_SPEED); + + if (oldSpeedAttribute != NULL) + { + getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED)->setBaseValue(oldSpeedAttribute->getBaseValue() + * 0.25f); + }*/ + + if (isChestedHorse()) { + ListTag* nbttaglist = + (ListTag*)tag->getList(L"Items"); + createInventory(); + + for (int i = 0; i < nbttaglist->size(); i++) { + CompoundTag* compoundTag = nbttaglist->get(i); + int slot = compoundTag->getByte(L"Slot") & 0xFF; + + if (slot >= INV_BASE_COUNT && + slot < inventory->getContainerSize()) { + inventory->setItem(slot, ItemInstance::fromTag(compoundTag)); + } + } + } + + if (tag->contains(L"ArmorItem")) { + std::shared_ptr armor = + ItemInstance::fromTag(tag->getCompound(L"ArmorItem")); + if (armor != NULL && isHorseArmor(armor->id)) { + inventory->setItem(INV_SLOT_ARMOR, armor); + } + } + + if (tag->contains(L"SaddleItem")) { + std::shared_ptr saddleItem = + ItemInstance::fromTag(tag->getCompound(L"SaddleItem")); + if (saddleItem != NULL && saddleItem->id == Item::saddle_Id) { + inventory->setItem(INV_SLOT_SADDLE, saddleItem); + } + } else if (tag->getBoolean(L"Saddle")) { + inventory->setItem( + INV_SLOT_SADDLE, + std::shared_ptr(new ItemInstance(Item::saddle))); + } + updateEquipment(); +} + +bool EntityHorse::canMate(std::shared_ptr partner) { + if (partner == shared_from_this()) return false; + if (partner->GetType() != GetType()) return false; + + std::shared_ptr horsePartner = + std::dynamic_pointer_cast(partner); + + if (!isReadyForParenting() || !horsePartner->isReadyForParenting()) { + return false; + } + int type = getType(); + int pType = horsePartner->getType(); + + return type == pType || (type == TYPE_HORSE && pType == TYPE_DONKEY) || + (type == TYPE_DONKEY && pType == TYPE_HORSE); +} + +std::shared_ptr EntityHorse::getBreedOffspring( + std::shared_ptr partner) { + std::shared_ptr horsePartner = + std::dynamic_pointer_cast(partner); + std::shared_ptr baby = + std::shared_ptr(new EntityHorse(level)); + + int type = getType(); + int partnerType = horsePartner->getType(); + int babyType = TYPE_HORSE; + + if (type == partnerType) { + babyType = type; + } else if (type == TYPE_HORSE && partnerType == TYPE_DONKEY || + type == TYPE_DONKEY && partnerType == TYPE_HORSE) { + babyType = TYPE_MULE; + } + + // select skin and marking colors + if (babyType == TYPE_HORSE) { + int skinResult; + int selectSkin = random->nextInt(9); + if (selectSkin < 4) { + skinResult = getVariant() & 0xff; + } else if (selectSkin < 8) { + skinResult = horsePartner->getVariant() & 0xff; + } else { + skinResult = random->nextInt(VARIANTS); + } + + int selectMarking = random->nextInt(5); + if (selectMarking < 4) { + skinResult |= getVariant() & 0xff00; + } else if (selectMarking < 8) { + skinResult |= horsePartner->getVariant() & 0xff00; + } else { + skinResult |= (random->nextInt(MARKINGS) << 8) & 0xff00; + } + baby->setVariant(skinResult); + } + + baby->setType(babyType); + + // generate stats from parents + double maxHealth = + getAttribute(SharedMonsterAttributes::MAX_HEALTH)->getBaseValue() + + partner->getAttribute(SharedMonsterAttributes::MAX_HEALTH) + ->getBaseValue() + + generateRandomMaxHealth(); + baby->getAttribute(SharedMonsterAttributes::MAX_HEALTH) + ->setBaseValue(maxHealth / 3.0f); + + double jumpStrength = getAttribute(JUMP_STRENGTH)->getBaseValue() + + partner->getAttribute(JUMP_STRENGTH)->getBaseValue() + + generateRandomJumpStrength(); + baby->getAttribute(JUMP_STRENGTH)->setBaseValue(jumpStrength / 3.0f); + + double speed = + getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED)->getBaseValue() + + partner->getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED) + ->getBaseValue() + + generateRandomSpeed(); + baby->getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED) + ->setBaseValue(speed / 3.0f); + + return baby; +} + +MobGroupData* EntityHorse::finalizeMobSpawn( + MobGroupData* groupData, int extraData /*= 0*/) // 4J Added extraData param +{ + groupData = Animal::finalizeMobSpawn(groupData); + + int type = 0; + int variant = 0; + + if (dynamic_cast(groupData) != NULL) { + type = ((HorseGroupData*)groupData)->horseType; + variant = ((HorseGroupData*)groupData)->horseVariant & 0xff | + (random->nextInt(MARKINGS) << 8); + } else { + if (extraData != 0) { + type = extraData - 1; + } else if (random->nextInt(10) == 0) { + type = TYPE_DONKEY; + } else { + type = TYPE_HORSE; + } + + if (type == TYPE_HORSE) { + int skin = random->nextInt(VARIANTS); + int mark = random->nextInt(MARKINGS); + variant = skin | (mark << 8); + } + groupData = new HorseGroupData(type, variant); + } + + setType(type); + setVariant(variant); + + if (random->nextInt(5) == 0) { + setAge(AgableMob::BABY_START_AGE); + } + + if (type == TYPE_SKELETON || type == TYPE_UNDEAD) { + getAttribute(SharedMonsterAttributes::MAX_HEALTH)->setBaseValue(15); + getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED) + ->setBaseValue(0.2f); + } else { + getAttribute(SharedMonsterAttributes::MAX_HEALTH) + ->setBaseValue(generateRandomMaxHealth()); + if (type == TYPE_HORSE) { + getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED) + ->setBaseValue(generateRandomSpeed()); + } else { + getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED) + ->setBaseValue(0.175f); + } + } + if (type == TYPE_MULE || type == TYPE_DONKEY) { + getAttribute(JUMP_STRENGTH)->setBaseValue(.5f); + } else { + getAttribute(JUMP_STRENGTH)->setBaseValue(generateRandomJumpStrength()); + } + setHealth(getMaxHealth()); + + return groupData; +} + +float EntityHorse::getEatAnim(float a) { + return eatAnimO + (eatAnim - eatAnimO) * a; +} + +float EntityHorse::getStandAnim(float a) { + return standAnimO + (standAnim - standAnimO) * a; +} + +float EntityHorse::getMouthAnim(float a) { + return mouthAnimO + (mouthAnim - mouthAnimO) * a; +} + +bool EntityHorse::useNewAi() { return true; } + +void EntityHorse::onPlayerJump(int jumpAmount) { + if (isSaddled()) { + if (jumpAmount < 0) { + jumpAmount = 0; + } else { + allowStandSliding = true; + stand(); + } + + if (jumpAmount >= 90) { + playerJumpPendingScale = 1.0f; + } else { + playerJumpPendingScale = .4f + .4f * (float)jumpAmount / 90.0f; + } + } +} + +void EntityHorse::spawnTamingParticles(bool success) { + ePARTICLE_TYPE particle = + success ? eParticleType_heart : eParticleType_smoke; + + for (int i = 0; i < 7; i++) { + double xa = random->nextGaussian() * 0.02; + double ya = random->nextGaussian() * 0.02; + double za = random->nextGaussian() * 0.02; + level->addParticle( + particle, x + random->nextFloat() * bbWidth * 2 - bbWidth, + y + .5f + random->nextFloat() * bbHeight, + z + random->nextFloat() * bbWidth * 2 - bbWidth, xa, ya, za); + } +} + +void EntityHorse::handleEntityEvent(uint8_t id) { + if (id == EntityEvent::TAMING_SUCCEEDED) { + spawnTamingParticles(true); + } else if (id == EntityEvent::TAMING_FAILED) { + spawnTamingParticles(false); + } else { + Animal::handleEntityEvent(id); + } +} + +void EntityHorse::positionRider() { + Animal::positionRider(); + + if (standAnimO > 0) { + float sin = Mth::sin(yBodyRot * PI / 180); + float cos = Mth::cos(yBodyRot * PI / 180); + float dist = .7f * standAnimO; + float height = .15f * standAnimO; + + rider.lock()->setPos( + x + dist * sin, + y + getRideHeight() + rider.lock()->getRidingHeight() + height, + z - dist * cos); + + if (rider.lock()->instanceof(eTYPE_LIVINGENTITY)) { + std::shared_ptr livingRider = + std::dynamic_pointer_cast(rider.lock()); + livingRider->yBodyRot = yBodyRot; + } + } +} + +// Health is between 15 and 30 +float EntityHorse::generateRandomMaxHealth() { + return 15.0f + random->nextInt(8) + random->nextInt(9); +} + +double EntityHorse::generateRandomJumpStrength() { + return .4f + random->nextDouble() * .2 + random->nextDouble() * .2 + + random->nextDouble() * .2; +} + +double EntityHorse::generateRandomSpeed() { + double speed = (0.45f + random->nextDouble() * .3 + + random->nextDouble() * .3 + random->nextDouble() * .3) * + 0.25f; + app.DebugPrintf(" Speed: %f\n", speed); + return speed; +} + +EntityHorse::HorseGroupData::HorseGroupData(int type, int variant) { + horseType = type; + horseVariant = variant; +} + +bool EntityHorse::isHorseArmor(int itemId) { + return itemId == Item::horseArmorMetal_Id || + itemId == Item::horseArmorGold_Id || + itemId == Item::horseArmorDiamond_Id; +} + +bool EntityHorse::onLadder() { + // prevent horses from climbing ladders + return false; +} + +std::shared_ptr EntityHorse::getOwner() { + return level->getPlayerByUUID(getOwnerName()); +} \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/EntityHorse.h b/Minecraft.World/Entities/Mobs/EntityHorse.h new file mode 100644 index 000000000..ccca686a5 --- /dev/null +++ b/Minecraft.World/Entities/Mobs/EntityHorse.h @@ -0,0 +1,343 @@ +#pragma once + +#include "Animal.h" +#include "../../Headers/net.minecraft.world.ContainerListener.h" +#include "MobGroupData.h" +#include "../EntitySelector.h" + +class Attribute; +class AnimalChest; + +class HorseEntitySelector : public EntitySelector { +public: + bool matches(std::shared_ptr entity) const; +}; + +class EntityHorse : public Animal, + public net_minecraft_world::ContainerListener { +public: + eINSTANCEOF GetType() { return eTYPE_HORSE; } + static Entity* create(Level* level) { return new EntityHorse(level); } + +private: + static const std::wstring TEX_FOLDER; + + static const EntitySelector* PARENT_HORSE_SELECTOR; + + static Attribute* JUMP_STRENGTH; + + static const int DATA_ID_HORSE_FLAGS = 16; + static const int DATA_ID_TYPE = 19; + static const int DATA_ID_TYPE_VARIANT = 20; + static const int DATA_ID_OWNER_NAME = 21; + static const int DATA_ID_ARMOR = 22; + + static const int FLAG_TAME = 1 << 1; + static const int FLAG_SADDLE = 1 << 2; + static const int FLAG_CHESTED = 1 << 3; + static const int FLAG_BRED = 1 << 4; + static const int FLAG_EATING = 1 << 5; + static const int FLAG_STANDING = 1 << 6; + static const int FLAG_OPEN_MOUTH = 1 << 7; + +public: + static const int INV_SLOT_SADDLE = 0; + static const int INV_SLOT_ARMOR = 1; + static const int INV_BASE_COUNT = 2; + static const int INV_DONKEY_CHEST_COUNT = 15; + + // TODO: USE ENUMS! // Original comment + static const int ARMOR_NONE = 0; + static const int ARMOR_IRON = 1; + static const int ARMOR_GOLD = 2; + static const int ARMOR_DIAMOND = 3; + +private: + static const int ARMORS = 4; + static std::wstring ARMOR_TEXTURES[ARMORS]; + static int ARMOR_TEXTURES_ID[ARMORS]; + static std::wstring ARMOR_HASHES[ARMORS]; + static int ARMOR_PROTECTION[ARMORS]; + +public: + static const int TYPE_HORSE = 0; + static const int TYPE_DONKEY = 1; + static const int TYPE_MULE = 2; + static const int TYPE_UNDEAD = 3; + static const int TYPE_SKELETON = 4; + + static const int VARIANT_WHITE = 0; + static const int VARIANT_CREAMY = 1; + static const int VARIANT_CHESTNUT = 2; + static const int VARIANT_BROWN = 3; + static const int VARIANT_BLACK = 4; + static const int VARIANT_GRAY = 5; + static const int VARIANT_DARKBROWN = 6; + +private: + static const int VARIANTS = 7; + static std::wstring VARIANT_TEXTURES[VARIANTS]; + static int VARIANT_TEXTURES_ID[VARIANTS]; + static std::wstring VARIANT_HASHES[VARIANTS]; + +public: + static const int MARKING_NONE = 0; + static const int MARKING_WHITE_DETAILS = 1; + static const int MARKING_WHITE_FIELDS = 2; + static const int MARKING_WHITE_DOTS = 3; + static const int MARKING_BLACK_DOTS = 4; + +private: + static const int MARKINGS = 5; + static std::wstring MARKING_TEXTURES[MARKINGS]; + static int MARKING_TEXTURES_ID[MARKINGS]; + static std::wstring MARKING_HASHES[MARKINGS]; + +private: + int countEating; // eating timer + int mouthCounter; + int standCounter; + +public: + int tailCounter; + int sprintCounter; + +protected: + bool isEntityJumping; + +private: + std::shared_ptr inventory; + bool hasReproduced; + +protected: + int temper; + float playerJumpPendingScale; + +private: + bool allowStandSliding; + + // animation data + float eatAnim, eatAnimO; + float standAnim, standAnimO; + float mouthAnim, mouthAnimO; + +public: + EntityHorse(Level* world); + ~EntityHorse(); + +protected: + virtual void defineSynchedData(); + +public: + virtual void setType(int i); + virtual int getType(); + virtual void setVariant(int i); + virtual int getVariant(); + virtual std::wstring getAName(); + +private: + virtual bool getHorseFlag(int flag); + virtual void setHorseFlag(int flag, bool value); + +public: + virtual bool isAdult(); + virtual bool isTamed(); + virtual bool isRidable(); + virtual std::wstring getOwnerName(); + virtual void setOwner(const std::wstring& par1Str); + virtual float getFoalScale(); + virtual void updateSize(bool isBaby); + virtual bool getIsJumping(); + virtual void setTamed(bool flag); + virtual void setIsJumping(bool flag); + virtual bool canBeLeashed(); + +protected: + virtual void onLeashDistance(float distanceToLeashHolder); + +public: + virtual bool isChestedHorse(); + virtual int getArmorType(); + virtual int getArmorTypeForItem(std::shared_ptr armorItem); + virtual bool isEating(); + virtual bool isStanding(); + virtual bool isBred(); + virtual bool getHasReproduced(); + virtual void setArmorType(int i); + virtual void setBred(bool flag); + virtual void setChestedHorse(bool flag); + virtual void setReproduced(bool flag); + virtual void setSaddled(bool flag); + virtual int getTemper(); + virtual void setTemper(int temper); + virtual int modifyTemper(int amount); + virtual bool hurt(DamageSource* damagesource, float dmg); + virtual int getArmorValue(); + virtual bool isPushable(); + virtual bool checkSpawningBiome(); + virtual void dropBags(); + +private: + virtual void eatingHorse(); + +protected: + virtual void causeFallDamage(float fallDistance); + +private: + virtual int getInventorySize(); + virtual void createInventory(); + virtual void updateEquipment(); + +public: + virtual void containerChanged(); + virtual bool canSpawn(); + +protected: + virtual std::shared_ptr getClosestMommy( + std::shared_ptr baby, double searchRadius); + +public: + virtual double getCustomJump(); + +protected: + virtual int getDeathSound(); + virtual int getDeathLoot(); + virtual int getHurtSound(); + +public: + virtual bool isSaddled(); + +protected: + virtual int getAmbientSound(); + virtual int getMadSound(); + +private: + int gallopSoundCounter; + +protected: + virtual void playStepSound(int xt, int yt, int zt, int t); + virtual void registerAttributes(); + +public: + virtual int getMaxSpawnClusterSize(); + virtual int getMaxTemper(); + +protected: + virtual float getSoundVolume(); + +public: + virtual int getAmbientSoundInterval(); + virtual bool hasLayeredTextures(); + +private: + std::wstring layerTextureHashName; + intArray layerTextureLayers; + +private: + virtual void clearLayeredTextureInfo(); + virtual void rebuildLayeredTextureInfo(); + +public: + virtual std::wstring getLayeredTextureHashName(); + virtual intArray getLayeredTextureLayers(); + virtual void openInventory(std::shared_ptr player); + virtual bool mobInteract(std::shared_ptr player); + +private: + virtual void doPlayerRide(std::shared_ptr player); + +public: + virtual bool isAmuletHorse(); + virtual bool canWearArmor(); + virtual bool canWearBags(); + +protected: + virtual bool isImmobile(); + +public: + virtual bool isPureBreed(); + virtual bool isUndead(); + virtual bool isSterile(); + virtual bool isFood(std::shared_ptr itemInstance); + +private: + virtual void moveTail(); + +public: + virtual int nameYOffset(); + virtual void die(DamageSource* damagesource); + virtual void aiStep(); + virtual void tick(); + +private: + virtual void openMouth(); + +public: + // 4J-JEV: Made public for tooltip code, doesn't change state anyway. + virtual bool isReadyForParenting(); + +public: + virtual bool renderName(); + virtual bool rideableEntity(); + virtual void setUsingItemFlag(bool flag); + virtual void setEating(bool state); + virtual void setStanding(bool state); + +private: + virtual void stand(); + +public: + virtual void makeMad(); + virtual void dropMyStuff(); + +private: + virtual void dropInventory(std::shared_ptr entity, + std::shared_ptr animalchest); + +public: + virtual bool tameWithName(std::shared_ptr player); + virtual void travel(float xa, float ya); + virtual void addAdditonalSaveData(CompoundTag* tag); + virtual void readAdditionalSaveData(CompoundTag* tag); + virtual bool canMate(std::shared_ptr partner); + virtual std::shared_ptr getBreedOffspring( + std::shared_ptr partner); + virtual MobGroupData* finalizeMobSpawn( + MobGroupData* groupData, + int extraData = 0); // 4J Added extraData param + virtual float getEatAnim(float a); + virtual float getStandAnim(float a); + virtual float getMouthAnim(float a); + +protected: + virtual bool useNewAi(); + +public: + virtual void onPlayerJump(int jumpAmount); + +protected: + virtual void spawnTamingParticles(bool success); + +public: + virtual void handleEntityEvent(uint8_t id); + virtual void positionRider(); + +private: + virtual float generateRandomMaxHealth(); + virtual double generateRandomJumpStrength(); + virtual double generateRandomSpeed(); + + std::shared_ptr getOwner(); + +public: + class HorseGroupData : public MobGroupData { + public: + int horseType; + int horseVariant; + + HorseGroupData(int type, int variant); + }; + + static bool isHorseArmor(int itemId); + virtual bool onLadder(); +}; \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/ExperienceOrb.cpp b/Minecraft.World/Entities/Mobs/ExperienceOrb.cpp index e5697ce0d..83aa925af 100644 --- a/Minecraft.World/Entities/Mobs/ExperienceOrb.cpp +++ b/Minecraft.World/Entities/Mobs/ExperienceOrb.cpp @@ -81,8 +81,8 @@ void ExperienceOrb::tick() { yd = 0.2f; xd = (random->nextFloat() - random->nextFloat()) * 0.2f; zd = (random->nextFloat() - random->nextFloat()) * 0.2f; - level->playSound(shared_from_this(), eSoundType_RANDOM_FIZZ, 0.4f, - 2.0f + random->nextFloat() * 0.4f); + playSound(eSoundType_RANDOM_FIZZ, 0.4f, + 2.0f + random->nextFloat() * 0.4f); } checkInTile(x, (bb->y0 + bb->y1) / 2, z); @@ -150,7 +150,8 @@ bool ExperienceOrb::updateInWaterState() { void ExperienceOrb::burn(int dmg) { hurt(DamageSource::inFire, dmg); } -bool ExperienceOrb::hurt(DamageSource* source, int damage) { +bool ExperienceOrb::hurt(DamageSource* source, float damage) { + if (isInvulnerable()) return false; markHurt(); health -= damage; if (health <= 0) { @@ -177,8 +178,8 @@ void ExperienceOrb::playerTouch(std::shared_ptr player) { if (throwTime == 0 && player->takeXpDelay == 0) { player->takeXpDelay = 2; // 4J - sound change brought forward from 1.2.3 - level->playSound( - shared_from_this(), eSoundType_RANDOM_ORB, 0.1f, + playSound( + eSoundType_RANDOM_ORB, 0.1f, 0.5f * ((random->nextFloat() - random->nextFloat()) * 0.7f + 1.8f)); player->take(shared_from_this(), 1); player->increaseXp(value); diff --git a/Minecraft.World/Entities/Mobs/ExperienceOrb.h b/Minecraft.World/Entities/Mobs/ExperienceOrb.h index 2d61f4649..0ecd4de42 100644 --- a/Minecraft.World/Entities/Mobs/ExperienceOrb.h +++ b/Minecraft.World/Entities/Mobs/ExperienceOrb.h @@ -45,7 +45,7 @@ protected: virtual void burn(int dmg); public: - virtual bool hurt(DamageSource* source, int damage); + virtual bool hurt(DamageSource* source, float damage); virtual void addAdditonalSaveData(CompoundTag* entityTag); virtual void readAdditionalSaveData(CompoundTag* tag); virtual void playerTouch(std::shared_ptr player); diff --git a/Minecraft.World/Entities/Mobs/EyeOfEnderSignal.cpp b/Minecraft.World/Entities/Mobs/EyeOfEnderSignal.cpp index 152c5f56e..71981b9b9 100644 --- a/Minecraft.World/Entities/Mobs/EyeOfEnderSignal.cpp +++ b/Minecraft.World/Entities/Mobs/EyeOfEnderSignal.cpp @@ -40,8 +40,8 @@ EyeOfEnderSignal::EyeOfEnderSignal(Level* level, double x, double y, double z) setSize(0.25f, 0.25f); - this->setPos(x, y, z); - this->heightOffset = 0; + setPos(x, y, z); + heightOffset = 0; } void EyeOfEnderSignal::signalTo(double tx, int ty, double tz) { @@ -68,8 +68,8 @@ void EyeOfEnderSignal::lerpMotion(double xd, double yd, double zd) { this->zd = zd; if (xRotO == 0 && yRotO == 0) { float sd = (float)sqrt(xd * xd + zd * zd); - yRotO = this->yRot = (float)(atan2(xd, zd) * 180 / PI); - xRotO = this->xRot = (float)(atan2(yd, (double)sd) * 180 / PI); + yRotO = yRot = (float)(atan2(xd, zd) * 180 / PI); + xRotO = xRot = (float)(atan2(yd, (double)sd) * 180 / PI); } } diff --git a/Minecraft.World/Entities/Mobs/Fireball.cpp b/Minecraft.World/Entities/Mobs/Fireball.cpp index a138dfe12..1bf7bf6df 100644 --- a/Minecraft.World/Entities/Mobs/Fireball.cpp +++ b/Minecraft.World/Entities/Mobs/Fireball.cpp @@ -55,8 +55,8 @@ Fireball::Fireball(Level* level, double x, double y, double z, double xa, setSize(16 / 16.0f, 16 / 16.0f); - this->moveTo(x, y, z, yRot, xRot); - this->setPos(x, y, z); + moveTo(x, y, z, yRot, xRot); + setPos(x, y, z); double dd = sqrt(xa * xa + ya * ya + za * za); @@ -74,8 +74,8 @@ Fireball::Fireball(Level* level, double x, double y, double z, double xa, } } -Fireball::Fireball(Level* level, std::shared_ptr mob, double xa, double ya, - double za) +Fireball::Fireball(Level* level, std::shared_ptr mob, double xa, + double ya, double za) : Entity(level) { // 4J Stu - This function call had to be moved here from the Entity ctor to // ensure that the derived version of the function is called @@ -83,13 +83,13 @@ Fireball::Fireball(Level* level, std::shared_ptr mob, double xa, double ya, _init(); - this->owner = mob; + owner = mob; setSize(16 / 16.0f, 16 / 16.0f); - this->moveTo(mob->x, mob->y, mob->z, mob->yRot, mob->xRot); - this->setPos(x, y, z); - this->heightOffset = 0; + moveTo(mob->x, mob->y, mob->z, mob->yRot, mob->xRot); + setPos(x, y, z); + heightOffset = 0; xd = yd = zd = 0.0; @@ -177,7 +177,7 @@ void Fireball::tick() { } std::shared_ptr hitEntity = nullptr; std::vector >* objects = level->getEntities( - shared_from_this(), this->bb->expand(xd, yd, zd)->grow(1, 1, 1)); + shared_from_this(), bb->expand(xd, yd, zd)->grow(1, 1, 1)); double nearest = 0; AUTO_VAR(itEnd, objects->end()); for (AUTO_VAR(it, objects->begin()); it != itEnd; it++) { @@ -214,8 +214,8 @@ void Fireball::tick() { z += zd; double sd = sqrt(xd * xd + zd * zd); - yRot = (float)(atan2(xd, zd) * 180 / PI); - xRot = (float)(atan2(yd, sd) * 180 / PI); + yRot = (float)(atan2(zd, xd) * 180 / PI) + 90; + xRot = (float)(atan2(sd, yd) * 180 / PI) - 90; while (xRot - xRotO < -180) xRotO -= 360; while (xRot - xRotO >= 180) xRotO += 360; @@ -226,7 +226,7 @@ void Fireball::tick() { xRot = xRotO + (xRot - xRotO) * 0.2f; yRot = yRotO + (yRot - yRotO) * 0.2f; - float inertia = 0.95f; + float inertia = getInertia(); if (isInWater()) { for (int i = 0; i < 4; i++) { float s = 1 / 4.0f; @@ -260,24 +260,7 @@ void Fireball::tick() { setPos(x, y, z); } -void Fireball::onHit(HitResult* res) { - if (!level->isClientSide) { - if (res->entity != NULL) { - DamageSource* damageSource = DamageSource::fireball( - std::dynamic_pointer_cast(shared_from_this()), owner); - if (res->entity->hurt(damageSource, 6)) { - } else { - } - delete damageSource; - } - - bool destroyBlocks = - true; // level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); - level->explode(nullptr, x, y, z, 1, true, destroyBlocks); - - remove(); - } -} +float Fireball::getInertia() { return 0.95f; } void Fireball::addAdditonalSaveData(CompoundTag* tag) { tag->putShort(L"xTile", (short)xTile); @@ -285,8 +268,7 @@ void Fireball::addAdditonalSaveData(CompoundTag* tag) { tag->putShort(L"zTile", (short)zTile); tag->putByte(L"inTile", (uint8_t)lastTile); tag->putByte(L"inGround", (uint8_t)(inGround ? 1 : 0)); - tag->put(L"direction", - this->newDoubleList(3, this->xd, this->yd, this->zd)); + tag->put(L"direction", newDoubleList(3, xd, yd, zd)); } void Fireball::readAdditionalSaveData(CompoundTag* tag) { @@ -301,11 +283,11 @@ void Fireball::readAdditionalSaveData(CompoundTag* tag) { if (tag->contains(L"direction")) { ListTag* listTag = (ListTag*)tag->getList(L"direction"); - this->xd = ((DoubleTag*)listTag->get(0))->data; - this->yd = ((DoubleTag*)listTag->get(1))->data; - this->zd = ((DoubleTag*)listTag->get(2))->data; + xd = ((DoubleTag*)listTag->get(0))->data; + yd = ((DoubleTag*)listTag->get(1))->data; + zd = ((DoubleTag*)listTag->get(2))->data; } else { - this->remove(); + remove(); } } @@ -313,7 +295,8 @@ bool Fireball::isPickable() { return true; } float Fireball::getPickRadius() { return 1; } -bool Fireball::hurt(DamageSource* source, int damage) { +bool Fireball::hurt(DamageSource* source, float damage) { + if (isInvulnerable()) return false; markHurt(); if (source->getEntity() != NULL) { @@ -326,10 +309,9 @@ bool Fireball::hurt(DamageSource* source, int damage) { yPower = yd * 0.1; zPower = zd * 0.1; } - std::shared_ptr mob = - std::dynamic_pointer_cast(source->getEntity()); - if (mob != NULL) { - owner = mob; + if (source->getEntity()->instanceof(eTYPE_LIVINGENTITY)) { + owner = + std::dynamic_pointer_cast(source->getEntity()); } return true; } @@ -342,8 +324,6 @@ float Fireball::getBrightness(float a) { return 1.0f; } int Fireball::getLightColor(float a) { return 15 << 20 | 15 << 4; } -bool Fireball::shouldBurn() { return true; } - -int Fireball::getIcon() { return 14 + 2 * 16; } - ePARTICLE_TYPE Fireball::getTrailParticleType() { return eParticleType_smoke; } + +bool Fireball::shouldBurn() { return true; } \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/Fireball.h b/Minecraft.World/Entities/Mobs/Fireball.h index 29534e61c..27e8aecdc 100644 --- a/Minecraft.World/Entities/Mobs/Fireball.h +++ b/Minecraft.World/Entities/Mobs/Fireball.h @@ -8,7 +8,6 @@ class HitResult; class Fireball : public Entity { public: eINSTANCEOF GetType() { return eTYPE_FIREBALL; } - static Entity* create(Level* level) { return new Fireball(level); } private: int xTile; @@ -20,7 +19,7 @@ private: bool inGround; public: - std::shared_ptr owner; + std::shared_ptr owner; private: int life; @@ -42,30 +41,29 @@ public: Fireball(Level* level, double x, double y, double z, double xa, double ya, double za); - Fireball(Level* level, std::shared_ptr mob, double xa, double ya, - double za); + Fireball(Level* level, std::shared_ptr mob, double xa, + double ya, double za); public: virtual void tick(); protected: - virtual void onHit(HitResult* res); + virtual float getInertia(); + virtual void onHit(HitResult* res) = 0; public: virtual void addAdditonalSaveData(CompoundTag* tag); virtual void readAdditionalSaveData(CompoundTag* tag); virtual bool isPickable(); virtual float getPickRadius(); - virtual bool hurt(DamageSource* source, int damage); + virtual bool hurt(DamageSource* source, float damage); virtual float getShadowHeightOffs(); virtual float getBrightness(float a); virtual int getLightColor(float a); - // 4J Added TU9 - virtual bool shouldBurn(); - virtual int getIcon(); - protected: // 4J Added TU9 virtual ePARTICLE_TYPE getTrailParticleType(); + + virtual bool shouldBurn(); }; diff --git a/Minecraft.World/Entities/Mobs/FishingHook.cpp b/Minecraft.World/Entities/Mobs/FishingHook.cpp index 9a193ed7f..f5bbb74d7 100644 --- a/Minecraft.World/Entities/Mobs/FishingHook.cpp +++ b/Minecraft.World/Entities/Mobs/FishingHook.cpp @@ -63,19 +63,19 @@ FishingHook::FishingHook(Level* level, std::shared_ptr mob) : Entity(level) { _init(); - this->owner = mob; + owner = mob; // 4J Stu - Moved this outside the ctor // owner->fishing = std::dynamic_pointer_cast( // shared_from_this() ); - this->moveTo(mob->x, mob->y + 1.62 - mob->heightOffset, mob->z, mob->yRot, - mob->xRot); + moveTo(mob->x, mob->y + 1.62 - mob->heightOffset, mob->z, mob->yRot, + mob->xRot); x -= Mth::cos(yRot / 180 * PI) * 0.16f; y -= 0.1f; z -= Mth::sin(yRot / 180 * PI) * 0.16f; - this->setPos(x, y, z); - this->heightOffset = 0; + setPos(x, y, z); + heightOffset = 0; float speed = 0.4f; xd = (-Mth::sin(yRot / 180 * PI) * Mth::cos(xRot / 180 * PI)) * speed; @@ -115,8 +115,8 @@ void FishingHook::shoot(double xd, double yd, double zd, float pow, double sd = sqrt(xd * xd + zd * zd); - yRotO = this->yRot = (float)(atan2(xd, zd) * 180 / PI); - xRotO = this->xRot = (float)(atan2(yd, sd) * 180 / PI); + yRotO = yRot = (float)(atan2(xd, zd) * 180 / PI); + xRotO = xRot = (float)(atan2(yd, sd) * 180 / PI); life = 0; } @@ -130,9 +130,9 @@ void FishingHook::lerpTo(double x, double y, double z, float yRot, float xRot, lSteps = steps; - this->xd = lxd; - this->yd = lyd; - this->zd = lzd; + xd = lxd; + yd = lyd; + zd = lzd; } void FishingHook::lerpMotion(double xd, double yd, double zd) { @@ -155,8 +155,8 @@ void FishingHook::tick() { xRot += (float)((lxr - xRot) / lSteps); lSteps--; - this->setPos(xt, yt, zt); - this->setRot(yRot, xRot); + setPos(xt, yt, zt); + setRot(yRot, xRot); return; } @@ -164,7 +164,7 @@ void FishingHook::tick() { std::shared_ptr selectedItem = owner->getSelectedItem(); if (owner->removed || !owner->isAlive() || selectedItem == NULL || selectedItem->getItem() != Item::fishingRod || - this->distanceToSqr(owner) > 32 * 32) { + distanceToSqr(owner) > 32 * 32) { remove(); owner->fishing = nullptr; return; @@ -214,7 +214,7 @@ void FishingHook::tick() { } std::shared_ptr hitEntity = nullptr; std::vector >* objects = level->getEntities( - shared_from_this(), this->bb->expand(xd, yd, zd)->grow(1, 1, 1)); + shared_from_this(), bb->expand(xd, yd, zd)->grow(1, 1, 1)); double nearest = 0; AUTO_VAR(itEnd, objects->end()); for (AUTO_VAR(it, objects->begin()); it != itEnd; it++) { @@ -243,8 +243,8 @@ void FishingHook::tick() { if (res->entity != NULL) { // 4J Stu Move fix for : fix for #48587 - CRASH: Code: Gameplay: // Hitting another player with the fishing bobber crashes the game. - // [Fishing pole, line] Incorrect std::dynamic_pointer_cast used - // around the shared_from_this() + // [Fishing pole, line] Incorrect dynamic_pointer_cast used around + // the shared_from_this() DamageSource* damageSource = DamageSource::thrown(shared_from_this(), owner); if (res->entity->hurt(damageSource, 0)) { @@ -305,8 +305,8 @@ void FishingHook::tick() { if (random->nextInt(nibbleOdds) == 0) { nibble = random->nextInt(30) + 10; yd -= 0.2f; - level->playSound( - shared_from_this(), eSoundType_RANDOM_SPLASH, 0.25f, + playSound( + eSoundType_RANDOM_SPLASH, 0.25f, 1 + (random->nextFloat() - random->nextFloat()) * 0.4f); float yt = (float)Mth::floor(bb->y0); for (int i = 0; i < 1 + bbWidth * 20; i++) { @@ -398,7 +398,7 @@ int FishingHook::retrieve() { owner->level->addEntity( std::shared_ptr(new ExperienceOrb( owner->level, owner->x, owner->y + 0.5f, owner->z + 0.5f, - random->nextInt(3) + 1))); // 4J Stu brought forward from 1.4 + random->nextInt(6) + 1))); // 4J Stu brought forward from 1.4 dmg = 1; } if (inGround) dmg = 2; diff --git a/Minecraft.World/Entities/Mobs/Ghast.cpp b/Minecraft.World/Entities/Mobs/Ghast.cpp index 8f4617ac9..c170a0f66 100644 --- a/Minecraft.World/Entities/Mobs/Ghast.cpp +++ b/Minecraft.World/Entities/Mobs/Ghast.cpp @@ -3,8 +3,10 @@ #include "../../Headers/net.minecraft.world.phys.h" #include "../../Headers/net.minecraft.world.level.h" #include "../../Headers/net.minecraft.world.entity.h" +#include "../../Headers/net.minecraft.world.entity.ai.attributes.h" #include "../../Headers/net.minecraft.world.entity.projectile.h" #include "../../Headers/net.minecraft.world.entity.player.h" +#include "../../Headers/net.minecraft.world.entity.monster.h" #include "../../Headers/net.minecraft.world.item.h" #include "../../Headers/net.minecraft.world.damagesource.h" #include "../../Headers/net.minecraft.stats.h" @@ -14,6 +16,7 @@ #include "../../Util/SoundTypes.h" void Ghast::_init() { + explosionPower = 1; floatDuration = 0; target = nullptr; retargetTime = 0; @@ -29,28 +32,27 @@ Ghast::Ghast(Level* level) : FlyingMob(level) { // 4J Stu - This function call had to be moved here from the Entity ctor to // ensure that the derived version of the function is called this->defineSynchedData(); - - // 4J Stu - This function call had to be moved here from the Entity ctor to - // ensure that the derived version of the function is called - health = getMaxHealth(); + registerAttributes(); + setHealth(getMaxHealth()); _init(); - this->textureIdx = TN_MOB_GHAST; // 4J was L"/mob/ghast.png"; - this->setSize(4, 4); - this->fireImmune = true; + setSize(4, 4); + fireImmune = true; xpReward = Enemy::XP_REWARD_MEDIUM; } -bool Ghast::hurt(DamageSource* source, int dmg) { +bool Ghast::isCharging() { return entityData->getByte(DATA_IS_CHARGING) != 0; } + +bool Ghast::hurt(DamageSource* source, float dmg) { + if (isInvulnerable()) return false; if (source->getMsgId() == ChatPacket::e_ChatDeathFireball) { - std::shared_ptr player = - std::dynamic_pointer_cast(source->getEntity()); - if (player != NULL) { + if ((source->getEntity() != NULL) && + source->getEntity()->instanceof(eTYPE_PLAYER)) { // reflected fireball, kill the ghast FlyingMob::hurt(source, 1000); - player->awardStat(GenericStats::ghast(), - GenericStats::param_ghast()); + std::dynamic_pointer_cast(source->getEntity()) + ->awardStat(GenericStats::ghast(), GenericStats::param_ghast()); return true; } } @@ -64,14 +66,10 @@ void Ghast::defineSynchedData() { entityData->define(DATA_IS_CHARGING, (uint8_t)0); } -int Ghast::getMaxHealth() { return 10; } +void Ghast::registerAttributes() { + FlyingMob::registerAttributes(); -void Ghast::tick() { - FlyingMob::tick(); - uint8_t current = entityData->getByte(DATA_IS_CHARGING); - // this->textureName = current == 1 ? L"/mob/ghast_fire.png" : - // L"/mob/ghast.png"; // 4J replaced with following line - this->textureIdx = current == 1 ? TN_MOB_GHAST_FIRE : TN_MOB_GHAST; + getAttribute(SharedMonsterAttributes::MAX_HEALTH)->setBaseValue(10); } void Ghast::serverAiStep() { @@ -125,7 +123,7 @@ void Ghast::serverAiStep() { double zdd = target->z - z; yBodyRot = yRot = -(float)atan2(xdd, zdd) * 180 / PI; - if (this->canSee(target)) { + if (canSee(target)) { if (charge == 10) { // 4J - change brought forward from 1.2.3 level->levelEvent(nullptr, LevelEvent::SOUND_GHAST_WARNING, @@ -136,11 +134,12 @@ void Ghast::serverAiStep() { // 4J - change brought forward from 1.2.3 level->levelEvent(nullptr, LevelEvent::SOUND_GHAST_FIREBALL, (int)x, (int)y, (int)z, 0); - std::shared_ptr ie = - std::shared_ptr(new Fireball( + std::shared_ptr ie = + std::shared_ptr(new LargeFireball( level, std::dynamic_pointer_cast(shared_from_this()), xdd, ydd, zdd)); + ie->explosionPower = explosionPower; double d = 4; Vec3* v = getViewVector(1); ie->x = x + v->x * d; @@ -186,7 +185,7 @@ int Ghast::getHurtSound() { return eSoundType_MOB_GHAST_SCREAM; } int Ghast::getDeathSound() { return eSoundType_MOB_GHAST_DEATH; } -int Ghast::getDeathLoot() { return Item::sulphur->id; } +int Ghast::getDeathLoot() { return Item::gunpowder_Id; } void Ghast::dropDeathLoot(bool wasKilledByPlayer, int playerBonusLevel) { int count = random->nextInt(2) + random->nextInt(1 + playerBonusLevel); @@ -195,7 +194,7 @@ void Ghast::dropDeathLoot(bool wasKilledByPlayer, int playerBonusLevel) { } count = random->nextInt(3) + random->nextInt(1 + playerBonusLevel); for (int i = 0; i < count; i++) { - spawnAtLocation(Item::sulphur_Id, 1); + spawnAtLocation(Item::gunpowder_Id, 1); } } @@ -209,3 +208,13 @@ bool Ghast::canSpawn() { } int Ghast::getMaxSpawnClusterSize() { return 1; } +void Ghast::addAdditonalSaveData(CompoundTag* tag) { + FlyingMob::addAdditonalSaveData(tag); + tag->putInt(L"ExplosionPower", explosionPower); +} + +void Ghast::readAdditionalSaveData(CompoundTag* tag) { + FlyingMob::readAdditionalSaveData(tag); + if (tag->contains(L"ExplosionPower")) + explosionPower = tag->getInt(L"ExplosionPower"); +} \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/Ghast.h b/Minecraft.World/Entities/Mobs/Ghast.h index ef00fa935..828cf8df2 100644 --- a/Minecraft.World/Entities/Mobs/Ghast.h +++ b/Minecraft.World/Entities/Mobs/Ghast.h @@ -27,21 +27,19 @@ public: int charge; private: + int explosionPower; + void _init(); public: Ghast(Level* level); - virtual bool hurt(DamageSource* source, int dmg); + virtual bool isCharging(); + virtual bool hurt(DamageSource* source, float dmg); protected: virtual void defineSynchedData(); - -public: - int getMaxHealth(); - -public: - virtual void tick(); + virtual void registerAttributes(); protected: virtual void serverAiStep(); @@ -60,4 +58,6 @@ protected: public: virtual bool canSpawn(); virtual int getMaxSpawnClusterSize(); + virtual void addAdditonalSaveData(CompoundTag* tag); + virtual void readAdditionalSaveData(CompoundTag* tag); }; diff --git a/Minecraft.World/Entities/Mobs/Giant.cpp b/Minecraft.World/Entities/Mobs/Giant.cpp index 415805739..29748a797 100644 --- a/Minecraft.World/Entities/Mobs/Giant.cpp +++ b/Minecraft.World/Entities/Mobs/Giant.cpp @@ -1,5 +1,7 @@ #include "../../Platform/stdafx.h" #include "../../Headers/net.minecraft.world.level.h" +#include "../../Headers/net.minecraft.world.entity.ai.attributes.h" +#include "../../Headers/net.minecraft.world.entity.monster.h" #include "Giant.h" #include "../../../Minecraft.Client/Textures/Textures.h" @@ -7,19 +9,19 @@ Giant::Giant(Level* level) : Monster(level) { // 4J Stu - This function call had to be moved here from the Entity ctor to // ensure that the derived version of the function is called this->defineSynchedData(); + registerAttributes(); - // 4J Stu - This function call had to be moved here from the Entity ctor to - // ensure that the derived version of the function is called - health = getMaxHealth(); - - this->textureIdx = TN_MOB_ZOMBIE; // 4J was L"/mob/zombie.png"; - runSpeed = 0.5f; - attackDamage = 50; - this->heightOffset *= 6; - this->setSize(bbWidth * 6, bbHeight * 6); + heightOffset *= 6; + setSize(bbWidth * 6, bbHeight * 6); } -int Giant::getMaxHealth() { return 100; } +void Giant::registerAttributes() { + Monster::registerAttributes(); + + getAttribute(SharedMonsterAttributes::MAX_HEALTH)->setBaseValue(100); + getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED)->setBaseValue(0.5f); + getAttribute(SharedMonsterAttributes::ATTACK_DAMAGE)->setBaseValue(50); +} float Giant::getWalkTargetValue(int x, int y, int z) { return level->getBrightness(x, y, z) - 0.5f; diff --git a/Minecraft.World/Entities/Mobs/Giant.h b/Minecraft.World/Entities/Mobs/Giant.h index e0bad7012..a9663eee5 100644 --- a/Minecraft.World/Entities/Mobs/Giant.h +++ b/Minecraft.World/Entities/Mobs/Giant.h @@ -11,6 +11,9 @@ public: Giant(Level* level); - int getMaxHealth(); +protected: + virtual void registerAttributes(); + +public: virtual float getWalkTargetValue(int x, int y, int z); }; diff --git a/Minecraft.World/Entities/Mobs/ItemFrame.cpp b/Minecraft.World/Entities/Mobs/ItemFrame.cpp index e3fa68c47..66f173cd9 100644 --- a/Minecraft.World/Entities/Mobs/ItemFrame.cpp +++ b/Minecraft.World/Entities/Mobs/ItemFrame.cpp @@ -16,6 +16,8 @@ void ItemFrame::_init() { // 4J Stu - This function call had to be moved here from the Entity ctor to // ensure that the derived version of the function is called this->defineSynchedData(); + + dropChance = 1; } ItemFrame::ItemFrame(Level* level) : HangingEntity(level) { _init(); } @@ -31,19 +33,40 @@ void ItemFrame::defineSynchedData() { getEntityData()->define(DATA_ROTATION, (uint8_t)0); } -void ItemFrame::dropItem() { - spawnAtLocation( - std::shared_ptr(new ItemInstance(Item::frame)), 0.0f); - std::shared_ptr item = getItem(); - if (item != NULL) { - std::shared_ptr data = - Item::map->getSavedData(item, level); - data->removeItemFrameDecoration(item); +bool ItemFrame::shouldRenderAtSqrDistance(double distance) { + double size = 16; + size *= 64.0f * viewScale; + return distance < size * size; +} - std::shared_ptr itemToDrop = item->copy(); - itemToDrop->setFramed(nullptr); - spawnAtLocation(itemToDrop, 0.0f); +void ItemFrame::dropItem(std::shared_ptr causedBy) { + std::shared_ptr item = getItem(); + + if (causedBy != NULL && causedBy->instanceof(eTYPE_PLAYER)) { + if (std::dynamic_pointer_cast(causedBy)->abilities.instabuild) { + removeFramedMap(item); + return; + } } + + spawnAtLocation( + std::shared_ptr(new ItemInstance(Item::frame)), 0); + if ((item != NULL) && (random->nextFloat() < dropChance)) { + item = item->copy(); + removeFramedMap(item); + spawnAtLocation(item, 0); + } +} + +void ItemFrame::removeFramedMap(std::shared_ptr item) { + if (item == NULL) return; + if (item->id == Item::map_Id) { + std::shared_ptr mapItemSavedData = + Item::map->getSavedData(item, level); + mapItemSavedData->removeItemFrameDecoration(item); + // mapItemSavedData.decorations.remove("frame-" + entityId); + } + item->setFramed(nullptr); } std::shared_ptr ItemFrame::getItem() { @@ -72,7 +95,7 @@ void ItemFrame::addAdditonalSaveData(CompoundTag* tag) { if (getItem() != NULL) { tag->putCompound(L"Item", getItem()->save(new CompoundTag())); tag->putByte(L"ItemRotation", (uint8_t)getRotation()); - // tag->putFloat(L"ItemDropChance", dropChance); + tag->putFloat(L"ItemDropChance", dropChance); } HangingEntity::addAdditonalSaveData(tag); } @@ -83,8 +106,8 @@ void ItemFrame::readAdditionalSaveData(CompoundTag* tag) { setItem(ItemInstance::fromTag(itemTag)); setRotation(tag->getByte(L"ItemRotation")); - // if (tag->contains(L"ItemDropChance")) dropChance = - // tag->getFloat(L"ItemDropChance"); + if (tag->contains(L"ItemDropChance")) + dropChance = tag->getFloat(L"ItemDropChance"); } HangingEntity::readAdditionalSaveData(tag); } diff --git a/Minecraft.World/Entities/Mobs/ItemFrame.h b/Minecraft.World/Entities/Mobs/ItemFrame.h index 3f83b6e6f..58a7394d4 100644 --- a/Minecraft.World/Entities/Mobs/ItemFrame.h +++ b/Minecraft.World/Entities/Mobs/ItemFrame.h @@ -14,6 +14,8 @@ private: static const int DATA_ITEM = 2; static const int DATA_ROTATION = 3; + float dropChance; + private: void _init(); @@ -23,9 +25,15 @@ public: protected: virtual void defineSynchedData(); + +public: virtual int getWidth() { return 9; } virtual int getHeight() { return 9; } - virtual void dropItem(); + virtual bool shouldRenderAtSqrDistance(double distance); + virtual void dropItem(std::shared_ptr causedBy); + +private: + void removeFramedMap(std::shared_ptr item); public: std::shared_ptr getItem(); diff --git a/Minecraft.World/Entities/Mobs/LavaSlime.cpp b/Minecraft.World/Entities/Mobs/LavaSlime.cpp index 143f64698..a84116c43 100644 --- a/Minecraft.World/Entities/Mobs/LavaSlime.cpp +++ b/Minecraft.World/Entities/Mobs/LavaSlime.cpp @@ -1,6 +1,8 @@ #include "../../Platform/stdafx.h" #include "../../Headers/net.minecraft.world.level.h" #include "../../Headers/net.minecraft.world.h" +#include "../../Headers/net.minecraft.world.entity.ai.attributes.h" +#include "../../Headers/net.minecraft.world.entity.monster.h" #include "../../Headers/net.minecraft.world.item.h" #include "../../../Minecraft.Client/Textures/Textures.h" #include "LavaSlime.h" @@ -12,14 +14,15 @@ LavaSlime::LavaSlime(Level* level) : Slime(level) { // Slime ctor has already called this, and as we don't override it here // don't need to call it // this->defineSynchedData(); + registerAttributes(); - // 4J Stu - This function call had to be moved here from the Entity ctor to - // ensure that the derived version of the function is called - health = getMaxHealth(); - - this->textureIdx = TN_MOB_LAVA; // 4J was "/mob/lava.png"; fireImmune = true; - walkingSpeed = .2f; +} + +void LavaSlime::registerAttributes() { + Slime::registerAttributes(); + + getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED)->setBaseValue(0.2f); } bool LavaSlime::canSpawn() { @@ -67,7 +70,7 @@ void LavaSlime::decreaseSquish() { targetSquish = targetSquish * 0.90f; } void LavaSlime::jumpFromGround() { yd = 0.42f + getSize() * .1f; - this->hasImpulse = true; + hasImpulse = true; } void LavaSlime::causeFallDamage(float distance) {} @@ -76,9 +79,15 @@ bool LavaSlime::isDealsDamage() { return true; } int LavaSlime::getAttackDamage() { return Slime::getAttackDamage() + 2; } -int LavaSlime::getHurtSound() { return eSoundType_MOB_SLIME; } +int LavaSlime::getHurtSound() { + return getSize() > 1 ? eSoundType_MOB_SLIME_BIG + : eSoundType_MOB_SLIME_SMALL; +} -int LavaSlime::getDeathSound() { return eSoundType_MOB_SLIME; } +int LavaSlime::getDeathSound() { + return getSize() > 1 ? eSoundType_MOB_SLIME_BIG + : eSoundType_MOB_SLIME_SMALL; +} int LavaSlime::getSquishSound() { if (getSize() > 1) { diff --git a/Minecraft.World/Entities/Mobs/LavaSlime.h b/Minecraft.World/Entities/Mobs/LavaSlime.h index bb4ea66de..73cc55ef7 100644 --- a/Minecraft.World/Entities/Mobs/LavaSlime.h +++ b/Minecraft.World/Entities/Mobs/LavaSlime.h @@ -10,6 +10,10 @@ public: public: LavaSlime(Level* level); +protected: + virtual void registerAttributes(); + +public: virtual bool canSpawn(); virtual int getArmorValue(); diff --git a/Minecraft.World/Entities/Mobs/LightningBolt.cpp b/Minecraft.World/Entities/Mobs/LightningBolt.cpp index 3d6dd4b37..fe864cb07 100644 --- a/Minecraft.World/Entities/Mobs/LightningBolt.cpp +++ b/Minecraft.World/Entities/Mobs/LightningBolt.cpp @@ -23,38 +23,35 @@ LightningBolt::LightningBolt(Level* level, double x, double y, double z) flashes = 1; // 4J - added clientside check - if (!level->isClientSide) { - if (level->difficulty >= 2 && - level->hasChunksAt(Mth::floor(x), Mth::floor(y), Mth::floor(z), - 10)) { - { - int xt = Mth::floor(x); - int yt = Mth::floor(y); - int zt = Mth::floor(z); - // 4J added - don't go setting tiles if we aren't tracking them - // for network synchronisation - if (MinecraftServer::getInstance() - ->getPlayers() - ->isTrackingTile(xt, yt, zt, level->dimension->id)) { - if (level->getTile(xt, yt, zt) == 0 && - Tile::fire->mayPlace(level, xt, yt, zt)) - level->setTile(xt, yt, zt, Tile::fire_Id); - } + if (!level->isClientSide && + level->getGameRules()->getBoolean(GameRules::RULE_DOFIRETICK) && + level->difficulty >= 2 && + level->hasChunksAt(Mth::floor(x), Mth::floor(y), Mth::floor(z), 10)) { + { + int xt = Mth::floor(x); + int yt = Mth::floor(y); + int zt = Mth::floor(z); + // 4J added - don't go setting tiles if we aren't tracking them for + // network synchronisation + if (MinecraftServer::getInstance()->getPlayers()->isTrackingTile( + xt, yt, zt, level->dimension->id)) { + if (level->getTile(xt, yt, zt) == 0 && + Tile::fire->mayPlace(level, xt, yt, zt)) + level->setTileAndUpdate(xt, yt, zt, Tile::fire_Id); } + } - for (int i = 0; i < 4; i++) { - int xt = Mth::floor(x) + random->nextInt(3) - 1; - int yt = Mth::floor(y) + random->nextInt(3) - 1; - int zt = Mth::floor(z) + random->nextInt(3) - 1; - // 4J added - don't go setting tiles if we aren't tracking them - // for network synchronisation - if (MinecraftServer::getInstance() - ->getPlayers() - ->isTrackingTile(xt, yt, zt, level->dimension->id)) { - if (level->getTile(xt, yt, zt) == 0 && - Tile::fire->mayPlace(level, xt, yt, zt)) - level->setTile(xt, yt, zt, Tile::fire_Id); - } + for (int i = 0; i < 4; i++) { + int xt = Mth::floor(x) + random->nextInt(3) - 1; + int yt = Mth::floor(y) + random->nextInt(3) - 1; + int zt = Mth::floor(z) + random->nextInt(3) - 1; + // 4J added - don't go setting tiles if we aren't tracking them for + // network synchronisation + if (MinecraftServer::getInstance()->getPlayers()->isTrackingTile( + xt, yt, zt, level->dimension->id)) { + if (level->getTile(xt, yt, zt) == 0 && + Tile::fire->mayPlace(level, xt, yt, zt)) + level->setTileAndUpdate(xt, yt, zt, Tile::fire_Id); } } } @@ -80,34 +77,34 @@ void LightningBolt::tick() { } else if (life < -random->nextInt(10)) { flashes--; life = 1; - // 4J - added clientside check - if (!level->isClientSide) { - seed = random->nextLong(); - if (level->hasChunksAt((int)floor(x), (int)floor(y), - (int)floor(z), 10)) { - int xt = (int)floor(x); - int yt = (int)floor(y); - int zt = (int)floor(z); - // 4J added - don't go setting tiles if we aren't tracking - // them for network synchronisation - if (MinecraftServer::getInstance() - ->getPlayers() - ->isTrackingTile(xt, yt, zt, - level->dimension->id)) { - if (level->getTile(xt, yt, zt) == 0 && - Tile::fire->mayPlace(level, xt, yt, zt)) - level->setTile(xt, yt, zt, Tile::fire_Id); - } + seed = random->nextLong(); + if (!level->isClientSide && + level->getGameRules()->getBoolean(GameRules::RULE_DOFIRETICK) && + level->hasChunksAt((int)floor(x), (int)floor(y), (int)floor(z), + 10)) { + int xt = (int)floor(x); + int yt = (int)floor(y); + int zt = (int)floor(z); + + // 4J added - don't go setting tiles if we aren't tracking them + // for network synchronisation + if (MinecraftServer::getInstance() + ->getPlayers() + ->isTrackingTile(xt, yt, zt, level->dimension->id)) { + if (level->getTile(xt, yt, zt) == 0 && + Tile::fire->mayPlace(level, xt, yt, zt)) + level->setTileAndUpdate(xt, yt, zt, Tile::fire_Id); } } } } if (life >= 0) { - double r = 3; - // 4J - added clientside check - if (!level->isClientSide) { + if (level->isClientSide) { + level->skyFlashTime = 2; + } else { + double r = 3; std::vector >* entities = level->getEntities(shared_from_this(), AABB::newTemp(x - r, y - r, z - r, x + r, @@ -118,8 +115,6 @@ void LightningBolt::tick() { e->thunderHit(this); } } - - level->lightningBoltTime = 2; } } diff --git a/Minecraft.World/Entities/Mobs/Minecart.cpp b/Minecraft.World/Entities/Mobs/Minecart.cpp index e87abddc1..308ae2195 100644 --- a/Minecraft.World/Entities/Mobs/Minecart.cpp +++ b/Minecraft.World/Entities/Mobs/Minecart.cpp @@ -1,4 +1,5 @@ #include "../../Platform/stdafx.h" +#include "../../Headers/net.minecraft.world.level.dimension.h" #include "../../Headers/net.minecraft.world.level.h" #include "../../Headers/net.minecraft.world.level.tile.h" #include "../../Headers/net.minecraft.world.phys.h" @@ -8,6 +9,8 @@ #include "../../Headers/net.minecraft.world.entity.animal.h" #include "../../Headers/net.minecraft.world.item.h" #include "../../Headers/net.minecraft.world.damagesource.h" +#include "../../../Minecraft.Client/MinecraftServer.h" +#include "../../../Minecraft.Client/Level/ServerLevel.h" #include "../../Headers/com.mojang.nbt.h" #include "Minecart.h" #include "../../Util/SharedConstants.h" @@ -29,14 +32,8 @@ const int Minecart::EXITS[][2][3] = { }; void Minecart::_init() { - // 4J TODO This gets replaced again later so should maybe be inited as NULL? - items = new ItemInstanceArray(9 * 4); - flipped = false; - type = fuel = 0; - xPush = zPush = 0.0; - lSteps = 0; lx = ly = lz = lyr = lxr = 0.0; lxd = lyd = lzd = 0.0; @@ -45,6 +42,8 @@ void Minecart::_init() { blocksBuilding = true; setSize(0.98f, 0.7f); heightOffset = bbHeight / 2.0f; + soundUpdater = NULL; + name = L""; // // 4J Added @@ -52,41 +51,63 @@ void Minecart::_init() { } Minecart::Minecart(Level* level) : Entity(level) { - // 4J Stu - This function call had to be moved here from the Entity ctor to - // ensure that the derived version of the function is called - this->defineSynchedData(); - _init(); + + // soundUpdater = level != NULL ? level->makeSoundUpdater(this) : NULL; +} + +Minecart::~Minecart() { delete soundUpdater; } + +std::shared_ptr Minecart::createMinecart(Level* level, double x, + double y, double z, + int type) { + switch (type) { + case TYPE_CHEST: + return std::shared_ptr( + new MinecartChest(level, x, y, z)); + case TYPE_FURNACE: + return std::shared_ptr( + new MinecartFurnace(level, x, y, z)); + case TYPE_TNT: + return std::shared_ptr( + new MinecartTNT(level, x, y, z)); + case TYPE_SPAWNER: + return std::shared_ptr( + new MinecartSpawner(level, x, y, z)); + case TYPE_HOPPER: + return std::shared_ptr( + new MinecartHopper(level, x, y, z)); + default: + return std::shared_ptr( + new MinecartRideable(level, x, y, z)); + } } bool Minecart::makeStepSound() { return false; } void Minecart::defineSynchedData() { - entityData->define(DATA_ID_FUEL, (uint8_t)0); entityData->define(DATA_ID_HURT, 0); entityData->define(DATA_ID_HURTDIR, 1); - entityData->define(DATA_ID_DAMAGE, 0); + entityData->define(DATA_ID_DAMAGE, 0.0f); + entityData->define(DATA_ID_DISPLAY_TILE, 0); + entityData->define(DATA_ID_DISPLAY_OFFSET, 6); + entityData->define(DATA_ID_CUSTOM_DISPLAY, (uint8_t)0); } AABB* Minecart::getCollideAgainstBox(std::shared_ptr entity) { - return entity->bb; -} - -AABB* Minecart::getCollideBox() { - // if (level->isClientSide) return NULL; + if (entity->isPushable()) { + return entity->bb; + } return NULL; } +AABB* Minecart::getCollideBox() { return NULL; } + bool Minecart::isPushable() { return true; } -Minecart::Minecart(Level* level, double x, double y, double z, int type) - : Entity(level) { - // 4J Stu - This function call had to be moved here from the Entity ctor to - // ensure that the derived version of the function is called - this->defineSynchedData(); - +Minecart::Minecart(Level* level, double x, double y, double z) : Entity(level) { _init(); - setPos(x, y + heightOffset, z); + setPos(x, y, z); xd = 0; yd = 0; @@ -95,28 +116,30 @@ Minecart::Minecart(Level* level, double x, double y, double z, int type) xo = x; yo = y; zo = z; - this->type = type; } double Minecart::getRideHeight() { return bbHeight * 0.0 - 0.3f; } -bool Minecart::hurt(DamageSource* source, int hurtDamage) { +bool Minecart::hurt(DamageSource* source, float hurtDamage) { if (level->isClientSide || removed) return true; + if (isInvulnerable()) return false; // 4J-JEV: Fix for #88212, // Untrusted players shouldn't be able to damage minecarts or boats. if (dynamic_cast(source) != NULL) { std::shared_ptr attacker = source->getDirectEntity(); - if (std::dynamic_pointer_cast(attacker) != NULL && + if (attacker->instanceof(eTYPE_PLAYER) && !std::dynamic_pointer_cast(attacker)->isAllowedToHurtEntity( - shared_from_this())) + shared_from_this())) { return false; + } } setHurtDir(-getHurtDir()); setHurtTime(10); markHurt(); + setDamage(getDamage() + (hurtDamage * 10)); // 4J Stu - If someone is riding in this, then it can tick multiple times // which causes the damage to decrease too quickly. So just make the damage @@ -125,106 +148,50 @@ bool Minecart::hurt(DamageSource* source, int hurtDamage) { if (rider.lock() != NULL && rider.lock() == source->getEntity()) hurtDamage += 1; - // 4J Stu - Brought froward from 12w36 to fix #46611 - TU5: Gameplay: - // Minecarts and boat requires more hits than one to be destroyed in - // creative mode - std::shared_ptr player = - std::dynamic_pointer_cast(source->getEntity()); - if (player != NULL && player->abilities.instabuild) this->setDamage(100); + bool creativePlayer = source->getEntity() != NULL && + source->getEntity()->instanceof(eTYPE_PLAYER) && + std::dynamic_pointer_cast(source->getEntity()) + ->abilities.instabuild; - this->setDamage(getDamage() + (hurtDamage * 10)); - if (this->getDamage() > 20 * 2) { + if (creativePlayer || getDamage() > 20 * 2) { // 4J HEG - Fixed issue with player falling through the ground on // destroying a minecart while riding (issue #160607) if (rider.lock() != NULL) rider.lock()->ride(nullptr); - remove(); - spawnAtLocation(Item::minecart->id, 1, 0); - if (type == Minecart::CHEST) { - std::shared_ptr container = - std::dynamic_pointer_cast(shared_from_this()); - for (unsigned int i = 0; i < container->getContainerSize(); i++) { - std::shared_ptr item = container->getItem(i); - if (item != NULL) { - float xo = random->nextFloat() * 0.8f + 0.1f; - float yo = random->nextFloat() * 0.8f + 0.1f; - float zo = random->nextFloat() * 0.8f + 0.1f; - - while (item->count > 0) { - int count = random->nextInt(21) + 10; - if (count > item->count) count = item->count; - item->count -= count; - - std::shared_ptr itemEntity = - std::shared_ptr(new ItemEntity( - level, x + xo, y + yo, z + zo, - std::shared_ptr(new ItemInstance( - item->id, count, item->getAuxValue())))); - float pow = 0.05f; - itemEntity->xd = (float)random->nextGaussian() * pow; - itemEntity->yd = - (float)random->nextGaussian() * pow + 0.2f; - itemEntity->zd = (float)random->nextGaussian() * pow; - if (item->hasTag()) { - itemEntity->getItem()->setTag( - (CompoundTag*)item->getTag()->copy()); - } - level->addEntity(itemEntity); - } - } - } - spawnAtLocation(Tile::chest_Id, 1, 0); - } else if (type == Minecart::FURNACE) { - spawnAtLocation(Tile::furnace_Id, 1, 0); + if (!creativePlayer || hasCustomName()) { + destroy(source); + } else { + remove(); } } return true; } +void Minecart::destroy(DamageSource* source) { + remove(); + std::shared_ptr item = + std::shared_ptr(new ItemInstance(Item::minecart, 1)); + if (!name.empty()) item->setHoverName(name); + spawnAtLocation(item, 0); +} + void Minecart::animateHurt() { setHurtDir(-getHurtDir()); setHurtTime(10); - this->setDamage(this->getDamage() + (getDamage() * 10)); + setDamage(getDamage() + (getDamage() * 10)); } bool Minecart::isPickable() { return !removed; } void Minecart::remove() { - for (unsigned int i = 0; i < getContainerSize(); i++) { - std::shared_ptr item = getItem(i); - if (item != NULL) { - float xo = random->nextFloat() * 0.8f + 0.1f; - float yo = random->nextFloat() * 0.8f + 0.1f; - float zo = random->nextFloat() * 0.8f + 0.1f; - - while (item->count > 0) { - int count = random->nextInt(21) + 10; - if (count > item->count) count = item->count; - item->count -= count; - - std::shared_ptr itemEntity = - std::shared_ptr(new ItemEntity( - level, x + xo, y + yo, z + zo, - std::shared_ptr(new ItemInstance( - item->id, count, item->getAuxValue())))); - float pow = 0.05f; - itemEntity->xd = (float)random->nextGaussian() * pow; - itemEntity->yd = (float)random->nextGaussian() * pow + 0.2f; - itemEntity->zd = (float)random->nextGaussian() * pow; - if (item->hasTag()) { - itemEntity->getItem()->setTag( - (CompoundTag*)item->getTag()->copy()); - } - level->addEntity(itemEntity); - } - } - } Entity::remove(); + // if (soundUpdater != NULL) soundUpdater->tick(); } void Minecart::tick() { - // 4J - make minecarts (server-side) tick twice, to put things back to how - // they were when we were accidently ticking them twice + // if (soundUpdater != NULL) soundUpdater->tick(); + // 4J - make minecarts (server-side) tick twice, to put things back to how + // they were when we were accidently ticking them twice for (int i = 0; i < 2; i++) { if (getHurtTime() > 0) setHurtTime(getHurtTime() - 1); if (getDamage() > 0) setDamage(getDamage() - 1); @@ -232,9 +199,36 @@ void Minecart::tick() { outOfWorld(); } - if (hasFuel() && random->nextInt(4) == 0) { - level->addParticle(eParticleType_largesmoke, x, y + 0.8, z, 0, 0, - 0); + if (!level->isClientSide && dynamic_cast(level) != NULL) { + MinecraftServer* server = ((ServerLevel*)level)->getServer(); + int waitTime = getPortalWaitTime(); + + if (isInsidePortal) { + if (server->isNetherEnabled()) { + if (riding == NULL) { + if (portalTime++ >= waitTime) { + portalTime = waitTime; + changingDimensionDelay = + getDimensionChangingDelay(); + + int targetDimension; + + if (level->dimension->id == -1) { + targetDimension = 0; + } else { + targetDimension = -1; + } + + changeDimension(targetDimension); + } + } + isInsidePortal = false; + } + } else { + if (portalTime > 0) portalTime -= 4; + if (portalTime < 0) portalTime = 0; + } + if (changingDimensionDelay > 0) changingDimensionDelay--; } // 4J Stu - Fix for #8284 - Gameplay: Collision: Minecart clips into/ @@ -252,11 +246,11 @@ void Minecart::tick() { xRot += (float)((lxr - xRot) / lSteps); lSteps--; - this->setPos(xt, yt, zt); - this->setRot(yRot, xRot); + setPos(xt, yt, zt); + setRot(yRot, xRot); } else { - this->setPos(x, y, z); - this->setRot(yRot, xRot); + setPos(x, y, z); + setRot(yRot, xRot); } return; // 4J - return here stops the client-side version of this @@ -271,7 +265,7 @@ void Minecart::tick() { int xt = Mth::floor(x); int yt = Mth::floor(y); int zt = Mth::floor(z); - if (RailTile::isRail(level, xt, yt - 1, zt)) { + if (BaseRailTile::isRail(level, xt, yt - 1, zt)) { yt--; } @@ -279,240 +273,20 @@ void Minecart::tick() { double slideSpeed = 1 / 128.0; int tile = level->getTile(xt, yt, zt); - if (RailTile::isRail(tile)) { - Vec3* oldPos = getPos(x, y, z); + if (BaseRailTile::isRail(tile)) { int data = level->getData(xt, yt, zt); - y = yt; + moveAlongTrack(xt, yt, zt, max, slideSpeed, tile, data); - bool powerTrack = false; - bool haltTrack = false; - if (tile == Tile::goldenRail_Id) { - powerTrack = (data & RailTile::RAIL_DATA_BIT) != 0; - haltTrack = !powerTrack; + if (tile == Tile::activatorRail_Id) { + activateMinecart(xt, yt, zt, + (data & BaseRailTile::RAIL_DATA_BIT) != 0); } - if (((RailTile*)Tile::tiles[tile])->isUsesDataBit()) { - data &= RailTile::RAIL_DIRECTION_MASK; - } - - if (data >= 2 && data <= 5) { - y = yt + 1; - } - - if (data == 2) xd -= slideSpeed; - if (data == 3) xd += slideSpeed; - if (data == 4) zd += slideSpeed; - if (data == 5) zd -= slideSpeed; - - // 4J TODO Is this a good way to copy the bit of the array that we - // need? - int exits[2][3]; - memcpy(&exits, (void*)EXITS[data], sizeof(int) * 2 * 3); - // int exits[2][3] = EXITS[data]; - - double xD = exits[1][0] - exits[0][0]; - double zD = exits[1][2] - exits[0][2]; - double dd = sqrt(xD * xD + zD * zD); - - double flip = xd * xD + zd * zD; - if (flip < 0) { - xD = -xD; - zD = -zD; - } - - double pow = sqrt(xd * xd + zd * zd); - - xd = pow * xD / dd; - zd = pow * zD / dd; - - std::shared_ptr sharedRider = rider.lock(); - if (sharedRider != NULL) { - double riderDist = (sharedRider->xd * sharedRider->xd + - sharedRider->zd * sharedRider->zd); - double ownDist = xd * xd + zd * zd; - - if (riderDist > 0.0001 && ownDist < 0.01) { - xd += sharedRider->xd * 0.1; - zd += sharedRider->zd * 0.1; - - haltTrack = false; - } - } - - // on golden rails without power, stop the cart - if (haltTrack) { - double speedLength = sqrt(xd * xd + zd * zd); - if (speedLength < 0.03) { - xd *= 0; - yd *= 0; - zd *= 0; - } else { - xd *= 0.5f; - yd *= 0; - zd *= 0.5f; - } - } - - double progress = 0; - double x0 = xt + 0.5 + exits[0][0] * 0.5; - double z0 = zt + 0.5 + exits[0][2] * 0.5; - double x1 = xt + 0.5 + exits[1][0] * 0.5; - double z1 = zt + 0.5 + exits[1][2] * 0.5; - - xD = x1 - x0; - zD = z1 - z0; - - if (xD == 0) { - x = xt + 0.5; - progress = z - zt; - } else if (zD == 0) { - z = zt + 0.5; - progress = x - xt; - } else { - double xx = x - x0; - double zz = z - z0; - - progress = (xx * xD + zz * zD) * 2; - } - - x = x0 + xD * progress; - z = z0 + zD * progress; - - setPos(x, y + heightOffset, z); - - double xdd = xd; - double zdd = zd; - if (rider.lock() != NULL) { - xdd *= 0.75; - zdd *= 0.75; - } - if (xdd < -max) xdd = -max; - if (xdd > +max) xdd = +max; - if (zdd < -max) zdd = -max; - if (zdd > +max) zdd = +max; - move(xdd, 0, zdd); - - if (exits[0][1] != 0 && Mth::floor(x) - xt == exits[0][0] && - Mth::floor(z) - zt == exits[0][2]) { - setPos(x, y + exits[0][1], z); - } else if (exits[1][1] != 0 && Mth::floor(x) - xt == exits[1][0] && - Mth::floor(z) - zt == exits[1][2]) { - setPos(x, y + exits[1][1], z); - } else { - } - - if (rider.lock() != NULL) { - xd *= 0.997f; - yd *= 0; - zd *= 0.997f; - } else { - if (type == Minecart::FURNACE) { - double sd = xPush * xPush + zPush * zPush; - if (sd > 0.01 * 0.01) { - sd = sqrt(sd); - xPush /= sd; - zPush /= sd; - double speed = 0.04; - xd *= 0.8f; - yd *= 0; - zd *= 0.8f; - xd += xPush * speed; - zd += zPush * speed; - } else { - xd *= 0.9f; - yd *= 0; - zd *= 0.9f; - } - } - xd *= 0.96f; - yd *= 0; - zd *= 0.96f; - } - - Vec3* newPos = getPos(x, y, z); - if (newPos != NULL && oldPos != NULL) { - double speed = (oldPos->y - newPos->y) * 0.05; - - pow = sqrt(xd * xd + zd * zd); - if (pow > 0) { - xd = xd / pow * (pow + speed); - zd = zd / pow * (pow + speed); - } - setPos(x, newPos->y, z); - } - - int xn = Mth::floor(x); - int zn = Mth::floor(z); - if (xn != xt || zn != zt) { - pow = sqrt(xd * xd + zd * zd); - - xd = pow * (xn - xt); - zd = pow * (zn - zt); - } - - if (type == Minecart::FURNACE) { - double sd = xPush * xPush + zPush * zPush; - if (sd > 0.01 * 0.01 && xd * xd + zd * zd > 0.001) { - sd = sqrt(sd); - xPush /= sd; - zPush /= sd; - - if (xPush * xd + zPush * zd < 0) { - xPush = 0; - zPush = 0; - } else { - xPush = xd; - zPush = zd; - } - } - } - - // if on golden rail with power, increase speed - if (powerTrack) { - double speedLength = sqrt(xd * xd + zd * zd); - if (speedLength > .01) { - double speed = 0.06; - xd += xd / speedLength * speed; - zd += zd / speedLength * speed; - } else { - // if the minecart is standing still, accelerate it away - // from potentional walls - if (data == RailTile::DIR_FLAT_X) { - if (level->isSolidBlockingTile(xt - 1, yt, zt)) { - xd = .02; - } else if (level->isSolidBlockingTile(xt + 1, yt, zt)) { - xd = -.02; - } - } else if (data == RailTile::DIR_FLAT_Z) { - if (level->isSolidBlockingTile(xt, yt, zt - 1)) { - zd = .02; - } else if (level->isSolidBlockingTile(xt, yt, zt + 1)) { - zd = -.02; - } - } - } - } - - checkInsideTiles(); } else { - if (xd < -max) xd = -max; - if (xd > +max) xd = +max; - if (zd < -max) zd = -max; - if (zd > +max) zd = +max; - if (onGround) { - xd *= 0.5f; - yd *= 0.5f; - zd *= 0.5f; - } - move(xd, yd, zd); - - if (onGround) { - } else { - xd *= 0.95f; - yd *= 0.95f; - zd *= 0.95f; - } + comeOffTrack(max); } + checkInsideTiles(); + xRot = 0; double xDiff = xo - x; double zDiff = zo - z; @@ -529,28 +303,24 @@ void Minecart::tick() { } setRot(yRot, xRot); - // if (!level->isClientSide) { - { - std::vector >* entities = - level->getEntities(shared_from_this(), - this->bb->grow(0.2f, 0, 0.2f)); - if (entities != NULL && !entities->empty()) { - AUTO_VAR(itEnd, entities->end()); - for (AUTO_VAR(it, entities->begin()); it != itEnd; it++) { - std::shared_ptr e = (*it); // entities->at(i); - if (e != rider.lock() && e->isPushable() && - e->GetType() == eTYPE_MINECART) { - std::shared_ptr cart = - std::dynamic_pointer_cast(e); - cart->m_bHasPushedCartThisTick = false; - cart->push(shared_from_this()); + std::vector >* entities = + level->getEntities(shared_from_this(), bb->grow(0.2f, 0, 0.2f)); + if (entities != NULL && !entities->empty()) { + AUTO_VAR(itEnd, entities->end()); + for (AUTO_VAR(it, entities->begin()); it != itEnd; it++) { + std::shared_ptr e = (*it); // entities->at(i); + if (e != rider.lock() && e->isPushable() && + e->instanceof(eTYPE_MINECART)) { + std::shared_ptr cart = + std::dynamic_pointer_cast(e); + cart->m_bHasPushedCartThisTick = false; + cart->push(shared_from_this()); - // 4J Added - We should only be pushed by one minecart - // per tick, the closest one Fix for #46937 - TU5: - // Gameplay: Crash/Freeze occurs when a minecart with an - // animal inside will be forced to despawn - if (cart->m_bHasPushedCartThisTick) break; - } + // 4J Added - We should only be pushed by one minecart per + // tick, the closest one Fix for #46937 - TU5: Gameplay: + // Crash/Freeze occurs when a minecart with an animal inside + // will be forced to despawn + if (cart->m_bHasPushedCartThisTick) break; } } } @@ -563,14 +333,219 @@ void Minecart::tick() { rider = std::weak_ptr(); } } + } +} - if (fuel > 0) { - fuel--; +void Minecart::activateMinecart(int xt, int yt, int zt, bool state) {} + +void Minecart::comeOffTrack(double maxSpeed) { + if (xd < -maxSpeed) xd = -maxSpeed; + if (xd > +maxSpeed) xd = +maxSpeed; + if (zd < -maxSpeed) zd = -maxSpeed; + if (zd > +maxSpeed) zd = +maxSpeed; + if (onGround) { + xd *= 0.5f; + yd *= 0.5f; + zd *= 0.5f; + } + move(xd, yd, zd); + + if (!onGround) { + xd *= 0.95f; + yd *= 0.95f; + zd *= 0.95f; + } +} + +void Minecart::moveAlongTrack(int xt, int yt, int zt, double maxSpeed, + double slideSpeed, int tile, int data) { + fallDistance = 0; + + Vec3* oldPos = getPos(x, y, z); + y = yt; + + bool powerTrack = false; + bool haltTrack = false; + if (tile == Tile::goldenRail_Id) { + powerTrack = (data & BaseRailTile::RAIL_DATA_BIT) != 0; + haltTrack = !powerTrack; + } + if (((BaseRailTile*)Tile::tiles[tile])->isUsesDataBit()) { + data &= BaseRailTile::RAIL_DIRECTION_MASK; + } + + if (data >= 2 && data <= 5) { + y = yt + 1; + } + + if (data == 2) xd -= slideSpeed; + if (data == 3) xd += slideSpeed; + if (data == 4) zd += slideSpeed; + if (data == 5) zd -= slideSpeed; + + int exits[2][3]; + memcpy(exits, EXITS[data], sizeof(int) * 2 * 3); + + double xD = exits[1][0] - exits[0][0]; + double zD = exits[1][2] - exits[0][2]; + double dd = sqrt(xD * xD + zD * zD); + + double flip = xd * xD + zd * zD; + if (flip < 0) { + xD = -xD; + zD = -zD; + } + + double pow = sqrt(xd * xd + zd * zd); + if (pow > 2) { + pow = 2; + } + + xd = pow * xD / dd; + zd = pow * zD / dd; + + if (rider.lock() != NULL && rider.lock()->instanceof(eTYPE_LIVINGENTITY)) { + std::shared_ptr living = + std::dynamic_pointer_cast(rider.lock()); + + double std::forward = living->yya; + + if (std::forward > 0) { + double riderXd = -sin(living->yRot * PI / 180); + double riderZd = cos(living->yRot * PI / 180); + + double ownDist = xd * xd + zd * zd; + + if (ownDist < 0.01) { + xd += riderXd * 0.1; + zd += riderZd * 0.1; + + haltTrack = false; + } } - if (fuel <= 0) { - xPush = zPush = 0; + } + + // on golden rails without power, stop the cart + if (haltTrack) { + double speedLength = sqrt(xd * xd + zd * zd); + if (speedLength < .03) { + xd *= 0; + yd *= 0; + zd *= 0; + } else { + xd *= 0.5f; + yd *= 0; + zd *= 0.5f; } - setHasFuel(fuel > 0); + } + + double progress = 0; + double x0 = xt + 0.5 + exits[0][0] * 0.5; + double z0 = zt + 0.5 + exits[0][2] * 0.5; + double x1 = xt + 0.5 + exits[1][0] * 0.5; + double z1 = zt + 0.5 + exits[1][2] * 0.5; + + xD = x1 - x0; + zD = z1 - z0; + + if (xD == 0) { + x = xt + 0.5; + progress = z - zt; + } else if (zD == 0) { + z = zt + 0.5; + progress = x - xt; + } else { + double xx = x - x0; + double zz = z - z0; + + progress = (xx * xD + zz * zD) * 2; + } + + x = x0 + xD * progress; + z = z0 + zD * progress; + + setPos(x, y + heightOffset, z); + + double xdd = xd; + double zdd = zd; + if (rider.lock() != NULL) { + xdd *= 0.75; + zdd *= 0.75; + } + if (xdd < -maxSpeed) xdd = -maxSpeed; + if (xdd > +maxSpeed) xdd = +maxSpeed; + if (zdd < -maxSpeed) zdd = -maxSpeed; + if (zdd > +maxSpeed) zdd = +maxSpeed; + + move(xdd, 0, zdd); + + if (exits[0][1] != 0 && Mth::floor(x) - xt == exits[0][0] && + Mth::floor(z) - zt == exits[0][2]) { + setPos(x, y + exits[0][1], z); + } else if (exits[1][1] != 0 && Mth::floor(x) - xt == exits[1][0] && + Mth::floor(z) - zt == exits[1][2]) { + setPos(x, y + exits[1][1], z); + } + + applyNaturalSlowdown(); + + Vec3* newPos = getPos(x, y, z); + if (newPos != NULL && oldPos != NULL) { + double speed = (oldPos->y - newPos->y) * 0.05; + + pow = sqrt(xd * xd + zd * zd); + if (pow > 0) { + xd = xd / pow * (pow + speed); + zd = zd / pow * (pow + speed); + } + setPos(x, newPos->y, z); + } + + int xn = Mth::floor(x); + int zn = Mth::floor(z); + if (xn != xt || zn != zt) { + pow = sqrt(xd * xd + zd * zd); + + xd = pow * (xn - xt); + zd = pow * (zn - zt); + } + + // if on golden rail with power, increase speed + if (powerTrack) { + double speedLength = sqrt(xd * xd + zd * zd); + if (speedLength > .01) { + double speed = 0.06; + xd += xd / speedLength * speed; + zd += zd / speedLength * speed; + } else { + // if the minecart is standing still, accelerate it away from + // potential walls + if (data == BaseRailTile::DIR_FLAT_X) { + if (level->isSolidBlockingTile(xt - 1, yt, zt)) { + xd = .02; + } else if (level->isSolidBlockingTile(xt + 1, yt, zt)) { + xd = -.02; + } + } else if (data == BaseRailTile::DIR_FLAT_Z) { + if (level->isSolidBlockingTile(xt, yt, zt - 1)) { + zd = .02; + } else if (level->isSolidBlockingTile(xt, yt, zt + 1)) { + zd = -.02; + } + } + } + } +} + +void Minecart::applyNaturalSlowdown() { + if (rider.lock() != NULL) { + xd *= 0.997f; + yd *= 0; + zd *= 0.997f; + } else { + xd *= 0.96f; + yd *= 0; + zd *= 0.96f; } } @@ -578,16 +553,16 @@ Vec3* Minecart::getPosOffs(double x, double y, double z, double offs) { int xt = Mth::floor(x); int yt = Mth::floor(y); int zt = Mth::floor(z); - if (RailTile::isRail(level, xt, yt - 1, zt)) { + if (BaseRailTile::isRail(level, xt, yt - 1, zt)) { yt--; } int tile = level->getTile(xt, yt, zt); - if (RailTile::isRail(tile)) { + if (BaseRailTile::isRail(tile)) { int data = level->getData(xt, yt, zt); - if (((RailTile*)Tile::tiles[tile])->isUsesDataBit()) { - data &= RailTile::RAIL_DIRECTION_MASK; + if (((BaseRailTile*)Tile::tiles[tile])->isUsesDataBit()) { + data &= BaseRailTile::RAIL_DIRECTION_MASK; } y = yt; @@ -615,7 +590,6 @@ Vec3* Minecart::getPosOffs(double x, double y, double z, double offs) { } else if (exits[1][1] != 0 && Mth::floor(x) - xt == exits[1][0] && Mth::floor(z) - zt == exits[1][2]) { y += exits[1][1]; - } else { } return getPos(x, y, z); @@ -627,17 +601,17 @@ Vec3* Minecart::getPos(double x, double y, double z) { int xt = Mth::floor(x); int yt = Mth::floor(y); int zt = Mth::floor(z); - if (RailTile::isRail(level, xt, yt - 1, zt)) { + if (BaseRailTile::isRail(level, xt, yt - 1, zt)) { yt--; } int tile = level->getTile(xt, yt, zt); - if (RailTile::isRail(tile)) { + if (BaseRailTile::isRail(tile)) { int data = level->getData(xt, yt, zt); y = yt; - if (((RailTile*)Tile::tiles[tile])->isUsesDataBit()) { - data &= RailTile::RAIL_DIRECTION_MASK; + if (((BaseRailTile*)Tile::tiles[tile])->isUsesDataBit()) { + data &= BaseRailTile::RAIL_DIRECTION_MASK; } if (data >= 2 && data <= 5) { @@ -684,46 +658,28 @@ Vec3* Minecart::getPos(double x, double y, double z) { return NULL; } -void Minecart::addAdditonalSaveData(CompoundTag* base) { - base->putInt(L"Type", type); - - if (type == Minecart::FURNACE) { - base->putDouble(L"PushX", xPush); - base->putDouble(L"PushZ", zPush); - base->putShort(L"Fuel", (short)fuel); - } else if (type == Minecart::CHEST) { - ListTag* listTag = new ListTag(); - - for (unsigned int i = 0; i < items->length; i++) { - if ((*items)[i] != NULL) { - CompoundTag* tag = new CompoundTag(); - tag->putByte(L"Slot", (uint8_t)i); - (*items)[i]->save(tag); - listTag->add(tag); - } - } - base->put(L"Items", listTag); +void Minecart::readAdditionalSaveData(CompoundTag* tag) { + if (tag->getBoolean(L"CustomDisplayTile")) { + setDisplayTile(tag->getInt(L"DisplayTile")); + setDisplayData(tag->getInt(L"DisplayData")); + setDisplayOffset(tag->getInt(L"DisplayOffset")); } + + if (tag->contains(L"CustomName") && + tag->getString(L"CustomName").length() > 0) + name = tag->getString(L"CustomName"); } -void Minecart::readAdditionalSaveData(CompoundTag* base) { - type = base->getInt(L"Type"); - if (type == Minecart::FURNACE) { - xPush = base->getDouble(L"PushX"); - zPush = base->getDouble(L"PushZ"); - fuel = base->getShort(L"Fuel"); - } else if (type == Minecart::CHEST) { - ListTag* inventoryList = - (ListTag*)base->getList(L"Items"); - items = new ItemInstanceArray(getContainerSize()); - for (int i = 0; i < inventoryList->size(); i++) { - CompoundTag* tag = inventoryList->get(i); - unsigned int slot = tag->getByte(L"Slot") & 0xff; - if (slot >= 0 && slot < items->length) - (*items)[slot] = - std::shared_ptr(ItemInstance::fromTag(tag)); - } +void Minecart::addAdditonalSaveData(CompoundTag* tag) { + if (hasCustomDisplay()) { + tag->putBoolean(L"CustomDisplayTile", true); + tag->putInt(L"DisplayTile", + getDisplayTile() == NULL ? 0 : getDisplayTile()->id); + tag->putInt(L"DisplayData", getDisplayData()); + tag->putInt(L"DisplayOffset", getDisplayOffset()); } + + if (!name.empty()) tag->putString(L"CustomName", name); } float Minecart::getShadowHeightOffs() { return 0; } @@ -732,11 +688,10 @@ void Minecart::push(std::shared_ptr e) { if (level->isClientSide) return; if (e == rider.lock()) return; - if ((std::dynamic_pointer_cast(e) != NULL) && - std::dynamic_pointer_cast(e) == NULL && - std::dynamic_pointer_cast(e) == NULL && - type == Minecart::RIDEABLE && xd * xd + zd * zd > 0.01) { - if (rider.lock() == NULL && e->riding == NULL) { + if (e->instanceof(eTYPE_LIVINGENTITY) && !e->instanceof(eTYPE_PLAYER) && + !e->instanceof(eTYPE_VILLAGERGOLEM) && (getType() == TYPE_RIDEABLE) && + (xd * xd + zd * zd > 0.01)) { + if ((rider.lock() == NULL) && (e->riding == NULL)) { e->ride(shared_from_this()); } } @@ -761,7 +716,7 @@ void Minecart::push(std::shared_ptr e) { xa *= 0.5; za *= 0.5; - if (e->GetType() == eTYPE_MINECART) { + if (e->instanceof(eTYPE_MINECART)) { double xo = e->x - x; double zo = e->z - z; @@ -785,16 +740,16 @@ void Minecart::push(std::shared_ptr e) { std::shared_ptr cart = std::dynamic_pointer_cast(e); - if (cart != NULL && cart->type == Minecart::FURNACE && - type != Minecart::FURNACE) { + if (cart != NULL && cart->getType() == TYPE_FURNACE && + getType() != TYPE_FURNACE) { xd *= 0.2f; zd *= 0.2f; - this->Entity::push(e->xd - xa, 0, e->zd - za); + push(e->xd - xa, 0, e->zd - za); e->xd *= 0.95f; e->zd *= 0.95f; m_bHasPushedCartThisTick = true; - } else if (cart != NULL && cart->type != Minecart::FURNACE && - type == Minecart::FURNACE) { + } else if (cart != NULL && cart->getType() != TYPE_FURNACE && + getType() == TYPE_FURNACE) { e->xd *= 0.2f; e->zd *= 0.2f; e->push(xd + xa, 0, zd + za); @@ -806,7 +761,7 @@ void Minecart::push(std::shared_ptr e) { zdd /= 2; xd *= 0.2f; zd *= 0.2f; - this->Entity::push(xdd - xa, 0, zdd - za); + push(xdd - xa, 0, zdd - za); e->xd *= 0.2f; e->zd *= 0.2f; e->push(xdd + xa, 0, zdd + za); @@ -833,97 +788,12 @@ void Minecart::push(std::shared_ptr e) { } } else { - this->Entity::push(-xa, 0, -za); + push(-xa, 0, -za); e->push(xa / 4, 0, za / 4); } } } -unsigned int Minecart::getContainerSize() { return 9 * 3; } - -std::shared_ptr Minecart::getItem(unsigned int slot) { - return (*items)[slot]; -} - -std::shared_ptr Minecart::removeItem(unsigned int slot, - int count) { - if ((*items)[slot] != NULL) { - if ((*items)[slot]->count <= count) { - std::shared_ptr item = (*items)[slot]; - (*items)[slot] = nullptr; - return item; - } else { - std::shared_ptr i = (*items)[slot]->remove(count); - if ((*items)[slot]->count == 0) (*items)[slot] = nullptr; - return i; - } - } - return nullptr; -} - -std::shared_ptr Minecart::removeItemNoUpdate(int slot) { - if ((*items)[slot] != NULL) { - std::shared_ptr item = (*items)[slot]; - (*items)[slot] = nullptr; - return item; - } - return nullptr; -} - -void Minecart::setItem(unsigned int slot, std::shared_ptr item) { - (*items)[slot] = item; - if (item != NULL && item->count > getMaxStackSize()) - item->count = getMaxStackSize(); -} - -int Minecart::getName() { return IDS_ITEM_MINECART; } - -int Minecart::getMaxStackSize() { return Container::LARGE_MAX_STACK_SIZE; } - -void Minecart::setChanged() {} - -bool Minecart::interact(std::shared_ptr player) { - if (type == Minecart::RIDEABLE) { - if (rider.lock() != NULL && - std::dynamic_pointer_cast(rider.lock()) != NULL && - rider.lock() != player) - return true; - if (!level->isClientSide) { - // 4J HEG - Fixed issue with player not being able to dismount - // minecart (issue #4455) - player->ride(rider.lock() == player ? nullptr : shared_from_this()); - } - } else if (type == Minecart::CHEST) { - if (player->isAllowedToInteract(shared_from_this())) { - if (!level->isClientSide) - player->openContainer( - std::dynamic_pointer_cast(shared_from_this())); - } else { - return false; - } - } else if (type == Minecart::FURNACE) { - std::shared_ptr selected = - player->inventory->getSelected(); - if (selected != NULL && selected->id == Item::coal->id) { - if (--selected->count == 0) - player->inventory->setItem(player->inventory->selected, - nullptr); - fuel += SharedConstants::TICKS_PER_SECOND * 180; - } - xPush = x - player->x; - zPush = z - player->z; - } - return true; -} - -float Minecart::getLootContent() { - int count = 0; - for (unsigned int i = 0; i < items->length; i++) { - if ((*items)[i] != NULL) count++; - } - return count / (float)items->length; -} - void Minecart::lerpTo(double x, double y, double z, float yRot, float xRot, int steps) { lx = x; @@ -934,9 +804,9 @@ void Minecart::lerpTo(double x, double y, double z, float yRot, float xRot, lSteps = steps + 2; - this->xd = lxd; - this->yd = lyd; - this->zd = lzd; + xd = lxd; + yd = lyd; + zd = lzd; } void Minecart::lerpMotion(double xd, double yd, double zd) { @@ -945,39 +815,11 @@ void Minecart::lerpMotion(double xd, double yd, double zd) { lzd = this->zd = zd; } -bool Minecart::stillValid(std::shared_ptr player) { - if (this->removed) return false; - if (player->distanceToSqr(shared_from_this()) > 8 * 8) return false; - return true; -} - -bool Minecart::hasFuel() { - return (entityData->getByte(DATA_ID_FUEL) & 1) != 0; -} - -void Minecart::setHasFuel(bool fuel) { - if (fuel) { - entityData->set(DATA_ID_FUEL, - (uint8_t)(entityData->getByte(DATA_ID_FUEL) | 1)); - } else { - entityData->set(DATA_ID_FUEL, - (uint8_t)(entityData->getByte(DATA_ID_FUEL) & ~1)); - } -} - -void Minecart::startOpen() { - // TODO Auto-generated method stub -} - -void Minecart::stopOpen() { - // TODO Auto-generated method stub -} - -void Minecart::setDamage(int damage) { +void Minecart::setDamage(float damage) { entityData->set(DATA_ID_DAMAGE, damage); } -int Minecart::getDamage() { return entityData->getInteger(DATA_ID_DAMAGE); } +float Minecart::getDamage() { return entityData->getFloat(DATA_ID_DAMAGE); } void Minecart::setHurtTime(int hurtTime) { entityData->set(DATA_ID_HURT, hurtTime); @@ -990,3 +832,63 @@ void Minecart::setHurtDir(int hurtDir) { } int Minecart::getHurtDir() { return entityData->getInteger(DATA_ID_HURTDIR); } + +Tile* Minecart::getDisplayTile() { + if (!hasCustomDisplay()) return getDefaultDisplayTile(); + int id = getEntityData()->getInteger(DATA_ID_DISPLAY_TILE) & 0xFFFF; + return id > 0 && id < Tile::TILE_NUM_COUNT ? Tile::tiles[id] : NULL; +} + +Tile* Minecart::getDefaultDisplayTile() { return NULL; } + +int Minecart::getDisplayData() { + if (!hasCustomDisplay()) return getDefaultDisplayData(); + return getEntityData()->getInteger(DATA_ID_DISPLAY_TILE) >> 16; +} + +int Minecart::getDefaultDisplayData() { return 0; } + +int Minecart::getDisplayOffset() { + if (!hasCustomDisplay()) return getDefaultDisplayOffset(); + return getEntityData()->getInteger(DATA_ID_DISPLAY_OFFSET); +} + +int Minecart::getDefaultDisplayOffset() { return 6; } + +void Minecart::setDisplayTile(int id) { + getEntityData()->set(DATA_ID_DISPLAY_TILE, + (id & 0xFFFF) | (getDisplayData() << 16)); + setCustomDisplay(true); +} + +void Minecart::setDisplayData(int data) { + Tile* tile = getDisplayTile(); + int id = tile == NULL ? 0 : tile->id; + + getEntityData()->set(DATA_ID_DISPLAY_TILE, (id & 0xFFFF) | (data << 16)); + setCustomDisplay(true); +} + +void Minecart::setDisplayOffset(int offset) { + getEntityData()->set(DATA_ID_DISPLAY_OFFSET, offset); + setCustomDisplay(true); +} + +bool Minecart::hasCustomDisplay() { + return getEntityData()->getByte(DATA_ID_CUSTOM_DISPLAY) == 1; +} + +void Minecart::setCustomDisplay(bool value) { + getEntityData()->set(DATA_ID_CUSTOM_DISPLAY, (uint8_t)(value ? 1 : 0)); +} + +void Minecart::setCustomName(const std::wstring& name) { this->name = name; } + +std::wstring Minecart::getAName() { + if (!name.empty()) return name; + return Entity::getAName(); +} + +bool Minecart::hasCustomName() { return !name.empty(); } + +std::wstring Minecart::getCustomName() { return name; } \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/Minecart.h b/Minecraft.World/Entities/Mobs/Minecart.h index 10efc94a6..353df82dc 100644 --- a/Minecraft.World/Entities/Mobs/Minecart.h +++ b/Minecraft.World/Entities/Mobs/Minecart.h @@ -1,47 +1,51 @@ #pragma once #include "../Entity.h" -#include "../../Containers/Container.h" class DamageSource; +class Tickable; + +class Minecart : public Entity { + friend class MinecartRenderer; -class Minecart : public Entity, public Container { public: eINSTANCEOF GetType() { return eTYPE_MINECART; }; - static Entity* create(Level* level) { return new Minecart(level); } public: - static const int RIDEABLE = 0; - static const int CHEST = 1; - static const int FURNACE = 2; - -private: - ItemInstanceArray* items; // Array + static const int TYPE_RIDEABLE = 0; + static const int TYPE_CHEST = 1; + static const int TYPE_FURNACE = 2; + static const int TYPE_TNT = 3; + static const int TYPE_SPAWNER = 4; + static const int TYPE_HOPPER = 5; public: static const int serialVersionUID = 0; private: - static const int DATA_ID_FUEL = 16; static const int DATA_ID_HURT = 17; static const int DATA_ID_HURTDIR = 18; static const int DATA_ID_DAMAGE = 19; + static const int DATA_ID_DISPLAY_TILE = 20; + static const int DATA_ID_DISPLAY_OFFSET = 21; + static const int DATA_ID_CUSTOM_DISPLAY = 22; - int fuel; - -private: bool flipped; + Tickable* soundUpdater; + std::wstring name; protected: // 4J Added bool m_bHasPushedCartThisTick; public: - int type; - double xPush, zPush; - void _init(); Minecart(Level* level); + virtual ~Minecart(); + + static std::shared_ptr createMinecart(Level* level, double x, + double y, double z, + int type); protected: virtual bool makeStepSound(); @@ -52,10 +56,11 @@ public: virtual AABB* getCollideBox(); virtual bool isPushable(); - Minecart(Level* level, double x, double y, double z, int type); + Minecart(Level* level, double x, double y, double z); virtual double getRideHeight(); - virtual bool hurt(DamageSource* source, int damage); + virtual bool hurt(DamageSource* source, float damage); + virtual void destroy(DamageSource* source); virtual void animateHurt(); virtual bool isPickable(); virtual void remove(); @@ -65,6 +70,13 @@ private: public: virtual void tick(); + virtual void activateMinecart(int xt, int yt, int zt, bool state); + +protected: + virtual void comeOffTrack(double maxSpeed); + virtual void moveAlongTrack(int xt, int yt, int zt, double maxSpeed, + double slideSpeed, int tile, int data); + virtual void applyNaturalSlowdown(); virtual Vec3* getPosOffs(double x, double y, double z, double offs); virtual Vec3* getPos(double x, double y, double z); @@ -74,18 +86,8 @@ protected: public: virtual float getShadowHeightOffs(); + using Entity::push; virtual void push(std::shared_ptr e); - virtual unsigned int getContainerSize(); - virtual std::shared_ptr getItem(unsigned int slot); - virtual std::shared_ptr removeItem(unsigned int slot, - int count); - virtual std::shared_ptr removeItemNoUpdate(int slot); - virtual void setItem(unsigned int slot, std::shared_ptr item); - int getName(); - virtual int getMaxStackSize(); - virtual void setChanged(); - virtual bool interact(std::shared_ptr player); - virtual float getLootContent(); private: int lSteps; @@ -96,20 +98,29 @@ public: virtual void lerpTo(double x, double y, double z, float yRot, float xRot, int steps); virtual void lerpMotion(double xd, double yd, double zd); - virtual bool stillValid(std::shared_ptr player); -protected: - bool hasFuel(); - void setHasFuel(bool fuel); + virtual void setDamage(float damage); + virtual float getDamage(); + virtual void setHurtTime(int hurtTime); + virtual int getHurtTime(); + virtual void setHurtDir(int hurtDir); + virtual int getHurtDir(); -public: - virtual void startOpen(); - virtual void stopOpen(); + virtual int getType() = 0; - void setDamage(int damage); - int getDamage(); - void setHurtTime(int hurtTime); - int getHurtTime(); - void setHurtDir(int hurtDir); - int getHurtDir(); + virtual Tile* getDisplayTile(); + virtual Tile* getDefaultDisplayTile(); + virtual int getDisplayData(); + virtual int getDefaultDisplayData(); + virtual int getDisplayOffset(); + virtual int getDefaultDisplayOffset(); + virtual void setDisplayTile(int id); + virtual void setDisplayData(int data); + virtual void setDisplayOffset(int offset); + virtual bool hasCustomDisplay(); + virtual void setCustomDisplay(bool value); + virtual void setCustomName(const std::wstring& name); + virtual std::wstring getAName(); + virtual bool hasCustomName(); + virtual std::wstring getCustomName(); }; diff --git a/Minecraft.World/Entities/Mobs/MobGroupData.h b/Minecraft.World/Entities/Mobs/MobGroupData.h new file mode 100644 index 000000000..df70cf721 --- /dev/null +++ b/Minecraft.World/Entities/Mobs/MobGroupData.h @@ -0,0 +1,7 @@ +#pragma once + +class MobGroupData { +public: + // Required so this class is polymorphic + virtual void emptyFunc() {} +}; \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/MultiEntityMob.h b/Minecraft.World/Entities/Mobs/MultiEntityMob.h new file mode 100644 index 000000000..60ebda88f --- /dev/null +++ b/Minecraft.World/Entities/Mobs/MultiEntityMob.h @@ -0,0 +1,10 @@ +#pragma once + +class MultiEntityMobPart; + +class MultiEntityMob { +public: + virtual Level* getLevel() = 0; + virtual bool hurt(std::shared_ptr MultiEntityMobPart, + DamageSource* source, float damage) = 0; +}; \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/MultiEntityMobPart.cpp b/Minecraft.World/Entities/Mobs/MultiEntityMobPart.cpp new file mode 100644 index 000000000..66597d950 --- /dev/null +++ b/Minecraft.World/Entities/Mobs/MultiEntityMobPart.cpp @@ -0,0 +1,34 @@ +#include "../../Platform/stdafx.h" +#include "BossMob.h" +#include "MultiEntityMob.h" +#include "MultiEntityMobPart.h" + +MultiEntityMobPart::MultiEntityMobPart( + std::shared_ptr parentMob, const std::wstring& id, float w, + float h) + : Entity(parentMob->getLevel()), parentMob(parentMob), id(id) { + // 4J Stu - This function call had to be moved here from the Entity ctor to + // ensure that the derived version of the function is called + this->defineSynchedData(); + + setSize(w, h); +} + +void MultiEntityMobPart::defineSynchedData() {} + +void MultiEntityMobPart::readAdditionalSaveData(CompoundTag* tag) {} + +void MultiEntityMobPart::addAdditonalSaveData(CompoundTag* tag) {} + +bool MultiEntityMobPart::isPickable() { return true; } + +bool MultiEntityMobPart::hurt(DamageSource* source, float damage) { + return parentMob.lock()->hurt( + std::dynamic_pointer_cast(shared_from_this()), + source, damage); +} + +bool MultiEntityMobPart::is(std::shared_ptr other) { + return shared_from_this() == other || + parentMob.lock() == std::dynamic_pointer_cast(other); +} \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/MultiEntityMobPart.h b/Minecraft.World/Entities/Mobs/MultiEntityMobPart.h new file mode 100644 index 000000000..0af0a1a5e --- /dev/null +++ b/Minecraft.World/Entities/Mobs/MultiEntityMobPart.h @@ -0,0 +1,27 @@ +#pragma once +#include "../Entity.h" + +class Level; +class MultiEntityMob; + +class MultiEntityMobPart : public Entity { +public: + eINSTANCEOF GetType() { return eTYPE_MULTIENTITY_MOB_PART; }; + +public: + std::weak_ptr parentMob; + const std::wstring id; + + MultiEntityMobPart(std::shared_ptr parentMob, + const std::wstring& id, float w, float h); + +protected: + virtual void defineSynchedData(); + virtual void readAdditionalSaveData(CompoundTag* tag); + virtual void addAdditonalSaveData(CompoundTag* tag); + +public: + virtual bool isPickable(); + virtual bool hurt(DamageSource* source, float damage); + virtual bool is(std::shared_ptr other); +}; \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/MushroomCow.cpp b/Minecraft.World/Entities/Mobs/MushroomCow.cpp index 54a5921a6..fe8bed57b 100644 --- a/Minecraft.World/Entities/Mobs/MushroomCow.cpp +++ b/Minecraft.World/Entities/Mobs/MushroomCow.cpp @@ -12,13 +12,13 @@ MushroomCow::MushroomCow(Level* level) : Cow(level) { // 4J Stu - This function call had to be moved here from the Entity ctor to // ensure that the derived version of the function is called - health = getMaxHealth(); + this->defineSynchedData(); + setHealth(getMaxHealth()); - this->textureIdx = TN_MOB_RED_COW; // 4J was "/mob/redcow.png"; this->setSize(0.9f, 1.3f); } -bool MushroomCow::interact(std::shared_ptr player) { +bool MushroomCow::mobInteract(std::shared_ptr player) { std::shared_ptr item = player->inventory->getSelected(); if (item != NULL && item->id == Item::bowl_Id && getAge() >= 0) { if (item->count == 1) { @@ -36,13 +36,13 @@ bool MushroomCow::interact(std::shared_ptr player) { return true; } } - if (item != NULL && item->id == Item::shears_Id && getAge() >= 0) { + // 4J: Do not allow shearing if we can't create more cows + if (item != NULL && item->id == Item::shears_Id && getAge() >= 0 && + level->canCreateMore(eTYPE_COW, Level::eSpawnType_Breed)) { remove(); level->addParticle(eParticleType_largeexplode, x, y + bbHeight / 2, z, 0, 0, 0); if (!level->isClientSide) { - // 4J Stu - We don't need to check spawn limits when adding the new - // cow, as we are removing the MushroomCow remove(); std::shared_ptr cow = std::shared_ptr(new Cow(level)); cow->moveTo(x, y, z, yRot, xRot); @@ -53,13 +53,13 @@ bool MushroomCow::interact(std::shared_ptr player) { level->addEntity(std::shared_ptr( new ItemEntity(level, x, y + bbHeight, z, std::shared_ptr( - new ItemInstance(Tile::mushroom2))))); + new ItemInstance(Tile::mushroom_red))))); } return true; } return true; } - return Cow::interact(player); + return Cow::mobInteract(player); } // 4J - added so that mushroom cows have more of a chance of spawning, they can diff --git a/Minecraft.World/Entities/Mobs/MushroomCow.h b/Minecraft.World/Entities/Mobs/MushroomCow.h index aed6adc0f..1724ee510 100644 --- a/Minecraft.World/Entities/Mobs/MushroomCow.h +++ b/Minecraft.World/Entities/Mobs/MushroomCow.h @@ -10,7 +10,7 @@ public: public: MushroomCow(Level* level); - virtual bool interact(std::shared_ptr player); + virtual bool mobInteract(std::shared_ptr player); virtual bool canSpawn(); // 4J added virtual std::shared_ptr getBreedOffspring( std::shared_ptr target); diff --git a/Minecraft.World/Entities/Mobs/Ocelot.cpp b/Minecraft.World/Entities/Mobs/Ocelot.cpp index 5b59b9bed..f9a0e1bdc 100644 --- a/Minecraft.World/Entities/Mobs/Ocelot.cpp +++ b/Minecraft.World/Entities/Mobs/Ocelot.cpp @@ -1,10 +1,12 @@ #include "../../Platform/stdafx.h" +#include "../../Headers/net.minecraft.world.entity.ai.attributes.h" #include "../../Headers/net.minecraft.world.entity.ai.control.h" #include "../../Headers/net.minecraft.world.entity.ai.goal.target.h" #include "../../Headers/net.minecraft.world.entity.ai.goal.h" #include "../../Headers/net.minecraft.world.entity.ai.navigation.h" #include "../../Headers/net.minecraft.world.entity.animal.h" #include "../../Headers/net.minecraft.world.entity.player.h" +#include "../../Headers/net.minecraft.world.entity.monster.h" #include "../../Headers/net.minecraft.world.entity.h" #include "../../Headers/net.minecraft.world.damagesource.h" #include "../../Headers/net.minecraft.world.level.h" @@ -13,67 +15,62 @@ #include "../SyncedEntityData.h" #include "../../Util/StringHelpers.h" #include "../../../Minecraft.Client/Textures/Textures.h" +#include "../../../Minecraft.Client/Minecraft.h" +#include "../../../Minecraft.Client/Player/MultiPlayerLocalPlayer.h" #include "../../Stats/GenericStats.h" #include "Ocelot.h" -const float Ozelot::SNEAK_SPEED = 0.18f; -const float Ozelot::WALK_SPEED = 0.23f; -const float Ozelot::FOLLOW_SPEED = 0.3f; -const float Ozelot::SPRINT_SPEED = 0.4f; +const double Ocelot::SNEAK_SPEED_MOD = 0.6; +const double Ocelot::WALK_SPEED_MOD = 0.8; +const double Ocelot::FOLLOW_SPEED_MOD = 1.0; +const double Ocelot::SPRINT_SPEED_MOD = 1.33; -const int Ozelot::DATA_TYPE_ID = 18; +const int Ocelot::DATA_TYPE_ID = 18; -const int Ozelot::TYPE_OZELOT = 0; -const int Ozelot::TYPE_BLACK = 1; -const int Ozelot::TYPE_RED = 2; -const int Ozelot::TYPE_SIAMESE = 3; - -Ozelot::Ozelot(Level* level) : TamableAnimal(level) { +Ocelot::Ocelot(Level* level) : TamableAnimal(level) { // 4J Stu - This function call had to be moved here from the Entity ctor to // ensure that the derived version of the function is called this->defineSynchedData(); + registerAttributes(); + setHealth(getMaxHealth()); - // 4J Stu - This function call had to be moved here from the Entity ctor to - // ensure that the derived version of the function is called - health = getMaxHealth(); - - this->textureIdx = TN_MOB_OZELOT; // "/mob/ozelot.png"; - this->setSize(0.6f, 0.8f); + setSize(0.6f, 0.8f); getNavigation()->setAvoidWater(true); goalSelector.addGoal(1, new FloatGoal(this)); goalSelector.addGoal(2, sitGoal, false); + goalSelector.addGoal(3, + temptGoal = new TemptGoal(this, SNEAK_SPEED_MOD, + Item::fish_raw_Id, true), + false); goalSelector.addGoal( - 3, - temptGoal = new TemptGoal(this, SNEAK_SPEED, Item::fish_raw_Id, true), - false); - goalSelector.addGoal(4, new AvoidPlayerGoal(this, typeid(Player), 16, - WALK_SPEED, SPRINT_SPEED)); - goalSelector.addGoal(5, new FollowOwnerGoal(this, FOLLOW_SPEED, 10, 5)); - goalSelector.addGoal(6, new OcelotSitOnTileGoal(this, SPRINT_SPEED)); + 4, new AvoidPlayerGoal(this, typeid(Player), 16, WALK_SPEED_MOD, + SPRINT_SPEED_MOD)); + goalSelector.addGoal(5, new FollowOwnerGoal(this, FOLLOW_SPEED_MOD, 10, 5)); + goalSelector.addGoal(6, new OcelotSitOnTileGoal(this, SPRINT_SPEED_MOD)); goalSelector.addGoal(7, new LeapAtTargetGoal(this, 0.3f)); - goalSelector.addGoal(8, new OzelotAttackGoal(this)); - goalSelector.addGoal(9, new BreedGoal(this, WALK_SPEED)); - goalSelector.addGoal(10, new RandomStrollGoal(this, WALK_SPEED)); + goalSelector.addGoal(8, new OcelotAttackGoal(this)); + goalSelector.addGoal(9, new BreedGoal(this, WALK_SPEED_MOD)); + goalSelector.addGoal(10, new RandomStrollGoal(this, WALK_SPEED_MOD)); goalSelector.addGoal(11, new LookAtPlayerGoal(this, typeid(Player), 10)); targetSelector.addGoal( - 1, new NonTameRandomTargetGoal(this, typeid(Chicken), 14, 750, false)); + 1, new NonTameRandomTargetGoal(this, typeid(Chicken), 750, false)); } -void Ozelot::defineSynchedData() { +void Ocelot::defineSynchedData() { TamableAnimal::defineSynchedData(); - entityData->define(DATA_TYPE_ID, (uint8_t)TYPE_OZELOT); + entityData->define(DATA_TYPE_ID, (uint8_t)0); } -void Ozelot::serverAiMobStep() { +void Ocelot::serverAiMobStep() { if (getMoveControl()->hasWanted()) { - float speed = getMoveControl()->getSpeed(); - if (speed == SNEAK_SPEED) { + double speed = getMoveControl()->getSpeedModifier(); + if (speed == SNEAK_SPEED_MOD) { setSneaking(true); setSprinting(false); - } else if (speed == SPRINT_SPEED) { + } else if (speed == SPRINT_SPEED_MOD) { setSneaking(false); setSprinting(true); } else { @@ -86,47 +83,39 @@ void Ozelot::serverAiMobStep() { } } -bool Ozelot::removeWhenFarAway() { - return Animal::removeWhenFarAway() && !isTame(); +bool Ocelot::removeWhenFarAway() { + return Animal::removeWhenFarAway() && !isTame() && + tickCount > SharedConstants::TICKS_PER_SECOND * 60 * 2; } -int Ozelot::getTexture() { - switch (getCatType()) { - case TYPE_OZELOT: - return TN_MOB_OZELOT; //"/mob/ozelot.png"; - case TYPE_BLACK: - return TN_MOB_CAT_BLACK; //"/mob/cat_black.png"; - case TYPE_RED: - return TN_MOB_CAT_RED; //"/mob/cat_red.png"; - case TYPE_SIAMESE: - return TN_MOB_CAT_SIAMESE; //"/mob/cat_siamese.png"; - } - return TamableAnimal::getTexture(); +bool Ocelot::useNewAi() { return true; } + +void Ocelot::registerAttributes() { + TamableAnimal::registerAttributes(); + + getAttribute(SharedMonsterAttributes::MAX_HEALTH)->setBaseValue(10); + getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED)->setBaseValue(0.3f); } -bool Ozelot::useNewAi() { return true; } - -int Ozelot::getMaxHealth() { return 10; } - -void Ozelot::causeFallDamage(float distance) { +void Ocelot::causeFallDamage(float distance) { // do nothing } -void Ozelot::addAdditonalSaveData(CompoundTag* tag) { +void Ocelot::addAdditonalSaveData(CompoundTag* tag) { TamableAnimal::addAdditonalSaveData(tag); tag->putInt(L"CatType", getCatType()); } -void Ozelot::readAdditionalSaveData(CompoundTag* tag) { +void Ocelot::readAdditionalSaveData(CompoundTag* tag) { TamableAnimal::readAdditionalSaveData(tag); if (isTame()) { setCatType(tag->getInt(L"CatType")); } else { - setCatType(TYPE_OZELOT); + setCatType(TYPE_OCELOT); } } -int Ozelot::getAmbientSound() { +int Ocelot::getAmbientSound() { if (isTame()) { if (isInLove()) { return eSoundType_MOB_CAT_PURR; @@ -140,28 +129,29 @@ int Ozelot::getAmbientSound() { return -1; } -int Ozelot::getHurtSound() { return eSoundType_MOB_CAT_HITT; } +int Ocelot::getHurtSound() { return eSoundType_MOB_CAT_HIT; } -int Ozelot::getDeathSound() { return eSoundType_MOB_CAT_HITT; } +int Ocelot::getDeathSound() { return eSoundType_MOB_CAT_HIT; } -float Ozelot::getSoundVolume() { return 0.4f; } +float Ocelot::getSoundVolume() { return 0.4f; } -int Ozelot::getDeathLoot() { return Item::leather_Id; } +int Ocelot::getDeathLoot() { return Item::leather_Id; } -bool Ozelot::doHurtTarget(std::shared_ptr target) { +bool Ocelot::doHurtTarget(std::shared_ptr target) { return target->hurt(DamageSource::mobAttack( std::dynamic_pointer_cast(shared_from_this())), 3); } -bool Ozelot::hurt(DamageSource* source, int dmg) { +bool Ocelot::hurt(DamageSource* source, float dmg) { + if (isInvulnerable()) return false; sitGoal->wantToSit(false); return TamableAnimal::hurt(source, dmg); } -void Ozelot::dropDeathLoot(bool wasKilledByPlayer, int playerBonusLevel) {} +void Ocelot::dropDeathLoot(bool wasKilledByPlayer, int playerBonusLevel) {} -bool Ozelot::interact(std::shared_ptr player) { +bool Ocelot::mobInteract(std::shared_ptr player) { std::shared_ptr item = player->inventory->getSelected(); if (isTame()) { if (equalsIgnoreCase(player->getUUID(), getOwnerUUID())) { @@ -186,8 +176,8 @@ bool Ozelot::interact(std::shared_ptr player) { // 4J-JEV, hook for durango event. player->awardStat( - GenericStats::tamedEntity(eTYPE_OZELOT), - GenericStats::param_tamedEntity(eTYPE_OZELOT)); + GenericStats::tamedEntity(eTYPE_OCELOT), + GenericStats::param_tamedEntity(eTYPE_OCELOT)); setCatType(1 + level->random->nextInt(3)); setOwnerUUID(player->getUUID()); @@ -204,15 +194,15 @@ bool Ozelot::interact(std::shared_ptr player) { return true; } } - return TamableAnimal::interact(player); + return TamableAnimal::mobInteract(player); } -std::shared_ptr Ozelot::getBreedOffspring( +std::shared_ptr Ocelot::getBreedOffspring( std::shared_ptr target) { // 4J - added limit to number of animals that can be bred if (level->canCreateMore(GetType(), Level::eSpawnType_Breed)) { - std::shared_ptr offspring = - std::shared_ptr(new Ozelot(level)); + std::shared_ptr offspring = + std::shared_ptr(new Ocelot(level)); if (isTame()) { offspring->setOwnerUUID(getOwnerUUID()); offspring->setTame(true); @@ -224,28 +214,28 @@ std::shared_ptr Ozelot::getBreedOffspring( } } -bool Ozelot::isFood(std::shared_ptr itemInstance) { +bool Ocelot::isFood(std::shared_ptr itemInstance) { return itemInstance != NULL && itemInstance->id == Item::fish_raw_Id; } -bool Ozelot::canMate(std::shared_ptr animal) { +bool Ocelot::canMate(std::shared_ptr animal) { if (animal == shared_from_this()) return false; if (!isTame()) return false; - std::shared_ptr partner = std::dynamic_pointer_cast(animal); + std::shared_ptr partner = std::dynamic_pointer_cast(animal); if (partner == NULL) return false; if (!partner->isTame()) return false; return isInLove() && partner->isInLove(); } -int Ozelot::getCatType() { return entityData->getByte(DATA_TYPE_ID); } +int Ocelot::getCatType() { return entityData->getByte(DATA_TYPE_ID); } -void Ozelot::setCatType(int type) { +void Ocelot::setCatType(int type) { entityData->set(DATA_TYPE_ID, (uint8_t)type); } -bool Ozelot::canSpawn() { +bool Ocelot::canSpawn() { // artificially make ozelots more rare if (level->random->nextInt(3) == 0) { return false; @@ -268,9 +258,51 @@ bool Ozelot::canSpawn() { return false; } -std::wstring Ozelot::getAName() { +std::wstring Ocelot::getAName() { + if (hasCustomName()) return getCustomName(); +#ifdef _DEBUG if (isTame()) { return L"entity.Cat.name"; } return TamableAnimal::getAName(); +#else + return L""; +#endif } + +MobGroupData* Ocelot::finalizeMobSpawn( + MobGroupData* groupData, int extraData /*= 0*/) // 4J Added extraData param +{ + groupData = TamableAnimal::finalizeMobSpawn(groupData); + +#ifndef _CONTENT_PACKAGE + if (app.DebugArtToolsOn() && (extraData != 0)) { + setTame(true); + setCatType(extraData - 1); + setOwnerUUID(Minecraft::GetInstance() + ->localplayers[ProfileManager.GetPrimaryPad()] + ->getUUID()); + } else +#endif + if (level->random->nextInt(7) == 0) { + for (int kitten = 0; kitten < 2; kitten++) { + std::shared_ptr ocelot = + std::shared_ptr(new Ocelot(level)); + ocelot->moveTo(x, y, z, yRot, 0); + ocelot->setAge(-20 * 60 * 20); + level->addEntity(ocelot); + } + } + return groupData; +} + +void Ocelot::setSittingOnTile(bool val) { + uint8_t current = entityData->getByte(DATA_FLAGS_ID); + entityData->set(DATA_FLAGS_ID, val ? (uint8_t)(current | 0x02) + : (uint8_t)(current & ~0x02)); +} + +bool Ocelot::isSittingOnTile() { + uint8_t current = entityData->getByte(DATA_FLAGS_ID); + return (current & 0x02) > 0; +} \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/Ocelot.h b/Minecraft.World/Entities/Mobs/Ocelot.h index ae3a52518..f60c51de7 100644 --- a/Minecraft.World/Entities/Mobs/Ocelot.h +++ b/Minecraft.World/Entities/Mobs/Ocelot.h @@ -4,29 +4,35 @@ class TemptGoal; -class Ozelot : public TamableAnimal { -public: - eINSTANCEOF GetType() { return eTYPE_OZELOT; } - static Entity* create(Level* level) { return new Ozelot(level); } +class Ocelot : public TamableAnimal { + friend class OcelotSitOnTileGoal; public: - static const float SNEAK_SPEED; - static const float WALK_SPEED; - static const float FOLLOW_SPEED; - static const float SPRINT_SPEED; + eINSTANCEOF GetType() { return eTYPE_OCELOT; } + static Entity* create(Level* level) { return new Ocelot(level); } + +public: + static const double SNEAK_SPEED_MOD; + static const double WALK_SPEED_MOD; + static const double FOLLOW_SPEED_MOD; + static const double SPRINT_SPEED_MOD; private: static const int DATA_TYPE_ID; - static const int TYPE_OZELOT; - static const int TYPE_BLACK; - static const int TYPE_RED; - static const int TYPE_SIAMESE; +public: + enum { + TYPE_OCELOT, + TYPE_BLACK, + TYPE_RED, + TYPE_SIAMESE, + }; +private: TemptGoal* temptGoal; public: - Ozelot(Level* level); + Ocelot(Level* level); protected: virtual void defineSynchedData(); @@ -38,11 +44,10 @@ protected: virtual bool removeWhenFarAway(); public: - virtual int getTexture(); virtual bool useNewAi(); - virtual int getMaxHealth(); protected: + virtual void registerAttributes(); virtual void causeFallDamage(float distance); public: @@ -58,13 +63,13 @@ protected: public: virtual bool doHurtTarget(std::shared_ptr target); - virtual bool hurt(DamageSource* source, int dmg); + virtual bool hurt(DamageSource* source, float dmg); protected: virtual void dropDeathLoot(bool wasKilledByPlayer, int playerBonusLevel); public: - virtual bool interact(std::shared_ptr player); + virtual bool mobInteract(std::shared_ptr player); virtual std::shared_ptr getBreedOffspring( std::shared_ptr target); virtual bool isFood(std::shared_ptr itemInstance); @@ -73,4 +78,15 @@ public: virtual void setCatType(int type); virtual bool canSpawn(); virtual std::wstring getAName(); + virtual MobGroupData* finalizeMobSpawn( + MobGroupData* groupData, + int extraData = 0); // 4J Added extraData param + + // 4J-JEV: Added for tooltips, is cat annoying player by sitting on chest or + // furnace. +private: + void setSittingOnTile(bool val); + +public: + bool isSittingOnTile(); }; \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/Painting.cpp b/Minecraft.World/Entities/Mobs/Painting.cpp index ea663b01b..c244eaee5 100644 --- a/Minecraft.World/Entities/Mobs/Painting.cpp +++ b/Minecraft.World/Entities/Mobs/Painting.cpp @@ -69,22 +69,30 @@ Painting::Painting(Level* level, int xTile, int yTile, int zTile, int dir) // PaintingPostConstructor } -// 4J Stu - Added this so that we can use some std::shared_ptr functions that -// were needed in the ctor -void Painting::PaintingPostConstructor(int dir) { - std::vector* survivableMotives = new std::vector(); - for (int i = 0; i < LAST_VALUE; i++) { - this->motive = (Motive*)Motive::values[i]; +// 4J Stu - Added this so that we can use some shared_ptr functions that were +// needed in the ctor 4J Stu - Added motive param for debugging/artists only +void Painting::PaintingPostConstructor(int dir, int motive) { +#ifndef _CONTENT_PACKAGE + if (app.DebugArtToolsOn() && motive >= 0) { + this->motive = (Motive*)Motive::values[motive]; setDir(dir); - if (survives()) { - survivableMotives->push_back(this->motive); + } else +#endif + { + std::vector* survivableMotives = new std::vector(); + for (int i = 0; i < LAST_VALUE; i++) { + this->motive = (Motive*)Motive::values[i]; + setDir(dir); + if (survives()) { + survivableMotives->push_back(this->motive); + } } + if (!survivableMotives->empty()) { + this->motive = survivableMotives->at( + random->nextInt((int)survivableMotives->size())); + } + setDir(dir); } - if (!survivableMotives->empty()) { - this->motive = survivableMotives->at( - random->nextInt((int)survivableMotives->size())); - } - setDir(dir); } Painting::Painting(Level* level, int x, int y, int z, int dir, @@ -125,7 +133,15 @@ int Painting::getWidth() { return motive->w; } int Painting::getHeight() { return motive->h; } -void Painting::dropItem() { +void Painting::dropItem(std::shared_ptr causedBy) { + if ((causedBy != NULL) && causedBy->instanceof(eTYPE_PLAYER)) { + std::shared_ptr player = + std::dynamic_pointer_cast(causedBy); + if (player->abilities.instabuild) { + return; + } + } + spawnAtLocation( std::shared_ptr(new ItemInstance(Item::painting)), 0.0f); } \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/Painting.h b/Minecraft.World/Entities/Mobs/Painting.h index 5bba42c2e..bf7b8d2b5 100644 --- a/Minecraft.World/Entities/Mobs/Painting.h +++ b/Minecraft.World/Entities/Mobs/Painting.h @@ -69,10 +69,6 @@ public: }; public: - // int dir; - // - // int xTile, yTile, zTile; - Motive* motive; private: @@ -85,9 +81,10 @@ public: Painting(Level* level, int x, int y, int z, int dir, std::wstring motiveName); - // 4J Stu - Added this so that we can use some std::shared_ptr functions - // that were needed in the ctor - void PaintingPostConstructor(int dir); + // 4J Stu - Added this so that we can use some shared_ptr functions that + // were needed in the ctor 4J Stu - Added motive param for debugging/artists + // only + void PaintingPostConstructor(int dir, int motive = -1); protected: // void defineSynchedData(); @@ -113,5 +110,5 @@ public: virtual int getWidth(); virtual int getHeight(); - virtual void dropItem(); + virtual void dropItem(std::shared_ptr causedBy); }; diff --git a/Minecraft.World/Entities/Mobs/Pig.cpp b/Minecraft.World/Entities/Mobs/Pig.cpp index ffd017e02..70b9c2090 100644 --- a/Minecraft.World/Entities/Mobs/Pig.cpp +++ b/Minecraft.World/Entities/Mobs/Pig.cpp @@ -6,6 +6,7 @@ #include "../../Headers/net.minecraft.world.level.h" #include "../../Headers/net.minecraft.world.item.h" #include "../../Headers/net.minecraft.world.entity.h" +#include "../../Headers/net.minecraft.world.entity.ai.attributes.h" #include "../../Headers/net.minecraft.world.entity.ai.goal.h" #include "../../Headers/net.minecraft.world.entity.ai.navigation.h" #include "../../Headers/net.minecraft.world.entity.item.h" @@ -20,34 +21,36 @@ Pig::Pig(Level* level) : Animal(level) { // 4J Stu - This function call had to be moved here from the Entity ctor to // ensure that the derived version of the function is called this->defineSynchedData(); + registerAttributes(); + setHealth(getMaxHealth()); - // 4J Stu - This function call had to be moved here from the Entity ctor to - // ensure that the derived version of the function is called - health = getMaxHealth(); - - this->textureIdx = TN_MOB_PIG; // 4J - was L"/mob/pig.png"; - this->setSize(0.9f, 0.9f); + setSize(0.9f, 0.9f); getNavigation()->setAvoidWater(true); - float walkSpeed = 0.25f; goalSelector.addGoal(0, new FloatGoal(this)); - goalSelector.addGoal(1, new PanicGoal(this, 0.38f)); + goalSelector.addGoal(1, new PanicGoal(this, 1.25)); goalSelector.addGoal( - 2, controlGoal = new ControlledByPlayerGoal(this, 0.34f, walkSpeed)); - goalSelector.addGoal(3, new BreedGoal(this, walkSpeed)); + 2, controlGoal = new ControlledByPlayerGoal(this, 0.3f, 0.25f)); + goalSelector.addGoal(3, new BreedGoal(this, 1.0)); goalSelector.addGoal( - 4, new TemptGoal(this, 0.3f, Item::carrotOnAStick_Id, false)); - goalSelector.addGoal(4, - new TemptGoal(this, 0.25f, Item::carrots_Id, false)); - goalSelector.addGoal(5, new FollowParentGoal(this, 0.28f)); - goalSelector.addGoal(6, new RandomStrollGoal(this, walkSpeed)); + 4, new TemptGoal(this, 1.2, Item::carrotOnAStick_Id, false)); + goalSelector.addGoal(4, new TemptGoal(this, 1.2, Item::carrots_Id, false)); + goalSelector.addGoal(5, new FollowParentGoal(this, 1.1)); + goalSelector.addGoal(6, new RandomStrollGoal(this, 1.0)); goalSelector.addGoal(7, new LookAtPlayerGoal(this, typeid(Player), 6)); goalSelector.addGoal(8, new RandomLookAroundGoal(this)); } bool Pig::useNewAi() { return true; } -int Pig::getMaxHealth() { return 10; } +void Pig::registerAttributes() { + Animal::registerAttributes(); + + getAttribute(SharedMonsterAttributes::MAX_HEALTH)->setBaseValue(10); + getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED)->setBaseValue(0.25f); +} + +void Pig::newServerAiStep() { Animal::newServerAiStep(); } bool Pig::canBeControlledByRider() { std::shared_ptr item = @@ -77,8 +80,12 @@ int Pig::getHurtSound() { return eSoundType_MOB_PIG_AMBIENT; } int Pig::getDeathSound() { return eSoundType_MOB_PIG_DEATH; } -bool Pig::interact(std::shared_ptr player) { - if (!Animal::interact(player)) { +void Pig::playStepSound(int xt, int yt, int zt, int t) { + playSound(eSoundType_MOB_PIG_STEP, 0.15f, 1); +} + +bool Pig::mobInteract(std::shared_ptr player) { + if (!Animal::mobInteract(player)) { if (hasSaddle() && !level->isClientSide && (rider.lock() == NULL || rider.lock() == player)) { // 4J HEG - Fixed issue with player not being able to dismount pig @@ -130,8 +137,8 @@ void Pig::thunderHit(const LightningBolt* lightningBolt) { void Pig::causeFallDamage(float distance) { Animal::causeFallDamage(distance); - if (distance > 5 && - std::dynamic_pointer_cast(rider.lock()) != NULL) { + if ((distance > 5) && rider.lock() != NULL && + rider.lock()->instanceof(eTYPE_PLAYER)) { (std::dynamic_pointer_cast(rider.lock())) ->awardStat(GenericStats::flyPig(), GenericStats::param_flyPig()); } diff --git a/Minecraft.World/Entities/Mobs/Pig.h b/Minecraft.World/Entities/Mobs/Pig.h index 8ceefe513..b6c4954f8 100644 --- a/Minecraft.World/Entities/Mobs/Pig.h +++ b/Minecraft.World/Entities/Mobs/Pig.h @@ -19,7 +19,12 @@ public: Pig(Level* level); virtual bool useNewAi(); - virtual int getMaxHealth(); + +protected: + virtual void registerAttributes(); + virtual void newServerAiStep(); + +public: virtual bool canBeControlledByRider(); protected: @@ -33,9 +38,10 @@ protected: virtual int getAmbientSound(); virtual int getHurtSound(); virtual int getDeathSound(); + virtual void playStepSound(int xt, int yt, int zt, int t); public: - virtual bool interact(std::shared_ptr player); + virtual bool mobInteract(std::shared_ptr player); protected: virtual int getDeathLoot(); diff --git a/Minecraft.World/Entities/Mobs/PigZombie.cpp b/Minecraft.World/Entities/Mobs/PigZombie.cpp index 240b95008..e619286cb 100644 --- a/Minecraft.World/Entities/Mobs/PigZombie.cpp +++ b/Minecraft.World/Entities/Mobs/PigZombie.cpp @@ -3,7 +3,9 @@ #include "../../Headers/net.minecraft.world.h" #include "../../Headers/net.minecraft.world.level.h" #include "../../Headers/net.minecraft.world.phys.h" +#include "../../Headers/net.minecraft.world.entity.ai.attributes.h" #include "../../Headers/net.minecraft.world.entity.player.h" +#include "../../Headers/net.minecraft.world.entity.monster.h" #include "../../Headers/net.minecraft.world.item.h" #include "../../Headers/net.minecraft.world.item.enchantment.h" #include "../../Headers/net.minecraft.world.entity.item.h" @@ -12,49 +14,52 @@ #include "../../../Minecraft.Client/Textures/Textures.h" #include "../../Util/SoundTypes.h" -std::shared_ptr PigZombie::sword; - -void PigZombie::staticCtor() { - PigZombie::sword = - std::shared_ptr(new ItemInstance(Item::sword_gold, 1)); -} +AttributeModifier* PigZombie::SPEED_MODIFIER_ATTACKING = + (new AttributeModifier(eModifierId_MOB_PIG_ATTACKSPEED, 0.45, + AttributeModifier::OPERATION_ADDITION)) + ->setSerialize(false); void PigZombie::_init() { + registerAttributes(); + angerTime = 0; playAngrySoundIn = 0; + lastAttackTarget = nullptr; } PigZombie::PigZombie(Level* level) : Zombie(level) { - // 4J Stu - This function call had to be moved here from the Entity ctor to - // ensure that the derived version of the function is called - - // 4J Stu - Zombie has already called this, and we don't override it so the - // Zombie one is the most derived version anyway - // this->defineSynchedData(); - - // 4J Stu - This function call had to be moved here from the Entity ctor to - // ensure that the derived version of the function is called - health = getMaxHealth(); - _init(); - this->textureIdx = TN_MOB_PIGZOMBIE; // 4J was L"/mob/pigzombie.png"; - runSpeed = 0.5f; - attackDamage = 5; - this->fireImmune = true; + fireImmune = true; +} + +void PigZombie::registerAttributes() { + Zombie::registerAttributes(); + + getAttribute(SPAWN_REINFORCEMENTS_CHANCE)->setBaseValue(0); + getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED)->setBaseValue(0.5f); + getAttribute(SharedMonsterAttributes::ATTACK_DAMAGE)->setBaseValue(5); } bool PigZombie::useNewAi() { return false; } -int PigZombie::getTexture() { return textureIdx; } - void PigZombie::tick() { - runSpeed = attackTarget != NULL ? 0.95f : 0.5f; + if (lastAttackTarget != attackTarget && !level->isClientSide) { + AttributeInstance* speed = + getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED); + speed->removeModifier(SPEED_MODIFIER_ATTACKING); + + if (attackTarget != NULL) { + speed->addModifier( + new AttributeModifier(*SPEED_MODIFIER_ATTACKING)); + } + } + lastAttackTarget = attackTarget; + if (playAngrySoundIn > 0) { if (--playAngrySoundIn == 0) { - level->playSound( - shared_from_this(), eSoundType_MOB_ZOMBIEPIG_ZPIGANGRY, - getSoundVolume() * 2, + playSound( + eSoundType_MOB_ZOMBIEPIG_ZPIGANGRY, getSoundVolume() * 2, ((random->nextFloat() - random->nextFloat()) * 0.2f + 1.0f) * 1.8f); } @@ -92,15 +97,15 @@ std::shared_ptr PigZombie::findAttackTarget() { return Zombie::findAttackTarget(); } -bool PigZombie::hurt(DamageSource* source, int dmg) { +bool PigZombie::hurt(DamageSource* source, float dmg) { std::shared_ptr sourceEntity = source->getEntity(); - if (std::dynamic_pointer_cast(sourceEntity) != NULL) { + if (sourceEntity != NULL && sourceEntity->instanceof(eTYPE_PLAYER)) { std::vector >* nearby = level->getEntities(shared_from_this(), bb->grow(32, 32, 32)); AUTO_VAR(itEnd, nearby->end()); for (AUTO_VAR(it, nearby->begin()); it != itEnd; it++) { std::shared_ptr e = *it; // nearby->at(i); - if (std::dynamic_pointer_cast(e) != NULL) { + if (e->instanceof(eTYPE_PIGZOMBIE)) { std::shared_ptr pigZombie = std::dynamic_pointer_cast(e); pigZombie->alert(sourceEntity); @@ -112,7 +117,7 @@ bool PigZombie::hurt(DamageSource* source, int dmg) { } void PigZombie::alert(std::shared_ptr target) { - this->attackTarget = target; + attackTarget = target; angerTime = 20 * 20 + random->nextInt(20 * 20); playAngrySoundIn = random->nextInt(20 * 2); } @@ -134,32 +139,23 @@ void PigZombie::dropDeathLoot(bool wasKilledByPlayer, int playerBonusLevel) { } } +bool PigZombie::mobInteract(std::shared_ptr player) { return false; } + void PigZombie::dropRareDeathLoot(int rareLootLevel) { - if (rareLootLevel > 0) { - std::shared_ptr sword = - std::shared_ptr(new ItemInstance(Item::sword_gold)); - EnchantmentHelper::enchantItem(random, sword, 5); - spawnAtLocation(sword, 0); - } else { - int select = random->nextInt(3); - if (select == 0) { - spawnAtLocation(Item::goldIngot_Id, 1); - } else if (select == 1) { - spawnAtLocation(Item::sword_gold_Id, 1); - } else if (select == 2) { - spawnAtLocation(Item::helmet_gold_Id, 1); - } - } + spawnAtLocation(Item::goldIngot_Id, 1); } int PigZombie::getDeathLoot() { return Item::rotten_flesh_Id; } -void PigZombie::finalizeMobSpawn() { - Zombie::finalizeMobSpawn(); - setVillager(false); +void PigZombie::populateDefaultEquipmentSlots() { + setEquippedSlot(SLOT_WEAPON, std::shared_ptr( + new ItemInstance(Item::sword_gold))); } -std::shared_ptr PigZombie::getCarriedItem() { - // TODO 4J - could be of const std::shared_ptr type. - return (std::shared_ptr)sword; -} +MobGroupData* PigZombie::finalizeMobSpawn( + MobGroupData* groupData, int extraData /*= 0*/) // 4J Added extraData param +{ + Zombie::finalizeMobSpawn(groupData); + setVillager(false); + return groupData; +} \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/PigZombie.h b/Minecraft.World/Entities/Mobs/PigZombie.h index 63bcfd98c..7d9787d5e 100644 --- a/Minecraft.World/Entities/Mobs/PigZombie.h +++ b/Minecraft.World/Entities/Mobs/PigZombie.h @@ -11,8 +11,11 @@ public: static Entity* create(Level* level) { return new PigZombie(level); } private: + static AttributeModifier* SPEED_MODIFIER_ATTACKING; + int angerTime; int playAngrySoundIn; + std::shared_ptr lastAttackTarget; void _init(); @@ -20,10 +23,10 @@ public: PigZombie(Level* level); protected: - bool useNewAi(); + virtual void registerAttributes(); + virtual bool useNewAi(); public: - virtual int getTexture(); virtual void tick(); virtual bool canSpawn(); virtual void addAdditonalSaveData(CompoundTag* tag); @@ -33,7 +36,7 @@ protected: virtual std::shared_ptr findAttackTarget(); public: - virtual bool hurt(DamageSource* source, int dmg); + virtual bool hurt(DamageSource* source, float dmg); private: void alert(std::shared_ptr target); @@ -43,16 +46,17 @@ protected: virtual int getHurtSound(); virtual int getDeathSound(); virtual void dropDeathLoot(bool wasKilledByPlayer, int playerBonusLevel); - virtual void dropRareDeathLoot(int rareLootLevel); - virtual int getDeathLoot(); - -private: - static std::shared_ptr sword; public: - virtual void finalizeMobSpawn(); + virtual bool mobInteract(std::shared_ptr player); - std::shared_ptr getCarriedItem(); +protected: + virtual void dropRareDeathLoot(int rareLootLevel); + virtual int getDeathLoot(); + virtual void populateDefaultEquipmentSlots(); - static void staticCtor(); +public: + virtual MobGroupData* finalizeMobSpawn( + MobGroupData* groupData, + int extraData = 0); // 4J Added extraData param }; diff --git a/Minecraft.World/Entities/Mobs/PrimedTnt.cpp b/Minecraft.World/Entities/Mobs/PrimedTnt.cpp index 4eda5e6f4..8fd49dce9 100644 --- a/Minecraft.World/Entities/Mobs/PrimedTnt.cpp +++ b/Minecraft.World/Entities/Mobs/PrimedTnt.cpp @@ -11,6 +11,8 @@ void PrimedTnt::_init() { blocksBuilding = true; setSize(0.98f, 0.98f); heightOffset = bbHeight / 2.0f; + + owner = std::weak_ptr(); } PrimedTnt::PrimedTnt(Level* level) : Entity(level) { @@ -21,7 +23,8 @@ PrimedTnt::PrimedTnt(Level* level) : Entity(level) { _init(); } -PrimedTnt::PrimedTnt(Level* level, double x, double y, double z) +PrimedTnt::PrimedTnt(Level* level, double x, double y, double z, + std::shared_ptr owner) : Entity(level) { _init(); @@ -37,6 +40,8 @@ PrimedTnt::PrimedTnt(Level* level, double x, double y, double z) xo = x; yo = y; zo = z; + + this->owner = std::weak_ptr(owner); } void PrimedTnt::defineSynchedData() {} @@ -74,7 +79,7 @@ void PrimedTnt::tick() { void PrimedTnt::explode() { float r = 4.0f; - level->explode(nullptr, x, y, z, r, true); + level->explode(shared_from_this(), x, y, z, r, true); } void PrimedTnt::addAdditonalSaveData(CompoundTag* entityTag) { @@ -85,4 +90,6 @@ void PrimedTnt::readAdditionalSaveData(CompoundTag* tag) { life = tag->getByte(L"Fuse"); } -float PrimedTnt::getShadowHeightOffs() { return 0; } \ No newline at end of file +float PrimedTnt::getShadowHeightOffs() { return 0; } + +std::shared_ptr PrimedTnt::getOwner() { return owner.lock(); } \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/PrimedTnt.h b/Minecraft.World/Entities/Mobs/PrimedTnt.h index abb4d3c94..46485788a 100644 --- a/Minecraft.World/Entities/Mobs/PrimedTnt.h +++ b/Minecraft.World/Entities/Mobs/PrimedTnt.h @@ -10,11 +10,13 @@ public: static const int serialVersionUID = 0; int life; + std::weak_ptr owner; void _init(); PrimedTnt(Level* level); - PrimedTnt(Level* level, double x, double y, double z); + PrimedTnt(Level* level, double x, double y, double z, + std::shared_ptr owner); protected: virtual void defineSynchedData(); @@ -33,4 +35,5 @@ protected: public: virtual float getShadowHeightOffs(); + virtual std::shared_ptr getOwner(); }; diff --git a/Minecraft.World/Entities/Mobs/RangedAttackMob.h b/Minecraft.World/Entities/Mobs/RangedAttackMob.h new file mode 100644 index 000000000..4680c318e --- /dev/null +++ b/Minecraft.World/Entities/Mobs/RangedAttackMob.h @@ -0,0 +1,7 @@ +#pragma once + +class RangedAttackMob { +public: + virtual void performRangedAttack(std::shared_ptr target, + float power) = 0; +}; \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/SharedMonsterAttributes.cpp b/Minecraft.World/Entities/Mobs/SharedMonsterAttributes.cpp new file mode 100644 index 000000000..c9a45521a --- /dev/null +++ b/Minecraft.World/Entities/Mobs/SharedMonsterAttributes.cpp @@ -0,0 +1,111 @@ +#include "../../Platform/stdafx.h" +#include "../../Headers/net.minecraft.world.entity.ai.attributes.h" +#include "../../Util/BasicTypeContainers.h" +#include "SharedMonsterAttributes.h" + +Attribute* SharedMonsterAttributes::MAX_HEALTH = + (new RangedAttribute(eAttributeId_GENERIC_MAXHEALTH, 20, 0, + Double::MAX_VALUE)) + ->setSyncable(true); +Attribute* SharedMonsterAttributes::FOLLOW_RANGE = + (new RangedAttribute(eAttributeId_GENERIC_FOLLOWRANGE, 32, 0, 2048)); +Attribute* SharedMonsterAttributes::KNOCKBACK_RESISTANCE = + (new RangedAttribute(eAttributeId_GENERIC_KNOCKBACKRESISTANCE, 0, 0, 1)); +Attribute* SharedMonsterAttributes::MOVEMENT_SPEED = + (new RangedAttribute(eAttributeId_GENERIC_MOVEMENTSPEED, 0.7f, 0, + Double::MAX_VALUE)) + ->setSyncable(true); +Attribute* SharedMonsterAttributes::ATTACK_DAMAGE = new RangedAttribute( + eAttributeId_GENERIC_ATTACKDAMAGE, 2, 0, Double::MAX_VALUE); + +ListTag* SharedMonsterAttributes::saveAttributes( + BaseAttributeMap* attributes) { + ListTag* list = new ListTag(); + + std::vector atts; + attributes->getAttributes(atts); + for (AUTO_VAR(it, atts.begin()); it != atts.end(); ++it) { + AttributeInstance* attribute = *it; + list->add(saveAttribute(attribute)); + } + + return list; +} + +CompoundTag* SharedMonsterAttributes::saveAttribute( + AttributeInstance* instance) { + CompoundTag* tag = new CompoundTag(); + Attribute* attribute = instance->getAttribute(); + + tag->putInt(L"ID", attribute->getId()); + tag->putDouble(L"Base", instance->getBaseValue()); + + std::unordered_set modifiers; + instance->getModifiers(modifiers); + + if (!modifiers.empty()) { + ListTag* list = new ListTag(); + + for (AUTO_VAR(it, modifiers.begin()); it != modifiers.end(); ++it) { + AttributeModifier* modifier = *it; + if (modifier->isSerializable()) { + list->add(saveAttributeModifier(modifier)); + } + } + + tag->put(L"Modifiers", list); + } + + return tag; +} + +CompoundTag* SharedMonsterAttributes::saveAttributeModifier( + AttributeModifier* modifier) { + CompoundTag* tag = new CompoundTag(); + + tag->putDouble(L"Amount", modifier->getAmount()); + tag->putInt(L"Operation", modifier->getOperation()); + tag->putInt(L"UUID", modifier->getId()); + + return tag; +} + +void SharedMonsterAttributes::loadAttributes(BaseAttributeMap* attributes, + ListTag* list) { + for (int i = 0; i < list->size(); i++) { + CompoundTag* tag = list->get(i); + AttributeInstance* instance = attributes->getInstance( + static_cast(tag->getInt(L"ID"))); + + if (instance != NULL) { + loadAttribute(instance, tag); + } else { + app.DebugPrintf("Ignoring unknown attribute '%d'", + tag->getInt(L"ID")); + } + } +} + +void SharedMonsterAttributes::loadAttribute(AttributeInstance* instance, + CompoundTag* tag) { + instance->setBaseValue(tag->getDouble(L"Base")); + + if (tag->contains(L"Modifiers")) { + ListTag* list = + (ListTag*)tag->getList(L"Modifiers"); + + for (int i = 0; i < list->size(); i++) { + AttributeModifier* modifier = loadAttributeModifier(list->get(i)); + AttributeModifier* old = instance->getModifier(modifier->getId()); + if (old != NULL) instance->removeModifier(old); + instance->addModifier(modifier); + } + } +} + +AttributeModifier* SharedMonsterAttributes::loadAttributeModifier( + CompoundTag* tag) { + eMODIFIER_ID id = (eMODIFIER_ID)tag->getInt(L"UUID"); + return new AttributeModifier(id, tag->getDouble(L"Amount"), + tag->getInt(L"Operation")); +} \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/SharedMonsterAttributes.h b/Minecraft.World/Entities/Mobs/SharedMonsterAttributes.h new file mode 100644 index 000000000..7082b90d5 --- /dev/null +++ b/Minecraft.World/Entities/Mobs/SharedMonsterAttributes.h @@ -0,0 +1,26 @@ +#pragma once + +class SharedMonsterAttributes { +public: + static Attribute* MAX_HEALTH; + static Attribute* FOLLOW_RANGE; + static Attribute* KNOCKBACK_RESISTANCE; + static Attribute* MOVEMENT_SPEED; + static Attribute* ATTACK_DAMAGE; + + static ListTag* saveAttributes(BaseAttributeMap* attributes); + +private: + static CompoundTag* saveAttribute(AttributeInstance* instance); + static CompoundTag* saveAttributeModifier(AttributeModifier* modifier); + +public: + static void loadAttributes(BaseAttributeMap* attributes, + ListTag* list); + +private: + static void loadAttribute(AttributeInstance* instance, CompoundTag* tag); + +public: + static AttributeModifier* loadAttributeModifier(CompoundTag* tag); +}; \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/Sheep.cpp b/Minecraft.World/Entities/Mobs/Sheep.cpp index 2fa6192cf..730bfe04a 100644 --- a/Minecraft.World/Entities/Mobs/Sheep.cpp +++ b/Minecraft.World/Entities/Mobs/Sheep.cpp @@ -1,5 +1,3 @@ - - #include "../../Platform/stdafx.h" #include "../../Headers/com.mojang.nbt.h" #include "../../Headers/net.minecraft.world.level.tile.h" @@ -8,6 +6,7 @@ #include "../../Headers/net.minecraft.world.item.h" #include "../../Headers/net.minecraft.world.item.crafting.h" #include "../../Headers/net.minecraft.world.inventory.h" +#include "../../Headers/net.minecraft.world.entity.ai.attributes.h" #include "../../Headers/net.minecraft.world.entity.ai.goal.h" #include "../../Headers/net.minecraft.world.entity.ai.navigation.h" #include "../../Headers/net.minecraft.world.entity.h" @@ -15,12 +14,13 @@ #include "../../Headers/net.minecraft.world.entity.player.h" #include "../../Headers/net.minecraft.world.entity.global.h" #include "../../Headers/net.minecraft.world.entity.player.h" +#include "../../Headers/net.minecraft.world.entity.monster.h" #include "Sheep.h" #include "../../../Minecraft.Client/Textures/Textures.h" #include "../MobCategory.h" #include "../../Stats/GenericStats.h" -const float Sheep::COLOR[][3] = { +const float Sheep::COLOR[Sheep::COLOR_LENGTH][3] = { {1.0f, 1.0f, 1.0f}, // white {0.85f, 0.5f, 0.2f}, // orange {0.7f, 0.3f, 0.85f}, // magenta @@ -43,27 +43,23 @@ Sheep::Sheep(Level* level) : Animal(level) { // 4J Stu - This function call had to be moved here from the Entity ctor to // ensure that the derived version of the function is called this->defineSynchedData(); + registerAttributes(); + setHealth(getMaxHealth()); - // 4J Stu - This function call had to be moved here from the Entity ctor to - // ensure that the derived version of the function is called - health = getMaxHealth(); - - this->textureIdx = TN_MOB_SHEEP; // 4J - was L"/mob/sheep.png"; - this->setSize(0.9f, 1.3f); + setSize(0.9f, 1.3f); eatAnimationTick = 0; eatTileGoal = new EatTileGoal(this); - float walkSpeed = 0.23f; getNavigation()->setAvoidWater(true); goalSelector.addGoal(0, new FloatGoal(this)); - goalSelector.addGoal(1, new PanicGoal(this, 0.38f)); - goalSelector.addGoal(2, new BreedGoal(this, walkSpeed)); - goalSelector.addGoal(3, new TemptGoal(this, 0.25f, Item::wheat_Id, false)); - goalSelector.addGoal(4, new FollowParentGoal(this, 0.25f)); + goalSelector.addGoal(1, new PanicGoal(this, 1.25)); + goalSelector.addGoal(2, new BreedGoal(this, 1.0)); + goalSelector.addGoal(3, new TemptGoal(this, 1.1, Item::wheat_Id, false)); + goalSelector.addGoal(4, new FollowParentGoal(this, 1.1)); goalSelector.addGoal(5, eatTileGoal, false); - goalSelector.addGoal(6, new RandomStrollGoal(this, walkSpeed)); + goalSelector.addGoal(6, new RandomStrollGoal(this, 1.0)); goalSelector.addGoal(7, new LookAtPlayerGoal(this, typeid(Player), 6)); goalSelector.addGoal(8, new RandomLookAroundGoal(this)); @@ -88,26 +84,30 @@ void Sheep::aiStep() { Animal::aiStep(); } -int Sheep::getMaxHealth() { return 8; } +void Sheep::registerAttributes() { + Animal::registerAttributes(); + + getAttribute(SharedMonsterAttributes::MAX_HEALTH)->setBaseValue(8); + getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED)->setBaseValue(0.23f); +} void Sheep::defineSynchedData() { Animal::defineSynchedData(); // sheared and color share a byte - entityData->define(DATA_WOOL_ID, - ((uint8_t)0)); // was new Byte((uint8_t), 0) + entityData->define(DATA_WOOL_ID, ((uint8_t)0)); // was new Byte((byte), 0) } void Sheep::dropDeathLoot(bool wasKilledByPlayer, int playerBonusLevel) { if (!isSheared()) { // killing a non-sheared sheep will drop a single block of cloth spawnAtLocation(std::shared_ptr( - new ItemInstance(Tile::cloth_Id, 1, getColor())), + new ItemInstance(Tile::wool_Id, 1, getColor())), 0.0f); } } -int Sheep::getDeathLoot() { return Tile::cloth_Id; } +int Sheep::getDeathLoot() { return Tile::wool_Id; } void Sheep::handleEntityEvent(uint8_t id) { if (id == EntityEvent::EAT_GRASS) { @@ -143,7 +143,7 @@ float Sheep::getHeadEatAngleScale(float a) { return ((xRot / (180.0f / PI))); } -bool Sheep::interact(std::shared_ptr player) { +bool Sheep::mobInteract(std::shared_ptr player) { std::shared_ptr item = player->inventory->getSelected(); // 4J-JEV: Fix for #88212, @@ -159,7 +159,7 @@ bool Sheep::interact(std::shared_ptr player) { for (int i = 0; i < count; i++) { std::shared_ptr ie = spawnAtLocation( std::shared_ptr( - new ItemInstance(Tile::cloth_Id, 1, getColor())), + new ItemInstance(Tile::wool_Id, 1, getColor())), 1.0f); ie->yd += random->nextFloat() * 0.05f; ie->xd += (random->nextFloat() - random->nextFloat()) * 0.1f; @@ -169,10 +169,11 @@ bool Sheep::interact(std::shared_ptr player) { player->awardStat(GenericStats::shearedEntity(eTYPE_SHEEP), GenericStats::param_shearedEntity(eTYPE_SHEEP)); } - item->hurt(1, player); + item->hurtAndBreak(1, player); + playSound(eSoundType_MOB_SHEEP_SHEAR, 1, 1); } - return Animal::interact(player); + return Animal::mobInteract(player); } void Sheep::addAdditonalSaveData(CompoundTag* tag) { @@ -193,6 +194,10 @@ int Sheep::getHurtSound() { return eSoundType_MOB_SHEEP_AMBIENT; } int Sheep::getDeathSound() { return eSoundType_MOB_SHEEP_AMBIENT; } +void Sheep::playStepSound(int xt, int yt, int zt, int t) { + playSound(eSoundType_MOB_SHEEP_STEP, 0.15f, 1); +} + int Sheep::getColor() { return (entityData->getByte(DATA_WOOL_ID) & 0x0f); } void Sheep::setColor(int color) { @@ -251,16 +256,17 @@ void Sheep::ate() { setSheared(false); if (isBaby()) { // remove a minute from aging - int age = getAge() + SharedConstants::TICKS_PER_SECOND * 60; - if (age > 0) { - age = 0; - } - setAge(age); + ageUp(60); } } -void Sheep::finalizeMobSpawn() { - setColor(Sheep::getSheepColor(level->random)); +MobGroupData* Sheep::finalizeMobSpawn( + MobGroupData* groupData, int extraData /*= 0*/) // 4J Added extraData param +{ + groupData = Animal::finalizeMobSpawn(groupData); + + setColor(getSheepColor(level->random)); + return groupData; } int Sheep::getOffspringColor(std::shared_ptr animal, diff --git a/Minecraft.World/Entities/Mobs/Sheep.h b/Minecraft.World/Entities/Mobs/Sheep.h index 02ae58050..a20b50b4a 100644 --- a/Minecraft.World/Entities/Mobs/Sheep.h +++ b/Minecraft.World/Entities/Mobs/Sheep.h @@ -29,7 +29,8 @@ private: EatTileGoal* eatTileGoal; public: - static const float COLOR[][3]; + static const int COLOR_LENGTH = 16; + static const float COLOR[COLOR_LENGTH][3]; public: Sheep(Level* level); @@ -40,9 +41,9 @@ protected: public: void aiStep(); - virtual int getMaxHealth(); protected: + virtual void registerAttributes(); virtual void defineSynchedData(); public: @@ -56,7 +57,7 @@ public: float getHeadEatPositionScale(float a); float getHeadEatAngleScale(float a); - virtual bool interact(std::shared_ptr player); + virtual bool mobInteract(std::shared_ptr player); virtual void addAdditonalSaveData(CompoundTag* tag); virtual void readAdditionalSaveData(CompoundTag* tag); @@ -64,6 +65,7 @@ protected: virtual int getAmbientSound(); virtual int getHurtSound(); virtual int getDeathSound(); + virtual void playStepSound(int xt, int yt, int zt, int t); public: int getColor(); @@ -77,7 +79,9 @@ public: virtual void ate(); - void finalizeMobSpawn(); + MobGroupData* finalizeMobSpawn( + MobGroupData* groupData, + int extraData = 0); // 4J Added extraData param private: int getOffspringColor(std::shared_ptr animal, diff --git a/Minecraft.World/Entities/Mobs/Silverfish.cpp b/Minecraft.World/Entities/Mobs/Silverfish.cpp index 733507b76..2ced041aa 100644 --- a/Minecraft.World/Entities/Mobs/Silverfish.cpp +++ b/Minecraft.World/Entities/Mobs/Silverfish.cpp @@ -3,6 +3,8 @@ #include "../../Headers/net.minecraft.world.level.tile.h" #include "../../Headers/net.minecraft.world.phys.h" #include "../../Headers/net.minecraft.world.damagesource.h" +#include "../../Headers/net.minecraft.world.entity.ai.attributes.h" +#include "../../Headers/net.minecraft.world.entity.monster.h" #include "../../Headers/net.minecraft.h" #include "../../../Minecraft.Client/Textures/Textures.h" #include "Silverfish.h" @@ -12,20 +14,19 @@ Silverfish::Silverfish(Level* level) : Monster(level) { // 4J Stu - This function call had to be moved here from the Entity ctor to // ensure that the derived version of the function is called this->defineSynchedData(); + registerAttributes(); + setHealth(getMaxHealth()); - // 4J Stu - This function call had to be moved here from the Entity ctor to - // ensure that the derived version of the function is called - health = getMaxHealth(); - - this->textureIdx = TN_MOB_SILVERFISH; // 4J was "/mob/silverfish.png"; - this->setSize(0.3f, 0.7f); - runSpeed = 0.6f; - - // 4J - Brought forward damage from 1.2.3 - attackDamage = 1; + setSize(0.3f, 0.7f); } -int Silverfish::getMaxHealth() { return 8; } +void Silverfish::registerAttributes() { + Monster::registerAttributes(); + + getAttribute(SharedMonsterAttributes::MAX_HEALTH)->setBaseValue(8); + getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED)->setBaseValue(0.6f); + getAttribute(SharedMonsterAttributes::ATTACK_DAMAGE)->setBaseValue(1); +} bool Silverfish::makeStepSound() { return false; } @@ -55,7 +56,8 @@ int Silverfish::getDeathSound() { return eSoundType_MOB_SILVERFISH_DEATH; } -bool Silverfish::hurt(DamageSource* source, int dmg) { +bool Silverfish::hurt(DamageSource* source, float dmg) { + if (isInvulnerable()) return false; if (lookForFriends <= 0 && (dynamic_cast(source) != NULL || source == DamageSource::magic)) { @@ -70,15 +72,12 @@ void Silverfish::checkHurtTarget(std::shared_ptr target, float d) { if (attackTime <= 0 && d < 1.2f && target->bb->y1 > bb->y0 && target->bb->y0 < bb->y1) { attackTime = 20; - DamageSource* damageSource = DamageSource::mobAttack( - std::dynamic_pointer_cast(shared_from_this())); - target->hurt(damageSource, attackDamage); - delete damageSource; + doHurtTarget(target); } } void Silverfish::playStepSound(int xt, int yt, int zt, int t) { - level->playSound(shared_from_this(), eSoundType_MOB_SILVERFISH_STEP, 1, 1); + playSound(eSoundType_MOB_SILVERFISH_STEP, 0.15f, 1); } int Silverfish::getDeathLoot() { return 0; } @@ -115,15 +114,26 @@ void Silverfish::serverAiStep() { int tile = level->getTile(baseX + xOff, baseY + yOff, baseZ + zOff); if (tile == Tile::monsterStoneEgg_Id) { - level->levelEvent( - LevelEvent::PARTICLES_DESTROY_BLOCK, - baseX + xOff, baseY + yOff, baseZ + zOff, - Tile::monsterStoneEgg_Id + - (level->getData(baseX + xOff, baseY + yOff, - baseZ + zOff) - << Tile::TILE_NUM_SHIFT)); - level->setTile(baseX + xOff, baseY + yOff, - baseZ + zOff, 0); + if (!level->getGameRules()->getBoolean( + GameRules::RULE_MOBGRIEFING)) { + int data = level->getData( + baseX + xOff, baseY + yOff, baseZ + zOff); + + Tile* restoreTile = Tile::stone; + if (data == StoneMonsterTile::HOST_COBBLE) { + restoreTile = Tile::cobblestone; + } + if (data == StoneMonsterTile::HOST_STONEBRICK) { + restoreTile = Tile::stoneBrick; + } + + level->setTileAndData( + baseX + xOff, baseY + yOff, baseZ + zOff, + restoreTile->id, 0, Tile::UPDATE_ALL); + } else { + level->destroyTile(baseX + xOff, baseY + yOff, + baseZ + zOff, false); + } Tile::monsterStoneEgg->destroy(level, baseX + xOff, baseY + yOff, baseZ + zOff, 0); @@ -153,7 +163,7 @@ void Silverfish::serverAiStep() { level->setTileAndData( tileX + Facing::STEP_X[facing], tileY + Facing::STEP_Y[facing], tileZ + Facing::STEP_Z[facing], Tile::monsterStoneEgg_Id, - StoneMonsterTile::getDataForHostBlock(tile)); + StoneMonsterTile::getDataForHostBlock(tile), Tile::UPDATE_ALL); spawnAnim(); remove(); } else { @@ -167,7 +177,7 @@ void Silverfish::serverAiStep() { float Silverfish::getWalkTargetValue(int x, int y, int z) { // silverfish LOVES stone =) - if (level->getTile(x, y - 1, z) == Tile::rock_Id) return 10; + if (level->getTile(x, y - 1, z) == Tile::stone_Id) return 10; return Monster::getWalkTargetValue(x, y, z); } @@ -182,4 +192,4 @@ bool Silverfish::canSpawn() { return false; } -MobType Silverfish::getMobType() { return ARTHROPOD; } +MobType Silverfish::getMobType() { return ARTHROPOD; } \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/Silverfish.h b/Minecraft.World/Entities/Mobs/Silverfish.h index 9d6171e92..f27d8b6f8 100644 --- a/Minecraft.World/Entities/Mobs/Silverfish.h +++ b/Minecraft.World/Entities/Mobs/Silverfish.h @@ -13,9 +13,8 @@ private: public: Silverfish(Level* level); - virtual int getMaxHealth(); - protected: + virtual void registerAttributes(); virtual bool makeStepSound(); virtual std::shared_ptr findAttackTarget(); @@ -24,7 +23,7 @@ protected: virtual int getDeathSound(); public: - virtual bool hurt(DamageSource* source, int dmg); + virtual bool hurt(DamageSource* source, float dmg); protected: virtual void checkHurtTarget(std::shared_ptr target, float d); diff --git a/Minecraft.World/Entities/Mobs/Skeleton.cpp b/Minecraft.World/Entities/Mobs/Skeleton.cpp index acaaa2ef3..51c3b7e48 100644 --- a/Minecraft.World/Entities/Mobs/Skeleton.cpp +++ b/Minecraft.World/Entities/Mobs/Skeleton.cpp @@ -1,14 +1,20 @@ #include "../../Platform/stdafx.h" #include "../../Headers/net.minecraft.world.h" #include "../../Headers/net.minecraft.world.level.h" +#include "../../Headers/net.minecraft.world.level.dimension.h" +#include "../../Headers/net.minecraft.world.level.tile.entity.h" #include "../../Headers/net.minecraft.world.item.h" #include "../../Headers/net.minecraft.world.item.enchantment.h" +#include "../../Headers/net.minecraft.world.effect.h" +#include "../../Headers/net.minecraft.world.entity.h" +#include "../../Headers/net.minecraft.world.entity.ai.attributes.h" #include "../../Headers/net.minecraft.world.entity.ai.goal.h" #include "../../Headers/net.minecraft.world.entity.ai.goal.target.h" #include "../../Headers/net.minecraft.world.entity.ai.navigation.h" #include "../../Headers/net.minecraft.world.entity.projectile.h" #include "../../Headers/net.minecraft.world.entity.item.h" #include "../../Headers/net.minecraft.world.entity.player.h" +#include "../../Headers/net.minecraft.world.entity.monster.h" #include "../../Headers/net.minecraft.stats.h" #include "../../Headers/net.minecraft.world.damagesource.h" #include "../../Util/SharedConstants.h" @@ -20,68 +26,126 @@ Skeleton::Skeleton(Level* level) : Monster(level) { // 4J Stu - This function call had to be moved here from the Entity ctor to // ensure that the derived version of the function is called this->defineSynchedData(); + registerAttributes(); + setHealth(getMaxHealth()); - // 4J Stu - This function call had to be moved here from the Entity ctor to - // ensure that the derived version of the function is called - health = getMaxHealth(); - - this->textureIdx = TN_MOB_SKELETON; // 4J was L"/mob/skeleton.png"; - - runSpeed = 0.25f; + bowGoal = new RangedAttackGoal(this, this, 1.0, + SharedConstants::TICKS_PER_SECOND * 1, + SharedConstants::TICKS_PER_SECOND * 3, 15); + meleeGoal = new MeleeAttackGoal(this, eTYPE_PLAYER, 1.2, false); goalSelector.addGoal(1, new FloatGoal(this)); goalSelector.addGoal(2, new RestrictSunGoal(this)); - goalSelector.addGoal(3, new FleeSunGoal(this, runSpeed)); - goalSelector.addGoal( - 4, new ArrowAttackGoal(this, runSpeed, ArrowAttackGoal::ArrowType, - SharedConstants::TICKS_PER_SECOND * 3)); - goalSelector.addGoal(5, new RandomStrollGoal(this, runSpeed)); + goalSelector.addGoal(3, new FleeSunGoal(this, 1.0)); + goalSelector.addGoal(5, new RandomStrollGoal(this, 1.0)); goalSelector.addGoal(6, new LookAtPlayerGoal(this, typeid(Player), 8)); goalSelector.addGoal(6, new RandomLookAroundGoal(this)); targetSelector.addGoal(1, new HurtByTargetGoal(this, false)); targetSelector.addGoal( - 2, new NearestAttackableTargetGoal(this, typeid(Player), 16, 0, true)); + 2, new NearestAttackableTargetGoal(this, typeid(Player), 0, true)); + + if (level != NULL && !level->isClientSide) reassessWeaponGoal(); +} + +Skeleton::~Skeleton() { + delete bowGoal; + delete meleeGoal; +} + +void Skeleton::registerAttributes() { + Monster::registerAttributes(); + + getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED)->setBaseValue(0.25f); +} + +void Skeleton::defineSynchedData() { + Monster::defineSynchedData(); + + entityData->define(DATA_TYPE_ID, (uint8_t)TYPE_DEFAULT); } bool Skeleton::useNewAi() { return true; } -int Skeleton::getMaxHealth() { return 20; } - int Skeleton::getAmbientSound() { return eSoundType_MOB_SKELETON_AMBIENT; } int Skeleton::getHurtSound() { return eSoundType_MOB_SKELETON_HURT; } -int Skeleton::getDeathSound() { return eSoundType_MOB_SKELETON_HURT; } +int Skeleton::getDeathSound() { return eSoundType_MOB_SKELETON_DEATH; } -std::shared_ptr Skeleton::bow; +void Skeleton::playStepSound(int xt, int yt, int zt, int t) { + playSound(eSoundType_MOB_SKELETON_STEP, 0.15f, 1); +} -std::shared_ptr Skeleton::getCarriedItem() { return bow; } +bool Skeleton::doHurtTarget(std::shared_ptr target) { + if (Monster::doHurtTarget(target)) { + if ((getSkeletonType() == TYPE_WITHER) && + target->instanceof(eTYPE_LIVINGENTITY)) { + std::dynamic_pointer_cast(target)->addEffect( + new MobEffectInstance(MobEffect::wither->id, + SharedConstants::TICKS_PER_SECOND * 10)); + } + return true; + } + return false; +} MobType Skeleton::getMobType() { return UNDEAD; } void Skeleton::aiStep() { - // isClientSide check brought forward from 1.8 (I assume it's related to - // the lighting changes) if (level->isDay() && !level->isClientSide) { float br = getBrightness(1); - if (br > 0.5f) { - if (level->canSeeSky(Mth::floor(x), Mth::floor(y), Mth::floor(z)) && - random->nextFloat() * 30 < (br - 0.4f) * 2) { + if (br > 0.5f && random->nextFloat() * 30 < (br - 0.4f) * 2 && + level->canSeeSky(Mth::floor(x), (int)floor(y + 0.5), + Mth::floor(z))) { + bool burn = true; + + std::shared_ptr helmet = getCarried(SLOT_HELM); + if (helmet != NULL) { + if (helmet->isDamageableItem()) { + helmet->setAuxValue(helmet->getDamageValue() + + random->nextInt(2)); + if (helmet->getDamageValue() >= helmet->getMaxDamage()) { + breakItem(helmet); + setEquippedSlot(SLOT_HELM, nullptr); + } + } + + burn = false; + } + + if (burn) { setOnFire(8); } } } + if (level->isClientSide) { + if (getSkeletonType() == TYPE_WITHER) { + setSize(0.6f * 1.2f, 1.8f * 1.3f); + } + } Monster::aiStep(); } +void Skeleton::rideTick() { + Monster::rideTick(); + + if (riding != NULL && riding->instanceof(eTYPE_PATHFINDER_MOB)) { + yBodyRot = std::dynamic_pointer_cast(riding)->yBodyRot; + } +} + void Skeleton::die(DamageSource* source) { Monster::die(source); - std::shared_ptr player = - std::dynamic_pointer_cast(source->getEntity()); - if (std::dynamic_pointer_cast(source->getDirectEntity()) != NULL && - player != NULL) { + + if (source->getDirectEntity() != NULL && + source->getDirectEntity()->instanceof(eTYPE_ARROW) && + source->getEntity() != NULL && + source->getEntity()->instanceof(eTYPE_PLAYER)) { + std::shared_ptr player = + std::dynamic_pointer_cast(source->getEntity()); + double xd = player->x - x; double zd = player->z - z; if (xd * xd + zd * zd >= 50 * 50) { @@ -94,30 +158,161 @@ void Skeleton::die(DamageSource* source) { int Skeleton::getDeathLoot() { return Item::arrow->id; } void Skeleton::dropDeathLoot(bool wasKilledByPlayer, int playerBonusLevel) { - // drop some arrows - int count = random->nextInt(3 + playerBonusLevel); - for (int i = 0; i < count; i++) { - spawnAtLocation(Item::arrow->id, 1); + if (getSkeletonType() == TYPE_WITHER) { + // drop some arrows + int count = random->nextInt(3 + playerBonusLevel) - 1; + for (int i = 0; i < count; i++) { + spawnAtLocation(Item::coal_Id, 1); + } + } else { + // drop some arrows + int count = random->nextInt(3 + playerBonusLevel); + for (int i = 0; i < count; i++) { + spawnAtLocation(Item::arrow_Id, 1); + } } + // and some bones - count = random->nextInt(3 + playerBonusLevel); + int count = random->nextInt(3 + playerBonusLevel); for (int i = 0; i < count; i++) { spawnAtLocation(Item::bone->id, 1); } } void Skeleton::dropRareDeathLoot(int rareLootLevel) { - if (rareLootLevel > 0) { - std::shared_ptr bow = - std::shared_ptr(new ItemInstance(Item::bow)); - EnchantmentHelper::enchantItem(random, bow, 5); - spawnAtLocation(bow, 0); - } else { - spawnAtLocation(Item::bow_Id, 1); + if (getSkeletonType() == TYPE_WITHER) { + spawnAtLocation(std::shared_ptr(new ItemInstance( + Item::skull_Id, 1, SkullTileEntity::TYPE_WITHER)), + 0); } } -void Skeleton::staticCtor() { - Skeleton::bow = - std::shared_ptr(new ItemInstance(Item::bow, 1)); +void Skeleton::populateDefaultEquipmentSlots() { + Monster::populateDefaultEquipmentSlots(); + + setEquippedSlot(SLOT_WEAPON, + std::shared_ptr(new ItemInstance(Item::bow))); } + +MobGroupData* Skeleton::finalizeMobSpawn( + MobGroupData* groupData, int extraData /*= 0*/) // 4J Added extraData param +{ + groupData = Monster::finalizeMobSpawn(groupData); + + if (dynamic_cast(level->dimension) != NULL && + getRandom()->nextInt(5) > 0) { + goalSelector.addGoal(4, meleeGoal, false); + + setSkeletonType(TYPE_WITHER); + setEquippedSlot(SLOT_WEAPON, std::shared_ptr( + new ItemInstance(Item::sword_stone))); + getAttribute(SharedMonsterAttributes::ATTACK_DAMAGE)->setBaseValue(4); + } else { + goalSelector.addGoal(4, bowGoal, false); + + populateDefaultEquipmentSlots(); + populateDefaultEquipmentEnchantments(); + } + + setCanPickUpLoot(random->nextFloat() < + MAX_PICKUP_LOOT_CHANCE * level->getDifficulty(x, y, z)); + + if (getCarried(SLOT_HELM) == NULL) { + if (Calendar::GetMonth() + 1 == 10 && Calendar::GetDayOfMonth() == 31 && + random->nextFloat() < 0.25f) { + // Halloween! OooOOo! 25% of all skeletons/zombies can wear pumpkins + // on their heads. + setEquippedSlot(SLOT_HELM, + std::shared_ptr(new ItemInstance( + random->nextFloat() < 0.1f ? Tile::litPumpkin + : Tile::pumpkin))); + dropChances[SLOT_HELM] = 0; + } + } + return groupData; +} + +void Skeleton::reassessWeaponGoal() { + goalSelector.removeGoal(meleeGoal); + goalSelector.removeGoal(bowGoal); + + std::shared_ptr carried = getCarriedItem(); + + if (carried != NULL && carried->id == Item::bow_Id) { + goalSelector.addGoal(4, bowGoal, false); + } else { + goalSelector.addGoal(4, meleeGoal, false); + } +} + +void Skeleton::performRangedAttack(std::shared_ptr target, + float power) { + std::shared_ptr arrow = std::shared_ptr(new Arrow( + level, std::dynamic_pointer_cast(shared_from_this()), + target, 1.60f, 14 - (level->difficulty * 4))); + int damageBonus = EnchantmentHelper::getEnchantmentLevel( + Enchantment::arrowBonus->id, getCarriedItem()); + int knockbackBonus = EnchantmentHelper::getEnchantmentLevel( + Enchantment::arrowKnockback->id, getCarriedItem()); + + arrow->setBaseDamage(power * 2.0f + (random->nextGaussian() * 0.25f + + (level->difficulty * 0.11f))); + + if (damageBonus > 0) { + arrow->setBaseDamage(arrow->getBaseDamage() + (double)damageBonus * .5 + + .5); + } + if (knockbackBonus > 0) { + arrow->setKnockback(knockbackBonus); + } + if (EnchantmentHelper::getEnchantmentLevel(Enchantment::arrowFire->id, + getCarriedItem()) > 0 || + getSkeletonType() == TYPE_WITHER) { + arrow->setOnFire(100); + } + + playSound(eSoundType_RANDOM_BOW, 1.0f, + 1 / (getRandom()->nextFloat() * 0.4f + 0.8f)); + level->addEntity(arrow); +} + +int Skeleton::getSkeletonType() { + return (int)entityData->getByte(DATA_TYPE_ID); +} + +void Skeleton::setSkeletonType(int type) { + entityData->set(DATA_TYPE_ID, (uint8_t)type); + + fireImmune = type == TYPE_WITHER; + if (type == TYPE_WITHER) { + setSize(0.6f * 1.2f, 1.8f * 1.3f); + } else { + setSize(0.6f, 1.8f); + } +} + +void Skeleton::readAdditionalSaveData(CompoundTag* tag) { + Monster::readAdditionalSaveData(tag); + + if (tag->contains(L"SkeletonType")) { + int value = tag->getByte(L"SkeletonType"); + setSkeletonType(value); + } + + reassessWeaponGoal(); +} + +void Skeleton::addAdditonalSaveData(CompoundTag* entityTag) { + Monster::addAdditonalSaveData(entityTag); + entityTag->putByte(L"SkeletonType", (uint8_t)getSkeletonType()); +} + +void Skeleton::setEquippedSlot(int slot, std::shared_ptr item) { + Monster::setEquippedSlot(slot, item); + + if (!level->isClientSide && slot == SLOT_WEAPON) { + reassessWeaponGoal(); + } +} + +double Skeleton::getRidingHeight() { return Monster::getRidingHeight() - .5; } \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/Skeleton.h b/Minecraft.World/Entities/Mobs/Skeleton.h index 7a38c77dd..714700696 100644 --- a/Minecraft.World/Entities/Mobs/Skeleton.h +++ b/Minecraft.World/Entities/Mobs/Skeleton.h @@ -1,36 +1,70 @@ #pragma once #include "../Monster.h" +#include "RangedAttackMob.h" -class Skeleton : public Monster { +class RangedAttackGoal; +class MeleeAttackGoal; + +class Skeleton : public Monster, public RangedAttackMob { public: eINSTANCEOF GetType() { return eTYPE_SKELETON; } static Entity* create(Level* level) { return new Skeleton(level); } - Skeleton(Level* level); +private: + static const int DATA_TYPE_ID = 13; +public: + static const int TYPE_DEFAULT = 0; + static const int TYPE_WITHER = 1; + +private: + RangedAttackGoal* bowGoal; + MeleeAttackGoal* meleeGoal; + +public: + Skeleton(Level* level); + virtual ~Skeleton(); + +protected: + virtual void registerAttributes(); + virtual void defineSynchedData(); + +public: virtual bool useNewAi(); - virtual int getMaxHealth(); protected: virtual int getAmbientSound(); virtual int getHurtSound(); virtual int getDeathSound(); + virtual void playStepSound(int xt, int yt, int zt, int t); + +public: + virtual bool doHurtTarget(std::shared_ptr target); public: - virtual std::shared_ptr getCarriedItem(); virtual MobType getMobType(); virtual void aiStep(); + virtual void rideTick(); virtual void die(DamageSource* source); protected: virtual int getDeathLoot(); virtual void dropDeathLoot(bool wasKilledByPlayer, int playerBonusLevel); virtual void dropRareDeathLoot(int rareLootLevel); - -private: - static std::shared_ptr bow; + virtual void populateDefaultEquipmentSlots(); public: - static void staticCtor(); + virtual MobGroupData* finalizeMobSpawn( + MobGroupData* groupData, + int extraData = 0); // 4J Added extraData param + virtual void reassessWeaponGoal(); + virtual void performRangedAttack(std::shared_ptr target, + float power); + virtual int getSkeletonType(); + virtual void setSkeletonType(int type); + virtual void readAdditionalSaveData(CompoundTag* tag); + virtual void addAdditonalSaveData(CompoundTag* entityTag); + virtual void setEquippedSlot(int slot, std::shared_ptr item); + virtual double getRidingHeight(); }; diff --git a/Minecraft.World/Entities/Mobs/Slime.cpp b/Minecraft.World/Entities/Mobs/Slime.cpp index db49dace1..a504db841 100644 --- a/Minecraft.World/Entities/Mobs/Slime.cpp +++ b/Minecraft.World/Entities/Mobs/Slime.cpp @@ -6,8 +6,10 @@ #include "../../Headers/net.minecraft.world.level.chunk.h" #include "../../Headers/net.minecraft.world.entity.h" #include "../../Headers/net.minecraft.world.item.h" +#include "../../Headers/net.minecraft.world.entity.ai.attributes.h" #include "../../Headers/net.minecraft.world.entity.item.h" #include "../../Headers/net.minecraft.world.entity.player.h" +#include "../../Headers/net.minecraft.world.entity.monster.h" #include "../../Headers/net.minecraft.world.damagesource.h" #include "../../Headers/com.mojang.nbt.h" #include "Slime.h" @@ -26,16 +28,13 @@ Slime::Slime(Level* level) : Mob(level) { // 4J Stu - This function call had to be moved here from the Entity ctor to // ensure that the derived version of the function is called this->defineSynchedData(); - - // 4J Stu - This function call had to be moved here from the Entity ctor to - // ensure that the derived version of the function is called - health = getMaxHealth(); + registerAttributes(); + setHealth(getMaxHealth()); _init(); - this->textureIdx = TN_MOB_SLIME; // 4J was L"/mob/slime.png"; int size = 1 << (random->nextInt(3)); - this->heightOffset = 0; + heightOffset = 0; jumpDelay = random->nextInt(20) + 10; setSize(size); } @@ -48,17 +47,14 @@ void Slime::defineSynchedData() { void Slime::setSize(int size) { entityData->set(ID_SIZE, (uint8_t)size); - Mob::setSize(0.6f * size, 0.6f * size); - this->setPos(x, y, z); + setSize(0.6f * size, 0.6f * size); + setPos(x, y, z); + getAttribute(SharedMonsterAttributes::MAX_HEALTH) + ->setBaseValue(size * size); setHealth(getMaxHealth()); xpReward = size; } -int Slime::getMaxHealth() { - int size = getSize(); - return size * size; -} - int Slime::getSize() { return entityData->getByte(ID_SIZE); } void Slime::addAdditonalSaveData(CompoundTag* tag) { @@ -73,7 +69,9 @@ void Slime::readAdditionalSaveData(CompoundTag* tag) { ePARTICLE_TYPE Slime::getParticleName() { return eParticleType_slime; } -int Slime::getSquishSound() { return eSoundType_MOB_SLIME; } +int Slime::getSquishSound() { + return getSize() > 1 ? eSoundType_MOB_SLIME_BIG : eSoundType_MOB_SLIME; +} void Slime::tick() { if (!level->isClientSide && level->difficulty == Difficulty::PEACEFUL && @@ -84,7 +82,7 @@ void Slime::tick() { squish = squish + (targetSquish - squish) * .5f; oSquish = squish; - bool wasOnGround = this->onGround; + bool wasOnGround = onGround; Mob::tick(); if (onGround && !wasOnGround) { int size = getSize(); @@ -98,8 +96,8 @@ void Slime::tick() { } if (doPlayLandSound()) { - level->playSound( - shared_from_this(), getSquishSound(), getSoundVolume(), + playSound( + getSquishSound(), getSoundVolume(), ((random->nextFloat() - random->nextFloat()) * 0.2f + 1.0f) / 0.8f); } @@ -110,6 +108,11 @@ void Slime::tick() { targetSquish = 1; } decreaseSquish(); + + if (level->isClientSide) { + int size = getSize(); + setSize(0.6f * size, 0.6f * size); + } } void Slime::serverAiStep() { @@ -126,8 +129,8 @@ void Slime::serverAiStep() { } jumping = true; if (doPlayJumpSound()) { - level->playSound( - shared_from_this(), getSquishSound(), getSoundVolume(), + playSound( + getSquishSound(), getSoundVolume(), ((random->nextFloat() - random->nextFloat()) * 0.2f + 1.0f) * 0.8f); } @@ -180,12 +183,12 @@ void Slime::playerTouch(std::shared_ptr player) { if (isDealsDamage()) { int size = getSize(); if (canSee(player) && - this->distanceToSqr(player) < (0.6 * size) * (0.6 * size)) { + distanceToSqr(player) < (0.6 * size) * (0.6 * size)) { DamageSource* damageSource = DamageSource::mobAttack( std::dynamic_pointer_cast(shared_from_this())); if (player->hurt(damageSource, getAttackDamage())) { - level->playSound( - shared_from_this(), eSoundType_MOB_SLIME_ATTACK, 1, + playSound( + eSoundType_MOB_SLIME_ATTACK, 1, (random->nextFloat() - random->nextFloat()) * 0.2f + 1.0f); } delete damageSource; @@ -197,9 +200,13 @@ bool Slime::isDealsDamage() { return getSize() > 1; } int Slime::getAttackDamage() { return getSize(); } -int Slime::getHurtSound() { return eSoundType_MOB_SLIME; } +int Slime::getHurtSound() { + return getSize() > 1 ? eSoundType_MOB_SLIME_BIG : eSoundType_MOB_SLIME; +} -int Slime::getDeathSound() { return eSoundType_MOB_SLIME; } +int Slime::getDeathSound() { + return getSize() > 1 ? eSoundType_MOB_SLIME_BIG : eSoundType_MOB_SLIME; +} int Slime::getDeathLoot() { if (getSize() == 1) return Item::slimeBall->id; @@ -214,11 +221,23 @@ bool Slime::canSpawn() { } Random* lcr = lc->getRandom(987234911l); // 4J - separated out so we can delete - if ((getSize() == 1 || level->difficulty > Difficulty::PEACEFUL) && - random->nextInt(10) == 0 && lcr->nextInt(10) == 0 && y < 40) { - delete lcr; - return Mob::canSpawn(); + if ((getSize() == 1 || level->difficulty > Difficulty::PEACEFUL)) { + // spawn slime in swamplands at night + Biome* biome = level->getBiome(Mth::floor(x), Mth::floor(z)); + + if (biome == Biome::swampland && y > 50 && y < 70 && + random->nextFloat() < 0.5f) { + if (random->nextFloat() < level->getMoonBrightness() && + level->getRawBrightness(Mth::floor(x), Mth::floor(y), + Mth::floor(z)) <= random->nextInt(8)) { + return Mob::canSpawn(); + } + } + if (random->nextInt(10) == 0 && lcr->nextInt(10) == 0 && y < 40) { + return Mob::canSpawn(); + } } + delete lcr; return false; } @@ -227,6 +246,6 @@ float Slime::getSoundVolume() { return 0.4f * getSize(); } int Slime::getMaxHeadXRot() { return 0; } -bool Slime::doPlayJumpSound() { return getSize() > 1; } +bool Slime::doPlayJumpSound() { return getSize() > 0; } bool Slime::doPlayLandSound() { return getSize() > 2; } \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/Slime.h b/Minecraft.World/Entities/Mobs/Slime.h index de0f87576..51483983a 100644 --- a/Minecraft.World/Entities/Mobs/Slime.h +++ b/Minecraft.World/Entities/Mobs/Slime.h @@ -29,10 +29,9 @@ protected: virtual void defineSynchedData(); public: - using Entity::setSize; + using Mob::setSize; virtual void setSize(int size); - virtual int getMaxHealth(); virtual int getSize(); virtual void addAdditonalSaveData(CompoundTag* tag); virtual void readAdditionalSaveData(CompoundTag* tag); diff --git a/Minecraft.World/Entities/Mobs/SmallFireball.cpp b/Minecraft.World/Entities/Mobs/SmallFireball.cpp index f349dff2c..139b1de69 100644 --- a/Minecraft.World/Entities/Mobs/SmallFireball.cpp +++ b/Minecraft.World/Entities/Mobs/SmallFireball.cpp @@ -10,8 +10,8 @@ SmallFireball::SmallFireball(Level* level) : Fireball(level) { setSize(5 / 16.0f, 5 / 16.0f); } -SmallFireball::SmallFireball(Level* level, std::shared_ptr mob, double xa, - double ya, double za) +SmallFireball::SmallFireball(Level* level, std::shared_ptr mob, + double xa, double ya, double za) : Fireball(level, mob, xa, ya, za) { setSize(5 / 16.0f, 5 / 16.0f); } @@ -57,7 +57,7 @@ void SmallFireball::onHit(HitResult* res) { break; }; if (level->isEmptyTile(tileX, tileY, tileZ)) { - level->setTile(tileX, tileY, tileZ, Tile::fire_Id); + level->setTileAndUpdate(tileX, tileY, tileZ, Tile::fire_Id); } } remove(); @@ -66,4 +66,4 @@ void SmallFireball::onHit(HitResult* res) { bool SmallFireball::isPickable() { return false; } -bool SmallFireball::hurt(DamageSource* source, int damage) { return false; } \ No newline at end of file +bool SmallFireball::hurt(DamageSource* source, float damage) { return false; } \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/SmallFireball.h b/Minecraft.World/Entities/Mobs/SmallFireball.h index bacdeaba9..8a1fa1634 100644 --- a/Minecraft.World/Entities/Mobs/SmallFireball.h +++ b/Minecraft.World/Entities/Mobs/SmallFireball.h @@ -11,8 +11,8 @@ public: public: SmallFireball(Level* level); - SmallFireball(Level* level, std::shared_ptr mob, double xa, double ya, - double za); + SmallFireball(Level* level, std::shared_ptr mob, double xa, + double ya, double za); SmallFireball(Level* level, double x, double y, double z, double xa, double ya, double za); @@ -21,5 +21,5 @@ protected: public: virtual bool isPickable(); - virtual bool hurt(DamageSource* source, int damage); + virtual bool hurt(DamageSource* source, float damage); }; \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/SnowMan.cpp b/Minecraft.World/Entities/Mobs/SnowMan.cpp index 7a90ec6af..3f44296a3 100644 --- a/Minecraft.World/Entities/Mobs/SnowMan.cpp +++ b/Minecraft.World/Entities/Mobs/SnowMan.cpp @@ -1,5 +1,6 @@ #include "../../Platform/stdafx.h" #include "../../Headers/net.minecraft.world.level.h" +#include "../../Headers/net.minecraft.world.entity.ai.attributes.h" #include "../../Headers/net.minecraft.world.entity.ai.goal.h" #include "../../Headers/net.minecraft.world.entity.ai.goal.target.h" #include "../../Headers/net.minecraft.world.entity.ai.navigation.h" @@ -19,34 +20,37 @@ SnowMan::SnowMan(Level* level) : Golem(level) { // 4J Stu - This function call had to be moved here from the Entity ctor to // ensure that the derived version of the function is called this->defineSynchedData(); + registerAttributes(); + setHealth(getMaxHealth()); - // 4J Stu - This function call had to be moved here from the Entity ctor to - // ensure that the derived version of the function is called - health = getMaxHealth(); - - this->textureIdx = TN_MOB_SNOWMAN; // 4J was "/mob/snowman.png"; this->setSize(0.4f, 1.8f); getNavigation()->setAvoidWater(true); goalSelector.addGoal( - 1, new ArrowAttackGoal(this, 0.25f, ArrowAttackGoal::SnowballType, - SharedConstants::TICKS_PER_SECOND * 1)); - goalSelector.addGoal(2, new RandomStrollGoal(this, 0.2f)); + 1, new RangedAttackGoal(this, this, 1.25, + SharedConstants::TICKS_PER_SECOND * 1, 10)); + goalSelector.addGoal(2, new RandomStrollGoal(this, 1.0)); goalSelector.addGoal(3, new LookAtPlayerGoal(this, typeid(Player), 6)); goalSelector.addGoal(4, new RandomLookAroundGoal(this)); targetSelector.addGoal( - 1, new NearestAttackableTargetGoal(this, typeid(Monster), 16, 0, true)); + 1, new NearestAttackableTargetGoal(this, typeid(Mob), 0, true, false, + Enemy::ENEMY_SELECTOR)); } bool SnowMan::useNewAi() { return true; } -int SnowMan::getMaxHealth() { return 4; } +void SnowMan::registerAttributes() { + Golem::registerAttributes(); + + getAttribute(SharedMonsterAttributes::MAX_HEALTH)->setBaseValue(4); + getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED)->setBaseValue(0.2f); +} void SnowMan::aiStep() { Golem::aiStep(); - if (this->isInWaterOrRain()) hurt(DamageSource::drown, 1); + if (isInWaterOrRain()) hurt(DamageSource::drown, 1); { int xx = Mth::floor(x); @@ -63,7 +67,7 @@ void SnowMan::aiStep() { if (level->getTile(xx, yy, zz) == 0) { if (level->getBiome(xx, zz)->getTemperature() < 0.8f) { if (Tile::topSnow->mayPlace(level, xx, yy, zz)) { - level->setTile(xx, yy, zz, Tile::topSnow_Id); + level->setTileAndUpdate(xx, yy, zz, Tile::topSnow_Id); } } } @@ -78,4 +82,19 @@ void SnowMan::dropDeathLoot(bool wasKilledByPlayer, int playerBonusLevel) { for (int i = 0; i < count; i++) { spawnAtLocation(Item::snowBall_Id, 1); } +} + +void SnowMan::performRangedAttack(std::shared_ptr target, + float power) { + std::shared_ptr snowball = std::shared_ptr(new Snowball( + level, std::dynamic_pointer_cast(shared_from_this()))); + double xd = target->x - x; + double yd = (target->y + target->getHeadHeight() - 1.1f) - snowball->y; + double zd = target->z - z; + float yo = Mth::sqrt(xd * xd + zd * zd) * 0.2f; + snowball->shoot(xd, yd + yo, zd, 1.60f, 12); + + playSound(eSoundType_RANDOM_BOW, 1.0f, + 1 / (getRandom()->nextFloat() * 0.4f + 0.8f)); + level->addEntity(snowball); } \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/SnowMan.h b/Minecraft.World/Entities/Mobs/SnowMan.h index fe2a52f4f..e582307de 100644 --- a/Minecraft.World/Entities/Mobs/SnowMan.h +++ b/Minecraft.World/Entities/Mobs/SnowMan.h @@ -1,8 +1,9 @@ #pragma once #include "Golem.h" +#include "RangedAttackMob.h" -class SnowMan : public Golem { +class SnowMan : public Golem, public RangedAttackMob { public: eINSTANCEOF GetType() { return eTYPE_SNOWMAN; } static Entity* create(Level* level) { return new SnowMan(level); } @@ -11,10 +12,17 @@ public: SnowMan(Level* level); virtual bool useNewAi(); - virtual int getMaxHealth(); +protected: + virtual void registerAttributes(); + +public: virtual void aiStep(); protected: virtual int getDeathLoot(); virtual void dropDeathLoot(bool wasKilledByPlayer, int playerBonusLevel); + +public: + virtual void performRangedAttack(std::shared_ptr target, + float power); }; \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/Snowball.cpp b/Minecraft.World/Entities/Mobs/Snowball.cpp index 52a67dbdc..5c64cc1a5 100644 --- a/Minecraft.World/Entities/Mobs/Snowball.cpp +++ b/Minecraft.World/Entities/Mobs/Snowball.cpp @@ -13,7 +13,7 @@ void Snowball::_init() { Snowball::Snowball(Level* level) : Throwable(level) { _init(); } -Snowball::Snowball(Level* level, std::shared_ptr mob) +Snowball::Snowball(Level* level, std::shared_ptr mob) : Throwable(level, mob) { _init(); } @@ -26,12 +26,12 @@ Snowball::Snowball(Level* level, double x, double y, double z) void Snowball::onHit(HitResult* res) { if (res->entity != NULL) { int damage = 0; - if (std::dynamic_pointer_cast(res->entity) != NULL) { + if (res->entity->instanceof(eTYPE_BLAZE)) { damage = 3; } DamageSource* damageSource = - DamageSource::thrown(shared_from_this(), owner); + DamageSource::thrown(shared_from_this(), getOwner()); res->entity->hurt(damageSource, damage); delete damageSource; } diff --git a/Minecraft.World/Entities/Mobs/Snowball.h b/Minecraft.World/Entities/Mobs/Snowball.h index a91978adb..9300d0e59 100644 --- a/Minecraft.World/Entities/Mobs/Snowball.h +++ b/Minecraft.World/Entities/Mobs/Snowball.h @@ -14,7 +14,7 @@ private: public: Snowball(Level* level); - Snowball(Level* level, std::shared_ptr mob); + Snowball(Level* level, std::shared_ptr mob); Snowball(Level* level, double x, double y, double z); protected: diff --git a/Minecraft.World/Entities/Mobs/Spider.cpp b/Minecraft.World/Entities/Mobs/Spider.cpp index ce721bbf9..ba427522f 100644 --- a/Minecraft.World/Entities/Mobs/Spider.cpp +++ b/Minecraft.World/Entities/Mobs/Spider.cpp @@ -2,11 +2,14 @@ #include "../../Headers/net.minecraft.world.h" #include "../../Headers/net.minecraft.world.level.h" #include "../../Headers/net.minecraft.world.item.h" +#include "../../Headers/net.minecraft.world.entity.ai.attributes.h" #include "../../Headers/net.minecraft.world.entity.item.h" #include "../../Headers/net.minecraft.world.entity.player.h" #include "../../Headers/net.minecraft.world.effect.h" #include "../../Headers/net.minecraft.world.entity.h" +#include "../../Headers/net.minecraft.world.entity.monster.h" #include "../../Headers/com.mojang.nbt.h" +#include "../../Util/BasicTypeContainers.h" #include "Spider.h" #include "../../../Minecraft.Client/Textures/Textures.h" #include "../../Util/SoundTypes.h" @@ -15,14 +18,10 @@ Spider::Spider(Level* level) : Monster(level) { // 4J Stu - This function call had to be moved here from the Entity ctor to // ensure that the derived version of the function is called this->defineSynchedData(); + registerAttributes(); + setHealth(getMaxHealth()); - // 4J Stu - This function call had to be moved here from the Entity ctor to - // ensure that the derived version of the function is called - health = getMaxHealth(); - - this->textureIdx = TN_MOB_SPIDER; // 4J was L"/mob/spider.png"; this->setSize(1.4f, 0.9f); - runSpeed = 0.8f; } void Spider::defineSynchedData() { @@ -41,11 +40,12 @@ void Spider::tick() { } } -int Spider::getMaxHealth() { return 16; } +void Spider::registerAttributes() { + Monster::registerAttributes(); -double Spider::getRideHeight() { return bbHeight * 0.75 - 0.5f; } - -bool Spider::makeStepSound() { return false; } + getAttribute(SharedMonsterAttributes::MAX_HEALTH)->setBaseValue(16); + getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED)->setBaseValue(0.8f); +} std::shared_ptr Spider::findAttackTarget() { #ifndef _FINAL_BUILD @@ -70,10 +70,14 @@ int Spider::getHurtSound() { return eSoundType_MOB_SPIDER_AMBIENT; } int Spider::getDeathSound() { return eSoundType_MOB_SPIDER_DEATH; } +void Spider::playStepSound(int xt, int yt, int zt, int t) { + playSound(eSoundType_MOB_SPIDER_STEP, 0.15f, 1); +} + void Spider::checkHurtTarget(std::shared_ptr target, float d) { float br = getBrightness(1); if (br > 0.5f && random->nextInt(100) == 0) { - this->attackTarget = nullptr; + attackTarget = nullptr; return; } @@ -113,8 +117,6 @@ void Spider::makeStuckInWeb() { // do nothing - spiders don't get stuck in web } -float Spider::getModelScale() { return 1.0f; } - MobType Spider::getMobType() { return ARTHROPOD; } bool Spider::canBeAffected(MobEffectInstance* newEffect) { @@ -136,4 +138,61 @@ void Spider::setClimbing(bool value) { flags &= ~0x1; } entityData->set(DATA_FLAGS_ID, flags); +} + +MobGroupData* Spider::finalizeMobSpawn( + MobGroupData* groupData, int extraData /*= 0*/) // 4J Added extraData param +{ + groupData = Monster::finalizeMobSpawn(groupData); + +#ifndef _CONTENT_PACKAGE + // 4J-JEV: Added for spider-jockey spawn-egg. + if ((level->random->nextInt(100) == 0) || (extraData != 0)) +#else + if (level->random->nextInt(100) == 0) +#endif + { + std::shared_ptr skeleton = + std::shared_ptr(new Skeleton(level)); + skeleton->moveTo(x, y, z, yRot, 0); + skeleton->finalizeMobSpawn(NULL); + level->addEntity(skeleton); + skeleton->ride(shared_from_this()); + } + + if (groupData == NULL) { + groupData = new SpiderEffectsGroupData(); + + if (level->difficulty > Difficulty::NORMAL && + level->random->nextFloat() < + SPIDER_SPECIAL_EFFECT_CHANCE * level->getDifficulty(x, y, z)) { + ((SpiderEffectsGroupData*)groupData) + ->setRandomEffect(level->random); + } + } + if (dynamic_cast(groupData) != NULL) { + int effect = ((SpiderEffectsGroupData*)groupData)->effectId; + if (effect > 0 && MobEffect::effects[effect] != NULL) { + addEffect(new MobEffectInstance(effect, Integer::MAX_VALUE)); + } + } + + return groupData; +} + +const float Spider::SPIDER_SPECIAL_EFFECT_CHANCE = .1f; + +Spider::SpiderEffectsGroupData::SpiderEffectsGroupData() { effectId = 0; } + +void Spider::SpiderEffectsGroupData::setRandomEffect(Random* random) { + int selection = random->nextInt(5); + if (selection <= 1) { + effectId = MobEffect::movementSpeed->id; + } else if (selection <= 2) { + effectId = MobEffect::damageBoost->id; + } else if (selection <= 3) { + effectId = MobEffect::regeneration->id; + } else if (selection <= 4) { + effectId = MobEffect::invisibility->id; + } } \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/Spider.h b/Minecraft.World/Entities/Mobs/Spider.h index 6642de01a..30d7a9509 100644 --- a/Minecraft.World/Entities/Mobs/Spider.h +++ b/Minecraft.World/Entities/Mobs/Spider.h @@ -1,6 +1,7 @@ #pragma once #include "../Monster.h" +#include "MobGroupData.h" class Spider : public Monster { public: @@ -18,15 +19,14 @@ protected: public: virtual void tick(); - virtual int getMaxHealth(); - virtual double getRideHeight(); protected: - virtual bool makeStepSound(); + virtual void registerAttributes(); virtual std::shared_ptr findAttackTarget(); virtual int getAmbientSound(); virtual int getHurtSound(); virtual int getDeathSound(); + virtual void playStepSound(int xt, int yt, int zt, int t); virtual void checkHurtTarget(std::shared_ptr target, float d); virtual int getDeathLoot(); virtual void dropDeathLoot(bool wasKilledByPlayer, int playerBonusLevel); @@ -35,9 +35,23 @@ public: virtual bool onLadder(); virtual void makeStuckInWeb(); - virtual float getModelScale(); virtual MobType getMobType(); virtual bool canBeAffected(MobEffectInstance* newEffect); virtual bool isClimbing(); virtual void setClimbing(bool value); + virtual MobGroupData* finalizeMobSpawn( + MobGroupData* groupData, + int extraData = 0); // 4J Added extraData param + +private: + static const float SPIDER_SPECIAL_EFFECT_CHANCE; + +public: + class SpiderEffectsGroupData : public MobGroupData { + public: + int effectId; + + SpiderEffectsGroupData(); + void setRandomEffect(Random* random); + }; }; diff --git a/Minecraft.World/Entities/Mobs/Squid.cpp b/Minecraft.World/Entities/Mobs/Squid.cpp index 6eb0eba1b..951d7b511 100644 --- a/Minecraft.World/Entities/Mobs/Squid.cpp +++ b/Minecraft.World/Entities/Mobs/Squid.cpp @@ -1,11 +1,12 @@ - - #include "../../Platform/stdafx.h" #include "../../Headers/com.mojang.nbt.h" #include "../../Headers/net.minecraft.world.level.tile.h" #include "../../Headers/net.minecraft.world.phys.h" #include "../../Headers/net.minecraft.world.level.h" #include "../../Headers/net.minecraft.world.item.h" +#include "../../Headers/net.minecraft.world.entity.h" +#include "../../Headers/net.minecraft.world.entity.ai.attributes.h" +#include "../../Headers/net.minecraft.world.entity.monster.h" #include "../../Util/SharedConstants.h" #include "Squid.h" #include "../../../Minecraft.Client/Textures/Textures.h" @@ -28,18 +29,19 @@ Squid::Squid(Level* level) : WaterAnimal(level) { // 4J Stu - This function call had to be moved here from the Entity ctor to // ensure that the derived version of the function is called this->defineSynchedData(); - - // 4J Stu - This function call had to be moved here from the Entity ctor to - // ensure that the derived version of the function is called - health = getMaxHealth(); + registerAttributes(); + setHealth(getMaxHealth()); _init(); - this->textureIdx = TN_MOB_SQUID; // 4J - was L"/mob/squid.png"; this->setSize(0.95f, 0.95f); tentacleSpeed = 1 / (random->nextFloat() + 1) * 0.2f; } -int Squid::getMaxHealth() { return 10; } +void Squid::registerAttributes() { + WaterAnimal::registerAttributes(); + + getAttribute(SharedMonsterAttributes::MAX_HEALTH)->setBaseValue(10); +} int Squid::getAmbientSound() { return -1; } @@ -51,6 +53,8 @@ float Squid::getSoundVolume() { return 0.4f; } int Squid::getDeathLoot() { return 0; } +bool Squid::makeStepSound() { return false; } + void Squid::dropDeathLoot(bool wasKilledByPlayer, int playerBonusLevel) { int count = random->nextInt(3 + playerBonusLevel) + 1; for (int i = 0; i < count; i++) { @@ -107,13 +111,12 @@ void Squid::aiStep() { double horizontalMovement = sqrt(xd * xd + zd * zd); - yBodyRot += - ((-(float)atan2(this->xd, this->zd) * 180 / PI) - yBodyRot) * 0.1f; + yBodyRot += ((-(float)atan2(xd, zd) * 180 / PI) - yBodyRot) * 0.1f; yRot = yBodyRot; zBodyRot = zBodyRot + (float)PI * rotateSpeed * 1.5f; - xBodyRot += ((-(float)atan2(horizontalMovement, this->yd) * 180 / PI) - - xBodyRot) * - 0.1f; + xBodyRot += + ((-(float)atan2(horizontalMovement, yd) * 180 / PI) - xBodyRot) * + 0.1f; } else { tentacleAngle = Mth::abs(Mth::sin(tentacleMovement)) * PI * 0.25f; diff --git a/Minecraft.World/Entities/Mobs/Squid.h b/Minecraft.World/Entities/Mobs/Squid.h index 64991b11a..84a078e15 100644 --- a/Minecraft.World/Entities/Mobs/Squid.h +++ b/Minecraft.World/Entities/Mobs/Squid.h @@ -26,14 +26,15 @@ private: public: Squid(Level* level); - virtual int getMaxHealth(); protected: + virtual void registerAttributes(); virtual int getAmbientSound(); virtual int getHurtSound(); virtual int getDeathSound(); virtual float getSoundVolume(); virtual int getDeathLoot(); + virtual bool makeStepSound(); virtual void dropDeathLoot(bool wasKilledByPlayer, int playerBonusLevel); public: diff --git a/Minecraft.World/Entities/Mobs/ThrownEgg.cpp b/Minecraft.World/Entities/Mobs/ThrownEgg.cpp index 8c01266df..0850e2705 100644 --- a/Minecraft.World/Entities/Mobs/ThrownEgg.cpp +++ b/Minecraft.World/Entities/Mobs/ThrownEgg.cpp @@ -15,7 +15,7 @@ void ThrownEgg::_init() { ThrownEgg::ThrownEgg(Level* level) : Throwable(level) { _init(); } -ThrownEgg::ThrownEgg(Level* level, std::shared_ptr mob) +ThrownEgg::ThrownEgg(Level* level, std::shared_ptr mob) : Throwable(level, mob) { _init(); } diff --git a/Minecraft.World/Entities/Mobs/ThrownEgg.h b/Minecraft.World/Entities/Mobs/ThrownEgg.h index bda47e94d..4215eec8f 100644 --- a/Minecraft.World/Entities/Mobs/ThrownEgg.h +++ b/Minecraft.World/Entities/Mobs/ThrownEgg.h @@ -13,7 +13,7 @@ private: public: ThrownEgg(Level* level); - ThrownEgg(Level* level, std::shared_ptr mob); + ThrownEgg(Level* level, std::shared_ptr mob); ThrownEgg(Level* level, double x, double y, double z); protected: diff --git a/Minecraft.World/Entities/Mobs/ThrownEnderPearl.cpp b/Minecraft.World/Entities/Mobs/ThrownEnderPearl.cpp index 1eb3f2370..1c5c0eb0a 100644 --- a/Minecraft.World/Entities/Mobs/ThrownEnderPearl.cpp +++ b/Minecraft.World/Entities/Mobs/ThrownEnderPearl.cpp @@ -13,7 +13,8 @@ ThrownEnderpearl::ThrownEnderpearl(Level* level) : Throwable(level) { this->defineSynchedData(); } -ThrownEnderpearl::ThrownEnderpearl(Level* level, std::shared_ptr mob) +ThrownEnderpearl::ThrownEnderpearl(Level* level, + std::shared_ptr mob) : Throwable(level, mob) { // 4J Stu - This function call had to be moved here from the Entity ctor to // ensure that the derived version of the function is called @@ -30,7 +31,7 @@ ThrownEnderpearl::ThrownEnderpearl(Level* level, double x, double y, double z) void ThrownEnderpearl::onHit(HitResult* res) { if (res->entity != NULL) { DamageSource* damageSource = - DamageSource::thrown(shared_from_this(), owner); + DamageSource::thrown(shared_from_this(), getOwner()); res->entity->hurt(damageSource, 0); delete damageSource; } @@ -45,14 +46,22 @@ void ThrownEnderpearl::onHit(HitResult* res) { // TU8: Code: Gameplay: The title crashes on Host's console when Client // Player leaves the game before the Ender Pearl thrown by him touches // the ground. If the owner has been removed, then ignore - std::shared_ptr serverPlayer = - std::dynamic_pointer_cast(owner); - if (serverPlayer != NULL && !serverPlayer->removed) { - if (!serverPlayer->connection->done && - serverPlayer->level == this->level) { - owner->teleportTo(x, y, z); - owner->fallDistance = 0; - owner->hurt(DamageSource::fall, 5); + + // 4J-JEV: Cheap type check first. + if ((getOwner() != NULL) && + getOwner()->instanceof(eTYPE_SERVERPLAYER)) { + std::shared_ptr serverPlayer = + std::dynamic_pointer_cast(getOwner()); + if (!serverPlayer->removed) { + if (!serverPlayer->connection->done && + serverPlayer->level == this->level) { + if (getOwner()->isRiding()) { + getOwner()->ride(nullptr); + } + getOwner()->teleportTo(x, y, z); + getOwner()->fallDistance = 0; + getOwner()->hurt(DamageSource::fall, 5); + } } } remove(); diff --git a/Minecraft.World/Entities/Mobs/ThrownEnderPearl.h b/Minecraft.World/Entities/Mobs/ThrownEnderPearl.h index 62d4f4598..29504b949 100644 --- a/Minecraft.World/Entities/Mobs/ThrownEnderPearl.h +++ b/Minecraft.World/Entities/Mobs/ThrownEnderPearl.h @@ -10,7 +10,7 @@ public: static Entity* create(Level* level) { return new ThrownEnderpearl(level); } ThrownEnderpearl(Level* level); - ThrownEnderpearl(Level* level, std::shared_ptr mob); + ThrownEnderpearl(Level* level, std::shared_ptr mob); ThrownEnderpearl(Level* level, double x, double y, double z); protected: diff --git a/Minecraft.World/Entities/Mobs/ThrownExpBottle.cpp b/Minecraft.World/Entities/Mobs/ThrownExpBottle.cpp index a2e5acfa0..df143c4e0 100644 --- a/Minecraft.World/Entities/Mobs/ThrownExpBottle.cpp +++ b/Minecraft.World/Entities/Mobs/ThrownExpBottle.cpp @@ -7,7 +7,8 @@ ThrownExpBottle::ThrownExpBottle(Level* level) : Throwable(level) {} -ThrownExpBottle::ThrownExpBottle(Level* level, std::shared_ptr mob) +ThrownExpBottle::ThrownExpBottle(Level* level, + std::shared_ptr mob) : Throwable(level, mob) {} ThrownExpBottle::ThrownExpBottle(Level* level, double x, double y, double z) diff --git a/Minecraft.World/Entities/Mobs/ThrownExpBottle.h b/Minecraft.World/Entities/Mobs/ThrownExpBottle.h index 262d3dd83..38740e12d 100644 --- a/Minecraft.World/Entities/Mobs/ThrownExpBottle.h +++ b/Minecraft.World/Entities/Mobs/ThrownExpBottle.h @@ -11,7 +11,7 @@ public: public: ThrownExpBottle(Level* level); - ThrownExpBottle(Level* level, std::shared_ptr mob); + ThrownExpBottle(Level* level, std::shared_ptr mob); ThrownExpBottle(Level* level, double x, double y, double z); protected: diff --git a/Minecraft.World/Entities/Mobs/ThrownPotion.cpp b/Minecraft.World/Entities/Mobs/ThrownPotion.cpp index 57d625ccb..3ed57fa98 100644 --- a/Minecraft.World/Entities/Mobs/ThrownPotion.cpp +++ b/Minecraft.World/Entities/Mobs/ThrownPotion.cpp @@ -17,24 +17,43 @@ void ThrownPotion::_init() { // ensure that the derived version of the function is called this->defineSynchedData(); - potionValue = 0; + potionItem = nullptr; } ThrownPotion::ThrownPotion(Level* level) : Throwable(level) { _init(); } -ThrownPotion::ThrownPotion(Level* level, std::shared_ptr mob, +ThrownPotion::ThrownPotion(Level* level, std::shared_ptr mob, int potionValue) : Throwable(level, mob) { _init(); - this->potionValue = potionValue; + potionItem = std::shared_ptr( + new ItemInstance(Item::potion, 1, potionValue)); +} + +ThrownPotion::ThrownPotion(Level* level, std::shared_ptr mob, + std::shared_ptr potion) + : Throwable(level, mob) { + _init(); + + potionItem = potion; } ThrownPotion::ThrownPotion(Level* level, double x, double y, double z, int potionValue) : Throwable(level, x, y, z) { _init(); - this->potionValue = potionValue; + + potionItem = std::shared_ptr( + new ItemInstance(Item::potion, 1, potionValue)); +} + +ThrownPotion::ThrownPotion(Level* level, double x, double y, double z, + std::shared_ptr potion) + : Throwable(level, x, y, z) { + _init(); + + potionItem = potion; } float ThrownPotion::getGravity() { return 0.05f; } @@ -44,28 +63,36 @@ float ThrownPotion::getThrowPower() { return 0.5f; } float ThrownPotion::getThrowUpAngleOffset() { return -20; } void ThrownPotion::setPotionValue(int potionValue) { - this->potionValue = potionValue; + if (potionItem == NULL) + potionItem = + std::shared_ptr(new ItemInstance(Item::potion, 1, 0)); + potionItem->setAuxValue(potionValue); } -int ThrownPotion::getPotionValue() { return potionValue; } +int ThrownPotion::getPotionValue() { + if (potionItem == NULL) + potionItem = + std::shared_ptr(new ItemInstance(Item::potion, 1, 0)); + return potionItem->getAuxValue(); +} void ThrownPotion::onHit(HitResult* res) { if (!level->isClientSide) { std::vector* mobEffects = - Item::potion->getMobEffects(potionValue); + Item::potion->getMobEffects(potionItem); if (mobEffects != NULL && !mobEffects->empty()) { AABB* aoe = bb->grow(SPLASH_RANGE, SPLASH_RANGE / 2, SPLASH_RANGE); std::vector >* entitiesOfClass = - level->getEntitiesOfClass(typeid(Mob), aoe); + level->getEntitiesOfClass(typeid(LivingEntity), aoe); if (entitiesOfClass != NULL && !entitiesOfClass->empty()) { // for (Entity e : entitiesOfClass) for (AUTO_VAR(it, entitiesOfClass->begin()); it != entitiesOfClass->end(); ++it) { - // std::shared_ptr e = *it; - std::shared_ptr e = - std::dynamic_pointer_cast(*it); + // shared_ptr e = *it; + std::shared_ptr e = + std::dynamic_pointer_cast(*it); double dist = distanceToSqr(e); if (dist < SPLASH_RANGE_SQ) { double scale = 1.0 - (sqrt(dist) / SPLASH_RANGE); @@ -80,7 +107,7 @@ void ThrownPotion::onHit(HitResult* res) { int id = effect->getId(); if (MobEffect::effects[id]->isInstantenous()) { MobEffect::effects[id]->applyInstantenousEffect( - this->owner, e, effect->getAmplifier(), + getOwner(), e, effect->getAmplifier(), scale); } else { int duration = @@ -101,7 +128,7 @@ void ThrownPotion::onHit(HitResult* res) { } level->levelEvent(LevelEvent::PARTICLES_POTION_SPLASH, (int)Math::round(x), (int)Math::round(y), - (int)Math::round(z), potionValue); + (int)Math::round(z), getPotionValue()); remove(); } @@ -110,11 +137,18 @@ void ThrownPotion::onHit(HitResult* res) { void ThrownPotion::readAdditionalSaveData(CompoundTag* tag) { Throwable::readAdditionalSaveData(tag); - potionValue = tag->getInt(L"potionValue"); + if (tag->contains(L"Potion")) { + potionItem = ItemInstance::fromTag(tag->getCompound(L"Potion")); + } else { + setPotionValue(tag->getInt(L"potionValue")); + } + + if (potionItem == NULL) remove(); } void ThrownPotion::addAdditonalSaveData(CompoundTag* tag) { Throwable::addAdditonalSaveData(tag); - tag->putInt(L"potionValue", potionValue); + if (potionItem != NULL) + tag->putCompound(L"Potion", potionItem->save(new CompoundTag())); } \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/ThrownPotion.h b/Minecraft.World/Entities/Mobs/ThrownPotion.h index 71789c811..2890f1c19 100644 --- a/Minecraft.World/Entities/Mobs/ThrownPotion.h +++ b/Minecraft.World/Entities/Mobs/ThrownPotion.h @@ -15,14 +15,19 @@ public: private: static const double SPLASH_RANGE_SQ; - int potionValue; + std::shared_ptr potionItem; void _init(); public: ThrownPotion(Level* level); - ThrownPotion(Level* level, std::shared_ptr mob, int potionValue); + ThrownPotion(Level* level, std::shared_ptr mob, + int potionValue); + ThrownPotion(Level* level, std::shared_ptr mob, + std::shared_ptr potion); ThrownPotion(Level* level, double x, double y, double z, int potionValue); + ThrownPotion(Level* level, double x, double y, double z, + std::shared_ptr potion); protected: virtual float getGravity(); diff --git a/Minecraft.World/Entities/Mobs/Villager.cpp b/Minecraft.World/Entities/Mobs/Villager.cpp index 5a3cb4a9c..71144d3f0 100644 --- a/Minecraft.World/Entities/Mobs/Villager.cpp +++ b/Minecraft.World/Entities/Mobs/Villager.cpp @@ -1,5 +1,6 @@ #include "../../Platform/stdafx.h" #include "../../Headers/com.mojang.nbt.h" +#include "../../Headers/net.minecraft.world.entity.ai.attributes.h" #include "../../Headers/net.minecraft.world.entity.ai.goal.h" #include "../../Headers/net.minecraft.world.entity.ai.navigation.h" #include "../../Headers/net.minecraft.world.entity.ai.village.h" @@ -15,7 +16,6 @@ #include "../../Headers/net.minecraft.world.level.h" #include "../../../Minecraft.Client/Textures/Textures.h" #include "Villager.h" -#include std::unordered_map > Villager::MIN_MAX_VALUES; std::unordered_map > Villager::MIN_MAX_PRICES; @@ -24,16 +24,12 @@ void Villager::_init(int profession) { // 4J Stu - This function call had to be moved here from the Entity ctor to // ensure that the derived version of the function is called this->defineSynchedData(); - - // 4J Stu - This function call had to be moved here from the Entity ctor to - // ensure that the derived version of the function is called - health = getMaxHealth(); + registerAttributes(); + setHealth(getMaxHealth()); setProfession(profession); setSize(.6f, 1.8f); - runSpeed = 0.5f; - villageUpdateInterval = 0; inLove = false; chasing = false; @@ -53,19 +49,19 @@ void Villager::_init(int profession) { goalSelector.addGoal(0, new FloatGoal(this)); goalSelector.addGoal( - 1, new AvoidPlayerGoal(this, typeid(Zombie), 8, 0.3f, 0.35f)); + 1, new AvoidPlayerGoal(this, typeid(Zombie), 8, 0.6, 0.6)); goalSelector.addGoal(1, new TradeWithPlayerGoal(this)); goalSelector.addGoal(1, new LookAtTradingPlayerGoal(this)); goalSelector.addGoal(2, new MoveIndoorsGoal(this)); goalSelector.addGoal(3, new RestrictOpenDoorGoal(this)); goalSelector.addGoal(4, new OpenDoorGoal(this, true)); - goalSelector.addGoal(5, new MoveTowardsRestrictionGoal(this, 0.3f)); + goalSelector.addGoal(5, new MoveTowardsRestrictionGoal(this, 0.6)); goalSelector.addGoal(6, new MakeLoveGoal(this)); goalSelector.addGoal(7, new TakeFlowerGoal(this)); - goalSelector.addGoal(8, new PlayGoal(this, 0.32f)); + goalSelector.addGoal(8, new PlayGoal(this, 0.32)); goalSelector.addGoal(9, new InteractGoal(this, typeid(Player), 3, 1.f)); goalSelector.addGoal(9, new InteractGoal(this, typeid(Villager), 5, 0.02f)); - goalSelector.addGoal(9, new RandomStrollGoal(this, 0.3f)); + goalSelector.addGoal(9, new RandomStrollGoal(this, 0.6)); goalSelector.addGoal(10, new LookAtPlayerGoal(this, typeid(Mob), 8)); } @@ -77,6 +73,12 @@ Villager::Villager(Level* level, int profession) : AgableMob(level) { Villager::~Villager() { delete offers; } +void Villager::registerAttributes() { + AgableMob::registerAttributes(); + + getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED)->setBaseValue(0.5f); +} + bool Villager::useNewAi() { return true; } void Villager::serverAiMobStep() { @@ -135,21 +137,24 @@ void Villager::serverAiMobStep() { AgableMob::serverAiMobStep(); } -bool Villager::interact(std::shared_ptr player) { +bool Villager::mobInteract(std::shared_ptr player) { // [EB]: Truly dislike this code but I don't see another easy way std::shared_ptr item = player->inventory->getSelected(); - bool holdingSpawnEgg = item != NULL && item->id == Item::monsterPlacer_Id; + bool holdingSpawnEgg = item != NULL && item->id == Item::spawnEgg_Id; if (!holdingSpawnEgg && isAlive() && !isTrading() && !isBaby()) { if (!level->isClientSide) { // note: stop() logic is controlled by trading ai goal setTradingPlayer(player); + + // 4J-JEV: Villagers in PC game don't display professions. player->openTrading( - std::dynamic_pointer_cast(shared_from_this())); + std::dynamic_pointer_cast(shared_from_this()), + getDisplayName()); } return true; } - return AgableMob::interact(player); + return AgableMob::mobInteract(player); } void Villager::defineSynchedData() { @@ -157,8 +162,6 @@ void Villager::defineSynchedData() { entityData->define(DATA_PROFESSION_ID, 0); } -int Villager::getMaxHealth() { return 20; } - void Villager::addAdditonalSaveData(CompoundTag* tag) { AgableMob::addAdditonalSaveData(tag); tag->putInt(L"Profession", getProfession()); @@ -179,36 +182,6 @@ void Villager::readAdditionalSaveData(CompoundTag* tag) { } } -int Villager::getTexture() { - // 4J Made switch - switch (getProfession()) { - case PROFESSION_FARMER: - return TN_MOB_VILLAGER_FARMER; // 4J was - // "/mob/villager/farmer.png"; - break; - case PROFESSION_LIBRARIAN: - return TN_MOB_VILLAGER_LIBRARIAN; // 4J was - // "/mob/villager/librarian.png"; - break; - case PROFESSION_PRIEST: - return TN_MOB_VILLAGER_PRIEST; // 4J was - // "/mob/villager/priest.png"; - break; - case PROFESSION_SMITH: - return TN_MOB_VILLAGER_SMITH; // 4J was "/mob/villager/smith.png"; - break; - case PROFESSION_BUTCHER: - return TN_MOB_VILLAGER_BUTCHER; // 4J was - // "/mob/villager/butcher.png"; - break; - // default: - // return TN_MOB_VILLAGER_VILLAGER; // 4J was - //"/mob/villager/villager.png"; break; - } - - return AgableMob::getTexture(); -} - bool Villager::removeWhenFarAway() { return false; } int Villager::getAmbientSound() { @@ -238,19 +211,19 @@ void Villager::setChasing(bool chasing) { this->chasing = chasing; } bool Villager::isChasing() { return chasing; } -void Villager::setLastHurtByMob(std::shared_ptr mob) { +void Villager::setLastHurtByMob(std::shared_ptr mob) { AgableMob::setLastHurtByMob(mob); std::shared_ptr _village = village.lock(); if (_village != NULL && mob != NULL) { _village->addAggressor(mob); - std::shared_ptr player = std::dynamic_pointer_cast(mob); - if (player) { + if (mob->instanceof(eTYPE_PLAYER)) { int amount = -1; if (isBaby()) { amount = -3; } - _village->modifyStanding(player->getName(), amount); + _village->modifyStanding( + std::dynamic_pointer_cast(mob)->getName(), amount); if (isAlive()) { level->broadcastEntityEvent(shared_from_this(), EntityEvent::VILLAGER_ANGRY); @@ -264,11 +237,11 @@ void Villager::die(DamageSource* source) { if (_village != NULL) { std::shared_ptr sourceEntity = source->getEntity(); if (sourceEntity != NULL) { - if ((sourceEntity->GetType() & eTYPE_PLAYER) == eTYPE_PLAYER) { - std::shared_ptr player = - std::dynamic_pointer_cast(sourceEntity); - _village->modifyStanding(player->getName(), -2); - } else if ((sourceEntity->GetType() & eTYPE_ENEMY) == eTYPE_ENEMY) { + if (sourceEntity->instanceof(eTYPE_PLAYER)) { + _village->modifyStanding( + std::dynamic_pointer_cast(sourceEntity)->getName(), + -2); + } else if (sourceEntity->instanceof(eTYPE_ENEMY)) { _village->resetNoBreedTimer(); } } else if (sourceEntity == NULL) { @@ -353,7 +326,7 @@ void Villager::addOffers(int addCount) { case PROFESSION_FARMER: addItemForTradeIn(newOffers, Item::wheat_Id, random, getRecipeChance(.9f)); - addItemForTradeIn(newOffers, Tile::cloth_Id, random, + addItemForTradeIn(newOffers, Tile::wool_Id, random, getRecipeChance(.5f)); addItemForTradeIn(newOffers, Item::chicken_raw_Id, random, getRecipeChance(.5f)); @@ -375,14 +348,14 @@ void Villager::addOffers(int addCount) { getRecipeChance(.3f)); addItemForPurchase(newOffers, Item::arrow_Id, random, getRecipeChance(.5f)); - if (random->nextFloat() < .5f) { + if (random->nextFloat() < getRecipeChance(.5f)) { newOffers->push_back(new MerchantRecipe( std::shared_ptr( new ItemInstance(Tile::gravel, 10)), std::shared_ptr( new ItemInstance(Item::emerald)), std::shared_ptr(new ItemInstance( - Item::flint_Id, 2 + random->nextInt(2), 0)))); + Item::flint_Id, 4 + random->nextInt(2), 0)))); } break; case PROFESSION_BUTCHER: @@ -394,13 +367,13 @@ void Villager::addOffers(int addCount) { getRecipeChance(.5f)); addItemForPurchase(newOffers, Item::saddle_Id, random, getRecipeChance(.1f)); - addItemForPurchase(newOffers, Item::chestplate_cloth_Id, random, + addItemForPurchase(newOffers, Item::chestplate_leather_Id, random, getRecipeChance(.3f)); - addItemForPurchase(newOffers, Item::boots_cloth_Id, random, + addItemForPurchase(newOffers, Item::boots_leather_Id, random, getRecipeChance(.3f)); - addItemForPurchase(newOffers, Item::helmet_cloth_Id, random, + addItemForPurchase(newOffers, Item::helmet_leather_Id, random, getRecipeChance(.3f)); - addItemForPurchase(newOffers, Item::leggings_cloth_Id, random, + addItemForPurchase(newOffers, Item::leggings_leather_Id, random, getRecipeChance(.3f)); addItemForPurchase(newOffers, Item::porkChop_cooked_Id, random, getRecipeChance(.3f)); @@ -503,7 +476,7 @@ void Villager::addOffers(int addCount) { getRecipeChance(.2f)); addItemForPurchase(newOffers, Item::redStone_Id, random, getRecipeChance(.4f)); - addItemForPurchase(newOffers, Tile::lightGem_Id, random, + addItemForPurchase(newOffers, Tile::glowstone_Id, random, getRecipeChance(.3f)); { int enchantItems[] = { @@ -535,8 +508,7 @@ void Villager::addOffers(int addCount) { } // shuffle the list to make it more interesting - static thread_local std::mt19937 g(std::random_device{}()); - std::shuffle(newOffers->begin(), newOffers->end(), g); + std::random_shuffle(newOffers->begin(), newOffers->end()); if (offers == NULL) { offers = new MerchantRecipeList(); @@ -559,7 +531,7 @@ void Villager::staticCtor() { MIN_MAX_VALUES[Item::diamond_Id] = std::pair(4, 6); MIN_MAX_VALUES[Item::paper_Id] = std::pair(24, 36); MIN_MAX_VALUES[Item::book_Id] = std::pair(11, 13); - // MIN_MAX_VALUES.insert(Item::writtenBook_Id, std::pair(1, 1)); + // MIN_MAX_VALUES.insert(Item::writtenBook_Id, pair(1, 1)); MIN_MAX_VALUES[Item::enderPearl_Id] = std::pair(3, 4); MIN_MAX_VALUES[Item::eyeOfEnder_Id] = std::pair(2, 3); MIN_MAX_VALUES[Item::porkChop_raw_Id] = std::pair(14, 18); @@ -570,7 +542,7 @@ void Villager::staticCtor() { MIN_MAX_VALUES[Item::seeds_melon_Id] = std::pair(30, 38); MIN_MAX_VALUES[Item::seeds_pumpkin_Id] = std::pair(30, 38); MIN_MAX_VALUES[Item::wheat_Id] = std::pair(18, 22); - MIN_MAX_VALUES[Tile::cloth_Id] = std::pair(14, 22); + MIN_MAX_VALUES[Tile::wool_Id] = std::pair(14, 22); MIN_MAX_VALUES[Item::rotten_flesh_Id] = std::pair(36, 64); MIN_MAX_PRICES[Item::flintAndSteel_Id] = std::pair(3, 4); @@ -603,16 +575,16 @@ void Villager::staticCtor() { MIN_MAX_PRICES[Item::cookie_Id] = std::pair(-10, -7); MIN_MAX_PRICES[Tile::glass_Id] = std::pair(-5, -3); MIN_MAX_PRICES[Tile::bookshelf_Id] = std::pair(3, 4); - MIN_MAX_PRICES[Item::chestplate_cloth_Id] = std::pair(4, 5); - MIN_MAX_PRICES[Item::boots_cloth_Id] = std::pair(2, 4); - MIN_MAX_PRICES[Item::helmet_cloth_Id] = std::pair(2, 4); - MIN_MAX_PRICES[Item::leggings_cloth_Id] = std::pair(2, 4); + MIN_MAX_PRICES[Item::chestplate_leather_Id] = std::pair(4, 5); + MIN_MAX_PRICES[Item::boots_leather_Id] = std::pair(2, 4); + MIN_MAX_PRICES[Item::helmet_leather_Id] = std::pair(2, 4); + MIN_MAX_PRICES[Item::leggings_leather_Id] = std::pair(2, 4); MIN_MAX_PRICES[Item::saddle_Id] = std::pair(6, 8); MIN_MAX_PRICES[Item::expBottle_Id] = std::pair(-4, -1); MIN_MAX_PRICES[Item::redStone_Id] = std::pair(-4, -1); MIN_MAX_PRICES[Item::compass_Id] = std::pair(10, 12); MIN_MAX_PRICES[Item::clock_Id] = std::pair(10, 12); - MIN_MAX_PRICES[Tile::lightGem_Id] = std::pair(-3, -1); + MIN_MAX_PRICES[Tile::glowstone_Id] = std::pair(-3, -1); MIN_MAX_PRICES[Item::porkChop_cooked_Id] = std::pair(-7, -5); MIN_MAX_PRICES[Item::beef_cooked_Id] = std::pair(-7, -5); MIN_MAX_PRICES[Item::chicken_cooked_Id] = std::pair(-8, -6); @@ -720,8 +692,14 @@ void Villager::addParticlesAroundSelf(ePARTICLE_TYPE particle) { } } -void Villager::finalizeMobSpawn() { - setProfession(level->random->nextInt(Villager::PROFESSION_MAX)); +MobGroupData* Villager::finalizeMobSpawn( + MobGroupData* groupData, int extraData /*= 0*/) // 4J Added extraData param +{ + groupData = AgableMob::finalizeMobSpawn(groupData); + + setProfession(level->random->nextInt(PROFESSION_MAX)); + + return groupData; } void Villager::setRewardPlayersInVillage() { @@ -734,14 +712,18 @@ std::shared_ptr Villager::getBreedOffspring( if (level->canCreateMore(GetType(), Level::eSpawnType_Breed)) { std::shared_ptr villager = std::shared_ptr(new Villager(level)); - villager->finalizeMobSpawn(); + villager->finalizeMobSpawn(NULL); return villager; } else { return nullptr; } } -int Villager::getDisplayName() { +bool Villager::canBeLeashed() { return false; } + +std::wstring Villager::getDisplayName() { + if (hasCustomName()) return getCustomName(); + int name = IDS_VILLAGER; switch (getProfession()) { case PROFESSION_FARMER: @@ -760,5 +742,5 @@ int Villager::getDisplayName() { name = IDS_VILLAGER_BUTCHER; break; }; - return name; + return app.GetString(name); } diff --git a/Minecraft.World/Entities/Mobs/Villager.h b/Minecraft.World/Entities/Mobs/Villager.h index 037e74c76..e4be4d88b 100644 --- a/Minecraft.World/Entities/Mobs/Villager.h +++ b/Minecraft.World/Entities/Mobs/Villager.h @@ -51,24 +51,25 @@ public: Villager(Level* level, int profession); ~Villager(); +protected: + virtual void registerAttributes(); + +public: virtual bool useNewAi(); protected: virtual void serverAiMobStep(); public: - virtual bool interact(std::shared_ptr player); + virtual bool mobInteract(std::shared_ptr player); protected: virtual void defineSynchedData(); public: - virtual int getMaxHealth(); virtual void addAdditonalSaveData(CompoundTag* tag); virtual void readAdditionalSaveData(CompoundTag* tag); - virtual int getTexture(); - protected: virtual bool removeWhenFarAway(); virtual int getAmbientSound(); @@ -82,7 +83,7 @@ public: void setInLove(bool inLove); void setChasing(bool chasing); bool isChasing(); - void setLastHurtByMob(std::shared_ptr mob); + void setLastHurtByMob(std::shared_ptr mob); void die(DamageSource* source); void handleEntityEvent(uint8_t id); @@ -143,10 +144,12 @@ private: static int getPurchaseCost(int itemId, Random* random); public: - void finalizeMobSpawn(); - void setRewardPlayersInVillage(); - std::shared_ptr getBreedOffspring( + virtual MobGroupData* finalizeMobSpawn( + MobGroupData* groupData, + int extraData = 0); // 4J Added extraData param + virtual void setRewardPlayersInVillage(); + virtual std::shared_ptr getBreedOffspring( std::shared_ptr target); - - virtual int getDisplayName(); + virtual bool canBeLeashed(); + virtual std::wstring getDisplayName(); }; \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/VillagerGolem.cpp b/Minecraft.World/Entities/Mobs/VillagerGolem.cpp index e3c1f39b1..6f1880259 100644 --- a/Minecraft.World/Entities/Mobs/VillagerGolem.cpp +++ b/Minecraft.World/Entities/Mobs/VillagerGolem.cpp @@ -1,4 +1,5 @@ #include "../../Platform/stdafx.h" +#include "../../Headers/net.minecraft.world.entity.ai.attributes.h" #include "../../Headers/net.minecraft.world.entity.ai.control.h" #include "../../Headers/net.minecraft.world.entity.ai.goal.h" #include "../../Headers/net.minecraft.world.entity.ai.goal.target.h" @@ -22,36 +23,32 @@ VillagerGolem::VillagerGolem(Level* level) : Golem(level) { // 4J Stu - This function call had to be moved here from the Entity ctor to // ensure that the derived version of the function is called this->defineSynchedData(); - - // 4J Stu - This function call had to be moved here from the Entity ctor to - // ensure that the derived version of the function is called - health = getMaxHealth(); + registerAttributes(); + setHealth(getMaxHealth()); villageUpdateInterval = 0; village = std::weak_ptr(); attackAnimationTick = 0; offerFlowerTick = 0; - this->textureIdx = TN_MOB_VILLAGER_GOLEM; // "/mob/villager_golem.png"; - this->setSize(1.4f, 2.9f); + setSize(1.4f, 2.9f); getNavigation()->setAvoidWater(true); - // 4J-JEV: These speed values are as they appear before 1.6.4 (1.5), - // as the movement speed system changes then. (Mob attributes added) - goalSelector.addGoal(1, new MeleeAttackGoal(this, 0.25f, true)); - goalSelector.addGoal(2, new MoveTowardsTargetGoal(this, 0.22f, 32)); - goalSelector.addGoal(3, new MoveThroughVillageGoal(this, 0.16f, true)); - goalSelector.addGoal(4, new MoveTowardsRestrictionGoal(this, 0.16f)); + goalSelector.addGoal(1, new MeleeAttackGoal(this, 1.0, true)); + goalSelector.addGoal(2, new MoveTowardsTargetGoal(this, 0.9, 32)); + goalSelector.addGoal(3, new MoveThroughVillageGoal(this, 0.6, true)); + goalSelector.addGoal(4, new MoveTowardsRestrictionGoal(this, 1.0)); goalSelector.addGoal(5, new OfferFlowerGoal(this)); - goalSelector.addGoal(6, new RandomStrollGoal(this, 0.16f)); + goalSelector.addGoal(6, new RandomStrollGoal(this, 0.6)); goalSelector.addGoal(7, new LookAtPlayerGoal(this, typeid(Player), 6)); goalSelector.addGoal(8, new RandomLookAroundGoal(this)); targetSelector.addGoal(1, new DefendVillageTargetGoal(this)); targetSelector.addGoal(2, new HurtByTargetGoal(this, false)); - targetSelector.addGoal(3, new NearestAttackableTargetGoal( - this, typeid(Monster), 16, 0, false, true)); + targetSelector.addGoal( + 3, new NearestAttackableTargetGoal(this, typeid(Mob), 0, false, true, + Enemy::ENEMY_SELECTOR)); } void VillagerGolem::defineSynchedData() { @@ -79,13 +76,27 @@ void VillagerGolem::serverAiMobStep() { Golem::serverAiMobStep(); } -int VillagerGolem::getMaxHealth() { return 100; } +void VillagerGolem::registerAttributes() { + Golem::registerAttributes(); + + getAttribute(SharedMonsterAttributes::MAX_HEALTH)->setBaseValue(100); + getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED)->setBaseValue(0.25f); +} int VillagerGolem::decreaseAirSupply(int currentSupply) { // infinite air supply return currentSupply; } +void VillagerGolem::doPush(std::shared_ptr e) { + if (e->instanceof(eTYPE_ENEMY)) { + if (getRandom()->nextInt(20) == 0) { + setTarget(std::dynamic_pointer_cast(e)); + } + } + Golem::doPush(e); +} + void VillagerGolem::aiStep() { Golem::aiStep(); @@ -95,7 +106,7 @@ void VillagerGolem::aiStep() { if (xd * xd + zd * zd > MoveControl::MIN_SPEED_SQR && random->nextInt(5) == 0) { int xt = Mth::floor(x); - int yt = Mth::floor(y - 0.2f - this->heightOffset); + int yt = Mth::floor(y - 0.2f - heightOffset); int zt = Mth::floor(z); int t = level->getTile(xt, yt, zt); int d = level->getData(xt, yt, zt); @@ -135,15 +146,14 @@ bool VillagerGolem::doHurtTarget(std::shared_ptr target) { std::dynamic_pointer_cast(shared_from_this())), 7 + random->nextInt(15)); if (hurt) target->yd += 0.4f; - level->playSound(shared_from_this(), eSoundType_MOB_IRONGOLEM_THROW, 1, 1); + playSound(eSoundType_MOB_IRONGOLEM_THROW, 1, 1); return hurt; } void VillagerGolem::handleEntityEvent(uint8_t id) { if (id == EntityEvent::START_ATTACKING) { attackAnimationTick = 10; - level->playSound(shared_from_this(), eSoundType_MOB_IRONGOLEM_THROW, 1, - 1); + playSound(eSoundType_MOB_IRONGOLEM_THROW, 1, 1); } else if (id == EntityEvent::OFFER_FLOWER) { offerFlowerTick = OfferFlowerGoal::OFFER_TICKS; } else @@ -166,7 +176,7 @@ int VillagerGolem::getHurtSound() { return eSoundType_MOB_IRONGOLEM_HIT; } int VillagerGolem::getDeathSound() { return eSoundType_MOB_IRONGOLEM_DEATH; } void VillagerGolem::playStepSound(int xt, int yt, int zt, int t) { - level->playSound(shared_from_this(), eSoundType_MOB_IRONGOLEM_WALK, 1, 1); + playSound(eSoundType_MOB_IRONGOLEM_WALK, 1, 1); } void VillagerGolem::dropDeathLoot(bool wasKilledByPlayer, @@ -202,4 +212,18 @@ void VillagerGolem::die(DamageSource* source) { village.lock()->modifyStanding(lastHurtByPlayer->getName(), -5); } Golem::die(source); +} + +bool VillagerGolem::hurt(DamageSource* source, float dmg) { + // 4J: Protect owned golem from untrusted players + if (isPlayerCreated()) { + std::shared_ptr entity = source->getDirectEntity(); + if (entity != NULL && entity->instanceof(eTYPE_PLAYER)) { + std::shared_ptr player = + std::dynamic_pointer_cast(entity); + if (!player->isAllowedToAttackPlayers()) return false; + } + } + + return Golem::hurt(source, dmg); } \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/VillagerGolem.h b/Minecraft.World/Entities/Mobs/VillagerGolem.h index b3b6ff370..d9477ba84 100644 --- a/Minecraft.World/Entities/Mobs/VillagerGolem.h +++ b/Minecraft.World/Entities/Mobs/VillagerGolem.h @@ -30,12 +30,9 @@ public: protected: virtual void serverAiMobStep(); - -public: - virtual int getMaxHealth(); - -protected: + virtual void registerAttributes(); virtual int decreaseAirSupply(int currentSupply); + virtual void doPush(std::shared_ptr e); public: virtual void aiStep(); @@ -60,4 +57,5 @@ public: virtual bool isPlayerCreated(); virtual void setPlayerCreated(bool value); virtual void die(DamageSource* source); + virtual bool hurt(DamageSource* source, float dmg); }; \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/Witch.cpp b/Minecraft.World/Entities/Mobs/Witch.cpp new file mode 100644 index 000000000..4099d1f95 --- /dev/null +++ b/Minecraft.World/Entities/Mobs/Witch.cpp @@ -0,0 +1,215 @@ +#include "../../Platform/stdafx.h" +#include "../../Headers/net.minecraft.world.effect.h" +#include "../../Headers/net.minecraft.world.damagesource.h" +#include "../../Headers/net.minecraft.world.entity.h" +#include "../../Headers/net.minecraft.world.entity.ai.attributes.h" +#include "../../Headers/net.minecraft.world.entity.ai.goal.h" +#include "../../Headers/net.minecraft.world.entity.ai.goal.target.h" +#include "../../Headers/net.minecraft.world.entity.monster.h" +#include "../../Headers/net.minecraft.world.entity.projectile.h" +#include "../../Headers/net.minecraft.world.item.h" +#include "../../Headers/net.minecraft.world.item.alchemy.h" +#include "../../Headers/net.minecraft.world.level.h" +#include "../../Headers/net.minecraft.world.phys.h" +#include "Witch.h" + +AttributeModifier* Witch::SPEED_MODIFIER_DRINKING = + (new AttributeModifier(eModifierId_MOB_WITCH_DRINKSPEED, -0.25f, + AttributeModifier::OPERATION_ADDITION)) + ->setSerialize(false); + +const int Witch::DEATH_LOOT[Witch::DEATH_LOOT_COUNT] = { + Item::yellowDust_Id, Item::sugar_Id, Item::redStone_Id, + Item::spiderEye_Id, Item::glassBottle_Id, Item::gunpowder_Id, + Item::stick_Id, Item::stick_Id, +}; + +Witch::Witch(Level* level) : Monster(level) { + // 4J Stu - This function call had to be moved here from the Entity ctor to + // ensure that the derived version of the function is called + defineSynchedData(); + registerAttributes(); + setHealth(getMaxHealth()); + + usingTime = 0; + + goalSelector.addGoal(1, new FloatGoal(this)); + goalSelector.addGoal( + 2, new RangedAttackGoal(this, this, 1.0, + SharedConstants::TICKS_PER_SECOND * 3, 10)); + goalSelector.addGoal(2, new RandomStrollGoal(this, 1.0)); + goalSelector.addGoal(3, new LookAtPlayerGoal(this, typeid(Player), 8)); + goalSelector.addGoal(3, new RandomLookAroundGoal(this)); + + targetSelector.addGoal(1, new HurtByTargetGoal(this, false)); + targetSelector.addGoal( + 2, new NearestAttackableTargetGoal(this, typeid(Player), 0, true)); +} + +void Witch::defineSynchedData() { + Monster::defineSynchedData(); + + getEntityData()->define(DATA_USING_ITEM, (uint8_t)0); +} + +int Witch::getAmbientSound() { + return eSoundType_MOB_WITCH_IDLE; //"mob.witch.idle"; +} + +int Witch::getHurtSound() { + return eSoundType_MOB_WITCH_HURT; //"mob.witch.hurt"; +} + +int Witch::getDeathSound() { + return eSoundType_MOB_WITCH_DEATH; //"mob.witch.death"; +} + +void Witch::setUsingItem(bool isUsing) { + getEntityData()->set(DATA_USING_ITEM, isUsing ? (uint8_t)1 : (uint8_t)0); +} + +bool Witch::isUsingItem() { + return getEntityData()->getByte(DATA_USING_ITEM) == 1; +} + +void Witch::registerAttributes() { + Monster::registerAttributes(); + + getAttribute(SharedMonsterAttributes::MAX_HEALTH)->setBaseValue(26); + getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED)->setBaseValue(0.25f); +} + +bool Witch::useNewAi() { return true; } + +void Witch::aiStep() { + if (!level->isClientSide) { + if (isUsingItem()) { + if (usingTime-- <= 0) { + setUsingItem(false); + std::shared_ptr item = getCarriedItem(); + setEquippedSlot(SLOT_WEAPON, nullptr); + + if (item != NULL && item->id == Item::potion_Id) { + std::vector* effects = + Item::potion->getMobEffects(item); + if (effects != NULL) { + for (AUTO_VAR(it, effects->begin()); + it != effects->end(); ++it) { + addEffect(new MobEffectInstance(*it)); + } + } + delete effects; + } + + getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED) + ->removeModifier(SPEED_MODIFIER_DRINKING); + } + } else { + int potion = -1; + + if (random->nextFloat() < 0.15f && isOnFire() && + !hasEffect(MobEffect::fireResistance)) { + potion = PotionBrewing::POTION_ID_FIRE_RESISTANCE; + } else if (random->nextFloat() < 0.05f && + getHealth() < getMaxHealth()) { + potion = PotionBrewing::POTION_ID_HEAL; + } else if (random->nextFloat() < 0.25f && getTarget() != NULL && + !hasEffect(MobEffect::movementSpeed) && + getTarget()->distanceToSqr(shared_from_this()) > + 11 * 11) { + potion = PotionBrewing::POTION_ID_SWIFTNESS; + } else if (random->nextFloat() < 0.25f && getTarget() != NULL && + !hasEffect(MobEffect::movementSpeed) && + getTarget()->distanceToSqr(shared_from_this()) > + 11 * 11) { + potion = PotionBrewing::POTION_ID_SWIFTNESS; + } + + if (potion > -1) { + setEquippedSlot(SLOT_WEAPON, + std::shared_ptr( + new ItemInstance(Item::potion, 1, potion))); + usingTime = getCarriedItem()->getUseDuration(); + setUsingItem(true); + AttributeInstance* speed = + getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED); + speed->removeModifier(SPEED_MODIFIER_DRINKING); + speed->addModifier( + new AttributeModifier(*SPEED_MODIFIER_DRINKING)); + } + } + + if (random->nextFloat() < 0.00075f) { + level->broadcastEntityEvent(shared_from_this(), + EntityEvent::WITCH_HAT_MAGIC); + } + } + + Monster::aiStep(); +} + +void Witch::handleEntityEvent(uint8_t id) { + if (id == EntityEvent::WITCH_HAT_MAGIC) { + for (int i = 0; i < random->nextInt(35) + 10; i++) { + level->addParticle(eParticleType_witchMagic, + x + random->nextGaussian() * .13f, + bb->y1 + 0.5f + random->nextGaussian() * .13f, + z + random->nextGaussian() * .13f, 0, 0, 0); + } + } else { + Monster::handleEntityEvent(id); + } +} + +float Witch::getDamageAfterMagicAbsorb(DamageSource* damageSource, + float damage) { + damage = Monster::getDamageAfterMagicAbsorb(damageSource, damage); + + if (damageSource->getEntity() == shared_from_this()) damage = 0; + if (damageSource->isMagic()) damage *= 0.15; + + return damage; +} + +void Witch::dropDeathLoot(bool wasKilledByPlayer, int playerBonusLevel) { + int passes = random->nextInt(3) + 1; + for (int pass = 0; pass < passes; pass++) { + int count = random->nextInt(3); + int type = DEATH_LOOT[random->nextInt(DEATH_LOOT_COUNT)]; + if (playerBonusLevel > 0) + count += random->nextInt(playerBonusLevel + 1); + + for (int i = 0; i < count; i++) { + spawnAtLocation(type, 1); + } + } +} + +void Witch::performRangedAttack(std::shared_ptr target, + float power) { + if (isUsingItem()) return; + + std::shared_ptr potion = + std::shared_ptr(new ThrownPotion( + level, std::dynamic_pointer_cast(shared_from_this()), + PotionBrewing::POTION_ID_SPLASH_DAMAGE)); + potion->xRot -= -20; + double xd = (target->x + target->xd) - x; + double yd = (target->y + target->getHeadHeight() - 1.1f) - y; + double zd = (target->z + target->zd) - z; + float dist = Mth::sqrt(xd * xd + zd * zd); + + if (dist >= 8 && !target->hasEffect(MobEffect::movementSlowdown)) { + potion->setPotionValue(PotionBrewing::POTION_ID_SPLASH_SLOWNESS); + } else if (target->getHealth() >= 8 && + !target->hasEffect(MobEffect::poison)) { + potion->setPotionValue(PotionBrewing::POTION_ID_SPLASH_POISON); + } else if (dist <= 3 && !target->hasEffect(MobEffect::weakness) && + random->nextFloat() < 0.25f) { + potion->setPotionValue(PotionBrewing::POTION_ID_SPLASH_WEAKNESS); + } + + potion->shoot(xd, yd + dist * 0.2f, zd, 0.75f, 8); + + level->addEntity(potion); +} \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/Witch.h b/Minecraft.World/Entities/Mobs/Witch.h new file mode 100644 index 000000000..07277ba8a --- /dev/null +++ b/Minecraft.World/Entities/Mobs/Witch.h @@ -0,0 +1,49 @@ +#pragma once + +#include "../Monster.h" +#include "RangedAttackMob.h" + +class Witch : public Monster, public RangedAttackMob { +public: + eINSTANCEOF GetType() { return eTYPE_WITCH; } + static Entity* create(Level* level) { return new Witch(level); } + +private: + static AttributeModifier* SPEED_MODIFIER_DRINKING; + + static const int DATA_USING_ITEM = 21; + static const int DEATH_LOOT_COUNT = 8; + static const int DEATH_LOOT[DEATH_LOOT_COUNT]; + + int usingTime; + +public: + Witch(Level* level); + +protected: + virtual void defineSynchedData(); + virtual int getAmbientSound(); + virtual int getHurtSound(); + virtual int getDeathSound(); + +public: + virtual void setUsingItem(bool isUsing); + virtual bool isUsingItem(); + +protected: + virtual void registerAttributes(); + +public: + virtual bool useNewAi(); + virtual void aiStep(); + virtual void handleEntityEvent(uint8_t id); + +protected: + virtual float getDamageAfterMagicAbsorb(DamageSource* damageSource, + float damage); + virtual void dropDeathLoot(bool wasKilledByPlayer, int playerBonusLevel); + +public: + virtual void performRangedAttack(std::shared_ptr target, + float power); +}; \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/WitherBoss.cpp b/Minecraft.World/Entities/Mobs/WitherBoss.cpp new file mode 100644 index 000000000..2c6a0d849 --- /dev/null +++ b/Minecraft.World/Entities/Mobs/WitherBoss.cpp @@ -0,0 +1,509 @@ +#include "../../Platform/stdafx.h" +#include "../../Headers/net.minecraft.world.h" +#include "../../Headers/net.minecraft.world.damagesource.h" +#include "../../Headers/net.minecraft.world.entity.ai.attributes.h" +#include "../../Headers/net.minecraft.world.entity.ai.goal.h" +#include "../../Headers/net.minecraft.world.entity.ai.goal.target.h" +#include "../../Headers/net.minecraft.world.entity.ai.navigation.h" +#include "../../Headers/net.minecraft.world.entity.monster.h" +#include "../../Headers/net.minecraft.world.entity.projectile.h" +#include "../../Headers/net.minecraft.world.entity.h" +#include "../../Headers/net.minecraft.world.item.h" +#include "../../Headers/net.minecraft.world.level.h" +#include "../../Headers/net.minecraft.world.level.tile.h" +#include "../../Headers/net.minecraft.world.phys.h" +#include "../../Util/Mth.h" + +#include "../../Util/SoundTypes.h" + +#include "WitherBoss.h" + +bool LivingEntitySelector::matches(std::shared_ptr entity) const { + if (entity->instanceof(eTYPE_LIVINGENTITY)) { + return std::dynamic_pointer_cast(entity)->getMobType() != + UNDEAD; + } else { + return false; + } +} + +EntitySelector* WitherBoss::livingEntitySelector = new LivingEntitySelector(); + +WitherBoss::WitherBoss(Level* level) : Monster(level) { + // 4J Stu - This function call had to be moved here from the Entity ctor to + // ensure that the derived version of the function is called + this->defineSynchedData(); + registerAttributes(); + setHealth(getMaxHealth()); + + for (unsigned int i = 0; i < 2; ++i) { + xRotHeads[i] = 0.0f; + yRotHeads[i] = 0.0f; + xRotOHeads[i] = 0.0f; + yRotOHeads[i] = 0.0f; + nextHeadUpdate[i] = 0; + idleHeadUpdates[i] = 0; + } + destroyBlocksTick = 0; + + setSize(.9f, 4); + + // noPhysics = true; + fireImmune = true; + + // noCulling = true; + + getNavigation()->setCanFloat(true); + + goalSelector.addGoal(0, new FloatGoal(this)); + goalSelector.addGoal( + 2, new RangedAttackGoal(this, this, 1.0, + SharedConstants::TICKS_PER_SECOND * 2, 20)); + + goalSelector.addGoal(5, new RandomStrollGoal(this, 1.0)); + goalSelector.addGoal(6, new LookAtPlayerGoal(this, typeid(Player), 8)); + goalSelector.addGoal(7, new RandomLookAroundGoal(this)); + + targetSelector.addGoal(1, new HurtByTargetGoal(this, false)); + targetSelector.addGoal( + 2, new NearestAttackableTargetGoal(this, typeid(Mob), 0, false, false, + livingEntitySelector)); + + xpReward = Enemy::XP_REWARD_BOSS; +} + +void WitherBoss::defineSynchedData() { + Monster::defineSynchedData(); + + entityData->define(DATA_TARGET_A, (int)0); + entityData->define(DATA_TARGET_B, (int)0); + entityData->define(DATA_TARGET_C, (int)0); + entityData->define(DATA_ID_INV, (int)0); +} + +void WitherBoss::addAdditonalSaveData(CompoundTag* entityTag) { + Monster::addAdditonalSaveData(entityTag); + + entityTag->putInt(L"Invul", getInvulnerableTicks()); +} + +void WitherBoss::readAdditionalSaveData(CompoundTag* tag) { + Monster::readAdditionalSaveData(tag); + + setInvulnerableTicks(tag->getInt(L"Invul")); +} + +float WitherBoss::getShadowHeightOffs() { return bbHeight / 8; } + +int WitherBoss::getAmbientSound() { + return eSoundType_MOB_WITHER_IDLE; //"mob.wither.idle"; +} + +int WitherBoss::getHurtSound() { + return eSoundType_MOB_WITHER_HURT; //"mob.wither.hurt"; +} + +int WitherBoss::getDeathSound() { + return eSoundType_MOB_WITHER_DEATH; //"mob.wither.death"; +} + +void WitherBoss::aiStep() { + yd *= 0.6f; + + if (!level->isClientSide && getAlternativeTarget(0) > 0) { + std::shared_ptr e = level->getEntity(getAlternativeTarget(0)); + if (e != NULL) { + if ((y < e->y) || (!isPowered() && y < (e->y + 5))) { + if (yd < 0) { + yd = 0; + } + yd += (.5f - yd) * .6f; + } + + double xdist = e->x - x; + double zdist = e->z - z; + double distSqr = xdist * xdist + zdist * zdist; + if (distSqr > 9) { + double sd = Mth::sqrt(distSqr); + xd += ((xdist / sd) * .5f - xd) * .6f; + zd += ((zdist / sd) * .5f - zd) * .6f; + } + } + } + if ((xd * xd + zd * zd) > .05f) { + yRot = (float)atan2(zd, xd) * Mth::RADDEG - 90; + } + Monster::aiStep(); + + for (int i = 0; i < 2; i++) { + yRotOHeads[i] = yRotHeads[i]; + xRotOHeads[i] = xRotHeads[i]; + } + + for (int i = 0; i < 2; i++) { + int entityId = getAlternativeTarget(i + 1); + std::shared_ptr e = nullptr; + if (entityId > 0) { + e = level->getEntity(entityId); + } + if (e != NULL) { + double hx = getHeadX(i + 1); + double hy = getHeadY(i + 1); + double hz = getHeadZ(i + 1); + + double xd = e->x - hx; + double yd = e->y + e->getHeadHeight() - hy; + double zd = e->z - hz; + double sd = Mth::sqrt(xd * xd + zd * zd); + + float yRotD = (float)(atan2(zd, xd) * 180 / PI) - 90; + float xRotD = (float)-(atan2(yd, sd) * 180 / PI); + xRotHeads[i] = rotlerp(xRotHeads[i], xRotD, 40); + yRotHeads[i] = rotlerp(yRotHeads[i], yRotD, 10); + + } else { + yRotHeads[i] = rotlerp(yRotHeads[i], yBodyRot, 10); + } + } + bool _isPowered = isPowered(); + for (int i = 0; i < 3; i++) { + double hx = getHeadX(i); + double hy = getHeadY(i); + double hz = getHeadZ(i); + + level->addParticle(eParticleType_smoke, + hx + random->nextGaussian() * .3f, + hy + random->nextGaussian() * .3f, + hz + random->nextGaussian() * .3f, 0, 0, 0); + if (_isPowered && level->random->nextInt(4) == 0) { + level->addParticle( + eParticleType_mobSpell, hx + random->nextGaussian() * .3f, + hy + random->nextGaussian() * .3f, + hz + random->nextGaussian() * .3f, .7f, .7f, .5f); + } + } + if (getInvulnerableTicks() > 0) { + for (int i = 0; i < 3; i++) { + level->addParticle( + eParticleType_mobSpell, x + random->nextGaussian() * 1.0f, + y + random->nextFloat() * 3.3f, + z + random->nextGaussian() * 1.0f, .7f, .7f, .9f); + } + } +} + +void WitherBoss::newServerAiStep() { + if (getInvulnerableTicks() > 0) { + int newCount = getInvulnerableTicks() - 1; + + if (newCount <= 0) { + level->explode( + shared_from_this(), x, y + getHeadHeight(), z, 7, false, + level->getGameRules()->getBoolean(GameRules::RULE_MOBGRIEFING)); + level->globalLevelEvent(LevelEvent::SOUND_WITHER_BOSS_SPAWN, (int)x, + (int)y, (int)z, 0); + } + + setInvulnerableTicks(newCount); + if (tickCount % 10 == 0) { + heal(10); + } + + return; + } + + Monster::newServerAiStep(); + + for (int i = 1; i < 3; i++) { + if (tickCount >= nextHeadUpdate[i - 1]) { + nextHeadUpdate[i - 1] = + tickCount + SharedConstants::TICKS_PER_SECOND / 2 + + random->nextInt(SharedConstants::TICKS_PER_SECOND / 2); + + if (level->difficulty >= Difficulty::NORMAL && + idleHeadUpdates[i - 1]++ > 15) { + float hrange = 10; + float vrange = 5; + double xt = Mth::nextDouble(random, x - hrange, x + hrange); + double yt = Mth::nextDouble(random, y - vrange, y + vrange); + double zt = Mth::nextDouble(random, z - hrange, z + hrange); + performRangedAttack(i + 1, xt, yt, zt, true); + idleHeadUpdates[i - 1] = 0; + } + + int headTarget = getAlternativeTarget(i); + if (headTarget > 0) { + std::shared_ptr current = level->getEntity(headTarget); + + // 4J: Added check for instance of living entity, had a problem + // with IDs being recycled to other entities + if (current == NULL || + !current->instanceof(eTYPE_LIVINGENTITY) || + !current->isAlive() || distanceToSqr(current) > 30 * 30 || + !canSee(current)) { + setAlternativeTarget(i, 0); + } else { + performRangedAttack( + i + 1, + std::dynamic_pointer_cast(current)); + nextHeadUpdate[i - 1] = + tickCount + SharedConstants::TICKS_PER_SECOND * 2 + + random->nextInt(SharedConstants::TICKS_PER_SECOND); + idleHeadUpdates[i - 1] = 0; + } + } else { + std::vector >* entities = + level->getEntitiesOfClass(typeid(LivingEntity), + bb->grow(20, 8, 20), + livingEntitySelector); + // randomly try to find a target 10 times + for (int attempt = 0; attempt < 10 && !entities->empty(); + attempt++) { + int randomIndex = random->nextInt(entities->size()); + std::shared_ptr selected = + std::dynamic_pointer_cast( + entities->at(randomIndex)); + + if (selected != shared_from_this() && selected->isAlive() && + canSee(selected)) { + if (selected->instanceof(eTYPE_PLAYER)) { + if (!std::dynamic_pointer_cast(selected) + ->abilities.invulnerable) { + assert( + selected->instanceof(eTYPE_LIVINGENTITY)); + setAlternativeTarget(i, selected->entityId); + } + break; + } else { + assert(selected->instanceof(eTYPE_LIVINGENTITY)); + setAlternativeTarget(i, selected->entityId); + break; + } + } + // don't pick this again + entities->erase(entities->begin() + randomIndex); + } + delete entities; + } + } + } + if (getTarget() != NULL) { + assert(getTarget()->instanceof(eTYPE_LIVINGENTITY)); + setAlternativeTarget(0, getTarget()->entityId); + } else { + setAlternativeTarget(0, 0); + } + + if (destroyBlocksTick > 0) { + destroyBlocksTick--; + + if (destroyBlocksTick == 0 && + level->getGameRules()->getBoolean(GameRules::RULE_MOBGRIEFING)) { + // destroy all blocks that are within 1 range, counting from + // feet and 3 blocks up + + int feet = Mth::floor(y); + int ox = Mth::floor(x); + int oz = Mth::floor(z); + bool destroyed = false; + + for (int xStep = -1; xStep <= 1; xStep++) { + for (int zStep = -1; zStep <= 1; zStep++) { + for (int yStep = 0; yStep <= 3; yStep++) { + int tx = ox + xStep; + int ty = feet + yStep; + int tz = oz + zStep; + int tile = level->getTile(tx, ty, tz); + if (tile > 0 && tile != Tile::unbreakable_Id && + tile != Tile::endPortalTile_Id && + tile != Tile::endPortalFrameTile_Id) { + destroyed = level->destroyTile(tx, ty, tz, true) || + destroyed; + } + } + } + } + if (destroyed) { + level->levelEvent(nullptr, LevelEvent::SOUND_ZOMBIE_DOOR_CRASH, + (int)x, (int)y, (int)z, 0); + } + } + } + + if ((tickCount % (SharedConstants::TICKS_PER_SECOND)) == 0) { + heal(1); + } +} + +void WitherBoss::makeInvulnerable() { + setInvulnerableTicks(SharedConstants::TICKS_PER_SECOND * 11); + setHealth(getMaxHealth() / 3); +} + +void WitherBoss::makeStuckInWeb() {} + +int WitherBoss::getArmorValue() { return 4; } + +double WitherBoss::getHeadX(int index) { + if (index <= 0) { + return x; + } + float headAngle = (yBodyRot + 180 * (index - 1)) / 180.0f * PI; + float cos = Mth::cos(headAngle); + return x + cos * 1.3; +} + +double WitherBoss::getHeadY(int index) { + if (index <= 0) { + return y + 3; + } else { + return y + 2.2; + } +} + +double WitherBoss::getHeadZ(int index) { + if (index <= 0) { + return z; + } + float headAngle = (yBodyRot + 180 * (index - 1)) / 180.0f * PI; + float sin = Mth::sin(headAngle); + return z + sin * 1.3; +} + +float WitherBoss::rotlerp(float a, float b, float max) { + float diff = Mth::wrapDegrees(b - a); + if (diff > max) { + diff = max; + } + if (diff < -max) { + diff = -max; + } + return a + diff; +} + +void WitherBoss::performRangedAttack(int head, + std::shared_ptr target) { + performRangedAttack(head, target->x, + target->y + target->getHeadHeight() * .5, target->z, + head == 0 && random->nextFloat() < 0.001f); +} + +void WitherBoss::performRangedAttack(int head, double tx, double ty, double tz, + bool dangerous) { + level->levelEvent(nullptr, LevelEvent::SOUND_WITHER_BOSS_SHOOT, (int)x, + (int)y, (int)z, 0); + + double hx = getHeadX(head); + double hy = getHeadY(head); + double hz = getHeadZ(head); + + double xd = tx - hx; + double yd = ty - hy; + double zd = tz - hz; + + std::shared_ptr ie = + std::shared_ptr(new WitherSkull( + level, std::dynamic_pointer_cast(shared_from_this()), + xd, yd, zd)); + if (dangerous) ie->setDangerous(true); + ie->y = hy; + ie->x = hx; + ie->z = hz; + level->addEntity(ie); +} + +void WitherBoss::performRangedAttack(std::shared_ptr target, + float power) { + performRangedAttack(0, target); +} + +bool WitherBoss::hurt(DamageSource* source, float dmg) { + if (isInvulnerable()) return false; + if (source == DamageSource::drown) return false; + if (getInvulnerableTicks() > 0) { + return false; + } + + if (isPowered()) { + std::shared_ptr directEntity = source->getDirectEntity(); + if (directEntity != NULL && directEntity->GetType() == eTYPE_ARROW) { + return false; + } + } + + std::shared_ptr sourceEntity = source->getEntity(); + if (sourceEntity != NULL) { + if (sourceEntity->instanceof(eTYPE_PLAYER)) { + } else if (sourceEntity->instanceof(eTYPE_LIVINGENTITY) && + std::dynamic_pointer_cast(sourceEntity) + ->getMobType() == getMobType()) { + // can't be harmed by other undead + return false; + } + } + if (destroyBlocksTick <= 0) { + destroyBlocksTick = SharedConstants::TICKS_PER_SECOND; + } + + for (int i = 0; i < IDLE_HEAD_UPDATES_SIZE; i++) { + idleHeadUpdates[i] += 3; + } + + return Monster::hurt(source, dmg); +} + +void WitherBoss::dropDeathLoot(bool wasKilledByPlayer, int playerBonusLevel) { + spawnAtLocation(Item::netherStar_Id, 1); +} + +void WitherBoss::checkDespawn() { noActionTime = 0; } + +int WitherBoss::getLightColor(float a) { + return SharedConstants::FULLBRIGHT_LIGHTVALUE; +} + +bool WitherBoss::isPickable() { return !removed; } + +void WitherBoss::causeFallDamage(float distance) {} + +void WitherBoss::addEffect(MobEffectInstance* newEffect) { + // do nothing +} + +bool WitherBoss::useNewAi() { return true; } + +void WitherBoss::registerAttributes() { + Monster::registerAttributes(); + + getAttribute(SharedMonsterAttributes::MAX_HEALTH)->setBaseValue(300); + getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED)->setBaseValue(0.6f); + + // 4J Stu - Don't make it so far! + // getAttribute(SharedMonsterAttributes::FOLLOW_RANGE)->setBaseValue(40); +} + +float WitherBoss::getHeadYRot(int i) { return yRotHeads[i]; } + +float WitherBoss::getHeadXRot(int i) { return xRotHeads[i]; } + +int WitherBoss::getInvulnerableTicks() { + return entityData->getInteger(DATA_ID_INV); +} + +void WitherBoss::setInvulnerableTicks(int invulnerableTicks) { + entityData->set(DATA_ID_INV, invulnerableTicks); +} + +int WitherBoss::getAlternativeTarget(int headIndex) { + return entityData->getInteger(DATA_TARGET_A + headIndex); +} + +void WitherBoss::setAlternativeTarget(int headIndex, int entityId) { + entityData->set(DATA_TARGET_A + headIndex, entityId); +} + +bool WitherBoss::isPowered() { return getHealth() <= getMaxHealth() / 2; } + +MobType WitherBoss::getMobType() { return UNDEAD; } + +void WitherBoss::ride(std::shared_ptr e) { riding = nullptr; } \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/WitherBoss.h b/Minecraft.World/Entities/Mobs/WitherBoss.h new file mode 100644 index 000000000..6fbe7a5ab --- /dev/null +++ b/Minecraft.World/Entities/Mobs/WitherBoss.h @@ -0,0 +1,110 @@ +#pragma once + +#include "../Monster.h" +#include "RangedAttackMob.h" +#include "BossMob.h" + +class LivingEntitySelector : public EntitySelector { +public: + virtual bool matches(std::shared_ptr entity) const; +}; + +class WitherBoss : public Monster, public RangedAttackMob, public BossMob { +public: + eINSTANCEOF GetType() { return eTYPE_WITHERBOSS; }; + static Entity* create(Level* level) { return new WitherBoss(level); } + +private: + static const int DATA_TARGET_A = 17; + static const int DATA_TARGET_B = 18; + static const int DATA_TARGET_C = 19; + static const int DATA_ID_INV = 20; + +private: + static const int IDLE_HEAD_UPDATES_SIZE = 2; + float xRotHeads[2]; + float yRotHeads[2]; + float xRotOHeads[2]; + float yRotOHeads[2]; + int nextHeadUpdate[2]; + int idleHeadUpdates[IDLE_HEAD_UPDATES_SIZE]; + int destroyBlocksTick; + + static EntitySelector* livingEntitySelector; + +public: + WitherBoss(Level* level); + +protected: + virtual void defineSynchedData(); + +public: + virtual void addAdditonalSaveData(CompoundTag* entityTag); + virtual void readAdditionalSaveData(CompoundTag* tag); + virtual float getShadowHeightOffs(); + +protected: + virtual int getAmbientSound(); + virtual int getHurtSound(); + virtual int getDeathSound(); + +public: + virtual void aiStep(); + +protected: + virtual void newServerAiStep(); + +public: + virtual void makeInvulnerable(); + virtual void makeStuckInWeb(); + virtual int getArmorValue(); + +private: + virtual double getHeadX(int index); + virtual double getHeadY(int index); + virtual double getHeadZ(int index); + virtual float rotlerp(float a, float b, float max); + virtual void performRangedAttack(int head, + std::shared_ptr target); + virtual void performRangedAttack(int head, double tx, double ty, double tz, + bool dangerous); + +public: + virtual void performRangedAttack(std::shared_ptr target, + float power); + virtual bool hurt(DamageSource* source, float dmg); + +protected: + virtual void dropDeathLoot(bool wasKilledByPlayer, int playerBonusLevel); + virtual void checkDespawn(); + +public: + virtual int getLightColor(float a); + virtual bool isPickable(); + +protected: + virtual void causeFallDamage(float distance); + +public: + virtual void addEffect(MobEffectInstance* newEffect); + +protected: + virtual bool useNewAi(); + virtual void registerAttributes(); + +public: + virtual float getHeadYRot(int i); + virtual float getHeadXRot(int i); + virtual int getInvulnerableTicks(); + virtual void setInvulnerableTicks(int invulnerableTicks); + virtual int getAlternativeTarget(int headIndex); + virtual void setAlternativeTarget(int headIndex, int entityId); + virtual bool isPowered(); + virtual MobType getMobType(); + virtual void ride(std::shared_ptr e); + + // 4J Stu - These are required for the BossMob interface + virtual float getMaxHealth() { return Monster::getMaxHealth(); }; + virtual float getHealth() { return Monster::getHealth(); }; + virtual std::wstring getAName() { return app.GetString(IDS_WITHER); }; +}; \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/Wolf.cpp b/Minecraft.World/Entities/Mobs/Wolf.cpp index bc88f97e7..e5ce60cd5 100644 --- a/Minecraft.World/Entities/Mobs/Wolf.cpp +++ b/Minecraft.World/Entities/Mobs/Wolf.cpp @@ -5,9 +5,12 @@ #include "../../Headers/net.minecraft.world.level.h" #include "../../Headers/net.minecraft.world.item.h" #include "../../Headers/net.minecraft.world.entity.h" +#include "../../Headers/net.minecraft.world.entity.animal.h" +#include "../../Headers/net.minecraft.world.entity.ai.attributes.h" #include "../../Headers/net.minecraft.world.entity.ai.goal.h" #include "../../Headers/net.minecraft.world.entity.ai.goal.target.h" #include "../../Headers/net.minecraft.world.entity.ai.navigation.h" +#include "../../Headers/net.minecraft.world.entity.monster.h" #include "../../Headers/net.minecraft.world.entity.player.h" #include "../../Headers/net.minecraft.world.entity.projectile.h" #include "../../Headers/net.minecraft.world.level.pathfinder.h" @@ -22,27 +25,23 @@ Wolf::Wolf(Level* level) : TamableAnimal(level) { // 4J Stu - This function call had to be moved here from the Entity ctor to // ensure that the derived version of the function is called this->defineSynchedData(); - - // 4J Stu - This function call had to be moved here from the Entity ctor to - // ensure that the derived version of the function is called - health = getMaxHealth(); + registerAttributes(); + setHealth(getMaxHealth()); interestedAngle = interestedAngleO = 0.0f; m_isWet = isShaking = false; shakeAnim = shakeAnimO = 0.0f; - this->textureIdx = TN_MOB_WOLF; // 4J - was L"/mob/wolf.png"; this->setSize(0.60f, 0.8f); - runSpeed = 0.3f; getNavigation()->setAvoidWater(true); goalSelector.addGoal(1, new FloatGoal(this)); goalSelector.addGoal(2, sitGoal, false); - goalSelector.addGoal(3, new LeapAtTargetGoal(this, 0.4f)); - goalSelector.addGoal(4, new MeleeAttackGoal(this, runSpeed, true)); - goalSelector.addGoal(5, new FollowOwnerGoal(this, runSpeed, 10, 2)); - goalSelector.addGoal(6, new BreedGoal(this, runSpeed)); - goalSelector.addGoal(7, new RandomStrollGoal(this, runSpeed)); + goalSelector.addGoal(3, new LeapAtTargetGoal(this, 0.4)); + goalSelector.addGoal(4, new MeleeAttackGoal(this, 1.0, true)); + goalSelector.addGoal(5, new FollowOwnerGoal(this, 1.0, 10, 2)); + goalSelector.addGoal(6, new BreedGoal(this, 1.0)); + goalSelector.addGoal(7, new RandomStrollGoal(this, 1.0)); goalSelector.addGoal(8, new BegGoal(this, 8)); goalSelector.addGoal(9, new LookAtPlayerGoal(this, typeid(Player), 8)); goalSelector.addGoal(9, new RandomLookAroundGoal(this)); @@ -51,14 +50,30 @@ Wolf::Wolf(Level* level) : TamableAnimal(level) { targetSelector.addGoal(2, new OwnerHurtTargetGoal(this)); targetSelector.addGoal(3, new HurtByTargetGoal(this, true)); targetSelector.addGoal( - 4, new NonTameRandomTargetGoal(this, typeid(Sheep), 16, 200, false)); + 4, new NonTameRandomTargetGoal(this, typeid(Sheep), 200, false)); + + setTame(false); // Initialize health +} + +void Wolf::registerAttributes() { + TamableAnimal::registerAttributes(); + + getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED)->setBaseValue(0.3f); + + if (isTame()) { + getAttribute(SharedMonsterAttributes::MAX_HEALTH) + ->setBaseValue(TAME_HEALTH); + } else { + getAttribute(SharedMonsterAttributes::MAX_HEALTH) + ->setBaseValue(START_HEALTH); + } } bool Wolf::useNewAi() { return true; } -void Wolf::setTarget(std::shared_ptr target) { +void Wolf::setTarget(std::shared_ptr target) { TamableAnimal::setTarget(target); - if (std::dynamic_pointer_cast(target) == NULL) { + if (target == NULL) { setAngry(false); } else if (!isTame()) { setAngry(true); @@ -67,32 +82,17 @@ void Wolf::setTarget(std::shared_ptr target) { void Wolf::serverAiMobStep() { entityData->set(DATA_HEALTH_ID, getHealth()); } -int Wolf::getMaxHealth() { - if (isTame()) { - return TAME_HEALTH; - } - return START_HEALTH; -} - void Wolf::defineSynchedData() { TamableAnimal::defineSynchedData(); entityData->define(DATA_HEALTH_ID, getHealth()); entityData->define(DATA_INTERESTED_ID, (uint8_t)0); entityData->define( DATA_COLLAR_COLOR, - (uint8_t)ClothTile::getTileDataForItemAuxValue(DyePowderItem::RED)); + (uint8_t)ColoredTile::getTileDataForItemAuxValue(DyePowderItem::RED)); } -bool Wolf::makeStepSound() { return false; } - -int Wolf::getTexture() { - if (isTame()) { - return TN_MOB_WOLF_TAME; // 4J was L"/mob/wolf_tame.png"; - } - if (isAngry()) { - return TN_MOB_WOLF_ANGRY; // 4J was L"/mob/wolf_angry.png"; - } - return TamableAnimal::getTexture(); +void Wolf::playStepSound(int xt, int yt, int zt, int t) { + playSound(eSoundType_MOB_WOLF_STEP, 0.15f, 1); } void Wolf::addAdditonalSaveData(CompoundTag* tag) { @@ -110,14 +110,12 @@ void Wolf::readAdditionalSaveData(CompoundTag* tag) { setCollarColor(tag->getByte(L"CollarColor")); } -bool Wolf::removeWhenFarAway() { return !isTame(); } - int Wolf::getAmbientSound() { if (isAngry()) { return eSoundType_MOB_WOLF_GROWL; } if (random->nextInt(3) == 0) { - if (isTame() && entityData->getInteger(DATA_HEALTH_ID) < 10) { + if (isTame() && entityData->getFloat(DATA_HEALTH_ID) < 10) { return eSoundType_MOB_WOLF_WHINE; } return eSoundType_MOB_WOLF_PANTING; @@ -168,9 +166,8 @@ void Wolf::tick() { } else if (m_isWet || isShaking) { if (isShaking) { if (shakeAnim == 0) { - level->playSound( - shared_from_this(), eSoundType_MOB_WOLF_SHAKE, - getSoundVolume(), + playSound( + eSoundType_MOB_WOLF_SHAKE, getSoundVolume(), (random->nextFloat() - random->nextFloat()) * 0.2f + 1.0f); } @@ -231,14 +228,23 @@ int Wolf::getMaxHeadXRot() { return TamableAnimal::getMaxHeadXRot(); } -bool Wolf::hurt(DamageSource* source, int dmg) { +bool Wolf::hurt(DamageSource* source, float dmg) { + // 4J: Protect owned wolves from untrusted players + if (isTame()) { + std::shared_ptr entity = source->getDirectEntity(); + if (entity != NULL && entity->instanceof(eTYPE_PLAYER)) { + std::shared_ptr attacker = + std::dynamic_pointer_cast(entity); + attacker->canHarmPlayer(getOwnerUUID()); + } + } + if (isInvulnerable()) return false; std::shared_ptr sourceEntity = source->getEntity(); sitGoal->wantToSit(false); - if (sourceEntity != NULL && - !(std::dynamic_pointer_cast(sourceEntity) != NULL || - std::dynamic_pointer_cast(sourceEntity) != NULL)) { - // take half damage from non-players and arrows + if (sourceEntity != NULL && !(sourceEntity->instanceof(eTYPE_PLAYER) || + sourceEntity->instanceof(eTYPE_ARROW))) { + // Take half damage from non-players and arrows dmg = (dmg + 1) / 2; } return TamableAnimal::hurt(source, dmg); @@ -251,6 +257,18 @@ bool Wolf::doHurtTarget(std::shared_ptr target) { damage); } +void Wolf::setTame(bool value) { + TamableAnimal::setTame(value); + + if (value) { + getAttribute(SharedMonsterAttributes::MAX_HEALTH) + ->setBaseValue(TAME_HEALTH); + } else { + getAttribute(SharedMonsterAttributes::MAX_HEALTH) + ->setBaseValue(START_HEALTH); + } +} + void Wolf::tame(const std::wstring& wsOwnerUUID, bool bDisplayTamingParticles, bool bSetSitting) { setTame(true); @@ -265,7 +283,7 @@ void Wolf::tame(const std::wstring& wsOwnerUUID, bool bDisplayTamingParticles, spawnTamingParticles(bDisplayTamingParticles); } -bool Wolf::interact(std::shared_ptr player) { +bool Wolf::mobInteract(std::shared_ptr player) { std::shared_ptr item = player->inventory->getSelected(); if (isTame()) { @@ -273,24 +291,22 @@ bool Wolf::interact(std::shared_ptr player) { if (dynamic_cast(Item::items[item->id]) != NULL) { FoodItem* food = dynamic_cast(Item::items[item->id]); - if (food->isMeat()) { - if (entityData->getInteger(DATA_HEALTH_ID) < MAX_HEALTH) { - heal(food->getNutrition()); - // 4J-PB - don't lose the bone in creative mode - if (player->abilities.instabuild == false) { - item->count--; - if (item->count <= 0) { - player->inventory->setItem( - player->inventory->selected, nullptr); - } + if (food->isMeat() && + entityData->getFloat(DATA_HEALTH_ID) < MAX_HEALTH) { + heal(food->getNutrition()); + // 4J-PB - don't lose the bone in creative mode + if (player->abilities.instabuild == false) { + item->count--; + if (item->count <= 0) { + player->inventory->setItem( + player->inventory->selected, nullptr); } - return true; - } else - return TamableAnimal::interact(player); + } + return true; } } else if (item->id == Item::dye_powder_Id) { - int color = - ClothTile::getTileDataForItemAuxValue(item->getAuxValue()); + int color = ColoredTile::getTileDataForItemAuxValue( + item->getAuxValue()); if (color != getCollarColor()) { setCollarColor(color); @@ -308,6 +324,8 @@ bool Wolf::interact(std::shared_ptr player) { sitGoal->wantToSit(!isSitting()); jumping = false; setPath(NULL); + setAttackTarget(nullptr); + setTarget(nullptr); } } } else { @@ -349,7 +367,7 @@ bool Wolf::interact(std::shared_ptr player) { return false; } } - return TamableAnimal::interact(player); + return TamableAnimal::mobInteract(player); } void Wolf::handleEntityEvent(uint8_t id) { @@ -367,7 +385,7 @@ float Wolf::getTailAngle() { return 0.49f * PI; } else if (isTame()) { return (0.55f - - (MAX_HEALTH - entityData->getInteger(DATA_HEALTH_ID)) * 0.02f) * + (MAX_HEALTH - entityData->getFloat(DATA_HEALTH_ID)) * 0.02f) * PI; } return 0.20f * PI; @@ -429,8 +447,6 @@ std::shared_ptr Wolf::getBreedOffspring( } void Wolf::setIsInterested(bool value) { - // uint8_t current = entityData->getByte(DATA_INTERESTED_ID); - if (value) { entityData->set(DATA_INTERESTED_ID, (uint8_t)1); } else { @@ -441,7 +457,10 @@ void Wolf::setIsInterested(bool value) { bool Wolf::canMate(std::shared_ptr animal) { if (animal == shared_from_this()) return false; if (!isTame()) return false; + + if (!animal->instanceof(eTYPE_WOLF)) return false; std::shared_ptr partner = std::dynamic_pointer_cast(animal); + if (partner == NULL) return false; if (!partner->isTame()) return false; if (partner->isSitting()) return false; @@ -452,3 +471,36 @@ bool Wolf::canMate(std::shared_ptr animal) { bool Wolf::isInterested() { return entityData->getByte(DATA_INTERESTED_ID) == 1; } + +bool Wolf::removeWhenFarAway() { + return !isTame() && tickCount > SharedConstants::TICKS_PER_SECOND * 60 * 2; +} + +bool Wolf::wantsToAttack(std::shared_ptr target, + std::shared_ptr owner) { + // filter un-attackable mobs + if (target->GetType() == eTYPE_CREEPER || + target->GetType() == eTYPE_GHAST) { + return false; + } + // never target wolves that has this player as owner + if (target->GetType() == eTYPE_WOLF) { + std::shared_ptr wolfTarget = + std::dynamic_pointer_cast(target); + if (wolfTarget->isTame() && wolfTarget->getOwner() == owner) { + return false; + } + } + if (target->instanceof(eTYPE_PLAYER) && owner->instanceof(eTYPE_PLAYER) && + !std::dynamic_pointer_cast(owner)->canHarmPlayer( + std::dynamic_pointer_cast(target))) { + // pvp is off + return false; + } + // don't attack tame horses + if ((target->GetType() == eTYPE_HORSE) && + std::dynamic_pointer_cast(target)->isTamed()) { + return false; + } + return true; +} \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/Wolf.h b/Minecraft.World/Entities/Mobs/Wolf.h index 8e7deb217..06aa4c49a 100644 --- a/Minecraft.World/Entities/Mobs/Wolf.h +++ b/Minecraft.World/Entities/Mobs/Wolf.h @@ -13,6 +13,7 @@ private: static const int DATA_HEALTH_ID = 18; static const int DATA_INTERESTED_ID = 19; static const int DATA_COLLAR_COLOR = 20; + static const int START_HEALTH = 8; static const int MAX_HEALTH = 20; static const int TAME_HEALTH = 20; @@ -23,26 +24,24 @@ private: public: Wolf(Level* level); + +protected: + virtual void registerAttributes(); + +public: virtual bool useNewAi(); - virtual void setTarget(std::shared_ptr target); + virtual void setTarget(std::shared_ptr target); protected: virtual void serverAiMobStep(); - -public: - virtual int getMaxHealth(); - -protected: virtual void defineSynchedData(); - virtual bool makeStepSound(); + virtual void playStepSound(int xt, int yt, int zt, int t); public: - virtual int getTexture(); // 4J - changed from std::wstring to ing virtual void addAdditonalSaveData(CompoundTag* tag); virtual void readAdditionalSaveData(CompoundTag* tag); protected: - virtual bool removeWhenFarAway(); virtual int getAmbientSound(); virtual int getHurtSound(); virtual int getDeathSound(); @@ -58,9 +57,10 @@ public: float getHeadRollAngle(float a); float getHeadHeight(); int getMaxHeadXRot(); - virtual bool hurt(DamageSource* source, int dmg); + virtual bool hurt(DamageSource* source, float dmg); virtual bool doHurtTarget(std::shared_ptr target); - virtual bool interact(std::shared_ptr player); + virtual void setTame(bool value); + virtual bool mobInteract(std::shared_ptr player); virtual void handleEntityEvent(uint8_t id); float getTailAngle(); virtual bool isFood(std::shared_ptr item); @@ -83,4 +83,11 @@ public: virtual void setIsInterested(bool isInterested); virtual bool canMate(std::shared_ptr animal); bool isInterested(); + +protected: + virtual bool removeWhenFarAway(); + +public: + virtual bool wantsToAttack(std::shared_ptr target, + std::shared_ptr owner); }; diff --git a/Minecraft.World/Entities/Mobs/Zombie.cpp b/Minecraft.World/Entities/Mobs/Zombie.cpp index ca2a612eb..d6d15644d 100644 --- a/Minecraft.World/Entities/Mobs/Zombie.cpp +++ b/Minecraft.World/Entities/Mobs/Zombie.cpp @@ -3,56 +3,71 @@ #include "../../Headers/net.minecraft.world.level.tile.h" #include "../../Headers/net.minecraft.world.h" #include "../../Headers/net.minecraft.world.item.h" +#include "../../Headers/net.minecraft.world.damagesource.h" #include "../../Headers/net.minecraft.world.effect.h" +#include "../../Headers/net.minecraft.world.entity.ai.attributes.h" #include "../../Headers/net.minecraft.world.entity.ai.goal.h" #include "../../Headers/net.minecraft.world.entity.ai.goal.target.h" #include "../../Headers/net.minecraft.world.entity.ai.navigation.h" +#include "../../Headers/net.minecraft.world.entity.monster.h" #include "../../Headers/net.minecraft.world.entity.npc.h" #include "../../Headers/net.minecraft.world.entity.player.h" #include "Zombie.h" #include "../../Stats/GenericStats.h" #include "../../../Minecraft.Client/Textures/Textures.h" #include "../../Headers/net.minecraft.world.entity.h" +#include "../../Util/JavaMath.h" #include "../../Util/SoundTypes.h" +Attribute* Zombie::SPAWN_REINFORCEMENTS_CHANCE = + (new RangedAttribute(eAttributeId_ZOMBIE_SPAWNREINFORCEMENTS, 0, 0, 1)); +AttributeModifier* Zombie::SPEED_MODIFIER_BABY = + new AttributeModifier(eModifierId_MOB_ZOMBIE_BABYSPEED, 0.5f, + AttributeModifier::OPERATION_MULTIPLY_BASE); + +const float Zombie::ZOMBIE_LEADER_CHANCE = 0.05f; + Zombie::Zombie(Level* level) : Monster(level) { // 4J Stu - This function call had to be moved here from the Entity ctor to // ensure that the derived version of the function is called this->defineSynchedData(); - - // 4J Stu - This function call had to be moved here from the Entity ctor to - // ensure that the derived version of the function is called - health = getMaxHealth(); - - this->textureIdx = TN_MOB_ZOMBIE; // 4J was L"/mob/zombie.png"; - runSpeed = 0.23f; - attackDamage = 4; + registerAttributes(); + setHealth(getMaxHealth()); villagerConversionTime = 0; - registeredBBWidth = -1; - registeredBBHeight = 0; - - setSize(bbWidth, bbHeight); - getNavigation()->setCanOpenDoors(true); goalSelector.addGoal(0, new FloatGoal(this)); goalSelector.addGoal(1, new BreakDoorGoal(this)); - goalSelector.addGoal( - 2, new MeleeAttackGoal(this, eTYPE_PLAYER, runSpeed, false)); - goalSelector.addGoal( - 3, new MeleeAttackGoal(this, eTYPE_VILLAGER, runSpeed, true)); - goalSelector.addGoal(4, new MoveTowardsRestrictionGoal(this, runSpeed)); - goalSelector.addGoal(5, new MoveThroughVillageGoal(this, runSpeed, false)); - goalSelector.addGoal(6, new RandomStrollGoal(this, runSpeed)); + goalSelector.addGoal(2, + new MeleeAttackGoal(this, eTYPE_PLAYER, 1.0, false)); + goalSelector.addGoal(3, + new MeleeAttackGoal(this, eTYPE_VILLAGER, 1.0, true)); + goalSelector.addGoal(4, new MoveTowardsRestrictionGoal(this, 1.0)); + goalSelector.addGoal(5, new MoveThroughVillageGoal(this, 1.0, false)); + goalSelector.addGoal(6, new RandomStrollGoal(this, 1.0)); goalSelector.addGoal(7, new LookAtPlayerGoal(this, typeid(Player), 8)); goalSelector.addGoal(7, new RandomLookAroundGoal(this)); - targetSelector.addGoal(1, new HurtByTargetGoal(this, false)); + targetSelector.addGoal(1, new HurtByTargetGoal(this, true)); targetSelector.addGoal( - 2, new NearestAttackableTargetGoal(this, typeid(Player), 16, 0, true)); - targetSelector.addGoal(2, new NearestAttackableTargetGoal( - this, typeid(Villager), 16, 0, false)); + 2, new NearestAttackableTargetGoal(this, typeid(Player), 0, true)); + targetSelector.addGoal( + 2, new NearestAttackableTargetGoal(this, typeid(Villager), 0, false)); +} + +void Zombie::registerAttributes() { + Monster::registerAttributes(); + + // 4J Stu - Don't make it so far! + // getAttribute(SharedMonsterAttributes::FOLLOW_RANGE)->setBaseValue(40); + + getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED)->setBaseValue(0.23f); + getAttribute(SharedMonsterAttributes::ATTACK_DAMAGE)->setBaseValue(3); + + getAttributes() + ->registerAttribute(SPAWN_REINFORCEMENTS_CHANCE) + ->setBaseValue(random->nextDouble() * 0.10f); } void Zombie::defineSynchedData() { @@ -63,18 +78,12 @@ void Zombie::defineSynchedData() { getEntityData()->define(DATA_CONVERTING_ID, (uint8_t)0); } -float Zombie::getWalkingSpeedModifier() { - return Monster::getWalkingSpeedModifier() * (isBaby() ? 1.5f : 1.0f); +int Zombie::getArmorValue() { + int value = Monster::getArmorValue() + 2; + if (value > 20) value = 20; + return value; } -int Zombie::getTexture() { - return isVillager() ? TN_MOB_ZOMBIE_VILLAGER : TN_MOB_ZOMBIE; -} - -int Zombie::getMaxHealth() { return 20; } - -int Zombie::getArmorValue() { return 2; } - bool Zombie::useNewAi() { return true; } bool Zombie::isBaby() { @@ -82,8 +91,16 @@ bool Zombie::isBaby() { } void Zombie::setBaby(bool baby) { - getEntityData()->set(DATA_BABY_ID, (uint8_t)1); - updateSize(isBaby()); + getEntityData()->set(DATA_BABY_ID, (uint8_t)(baby ? 1 : 0)); + + if (level != NULL && !level->isClientSide) { + AttributeInstance* speed = + getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED); + speed->removeModifier(SPEED_MODIFIER_BABY); + if (baby) { + speed->addModifier(new AttributeModifier(*SPEED_MODIFIER_BABY)); + } + } } bool Zombie::isVillager() { @@ -95,18 +112,96 @@ void Zombie::setVillager(bool villager) { } void Zombie::aiStep() { - if (level->isClientSide) { - updateSize(isBaby()); - } else if (level->isDay() && !level->isClientSide && !isBaby()) { + if (level->isDay() && !level->isClientSide && !isBaby()) { float br = getBrightness(1); if (br > 0.5f && random->nextFloat() * 30 < (br - 0.4f) * 2 && - level->canSeeSky(Mth::floor(x), Mth::floor(y), Mth::floor(z))) { - setOnFire(8); + level->canSeeSky(Mth::floor(x), (int)floor(y + 0.5), + Mth::floor(z))) { + bool burn = true; + + std::shared_ptr helmet = getCarried(SLOT_HELM); + if (helmet != NULL) { + if (helmet->isDamageableItem()) { + helmet->setAuxValue(helmet->getDamageValue() + + random->nextInt(2)); + if (helmet->getDamageValue() >= helmet->getMaxDamage()) { + breakItem(helmet); + setEquippedSlot(SLOT_HELM, nullptr); + } + } + + burn = false; + } + + if (burn) { + setOnFire(8); + } } } Monster::aiStep(); } +bool Zombie::hurt(DamageSource* source, float dmg) { + if (Monster::hurt(source, dmg)) { + std::shared_ptr target = getTarget(); + if ((target == NULL) && getAttackTarget() != NULL && + getAttackTarget()->instanceof(eTYPE_LIVINGENTITY)) + target = std::dynamic_pointer_cast(getAttackTarget()); + if ((target == NULL) && source->getEntity() != NULL && + source->getEntity()->instanceof(eTYPE_LIVINGENTITY)) + target = + std::dynamic_pointer_cast(source->getEntity()); + + if ((target != NULL) && level->difficulty >= Difficulty::HARD && + random->nextFloat() < + getAttribute(SPAWN_REINFORCEMENTS_CHANCE)->getValue()) { + int x = Mth::floor(this->x); + int y = Mth::floor(this->y); + int z = Mth::floor(this->z); + std::shared_ptr reinforcement = + std::shared_ptr(new Zombie(level)); + + for (int i = 0; i < REINFORCEMENT_ATTEMPTS; i++) { + int xt = x + Mth::nextInt(random, REINFORCEMENT_RANGE_MIN, + REINFORCEMENT_RANGE_MAX) * + Mth::nextInt(random, -1, 1); + int yt = y + Mth::nextInt(random, REINFORCEMENT_RANGE_MIN, + REINFORCEMENT_RANGE_MAX) * + Mth::nextInt(random, -1, 1); + int zt = z + Mth::nextInt(random, REINFORCEMENT_RANGE_MIN, + REINFORCEMENT_RANGE_MAX) * + Mth::nextInt(random, -1, 1); + + if (level->isTopSolidBlocking(xt, yt - 1, zt) && + level->getRawBrightness(xt, yt, zt) < 10) { + reinforcement->setPos(xt, yt, zt); + + if (level->isUnobstructed(reinforcement->bb) && + level->getCubes(reinforcement, reinforcement->bb) + ->empty() && + !level->containsAnyLiquid(reinforcement->bb)) { + level->addEntity(reinforcement); + reinforcement->setTarget(target); + reinforcement->finalizeMobSpawn(NULL); + + getAttribute(SPAWN_REINFORCEMENTS_CHANCE) + ->addModifier(new AttributeModifier( + -0.05f, AttributeModifier::OPERATION_ADDITION)); + reinforcement->getAttribute(SPAWN_REINFORCEMENTS_CHANCE) + ->addModifier(new AttributeModifier( + -0.05f, AttributeModifier::OPERATION_ADDITION)); + break; + } + } + } + } + + return true; + } + + return false; +} + void Zombie::tick() { if (!level->isClientSide && isConverting()) { int amount = getConversionProgress(); @@ -121,6 +216,19 @@ void Zombie::tick() { Monster::tick(); } +bool Zombie::doHurtTarget(std::shared_ptr target) { + bool result = Monster::doHurtTarget(target); + + if (result) { + if (getCarriedItem() == NULL && isOnFire() && + random->nextFloat() < level->difficulty * 0.3f) { + target->setOnFire(2 * level->difficulty); + } + } + + return result; +} + int Zombie::getAmbientSound() { return eSoundType_MOB_ZOMBIE_AMBIENT; } int Zombie::getHurtSound() { return eSoundType_MOB_ZOMBIE_HURT; } @@ -129,22 +237,14 @@ int Zombie::getDeathSound() { return eSoundType_MOB_ZOMBIE_DEATH; } int Zombie::getDeathLoot() { return Item::rotten_flesh_Id; } +void Zombie::playStepSound(int xt, int yt, int zt, int t) { + playSound(eSoundType_MOB_ZOMBIE_STEP, 0.15f, 1); +} + MobType Zombie::getMobType() { return UNDEAD; } void Zombie::dropRareDeathLoot(int rareLootLevel) { switch (random->nextInt(3)) { - /* case 0: - spawnAtLocation(Item::sword_iron_Id, 1); - break; - case 1: - spawnAtLocation(Item::helmet_iron_Id, 1); - break; - case 2: - spawnAtLocation(Item::ironIngot_Id, 1); - break; - case 3: - spawnAtLocation(Item::shovel_iron_Id, 1); - break;*/ case 0: spawnAtLocation(Item::ironIngot_Id, 1); break; @@ -157,6 +257,24 @@ void Zombie::dropRareDeathLoot(int rareLootLevel) { } } +void Zombie::populateDefaultEquipmentSlots() { + Monster::populateDefaultEquipmentSlots(); + + if (random->nextFloat() < + (level->difficulty == Difficulty::HARD ? 0.05f : 0.01f)) { + int rand = random->nextInt(3); + if (rand == 0) { + setEquippedSlot(SLOT_WEAPON, + std::shared_ptr( + new ItemInstance(Item::sword_iron))); + } else { + setEquippedSlot(SLOT_WEAPON, + std::shared_ptr( + new ItemInstance(Item::shovel_iron))); + } + } +} + void Zombie::addAdditonalSaveData(CompoundTag* tag) { Monster::addAdditonalSaveData(tag); @@ -175,12 +293,14 @@ void Zombie::readAdditionalSaveData(CompoundTag* tag) { startConverting(tag->getInt(L"ConversionTime")); } -void Zombie::killed(std::shared_ptr mob) { +void Zombie::killed(std::shared_ptr mob) { Monster::killed(mob); if (level->difficulty >= Difficulty::NORMAL && - ((mob->GetType() & eTYPE_VILLAGER) == eTYPE_VILLAGER)) { - if (!level->canCreateMore(GetType(), Level::eSpawnType_Egg)) return; + (mob->GetType() == + eTYPE_VILLAGER)) // 4J-JEV: Villager isn't a non-terminal class, no + // need to instanceof. + { if (level->difficulty == Difficulty::NORMAL && random->nextBoolean()) return; @@ -188,7 +308,7 @@ void Zombie::killed(std::shared_ptr mob) { std::shared_ptr(new Zombie(level)); zombie->copyPosition(mob); level->removeEntity(mob); - zombie->finalizeMobSpawn(); + zombie->finalizeMobSpawn(NULL); zombie->setVillager(true); if (mob->isBaby()) zombie->setBaby(true); level->addEntity(zombie); @@ -198,36 +318,75 @@ void Zombie::killed(std::shared_ptr mob) { } } -void Zombie::finalizeMobSpawn() { - // 4J Stu - TODO TU15 -#if 0 - canPickUpLoot = random->nextFloat() < CAN_PICK_UP_LOOT_CHANCES[level->difficulty]; -#endif +MobGroupData* Zombie::finalizeMobSpawn( + MobGroupData* groupData, int extraData /*= 0*/) // 4J Added extraData param +{ + groupData = Monster::finalizeMobSpawn(groupData); + float difficulty = level->getDifficulty(x, y, z); - if (level->random->nextFloat() < 0.05f) { - setVillager(true); + setCanPickUpLoot(random->nextFloat() < MAX_PICKUP_LOOT_CHANCE * difficulty); + + if (groupData == NULL) { + groupData = new ZombieGroupData(level->random->nextFloat() < 0.05f, + level->random->nextFloat() < 0.05f); } - // 4J Stu - TODO TU15 -#if 0 - populateDefaultEquipmentSlots(); - populateDefaultEquipmentEnchantments(); + if (dynamic_cast(groupData) != NULL) { + ZombieGroupData* zombieData = (ZombieGroupData*)groupData; - if (getCarried(SLOT_HELM) == null) - { - Calendar cal = level.getCalendar(); + if (zombieData->isVillager) { + setVillager(true); + } - if (cal.get(Calendar.MONTH) + 1 == 10 && cal.get(Calendar.DAY_OF_MONTH) == 31 && random.nextFloat() < 0.25f) { - // Halloween! OooOOo! 25% of all skeletons/zombies can wear - // pumpkins on their heads. - setEquippedSlot(SLOT_HELM, new ItemInstance(random.nextFloat() < 0.1f ? Tile.litPumpkin : Tile.pumpkin)); - dropChances[SLOT_HELM] = 0; - } - } -#endif + if (zombieData->isBaby) { + setBaby(true); + } + } + + populateDefaultEquipmentSlots(); + populateDefaultEquipmentEnchantments(); + + if (getCarried(SLOT_HELM) == NULL) { + // [EB]: We have this code in quite some places, shouldn't we set + // something like this globally? + if (Calendar::GetMonth() + 1 == 10 && Calendar::GetDayOfMonth() == 31 && + random->nextFloat() < 0.25f) { + // Halloween! OooOOo! 25% of all skeletons/zombies can wear + // pumpkins on their heads. + setEquippedSlot(SLOT_HELM, + std::shared_ptr(new ItemInstance( + random->nextFloat() < 0.1f ? Tile::litPumpkin + : Tile::pumpkin))); + dropChances[SLOT_HELM] = 0; + } + } + + getAttribute(SharedMonsterAttributes::KNOCKBACK_RESISTANCE) + ->addModifier( + new AttributeModifier(random->nextDouble() * 0.05f, + AttributeModifier::OPERATION_ADDITION)); + + // 4J Stu - Take this out, it's not good and nobody will notice. Also not + // great for performance. + // getAttribute(SharedMonsterAttributes::FOLLOW_RANGE)->addModifier(new + // AttributeModifier(random->nextDouble() * 1.50f, + // AttributeModifier::OPERATION_MULTIPLY_TOTAL)); + + if (random->nextFloat() < difficulty * ZOMBIE_LEADER_CHANCE) { + getAttribute(SPAWN_REINFORCEMENTS_CHANCE) + ->addModifier( + new AttributeModifier(random->nextDouble() * 0.25f + 0.50f, + AttributeModifier::OPERATION_ADDITION)); + getAttribute(SharedMonsterAttributes::MAX_HEALTH) + ->addModifier(new AttributeModifier( + random->nextDouble() * 3.0f + 1.0f, + AttributeModifier::OPERATION_MULTIPLY_TOTAL)); + } + + return groupData; } -bool Zombie::interact(std::shared_ptr player) { +bool Zombie::mobInteract(std::shared_ptr player) { std::shared_ptr item = player->getSelectedItem(); if (item != NULL && item->getItem() == Item::apple_gold && @@ -269,15 +428,16 @@ void Zombie::startConverting(int time) { void Zombie::handleEntityEvent(uint8_t id) { if (id == EntityEvent::ZOMBIE_CONVERTING) { - level->playLocalSound(x + 0.5f, y + 0.5f, z + 0.5f, - eSoundType_MOB_ZOMBIE_REMEDY, - 1 + random->nextFloat(), - random->nextFloat() * 0.7f + 0.3f); //, false); + level->playLocalSound( + x + 0.5f, y + 0.5f, z + 0.5f, eSoundType_MOB_ZOMBIE_REMEDY, + 1 + random->nextFloat(), random->nextFloat() * 0.7f + 0.3f, false); } else { Monster::handleEntityEvent(id); } } +bool Zombie::removeWhenFarAway() { return !isConverting(); } + bool Zombie::isConverting() { return getEntityData()->getByte(DATA_CONVERTING_ID) == (uint8_t)1; } @@ -286,7 +446,7 @@ void Zombie::finishConversion() { std::shared_ptr villager = std::shared_ptr(new Villager(level)); villager->copyPosition(shared_from_this()); - villager->finalizeMobSpawn(); + villager->finalizeMobSpawn(NULL); villager->setRewardPlayersInVillage(); if (isBaby()) villager->setAge(-20 * 60 * 20); level->removeEntity(shared_from_this()); @@ -329,20 +489,7 @@ int Zombie::getConversionProgress() { return amount; } -void Zombie::updateSize(bool isBaby) { internalSetSize(isBaby ? .5f : 1.0f); } - -void Zombie::setSize(float w, float h) { - bool inited = registeredBBWidth > 0; - - registeredBBWidth = w; - registeredBBHeight = h; - - if (!inited) { - internalSetSize(1.0f); - } -} - -void Zombie::internalSetSize(float scale) { - PathfinderMob::setSize(registeredBBWidth * scale, - registeredBBHeight * scale); -} +Zombie::ZombieGroupData::ZombieGroupData(bool baby, bool villager) { + isBaby = baby; + isVillager = villager; +} \ No newline at end of file diff --git a/Minecraft.World/Entities/Mobs/Zombie.h b/Minecraft.World/Entities/Mobs/Zombie.h index f84221528..0e766ee1c 100644 --- a/Minecraft.World/Entities/Mobs/Zombie.h +++ b/Minecraft.World/Entities/Mobs/Zombie.h @@ -2,6 +2,7 @@ #include "../Monster.h" #include "../../Util/SharedConstants.h" +#include "MobGroupData.h" class Zombie : public Monster { private: @@ -10,14 +11,24 @@ private: static const int VILLAGER_CONVERSION_WAIT_MAX = SharedConstants::TICKS_PER_SECOND * 60 * 5; +protected: + static Attribute* SPAWN_REINFORCEMENTS_CHANCE; + +private: + static AttributeModifier* SPEED_MODIFIER_BABY; + static const int DATA_BABY_ID = 12; static const int DATA_VILLAGER_ID = 13; static const int DATA_CONVERTING_ID = 14; - int villagerConversionTime; +public: + static const float ZOMBIE_LEADER_CHANCE; + static const int REINFORCEMENT_ATTEMPTS = 50; + static const int REINFORCEMENT_RANGE_MAX = 40; + static const int REINFORCEMENT_RANGE_MIN = 7; - float registeredBBWidth; - float registeredBBHeight; +private: + int villagerConversionTime; public: static const int MAX_SPECIAL_BLOCKS_COUNT = 14; @@ -28,61 +39,72 @@ public: static Entity* create(Level* level) { return new Zombie(level); } Zombie(Level* level); - virtual float getWalkingSpeedModifier(); protected: + virtual void registerAttributes(); virtual void defineSynchedData(); public: - virtual int getTexture(); - virtual int getMaxHealth(); - int getArmorValue(); + virtual int getArmorValue(); protected: virtual bool useNewAi(); public: - bool isBaby(); - void setBaby(bool baby); - bool isVillager(); - void setVillager(bool villager); + virtual bool isBaby(); + virtual void setBaby(bool baby); + virtual bool isVillager(); + virtual void setVillager(bool villager); virtual void aiStep(); + virtual bool hurt(DamageSource* source, float dmg); virtual void tick(); + virtual bool doHurtTarget(std::shared_ptr target); protected: virtual int getAmbientSound(); virtual int getHurtSound(); virtual int getDeathSound(); virtual int getDeathLoot(); + virtual void playStepSound(int xt, int yt, int zt, int t); public: - MobType getMobType(); + virtual MobType getMobType(); protected: virtual void dropRareDeathLoot(int rareLootLevel); + virtual void populateDefaultEquipmentSlots(); public: virtual void addAdditonalSaveData(CompoundTag* tag); virtual void readAdditionalSaveData(CompoundTag* tag); - void killed(std::shared_ptr mob); - virtual void finalizeMobSpawn(); - bool interact(std::shared_ptr player); + virtual void killed(std::shared_ptr mob); + virtual MobGroupData* finalizeMobSpawn( + MobGroupData* groupData, + int extraData = 0); // 4J Added extraData param + virtual bool mobInteract(std::shared_ptr player); protected: - void startConverting(int time); + virtual void startConverting(int time); public: - void handleEntityEvent(uint8_t id); - bool isConverting(); + virtual void handleEntityEvent(uint8_t id); protected: - void finishConversion(); - int getConversionProgress(); + virtual bool removeWhenFarAway(); public: - virtual void updateSize(bool isBaby); + virtual bool isConverting(); protected: - virtual void setSize(float w, float h); - void internalSetSize(float scale); + virtual void finishConversion(); + virtual int getConversionProgress(); + +private: + class ZombieGroupData : public MobGroupData { + public: + bool isBaby; + bool isVillager; + + ZombieGroupData(bool baby, bool villager); + }; }; diff --git a/Minecraft.World/Entities/Monster.cpp b/Minecraft.World/Entities/Monster.cpp index 6a8616c0c..2b89198e5 100644 --- a/Minecraft.World/Entities/Monster.cpp +++ b/Minecraft.World/Entities/Monster.cpp @@ -2,28 +2,22 @@ #include "../Headers/net.minecraft.world.h" #include "../Headers/net.minecraft.world.phys.h" #include "../Headers/net.minecraft.world.level.h" +#include "../Headers/net.minecraft.world.entity.ai.attributes.h" #include "../Headers/net.minecraft.world.entity.player.h" +#include "../Headers/net.minecraft.world.entity.monster.h" #include "../Headers/net.minecraft.world.damagesource.h" #include "../Headers/net.minecraft.world.effect.h" #include "../Headers/net.minecraft.world.item.enchantment.h" #include "Monster.h" + #include "../../Minecraft.Client/Minecraft.h" -void Monster::_init() { attackDamage = 2; } - Monster::Monster(Level* level) : PathfinderMob(level) { - // 4J Stu - This function call had to be moved here from the Entity ctor to - // ensure that the derived version of the function is called - - // 4J Stu - Only the most derived classes should call this - // this->defineSynchedData(); - - _init(); - xpReward = Enemy::XP_REWARD_MEDIUM; } void Monster::aiStep() { + updateSwingTime(); float br = getBrightness(1); if (br > 0.5f) { noActionTime += 2; @@ -52,13 +46,14 @@ std::shared_ptr Monster::findAttackTarget() { return std::shared_ptr(); } -bool Monster::hurt(DamageSource* source, int dmg) { +bool Monster::hurt(DamageSource* source, float dmg) { + if (isInvulnerable()) return false; if (PathfinderMob::hurt(source, dmg)) { std::shared_ptr sourceEntity = source->getEntity(); if (rider.lock() == sourceEntity || riding == sourceEntity) return true; if (sourceEntity != shared_from_this()) { - this->attackTarget = sourceEntity; + attackTarget = sourceEntity; } return true; } @@ -72,34 +67,49 @@ bool Monster::hurt(DamageSource* source, int dmg) { * @return */ bool Monster::doHurtTarget(std::shared_ptr target) { - int dmg = attackDamage; - if (hasEffect(MobEffect::damageBoost)) { - dmg += (3 << getEffect(MobEffect::damageBoost)->getAmplifier()); - } - if (hasEffect(MobEffect::weakness)) { - dmg -= (2 << getEffect(MobEffect::weakness)->getAmplifier()); + float dmg = + (float)getAttribute(SharedMonsterAttributes::ATTACK_DAMAGE)->getValue(); + int knockback = 0; + + if (target->instanceof(eTYPE_LIVINGENTITY)) { + std::shared_ptr livingTarget = + std::dynamic_pointer_cast(target); + dmg += EnchantmentHelper::getDamageBonus( + std::dynamic_pointer_cast(shared_from_this()), + livingTarget); + knockback += EnchantmentHelper::getKnockbackBonus( + std::dynamic_pointer_cast(shared_from_this()), + livingTarget); } - DamageSource* damageSource = DamageSource::mobAttack( - std::dynamic_pointer_cast(shared_from_this())); - bool didHurt = target->hurt(damageSource, dmg); - delete damageSource; + boolean wasHurt = target->hurt( + DamageSource::mobAttack( + std::dynamic_pointer_cast(shared_from_this())), + dmg); + + if (wasHurt) { + if (knockback > 0) { + target->push(-Mth::sin(yRot * PI / 180) * knockback * .5f, 0.1, + Mth::cos(yRot * PI / 180) * knockback * .5f); + xd *= 0.6; + zd *= 0.6; + } - if (didHurt) { int fireAspect = EnchantmentHelper::getFireAspect( - std::dynamic_pointer_cast(shared_from_this())); + std::dynamic_pointer_cast(shared_from_this())); if (fireAspect > 0) { target->setOnFire(fireAspect * 4); } - std::shared_ptr mob = std::dynamic_pointer_cast(target); - if (mob != NULL) { - ThornsEnchantment::doThornsAfterAttack(shared_from_this(), mob, - random); + if (target->instanceof(eTYPE_LIVINGENTITY)) { + std::shared_ptr livingTarget = + std::dynamic_pointer_cast(target); + ThornsEnchantment::doThornsAfterAttack(shared_from_this(), + livingTarget, random); } } - return didHurt; + return wasHurt; } void Monster::checkHurtTarget(std::shared_ptr target, float distance) { @@ -134,9 +144,12 @@ bool Monster::isDarkEnoughToSpawn() { } bool Monster::canSpawn() { - // 4J Stu - // Fix for #8265 - AI: Monsters will flash briefly on the screen around - // Monster Spawners when the game settings are set to Peaceful. - return isDarkEnoughToSpawn() && PathfinderMob::canSpawn() && - level->difficulty > Difficulty::PEACEFUL; + return level->difficulty > Difficulty::PEACEFUL && isDarkEnoughToSpawn() && + PathfinderMob::canSpawn(); } + +void Monster::registerAttributes() { + PathfinderMob::registerAttributes(); + + getAttributes()->registerAttribute(SharedMonsterAttributes::ATTACK_DAMAGE); +} \ No newline at end of file diff --git a/Minecraft.World/Entities/Monster.h b/Minecraft.World/Entities/Monster.h index 3bfad2f45..0e8d5baa9 100644 --- a/Minecraft.World/Entities/Monster.h +++ b/Minecraft.World/Entities/Monster.h @@ -12,12 +12,6 @@ public: eINSTANCEOF GetType() { return eTYPE_MONSTER; } static Entity* create(Level* level) { return NULL; } -protected: - int attackDamage; - -private: - void _init(); - public: Monster(Level* level); @@ -28,7 +22,7 @@ protected: virtual std::shared_ptr findAttackTarget(); public: - virtual bool hurt(DamageSource* source, int dmg); + virtual bool hurt(DamageSource* source, float dmg); virtual bool doHurtTarget(std::shared_ptr target); protected: @@ -43,4 +37,7 @@ protected: public: virtual bool canSpawn(); -}; + +protected: + void registerAttributes(); +}; \ No newline at end of file diff --git a/Minecraft.World/Entities/OwnableEntity.h b/Minecraft.World/Entities/OwnableEntity.h new file mode 100644 index 000000000..d360115a7 --- /dev/null +++ b/Minecraft.World/Entities/OwnableEntity.h @@ -0,0 +1,7 @@ +#pragma once + +class OwnableEntity { +public: + virtual std::wstring getOwnerUUID() = 0; + virtual std::shared_ptr getOwner() = 0; +}; \ No newline at end of file diff --git a/Minecraft.World/Entities/PathfinderMob.cpp b/Minecraft.World/Entities/PathfinderMob.cpp index c52d1971e..8b30f6fb8 100644 --- a/Minecraft.World/Entities/PathfinderMob.cpp +++ b/Minecraft.World/Entities/PathfinderMob.cpp @@ -1,24 +1,49 @@ #include "../Platform/stdafx.h" #include "../Headers/net.minecraft.world.entity.h" +#include "../Headers/net.minecraft.world.entity.animal.h" +#include "../Headers/net.minecraft.world.entity.ai.attributes.h" +#include "../Headers/net.minecraft.world.entity.ai.goal.h" +#include "../Headers/net.minecraft.world.entity.ai.navigation.h" +#include "../Headers/net.minecraft.world.entity.monster.h" #include "../Headers/net.minecraft.world.level.h" #include "../Headers/net.minecraft.world.level.pathfinder.h" #include "../Headers/net.minecraft.world.phys.h" #include "../Util/SharedConstants.h" #include "PathfinderMob.h" +AttributeModifier* PathfinderMob::SPEED_MODIFIER_FLEEING = + (new AttributeModifier(eModifierId_MOB_FLEEING, 2.0f, + AttributeModifier::OPERATION_MULTIPLY_TOTAL)) + ->setSerialize(false); + PathfinderMob::PathfinderMob(Level* level) : Mob(level) { path = NULL; attackTarget = nullptr; holdGround = false; fleeTime = 0; + + restrictRadius = -1; + restrictCenter = new Pos(0, 0, 0); + addedLeashRestrictionGoal = false; + leashRestrictionGoal = new MoveTowardsRestrictionGoal(this, 1.0f); } bool PathfinderMob::shouldHoldGround() { return false; } -PathfinderMob::~PathfinderMob() { delete path; } +PathfinderMob::~PathfinderMob() { + delete path; + delete restrictCenter; + delete leashRestrictionGoal; +} void PathfinderMob::serverAiStep() { - if (fleeTime > 0) fleeTime--; + if (fleeTime > 0) { + if (--fleeTime == 0) { + AttributeInstance* speed = + getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED); + speed->removeModifier(SPEED_MODIFIER_FLEEING); + } + } holdGround = shouldHoldGround(); float maxDist = 16; @@ -113,7 +138,8 @@ void PathfinderMob::serverAiStep() { double yd = target->y - yFloor; float yRotD = (float)(atan2(zd, xd) * 180 / PI) - 90; float rotDiff = Mth::wrapDegrees(yRotD - yRot); - yya = runSpeed; + yya = (float)getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED) + ->getValue(); if (rotDiff > MAX_TURN) { rotDiff = MAX_TURN; } @@ -144,7 +170,7 @@ void PathfinderMob::serverAiStep() { lookAt(attackTarget, 30, 30); } - if (this->horizontalCollision && !isPathFinding()) jumping = true; + if (horizontalCollision && !isPathFinding()) jumping = true; if (random->nextFloat() < 0.8f && (inWater || inLava)) jumping = true; } @@ -217,13 +243,88 @@ void PathfinderMob::setAttackTarget(std::shared_ptr attacker) { attackTarget = attacker; } -float PathfinderMob::getWalkingSpeedModifier() { - if (useNewAi()) return 1.0f; - float speed = Mob::getWalkingSpeedModifier(); - if (fleeTime > 0) speed *= 2; - return speed; +// might move to navigation, might make area +bool PathfinderMob::isWithinRestriction() { + return isWithinRestriction(Mth::floor(x), Mth::floor(y), Mth::floor(z)); } +bool PathfinderMob::isWithinRestriction(int x, int y, int z) { + if (restrictRadius == -1) return true; + return restrictCenter->distSqr(x, y, z) < restrictRadius * restrictRadius; +} + +void PathfinderMob::restrictTo(int x, int y, int z, int radius) { + restrictCenter->set(x, y, z); + restrictRadius = radius; +} + +Pos* PathfinderMob::getRestrictCenter() { return restrictCenter; } + +float PathfinderMob::getRestrictRadius() { return restrictRadius; } + +void PathfinderMob::clearRestriction() { restrictRadius = -1; } + +bool PathfinderMob::hasRestriction() { return restrictRadius != -1; } + +void PathfinderMob::tickLeash() { + Mob::tickLeash(); + + if (isLeashed() && getLeashHolder() != NULL && + getLeashHolder()->level == this->level) { + // soft restriction + std::shared_ptr leashHolder = getLeashHolder(); + restrictTo((int)leashHolder->x, (int)leashHolder->y, + (int)leashHolder->z, 5); + + float _distanceTo = distanceTo(leashHolder); + + std::shared_ptr tamabaleAnimal = + shared_from_this()->instanceof(eTYPE_TAMABLE_ANIMAL) + ? std::dynamic_pointer_cast(shared_from_this()) + : nullptr; + if ((tamabaleAnimal != NULL) && tamabaleAnimal->isSitting()) { + if (_distanceTo > 10) { + dropLeash(true, true); + } + return; + } + + if (!addedLeashRestrictionGoal) { + goalSelector.addGoal(2, leashRestrictionGoal, false); + getNavigation()->setAvoidWater(false); + addedLeashRestrictionGoal = true; + } + + onLeashDistance(_distanceTo); + + if (_distanceTo > 4) { + // harder restriction + getNavigation()->moveTo(leashHolder, 1.0); + } + if (_distanceTo > 6) { + // hardest restriction + double dx = (leashHolder->x - x) / _distanceTo; + double dy = (leashHolder->y - y) / _distanceTo; + double dz = (leashHolder->z - z) / _distanceTo; + + xd += dx * abs(dx) * .4; + yd += dy * abs(dy) * .4; + zd += dz * abs(dz) * .4; + } + if (_distanceTo > 10) { + dropLeash(true, true); + } + + } else if (!isLeashed() && addedLeashRestrictionGoal) { + addedLeashRestrictionGoal = false; + goalSelector.removeGoal(leashRestrictionGoal); + getNavigation()->setAvoidWater(true); + clearRestriction(); + } +} + +void PathfinderMob::onLeashDistance(float distanceToLeashHolder) {} + bool PathfinderMob::couldWander() { return (noActionTime < SharedConstants::TICKS_PER_SECOND * 5) || (isExtraWanderingEnabled()); diff --git a/Minecraft.World/Entities/PathfinderMob.h b/Minecraft.World/Entities/PathfinderMob.h index 610031481..f4d6739bf 100644 --- a/Minecraft.World/Entities/PathfinderMob.h +++ b/Minecraft.World/Entities/PathfinderMob.h @@ -6,6 +6,9 @@ class Level; class Path; class PathfinderMob : public Mob { +public: + static AttributeModifier* SPEED_MODIFIER_FLEEING; + private: static const int MAX_TURN = 30; @@ -21,6 +24,13 @@ protected: bool holdGround; int fleeTime; +private: + Pos* restrictCenter; + float restrictRadius; + Goal* leashRestrictionGoal; + bool addedLeashRestrictionGoal; + +protected: virtual bool shouldHoldGround(); virtual void serverAiStep(); virtual void findRandomStrollLocation(int quadrant = -1); @@ -34,13 +44,23 @@ protected: public: virtual bool canSpawn(); - bool isPathFinding(); - void setPath(Path* path); - std::shared_ptr getAttackTarget(); - void setAttackTarget(std::shared_ptr attacker); + virtual bool isPathFinding(); + virtual void setPath(Path* path); + virtual std::shared_ptr getAttackTarget(); + virtual void setAttackTarget(std::shared_ptr attacker); + + // might move to navigation, might make area + virtual bool isWithinRestriction(); + virtual bool isWithinRestriction(int x, int y, int z); + virtual void restrictTo(int x, int y, int z, int radius); + virtual Pos* getRestrictCenter(); + virtual float getRestrictRadius(); + virtual void clearRestriction(); + virtual bool hasRestriction(); protected: - float getWalkingSpeedModifier(); + void tickLeash(); + void onLeashDistance(float distanceToLeashHolder); // 4J added public: diff --git a/Minecraft.World/Entities/Projectile.h b/Minecraft.World/Entities/Projectile.h new file mode 100644 index 000000000..b80fbf4bb --- /dev/null +++ b/Minecraft.World/Entities/Projectile.h @@ -0,0 +1,7 @@ +#pragma once + +class Projectile { +public: + virtual void shoot(double xd, double yd, double zd, float pow, + float uncertainty) = 0; +}; \ No newline at end of file diff --git a/Minecraft.World/Entities/SyncedEntityData.cpp b/Minecraft.World/Entities/SyncedEntityData.cpp index c985da44f..e12f9b559 100644 --- a/Minecraft.World/Entities/SyncedEntityData.cpp +++ b/Minecraft.World/Entities/SyncedEntityData.cpp @@ -45,6 +45,17 @@ void SynchedEntityData::define(int id, short value) { m_isEmpty = false; } +void SynchedEntityData::define(int id, float value) { + MemSect(17); + checkId(id); + int type = TYPE_FLOAT; + std::shared_ptr dataItem = + std::shared_ptr(new DataItem(type, id, value)); + itemsById[id] = dataItem; + MemSect(0); + m_isEmpty = false; +} + void SynchedEntityData::define(int id, const std::wstring& value) { MemSect(17); checkId(id); @@ -93,8 +104,7 @@ int SynchedEntityData::getInteger(int id) { } float SynchedEntityData::getFloat(int id) { - assert(false); // 4J - not currently implemented - return 0; + return itemsById[id]->getValue_float(); } std::wstring SynchedEntityData::getString(int id) { @@ -144,6 +154,17 @@ void SynchedEntityData::set(int id, short value) { } } +void SynchedEntityData::set(int id, float value) { + std::shared_ptr dataItem = itemsById[id]; + + // update the value if it has changed + if (value != dataItem->getValue_float()) { + dataItem->setValue(value); + dataItem->setDirty(true); + m_isDirty = true; + } +} + void SynchedEntityData::set(int id, const std::wstring& value) { std::shared_ptr dataItem = itemsById[id]; @@ -167,7 +188,7 @@ void SynchedEntityData::set(int id, std::shared_ptr value) { } void SynchedEntityData::markDirty(int id) { - (*itemsById.find(id)).second->dirty = true; + itemsById[id]->dirty = true; m_isDirty = true; } @@ -194,10 +215,9 @@ SynchedEntityData::packDirty() { std::vector >* result = NULL; if (m_isDirty) { - AUTO_VAR(itEnd, itemsById.end()); - for (AUTO_VAR(it, itemsById.begin()); it != itEnd; it++) { - std::shared_ptr dataItem = (*it).second; - if (dataItem->isDirty()) { + for (int i = 0; i <= MAX_ID_VALUE; i++) { + std::shared_ptr dataItem = itemsById[i]; + if ((dataItem != NULL) && dataItem->isDirty()) { dataItem->setDirty(false); if (result == NULL) { @@ -214,10 +234,11 @@ SynchedEntityData::packDirty() { void SynchedEntityData::packAll(DataOutputStream* output) // throws IOException { - AUTO_VAR(itEnd, itemsById.end()); - for (AUTO_VAR(it, itemsById.begin()); it != itEnd; it++) { - std::shared_ptr dataItem = (*it).second; - writeDataItem(output, dataItem); + for (int i = 0; i <= MAX_ID_VALUE; i++) { + std::shared_ptr dataItem = itemsById[i]; + if (dataItem != NULL) { + writeDataItem(output, dataItem); + } } // add an eof @@ -228,13 +249,14 @@ std::vector >* SynchedEntityData::getAll() { std::vector >* result = NULL; - AUTO_VAR(itEnd, itemsById.end()); - for (AUTO_VAR(it, itemsById.begin()); it != itEnd; it++) { - if (result == NULL) { - result = new std::vector >(); + for (int i = 0; i <= MAX_ID_VALUE; i++) { + std::shared_ptr dataItem = itemsById[i]; + if (dataItem != NULL) { + if (result == NULL) { + result = new std::vector >(); + } + result->push_back(dataItem); } - std::shared_ptr dataItem = (*it).second; - result->push_back(dataItem); } return result; @@ -261,6 +283,9 @@ void SynchedEntityData::writeDataItem( case TYPE_SHORT: output->writeShort(dataItem->getValue_short()); break; + case TYPE_FLOAT: + output->writeFloat(dataItem->getValue_float()); + break; case TYPE_STRING: Packet::writeUtf(dataItem->getValue_wstring(), output); break; @@ -310,6 +335,12 @@ SynchedEntityData::unpack(DataInputStream* input) // throws IOException item = std::shared_ptr( new DataItem(itemType, itemId, dataRead)); } break; + case TYPE_FLOAT: { + float dataRead = input->readFloat(); + item = std::shared_ptr( + new DataItem(itemType, itemId, dataRead)); + + } break; case TYPE_STRING: item = std::shared_ptr(new DataItem( itemType, itemId, @@ -346,23 +377,27 @@ void SynchedEntityData::assignValues( AUTO_VAR(itEnd, items->end()); for (AUTO_VAR(it, items->begin()); it != itEnd; it++) { std::shared_ptr item = *it; - AUTO_VAR(itemFromId, itemsById.find(item->getId())); - if (itemFromId != itemsById.end()) { + + std::shared_ptr itemFromId = itemsById[item->getId()]; + if (itemFromId != NULL) { switch (item->getType()) { case TYPE_BYTE: - itemFromId->second->setValue(item->getValue_byte()); + itemFromId->setValue(item->getValue_byte()); break; case TYPE_SHORT: - itemFromId->second->setValue(item->getValue_short()); + itemFromId->setValue(item->getValue_short()); break; case TYPE_INT: - itemFromId->second->setValue(item->getValue_int()); + itemFromId->setValue(item->getValue_int()); + break; + case TYPE_FLOAT: + itemFromId->setValue(item->getValue_float()); break; case TYPE_STRING: - itemFromId->second->setValue(item->getValue_wstring()); + itemFromId->setValue(item->getValue_wstring()); break; case TYPE_ITEMINSTANCE: - itemFromId->second->setValue(item->getValue_itemInstance()); + itemFromId->setValue(item->getValue_itemInstance()); break; default: assert(false); // 4J - not implemented @@ -370,40 +405,48 @@ void SynchedEntityData::assignValues( } } } + + // client-side dirty + m_isDirty = true; } bool SynchedEntityData::isEmpty() { return m_isEmpty; } +void SynchedEntityData::clearDirty() { m_isDirty = false; } + int SynchedEntityData::getSizeInBytes() { int size = 1; - AUTO_VAR(itEnd, itemsById.end()); - for (AUTO_VAR(it, itemsById.begin()); it != itEnd; it++) { - std::shared_ptr dataItem = (*it).second; + for (int i = 0; i <= MAX_ID_VALUE; i++) { + std::shared_ptr dataItem = itemsById[i]; + if (dataItem != NULL) { + size += 1; - size += 1; - - // write value - switch (dataItem->getType()) { - case TYPE_BYTE: - size += 1; - break; - case TYPE_SHORT: - size += 2; - break; - case TYPE_INT: - size += 4; - break; - case TYPE_STRING: - size += (int)dataItem->getValue_wstring().length() + - 2; // Estimate, assuming all ascii chars - break; - case TYPE_ITEMINSTANCE: - // short + byte + short - size += 2 + 1 + 2; // Estimate, assuming all ascii chars - break; - default: - break; + // write value + switch (dataItem->getType()) { + case TYPE_BYTE: + size += 1; + break; + case TYPE_SHORT: + size += 2; + break; + case TYPE_INT: + size += 4; + break; + case TYPE_FLOAT: + size += 4; + break; + case TYPE_STRING: + size += (int)dataItem->getValue_wstring().length() + + 2; // Estimate, assuming all ascii chars + break; + case TYPE_ITEMINSTANCE: + // short + byte + short + size += 2 + 1 + 2; // Estimate, assuming all ascii chars + break; + default: + break; + } } } return size; @@ -431,6 +474,12 @@ SynchedEntityData::DataItem::DataItem(int type, int id, short value) this->dirty = true; } +SynchedEntityData::DataItem::DataItem(int type, int id, float value) + : type(type), id(id) { + this->value_float = value; + this->dirty = true; +} + SynchedEntityData::DataItem::DataItem(int type, int id, const std::wstring& value) : type(type), id(id) { @@ -459,6 +508,10 @@ void SynchedEntityData::DataItem::setValue(short value) { this->value_short = value; } +void SynchedEntityData::DataItem::setValue(float value) { + this->value_float = value; +} + void SynchedEntityData::DataItem::setValue(const std::wstring& value) { this->value_wstring = value; } @@ -472,6 +525,8 @@ int SynchedEntityData::DataItem::getValue_int() { return value_int; } short SynchedEntityData::DataItem::getValue_short() { return value_short; } +float SynchedEntityData::DataItem::getValue_float() { return value_float; } + uint8_t SynchedEntityData::DataItem::getValue_byte() { return value_byte; } std::wstring SynchedEntityData::DataItem::getValue_wstring() { @@ -487,4 +542,4 @@ int SynchedEntityData::DataItem::getType() { return type; } bool SynchedEntityData::DataItem::isDirty() { return dirty; } -void SynchedEntityData::DataItem::setDirty(bool dirty) { this->dirty = dirty; } +void SynchedEntityData::DataItem::setDirty(bool dirty) { this->dirty = dirty; } \ No newline at end of file diff --git a/Minecraft.World/Entities/SyncedEntityData.h b/Minecraft.World/Entities/SyncedEntityData.h index d902f5c7c..8b6a558e0 100644 --- a/Minecraft.World/Entities/SyncedEntityData.h +++ b/Minecraft.World/Entities/SyncedEntityData.h @@ -12,9 +12,12 @@ public: const int id; // 4J - there used to be one "value" type here of general type Object, // just storing the different (used) varieties here separately for us - uint8_t value_byte; - int value_int; - short value_short; + union { + uint8_t value_byte; + int value_int; + short value_short; + float value_float; + }; std::wstring value_wstring; std::shared_ptr value_itemInstance; bool dirty; @@ -27,16 +30,19 @@ public: DataItem(int type, int id, const std::wstring& value); DataItem(int type, int id, std::shared_ptr itemInstance); DataItem(int type, int id, short value); + DataItem(int type, int id, float value); int getId(); void setValue(uint8_t value); void setValue(int value); void setValue(short value); + void setValue(float value); void setValue(const std::wstring& value); void setValue(std::shared_ptr value); uint8_t getValue_byte(); int getValue_int(); short getValue_short(); + float getValue_float(); std::wstring getValue_wstring(); std::shared_ptr getValue_itemInstance(); int getType(); @@ -48,7 +54,6 @@ public: static const int MAX_STRING_DATA_LENGTH = 64; static const int EOF_MARKER = 0x7f; -private: static const int TYPE_BYTE = 0; static const int TYPE_SHORT = 1; static const int TYPE_INT = 2; @@ -69,7 +74,7 @@ private: // the id value must fit in the remaining bits static const int MAX_ID_VALUE = ~TYPE_MASK & 0xff; - std::unordered_map > itemsById; + std::shared_ptr itemsById[MAX_ID_VALUE + 1]; bool m_isDirty; public: @@ -83,6 +88,7 @@ public: void define(int id, const std::wstring& value); void define(int id, int value); void define(int id, short value); + void define(int id, float value); void defineNULL(int id, void* pVal); void checkId(int id); // 4J - added to contain common code from overloaded @@ -98,6 +104,7 @@ public: void set(int id, uint8_t value); void set(int id, int value); void set(int id, short value); + void set(int id, float value); void set(int id, const std::wstring& value); void set(int id, std::shared_ptr); void markDirty(int id); @@ -125,6 +132,7 @@ public: public: void assignValues(std::vector >* items); bool isEmpty(); + void clearDirty(); // 4J Added int getSizeInBytes(); diff --git a/Minecraft.World/Entities/TamableAnimal.cpp b/Minecraft.World/Entities/TamableAnimal.cpp index 3a4693f3c..371ad62ba 100644 --- a/Minecraft.World/Entities/TamableAnimal.cpp +++ b/Minecraft.World/Entities/TamableAnimal.cpp @@ -57,6 +57,7 @@ void TamableAnimal::readAdditionalSaveData(CompoundTag* tag) { setTame(true); } sitGoal->wantToSit(tag->getBoolean(L"Sitting")); + setSitting(tag->getBoolean(L"Sitting")); } void TamableAnimal::spawnTamingParticles(bool success) { @@ -119,8 +120,38 @@ void TamableAnimal::setOwnerUUID(const std::wstring& name) { entityData->set(DATA_OWNERUUID_ID, name); } -std::shared_ptr TamableAnimal::getOwner() { +std::shared_ptr TamableAnimal::getOwner() { return level->getPlayerByUUID(getOwnerUUID()); } -SitGoal* TamableAnimal::getSitGoal() { return sitGoal; } \ No newline at end of file +SitGoal* TamableAnimal::getSitGoal() { return sitGoal; } + +bool TamableAnimal::wantsToAttack(std::shared_ptr target, + std::shared_ptr owner) { + return true; +} + +Team* TamableAnimal::getTeam() { + if (isTame()) { + std::shared_ptr owner = + std::dynamic_pointer_cast(getOwner()); + if (owner != NULL) { + return owner->getTeam(); + } + } + return Animal::getTeam(); +} + +bool TamableAnimal::isAlliedTo(std::shared_ptr other) { + if (isTame()) { + std::shared_ptr owner = + std::dynamic_pointer_cast(getOwner()); + if (other == owner) { + return true; + } + if (owner != NULL) { + return owner->isAlliedTo(other); + } + } + return Animal::isAlliedTo(other); +} \ No newline at end of file diff --git a/Minecraft.World/Entities/TamableAnimal.h b/Minecraft.World/Entities/TamableAnimal.h index b7557eb47..9a9aa12d3 100644 --- a/Minecraft.World/Entities/TamableAnimal.h +++ b/Minecraft.World/Entities/TamableAnimal.h @@ -1,10 +1,11 @@ #pragma once #include "Mobs/Animal.h" +#include "OwnableEntity.h" class SitGoal; -class TamableAnimal : public Animal { +class TamableAnimal : public Animal, public OwnableEntity { protected: static const int DATA_FLAGS_ID = 16; static const int DATA_OWNERUUID_ID = 17; @@ -33,6 +34,10 @@ public: virtual void setSitting(bool value); virtual std::wstring getOwnerUUID(); virtual void setOwnerUUID(const std::wstring& name); - virtual std::shared_ptr getOwner(); + virtual std::shared_ptr getOwner(); virtual SitGoal* getSitGoal(); + bool wantsToAttack(std::shared_ptr target, + std::shared_ptr owner); + Team* getTeam(); + bool isAlliedTo(std::shared_ptr other); }; \ No newline at end of file diff --git a/Minecraft.World/Entities/Throwable.cpp b/Minecraft.World/Entities/Throwable.cpp index 06019f032..5e0165b8e 100644 --- a/Minecraft.World/Entities/Throwable.cpp +++ b/Minecraft.World/Entities/Throwable.cpp @@ -2,6 +2,7 @@ #include "../Headers/net.minecraft.world.phys.h" #include "../Headers/net.minecraft.world.entity.h" #include "../Headers/net.minecraft.world.level.h" +#include "../Headers/net.minecraft.world.level.tile.h" #include "../Headers/com.mojang.nbt.h" #include "Throwable.h" @@ -15,6 +16,7 @@ void Throwable::_throwableInit() { owner = nullptr; life = 0; flightTime = 0; + ownerName = L""; } Throwable::Throwable(Level* level) : Entity(level) { @@ -30,20 +32,20 @@ bool Throwable::shouldRenderAtSqrDistance(double distance) { return distance < size * size; } -Throwable::Throwable(Level* level, std::shared_ptr mob) : Entity(level) { +Throwable::Throwable(Level* level, std::shared_ptr mob) + : Entity(level) { _throwableInit(); - this->owner = mob; + owner = mob; setSize(4 / 16.0f, 4 / 16.0f); - this->moveTo(mob->x, mob->y + mob->getHeadHeight(), mob->z, mob->yRot, - mob->xRot); + moveTo(mob->x, mob->y + mob->getHeadHeight(), mob->z, mob->yRot, mob->xRot); x -= cos(yRot / 180 * PI) * 0.16f; y -= 0.1f; z -= sin(yRot / 180 * PI) * 0.16f; - this->setPos(x, y, z); - this->heightOffset = 0; + setPos(x, y, z); + heightOffset = 0; float speed = 0.4f; xd = (-sin(yRot / 180 * PI) * cos(xRot / 180 * PI)) * speed; @@ -60,8 +62,8 @@ Throwable::Throwable(Level* level, double x, double y, double z) setSize(4 / 16.0f, 4 / 16.0f); - this->setPos(x, y, z); - this->heightOffset = 0; + setPos(x, y, z); + heightOffset = 0; } float Throwable::getThrowPower() { return 1.5f; } @@ -90,8 +92,8 @@ void Throwable::shoot(double xd, double yd, double zd, float pow, float sd = (float)sqrt(xd * xd + zd * zd); - yRotO = this->yRot = (float)(atan2(xd, zd) * 180 / PI); - xRotO = this->xRot = (float)(atan2(yd, (double)sd) * 180 / PI); + yRotO = yRot = (float)(atan2(xd, zd) * 180 / PI); + xRotO = xRot = (float)(atan2(yd, (double)sd) * 180 / PI); life = 0; } @@ -101,8 +103,8 @@ void Throwable::lerpMotion(double xd, double yd, double zd) { this->zd = zd; if (xRotO == 0 && yRotO == 0) { float sd = (float)sqrt(xd * xd + zd * zd); - yRotO = this->yRot = (float)(atan2(xd, zd) * 180 / PI); - xRotO = this->xRot = (float)(atan2(yd, (double)sd) * 180 / PI); + yRotO = yRot = (float)(atan2(xd, zd) * 180 / PI); + xRotO = xRot = (float)(atan2(yd, (double)sd) * 180 / PI); } } @@ -146,8 +148,9 @@ void Throwable::tick() { if (!level->isClientSide) { std::shared_ptr hitEntity = nullptr; std::vector >* objects = level->getEntities( - shared_from_this(), this->bb->expand(xd, yd, zd)->grow(1, 1, 1)); + shared_from_this(), bb->expand(xd, yd, zd)->grow(1, 1, 1)); double nearest = 0; + std::shared_ptr owner = getOwner(); for (int i = 0; i < objects->size(); i++) { std::shared_ptr e = objects->at(i); if (!e->isPickable() || (e == owner && flightTime < 5)) continue; @@ -172,7 +175,12 @@ void Throwable::tick() { } if (res != NULL) { - onHit(res); + if ((res->type == HitResult::TILE) && + (level->getTile(res->x, res->y, res->z) == Tile::portalTile_Id)) { + handleInsidePortal(); + } else { + onHit(res); + } delete res; } x += xd; @@ -221,6 +229,13 @@ void Throwable::addAdditonalSaveData(CompoundTag* tag) { tag->putByte(L"inTile", (uint8_t)lastTile); tag->putByte(L"shake", (uint8_t)shakeTime); tag->putByte(L"inGround", (uint8_t)(inGround ? 1 : 0)); + + if (ownerName.empty() && (owner != NULL) && + owner->instanceof(eTYPE_PLAYER)) { + ownerName = owner->getAName(); + } + + tag->putString(L"ownerName", ownerName.empty() ? L"" : ownerName); } void Throwable::readAdditionalSaveData(CompoundTag* tag) { @@ -230,6 +245,15 @@ void Throwable::readAdditionalSaveData(CompoundTag* tag) { lastTile = tag->getByte(L"inTile") & 0xff; shakeTime = tag->getByte(L"shake") & 0xff; inGround = tag->getByte(L"inGround") == 1; + ownerName = tag->getString(L"ownerName"); + if (ownerName.empty()) ownerName = L""; } -float Throwable::getShadowHeightOffs() { return 0; } \ No newline at end of file +float Throwable::getShadowHeightOffs() { return 0; } + +std::shared_ptr Throwable::getOwner() { + if (owner == NULL && !ownerName.empty()) { + owner = level->getPlayerByName(ownerName); + } + return owner; +} \ No newline at end of file diff --git a/Minecraft.World/Entities/Throwable.h b/Minecraft.World/Entities/Throwable.h index 33d5103ce..fa8c65746 100644 --- a/Minecraft.World/Entities/Throwable.h +++ b/Minecraft.World/Entities/Throwable.h @@ -1,11 +1,12 @@ #pragma once #include "Entity.h" +#include "Projectile.h" class Mob; class HitResult; -class Throwable : public Entity { +class Throwable : public Entity, public Projectile { private: int xTile; int yTile; @@ -18,10 +19,10 @@ protected: public: int shakeTime; -protected: - std::shared_ptr owner; + std::shared_ptr owner; private: + std::wstring ownerName; int life; int flightTime; @@ -36,7 +37,7 @@ protected: public: virtual bool shouldRenderAtSqrDistance(double distance); - Throwable(Level* level, std::shared_ptr mob); + Throwable(Level* level, std::shared_ptr mob); Throwable(Level* level, double x, double y, double z); protected: @@ -57,4 +58,5 @@ public: virtual void addAdditonalSaveData(CompoundTag* tag); virtual void readAdditionalSaveData(CompoundTag* tag); virtual float getShadowHeightOffs(); + virtual std::shared_ptr getOwner(); }; \ No newline at end of file diff --git a/Minecraft.World/Entities/WaterAnimal.cpp b/Minecraft.World/Entities/WaterAnimal.cpp index 0815ed7bd..7cdd2ed31 100644 --- a/Minecraft.World/Entities/WaterAnimal.cpp +++ b/Minecraft.World/Entities/WaterAnimal.cpp @@ -3,6 +3,7 @@ #include "../Headers/net.minecraft.world.level.tile.h" #include "../Headers/net.minecraft.world.phys.h" #include "../Headers/net.minecraft.world.level.h" +#include "../Headers/net.minecraft.world.damagesource.h" #include "WaterAnimal.h" WaterAnimal::WaterAnimal(Level* level) : PathfinderMob(level) { @@ -25,4 +26,20 @@ bool WaterAnimal::removeWhenFarAway() { return true; } int WaterAnimal::getExperienceReward(std::shared_ptr killedBy) { return 1 + level->random->nextInt(3); +} + +void WaterAnimal::baseTick() { + int airSupply = getAirSupply(); + + PathfinderMob::baseTick(); // this modified the airsupply + + if (isAlive() && !isInWater()) { + setAirSupply(--airSupply); + if (getAirSupply() == -20) { + setAirSupply(0); + hurt(DamageSource::drown, 2); + } + } else { + setAirSupply(TOTAL_AIR_SUPPLY); + } } \ No newline at end of file diff --git a/Minecraft.World/Entities/WaterAnimal.h b/Minecraft.World/Entities/WaterAnimal.h index 8fd66160b..953966c81 100644 --- a/Minecraft.World/Entities/WaterAnimal.h +++ b/Minecraft.World/Entities/WaterAnimal.h @@ -14,4 +14,7 @@ public: protected: virtual bool removeWhenFarAway(); virtual int getExperienceReward(std::shared_ptr killedBy); + +public: + virtual void baseTick(); }; diff --git a/Minecraft.World/Entities/WitherSkull.cpp b/Minecraft.World/Entities/WitherSkull.cpp new file mode 100644 index 000000000..81a6a4cca --- /dev/null +++ b/Minecraft.World/Entities/WitherSkull.cpp @@ -0,0 +1,108 @@ +#include "../Platform/stdafx.h" +#include "../Headers/net.minecraft.world.h" +#include "../Headers/net.minecraft.world.damagesource.h" +#include "../Headers/net.minecraft.world.effect.h" +#include "../Headers/net.minecraft.world.entity.h" +#include "../Headers/net.minecraft.world.level.h" +#include "../Headers/net.minecraft.world.level.tile.h" +#include "../Headers/net.minecraft.world.phys.h" +#include "WitherSkull.h" + +WitherSkull::WitherSkull(Level* level) : Fireball(level) { + defineSynchedData(); + + setSize(5 / 16.0f, 5 / 16.0f); +} + +WitherSkull::WitherSkull(Level* level, std::shared_ptr mob, + double xa, double ya, double za) + : Fireball(level, mob, xa, ya, za) { + defineSynchedData(); + + setSize(5 / 16.0f, 5 / 16.0f); +} + +float WitherSkull::getInertia() { + return isDangerous() ? 0.73f : Fireball::getInertia(); +} + +WitherSkull::WitherSkull(Level* level, double x, double y, double z, double xa, + double ya, double za) + : Fireball(level, x, y, z, xa, ya, za) { + defineSynchedData(); + + setSize(5 / 16.0f, 5 / 16.0f); +} + +bool WitherSkull::isOnFire() { return false; } + +float WitherSkull::getTileExplosionResistance(Explosion* explosion, + Level* level, int x, int y, int z, + Tile* tile) { + float result = + Fireball::getTileExplosionResistance(explosion, level, x, y, z, tile); + + if (isDangerous() && tile != Tile::unbreakable && + tile != Tile::endPortalTile && tile != Tile::endPortalFrameTile) { + result = std::min(0.8f, result); + } + + return result; +} + +void WitherSkull::onHit(HitResult* res) { + if (!level->isClientSide) { + if (res->entity != NULL) { + if (owner != NULL) { + DamageSource* damageSource = DamageSource::mobAttack(owner); + if (res->entity->hurt(damageSource, 8)) { + if (!res->entity->isAlive()) { + owner->heal(5); + } + } + delete damageSource; + } else { + res->entity->hurt(DamageSource::magic, 5); + } + if (res->entity->instanceof(eTYPE_LIVINGENTITY)) { + int witherSeconds = 0; + if (level->difficulty <= Difficulty::EASY) { + // Nothing + } else if (level->difficulty == Difficulty::NORMAL) { + witherSeconds = 10; + } else if (level->difficulty == Difficulty::HARD) { + witherSeconds = 40; + } + if (witherSeconds > 0) { + std::dynamic_pointer_cast(res->entity) + ->addEffect(new MobEffectInstance( + MobEffect::wither->id, + SharedConstants::TICKS_PER_SECOND * witherSeconds, + 1)); + } + } + } + level->explode( + shared_from_this(), x, y, z, 1, false, + level->getGameRules()->getBoolean(GameRules::RULE_MOBGRIEFING)); + remove(); + } +} + +bool WitherSkull::isPickable() { return false; } + +bool WitherSkull::hurt(DamageSource* source, float damage) { return false; } + +void WitherSkull::defineSynchedData() { + entityData->define(DATA_DANGEROUS, (uint8_t)0); +} + +bool WitherSkull::isDangerous() { + return entityData->getByte(DATA_DANGEROUS) == 1; +} + +void WitherSkull::setDangerous(bool value) { + entityData->set(DATA_DANGEROUS, value ? (uint8_t)1 : (uint8_t)0); +} + +bool WitherSkull::shouldBurn() { return false; } \ No newline at end of file diff --git a/Minecraft.World/Entities/WitherSkull.h b/Minecraft.World/Entities/WitherSkull.h new file mode 100644 index 000000000..3ab4d7846 --- /dev/null +++ b/Minecraft.World/Entities/WitherSkull.h @@ -0,0 +1,45 @@ +#pragma once + +#include "Mobs/Fireball.h" + +class WitherSkull : public Fireball { +public: + eINSTANCEOF GetType() { return eTYPE_WITHER_SKULL; } + static Entity* create(Level* level) { return new WitherSkull(level); } + +private: + static const int DATA_DANGEROUS = 10; + +public: + WitherSkull(Level* level); + WitherSkull(Level* level, std::shared_ptr mob, double xa, + double ya, double za); + +protected: + virtual float getInertia(); + +public: + WitherSkull(Level* level, double x, double y, double z, double xa, + double ya, double za); + + virtual bool isOnFire(); + virtual float getTileExplosionResistance(Explosion* explosion, Level* level, + int x, int y, int z, Tile* tile); + +protected: + virtual void onHit(HitResult* res); + +public: + virtual bool isPickable(); + virtual bool hurt(DamageSource* source, float damage); + +protected: + virtual void defineSynchedData(); + +public: + virtual bool isDangerous(); + virtual void setDangerous(bool value); + +protected: + virtual bool shouldBurn(); // 4J Added. +}; \ No newline at end of file