diff --git a/Source/ACE.Server/Command/Handlers/DeveloperCommands.cs b/Source/ACE.Server/Command/Handlers/DeveloperCommands.cs index cad9e528d1..c78f1b9e47 100644 --- a/Source/ACE.Server/Command/Handlers/DeveloperCommands.cs +++ b/Source/ACE.Server/Command/Handlers/DeveloperCommands.cs @@ -2741,6 +2741,7 @@ public static void HandleGeneratorDump(Session session, params string[] paramete msg += $"Generator WCID: {wo.WeenieClassId}\n"; msg += $"Generator WeenieClassName: {wo.WeenieClassName}\n"; msg += $"Generator WeenieType: {wo.WeenieType.ToString()}\n"; + msg += $"Generator IsEncounter: {wo.IsEncounter}\n"; msg += $"Generator Status: {(wo.GeneratorDisabled ? "Disabled" : "Enabled")}\n"; msg += $"GeneratorType: {wo.GeneratorType.ToString()}\n"; msg += $"GeneratorTimeType: {wo.GeneratorTimeType.ToString()}\n"; @@ -2782,6 +2783,19 @@ public static void HandleGeneratorDump(Session session, params string[] paramete msg += $"IsMaxed: {profile.IsMaxed}\n"; if (!profile.IsMaxed) msg += $"IsAvailable: {profile.IsAvailable}{(profile.IsAvailable ? "" : $", NextAvailable: {profile.NextAvailable.ToLocalTime()}")}\n"; + msg += $"AdjustedProbability: {wo.GetAdjustedProbability(activeProfile)} / {wo.GetTotalProbability()}\n"; + msg += $"MostRecentSpawnTime: {profile.MostRecentSpawnTime.ToLocalTime()}\n"; + if (wo.IsEncounter) + { + var hasNonWorldObjects = 0; + var hasAwakeCreatures = 0; + var hasOpenContainers = 0; + var hasUnlockedChests = 0; + var isAbleToBeMarkedStale = profile.IsAbleToBeMarkedStale(ref hasNonWorldObjects, ref hasAwakeCreatures, ref hasOpenContainers, ref hasUnlockedChests); + msg += $"IsAbleToBeMarkedStale: {isAbleToBeMarkedStale}\n"; + if (isAbleToBeMarkedStale) + msg += $"StaleTime: {profile.StaleTime.ToLocalTime()}\n"; + } msg += $"--====--\n"; if (profile.Spawned.Count > 0) { diff --git a/Source/ACE.Server/Entity/GeneratorProfile.cs b/Source/ACE.Server/Entity/GeneratorProfile.cs index fc859c02e9..07a3664d39 100644 --- a/Source/ACE.Server/Entity/GeneratorProfile.cs +++ b/Source/ACE.Server/Entity/GeneratorProfile.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Numerics; using log4net; +using ACE.Common; using ACE.Database; using ACE.Entity; using ACE.Entity.Enum; @@ -114,6 +116,16 @@ public float Delay } } + /// + /// DateTime for when the profile last spawned something + /// + public DateTime MostRecentSpawnTime { get; set; } = DateTime.MinValue; + + /// + /// DateTime for when the profile is considered stale + /// + public DateTime StaleTime { get; set; } = DateTime.MinValue; + /// /// DateTime for when the profile is available as a possible spawn choice /// @@ -129,6 +141,60 @@ public float Delay /// public bool IsMaxed => MaxCreate != -1 && CurrentCreate >= MaxCreate; + ///// + ///// Returns TRUE if this profile has any non-Creature WorldObjects, Creatures with IsAwake being false, any Containers with IsOpen being false, + ///// any Chests with DefaultLocked true and IsLocked being true + ///// + public bool IsAbleToBeMarkedStale(ref int hasNonWorldObjects, ref int hasAwakeCreatures, ref int hasOpenContainers, ref int hasUnlockedChests) + { + //var hasNonWorldObjects = 0; + //var hasAwakeCreatures = 0; + //var hasOpenContainers = 0; + //var hasUnlockedChests = 0; + + if (Spawned.Count == 0) + return false; + + foreach (var spawn in Spawned.Values) + { + var wo = spawn.TryGetWorldObject(); + if (wo != null) + { + if (wo is not Creature && !wo.IsGenerator) + hasNonWorldObjects++; + + if (wo.IsGenerator) + { + //if (wo is not Creature) + // hasNonWorldObjects++; + + //if (wo is not Creature && wo is not GenericObject) + // hasNonWorldObjects++; + + //if (wo is Switch) + // hasNonWorldObjects++; + + foreach (var profile in wo.GeneratorProfiles) + { + if (profile.IsAbleToBeMarkedStale(ref hasNonWorldObjects, ref hasAwakeCreatures, ref hasOpenContainers, ref hasUnlockedChests)) + hasNonWorldObjects++; + } + } + + if (wo is Creature creature && creature.IsAwake) + hasAwakeCreatures++; + + if (wo is Container container && container.IsOpen) + hasOpenContainers++; + + if (wo is Chest chest && (chest.GetProperty(PropertyBool.DefaultLocked) ?? false) && !chest.IsLocked) + hasUnlockedChests++; + } + } + + return hasNonWorldObjects > 0 && hasAwakeCreatures == 0 && hasOpenContainers == 0 && hasUnlockedChests == 0; + } + /// /// The generator world object for this profile /// @@ -180,6 +246,16 @@ public void Enqueue(int numObjects = 1) break; }*/ SpawnQueue.Add(GetSpawnTime()); + + //if (Generator.IsEncounter) + //{ + // Generator.CurrentLandblock?.NotifyEncounterGenerators(Generator, (int)Id); + // foreach (var landblock in Generator.CurrentLandblock?.Adjacents) + // landblock.NotifyEncounterGenerators(Generator, (int)Id); + //} + + //if (Generator.IsEncounter) + // Generator.CurrentLandblock?.RegisterEncounterProfileUsed(Generator, Id, Delay); } } @@ -214,6 +290,16 @@ public void ProcessQueue() Spawned.Add(obj.Guid.Full, woi); } + + MostRecentSpawnTime = DateTime.UtcNow; + + if (Generator.IsEncounter) + { + var variance = ThreadSafeRandom.Next(0, Delay); + StaleTime = DateTime.UtcNow.AddSeconds(Delay * MaxCreate + variance); + + //Generator.CurrentLandblock?.NotifyEncounterGenerators(Generator, (int)Id); + } } } else @@ -618,6 +704,8 @@ private void CleanupProfile() Spawned.Clear(); SpawnQueue.Clear(); + MostRecentSpawnTime = DateTime.MinValue; + StaleTime = DateTime.MinValue; NextAvailable = DateTime.UtcNow; GeneratedTreasureItem = false; diff --git a/Source/ACE.Server/Entity/Landblock.cs b/Source/ACE.Server/Entity/Landblock.cs index 23e911f913..ae8c365cbe 100644 --- a/Source/ACE.Server/Entity/Landblock.cs +++ b/Source/ACE.Server/Entity/Landblock.cs @@ -277,6 +277,8 @@ private void SpawnEncounters() if (wo == null) continue; + wo.IsEncounter = true; + actionQueue.EnqueueAction(new ActionEventDelegate(() => { var xPos = Math.Clamp(encounter.CellX * 24.0f, 0.5f, 191.5f); @@ -1349,5 +1351,52 @@ public void DoEnvironChange(EnvironChangeType environChangeType) else SendEnvironSound(environChangeType); } + + //public void NotifyEncounterGenerators(WorldObject generator, int profileId) + //{ + // //if (string.IsNullOrWhiteSpace(message)) return; + + // //foreach (var wo in worldObjects.Values.Where(w => w.HearLocalSignals).ToList()) + // //{ + // // if (emitter == wo) continue; + + // // if (emitter.IsWithinUseRadiusOf(wo, wo.HearLocalSignalsRadius)) + // // { + // // //Console.WriteLine($"{wo.Name}.EmoteManager.OnLocalSignal({emitter.Name}, {message})"); + // // wo.EmoteManager.OnLocalSignal(emitter, message); + // // } + // //} + + // var nextAvailable = DateTime.UtcNow.AddSeconds(generator.GeneratorProfiles[profileId].Delay); + + // var encounterGenerators = worldObjects.Values.Where(w => w.WeenieClassId == generator.WeenieClassId && w.Guid != generator.Guid).ToList(); + // foreach (var encounterGenerator in encounterGenerators) + // { + // encounterGenerator.GeneratorProfiles[profileId].NextAvailable = nextAvailable; + // } + //} + + //private readonly Dictionary> EncounterSomething = new(); + + //public void RegisterEncounterProfileUsed(WorldObject generator, uint profileId, float delay) + //{ + // EncounterSomething.TryAdd(generator.WeenieClassId, new()); + + // EncounterSomething[generator.WeenieClassId][(int)profileId] = DateTime.UtcNow.AddSeconds(delay); + //} + + //public bool CheckEncounterProfileIsAvailable(WorldObject generator, uint profileId) + //{ + // if (!EncounterSomething.TryGetValue(generator.WeenieClassId, out var encounter)) + // return true; + + // if (!encounter.TryGetValue((int)profileId, out var nextAvailable)) + // return true; + + // if (DateTime.UtcNow > nextAvailable) + // return true; + + // return false; + //} } } diff --git a/Source/ACE.Server/WorldObjects/WorldObject_Generators.cs b/Source/ACE.Server/WorldObjects/WorldObject_Generators.cs index 6915571303..747d83102b 100644 --- a/Source/ACE.Server/WorldObjects/WorldObject_Generators.cs +++ b/Source/ACE.Server/WorldObjects/WorldObject_Generators.cs @@ -25,7 +25,12 @@ partial class WorldObject /// (spawns other world objects) /// public bool IsGenerator { get => GeneratorProfiles != null && GeneratorProfiles.Count > 0; } - + + /// + /// Is this WorldObject created from an Encounter, Set by Landblock + /// + public bool IsEncounter { get; set; } + //public List History = new List(); /// @@ -116,8 +121,9 @@ public void SelectAProfile() //var totalProbability = rng_selected ? GetTotalProbability() : 1.0f; //var rng = ThreadSafeRandom.Next(0.0f, totalProbability); - //var rng = ThreadSafeRandom.Next(0.0f, 1.0f); - var rng = ThreadSafeRandom.Next(0.0f, GetTotalProbability()); + var rng = ThreadSafeRandom.Next(0.0f, 1.0f); + //var rng = ThreadSafeRandom.Next(0.0f, GetTotalProbability()); + //var rng = ThreadSafeRandom.Next(0.0f, GetMaxProbability()); for (var i = 0; i < GeneratorProfiles.Count; i++) { @@ -135,6 +141,30 @@ public void SelectAProfile() if (!profile.IsAvailable) continue; + //if (IsEncounter && CurrentLandblock != null && !CurrentLandblock.CheckEncounterProfileIsAvailable(this, profile.Id)) + // continue; + //if (IsEncounter) + //{ + // if (CurrentLandblock != null) + // { + // if (!CurrentLandblock.CheckEncounterProfileIsAvailable(this, profile.Id)) + // continue; + + // //var adjacentIsAvailable = true; + // //foreach(var adjacent in CurrentLandblock.Adjacents) + // //{ + // // if (!adjacent.CheckEncounterProfileIsAvailable(this, profile.Id)) + // // { + // // adjacentIsAvailable = false; + // // break; + // // } + // //} + + // //if (!adjacentIsAvailable) + // // continue; + // } + //} + if (profile.RegenLocationType.HasFlag(RegenLocationType.Treasure)) { if (profile.Biota.InitCreate > 1) @@ -151,8 +181,8 @@ public void SelectAProfile() } //var probability = rng_selected ? GetAdjustedProbability(i) : profile.Biota.Probability; - //var probability = profile.Biota.Probability; - var probability = GetAdjustedProbability(i); + var probability = profile.Biota.Probability; + //var probability = GetAdjustedProbability(i); if (rng < probability || probability == -1) { @@ -308,7 +338,7 @@ public int GetSpawnObjectsForProfile(GeneratorProfile profile) if (numObjects == 0 && initCreate == 0) log.Warn($"[GENERATOR] 0x{Guid}:{WeenieClassId} {Name}.GetSpawnObjectsForProfile(profile[{profile.LinkId}]): profile.InitCreate = {profile.InitCreate} | profile.MaxCreate = {profile.MaxCreate} | profile.WeenieClassId = {profile.WeenieClassId} | Profile Init invalid, cannot spawn."); else if (numObjects == 0) - log.Warn($"[GENERATOR] 0x{Guid}:{WeenieClassId} {Name}.GetSpawnObjectsForProfile(profile[{profile.LinkId}]): profile.InitCreate = {profile.InitCreate} | profile.MaxCreate = {profile.MaxCreate} | profile.WeenieClassId = {profile.WeenieClassId} | genSlotsAvailable = {genSlotsAvailable} | profileSlotsAvailable = {profileSlotsAvailable} | numObjects = {numObjects}, cannot spawn."); + log.Warn($"[GENERATOR] 0x{Guid}:{WeenieClassId} {Name}.GetSpawnObjectsForProfile(profile[{profile.LinkId}]): profile.InitCreate = {profile.InitCreate} | profile.MaxCreate = {profile.MaxCreate} | profile.WeenieClassId = {profile.WeenieClassId} | genSlotsAvailable = {genSlotsAvailable} | profileSlotsAvailable = {profileSlotsAvailable} | numObjects = {numObjects}, cannot spawn."); return numObjects; } @@ -355,7 +385,7 @@ public void CheckGeneratorStatus() case GeneratorTimeType.Day: CheckTimeOfDayStatus(); break; - } + } } /// @@ -364,7 +394,7 @@ public void CheckGeneratorStatus() public void CheckTimeOfDayStatus() { var prevDisabled = GeneratorDisabled; - + var isDay = Timers.CurrentInGameTime.IsDay; var isDayGenerator = GeneratorTimeType == GeneratorTimeType.Day; @@ -656,6 +686,8 @@ public void Generator_Generate() { //Console.WriteLine($"{Name}.Generator_Generate({RegenerationInterval})"); + CheckForStaleEncounters(); + if (!GeneratorDisabled) { if (CurrentlyPoweringUp) @@ -684,6 +716,8 @@ public void Generator_Generate() foreach (var profile in GeneratorProfiles) profile.Spawn_HeartBeat(); + + //CheckForStaleEncounters(); } public virtual void ResetGenerator() @@ -703,5 +737,26 @@ public virtual void ResetGenerator() } return null; } + + /// + /// If Generator has been marked an Encounter by Landblock, check for idle, stale profiles and reset them for long lived landblocks. + /// + public void CheckForStaleEncounters() + { + if (!IsEncounter) return; + + var hasNonWorldObjects = 0; + var hasAwakeCreatures = 0; + var hasOpenContainers = 0; + var hasUnlockedChests = 0; + + var idleStaleProfiles = GeneratorProfiles.Where(p => p.IsMaxed && (DateTime.UtcNow > p.StaleTime) && p.IsAbleToBeMarkedStale(ref hasNonWorldObjects, ref hasAwakeCreatures, ref hasOpenContainers, ref hasUnlockedChests)); + + foreach (var profile in idleStaleProfiles) + { + profile.Reset(); + profile.NextAvailable = DateTime.UtcNow.AddSeconds(profile.Delay); + } + } } }