Newer
Older
TheVengeance-Project-IADE-Unity2D / Assets / Ink / InkLibs / InkRuntime / InkList.cs
  1. using System.Collections.Generic;
  2. using System.Text;
  3. namespace Ink.Runtime
  4. {
  5. /// <summary>
  6. /// The underlying type for a list item in ink. It stores the original list definition
  7. /// name as well as the item name, but without the value of the item. When the value is
  8. /// stored, it's stored in a KeyValuePair of InkListItem and int.
  9. /// </summary>
  10. public struct InkListItem
  11. {
  12. /// <summary>
  13. /// The name of the list where the item was originally defined.
  14. /// </summary>
  15. public readonly string originName;
  16. /// <summary>
  17. /// The main name of the item as defined in ink.
  18. /// </summary>
  19. public readonly string itemName;
  20. /// <summary>
  21. /// Create an item with the given original list definition name, and the name of this
  22. /// item.
  23. /// </summary>
  24. public InkListItem (string originName, string itemName)
  25. {
  26. this.originName = originName;
  27. this.itemName = itemName;
  28. }
  29. /// <summary>
  30. /// Create an item from a dot-separted string of the form "listDefinitionName.listItemName".
  31. /// </summary>
  32. public InkListItem (string fullName)
  33. {
  34. var nameParts = fullName.Split ('.');
  35. this.originName = nameParts [0];
  36. this.itemName = nameParts [1];
  37. }
  38. public static InkListItem Null {
  39. get {
  40. return new InkListItem (null, null);
  41. }
  42. }
  43. public bool isNull {
  44. get {
  45. return originName == null && itemName == null;
  46. }
  47. }
  48. /// <summary>
  49. /// Get the full dot-separated name of the item, in the form "listDefinitionName.itemName".
  50. /// </summary>
  51. public string fullName {
  52. get {
  53. return (originName ?? "?") + "." + itemName;
  54. }
  55. }
  56. /// <summary>
  57. /// Get the full dot-separated name of the item, in the form "listDefinitionName.itemName".
  58. /// Calls fullName internally.
  59. /// </summary>
  60. public override string ToString ()
  61. {
  62. return fullName;
  63. }
  64. /// <summary>
  65. /// Is this item the same as another item?
  66. /// </summary>
  67. public override bool Equals (object obj)
  68. {
  69. if (obj is InkListItem) {
  70. var otherItem = (InkListItem)obj;
  71. return otherItem.itemName == itemName
  72. && otherItem.originName == originName;
  73. }
  74. return false;
  75. }
  76. /// <summary>
  77. /// Get the hashcode for an item.
  78. /// </summary>
  79. public override int GetHashCode ()
  80. {
  81. int originCode = 0;
  82. int itemCode = itemName.GetHashCode ();
  83. if (originName != null)
  84. originCode = originName.GetHashCode ();
  85. return originCode + itemCode;
  86. }
  87. }
  88. /// <summary>
  89. /// The InkList is the underlying type that's used to store an instance of a
  90. /// list in ink. It's not used for the *definition* of the list, but for a list
  91. /// value that's stored in a variable.
  92. /// Somewhat confusingly, it's backed by a C# Dictionary, and has nothing to
  93. /// do with a C# List!
  94. /// </summary>
  95. public class InkList : Dictionary<InkListItem, int>
  96. {
  97. /// <summary>
  98. /// Create a new empty ink list.
  99. /// </summary>
  100. public InkList () { }
  101. /// <summary>
  102. /// Create a new ink list that contains the same contents as another list.
  103. /// </summary>
  104. public InkList(InkList otherList) : base(otherList)
  105. {
  106. var otherOriginNames = otherList.originNames;
  107. if( otherOriginNames != null )
  108. _originNames = new List<string>(otherOriginNames);
  109. if (otherList.origins != null)
  110. {
  111. origins = new List<ListDefinition>(otherList.origins);
  112. }
  113. }
  114. /// <summary>
  115. /// Create a new empty ink list that's intended to hold items from a particular origin
  116. /// list definition. The origin Story is needed in order to be able to look up that definition.
  117. /// </summary>
  118. public InkList (string singleOriginListName, Story originStory)
  119. {
  120. SetInitialOriginName (singleOriginListName);
  121. ListDefinition def;
  122. if (originStory.listDefinitions.TryListGetDefinition (singleOriginListName, out def))
  123. origins = new List<ListDefinition> { def };
  124. else
  125. throw new System.Exception ("InkList origin could not be found in story when constructing new list: " + singleOriginListName);
  126. }
  127. public InkList (KeyValuePair<InkListItem, int> singleElement)
  128. {
  129. Add (singleElement.Key, singleElement.Value);
  130. }
  131. /// <summary>
  132. /// Converts a string to an ink list and returns for use in the story.
  133. /// </summary>
  134. /// <returns>InkList created from string list item</returns>
  135. /// <param name="itemKey">Item key.</param>
  136. /// <param name="originStory">Origin story.</param>
  137. public static InkList FromString(string myListItem, Story originStory) {
  138. var listValue = originStory.listDefinitions.FindSingleItemListWithName (myListItem);
  139. if (listValue)
  140. return new InkList (listValue.value);
  141. else
  142. 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.");
  143. }
  144. /// <summary>
  145. /// Adds the given item to the ink list. Note that the item must come from a list definition that
  146. /// is already "known" to this list, so that the item's value can be looked up. By "known", we mean
  147. /// that it already has items in it from that source, or it did at one point - it can't be a
  148. /// completely fresh empty list, or a list that only contains items from a different list definition.
  149. /// </summary>
  150. public void AddItem (InkListItem item)
  151. {
  152. if (item.originName == null) {
  153. AddItem (item.itemName);
  154. return;
  155. }
  156. foreach (var origin in origins) {
  157. if (origin.name == item.originName) {
  158. int intVal;
  159. if (origin.TryGetValueForItem (item, out intVal)) {
  160. this [item] = intVal;
  161. return;
  162. } else {
  163. 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.");
  164. }
  165. }
  166. }
  167. 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.");
  168. }
  169. /// <summary>
  170. /// Adds the given item to the ink list, attempting to find the origin list definition that it belongs to.
  171. /// The item must therefore come from a list definition that is already "known" to this list, so that the
  172. /// item's value can be looked up. By "known", we mean that it already has items in it from that source, or
  173. /// it did at one point - it can't be a completely fresh empty list, or a list that only contains items from
  174. /// a different list definition.
  175. /// </summary>
  176. public void AddItem (string itemName)
  177. {
  178. ListDefinition foundListDef = null;
  179. foreach (var origin in origins) {
  180. if (origin.ContainsItemWithName (itemName)) {
  181. if (foundListDef != null) {
  182. throw new System.Exception ("Could not add the item " + itemName + " to this list because it could come from either " + origin.name + " or " + foundListDef.name);
  183. } else {
  184. foundListDef = origin;
  185. }
  186. }
  187. }
  188. if (foundListDef == null)
  189. 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.");
  190. var item = new InkListItem (foundListDef.name, itemName);
  191. var itemVal = foundListDef.ValueForItem(item);
  192. this [item] = itemVal;
  193. }
  194. /// <summary>
  195. /// Returns true if this ink list contains an item with the given short name
  196. /// (ignoring the original list where it was defined).
  197. /// </summary>
  198. public bool ContainsItemNamed (string itemName)
  199. {
  200. foreach (var itemWithValue in this) {
  201. if (itemWithValue.Key.itemName == itemName) return true;
  202. }
  203. return false;
  204. }
  205. // Story has to set this so that the value knows its origin,
  206. // necessary for certain operations (e.g. interacting with ints).
  207. // Only the story has access to the full set of lists, so that
  208. // the origin can be resolved from the originListName.
  209. public List<ListDefinition> origins;
  210. public ListDefinition originOfMaxItem {
  211. get {
  212. if (origins == null) return null;
  213. var maxOriginName = maxItem.Key.originName;
  214. foreach (var origin in origins) {
  215. if (origin.name == maxOriginName)
  216. return origin;
  217. }
  218. return null;
  219. }
  220. }
  221. // Origin name needs to be serialised when content is empty,
  222. // assuming a name is availble, for list definitions with variable
  223. // that is currently empty.
  224. public List<string> originNames {
  225. get {
  226. if (this.Count > 0) {
  227. if (_originNames == null && this.Count > 0)
  228. _originNames = new List<string> ();
  229. else
  230. _originNames.Clear ();
  231. foreach (var itemAndValue in this)
  232. _originNames.Add (itemAndValue.Key.originName);
  233. }
  234. return _originNames;
  235. }
  236. }
  237. List<string> _originNames;
  238. public void SetInitialOriginName (string initialOriginName)
  239. {
  240. _originNames = new List<string> { initialOriginName };
  241. }
  242. public void SetInitialOriginNames (List<string> initialOriginNames)
  243. {
  244. if (initialOriginNames == null)
  245. _originNames = null;
  246. else
  247. _originNames = new List<string>(initialOriginNames);
  248. }
  249. /// <summary>
  250. /// Get the maximum item in the list, equivalent to calling LIST_MAX(list) in ink.
  251. /// </summary>
  252. public KeyValuePair<InkListItem, int> maxItem {
  253. get {
  254. KeyValuePair<InkListItem, int> max = new KeyValuePair<InkListItem, int>();
  255. foreach (var kv in this) {
  256. if (max.Key.isNull || kv.Value > max.Value)
  257. max = kv;
  258. }
  259. return max;
  260. }
  261. }
  262. /// <summary>
  263. /// Get the minimum item in the list, equivalent to calling LIST_MIN(list) in ink.
  264. /// </summary>
  265. public KeyValuePair<InkListItem, int> minItem {
  266. get {
  267. var min = new KeyValuePair<InkListItem, int> ();
  268. foreach (var kv in this) {
  269. if (min.Key.isNull || kv.Value < min.Value)
  270. min = kv;
  271. }
  272. return min;
  273. }
  274. }
  275. /// <summary>
  276. /// The inverse of the list, equivalent to calling LIST_INVERSE(list) in ink
  277. /// </summary>
  278. public InkList inverse {
  279. get {
  280. var list = new InkList ();
  281. if (origins != null) {
  282. foreach (var origin in origins) {
  283. foreach (var itemAndValue in origin.items) {
  284. if (!this.ContainsKey (itemAndValue.Key))
  285. list.Add (itemAndValue.Key, itemAndValue.Value);
  286. }
  287. }
  288. }
  289. return list;
  290. }
  291. }
  292. /// <summary>
  293. /// The list of all items from the original list definition, equivalent to calling
  294. /// LIST_ALL(list) in ink.
  295. /// </summary>
  296. public InkList all {
  297. get {
  298. var list = new InkList ();
  299. if (origins != null) {
  300. foreach (var origin in origins) {
  301. foreach (var itemAndValue in origin.items)
  302. list[itemAndValue.Key] = itemAndValue.Value;
  303. }
  304. }
  305. return list;
  306. }
  307. }
  308. /// <summary>
  309. /// Returns a new list that is the combination of the current list and one that's
  310. /// passed in. Equivalent to calling (list1 + list2) in ink.
  311. /// </summary>
  312. public InkList Union (InkList otherList)
  313. {
  314. var union = new InkList (this);
  315. foreach (var kv in otherList) {
  316. union [kv.Key] = kv.Value;
  317. }
  318. return union;
  319. }
  320. /// <summary>
  321. /// Returns a new list that is the intersection of the current list with another
  322. /// list that's passed in - i.e. a list of the items that are shared between the
  323. /// two other lists. Equivalent to calling (list1 ^ list2) in ink.
  324. /// </summary>
  325. public InkList Intersect (InkList otherList)
  326. {
  327. var intersection = new InkList ();
  328. foreach (var kv in this) {
  329. if (otherList.ContainsKey (kv.Key))
  330. intersection.Add (kv.Key, kv.Value);
  331. }
  332. return intersection;
  333. }
  334. /// <summary>
  335. /// Fast test for the existence of any intersection between the current list and another
  336. /// </summary>
  337. public bool HasIntersection(InkList otherList)
  338. {
  339. foreach (var kv in this)
  340. {
  341. if (otherList.ContainsKey(kv.Key))
  342. return true;
  343. }
  344. return false;
  345. }
  346. /// <summary>
  347. /// Returns a new list that's the same as the current one, except with the given items
  348. /// removed that are in the passed in list. Equivalent to calling (list1 - list2) in ink.
  349. /// </summary>
  350. /// <param name="listToRemove">List to remove.</param>
  351. public InkList Without (InkList listToRemove)
  352. {
  353. var result = new InkList (this);
  354. foreach (var kv in listToRemove)
  355. result.Remove (kv.Key);
  356. return result;
  357. }
  358. /// <summary>
  359. /// Returns true if the current list contains all the items that are in the list that
  360. /// is passed in. Equivalent to calling (list1 ? list2) in ink.
  361. /// </summary>
  362. /// <param name="otherList">Other list.</param>
  363. public bool Contains (InkList otherList)
  364. {
  365. if( otherList.Count == 0 || this.Count == 0 ) return false;
  366. foreach (var kv in otherList) {
  367. if (!this.ContainsKey (kv.Key)) return false;
  368. }
  369. return true;
  370. }
  371. /// <summary>
  372. /// Returns true if the current list contains an item matching the given name.
  373. /// </summary>
  374. /// <param name="otherList">Other list.</param>
  375. public bool Contains(string listItemName)
  376. {
  377. foreach (var kv in this)
  378. {
  379. if (kv.Key.itemName == listItemName) return true;
  380. }
  381. return false;
  382. }
  383. /// <summary>
  384. /// Returns true if all the item values in the current list are greater than all the
  385. /// item values in the passed in list. Equivalent to calling (list1 > list2) in ink.
  386. /// </summary>
  387. public bool GreaterThan (InkList otherList)
  388. {
  389. if (Count == 0) return false;
  390. if (otherList.Count == 0) return true;
  391. // All greater
  392. return minItem.Value > otherList.maxItem.Value;
  393. }
  394. /// <summary>
  395. /// Returns true if the item values in the current list overlap or are all greater than
  396. /// the item values in the passed in list. None of the item values in the current list must
  397. /// fall below the item values in the passed in list. Equivalent to (list1 >= list2) in ink,
  398. /// or LIST_MIN(list1) >= LIST_MIN(list2) &amp;&amp; LIST_MAX(list1) >= LIST_MAX(list2).
  399. /// </summary>
  400. public bool GreaterThanOrEquals (InkList otherList)
  401. {
  402. if (Count == 0) return false;
  403. if (otherList.Count == 0) return true;
  404. return minItem.Value >= otherList.minItem.Value
  405. && maxItem.Value >= otherList.maxItem.Value;
  406. }
  407. /// <summary>
  408. /// Returns true if all the item values in the current list are less than all the
  409. /// item values in the passed in list. Equivalent to calling (list1 &lt; list2) in ink.
  410. /// </summary>
  411. public bool LessThan (InkList otherList)
  412. {
  413. if (otherList.Count == 0) return false;
  414. if (Count == 0) return true;
  415. return maxItem.Value < otherList.minItem.Value;
  416. }
  417. /// <summary>
  418. /// Returns true if the item values in the current list overlap or are all less than
  419. /// the item values in the passed in list. None of the item values in the current list must
  420. /// go above the item values in the passed in list. Equivalent to (list1 &lt;= list2) in ink,
  421. /// or LIST_MAX(list1) &lt;= LIST_MAX(list2) &amp;&amp; LIST_MIN(list1) &lt;= LIST_MIN(list2).
  422. /// </summary>
  423. public bool LessThanOrEquals (InkList otherList)
  424. {
  425. if (otherList.Count == 0) return false;
  426. if (Count == 0) return true;
  427. return maxItem.Value <= otherList.maxItem.Value
  428. && minItem.Value <= otherList.minItem.Value;
  429. }
  430. public InkList MaxAsList ()
  431. {
  432. if (Count > 0)
  433. return new InkList (maxItem);
  434. else
  435. return new InkList ();
  436. }
  437. public InkList MinAsList ()
  438. {
  439. if (Count > 0)
  440. return new InkList (minItem);
  441. else
  442. return new InkList ();
  443. }
  444. /// <summary>
  445. /// Returns a sublist with the elements given the minimum and maxmimum bounds.
  446. /// The bounds can either be ints which are indices into the entire (sorted) list,
  447. /// or they can be InkLists themselves. These are intended to be single-item lists so
  448. /// you can specify the upper and lower bounds. If you pass in multi-item lists, it'll
  449. /// use the minimum and maximum items in those lists respectively.
  450. /// WARNING: Calling this method requires a full sort of all the elements in the list.
  451. /// </summary>
  452. public InkList ListWithSubRange(object minBound, object maxBound)
  453. {
  454. if (this.Count == 0) return new InkList();
  455. var ordered = orderedItems;
  456. int minValue = 0;
  457. int maxValue = int.MaxValue;
  458. if (minBound is int)
  459. {
  460. minValue = (int)minBound;
  461. }
  462. else
  463. {
  464. if( minBound is InkList && ((InkList)minBound).Count > 0 )
  465. minValue = ((InkList)minBound).minItem.Value;
  466. }
  467. if (maxBound is int)
  468. maxValue = (int)maxBound;
  469. else
  470. {
  471. if (minBound is InkList && ((InkList)minBound).Count > 0)
  472. maxValue = ((InkList)maxBound).maxItem.Value;
  473. }
  474. var subList = new InkList();
  475. subList.SetInitialOriginNames(originNames);
  476. foreach(var item in ordered) {
  477. if( item.Value >= minValue && item.Value <= maxValue ) {
  478. subList.Add(item.Key, item.Value);
  479. }
  480. }
  481. return subList;
  482. }
  483. /// <summary>
  484. /// Returns true if the passed object is also an ink list that contains
  485. /// the same items as the current list, false otherwise.
  486. /// </summary>
  487. public override bool Equals (object other)
  488. {
  489. var otherRawList = other as InkList;
  490. if (otherRawList == null) return false;
  491. if (otherRawList.Count != Count) return false;
  492. foreach (var kv in this) {
  493. if (!otherRawList.ContainsKey (kv.Key))
  494. return false;
  495. }
  496. return true;
  497. }
  498. /// <summary>
  499. /// Return the hashcode for this object, used for comparisons and inserting into dictionaries.
  500. /// </summary>
  501. public override int GetHashCode ()
  502. {
  503. int ownHash = 0;
  504. foreach (var kv in this)
  505. ownHash += kv.Key.GetHashCode ();
  506. return ownHash;
  507. }
  508. List<KeyValuePair<InkListItem, int>> orderedItems {
  509. get {
  510. var ordered = new List<KeyValuePair<InkListItem, int>>();
  511. ordered.AddRange(this);
  512. ordered.Sort((x, y) => {
  513. // Ensure consistent ordering of mixed lists.
  514. if( x.Value == y.Value ) {
  515. return x.Key.originName.CompareTo(y.Key.originName);
  516. } else {
  517. return x.Value.CompareTo(y.Value);
  518. }
  519. });
  520. return ordered;
  521. }
  522. }
  523. /// <summary>
  524. /// Returns a string in the form "a, b, c" with the names of the items in the list, without
  525. /// the origin list definition names. Equivalent to writing {list} in ink.
  526. /// </summary>
  527. public override string ToString ()
  528. {
  529. var ordered = orderedItems;
  530. var sb = new StringBuilder ();
  531. for (int i = 0; i < ordered.Count; i++) {
  532. if (i > 0)
  533. sb.Append (", ");
  534. var item = ordered [i].Key;
  535. sb.Append (item.itemName);
  536. }
  537. return sb.ToString ();
  538. }
  539. }
  540. }