From 3da21c359ac48d280281074ad77d804891c2f1c9 Mon Sep 17 00:00:00 2001 From: Ty Conner Date: Mon, 30 Dec 2024 15:58:57 -0500 Subject: [PATCH 1/6] Add support for scatter position retries Used by Landblock for dynamic world objects only, if enabled. Adds shard configurable properties: `dynamic_scatter_retry` - if TRUE, landblock will retry to spawn dynamic world objects that fail their first position per `dynamic_scatter_num_retries` with a radius of `dynamic_scatter_retry_radius` This attempts to mitigate the issue in which some dynamic (player created/accessible) objects have been assigned positions that do not allow them to enter the world. Some examples of objects seen in these states include player corpses and/or objects player corpses spewed when the corpse decayed, and objects that were dropped by players that failed to enter world and then return to player's inventory. --- Source/ACE.Server/Entity/Landblock.cs | 18 ++++++++++--- Source/ACE.Server/Managers/PropertyManager.cs | 25 +++++++++++-------- Source/ACE.Server/Physics/PhysicsObj.cs | 8 ++++++ Source/ACE.Server/WorldObjects/WorldObject.cs | 8 ++++++ 4 files changed, 45 insertions(+), 14 deletions(-) diff --git a/Source/ACE.Server/Entity/Landblock.cs b/Source/ACE.Server/Entity/Landblock.cs index 43c0a438f1..ca5ddcfa99 100644 --- a/Source/ACE.Server/Entity/Landblock.cs +++ b/Source/ACE.Server/Entity/Landblock.cs @@ -884,10 +884,22 @@ private bool AddWorldObjectInternal(WorldObject wo) if (log.IsDebugEnabled) log.Debug($"AddWorldObjectInternal: couldn't spawn generator 0x{wo.Guid}:{wo.Name} [{wo.WeenieClassId} - {wo.WeenieType}] at {wo.Location.ToLOCString()}"); } - else if (wo.ProjectileTarget == null && !(wo is SpellProjectile)) - log.Warn($"AddWorldObjectInternal: couldn't spawn 0x{wo.Guid}:{wo.Name} [{wo.WeenieClassId} - {wo.WeenieType}] at {wo.Location.ToLOCString()}"); + else if (wo.ProjectileTarget is null && wo is not SpellProjectile) + { + if (wo.Guid.IsDynamic() && PropertyManager.GetBool("dynamic_scatter_retry").Item) + { + wo.RetryEnterWorldWithScatter = true; + wo.InitPhysicsObj(); + success = wo.AddPhysicsObj(); + if (!success) + log.Warn($"Landblock.AddWorldObjectInternal: Could not spawn dynamic object!\n 0x{wo.Guid} - {wo.NameWithMaterial} (WCID: {wo.WeenieClassId} | WeenieType: {wo.WeenieType})\n at {wo.Location.ToLOCString()}"); + } + else + log.Warn($"AddWorldObjectInternal: couldn't spawn 0x{wo.Guid}:{wo.Name} [{wo.WeenieClassId} - {wo.WeenieType}] at {wo.Location.ToLOCString()}"); + } - return false; + if (!success) + return false; } } diff --git a/Source/ACE.Server/Managers/PropertyManager.cs b/Source/ACE.Server/Managers/PropertyManager.cs index 4de87fd0e2..7be9eb8a10 100644 --- a/Source/ACE.Server/Managers/PropertyManager.cs +++ b/Source/ACE.Server/Managers/PropertyManager.cs @@ -539,8 +539,7 @@ public static void LoadDefaultProperties() ("chat_log_trade", new Property(false, "log trade chat")), ("chat_log_townchans", new Property(false, "log advocate town chat")), ("chat_requires_account_15days", new Property(false, "global chat privileges requires accounts to be 15 days or older")), - ("chess_enabled", new Property(true, "if FALSE then chess will be disabled")), - ("use_cloak_proc_custom_scale", new Property(false, "If TRUE, the calculation for cloak procs will be based upon the values set by the server oeprator.")), + ("chess_enabled", new Property(true, "if FALSE then chess will be disabled")), ("client_movement_formula", new Property(false, "If enabled, server uses DoMotion/StopMotion self-client movement methods instead of apply_raw_movement")), ("container_opener_name", new Property(false, "If enabled, when a player tries to open a container that is already in use by someone else, replaces 'someone else' in the message with the actual name of the player")), ("corpse_decay_tick_logging", new Property(false, "If ENABLED then player corpse ticks will be logged")), @@ -548,6 +547,7 @@ public static void LoadDefaultProperties() ("craft_exact_msg", new Property(false, "If TRUE, and player has crafting chance of success dialog enabled, shows them an additional message in their chat window with exact %")), ("creature_name_check", new Property(true, "if enabled, creature names in world database restricts player names during character creation")), ("creatures_drop_createlist_wield", new Property(false, "If FALSE then Wielded items in CreateList will not drop. Retail defaulted to TRUE but there are currently data errors")), + ("dynamic_scatter_retry", new Property(false, "if TRUE, landblock will retry to spawn dynamic worldobjects that fail their first position per dynamic_scatter_num_retries with a radius of dynamic_scatter_retry_radius")), ("equipmentsetid_enabled", new Property(true, "enable this to allow adding EquipmentSetIDs to loot armor")), ("equipmentsetid_name_decoration", new Property(false, "enable this to add the EquipmentSet name to loot armor name")), ("fastbuff", new Property(true, "If TRUE, enables the fast buffing trick from retail.")), @@ -606,6 +606,7 @@ public static void LoadDefaultProperties() ("universal_masteries", new Property(true, "if TRUE, matches end of retail masteries - players wielding almost any weapon get +5 DR, except if the weapon \"seems tough to master\". " + "if FALSE, players start with mastery of 1 melee and 1 ranged weapon type based on heritage, and can later re-select these 2 masteries")), ("unlimited_sequence_gaps", new Property(false, "upon startup, allows server to find all unused guids in a range instead of a set hard limit")), + ("use_cloak_proc_custom_scale", new Property(false, "If TRUE, the calculation for cloak procs will be based upon the values set by the server oeprator.")), ("use_generator_rotation_offset", new Property(true, "enables or disables using the generator's current rotation when offseting relative positions")), ("use_portal_max_level_requirement", new Property(true, "disable this to ignore the max level restriction on portals")), ("use_turbine_chat", new Property(true, "enables or disables global chat channels (General, LFG, Roleplay, Trade, Olthoi, Society, Allegience)")), @@ -623,6 +624,7 @@ public static void LoadDefaultProperties() ("chat_requires_player_level", new Property(0, "the level a player is required to have for global chat privileges")), ("corpse_spam_limit", new Property(15, "the number of corpses a player is allowed to leave on a landblock at one time")), ("default_subscription_level", new Property(1, "retail defaults to 1, 1 = standard subscription (same as 2 and 3), 4 grants ToD pre-order bonus item Asheron's Benediction")), + ("dynamic_scatter_num_retries", new Property(20, "number of retry attempts for dynamic scatter position. Be cautious in adjusting this number")), ("fellowship_even_share_level", new Property(50, "level when fellowship XP sharing is no longer restricted")), ("mansion_min_rank", new Property(6, "overrides the default allegiance rank required to own a mansion")), ("max_chars_per_account", new Property(11, "retail defaults to 11, client supports up to 20")), @@ -636,28 +638,29 @@ public static void LoadDefaultProperties() public static readonly ReadOnlyDictionary> DefaultDoubleProperties = DictOf( - - ("cantrip_drop_rate", new Property(1.0, "Scales the chance for cantrips to drop in each tier. Defaults to 1.0, as per end of retail")), - ("cloak_cooldown_seconds", new Property(5.0, "The number of seconds between possible cloak procs.")), - ("cloak_max_proc_base", new Property(0.25, "The max proc chance of a cloak.")), - ("cloak_max_proc_damage_percentage", new Property(0.30, "The damage percentage at which cloak proc chance plateaus.")), - ("cloak_min_proc", new Property(0, "The minimum proc chance of a cloak.")), ("minor_cantrip_drop_rate", new Property(1.0, "Scales the chance for minor cantrips to drop, relative to other cantrip levels in the tier. Defaults to 1.0, as per end of retail")), ("major_cantrip_drop_rate", new Property(1.0, "Scales the chance for major cantrips to drop, relative to other cantrip levels in the tier. Defaults to 1.0, as per end of retail")), - ("epic_cantrip_drop_rate", new Property(1.0, "Scales the chance for epic cantrips to drop, relative to other cantrip levels in the tier. Defaults to 1.0, as per end of retail")), - ("legendary_cantrip_drop_rate", new Property(1.0, "Scales the chance for legendary cantrips to drop, relative to other cantrip levels in the tier. Defaults to 1.0, as per end of retail")), - ("advocate_fane_auto_bestow_level", new Property(1, "the level that advocates are automatically bestowed by Advocate Fane if advocate_fane_auto_bestow is true")), ("aetheria_drop_rate", new Property(1.0, "Modifier for Aetheria drop rate, 1 being normal")), + ("cantrip_drop_rate", new Property(1.0, "Scales the chance for cantrips to drop in each tier. Defaults to 1.0, as per end of retail")), ("chess_ai_start_time", new Property(-1.0, "the number of seconds for the chess ai to start. defaults to -1 (disabled)")), + ("cloak_cooldown_seconds", new Property(5.0, "The number of seconds between possible cloak procs.")), + ("cloak_max_proc_base", new Property(0.25, "The max proc chance of a cloak.")), + ("cloak_max_proc_damage_percentage", new Property(0.30, "The damage percentage at which cloak proc chance plateaus.")), + ("cloak_min_proc", new Property(0, "The minimum proc chance of a cloak.")), + ("dynamic_scatter_retry_radius", new Property(0.05, "The radius at which each dynamic scatter retry is adjusted by. Be cautious in adjusting this number")), ("encounter_delay", new Property(1800, "the number of seconds a generator profile for regions is delayed from returning to free slots")), ("encounter_regen_interval", new Property(600, "the number of seconds a generator for regions at which spawns its next set of objects")), + ("epic_cantrip_drop_rate", new Property(1.0, "Scales the chance for epic cantrips to drop, relative to other cantrip levels in the tier. Defaults to 1.0, as per end of retail")), ("equipmentsetid_drop_rate", new Property(1.0, "Modifier for EquipmentSetID drop rate, 1 being normal")), ("fast_missile_modifier", new Property(1.2, "The speed multiplier applied to fast missiles. Defaults to retail value of 1.2")), ("ignore_magic_armor_pvp_scalar", new Property(1.0, "Scales the effectiveness of IgnoreMagicArmor (ie. hollow weapons) in pvp battles. 1.0 = full effectiveness / ignore all enchantments on armor (default), 0.5 = half effectiveness / use half enchantments from armor, 0.0 = no effectiveness / use full enchantments from armor")), ("ignore_magic_resist_pvp_scalar", new Property(1.0, "Scales the effectiveness of IgnoreMagicResist (ie. hollow weapons) in pvp battles. 1.0 = full effectiveness / ignore all resistances from life enchantments (default), 0.5 = half effectiveness / use half resistances from life enchantments, 0.0 = no effectiveness / use full resistances from life enchantments")), + ("legendary_cantrip_drop_rate", new Property(1.0, "Scales the chance for legendary cantrips to drop, relative to other cantrip levels in the tier. Defaults to 1.0, as per end of retail")), ("luminance_modifier", new Property(1.0, "Scales the amount of luminance received by players")), + ("major_cantrip_drop_rate", new Property(1.0, "Scales the chance for major cantrips to drop, relative to other cantrip levels in the tier. Defaults to 1.0, as per end of retail")), ("melee_max_angle", new Property(0.0, "for melee players, the maximum angle before a TurnTo is required. retail appeared to have required a TurnTo even for the smallest of angle offsets.")), + ("minor_cantrip_drop_rate", new Property(1.0, "Scales the chance for minor cantrips to drop, relative to other cantrip levels in the tier. Defaults to 1.0, as per end of retail")), ("mob_awareness_range", new Property(1.0, "Scales the distance the monsters become alerted and aggro the players")), ("pk_new_character_grace_period", new Property(300, "the number of seconds, in addition to pk_respite_timer, that a player killer is set to non-player killer status after first exiting training academy")), ("pk_respite_timer", new Property(300, "the number of seconds that a player killer is set to non-player killer status after dying to another player killer")), diff --git a/Source/ACE.Server/Physics/PhysicsObj.cs b/Source/ACE.Server/Physics/PhysicsObj.cs index 930aea489c..53691eabda 100644 --- a/Source/ACE.Server/Physics/PhysicsObj.cs +++ b/Source/ACE.Server/Physics/PhysicsObj.cs @@ -2430,6 +2430,14 @@ public bool enter_world(bool slide) if (slide) setPos.Flags |= SetPositionFlags.Slide; + if (WeenieObj.WorldObject.RetryEnterWorldWithScatter) + { + setPos.Flags |= SetPositionFlags.Scatter; + setPos.NumTries = (int)PropertyManager.GetLong("dynamic_scatter_num_retries").Item; + setPos.RadX = (float)PropertyManager.GetDouble("dynamic_scatter_retry_radius").Item; + setPos.RadY = (float)PropertyManager.GetDouble("dynamic_scatter_retry_radius").Item; + } + var result = SetPosition(setPos); if (result != SetPositionError.OK) return false; diff --git a/Source/ACE.Server/WorldObjects/WorldObject.cs b/Source/ACE.Server/WorldObjects/WorldObject.cs index 0ac00a8e7e..74ae0b6973 100644 --- a/Source/ACE.Server/WorldObjects/WorldObject.cs +++ b/Source/ACE.Server/WorldObjects/WorldObject.cs @@ -1097,5 +1097,13 @@ public int StructureUnitValue return Math.Max(0, structureUnitValue); } } + + /// + /// Retry PhysicsObj.enter_world with added Scatter position flag and options + /// + /// + /// Used by Landblock, this lets the physics system attempt to adjust failed position slightly with scatter to allow object to successfully enter the world. + /// + public bool RetryEnterWorldWithScatter; } } From 379b53967446ddd5b147fa5cc45f1dea66f61f5d Mon Sep 17 00:00:00 2001 From: Ty Conner Date: Mon, 30 Dec 2024 16:01:03 -0500 Subject: [PATCH 2/6] Update PropertyManager.cs --- Source/ACE.Server/Managers/PropertyManager.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Source/ACE.Server/Managers/PropertyManager.cs b/Source/ACE.Server/Managers/PropertyManager.cs index 7be9eb8a10..a2217b4c11 100644 --- a/Source/ACE.Server/Managers/PropertyManager.cs +++ b/Source/ACE.Server/Managers/PropertyManager.cs @@ -638,8 +638,6 @@ public static void LoadDefaultProperties() public static readonly ReadOnlyDictionary> DefaultDoubleProperties = DictOf( - ("minor_cantrip_drop_rate", new Property(1.0, "Scales the chance for minor cantrips to drop, relative to other cantrip levels in the tier. Defaults to 1.0, as per end of retail")), - ("major_cantrip_drop_rate", new Property(1.0, "Scales the chance for major cantrips to drop, relative to other cantrip levels in the tier. Defaults to 1.0, as per end of retail")), ("advocate_fane_auto_bestow_level", new Property(1, "the level that advocates are automatically bestowed by Advocate Fane if advocate_fane_auto_bestow is true")), ("aetheria_drop_rate", new Property(1.0, "Modifier for Aetheria drop rate, 1 being normal")), ("cantrip_drop_rate", new Property(1.0, "Scales the chance for cantrips to drop in each tier. Defaults to 1.0, as per end of retail")), From 423166b92b04c0abf397f3c6d443d53f1f60ff54 Mon Sep 17 00:00:00 2001 From: Ty Conner Date: Mon, 30 Dec 2024 16:29:13 -0500 Subject: [PATCH 3/6] Update Landblock.cs --- Source/ACE.Server/Entity/Landblock.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/ACE.Server/Entity/Landblock.cs b/Source/ACE.Server/Entity/Landblock.cs index ca5ddcfa99..9f748de3c5 100644 --- a/Source/ACE.Server/Entity/Landblock.cs +++ b/Source/ACE.Server/Entity/Landblock.cs @@ -888,11 +888,15 @@ private bool AddWorldObjectInternal(WorldObject wo) { if (wo.Guid.IsDynamic() && PropertyManager.GetBool("dynamic_scatter_retry").Item) { + var woFailedLocation = new Position(wo.Location); wo.RetryEnterWorldWithScatter = true; wo.InitPhysicsObj(); success = wo.AddPhysicsObj(); if (!success) log.Warn($"Landblock.AddWorldObjectInternal: Could not spawn dynamic object!\n 0x{wo.Guid} - {wo.NameWithMaterial} (WCID: {wo.WeenieClassId} | WeenieType: {wo.WeenieType})\n at {wo.Location.ToLOCString()}"); + else if (log.IsDebugEnabled) + log.Debug($"Landblock.AddWorldObjectInternal: Successfully moved and spawned dynamic object!\n 0x{wo.Guid} - {wo.NameWithMaterial} (WCID: {wo.WeenieClassId} | WeenieType: {wo.WeenieType})\n from: {wo.Location.ToLOCString()}\n to: {woFailedLocation.ToLOCString()}"); + } else log.Warn($"AddWorldObjectInternal: couldn't spawn 0x{wo.Guid}:{wo.Name} [{wo.WeenieClassId} - {wo.WeenieType}] at {wo.Location.ToLOCString()}"); From bc1fb9b71169018e7839b109c6f72af8c3443cd6 Mon Sep 17 00:00:00 2001 From: Ty Conner Date: Mon, 30 Dec 2024 16:37:06 -0500 Subject: [PATCH 4/6] Update Landblock.cs --- Source/ACE.Server/Entity/Landblock.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Source/ACE.Server/Entity/Landblock.cs b/Source/ACE.Server/Entity/Landblock.cs index 9f748de3c5..fca1cd432f 100644 --- a/Source/ACE.Server/Entity/Landblock.cs +++ b/Source/ACE.Server/Entity/Landblock.cs @@ -894,9 +894,11 @@ private bool AddWorldObjectInternal(WorldObject wo) success = wo.AddPhysicsObj(); if (!success) log.Warn($"Landblock.AddWorldObjectInternal: Could not spawn dynamic object!\n 0x{wo.Guid} - {wo.NameWithMaterial} (WCID: {wo.WeenieClassId} | WeenieType: {wo.WeenieType})\n at {wo.Location.ToLOCString()}"); - else if (log.IsDebugEnabled) - log.Debug($"Landblock.AddWorldObjectInternal: Successfully moved and spawned dynamic object!\n 0x{wo.Guid} - {wo.NameWithMaterial} (WCID: {wo.WeenieClassId} | WeenieType: {wo.WeenieType})\n from: {wo.Location.ToLOCString()}\n to: {woFailedLocation.ToLOCString()}"); - + else + { + if (log.IsDebugEnabled) + log.Debug($"Landblock.AddWorldObjectInternal: Successfully moved and spawned dynamic object!\n 0x{wo.Guid} - {wo.NameWithMaterial} (WCID: {wo.WeenieClassId} | WeenieType: {wo.WeenieType})\n from: {wo.Location.ToLOCString()}\n to: {woFailedLocation.ToLOCString()}"); + } } else log.Warn($"AddWorldObjectInternal: couldn't spawn 0x{wo.Guid}:{wo.Name} [{wo.WeenieClassId} - {wo.WeenieType}] at {wo.Location.ToLOCString()}"); From 05b56295af697877ad8c8757dac7779d9ba5cda2 Mon Sep 17 00:00:00 2001 From: Ty Conner Date: Mon, 30 Dec 2024 16:50:06 -0500 Subject: [PATCH 5/6] Update Landblock.cs --- Source/ACE.Server/Entity/Landblock.cs | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/Source/ACE.Server/Entity/Landblock.cs b/Source/ACE.Server/Entity/Landblock.cs index fca1cd432f..8652a9d94b 100644 --- a/Source/ACE.Server/Entity/Landblock.cs +++ b/Source/ACE.Server/Entity/Landblock.cs @@ -886,19 +886,24 @@ private bool AddWorldObjectInternal(WorldObject wo) } else if (wo.ProjectileTarget is null && wo is not SpellProjectile) { - if (wo.Guid.IsDynamic() && PropertyManager.GetBool("dynamic_scatter_retry").Item) + if (wo.Guid.IsDynamic()) { - var woFailedLocation = new Position(wo.Location); - wo.RetryEnterWorldWithScatter = true; - wo.InitPhysicsObj(); - success = wo.AddPhysicsObj(); - if (!success) - log.Warn($"Landblock.AddWorldObjectInternal: Could not spawn dynamic object!\n 0x{wo.Guid} - {wo.NameWithMaterial} (WCID: {wo.WeenieClassId} | WeenieType: {wo.WeenieType})\n at {wo.Location.ToLOCString()}"); - else + if (PropertyManager.GetBool("dynamic_scatter_retry").Item) { - if (log.IsDebugEnabled) - log.Debug($"Landblock.AddWorldObjectInternal: Successfully moved and spawned dynamic object!\n 0x{wo.Guid} - {wo.NameWithMaterial} (WCID: {wo.WeenieClassId} | WeenieType: {wo.WeenieType})\n from: {wo.Location.ToLOCString()}\n to: {woFailedLocation.ToLOCString()}"); + var woFailedLocation = new Position(wo.Location); + wo.RetryEnterWorldWithScatter = true; + wo.InitPhysicsObj(); + success = wo.AddPhysicsObj(); + if (!success) + log.Warn($"Landblock.AddWorldObjectInternal: Could not spawn dynamic object!\n 0x{wo.Guid} - {wo.NameWithMaterial} (WCID: {wo.WeenieClassId} | WeenieType: {wo.WeenieType})\n at {wo.Location.ToLOCString()}"); + else + { + if (log.IsDebugEnabled) + log.Debug($"Landblock.AddWorldObjectInternal: Successfully moved and spawned dynamic object!\n 0x{wo.Guid} - {wo.NameWithMaterial} (WCID: {wo.WeenieClassId} | WeenieType: {wo.WeenieType})\n from: {wo.Location.ToLOCString()}\n to: {woFailedLocation.ToLOCString()}"); + } } + else + log.Warn($"Landblock.AddWorldObjectInternal: Could not spawn dynamic object!\n 0x{wo.Guid} - {wo.NameWithMaterial} (WCID: {wo.WeenieClassId} | WeenieType: {wo.WeenieType})\n at {wo.Location.ToLOCString()}"); } else log.Warn($"AddWorldObjectInternal: couldn't spawn 0x{wo.Guid}:{wo.Name} [{wo.WeenieClassId} - {wo.WeenieType}] at {wo.Location.ToLOCString()}"); From 07aeb0b1916dd6b4c4bd28e301c52346a490eeff Mon Sep 17 00:00:00 2001 From: Ty Conner Date: Mon, 30 Dec 2024 18:13:58 -0500 Subject: [PATCH 6/6] Update Landblock.cs --- Source/ACE.Server/Entity/Landblock.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/ACE.Server/Entity/Landblock.cs b/Source/ACE.Server/Entity/Landblock.cs index 8652a9d94b..7dbc5cef6a 100644 --- a/Source/ACE.Server/Entity/Landblock.cs +++ b/Source/ACE.Server/Entity/Landblock.cs @@ -886,7 +886,7 @@ private bool AddWorldObjectInternal(WorldObject wo) } else if (wo.ProjectileTarget is null && wo is not SpellProjectile) { - if (wo.Guid.IsDynamic()) + if (wo.Guid.IsDynamic() && wo is not CombatPet && wo is not Pet && wo is not Portal) { if (PropertyManager.GetBool("dynamic_scatter_retry").Item) {