using System;
using System.Collections.Generic;
using System.Linq;
using Artemis.Core.Internal;
using Artemis.Storage.Entities.Profile.Abstract;
using Artemis.Storage.Entities.Profile.Conditions;
namespace Artemis.Core;
///
/// Represents a condition that is based on a
///
public class EventCondition : CorePropertyChanged, INodeScriptCondition
{
private readonly string _displayName;
private readonly EventConditionEntity _entity;
private NodeScript _script;
private DefaultNode _startNode;
private DataModelPath? _eventPath;
private DateTime _lastProcessedTrigger;
private object? _lastProcessedValue;
private EventOverlapMode _overlapMode;
private EventToggleOffMode _toggleOffMode;
private EventTriggerMode _triggerMode;
private bool _wasMet;
///
/// Creates a new instance of the class
///
public EventCondition(RenderProfileElement profileElement)
{
ProfileElement = profileElement;
_entity = new EventConditionEntity();
_displayName = profileElement.GetType().Name;
_startNode = new EventConditionEventStartNode {X = -300};
_script = new NodeScript($"Activate {_displayName}", $"Whether or not the event should activate the {_displayName}", ProfileElement.Profile, new List {_startNode});
}
internal EventCondition(EventConditionEntity entity, RenderProfileElement profileElement)
{
ProfileElement = profileElement;
_entity = entity;
_displayName = profileElement.GetType().Name;
_startNode = new EventConditionEventStartNode();
_script = null!;
Load();
}
///
/// Gets the script that drives the event condition
///
public NodeScript Script
{
get => _script;
private set => SetAndNotify(ref _script, value);
}
///
/// Gets or sets the path to the event that drives this event condition
///
public DataModelPath? EventPath
{
get => _eventPath;
set => SetAndNotify(ref _eventPath, value);
}
///
/// Gets or sets how the condition behaves when the event fires.
///
public EventTriggerMode TriggerMode
{
get => _triggerMode;
set => SetAndNotify(ref _triggerMode, value);
}
///
/// Gets or sets how the condition behaves when events trigger before the timeline finishes in the
/// event trigger mode.
///
public EventOverlapMode OverlapMode
{
get => _overlapMode;
set => SetAndNotify(ref _overlapMode, value);
}
///
/// Gets or sets the mode for render elements when toggling off the event when using
/// .
///
public EventToggleOffMode ToggleOffMode
{
get => _toggleOffMode;
set => SetAndNotify(ref _toggleOffMode, value);
}
///
public IConditionEntity Entity => _entity;
///
public RenderProfileElement ProfileElement { get; }
///
public bool IsMet { get; private set; }
///
public void Update()
{
_wasMet = IsMet;
if (TriggerMode == EventTriggerMode.Toggle)
{
if (Evaluate())
IsMet = !IsMet;
}
else
{
IsMet = Evaluate();
}
}
///
public void UpdateTimeline(double deltaTime)
{
if (TriggerMode == EventTriggerMode.Toggle)
{
if (IsMet && !_wasMet)
ProfileElement.Timeline.JumpToStart();
if (!IsMet && _wasMet && ToggleOffMode == EventToggleOffMode.SkipToEnd)
ProfileElement.Timeline.JumpToEndSegment();
}
else
{
if (IsMet && ProfileElement.Timeline.IsFinished)
{
ProfileElement.Timeline.JumpToStart();
}
else if (IsMet)
{
if (OverlapMode == EventOverlapMode.Restart)
ProfileElement.Timeline.JumpToStart();
else if (OverlapMode == EventOverlapMode.Copy && ProfileElement is Layer layer && layer.Parent is not Layer)
layer.CreateRenderCopy(10);
}
}
// Stick to mean segment in toggle mode for as long as the condition is met
ProfileElement.Timeline.Update(TimeSpan.FromSeconds(deltaTime), TriggerMode == EventTriggerMode.Toggle && IsMet);
}
///
public void OverrideTimeline(TimeSpan position)
{
ProfileElement.Timeline.Override(position, TriggerMode == EventTriggerMode.Toggle && position > ProfileElement.Timeline.Length);
}
///
public void Dispose()
{
Script?.Dispose();
EventPath?.Dispose();
}
///
/// Updates the event node, applying the selected event
///
public void UpdateEventNode(bool updateScript)
{
if (EventPath == null)
return;
Type? pathType = EventPath.GetPropertyType();
if (pathType == null)
return;
// Create an event node if the path type is a data model event
if (pathType.IsAssignableTo(typeof(IDataModelEvent)))
{
EventConditionEventStartNode eventNode;
// Ensure the start node is an event node
if (_startNode is not EventConditionEventStartNode node)
{
eventNode = new EventConditionEventStartNode();
if (updateScript)
ReplaceStartNode(eventNode);
_startNode = eventNode;
}
else
{
eventNode = node;
}
IDataModelEvent? dataModelEvent = EventPath?.GetValue() as IDataModelEvent;
eventNode.CreatePins(dataModelEvent);
}
// Create a value changed node if the path type is a regular value
else
{
// Ensure the start nod is a value changed node
EventConditionValueChangedStartNode valueChangedNode;
// Ensure the start node is an event node
if (_startNode is not EventConditionValueChangedStartNode node)
{
valueChangedNode = new EventConditionValueChangedStartNode();
if (updateScript)
ReplaceStartNode(valueChangedNode);
_startNode = valueChangedNode;
}
else
{
valueChangedNode = node;
}
valueChangedNode.UpdateOutputPins(EventPath);
}
// Script can be null if called before load
if (!updateScript)
return;
if (!Script.Nodes.Contains(_startNode))
{
Script.AddNode(_startNode);
Script.LoadConnections();
}
Script.Save();
}
///
/// Gets the start node of the event script, if any
///
/// The start node of the event script, if any.
public INode GetStartNode()
{
return _startNode;
}
private void ReplaceStartNode(DefaultNode newStartNode)
{
if (Script.Nodes.Contains(_startNode))
Script.RemoveNode(_startNode);
if (!Script.Nodes.Contains(newStartNode))
Script.AddNode(newStartNode);
}
private bool Evaluate()
{
if (EventPath == null)
return false;
object? value = EventPath.GetValue();
if (_startNode is EventConditionEventStartNode)
{
if (value is not IDataModelEvent dataModelEvent || dataModelEvent.LastTrigger <= _lastProcessedTrigger)
return false;
_lastProcessedTrigger = dataModelEvent.LastTrigger;
}
else if (_startNode is EventConditionValueChangedStartNode valueChangedNode)
{
if (Equals(value, _lastProcessedValue))
return false;
valueChangedNode.UpdateValues(value, _lastProcessedValue);
_lastProcessedValue = value;
}
if (!Script.ExitNodeConnected)
return true;
Script.Run();
return Script.Result;
}
#region Storage
///
public void Load()
{
TriggerMode = (EventTriggerMode) _entity.TriggerMode;
OverlapMode = (EventOverlapMode) _entity.OverlapMode;
ToggleOffMode = (EventToggleOffMode) _entity.ToggleOffMode;
if (_entity.EventPath != null)
EventPath = new DataModelPath(_entity.EventPath);
UpdateEventNode(false);
string name = $"Activate {_displayName}";
string description = $"Whether or not the event should activate the {_displayName}";
Script = _entity.Script != null
? new NodeScript(name, description, _entity.Script, ProfileElement.Profile, new List {_startNode})
: new NodeScript(name, description, ProfileElement.Profile, new List {_startNode});
}
///
public void Save()
{
_entity.TriggerMode = (int) TriggerMode;
_entity.OverlapMode = (int) OverlapMode;
_entity.ToggleOffMode = (int) ToggleOffMode;
// If the exit node isn't connected and there are only the start- and exit node, don't save the script
if (!Script.ExitNodeConnected && Script.Nodes.Count() <= 2)
{
_entity.Script = null;
}
else
{
Script.Save();
_entity.Script = Script.Entity;
}
EventPath?.Save();
_entity.EventPath = EventPath?.Entity;
}
///
public INodeScript NodeScript => Script;
///
public void LoadNodeScript()
{
UpdateEventNode(true);
Script.Load();
}
#endregion
#region Implementation of IPluginFeatureDependent
///
public IEnumerable GetFeatureDependencies()
{
return Script.GetFeatureDependencies().Concat(EventPath?.GetFeatureDependencies() ?? []);
}
#endregion
}