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);
+ }
+ }
}
}