From db05394d99d69175c0caff0a1c2a26151c9d4855 Mon Sep 17 00:00:00 2001 From: hondacrx Date: Thu, 29 Feb 2024 17:08:30 -0500 Subject: [PATCH] Core/Creatures: implemented most movement related static flags and migrated existing movement data into static flags * implemented CREATURE_STATIC_FLAG_AQUATIC - creatures cannot leave liquids * implemented CREATURE_STATIC_FLAG_AMPHIBIOUS - creatures can enter and leave liquids but remain on the ocean floor when swimming is not enabled until engaged * implemented CREATURE_STATIC_FLAG_FLOATING - creatures have their gravity on spawn / reset disabled * implemented CREATURE_STATIC_FLAG_SESSILE - creatures are rooted in place * implemented CREATURE_STATIC_FLAG_CAN_SWIM - creature can swim in liquids * implemented CREATURE_STATIC_FLAG_3_CANNOT_SWIM - Amphibious creatures cannot toggle on swimming * implemented CREATURE_STATIC_FLAG_3_CANNOT_TURN - Creatures cannot turn at all * implemented CREATURE_STATIC_FLAG_4_PREVENT_SWIM - Amphibious creatures won't toggle on swimming until their victim starts leaving the ocean floor * partially implemented CREATURE_STATIC_FLAG_3_CANNOT_PENETRATE_WATER * deprecated CREATURE_FLAG_EXTRA_NO_MOVE_FLAGS_UPDATE as this flag was a hackfix to a wrong implementation that is now gone Port From (https://github.com/TrinityCore/TrinityCore/commit/c541eda54d7e0dddeec329a6beac2948e0b0a40b) --- Source/Framework/Constants/CreatureConst.cs | 30 +--- .../Database/Databases/WorldDatabase.cs | 2 +- .../Game/Entities/Creature/Creature.Fields.cs | 2 - Source/Game/Entities/Creature/Creature.cs | 139 ++++++------------ Source/Game/Entities/Creature/CreatureData.cs | 18 +-- Source/Game/Entities/Object/WorldObject.cs | 13 +- Source/Game/Entities/Unit/Unit.Movement.cs | 29 +++- Source/Game/Entities/Unit/Unit.Pets.cs | 4 - Source/Game/Globals/ObjectManager.cs | 76 ++++------ .../Game/Movement/Generators/HomeMovement.cs | 4 +- .../Game/Movement/Generators/PathGenerator.cs | 4 +- Source/Game/Spells/Auras/AuraEffect.cs | 10 +- .../world/master/2024_02_29_00_world.sql | 80 ++++++++++ 13 files changed, 207 insertions(+), 204 deletions(-) create mode 100644 sql/updates/world/master/2024_02_29_00_world.sql diff --git a/Source/Framework/Constants/CreatureConst.cs b/Source/Framework/Constants/CreatureConst.cs index f599d2056..6b3d39103 100644 --- a/Source/Framework/Constants/CreatureConst.cs +++ b/Source/Framework/Constants/CreatureConst.cs @@ -72,7 +72,7 @@ namespace Framework.Constants Disallowed = (ServerControlled | NonAttackable | RemoveClientControl | PlayerControlled | Rename | Preparation | /* UNIT_FLAG_UNK_6 | */ NotAttackable1 | Looting | PetInCombat | PvpEnabling | - NonAttackable2 | Pacified | Stunned | + CantSwim | CanSwim | NonAttackable2 | Pacified | Stunned | InCombat | OnTaxi | Disarmed | Confused | Fleeing | Possessed | Skinnable | Mount | Unk28 | PreventEmotesFromChatText | Sheathe | Immune), @@ -118,7 +118,7 @@ namespace Framework.Constants Disallowed = (FeignDeath | IgnoreReputation | ComprehendLang | MirrorImage | ForceMovement | DisarmOffhand | DisablePredStats | AllowChangingTalents | DisarmRanged | - /* UNIT_FLAG2_REGENERATE_POWER | */ RestrictPartyInteraction | + /* UNIT_FLAG2_REGENERATE_POWER | */ RestrictPartyInteraction | CannotTurn | PreventSpellClick | /*InteractWhileHostile | */ /* Unk2 | */ /* UNIT_FLAG2_PLAY_DEATH_ANIM | */ AllowCheatSpells | SuppressHighlightWhenTargetedOrMousedOver | TreatAsRaidUnitForHelpfulSpells | LargeAoi | GiganticAoi | NoActions | @@ -278,7 +278,7 @@ namespace Framework.Constants ImmuneToPc = 0x00000020, // UnitFlagImmuneToPc ImmuneToNpc = 0x00000040, // UnitFlagImmuneToNpc CanWieldLoot = 0x00000080, - Sessile = 0x00000100, // CreatureTemplateMovement.Rooted = 1 + Sessile = 0x00000100, // Rooted movementflag, creature is permanently rooted in place Uninteractible = 0x00000200, // UnitFlagUninteractible NoAutomaticRegen = 0x00000400, // Creatures With That Flag Uses No UnitFlag2RegeneratePower DespawnInstantly = 0x00000800, // Creature Instantly Disappear When Killed @@ -289,7 +289,7 @@ namespace Framework.Constants BossMob = 0x00010000, // CreatureTypeFlagBossMob, Original Description: Raid Boss Mob CombatPing = 0x00020000, Aquatic = 0x00040000, // Aka Water Only, CreatureTemplateMovement.Ground = 0 - Amphibious = 0x00080000, // CreatureTemplateMovement.Swim = 1 + Amphibious = 0x00080000, // Creatures will be able to enter and leave water but can only move on the ocean floor when CREATURE_STATIC_FLAG_CAN_SWIM is not present NoMeleeFlee = 0x00100000, // "No Melee (Flee)" Prevents melee (moves as-if feared, does not make creature passive) VisibleToGhosts = 0x00200000, // CreatureTypeFlagVisibleToGhosts PvpEnabling = 0x00400000, // Old UnitFlagPvpEnabling, Now UnitBytes2OffsetPvpFlag From UnitFieldBytes2 @@ -299,7 +299,7 @@ namespace Framework.Constants OnlyAttackPvpEnabling = 0x04000000, // "Only Attack Targets That Are Pvp Enabling" CallsGuards = 0x08000000, // Creature Will Summon A Guard If Player Is Within Its Aggro Range (Even If Creature Doesn'T Attack Per Se) CanSwim = 0x10000000, // Unitflags 0x8000 UnitFlagCanSwim - Floating = 0x20000000, // CreatureTemplateMovement.Flight = 1 + Floating = 0x20000000, // sets DisableGravity movementflag on spawn/reset MoreAudible = 0x40000000, // CreatureTypeFlagMoreAudible LargeAoi = 0x80000000 // Unitflags2 0x200000 } @@ -526,7 +526,7 @@ namespace Framework.Constants NoXP = 0x40, // creature kill does not provide XP Trigger = 0x80, // Trigger Creature NoTaunt = 0x100, // Creature Is Immune To Taunt Auras And 'attack me' effects - NoMoveFlagsUpdate = 0x200, // Creature won't update movement flags + Unused9 = 0x200, GhostVisibility = 0x400, // creature will only be visible to dead players UseOffhandAttack = 0x800, // creature will use offhand attacks NoSellVendor = 0x1000, // players can't sell items to this vendor @@ -704,24 +704,6 @@ namespace Framework.Constants IdleInFormation = 0x200, // The member will follow the leader when pathing idly } - public enum CreatureGroundMovementType - { - None, - Run, - Hover, - - Max - } - - public enum CreatureFlightMovementType - { - None, - DisableGravity, - CanFly, - - Max - } - public enum CreatureChaseMovementType { Run, diff --git a/Source/Framework/Database/Databases/WorldDatabase.cs b/Source/Framework/Database/Databases/WorldDatabase.cs index 16cc9dd7c..a3882c1f4 100644 --- a/Source/Framework/Database/Databases/WorldDatabase.cs +++ b/Source/Framework/Database/Databases/WorldDatabase.cs @@ -46,7 +46,7 @@ namespace Framework.Database PrepareStatement(WorldStatements.SEL_CREATURE_ADDON_BY_GUID, "SELECT guid FROM creature_addon WHERE guid = ?"); PrepareStatement(WorldStatements.DEL_CREATURE, "DELETE FROM creature WHERE guid = ?"); PrepareStatement(WorldStatements.SEL_COMMANDS, "SELECT name, help FROM command"); - PrepareStatement(WorldStatements.SEL_CREATURE_TEMPLATE, "SELECT entry, KillCredit1, KillCredit2, name, femaleName, subname, TitleAlt, IconName, RequiredExpansion, VignetteID, faction, npcflag, speed_walk, speed_run, scale, Classification, dmgschool, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, unit_flags3, family, trainer_class, type, VehicleId, AIName, MovementType, ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, ExperienceModifier, RacialLeader, movementId, WidgetSetID, WidgetSetUnitConditionID, RegenHealth, CreatureImmunitiesId, flags_extra, ScriptName, StringId FROM creature_template ct LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId WHERE entry = ? OR 1 = ?"); + PrepareStatement(WorldStatements.SEL_CREATURE_TEMPLATE, "SELECT entry, KillCredit1, KillCredit2, name, femaleName, subname, TitleAlt, IconName, RequiredExpansion, VignetteID, faction, npcflag, speed_walk, speed_run, scale, Classification, dmgschool, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, unit_flags3, family, trainer_class, type, VehicleId, AIName, MovementType, ctm.HoverInitiallyEnabled, ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, ExperienceModifier, RacialLeader, movementId, WidgetSetID, WidgetSetUnitConditionID, RegenHealth, CreatureImmunitiesId, flags_extra, ScriptName, StringId FROM creature_template ct LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId WHERE entry = ? OR 1 = ?"); PrepareStatement(WorldStatements.SEL_CREATURE_BY_ID, "SELECT guid FROM creature WHERE id = ?"); PrepareStatement(WorldStatements.SEL_GAMEOBJECT_NEAREST, "SELECT guid, id, position_x, position_y, position_z, map, (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) AS order_ FROM gameobject WHERE map = ? AND (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) <= ? ORDER BY order_"); PrepareStatement(WorldStatements.SEL_CREATURE_NEAREST, "SELECT guid, id, position_x, position_y, position_z, map, (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) AS order_ FROM creature WHERE map = ? AND (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) <= ? ORDER BY order_"); diff --git a/Source/Game/Entities/Creature/Creature.Fields.cs b/Source/Game/Entities/Creature/Creature.Fields.cs index 963fd3a9d..5efcb7ac9 100644 --- a/Source/Game/Entities/Creature/Creature.Fields.cs +++ b/Source/Game/Entities/Creature/Creature.Fields.cs @@ -27,8 +27,6 @@ namespace Game.Entities // Regenerate health bool _regenerateHealth; // Set on creation - bool _isMissingCanSwimFlagOutOfCombat; - uint? _defaultMountDisplayIdOverride; int _creatureImmunitiesId; uint _gossipMenuId; diff --git a/Source/Game/Entities/Creature/Creature.cs b/Source/Game/Entities/Creature/Creature.cs index 775079475..347bf2088 100644 --- a/Source/Game/Entities/Creature/Creature.cs +++ b/Source/Game/Entities/Creature/Creature.cs @@ -306,7 +306,7 @@ namespace Game.Entities SetFaction(cInfo.Faction); - ObjectManager.ChooseCreatureFlags(cInfo, out ulong npcFlags, out uint unitFlags, out uint unitFlags2, out uint unitFlags3, data); + ObjectManager.ChooseCreatureFlags(cInfo, out ulong npcFlags, out uint unitFlags, out uint unitFlags2, out uint unitFlags3, _staticFlags, data); if (cInfo.FlagsExtra.HasAnyFlag(CreatureFlagsExtra.Worldevent)) npcFlags |= Global.GameEventMgr.GetNPCFlag(this); @@ -399,7 +399,7 @@ namespace Game.Entities SetIsCombatDisallowed(cInfo.FlagsExtra.HasFlag(CreatureFlagsExtra.CannotEnterCombat)); - InitializeMovementFlags(); + InitializeMovementCapabilities(); LoadCreaturesAddon(); LoadCreaturesSparringHealth(); @@ -419,7 +419,6 @@ namespace Game.Entities _staticFlags = flags; // Apply all other side effects of flag changes - SetTemplateRooted(flags.HasFlag(CreatureStaticFlags.Sessile)); m_updateFlag.NoBirthAnim = flags.HasFlag(CreatureStaticFlags4.NoBirthAnim); } @@ -437,7 +436,7 @@ namespace Game.Entities GetAI().JustAppeared(); } - UpdateMovementFlags(); + UpdateMovementCapabilities(); switch (m_deathState) { @@ -1062,8 +1061,6 @@ namespace Game.Entities if (!HasFlag(CreatureStaticFlags2.AllowMountedCombat)) Dismount(); - RefreshCanSwimFlag(); - if (IsPet() || IsGuardian()) // update pets' speed for catchup OOC speed { UpdateSpeed(UnitMoveType.Run); @@ -1783,19 +1780,6 @@ namespace Game.Entities SetHealth((m_deathState == DeathState.Alive || m_deathState == DeathState.JustRespawned) ? curhealth : 0); } - void LoadTemplateRoot() - { - SetTemplateRooted(GetMovementTemplate().IsRooted()); - } - - public bool IsTemplateRooted() { return _staticFlags.HasFlag(CreatureStaticFlags.Sessile); } - - public void SetTemplateRooted(bool rooted) - { - _staticFlags.ApplyFlag(CreatureStaticFlags.Sessile, rooted); - SetControlled(rooted, UnitState.Root); - } - public override bool HasQuest(uint questId) { return Global.ObjectMgr.GetCreatureQuestRelations(GetEntry()).HasQuest(questId); @@ -2021,7 +2005,7 @@ namespace Game.Entities if (m_formation != null && m_formation.GetLeader() == this) m_formation.FormationReset(true); - bool needsFalling = (IsFlying() || IsHovering()) && !IsUnderWater(); + bool needsFalling = (IsFlying() || IsHovering()) && !IsUnderWater() && !HasUnitState(UnitState.Root); SetHover(false, false); SetDisableGravity(false, false); @@ -2041,7 +2025,7 @@ namespace Game.Entities ResetPlayerDamageReq(); SetCannotReachTarget(false); - UpdateMovementFlags(); + UpdateMovementCapabilities(); ClearUnitState(UnitState.AllErasable); @@ -2050,7 +2034,7 @@ namespace Game.Entities CreatureData creatureData = GetCreatureData(); CreatureTemplate cInfo = GetCreatureTemplate(); - ObjectManager.ChooseCreatureFlags(cInfo, out ulong npcFlags, out uint unitFlags, out uint unitFlags2, out uint unitFlags3, creatureData); + ObjectManager.ChooseCreatureFlags(cInfo, out ulong npcFlags, out uint unitFlags, out uint unitFlags2, out uint unitFlags3, _staticFlags, creatureData); if (cInfo.FlagsExtra.HasAnyFlag(CreatureFlagsExtra.Worldevent)) npcFlags |= Global.GameEventMgr.GetNPCFlag(this); @@ -2500,7 +2484,7 @@ namespace Game.Entities dist += GetCombatReach() + victim.GetCombatReach(); // to prevent creatures in air ignore attacks because distance is already too high... - if (GetMovementTemplate().IsFlightAllowed()) + if (CanFly()) return victim.IsInDist2d(m_homePosition, dist); else return victim.IsInDist(m_homePosition, dist); @@ -2525,23 +2509,15 @@ namespace Game.Entities CreatureAddon creatureAddon = GetCreatureAddon(); if (creatureAddon == null) return false; - + uint mountDisplayId = _defaultMountDisplayIdOverride.GetValueOrDefault(creatureAddon.mount); - if ( mountDisplayId != 0) + if (mountDisplayId != 0) Mount(creatureAddon.mount); SetStandState((UnitStandStateType)creatureAddon.standState); ReplaceAllVisFlags((UnitVisFlags)creatureAddon.visFlags); SetAnimTier((AnimTier)creatureAddon.animTier, false); - //! Suspected correlation between UNIT_FIELD_BYTES_1, offset 3, value 0x2: - //! If no inhabittype_fly (if no MovementFlag_DisableGravity or MovementFlag_CanFly flag found in sniffs) - //! Check using InhabitType as movement flags are assigned dynamically - //! basing on whether the creature is in air or not - //! Set MovementFlag_Hover. Otherwise do nothing. - if (CanHover()) - AddUnitMovementFlag(MovementFlag.Hover); - SetSheath((SheathState)creatureAddon.sheathState); ReplaceAllPvpFlags((UnitPVPStateFlags)creatureAddon.pvpFlags); @@ -2686,52 +2662,38 @@ namespace Game.Entities bool IsSpawnedOnTransport() { return m_creatureData != null && m_creatureData.MapId != GetMapId(); } - void InitializeMovementFlags() + public void InitializeMovementCapabilities() { - LoadTemplateRoot(); + SetHover(GetMovementTemplate().IsHoverInitiallyEnabled()); + SetDisableGravity(IsFloating()); + SetControlled(IsSessile(), UnitState.Root); - // It does the same, for now - UpdateMovementFlags(); + // If an amphibious creatures was swimming while engaged, disable swimming again + if (IsAmphibious() && !_staticFlags.HasFlag(CreatureStaticFlags.CanSwim)) + RemoveUnitFlag(UnitFlags.CanSwim); + + UpdateMovementCapabilities(); } - public void UpdateMovementFlags() + public void UpdateMovementCapabilities() { // Do not update movement flags if creature is controlled by a player (charm/vehicle) if (m_playerMovingMe != null) return; - // Creatures with CREATURE_FLAG_EXTRA_NO_MOVE_FLAGS_UPDATE should control MovementFlags in your own scripts - if (GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.NoMoveFlagsUpdate)) - return; - // Set the movement flags if the creature is in that mode. (Only fly if actually in air, only swim if in water, etc) float ground = GetFloorZ(); - bool canHover = CanHover(); - bool isInAir = (MathFunctions.fuzzyGt(GetPositionZ(), ground + (canHover ? m_unitData.HoverHeight : 0.0f) + MapConst.GroundHeightTolerance) || MathFunctions.fuzzyLt(GetPositionZ(), ground - MapConst.GroundHeightTolerance)); // Can be underground too, prevent the falling - - if (GetMovementTemplate().IsFlightAllowed() && (isInAir || !GetMovementTemplate().IsGroundAllowed()) && !IsFalling()) - { - if (GetMovementTemplate().Flight == CreatureFlightMovementType.CanFly) - SetCanFly(true); - else - SetDisableGravity(true); - - if (!HasAuraType(AuraType.Hover) && GetMovementTemplate().Ground != CreatureGroundMovementType.Hover) - SetHover(false); - } - else - { - SetCanFly(false); - SetDisableGravity(false); - if (IsAlive() && (CanHover() || HasAuraType(AuraType.Hover))) - SetHover(true); - } - + bool isInAir = (MathFunctions.fuzzyGt(GetPositionZ(), ground + GetHoverOffset() + MapConst.GroundHeightTolerance) || MathFunctions.fuzzyLt(GetPositionZ(), ground - MapConst.GroundHeightTolerance)); // Can be underground too, prevent the falling if (!isInAir) SetFall(false); - SetSwim(CanSwim() && IsInWater()); + // Some Amphibious creatures toggle swimming while engaged + if (IsAmphibious() && !HasUnitFlag(UnitFlags.CantSwim) && !HasUnitFlag(UnitFlags.CanSwim)) + if (!IsSwimPrevented() || (GetVictim() != null && !GetVictim().IsOnOceanFloor())) + SetUnitFlag(UnitFlags.CanSwim); + + SetSwim(IsInWater() && CanSwim()); } public CreatureMovementData GetMovementTemplate() @@ -2754,29 +2716,7 @@ namespace Game.Entities return false; } - public override bool CanEnterWater() - { - if (CanSwim()) - return true; - - return GetMovementTemplate().IsSwimAllowed(); - } - - public void RefreshCanSwimFlag(bool recheck = false) - { - if (!_isMissingCanSwimFlagOutOfCombat || recheck) - _isMissingCanSwimFlagOutOfCombat = !HasUnitFlag(UnitFlags.CanSwim); - - // Check if the creature has UNIT_FLAG_CAN_SWIM and add it if it's missing - // Creatures must be able to chase a target in water if they can enter water - if (_isMissingCanSwimFlagOutOfCombat && CanEnterWater()) - SetUnitFlag(UnitFlags.CanSwim); - } - - public bool HasCanSwimFlagOutOfCombat() - { - return !_isMissingCanSwimFlagOutOfCombat; - } + public override bool CanEnterWater() { return CanSwim() || IsAmphibious(); } public void AllLootRemovedFromCorpse() { @@ -3384,9 +3324,28 @@ namespace Game.Entities return GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.Guard); } - public bool CanWalk() { return GetMovementTemplate().IsGroundAllowed(); } - public override bool CanFly() { return GetMovementTemplate().IsFlightAllowed() || IsFlying(); } - bool CanHover() { return GetMovementTemplate().Ground == CreatureGroundMovementType.Hover || IsHovering(); } + // Returns true if CREATURE_STATIC_FLAG_AQUATIC is set which strictly binds the creature to liquids + public bool IsAquatic() { return _staticFlags.HasFlag(CreatureStaticFlags.Aquatic); } + + // Returns true if CREATURE_STATIC_FLAG_AMPHIBIOUS is set which allows a creature to enter and leave liquids while sticking to the ocean floor. These creatures will become able to swim when engaged + public bool IsAmphibious() { return _staticFlags.HasFlag(CreatureStaticFlags.Amphibious); } + + // Returns true if CREATURE_STATIC_FLAG_FLOATING is set which is disabling the gravity of the creature on spawn and reset + public bool IsFloating() { return _staticFlags.HasFlag(CreatureStaticFlags.Floating); } + + // Returns true if CREATURE_STATIC_FLAG_SESSILE is set which permanently roots the creature in place + public bool IsSessile() { return _staticFlags.HasFlag(CreatureStaticFlags.Sessile); } + + // Returns true if CREATURE_STATIC_FLAG_3_CANNOT_PENETRATE_WATER is set which does not allow the creature to go below liquid surfaces + public bool CannotPenetrateWater() { return _staticFlags.HasFlag(CreatureStaticFlags3.CannotPenetrateWater); } + + // Returns true if CREATURE_STATIC_FLAG_3_CANNOT_SWIM is set which prevents 'Amphibious' creatures from swimming when engaged + public bool IsSwimDisabled() { return _staticFlags.HasFlag(CreatureStaticFlags3.CannotSwim); } + + // Returns true if CREATURE_STATIC_FLAG_4_PREVENT_SWIM is set which prevents 'Amphibious' creatures from swimming when engaged until the victim is no longer on the ocean floor + public bool IsSwimPrevented() { return _staticFlags.HasFlag(CreatureStaticFlags4.PreventSwim); } + + public override bool CanFly() { return IsFlying() || HasUnitMovementFlag(MovementFlag.CanFly); } public bool IsDungeonBoss() { return (GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.DungeonBoss)); } public override bool IsAffectedByDiminishingReturns() { return base.IsAffectedByDiminishingReturns() || GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.AllDiminish); } diff --git a/Source/Game/Entities/Creature/CreatureData.cs b/Source/Game/Entities/Creature/CreatureData.cs index ed167b757..4a8234089 100644 --- a/Source/Game/Entities/Creature/CreatureData.cs +++ b/Source/Game/Entities/Creature/CreatureData.cs @@ -279,38 +279,28 @@ namespace Game.Entities public class CreatureMovementData { - public CreatureGroundMovementType Ground; - public CreatureFlightMovementType Flight; - public bool Swim; - public bool Rooted; + public bool HoverInitiallyEnabled; public CreatureChaseMovementType Chase; public CreatureRandomMovementType Random; public uint InteractionPauseTimer; public CreatureMovementData() { - Ground = CreatureGroundMovementType.Run; - Flight = CreatureFlightMovementType.None; - Swim = true; - Rooted = false; Chase = CreatureChaseMovementType.Run; Random = CreatureRandomMovementType.Walk; InteractionPauseTimer = WorldConfig.GetUIntValue(WorldCfg.CreatureStopForPlayer); } - public bool IsGroundAllowed() { return Ground != CreatureGroundMovementType.None; } - public bool IsSwimAllowed() { return Swim; } - public bool IsFlightAllowed() { return Flight != CreatureFlightMovementType.None; } - public bool IsRooted() { return Rooted; } - public CreatureChaseMovementType GetChase() { return Chase; } public CreatureRandomMovementType GetRandom() { return Random; } + public bool IsHoverInitiallyEnabled() { return HoverInitiallyEnabled; } + public uint GetInteractionPauseTimer() { return InteractionPauseTimer; } public override string ToString() { - return $"Ground: {Ground}, Swim: {Swim}, Flight: {Flight} {(Rooted ? ", Rooted" : "")}, Chase: {Chase}, Random: {Random}, InteractionPauseTimer: {InteractionPauseTimer}"; + return $"HoverInitiallyEnabled: {HoverInitiallyEnabled}, Chase: {Chase}, Random: {Random}, InteractionPauseTimer: {InteractionPauseTimer}"; } } diff --git a/Source/Game/Entities/Object/WorldObject.cs b/Source/Game/Entities/Object/WorldObject.cs index 2da5de746..1d70f67a4 100644 --- a/Source/Game/Entities/Object/WorldObject.cs +++ b/Source/Game/Entities/Object/WorldObject.cs @@ -3752,7 +3752,18 @@ namespace Game.Entities public float GetMapWaterOrGroundLevel(float x, float y, float z, ref float ground) { - return GetMap().GetWaterOrGroundLevel(GetPhaseShift(), x, y, z, ref ground, IsTypeMask(TypeMask.Unit) ? !ToUnit().HasAuraType(AuraType.WaterWalk) : false, GetCollisionHeight()); + bool swimming = true; + Creature creature = ToCreature(); + if (creature != null) + swimming = !creature.CannotPenetrateWater() && !creature.HasAuraType(AuraType.WaterWalk); + else + { + Unit unit = ToUnit(); + if (unit != null) + swimming = !unit.HasAuraType(AuraType.WaterWalk); + } + + return GetMap().GetWaterOrGroundLevel(GetPhaseShift(), x, y, z, ref ground, swimming, GetCollisionHeight()); } public float GetMapHeight(float x, float y, float z, bool vmap = true, float distanceToSearch = MapConst.DefaultHeightSearch) diff --git a/Source/Game/Entities/Unit/Unit.Movement.cs b/Source/Game/Entities/Unit/Unit.Movement.cs index 8e3de302a..673805074 100644 --- a/Source/Game/Entities/Unit/Unit.Movement.cs +++ b/Source/Game/Entities/Unit/Unit.Movement.cs @@ -750,7 +750,7 @@ namespace Game.Entities SendMessageToSet(packet, true); } - if (IsCreature() && updateAnimTier && IsAlive() && !HasUnitState(UnitState.Root) && !ToCreature().IsTemplateRooted()) + if (IsCreature() && updateAnimTier && IsAlive() && !HasUnitState(UnitState.Root)) { if (IsGravityDisabled()) SetAnimTier(AnimTier.Fly); @@ -1138,7 +1138,7 @@ namespace Game.Entities SendMessageToSet(packet, true); } - if (IsCreature() && updateAnimTier && IsAlive() && !HasUnitState(UnitState.Root) && !ToCreature().IsTemplateRooted()) + if (IsCreature() && updateAnimTier && IsAlive() && !HasUnitState(UnitState.Root)) { if (IsGravityDisabled()) SetAnimTier(AnimTier.Fly); @@ -1186,12 +1186,25 @@ namespace Game.Entities { return IsWithinDistInMap(target, distance) && !HasInArc(MathFunctions.TwoPi - arc, target); } - public bool IsInAccessiblePlaceFor(Creature c) + public bool IsInAccessiblePlaceFor(Creature creature) { - if (IsInWater()) - return c.CanEnterWater(); - else - return c.CanWalk() || c.CanFly(); + // Aquatic creatures are not allowed to leave liquids + if (!IsInWater() && creature.IsAquatic()) + return false; + + // Underwater special case. Some creatures may not go below liquid surfaces + if (IsUnderWater() && creature.CannotPenetrateWater()) + return false; + + // Water checks + if (IsInWater() && !creature.CanEnterWater()) + return false; + + // Some creatures are tied to the ocean floor and cannot chase swimming targets. + if (!IsOnOceanFloor() && creature.IsUnderWater() && creature.HasUnitFlag(UnitFlags.CantSwim)) + return false; + + return true; } public void NearTeleportTo(float x, float y, float z, float orientation, bool casting = false) { NearTeleportTo(new Position(x, y, z, orientation), casting); } @@ -1274,7 +1287,7 @@ namespace Game.Entities SetStunned(false); break; case UnitState.Root: - if (HasAuraType(AuraType.ModRoot) || HasAuraType(AuraType.ModRoot2) || HasAuraType(AuraType.ModRootDisableGravity) || GetVehicle() != null || (IsCreature() && ToCreature().IsTemplateRooted())) + if (HasAuraType(AuraType.ModRoot) || HasAuraType(AuraType.ModRoot2) || HasAuraType(AuraType.ModRootDisableGravity) || GetVehicle() != null || (IsCreature() && ToCreature().IsSessile())) return; ClearUnitState(state); diff --git a/Source/Game/Entities/Unit/Unit.Pets.cs b/Source/Game/Entities/Unit/Unit.Pets.cs index 747fd872c..aa4edb388 100644 --- a/Source/Game/Entities/Unit/Unit.Pets.cs +++ b/Source/Game/Entities/Unit/Unit.Pets.cs @@ -412,10 +412,6 @@ namespace Game.Entities AddUnitState(UnitState.Charmed); - Creature creature = ToCreature(); - if (creature != null) - creature.RefreshCanSwimFlag(); - if (!IsPlayer() || !charmer.IsPlayer()) { // AI will schedule its own change if appropriate diff --git a/Source/Game/Globals/ObjectManager.cs b/Source/Game/Globals/ObjectManager.cs index d06a57b70..f993feebd 100644 --- a/Source/Game/Globals/ObjectManager.cs +++ b/Source/Game/Globals/ObjectManager.cs @@ -84,11 +84,20 @@ namespace Game return cinfo.GetFirstInvisibleModel(); } - public static void ChooseCreatureFlags(CreatureTemplate cInfo, out ulong npcFlag, out uint unitFlags, out uint unitFlags2, out uint unitFlags3, CreatureData data = null) + public static void ChooseCreatureFlags(CreatureTemplate cInfo, out ulong npcFlag, out uint unitFlags, out uint unitFlags2, out uint unitFlags3, CreatureStaticFlagsHolder staticFlags, CreatureData data = null) { npcFlag = data != null && data.npcflag.HasValue ? data.npcflag.Value : cInfo.Npcflag; + unitFlags = data != null && data.unit_flags.HasValue ? data.unit_flags.Value : (uint)cInfo.UnitFlags; + if (staticFlags.HasFlag(CreatureStaticFlags.CanSwim)) + unitFlags |= (uint)UnitFlags.CanSwim; + if (staticFlags.HasFlag(CreatureStaticFlags3.CannotSwim)) + unitFlags |= (uint)UnitFlags.CantSwim; + unitFlags2 = data != null && data.unit_flags2.HasValue ? data.unit_flags2.Value : cInfo.UnitFlags2; + if (staticFlags.HasFlag(CreatureStaticFlags3.CannotTurn)) + unitFlags2 |= (uint)UnitFlags2.CannotTurn; + unitFlags3 = data != null && data.unit_flags3.HasValue ? data.unit_flags3.Value : cInfo.UnitFlags3; } @@ -1814,36 +1823,27 @@ namespace Game creature.MovementType = fields.Read(30); if (!fields.IsNull(31)) - creature.Movement.Ground = (CreatureGroundMovementType)fields.Read(31); + creature.Movement.HoverInitiallyEnabled = fields.Read(31); if (!fields.IsNull(32)) - creature.Movement.Swim = fields.Read(32); + creature.Movement.Chase = (CreatureChaseMovementType)fields.Read(32); if (!fields.IsNull(33)) - creature.Movement.Flight = (CreatureFlightMovementType)fields.Read(33); + creature.Movement.Random = (CreatureRandomMovementType)fields.Read(33); if (!fields.IsNull(34)) - creature.Movement.Rooted = fields.Read(34); + creature.Movement.InteractionPauseTimer = fields.Read(34); - if (!fields.IsNull(35)) - creature.Movement.Chase = (CreatureChaseMovementType)fields.Read(35); - - if (!fields.IsNull(36)) - creature.Movement.Random = (CreatureRandomMovementType)fields.Read(36); - - if (!fields.IsNull(37)) - creature.Movement.InteractionPauseTimer = fields.Read(37); - - creature.ModExperience = fields.Read(38); - creature.RacialLeader = fields.Read(39); - creature.MovementId = fields.Read(40); - creature.WidgetSetID = fields.Read(41); - creature.WidgetSetUnitConditionID = fields.Read(42); - creature.RegenHealth = fields.Read(43); - creature.CreatureImmunitiesId = fields.Read(44); - creature.FlagsExtra = (CreatureFlagsExtra)fields.Read(45); - creature.ScriptID = GetScriptId(fields.Read(46)); - creature.StringId = fields.Read(47); + creature.ModExperience = fields.Read(35); + creature.RacialLeader = fields.Read(36); + creature.MovementId = fields.Read(37); + creature.WidgetSetID = fields.Read(38); + creature.WidgetSetUnitConditionID = fields.Read(39); + creature.RegenHealth = fields.Read(40); + creature.CreatureImmunitiesId = fields.Read(41); + creature.FlagsExtra = (CreatureFlagsExtra)fields.Read(42); + creature.ScriptID = GetScriptId(fields.Read(43)); + creature.StringId = fields.Read(44); creatureTemplateStorage[entry] = creature; } @@ -2504,7 +2504,7 @@ namespace Game creatureMovementOverrides.Clear(); // Load the data from creature_movement_override and if NULL fallback to creature_template_movement - SQLResult result = DB.World.Query("SELECT cmo.SpawnId,COALESCE(cmo.Ground, ctm.Ground),COALESCE(cmo.Swim, ctm.Swim),COALESCE(cmo.Flight, ctm.Flight),COALESCE(cmo.Rooted, ctm.Rooted),COALESCE(cmo.Chase, ctm.Chase),COALESCE(cmo.Random, ctm.Random)," + + SQLResult result = DB.World.Query("SELECT cmo.SpawnId,COALESCE(cmo.HoverInitiallyEnabled, ctm.HoverInitiallyEnabled),COALESCE(cmo.Chase, ctm.Chase),COALESCE(cmo.Random, ctm.Random)," + "COALESCE(cmo.InteractionPauseTimer, ctm.InteractionPauseTimer) FROM creature_movement_override AS cmo LEFT JOIN creature AS c ON c.guid = cmo.SpawnId LEFT JOIN creature_template_movement AS ctm ON ctm.CreatureId = c.id"); if (result.IsEmpty()) @@ -2524,19 +2524,13 @@ namespace Game CreatureMovementData movement = new(); if (!result.IsNull(1)) - movement.Ground = (CreatureGroundMovementType)result.Read(1); + movement.HoverInitiallyEnabled = result.Read(1); if (!result.IsNull(2)) - movement.Swim = result.Read(2); + movement.Chase = (CreatureChaseMovementType)result.Read(2); if (!result.IsNull(3)) - movement.Flight = (CreatureFlightMovementType)result.Read(3); + movement.Random = (CreatureRandomMovementType)result.Read(3); if (!result.IsNull(4)) - movement.Rooted = result.Read(4); - if (!result.IsNull(5)) - movement.Chase = (CreatureChaseMovementType)result.Read(5); - if (!result.IsNull(6)) - movement.Random = (CreatureRandomMovementType)result.Read(6); - if (!result.IsNull(7)) - movement.InteractionPauseTimer = result.Read(7); + movement.InteractionPauseTimer = result.Read(4); CheckCreatureMovement("creature_movement_override", spawnId, movement); @@ -2903,18 +2897,6 @@ namespace Game } void CheckCreatureMovement(string table, ulong id, CreatureMovementData creatureMovement) { - if (creatureMovement.Ground >= CreatureGroundMovementType.Max) - { - Log.outError(LogFilter.Sql, $"`{table}`.`Ground` wrong value ({creatureMovement.Ground}) for Id {id}, setting to Run."); - creatureMovement.Ground = CreatureGroundMovementType.Run; - } - - if (creatureMovement.Flight >= CreatureFlightMovementType.Max) - { - Log.outError(LogFilter.Sql, $"`{table}`.`Flight` wrong value ({creatureMovement.Flight}) for Id {id}, setting to None."); - creatureMovement.Flight = CreatureFlightMovementType.None; - } - if (creatureMovement.Chase >= CreatureChaseMovementType.Max) { Log.outError(LogFilter.Sql, $"`{table}`.`Chase` wrong value ({creatureMovement.Chase}) for Id {id}, setting to Run."); diff --git a/Source/Game/Movement/Generators/HomeMovement.cs b/Source/Game/Movement/Generators/HomeMovement.cs index 75fb77777..3871165ba 100644 --- a/Source/Game/Movement/Generators/HomeMovement.cs +++ b/Source/Game/Movement/Generators/HomeMovement.cs @@ -60,11 +60,9 @@ namespace Game.AI if (movementInform && HasFlag(MovementGeneratorFlags.InformEnabled)) { - if (!owner.HasCanSwimFlagOutOfCombat()) - owner.RemoveUnitFlag(UnitFlags.CanSwim); - if (owner.IsStateRestoredOnEvade()) { + owner.InitializeMovementCapabilities(); owner.SetSpawnHealth(); owner.LoadCreaturesAddon(); owner.LoadCreaturesSparringHealth(); diff --git a/Source/Game/Movement/Generators/PathGenerator.cs b/Source/Game/Movement/Generators/PathGenerator.cs index 80314c7bf..88f36f959 100644 --- a/Source/Game/Movement/Generators/PathGenerator.cs +++ b/Source/Game/Movement/Generators/PathGenerator.cs @@ -155,7 +155,7 @@ namespace Game.Movement BuildShortcut(); bool path = _source.IsTypeId(TypeId.Unit) && _source.ToCreature().CanFly(); - bool waterPath = _source.IsTypeId(TypeId.Unit) && _source.ToCreature().CanSwim(); + bool waterPath = _source.IsTypeId(TypeId.Unit) && _source.ToCreature().CanEnterWater(); if (waterPath) { // Check both start and end points, if they're both in water, then we can *safely* let the creature move @@ -850,7 +850,7 @@ namespace Game.Movement if (_source.IsTypeId(TypeId.Unit)) { Creature creature = _source.ToCreature(); - if (creature.CanWalk()) + if (!creature.IsAquatic()) includeFlags |= NavTerrainFlag.Ground; // creatures don't take environmental damage diff --git a/Source/Game/Spells/Auras/AuraEffect.cs b/Source/Game/Spells/Auras/AuraEffect.cs index 737031a6e..77ce6b909 100644 --- a/Source/Game/Spells/Auras/AuraEffect.cs +++ b/Source/Game/Spells/Auras/AuraEffect.cs @@ -2531,10 +2531,7 @@ namespace Game.Spells target.SetControlled(apply, UnitState.Root); // Do not remove DisableGravity if there are more than this auraEffect of that kind on the unit or if it's a creature with DisableGravity on its movement template. - if (!apply - && (target.HasAuraType(GetAuraType()) - || target.HasAuraType(AuraType.ModStunDisableGravity) - || (target.IsCreature() && target.ToCreature().GetMovementTemplate().Flight == CreatureFlightMovementType.DisableGravity))) + if (!apply && (target.HasAuraType(GetAuraType()) || target.HasAuraType(AuraType.ModRootDisableGravity) || (target.IsCreature() && target.ToCreature().IsFloating()))) return; if (target.SetDisableGravity(apply)) @@ -2556,10 +2553,7 @@ namespace Game.Spells target.GetThreatManager().EvaluateSuppressed(); // Do not remove DisableGravity if there are more than this auraEffect of that kind on the unit or if it's a creature with DisableGravity on its movement template. - if (!apply - && (target.HasAuraType(GetAuraType()) - || target.HasAuraType(AuraType.ModStunDisableGravity) - || (target.IsCreature() && target.ToCreature().GetMovementTemplate().Flight == CreatureFlightMovementType.DisableGravity))) + if (!apply && (target.HasAuraType(GetAuraType()) || target.HasAuraType(AuraType.ModStunDisableGravity) || (target.IsCreature() && target.ToCreature().IsFloating()))) return; if (target.SetDisableGravity(apply)) diff --git a/sql/updates/world/master/2024_02_29_00_world.sql b/sql/updates/world/master/2024_02_29_00_world.sql new file mode 100644 index 000000000..faf1e29c7 --- /dev/null +++ b/sql/updates/world/master/2024_02_29_00_world.sql @@ -0,0 +1,80 @@ +-- Convert existing data to static flags. There may be some errors in conversion due to dirty unit flag that that's been dumped into the DB which needs manual fixups + +-- CREATURE_STATIC_FLAG_CAN_SWIM +UPDATE `creature_template_difficulty` AS ctd LEFT JOIN `creature_template` AS ct ON ct.`entry` = ctd.`Entry` +SET ctd.`StaticFlags1`= ctd.`StaticFlags1` | 0x10000000 +WHERE (ct.`unit_flags` & 0x00008000) != 0; + +UPDATE `creature_template_difficulty` AS ctd LEFT JOIN `creature_template` AS ct ON ct.`entry` = ctd.`Entry` +SET ctd.`StaticFlags1`= ctd.`StaticFlags1` &~ 0x00000100 +WHERE (ct.`unit_flags` & 0x00008000) != 0; + +-- CREATURE_STATIC_FLAG_CANT_SWIM +UPDATE `creature_template_difficulty` AS ctd LEFT JOIN `creature_template` AS ct ON ct.`entry` = ctd.`Entry` +SET ctd.`StaticFlags1`= ctd.`StaticFlags1` | 0x00000100 +WHERE (ct.`unit_flags` & 0x00004000) != 0; + +UPDATE `creature_template_difficulty` AS ctd LEFT JOIN `creature_template` AS ct ON ct.`entry` = ctd.`Entry` +SET ctd.`StaticFlags1`= ctd.`StaticFlags1` &~ 0x10000000 +WHERE (ct.`unit_flags` & 0x00004000) != 0; + +-- CREATURE_STATIC_FLAG_AMPHIBIOUS +UPDATE `creature_template_difficulty` AS ctd +LEFT JOIN `creature_template_movement` AS ctm ON ctm.`CreatureId` = ctd.`Entry` +LEFT JOIN `creature_template` AS ct ON ct.`entry` = ctd.`Entry` +SET ctd.`StaticFlags1`= ctd.`StaticFlags1` | 0x00080000 +WHERE ctm.`Ground`= 1 AND ctm.`Swim`= 1 AND (ct.`unit_flags` & 0x00008000) = 0; + +-- CREATURE_STATIC_FLAG_SESSILE +UPDATE `creature_template_difficulty` AS ctd LEFT JOIN `creature_template_movement` AS ctm ON ctm.`CreatureId` = ctd.`Entry` +SET ctd.`StaticFlags1`= ctd.`StaticFlags1` | 0x00000100 +WHERE ctm.`Rooted`= 1; + +-- CREATURE_STATIC_FLAG_FLOATING +UPDATE `creature_template_difficulty` AS ctd LEFT JOIN `creature_template_movement` AS ctm ON ctm.`CreatureId` = ctd.`Entry` +SET ctd.`StaticFlags1`= ctd.`StaticFlags1` | 0x20000000 +WHERE ctm.`Flight`!= 0; + +-- CREATURE_STATIC_FLAG_AQUATIC +UPDATE `creature_template_difficulty` AS ctd LEFT JOIN `creature_template_movement` AS ctm ON ctm.`CreatureId` = ctd.`Entry` +SET ctd.`StaticFlags1`= ctd.`StaticFlags1` | 0x00040000 +WHERE ctm.`Ground`= 0 AND ctm.`Swim`= 1; + +-- CREATURE_STATIC_FLAG_3_CANNOT_TURN +UPDATE `creature_template_difficulty` AS ctd LEFT JOIN `creature_template` AS ct ON ct.`entry` = ctd.`Entry` +SET ctd.`StaticFlags3`= ctd.`StaticFlags3` | 0x02000000 +WHERE (ct.`unit_flags2` & 0x00008000) != 0; + +-- Remove unit flags that are now handled by static flags +UPDATE `creature_template` SET `unit_flags`= `unit_flags` &~ (0x00008000 | 0x00004000); +UPDATE `creature_template` SET `unit_flags2`= `unit_flags2` &~ 0x00008000; + +-- Add new movement settings +ALTER TABLE `creature_template_movement` + ADD COLUMN `HoverInitiallyEnabled` TINYINT UNSIGNED NULL AFTER `CreatureId`; + +UPDATE `creature_template_movement` SET `HoverInitiallyEnabled`= 1 WHERE `Ground` = 2; + +ALTER TABLE `creature_movement_override` + ADD COLUMN `HoverInitiallyEnabled` TINYINT UNSIGNED NULL AFTER `SpawnId`; + +UPDATE `creature_movement_override` SET `HoverInitiallyEnabled`= 1 WHERE `Ground` = 2; + +-- Remove deprecated CREATURE_FLAG_EXTRA_NO_MOVE_FLAGS_UPDATE +UPDATE `creature_template` SET `flags_extra`= `flags_extra` &~ 0x200; + +-- Conversion done. Drop old columns +ALTER TABLE `creature_movement_override` + DROP COLUMN `Ground`, + DROP COLUMN `Swim`, + DROP COLUMN `Flight`, + DROP COLUMN `Rooted`; + +ALTER TABLE `creature_template_movement` + DROP COLUMN `Ground`, + DROP COLUMN `Swim`, + DROP COLUMN `Flight`, + DROP COLUMN `Rooted`; + +-- Converted a couple script hacks to static flags where they belong +UPDATE `creature_template_difficulty` SET `StaticFlags1`= `StaticFlags1` | 0x00000100 WHERE `Entry` IN (37081, 38038);