string IDebug.StatusText() { string msg = DisplayName; msg += "\n\nStats"; for (int i = 0; i < m_Stats.Count; i++) { msg += "\n" + m_Stats[i].statusDescription; } msg += GetActiveInfluencersDescription(); msg += "\n\nUnsatisfied Desired States"; if (UnsatisfiedDesiredStates.Count == 0) { msg += "\nNone"; } for (int i = 0; i < UnsatisfiedDesiredStates.Count; i++) { StatSO stat = GetOrCreateStat(UnsatisfiedDesiredStates[i].statTemplate); msg += "\nIs not "; msg += UnsatisfiedDesiredStates[i].name + " "; msg += " (" + stat.name + " should be " + UnsatisfiedDesiredStates[i].objective + " " + UnsatisfiedDesiredStates[i].normalizedTargetValue + ")"; } return(msg); }
string IDebug.StatusText() { string msg = DisplayName; msg += "\n\nStats"; for (int i = 0; i < m_Stats.Count; i++) { msg += "\n" + m_Stats[i].statusDescription; } msg += GetActiveInfluencersDescription(); msg += "\n\nUnsatisfied Desired States"; if (UnsatisfiedDesiredStates.Count == 0) { msg += "\nNone"; } for (int i = 0; i < UnsatisfiedDesiredStates.Count; i++) { StatSO stat = GetOrCreateStat(UnsatisfiedDesiredStates[i].statTemplate); msg += "\nIs not "; msg += UnsatisfiedDesiredStates[i].name + " "; msg += " (" + stat.name + " should be " + UnsatisfiedDesiredStates[i].objective + " " + UnsatisfiedDesiredStates[i].normalizedTargetValue + ")"; } if (Memory == null) { msg += "\n\nThis actor has no memory."; } else { msg += "\n\nThis actor has a memory."; msg += "\nShort term memories: " + Memory.GetShortTermMemories().Length; msg += "\nLong term memories: " + Memory.GetLongTermMemories().Length; } if (ActiveBlockingBehaviour != null) { float timeLeft = Mathf.Clamp(ActiveBlockingBehaviour.EndTime - Time.timeSinceLevelLoad, 0, float.MaxValue); msg += "\n\nCurrent Behaviour"; msg += "\n" + ActiveBlockingBehaviour + " (time to abort / end " + timeLeft.ToString("0.0") + ")"; if (TargetInteractable != null) { msg += "\nTarget interaction: " + TargetInteractable.InteractionName + " at " + TargetInteractable.name; } else { msg += "\nNo target interaction"; } } else { msg += "\n\nCurrent Behaviour"; msg += "\nNone"; } return(msg); }
/// <summary> /// Add an influencer to this controller. If this controller is not managing the required stat then /// do nothing. /// </summary> /// <param name="influencer">The influencer to add.</param> /// <returns>True if the influencer was added, otherwise false.</returns> public override bool TryAddInfluencer(StatInfluencerSO influencer) { if (Memory != null && influencer.Trigger != null) { MemorySO[] memories = Memory.GetAllMemoriesAbout(influencer.Generator); for (int i = 0; i < memories.Length; i++) { if (memories[i].stat == influencer.stat && memories[i].time + memories[i].cooldown > Time.timeSinceLevelLoad) { return(false); } } } if (Memory != null) { StatSO stat = GetOrCreateStat(influencer.stat); List <StateSO> states = GetDesiredStatesFor(stat); bool isGood = true; for (int i = 0; i < states.Count; i++) { switch (states[i].objective) { case StateSO.Objective.LessThan: if (influencer.maxChange > 0) { isGood = false; } break; case StateSO.Objective.Approximately: float currentDelta = states[i].normalizedTargetValue - stat.NormalizedValue; float influencedDelta = states[i].normalizedTargetValue - (stat.NormalizedValue + influencer.maxChange); if (currentDelta < influencedDelta) { isGood = false; } break; case StateSO.Objective.GreaterThan: if (influencer.maxChange < 0) { isGood = false; } break; } if (influencer.Generator != null) { Memory.AddMemory(influencer, isGood); } } } return(base.TryAddInfluencer(influencer)); }
public IEnumerator StatsControllerAddUnknownStat() { string statName = "Test Unkown Stat"; StatsController controller = new GameObject().AddComponent <StatsController>(); StatSO stat = controller.GetOrCreateStat(statName, 50); Assert.True(stat.name == statName, "Did not create the unknown stat."); yield return(null); }
/// <summary> /// Get the current goal for a given stat. That is do we currently want to /// increase, decrease or maintaint his stat. /// If there are multiple desired states then an attempt is made to create /// a meaningful goal. For example, if there are multiple greater than goals /// then the target will be the highest goal. /// /// If there are conflicting goals, /// such as a greater than and a less than then lessThan will take preference /// over greaterThan, but approximately will always be given prefernce. /// </summary> /// <returns>The current goal for the stat.</returns> public Goal GetGoalFor(StatSO stat) { float lessThan = float.MaxValue; float greaterThan = float.MinValue; List <StateSO> states = GetDesiredStatesFor(stat); for (int i = 0; i < states.Count; i++) { switch (states[i].objective) { case Objective.LessThan: if (stat.NormalizedValue >= states[i].normalizedTargetValue && states[i].normalizedTargetValue < lessThan) { lessThan = states[i].normalizedTargetValue; } break; case Objective.Approximately: if (Mathf.Approximately(stat.NormalizedValue, states[i].normalizedTargetValue)) { if (stat.NormalizedValue > states[i].normalizedTargetValue) { return(StateSO.Goal.Decrease); } else { return(StateSO.Goal.Increase); } } break; case Objective.GreaterThan: if (stat.NormalizedValue <= states[i].normalizedTargetValue && states[i].normalizedTargetValue > greaterThan) { greaterThan = states[i].normalizedTargetValue; } break; } } if (lessThan != float.MaxValue) { return(StateSO.Goal.Decrease); } if (greaterThan != float.MinValue) { return(StateSO.Goal.Increase); } return(StateSO.Goal.NoAction); }
/// <summary> /// Add an influencer to this controller. If this controller is not managing the required stat then /// do nothing. /// </summary> /// <param name="influencer">The influencer to add.</param> /// <returns>True if the influencer was added, otherwise false.</returns> public bool TryAddInfluencer(StatInfluencerSO influencer) { if (m_Memory != null && influencer.generator != null) { MemorySO[] memories = m_Memory.GetAllMemoriesAbout(influencer.generator); for (int i = 0; i < memories.Length; i++) { if (memories[i].stat == influencer.stat && memories[i].time + memories[i].cooldown > Time.timeSinceLevelLoad) { return(false); } } } StatSO stat = GetOrCreateStat(influencer.stat.name); bool isGood = true; switch (stat.desiredState.objective) { case DesiredState.Objective.LessThan: if (influencer.maxChange > 0) { isGood = false; } break; case DesiredState.Objective.Approximately: float currentDelta = stat.desiredState.targetValue - stat.value; float influencedDelta = stat.desiredState.targetValue - (stat.value + influencer.maxChange); if (currentDelta < influencedDelta) { isGood = false; } break; case DesiredState.Objective.GreaterThan: if (influencer.maxChange < 0) { isGood = false; } break; } if (m_Memory != null) { m_Memory.AddMemory(influencer, isGood); } m_StatsInfluencers.Add(influencer); return(true); }
/// <summary> /// Get the Stat of a given type. /// </summary> /// <param name="name">The name of the stat we want to retrieve</param> /// <returns>The stat, if it exists, or null.</returns> public StatSO GetStat(StatSO template) { for (int i = 0; i < m_Stats.Count; i++) { if (template != null) { if (m_Stats[i].name == template.name) { return(m_Stats[i]); } } } return(null); }
/// <summary> /// Get a list of stats that are currently outside the desired state for that stat. /// This can be used, for example. by AI deciding what action to take next. /// </summary> /// <returns>A list of stats that are not in a desired state.</returns> public StatSO[] GetStatsNotInDesiredState() { List <StatSO> stats = new List <StatSO>(); for (int i = 0; i < desiredStates.Length; i++) { StatSO stat = GetOrCreateStat(desiredStates[i].stat.name); if (stat.goal != DesiredState.Goal.NoAction) { stats.Add(stat); } } return(stats.ToArray()); }
internal List <StateSO> GetDesiredStatesFor(StatSO stat) { List <StateSO> states = new List <StateSO>(); for (int i = 0; i < DesiredStates.Length; i++) { if (DesiredStates[i].statTemplate != null && stat.name == DesiredStates[i].statTemplate.name) { states.Add(DesiredStates[i]); break; } } return(states); }
public IEnumerator StatsControllerAddUnknownStat() { StatSO template = ScriptableObject.CreateInstance <StatSO>(); string statName = "Test Unkown Stat"; template.name = statName; Brain controller = new GameObject().AddComponent <Brain>(); StatSO stat = controller.GetOrCreateStat(template, 50); Assert.True(stat.name == statName, "Did not create the unknown stat."); yield return(null); }
/// <summary> /// Apply an immediate change to a given Stats, if this controller is tracking that stat. /// </summary> /// /// <param name="influencer">The influencer imparting the change.</param> internal void ChangeStat(StatInfluencerSO influencer) { StatSO stat = GetOrCreateStat(influencer.stat.name); float change; if (influencer.duration > 0) { change = Mathf.Clamp(influencer.changePerSecond * (Time.timeSinceLevelLoad - m_TimeOfLastUpdate), float.MinValue, influencer.maxChange - influencer.influenceApplied); } else { change = Mathf.Clamp(influencer.maxChange, influencer.maxChange - influencer.influenceApplied, influencer.maxChange); } stat.value += change; influencer.influenceApplied += change; //Debug.Log(gameObject.name + " changed stat " + influencer.statName + " by " + change); }
private List <StatSO> GetStatsDesiredForState(StateSO state) { List <StatSO> stats = new List <StatSO>(); if (state.statTemplate != null) { StatSO stat = GetOrCreateStat(state.statTemplate); if (GetGoalFor(state.statTemplate) != StateSO.Goal.NoAction) { stats.Add(stat); } } for (int idx = 1; idx < state.SubStates.Length; idx++) { stats.AddRange(GetStatsDesiredForState(state.SubStates[idx])); } return(stats); }
/// <summary> /// Get the stat object representing a named stat. If it does not already /// exist it will be created with a base value. /// </summary> /// <param name="name">Tha name of the stat to Get or Create for this controller</param> /// <returns>A StatSO representing the named stat</returns> public StatSO GetOrCreateStat(StatSO template, float?value = null) { StatSO stat = GetStat(template); if (stat != null) { return(stat); } stat = Instantiate(template); stat.name = template.name; if (value != null) { stat.NormalizedValue = (float)value; } m_Stats.Add(stat); return(stat); }
/// <summary> /// Apply a change from a stat influencer. /// </summary> /// /// <param name="statsTracker">The stats tracker managing the stats to be changed.</param> internal void ChangeStat(StatsTracker statsTracker) { StatSO statToUpdate = statsTracker.GetOrCreateStat(stat); float change; if (duration > 0) { change = Mathf.Clamp(changePerSecond * (Time.timeSinceLevelLoad - m_TimeOfLastUpdate), float.MinValue, Mathf.Abs(maxChange) - Mathf.Abs(influenceApplied)); } else { change = Mathf.Clamp(maxChange, maxChange - influenceApplied, Mathf.Abs(maxChange)); } statToUpdate.Value += change; influenceApplied += change; m_TimeOfLastUpdate = Time.timeSinceLevelLoad; //Debug.Log(gameObject.name + " changed stat " + influencer.statName + " by " + change); }
public IEnumerator StatInstantInfluencer() { string statName = "Test Immediate Influencer Stat"; StatsController controller = new GameObject().AddComponent <StatsController>(); StatInfluencerSO influencer = ScriptableObject.CreateInstance <StatInfluencerSO>(); StatSO stat = controller.GetOrCreateStat(statName); influencer.stat = stat; influencer.maxChange = 10; influencer.duration = 0; Assert.True(controller.TryAddInfluencer(influencer), "Was not able to add the Stat influencer"); yield return(null); Assert.True(controller.GetOrCreateStat(statName).value > 0, "Seems the influencer has had no effect."); yield return(null); }
/// <summary> /// Get the stat object representing a named stat. If it does not already /// exist it will be created with a base value. /// </summary> /// <param name="name">Tha name of the stat to Get or Create for this controller</param> /// <returns>A StatSO representing the named stat</returns> public StatSO GetOrCreateStat(string name, float value = 0) { StatSO stat = GetStat(name); if (stat != null) { return(stat); } stat = ScriptableObject.CreateInstance <StatSO>(); stat.name = name; stat.value = value; for (int i = 0; i < desiredStates.Length; i++) { if (stat.GetType() == desiredStates[i].stat.GetType()) { stat.desiredState = desiredStates[i]; break; } } m_Stats.Add(stat); return(stat); }
public IEnumerator StatInstantInfluencer() { StatSO template = ScriptableObject.CreateInstance <StatSO>(); string statName = "Test Immediate Influencer Stat"; template.name = statName; Brain controller = new GameObject().AddComponent <Brain>(); StatInfluencerSO influencer = ScriptableObject.CreateInstance <StatInfluencerSO>(); StatSO stat = controller.GetOrCreateStat(template); influencer.stat = stat; influencer.maxChange = 10; influencer.duration = 0; Assert.True(controller.TryAddInfluencer(influencer), "Was not able to add the Stat influencer"); yield return(new WaitForSeconds(0.51f)); // ensure the brain has time to apply the influencer Assert.True(stat.NormalizedValue > 0, "Seems the influencer has had no effect."); yield return(null); }
/// <summary> /// Test if the stat tracker is currently tracking the stat provided in the template. /// </summary> /// <param name="statTemplate">The stat to test for</param> /// <returns>True if the stat is currently being tracked.</returns> internal bool HasStat(StatSO statTemplate) { return(GetStat(statTemplate) != null); }