A custom dictionary wrapper mapping int keys to CrosshairAnimation objects. It enforces that all animations share the same property size based on an AnimationValueSetter, provides helpers to add new animations (by key or next free key), and implements IDictionary<int, CrosshairAnimation> plus non-generic IDictionary members for editor/runtime use.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using Sandbox;
#nullable enable
namespace CrosshairMaker.Animator
{
public sealed class AnimationDictionary : IDictionary<int,CrosshairAnimation> , IDictionary
{
//Init
//-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-//
public void Init(AnimationValueSetter setterCollection )
{
if (AnimationSize.Length != 0) this.Clear();
ValueSetters = setterCollection;
}
//-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-//
//Init
//General features
//-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-//
/// <summary>
/// Add an animation the same size as the others in this AnimationDictionary with given key , returns the key if sucessful
/// throws an error if AnimationDictionary is empty
/// throws an error if key is already contained in this AnimationDictionary
/// </summary>
/// <param name="key">key of animation</param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
public int AddNewAnimation( int key )
{
if ( AnimationSize.Length == 0 ) throw new InvalidOperationException( "Cannot create an animation, no setters found" );
if ( _internalDic.ContainsKey( key ) ) throw new InvalidOperationException( $"Cannot create an animation in an already existing key, animation {key} is {_internalDic[key]}" );
CrosshairAnimation newAnim = CrosshairAnimation.FromPropertyCount( AnimationSize );
_internalDic.Add( key, newAnim );
return key;
}
/// <summary>
/// Add an animation the same size as the others in this AnimationDictionary in first avaliable key between 0 -> (int.MaxValue - 1) , returns the key if sucessful
/// throws an exception if AnimationDictionary is empty
/// throws an exception if all keys from 0 to (int.MaxValue - 1) are filled
/// </summary>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
public int AddNewAnimation()
{
_internalDic ??= new();
if ( AnimationSize.Length == 0 ) throw new InvalidOperationException( "Cannot create an animation, no setters found" );
for ( int i = 0; true; i++ )
{
if ( i == int.MaxValue ) throw new InvalidOperationException( $"AnimationDictionary could not find a key smaller than {int.MaxValue} to add a new animation" );
if ( _internalDic.ContainsKey( i ) ) continue;
CrosshairAnimation newAnim = CrosshairAnimation.FromPropertyCount( AnimationSize );
_internalDic.Add( i, newAnim ); //Skips the implemented Add method, since both validity conditions are met
return i;
}
}
/// <summary>
/// Returns the expected animation size based on the setters
/// </summary>
public int[] AnimationSize => ValueSetters?.Sizes ?? Array.Empty<int>();
public override string ToString()
{
int[] s = AnimationSize;
return (s.Length == 3) ?
$"AnimationSize : [float:{s[0]}, int:{s[1]}, Color:{s[2]}]" :
"No AnimationSize set : length = " + s.Length;
}
//-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-//
//General features
//Overriden Dictionary behaviour
//-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-//
public void Add( int key, CrosshairAnimation value )
{
_internalDic ??= new Dictionary<int,CrosshairAnimation>();
if ( CrosshairAnimation.SameSize( AnimationSize,value )) _internalDic.Add( key, value );
}
public CrosshairAnimation this[int key]
{
get => _internalDic[key];
set
{
if ( CrosshairAnimation.SameSize( value,AnimationSize ) ) _internalDic[key] = value;
else throw new InvalidOperationException( "Cannot insert an animation of a different size from the others" );
}
}
//-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-//
//Overriden Dictionary behaviour
//Editor features
//-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-//
#pragma warning disable
#pragma warning enable
public string[] FloatsPropertyNames;
public string[] IntsPropertyNames;
public string[] ColorsPropertyNames;
//-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-//
//Editor features
//Internal properties
//-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-//
public AnimationValueSetter? ValueSetters { get; private set; }
private Dictionary<int, CrosshairAnimation> _internalDic;
//-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-//
//Internal properties
//Unchanged IDictionary Implementations
//-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-//
public void Add( KeyValuePair<int, CrosshairAnimation> item ) => Add( item.Key, item.Value );
public bool ContainsKey( int key ) => _internalDic.ContainsKey( key );
public bool Remove( int key ) => _internalDic.Remove( key );
public bool TryGetValue( int key, [MaybeNullWhen( false )] out CrosshairAnimation value ) => _internalDic.TryGetValue( key, out value );
public void Clear() => _internalDic.Clear();
public bool Contains( KeyValuePair<int, CrosshairAnimation> item ) => _internalDic.Contains( item );
private void CopyTo( KeyValuePair<int, CrosshairAnimation>[] array, int index )
{
if ( array == null ) throw new ArgumentNullException();
if ( (uint)index > (uint)array.Length ) throw new ArgumentOutOfRangeException( "Array is too short to handle offset" );
if ( array.Length - index < Count ) throw new ArgumentOutOfRangeException( "Array is too short to handle offset" );
foreach ( var kvp in _internalDic )
{
array[index++] = new KeyValuePair<int, CrosshairAnimation>( kvp.Key, kvp.Value );
}
}
public bool Remove( KeyValuePair<int, CrosshairAnimation> item )
{
bool success = _internalDic.TryGetValue( item.Key, out CrosshairAnimation correct );
if ( !success ) return false;
success = correct == item.Value;
if ( success ) _internalDic.Remove( item.Key );
return success;
}
public IEnumerator<KeyValuePair<int, CrosshairAnimation>> GetEnumerator() => _internalDic.GetEnumerator();
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
void ICollection<KeyValuePair<int, CrosshairAnimation>>.CopyTo( KeyValuePair<int, CrosshairAnimation>[] array, int arrayIndex ) => CopyTo( array, arrayIndex );
public void Add( object key, object value )
{
if(key is not int ik) throw new InvalidCastException();
if(value is not CrosshairAnimation av) throw new InvalidCastException();
Add( ik, av );
}
public bool Contains( object key ) => key is int ik ? _internalDic.ContainsKey( ik ) : false;
IDictionaryEnumerator IDictionary.GetEnumerator() => _internalDic.GetEnumerator();
public void Remove( object key )
{
if( key is int ik) _internalDic.Remove( ik );
}
public void CopyTo( Array array, int index )
{
if(array == null) throw new ArgumentNullException();
if(array.Rank != 1) throw new ArgumentException("Array cannot be multi-dimentional");
if ( array.GetLowerBound( 0 ) != 0 ) throw new ArgumentException( "Array's lower bound must be 0" );
if ( (uint)index > (uint)array.Length ) throw new ArgumentOutOfRangeException( "Array is too short to handle offset" );
if ( array.Length - index < Count ) throw new ArgumentOutOfRangeException( "Array is too short to handle offset" );
if ( array is KeyValuePair<int, CrosshairAnimation>[] kvpArr )
{
CopyTo( kvpArr, 0 );
}
else if ( array is DictionaryEntry[] deArr )
{
foreach(var kvp in _internalDic )
{
deArr[index++] = new DictionaryEntry( kvp.Key, kvp.Value );
}
}
else if ( array is object[] objArr )
{
}
else throw new ArgumentException( "Array type is not compatible" );
}
public ICollection<int> Keys => _internalDic.Keys;
public ICollection<CrosshairAnimation> Values => _internalDic.Values;
public int Count => _internalDic.Count;
public bool IsReadOnly => false;
public bool IsFixedSize => false;
ICollection IDictionary.Keys => _internalDic.Keys;
ICollection IDictionary.Values => _internalDic.Values;
public bool IsSynchronized => false;
public object SyncRoot => _internalDic;
public object this[object key] { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
//-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-//
//Unchanged IDictionary Implementations
}
}