mirror of
https://github.com/Ratstail91/Mementos.git
synced 2025-11-29 02:24:28 +11:00
585 lines
21 KiB
C#
585 lines
21 KiB
C#
using System.Collections.Generic;
|
|
using System.Text;
|
|
|
|
namespace Ink.Runtime
|
|
{
|
|
/// <summary>
|
|
/// The underlying type for a list item in ink. It stores the original list definition
|
|
/// name as well as the item name, but without the value of the item. When the value is
|
|
/// stored, it's stored in a KeyValuePair of InkListItem and int.
|
|
/// </summary>
|
|
public struct InkListItem
|
|
{
|
|
/// <summary>
|
|
/// The name of the list where the item was originally defined.
|
|
/// </summary>
|
|
public readonly string originName;
|
|
|
|
/// <summary>
|
|
/// The main name of the item as defined in ink.
|
|
/// </summary>
|
|
public readonly string itemName;
|
|
|
|
/// <summary>
|
|
/// Create an item with the given original list definition name, and the name of this
|
|
/// item.
|
|
/// </summary>
|
|
public InkListItem (string originName, string itemName)
|
|
{
|
|
this.originName = originName;
|
|
this.itemName = itemName;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create an item from a dot-separted string of the form "listDefinitionName.listItemName".
|
|
/// </summary>
|
|
public InkListItem (string fullName)
|
|
{
|
|
var nameParts = fullName.Split ('.');
|
|
this.originName = nameParts [0];
|
|
this.itemName = nameParts [1];
|
|
}
|
|
|
|
public static InkListItem Null {
|
|
get {
|
|
return new InkListItem (null, null);
|
|
}
|
|
}
|
|
|
|
public bool isNull {
|
|
get {
|
|
return originName == null && itemName == null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the full dot-separated name of the item, in the form "listDefinitionName.itemName".
|
|
/// </summary>
|
|
public string fullName {
|
|
get {
|
|
return (originName ?? "?") + "." + itemName;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the full dot-separated name of the item, in the form "listDefinitionName.itemName".
|
|
/// Calls fullName internally.
|
|
/// </summary>
|
|
public override string ToString ()
|
|
{
|
|
return fullName;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Is this item the same as another item?
|
|
/// </summary>
|
|
public override bool Equals (object obj)
|
|
{
|
|
if (obj is InkListItem) {
|
|
var otherItem = (InkListItem)obj;
|
|
return otherItem.itemName == itemName
|
|
&& otherItem.originName == originName;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the hashcode for an item.
|
|
/// </summary>
|
|
public override int GetHashCode ()
|
|
{
|
|
int originCode = 0;
|
|
int itemCode = itemName.GetHashCode ();
|
|
if (originName != null)
|
|
originCode = originName.GetHashCode ();
|
|
|
|
return originCode + itemCode;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The InkList is the underlying type that's used to store an instance of a
|
|
/// list in ink. It's not used for the *definition* of the list, but for a list
|
|
/// value that's stored in a variable.
|
|
/// Somewhat confusingly, it's backed by a C# Dictionary, and has nothing to
|
|
/// do with a C# List!
|
|
/// </summary>
|
|
public class InkList : Dictionary<InkListItem, int>
|
|
{
|
|
/// <summary>
|
|
/// Create a new empty ink list.
|
|
/// </summary>
|
|
public InkList () { }
|
|
|
|
/// <summary>
|
|
/// Create a new ink list that contains the same contents as another list.
|
|
/// </summary>
|
|
public InkList(InkList otherList) : base(otherList)
|
|
{
|
|
_originNames = otherList.originNames;
|
|
if (otherList.origins != null)
|
|
{
|
|
origins = new List<ListDefinition>(otherList.origins);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a new empty ink list that's intended to hold items from a particular origin
|
|
/// list definition. The origin Story is needed in order to be able to look up that definition.
|
|
/// </summary>
|
|
public InkList (string singleOriginListName, Story originStory)
|
|
{
|
|
SetInitialOriginName (singleOriginListName);
|
|
|
|
ListDefinition def;
|
|
if (originStory.listDefinitions.TryListGetDefinition (singleOriginListName, out def))
|
|
origins = new List<ListDefinition> { def };
|
|
else
|
|
throw new System.Exception ("InkList origin could not be found in story when constructing new list: " + singleOriginListName);
|
|
}
|
|
|
|
public InkList (KeyValuePair<InkListItem, int> singleElement)
|
|
{
|
|
Add (singleElement.Key, singleElement.Value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts a string to an ink list and returns for use in the story.
|
|
/// </summary>
|
|
/// <returns>InkList created from string list item</returns>
|
|
/// <param name="itemKey">Item key.</param>
|
|
/// <param name="originStory">Origin story.</param>
|
|
public static InkList FromString(string myListItem, Story originStory) {
|
|
var listValue = originStory.listDefinitions.FindSingleItemListWithName (myListItem);
|
|
if (listValue)
|
|
return new InkList (listValue.value);
|
|
else
|
|
throw new System.Exception ("Could not find the InkListItem from the string '" + myListItem + "' to create an InkList because it doesn't exist in the original list definition in ink.");
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Adds the given item to the ink list. Note that the item must come from a list definition that
|
|
/// is already "known" to this list, so that the item's value can be looked up. By "known", we mean
|
|
/// that it already has items in it from that source, or it did at one point - it can't be a
|
|
/// completely fresh empty list, or a list that only contains items from a different list definition.
|
|
/// </summary>
|
|
public void AddItem (InkListItem item)
|
|
{
|
|
if (item.originName == null) {
|
|
AddItem (item.itemName);
|
|
return;
|
|
}
|
|
|
|
foreach (var origin in origins) {
|
|
if (origin.name == item.originName) {
|
|
int intVal;
|
|
if (origin.TryGetValueForItem (item, out intVal)) {
|
|
this [item] = intVal;
|
|
return;
|
|
} else {
|
|
throw new System.Exception ("Could not add the item " + item + " to this list because it doesn't exist in the original list definition in ink.");
|
|
}
|
|
}
|
|
}
|
|
|
|
throw new System.Exception ("Failed to add item to list because the item was from a new list definition that wasn't previously known to this list. Only items from previously known lists can be used, so that the int value can be found.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds the given item to the ink list, attempting to find the origin list definition that it belongs to.
|
|
/// The item must therefore come from a list definition that is already "known" to this list, so that the
|
|
/// item's value can be looked up. By "known", we mean that it already has items in it from that source, or
|
|
/// it did at one point - it can't be a completely fresh empty list, or a list that only contains items from
|
|
/// a different list definition.
|
|
/// </summary>
|
|
public void AddItem (string itemName)
|
|
{
|
|
ListDefinition foundListDef = null;
|
|
|
|
foreach (var origin in origins) {
|
|
if (origin.ContainsItemWithName (itemName)) {
|
|
if (foundListDef != null) {
|
|
throw new System.Exception ("Could not add the item " + itemName + " to this list because it could come from either " + origin.name + " or " + foundListDef.name);
|
|
} else {
|
|
foundListDef = origin;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (foundListDef == null)
|
|
throw new System.Exception ("Could not add the item " + itemName + " to this list because it isn't known to any list definitions previously associated with this list.");
|
|
|
|
var item = new InkListItem (foundListDef.name, itemName);
|
|
var itemVal = foundListDef.ValueForItem(item);
|
|
this [item] = itemVal;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if this ink list contains an item with the given short name
|
|
/// (ignoring the original list where it was defined).
|
|
/// </summary>
|
|
public bool ContainsItemNamed (string itemName)
|
|
{
|
|
foreach (var itemWithValue in this) {
|
|
if (itemWithValue.Key.itemName == itemName) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Story has to set this so that the value knows its origin,
|
|
// necessary for certain operations (e.g. interacting with ints).
|
|
// Only the story has access to the full set of lists, so that
|
|
// the origin can be resolved from the originListName.
|
|
public List<ListDefinition> origins;
|
|
public ListDefinition originOfMaxItem {
|
|
get {
|
|
if (origins == null) return null;
|
|
|
|
var maxOriginName = maxItem.Key.originName;
|
|
foreach (var origin in origins) {
|
|
if (origin.name == maxOriginName)
|
|
return origin;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Origin name needs to be serialised when content is empty,
|
|
// assuming a name is availble, for list definitions with variable
|
|
// that is currently empty.
|
|
public List<string> originNames {
|
|
get {
|
|
if (this.Count > 0) {
|
|
if (_originNames == null && this.Count > 0)
|
|
_originNames = new List<string> ();
|
|
else
|
|
_originNames.Clear ();
|
|
|
|
foreach (var itemAndValue in this)
|
|
_originNames.Add (itemAndValue.Key.originName);
|
|
}
|
|
|
|
return _originNames;
|
|
}
|
|
}
|
|
List<string> _originNames;
|
|
|
|
public void SetInitialOriginName (string initialOriginName)
|
|
{
|
|
_originNames = new List<string> { initialOriginName };
|
|
}
|
|
|
|
public void SetInitialOriginNames (List<string> initialOriginNames)
|
|
{
|
|
if (initialOriginNames == null)
|
|
_originNames = null;
|
|
else
|
|
_originNames = new List<string>(initialOriginNames);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the maximum item in the list, equivalent to calling LIST_MAX(list) in ink.
|
|
/// </summary>
|
|
public KeyValuePair<InkListItem, int> maxItem {
|
|
get {
|
|
KeyValuePair<InkListItem, int> max = new KeyValuePair<InkListItem, int>();
|
|
foreach (var kv in this) {
|
|
if (max.Key.isNull || kv.Value > max.Value)
|
|
max = kv;
|
|
}
|
|
return max;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the minimum item in the list, equivalent to calling LIST_MIN(list) in ink.
|
|
/// </summary>
|
|
public KeyValuePair<InkListItem, int> minItem {
|
|
get {
|
|
var min = new KeyValuePair<InkListItem, int> ();
|
|
foreach (var kv in this) {
|
|
if (min.Key.isNull || kv.Value < min.Value)
|
|
min = kv;
|
|
}
|
|
return min;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The inverse of the list, equivalent to calling LIST_INVERSE(list) in ink
|
|
/// </summary>
|
|
public InkList inverse {
|
|
get {
|
|
var list = new InkList ();
|
|
if (origins != null) {
|
|
foreach (var origin in origins) {
|
|
foreach (var itemAndValue in origin.items) {
|
|
if (!this.ContainsKey (itemAndValue.Key))
|
|
list.Add (itemAndValue.Key, itemAndValue.Value);
|
|
}
|
|
}
|
|
|
|
}
|
|
return list;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The list of all items from the original list definition, equivalent to calling
|
|
/// LIST_ALL(list) in ink.
|
|
/// </summary>
|
|
public InkList all {
|
|
get {
|
|
var list = new InkList ();
|
|
if (origins != null) {
|
|
foreach (var origin in origins) {
|
|
foreach (var itemAndValue in origin.items)
|
|
list[itemAndValue.Key] = itemAndValue.Value;
|
|
}
|
|
}
|
|
return list;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a new list that is the combination of the current list and one that's
|
|
/// passed in. Equivalent to calling (list1 + list2) in ink.
|
|
/// </summary>
|
|
public InkList Union (InkList otherList)
|
|
{
|
|
var union = new InkList (this);
|
|
foreach (var kv in otherList) {
|
|
union [kv.Key] = kv.Value;
|
|
}
|
|
return union;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a new list that is the intersection of the current list with another
|
|
/// list that's passed in - i.e. a list of the items that are shared between the
|
|
/// two other lists. Equivalent to calling (list1 ^ list2) in ink.
|
|
/// </summary>
|
|
public InkList Intersect (InkList otherList)
|
|
{
|
|
var intersection = new InkList ();
|
|
foreach (var kv in this) {
|
|
if (otherList.ContainsKey (kv.Key))
|
|
intersection.Add (kv.Key, kv.Value);
|
|
}
|
|
return intersection;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a new list that's the same as the current one, except with the given items
|
|
/// removed that are in the passed in list. Equivalent to calling (list1 - list2) in ink.
|
|
/// </summary>
|
|
/// <param name="listToRemove">List to remove.</param>
|
|
public InkList Without (InkList listToRemove)
|
|
{
|
|
var result = new InkList (this);
|
|
foreach (var kv in listToRemove)
|
|
result.Remove (kv.Key);
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the current list contains all the items that are in the list that
|
|
/// is passed in. Equivalent to calling (list1 ? list2) in ink.
|
|
/// </summary>
|
|
/// <param name="otherList">Other list.</param>
|
|
public bool Contains (InkList otherList)
|
|
{
|
|
foreach (var kv in otherList) {
|
|
if (!this.ContainsKey (kv.Key)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if all the item values in the current list are greater than all the
|
|
/// item values in the passed in list. Equivalent to calling (list1 > list2) in ink.
|
|
/// </summary>
|
|
public bool GreaterThan (InkList otherList)
|
|
{
|
|
if (Count == 0) return false;
|
|
if (otherList.Count == 0) return true;
|
|
|
|
// All greater
|
|
return minItem.Value > otherList.maxItem.Value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the item values in the current list overlap or are all greater than
|
|
/// the item values in the passed in list. None of the item values in the current list must
|
|
/// fall below the item values in the passed in list. Equivalent to (list1 >= list2) in ink,
|
|
/// or LIST_MIN(list1) >= LIST_MIN(list2) && LIST_MAX(list1) >= LIST_MAX(list2).
|
|
/// </summary>
|
|
public bool GreaterThanOrEquals (InkList otherList)
|
|
{
|
|
if (Count == 0) return false;
|
|
if (otherList.Count == 0) return true;
|
|
|
|
return minItem.Value >= otherList.minItem.Value
|
|
&& maxItem.Value >= otherList.maxItem.Value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if all the item values in the current list are less than all the
|
|
/// item values in the passed in list. Equivalent to calling (list1 < list2) in ink.
|
|
/// </summary>
|
|
public bool LessThan (InkList otherList)
|
|
{
|
|
if (otherList.Count == 0) return false;
|
|
if (Count == 0) return true;
|
|
|
|
return maxItem.Value < otherList.minItem.Value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the item values in the current list overlap or are all less than
|
|
/// the item values in the passed in list. None of the item values in the current list must
|
|
/// go above the item values in the passed in list. Equivalent to (list1 <= list2) in ink,
|
|
/// or LIST_MAX(list1) <= LIST_MAX(list2) && LIST_MIN(list1) <= LIST_MIN(list2).
|
|
/// </summary>
|
|
public bool LessThanOrEquals (InkList otherList)
|
|
{
|
|
if (otherList.Count == 0) return false;
|
|
if (Count == 0) return true;
|
|
|
|
return maxItem.Value <= otherList.maxItem.Value
|
|
&& minItem.Value <= otherList.minItem.Value;
|
|
}
|
|
|
|
public InkList MaxAsList ()
|
|
{
|
|
if (Count > 0)
|
|
return new InkList (maxItem);
|
|
else
|
|
return new InkList ();
|
|
}
|
|
|
|
public InkList MinAsList ()
|
|
{
|
|
if (Count > 0)
|
|
return new InkList (minItem);
|
|
else
|
|
return new InkList ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a sublist with the elements given the minimum and maxmimum bounds.
|
|
/// The bounds can either be ints which are indices into the entire (sorted) list,
|
|
/// or they can be InkLists themselves. These are intended to be single-item lists so
|
|
/// you can specify the upper and lower bounds. If you pass in multi-item lists, it'll
|
|
/// use the minimum and maximum items in those lists respectively.
|
|
/// WARNING: Calling this method requires a full sort of all the elements in the list.
|
|
/// </summary>
|
|
public InkList ListWithSubRange(object minBound, object maxBound)
|
|
{
|
|
if (this.Count == 0) return new InkList();
|
|
|
|
var ordered = orderedItems;
|
|
|
|
int minValue = 0;
|
|
int maxValue = int.MaxValue;
|
|
|
|
if (minBound is int)
|
|
{
|
|
minValue = (int)minBound;
|
|
}
|
|
|
|
else
|
|
{
|
|
if( minBound is InkList && ((InkList)minBound).Count > 0 )
|
|
minValue = ((InkList)minBound).minItem.Value;
|
|
}
|
|
|
|
if (maxBound is int)
|
|
maxValue = (int)maxBound;
|
|
else
|
|
{
|
|
if (minBound is InkList && ((InkList)minBound).Count > 0)
|
|
maxValue = ((InkList)maxBound).maxItem.Value;
|
|
}
|
|
|
|
var subList = new InkList();
|
|
subList.SetInitialOriginNames(originNames);
|
|
foreach(var item in ordered) {
|
|
if( item.Value >= minValue && item.Value <= maxValue ) {
|
|
subList.Add(item.Key, item.Value);
|
|
}
|
|
}
|
|
|
|
return subList;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the passed object is also an ink list that contains
|
|
/// the same items as the current list, false otherwise.
|
|
/// </summary>
|
|
public override bool Equals (object other)
|
|
{
|
|
var otherRawList = other as InkList;
|
|
if (otherRawList == null) return false;
|
|
if (otherRawList.Count != Count) return false;
|
|
|
|
foreach (var kv in this) {
|
|
if (!otherRawList.ContainsKey (kv.Key))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return the hashcode for this object, used for comparisons and inserting into dictionaries.
|
|
/// </summary>
|
|
public override int GetHashCode ()
|
|
{
|
|
int ownHash = 0;
|
|
foreach (var kv in this)
|
|
ownHash += kv.Key.GetHashCode ();
|
|
return ownHash;
|
|
}
|
|
|
|
List<KeyValuePair<InkListItem, int>> orderedItems {
|
|
get {
|
|
var ordered = new List<KeyValuePair<InkListItem, int>>();
|
|
ordered.AddRange(this);
|
|
ordered.Sort((x, y) => {
|
|
// Ensure consistent ordering of mixed lists.
|
|
if( x.Value == y.Value ) {
|
|
return x.Key.originName.CompareTo(y.Key.originName);
|
|
} else {
|
|
return x.Value.CompareTo(y.Value);
|
|
}
|
|
});
|
|
return ordered;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a string in the form "a, b, c" with the names of the items in the list, without
|
|
/// the origin list definition names. Equivalent to writing {list} in ink.
|
|
/// </summary>
|
|
public override string ToString ()
|
|
{
|
|
var ordered = orderedItems;
|
|
|
|
var sb = new StringBuilder ();
|
|
for (int i = 0; i < ordered.Count; i++) {
|
|
if (i > 0)
|
|
sb.Append (", ");
|
|
|
|
var item = ordered [i].Key;
|
|
sb.Append (item.itemName);
|
|
}
|
|
|
|
return sb.ToString ();
|
|
}
|
|
}
|
|
}
|