ace/Source/ACE.Server/WorldObjects/WorldObject_Generators.cs
Ty Conner ca6c8d1fc3
Fixes for Anniversary Event emotes (#1086)
* Fixes for Anniversary Event emotes

* Add Pre+Permaload of Global Event Landblock

* Making requested changes

* Add correct check for GlobalEventLandblockPreloading

* Further requested changes. New config setup for Preloaded and Permaloaded Landblocks

* Minor cleanup

* Add a hardcoded fallback incase config file not updated

* Update example to fix syntax error

* Minor tweak to example and console output
2018-11-01 20:10:54 -04:00

523 lines
18 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using ACE.Common;
using ACE.Database.Models.Shard;
using ACE.Entity.Enum;
using ACE.Server.Entity;
using ACE.Server.Managers;
namespace ACE.Server.WorldObjects
{
/// <summary>
/// A generator handles spawning other WorldObjects
/// </summary>
partial class WorldObject
{
/// <summary>
/// The generator that spawned this WorldObject
/// </summary>
public WorldObject Generator { get; set; }
/// <summary>
/// Returns TRUE if this object is a generator
/// (spawns other world objects)
/// </summary>
public bool IsGenerator { get => GeneratorProfiles.Count > 0; }
/// <summary>
/// Returns TRUE if generator is linked
/// </summary>
public bool IsLinked;
//public List<string> History = new List<string>();
/// <summary>
/// A generator can have multiple profiles / spawn multiple types of objects
/// Each generator profile can in turn spawn multiple objects (init_create / max_create)
/// </summary>
public List<Generator> GeneratorProfiles;
/// <summary>
/// Creates a list of active generator profiles
/// from a list of biota generators
/// </summary>
public void AddGeneratorProfiles()
{
GeneratorProfiles = new List<Generator>();
foreach (var generator in Biota.BiotaPropertiesGenerator)
GeneratorProfiles.Add(new Generator(this, generator));
}
/// <summary>
/// The number of currently spawned objects +
/// the number of objects currently in the spawn queue
/// </summary>
//public int CurrentCreate { get => GeneratorProfiles.Select(i => i.CurrentCreate).Sum(); }
public int CurrentCreate; // maintained directly
/// <summary>
/// A list of indices into GeneratorProfiles where CurrentCreate > 0
/// </summary>
public List<int> GeneratorActiveProfiles
{
get
{
var activeProfiles = new List<int>();
for (var i = 0; i < GeneratorProfiles.Count; i++)
{
var profile = GeneratorProfiles[i];
if (profile.CurrentCreate > 0)
activeProfiles.Add(i);
}
return activeProfiles;
}
}
/// <summary>
/// Returns TRUE if all generator profiles are at init objects created
/// </summary>
public bool AllProfilesInitted { get => GeneratorProfiles.Where(i => i.InitObjectsSpawned).Count() == GeneratorProfiles.Count; }
/// <summary>
/// Retunrs TRUE if all generator profiles are at max objects created
/// </summary>
public bool AllProfilesMaxed { get => GeneratorProfiles.Where(i => i.MaxObjectsSpawned).Count() == GeneratorProfiles.Count; }
/// <summary>
/// Adds initial objects to the spawn queue based on RNG rolls
/// </summary>
public void SelectProfilesInit()
{
//History.Add($"[{DateTime.UtcNow}] - SelectProfilesInit()");
bool rng_selected = false;
while (true)
{
if (StopConditionsInit) return;
var totalProbability = rng_selected ? GetTotalProbability() : 1.0f;
var rng = Physics.Common.Random.RollDice(0.0f, totalProbability);
for (var i = 0; i < GeneratorProfiles.Count; i++)
{
var profile = GeneratorProfiles[i];
// is this profile already at its max_create?
if (profile.MaxObjectsSpawned)
continue;
var probability = rng_selected ? GetAdjustedProbability(i) : profile.Biota.Probability;
if (rng < probability || probability == -1)
{
var numObjects = GetInitObjects(profile);
profile.Enqueue(numObjects);
//var rng_str = probability == -1 ? "" : "RNG ";
//History.Add($"[{DateTime.UtcNow}] - SelectProfilesInit() - {rng_str}selected slot {i} to spawn, adding {numObjects} objects ({profile.CurrentCreate}/{profile.MaxCreate})");
// if RNG rolled, we are done with this roll
if (profile.Biota.Probability != -1)
{
rng_selected = true;
break;
}
// stop conditions
if (StopConditionsInit) return;
}
}
}
}
/// <summary>
/// Adds subsequent objects to the spawn queue based on RNG rolls
/// </summary>
public void SelectProfilesMax()
{
//History.Add($"[{DateTime.UtcNow}] - SelectProfilesMax()");
// stop conditions
if (StopConditionsMax) return;
// only roll once here?
var totalProbability = GetTotalProbability();
var rng = Physics.Common.Random.RollDice(0.0f, totalProbability);
for (var i = 0; i < GeneratorProfiles.Count; i++)
{
var profile = GeneratorProfiles[i];
// is this profile already at its max_create?
if (profile.MaxObjectsSpawned)
continue;
var probability = GetAdjustedProbability(i);
if (rng < probability || probability == -1)
{
var rng_str = probability == -1 ? "" : "RNG ";
profile.Enqueue(1);
//History.Add($"[{DateTime.UtcNow}] - SelectProfilesMax() - {rng_str}selected slot {i} to spawn ({profile.CurrentCreate}/{profile.MaxCreate})");
// if RNG rolled, we are done with this roll
if (profile.Biota.Probability != -1)
break;
// stop conditions
if (StopConditionsMax) return;
}
}
}
/// <summary>
/// Returns the total probability of all RNG profiles
/// which arent at max objects spawned yet
/// </summary>
public float GetTotalProbability()
{
var totalProbability = 0.0f;
var lastProbability = 0.0f;
foreach (var profile in GeneratorProfiles)
{
var probability = profile.Biota.Probability;
if (probability == -1)
continue;
if (!profile.MaxObjectsSpawned)
{
if (lastProbability > probability)
lastProbability = 0.0f;
var diff = probability - lastProbability;
totalProbability += diff;
}
lastProbability = probability;
}
return totalProbability;
}
/// <summary>
/// Returns the max probability from all the generator profiles
/// </summary>
public float GetMaxProbability()
{
var maxProbability = float.MinValue;
// note: this will also include maxed generator profiles!
foreach (var profile in GeneratorProfiles)
{
var probability = profile.Biota.Probability;
if (probability > maxProbability)
maxProbability = probability;
}
return maxProbability;
}
/// <summary>
/// Returns the adjust probability for a generator profile index,
/// taking into account previous profile probabilities which are already at max objects spawned
/// </summary>
public float GetAdjustedProbability(int index)
{
// say theres a generator with 2 profiles
// the first has a 99% chance to spawn, and the second has a 1% chance
// the generator init_create is 1, and the max_create is 2
// when the generator first spawns in, the first object is created, as expected
// then the hearbeat happens later, and it sees it can spawn up to 1 additional object
// the rare item is the only item left, with the 1 % chance
// so the question is, in that scenario, would the rare item always spawn then?
// or would it only do 1 roll, and the rare item would still have only a 1% chance to spawn?
var profile = GeneratorProfiles[index];
if (profile.Biota.Probability == -1)
return -1;
var totalProbability = 0.0f;
var lastProbability = 0.0f;
for (var i = 0; i <= index; i++)
{
var probability = profile.Biota.Probability;
if (probability == -1)
continue;
if (!profile.MaxObjectsSpawned)
{
if (lastProbability > probability)
lastProbability = 0.0f;
var diff = probability - lastProbability;
totalProbability += diff;
}
lastProbability = probability;
}
return totalProbability;
}
/// <summary>
/// Get the current number of objects to spawn
/// for profile initialization
/// </summary>
public int GetInitObjects(Generator profile)
{
// get the number of objects to spawn for this profile
// usually profile.InitCreate, not to exceed generator.InitCreate
var numObjects = profile.Biota.InitCreate;
var leftObjects = InitCreate - CurrentCreate;
if (numObjects > leftObjects && InitCreate != 0 && !IsLinked)
numObjects = leftObjects;
return numObjects;
}
/// <summary>
/// Returns TRUE if stop conditions have been reached for initial generator spawn
/// </summary>
public bool StopConditionsInit
{
get
{
if (CurrentCreate >= InitCreate && !IsLinked)
{
if (CurrentCreate > InitCreate)
Console.WriteLine($"{Name}.StopConditionsInit(): CurrentCreate({CurrentCreate}) > InitCreate({InitCreate})");
return true;
}
return AllProfilesInitted || AllProfilesMaxed;
}
}
/// <summary>
/// Returns TRUE if stop conditions have been reached for subsequent generator spawn
/// </summary>
public bool StopConditionsMax
{
get
{
if (CurrentCreate >= MaxCreate && MaxCreate != 0 && !IsLinked)
{
if (CurrentCreate > MaxCreate && MaxCreate != 0)
Console.WriteLine($"{Name}.StopConditionsMax(): CurrentCreate({CurrentCreate}) > MaxCreate({MaxCreate})");
return true;
}
return AllProfilesMaxed;
}
}
/// <summary>
/// Enables/disables a generator based on time status
/// </summary>
public void CheckGeneratorStatus()
{
switch (GeneratorTimeType)
{
// TODO: defined/night/day
case GeneratorTimeType.RealTime:
CheckRealTimeStatus();
break;
case GeneratorTimeType.Event:
CheckEventStatus();
break;
}
}
/// <summary>
/// Enables/disables a generator based on realtime between start/end time
/// </summary>
public void CheckRealTimeStatus()
{
var prevDisabled = GeneratorDisabled;
var now = (int)Time.GetUnixTime();
GeneratorDisabled = (now < GeneratorStartTime) || (now > GeneratorEndTime);
HandleStatus(prevDisabled);
}
/// <summary>
/// Enables/disables a generator based on world event status
/// </summary>
public void CheckEventStatus()
{
if (GeneratorEvent == null || GeneratorEvent == "")
return;
var prevState = GeneratorDisabled;
if (!EventManager.IsEventAvailable(GeneratorEvent))
return;
var enabled = EventManager.IsEventEnabled(GeneratorEvent);
var started = EventManager.IsEventStarted(GeneratorEvent);
GeneratorDisabled = !enabled || !started;
HandleStatus(prevState);
}
/// <summary>
/// Handles starting/stopping the generator
/// </summary>
public void HandleStatus(bool prevDisabled)
{
if (prevDisabled == GeneratorDisabled)
return; // no state change
if (prevDisabled)
StartGenerator();
else
DisableGenerator();
}
/// <summary>
/// Called when a generator is first created
/// </summary>
public void StartGenerator()
{
CurrentlyPoweringUp = true;
//foreach (var generator in GeneratorProfiles)
//generator.TrySpawn(true);
SelectProfilesInit();
CurrentlyPoweringUp = false;
}
/// <summary>
/// Disables a generator, and destroys all of its spawned objects
/// </summary>
public void DisableGenerator()
{
// generator has been disabled, de-spawn everything in registry and reset back to defaults
switch (GeneratorEndDestructionType)
{
case GeneratorDestruct.Kill:
foreach (var generator in GeneratorProfiles)
{
foreach (var rNode in generator.Spawned.Values)
{
if (rNode.WorldObject is Creature)
{
var wo = rNode.WorldObject as Creature;
wo.Smite(this);
}
}
generator.Spawned.Clear();
generator.SpawnQueue.Clear();
}
break;
case GeneratorDestruct.Nothing:
break;
case GeneratorDestruct.Destroy:
default:
foreach (var generator in GeneratorProfiles)
{
foreach (var rNode in generator.Spawned.Values)
rNode.WorldObject.Destroy();
generator.Spawned.Clear();
generator.SpawnQueue.Clear();
}
break;
}
}
/// <summary>
/// Callback system for objects notifying their generators of events,
/// ie. item pickup
/// </summary>
public void NotifyOfEvent(RegenerationType regenerationType)
{
if (GeneratorId == null) return;
if (!Generator.GeneratorDisabled)
{
foreach (var generator in Generator.GeneratorProfiles)
generator.NotifyGenerator(Guid, regenerationType);
}
Generator = null;
GeneratorId = null;
}
/// <summary>
/// Called by ActivateLinks in WorldObject_Links for generators
/// </summary>
public void AddGeneratorLinks()
{
if (GeneratorProfiles == null || GeneratorProfiles.Count == 0)
{
Console.WriteLine($"{Name}.AddGeneratorLinks(): no profiles to link!");
return;
}
var profileTemplate = GeneratorProfiles[0];
foreach (var link in LinkedInstances)
{
var profile = new BiotaPropertiesGenerator();
profile.WeenieClassId = link.WeenieClassId;
profile.ObjCellId = link.ObjCellId;
profile.OriginX = link.OriginX;
profile.OriginY = link.OriginY;
profile.OriginZ = link.OriginZ;
profile.AnglesW = link.AnglesW;
profile.AnglesX = link.AnglesX;
profile.AnglesY = link.AnglesY;
profile.AnglesZ = link.AnglesZ;
profile.Probability = profileTemplate.Biota.Probability;
profile.InitCreate = profileTemplate.Biota.InitCreate;
profile.MaxCreate = profileTemplate.Biota.MaxCreate;
profile.WhenCreate = profileTemplate.Biota.WhenCreate;
profile.WhereCreate = profileTemplate.Biota.WhereCreate;
GeneratorProfiles.Add(new Generator(this, profile));
}
IsLinked = true;
}
/// <summary>
/// Called every ~5 seconds for object generators
/// </summary>
public void Generator_HeartBeat()
{
if (!IsGenerator) return;
//Console.WriteLine($"{Name}.Generator_HeartBeat({HeartbeatInterval})");
if (!FirstEnterWorldDone)
FirstEnterWorldDone = true;
CheckGeneratorStatus();
if (!GeneratorEnteredWorld)
{
if (!GeneratorDisabled)
StartGenerator(); // spawn initial objects for this generator
GeneratorEnteredWorld = true;
}
else if (!GeneratorDisabled)
SelectProfilesMax();
foreach (var generator in GeneratorProfiles)
generator.HeartBeat();
}
}
}