using Photon.Deterministic; using Quantum.Collections; namespace Quantum { public unsafe partial struct UtilityReasoner { public void Initialize(Frame frame, AssetRefUTRoot utRootRef = default, EntityRef entity = default) { // If we don't receive the UTRoot as parameter, we try to find it on the component itself // Useful for pre-seting the UTRoot on a Prototype UTRoot utRootInstance; if (utRootRef == default) { utRootInstance = frame.FindAsset(UTRoot.Id); } else { UTRoot = utRootRef; utRootInstance = frame.FindAsset(utRootRef.Id); } // Initialize the Reasoner's considerations. // Can be useful further when creating dynamically added Considerations to the Agent (runtime) QList considerationsList = frame.AllocateList(); for (int i = 0; i < utRootInstance.ConsiderationsRefs.Length; i++) { considerationsList.Add(utRootInstance.ConsiderationsRefs[i]); } Considerations = considerationsList; CooldownsDict = frame.AllocateDictionary(); QList previousExecution = frame.AllocateList(); for (int i = 0; i < 6; i++) { previousExecution.Add(default); } PreviousExecution = previousExecution; MomentumList = frame.AllocateList(); if (entity != default) { UTManager.SetupDebugger?.Invoke(entity, utRootInstance.Path); } } public void Free(Frame frame) { UTRoot = default; frame.FreeList(Considerations); frame.FreeDictionary(CooldownsDict); frame.FreeList(PreviousExecution); frame.FreeList(MomentumList); } public void Update(Frame frame, UtilityReasoner* reasoner, EntityRef entity = default) { Consideration[] considerations = SolveConsiderationsList(frame, Considerations, reasoner, entity); Consideration chosenConsideration = SelectBestConsideration(frame, considerations, 1, reasoner, entity); if (chosenConsideration != default) { chosenConsideration.OnUpdate(frame, reasoner, entity); UTManager.ConsiderationChosen?.Invoke(entity, chosenConsideration.Identifier.Guid.Value); } TickMomentum(frame, entity); } public Consideration[] SolveConsiderationsList(Frame frame, QListPtr considerationsRefs, UtilityReasoner* reasoner, EntityRef entity = default) { QList solvedRefs = frame.ResolveList(considerationsRefs); Consideration[] considerationsArray = new Consideration[solvedRefs.Count]; for (int i = 0; i < solvedRefs.Count; i++) { considerationsArray[i] = frame.FindAsset(solvedRefs[i].Id); } return considerationsArray; } public Consideration SelectBestConsideration(Frame frame, Consideration[] considerations, byte depth, UtilityReasoner* reasoner, EntityRef entity = default) { if (considerations == default) return null; QList momentumList = frame.ResolveList(MomentumList); // We get the Rank of every Consideration Set // This "filters" the Considerations with higher absolute utility AssetRefConsideration[] highRankConsiderations = new AssetRefConsideration[considerations.Length]; int highestRank = -1; int counter = 0; QDictionary cooldowns = frame.ResolveDictionary(CooldownsDict); for (int i = 0; i < considerations.Length; i++) { Consideration consideration = considerations[i]; // Force low Rank for Considerations in Cooldown if (cooldowns.Count > 0 && cooldowns.ContainsKey(considerations[i]) == true) { cooldowns[considerations[i]] -= frame.DeltaTime; if (cooldowns[considerations[i]] <= 0) { cooldowns.Remove(considerations[i]); } { continue; } } // If the Consideration has Momentum, then it's Rank should is defined by it // Otherwise, we calculate the Rank dynamically int rank; if (ContainsMomentum(momentumList, consideration, out var momentum) == true) { rank = momentum.Value; } else { rank = consideration.GetRank(frame, entity); } if (rank > highestRank) { counter = 0; highestRank = rank; highRankConsiderations[counter] = considerations[i]; } else if (highestRank == rank) { counter++; highRankConsiderations[counter] = considerations[i]; } } // We clean the indices on the high rank sets that were not selected for (int i = counter + 1; i < highRankConsiderations.Length; i++) { if (highRankConsiderations[i] == default) break; highRankConsiderations[i] = default; } // Based on the higher rank, we check which Considerations sets have greater utility // Then we choose that set this frame Consideration chosenConsideration = default; FP highestScore = FP.UseableMin; for (int i = 0; i <= counter; i++) { if (highRankConsiderations[i] == default) continue; Consideration consideration = frame.FindAsset(highRankConsiderations[i].Id); FP score = consideration.Score(frame, entity); if (highestScore < score) { highestScore = score; chosenConsideration = consideration; } } if (chosenConsideration != default) { // If the chosen Consideration and it is not already under Momentum, // we add add it there, replacing the previous Momentum (if any) if (chosenConsideration.MomentumData.Value > 0 && ContainsMomentum(momentumList, chosenConsideration, out var momentum) == false) { InsertMomentum(frame, momentumList, chosenConsideration); } // If the chosen Consideration has cooldown and it is not yet on the cooldowns dictionary, // we add it there if (chosenConsideration.Cooldown > 0 && cooldowns.ContainsKey(chosenConsideration) == false) { cooldowns.Add(chosenConsideration, chosenConsideration.Cooldown); } // Add the chosen set to the choices history OnConsiderationChosen(frame, reasoner, chosenConsideration, entity); } else { OnNoConsiderationChosen(frame, reasoner, depth, entity); } // We return the chosen set so it can be executed return chosenConsideration; } #region Momentum private bool ContainsMomentum(QList momentumList, Consideration consideration, out UTMomentumData momentum) { for (int i = 0; i < momentumList.Count; i++) { if (momentumList[i].ConsiderationRef == consideration) { momentum = momentumList[i].MomentumData; return true; } } momentum = default; return false; } private void InsertMomentum(Frame frame, QList momentumList, AssetRefConsideration considerationRef) { Consideration newConsideration = frame.FindAsset(considerationRef.Id); // First, we check if this should be a replacement, which happens if: // . The momentum list already have that same Depth added // . Or when it have a higher Depth added bool wasReplacedment = false; for (int i = 0; i < momentumList.Count; i++) { Consideration currentConsideration = frame.FindAsset(momentumList[i].ConsiderationRef.Id); if (currentConsideration.Depth == newConsideration.Depth || currentConsideration.Depth > newConsideration.Depth) { momentumList.GetPointer(i)->ConsiderationRef = considerationRef; momentumList.GetPointer(i)->MomentumData = frame.FindAsset(considerationRef.Id).MomentumData; // We clear the rightmost momentum entries if (i < momentumList.Count - 1) { for (int k = i + 1; k < momentumList.Count; k++) { momentumList.RemoveAt(k); } } wasReplacedment = true; break; } } // If there was no replacement, we simply add it to the end of the list as this // consideration probably has higher Depth than the others currently on the list // which can also mean that the list was empty if (wasReplacedment == false) { UTMomentumPack newMomentum = new UTMomentumPack() { ConsiderationRef = considerationRef, MomentumData = frame.FindAsset(considerationRef.Id).MomentumData, }; momentumList.Add(newMomentum); } } private void TickMomentum(Frame frame, EntityRef entity = default) { QList momentumList = frame.ResolveList(MomentumList); // We decrease the timer and check if it is time already to decay all of the current Momentums TimeToTick -= frame.DeltaTime; bool decay = false; if (TimeToTick <= 0) { decay = true; TimeToTick = 1; } for (int i = 0; i < momentumList.Count; i++) { UTMomentumPack* momentum = momentumList.GetPointer(i); // If we currently have a commitment, we check if it is done already // If it is done, that Consideration's Rank shall be re-calculated // If it is not done, then the Consideration's Rank will be kept due to the commitment // unless some other Consideration has greater Rank and replaces the current commitment Consideration momentumConsideration = frame.FindAsset(momentum->ConsiderationRef.Id); if (momentum->MomentumData.Value > 0 && momentumConsideration.MomentumData.DecayAmount > 0) { if (decay) { momentum->MomentumData.Value -= momentumConsideration.MomentumData.DecayAmount; } } bool isDone = false; if (momentumConsideration.Commitment != default) { isDone = momentumConsideration.Commitment.Execute(frame, entity); } if (isDone == true || momentum->MomentumData.Value <= 0) { momentum->MomentumData.Value = 0; momentumList.RemoveAt(i); } } } #endregion #region ConsiderationsChoiceReactions private static void OnConsiderationChosen(Frame frame, UtilityReasoner* reasoner, AssetRefConsideration chosenConsiderationRef, EntityRef entity = default) { Consideration chosenConsideration = frame.FindAsset(chosenConsiderationRef.Id); QList previousExecution = frame.ResolveList(reasoner->PreviousExecution); if (previousExecution[chosenConsideration.Depth - 1] != chosenConsideration) { // Exit the one that we're replacing var replacedSet = frame.FindAsset(previousExecution[chosenConsideration.Depth - 1].Id); if (replacedSet != default) { replacedSet.OnExit(frame, reasoner, entity); } // Exit the consecutive ones for (int i = chosenConsideration.Depth; i < previousExecution.Count; i++) { var cs = frame.FindAsset(previousExecution[i].Id); if (cs == default) break; cs.OnExit(frame, reasoner, entity); previousExecution[i] = default; } // Insert and Enter on the new chosen consideration previousExecution[chosenConsideration.Depth - 1] = chosenConsideration; chosenConsideration.OnEnter(frame, reasoner, entity); } } private static void OnNoConsiderationChosen(Frame frame, UtilityReasoner* reasoner, byte depth, EntityRef entity = default) { QList previousExecution = frame.ResolveList(reasoner->PreviousExecution); for (int i = depth - 1; i < previousExecution.Count; i++) { var cs = frame.FindAsset(previousExecution[i].Id); if (cs == default) break; cs.OnExit(frame, reasoner, entity); previousExecution[i] = default; } } #endregion } }