Thing.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. /// <reference path="../../Elements/Classes/Say.ts" />
  2. /// <reference path="Relations/RelationOneToMany.ts" />
  3. /// <reference path="../../Elements/Classes/Say/SayImage.ts" />
  4. interface ThingOptions {
  5. name? : string;
  6. properName? : string;
  7. description? : Say | any;
  8. unique? : boolean;
  9. image? : SayImage | string;
  10. shiny? : boolean;
  11. breakable? : boolean;
  12. resistance? : number;
  13. breaksTo? : string;
  14. }
  15. // A thing is something that exists in the World
  16. class Thing implements Printable {
  17. protected name : string;
  18. public properlyNamed : boolean = false;
  19. public scenery : boolean = false;
  20. public fixedInPlace : boolean = false;
  21. public animated : boolean = false;
  22. public visible : boolean = true;
  23. public unique : boolean = false;
  24. public image : SayImage;
  25. protected shiny : boolean = false;
  26. public breakable = false;
  27. public breakableOn = 5;
  28. public breaksTo;
  29. public lastActionTurn = -1;
  30. protected setAlterations : Array<(thisObject : Thing, simpleAlterationObject : {[id : string] : any}) => void> = [];
  31. protected getAlterations : Array<(thisObject : Thing) => {[id : string] : any}> = [];
  32. public clone : (includeChanges? : boolean) => Thing = function () {
  33. throw new Error("Non-unique Objects can't be cloned.");
  34. };
  35. public cloneOptions : ThingOptions;
  36. public description : Say | string | (() => Say | string);
  37. public static uniqueThings : {[id : string] : Thing} = {};
  38. public static things : {[id : string] : Array<Thing>} = {};
  39. // This is not safe.
  40. // public duplicate () {
  41. // return new (<any> this.constructor)(<ThingOptions> {
  42. // name : this.name,
  43. // properName : this.properlyNamed ? this.name : undefined,
  44. // description : this.description,
  45. // unique : this.unique,
  46. // image : this.image
  47. // });
  48. // }
  49. public constructor (options? : ThingOptions) {
  50. options = options == undefined ? {} : options;
  51. this.breakable = options.breakable === true;
  52. this.breakableOn = options.resistance == undefined ? 5 : options.resistance;
  53. this.breaksTo = options.breaksTo;
  54. if (options.properName != undefined) {
  55. this.name = options.properName;
  56. this.properlyNamed = true;
  57. } else if (options.name != undefined) {
  58. this.name = options.name;
  59. } else {
  60. this.name = (<any> this.constructor).name; // If there is no name, use Class as name
  61. }
  62. if (options.description != undefined) {
  63. if (options.description instanceof Say) {
  64. this.description = options.description;
  65. } else {
  66. this.description = new Say(options.description);
  67. }
  68. }
  69. if (options.unique) {
  70. Thing.storeUnique(this);
  71. this.unique = true;
  72. } else {
  73. Thing.storeNonUnique(this);
  74. this.cloneOptions = options;
  75. this.clone = (includeChanges? : boolean) => {
  76. let cons = <any> eval((<any> this.constructor).name);
  77. let newThing = <Thing> new cons(this.cloneOptions);
  78. if (includeChanges == undefined || includeChanges) {
  79. newThing.setChanges(this.getChanges());
  80. }
  81. return newThing;
  82. };
  83. }
  84. if (options.image != undefined) {
  85. if (options.image instanceof SayImage) {
  86. this.image = options.image;
  87. } else {
  88. this.image = new SayImage(options.image);
  89. }
  90. }
  91. this.shiny = options.shiny == true;
  92. // TODO: Make sure you never bring back a fodder Room - these disappear between games and we don't want to lose Things in them.
  93. this.addGetAlterations((thing : Thing) => {
  94. function getClosestRoom (currentRoom : RoomRandom, rooms : Array<RoomRandom>) {
  95. if (currentRoom instanceof RoomRandom && rooms.length > 0) {
  96. rooms.sort((a : RoomRandom, b : RoomRandom) => {
  97. if (!(a instanceof RoomRandom)) return -1;
  98. if (!(b instanceof RoomRandom)) return 1;
  99. let da = a.getDistanceTo(<RoomRandom> currentRoom);
  100. let db = b.getDistanceTo(<RoomRandom> currentRoom);
  101. return da - db;
  102. });
  103. return {
  104. Location : rooms[0].getName()
  105. }
  106. }
  107. }
  108. if (Thing.EnclosedRelation.getLeft(thing) == thing.getRoom() && thing.getRoom() != undefined) {
  109. if (thing.getRoom().fodder) {
  110. if (thing.isPlayer()) {
  111. // put at closest remembered room
  112. let rooms = WorldState.getRememberedRoomsAsRooms();
  113. let currentRoom = thing.getRoom();
  114. return getClosestRoom(<RoomRandom> currentRoom, <Array<RoomRandom>> rooms);
  115. } else {
  116. // put at closest room
  117. let rooms = thing.getRoom().getConnectedRooms();
  118. let currentRoom = thing.getRoom();
  119. let foundRoom = getClosestRoom(<RoomRandom> currentRoom, <Array<RoomRandom>> rooms);
  120. if (foundRoom != undefined) {
  121. return foundRoom;
  122. } else {
  123. rooms = (<Region> Region.InRelation.getLeft(thing.getRoom())).getRooms();
  124. return getClosestRoom(<RoomRandom> currentRoom, <Array<RoomRandom>> rooms);
  125. }
  126. }
  127. } else {
  128. return {
  129. Location: thing.getRoom().getName()
  130. }
  131. }
  132. }
  133. });
  134. this.addSetAlterations((thing : Thing, changes) => {
  135. if (changes.Location != undefined) {
  136. let room = Room.getRoom(changes.Location);
  137. if (room != undefined) {
  138. room.place(thing);
  139. } else {
  140. console.error("Unable to place ", thing, " at room ", changes.Location);
  141. }
  142. }
  143. });
  144. }
  145. public addGetAlterations (newGet) {
  146. this.getAlterations.push(newGet);
  147. }
  148. /**
  149. * This adds a function to run over when loading from a save file.
  150. * Always remember that save files are NOT SAFE. Ideally we should check for invalid information,
  151. * but at least check if they exist, because if you added something new, old saves will not have them.
  152. * It's okay to break on bad information because if someone decided to hack their save, them they should
  153. * deal with the issues.
  154. * @param newSet
  155. */
  156. public addSetAlterations (newSet) {
  157. this.setAlterations.push(newSet);
  158. }
  159. public getChanges () : {[id : string] : any}{
  160. let changes = {};
  161. for (let i = 0; i < this.getAlterations.length; i++) {
  162. let change = this.getAlterations[i](this);
  163. for (let key in change) {
  164. changes[key] = change[key];
  165. }
  166. }
  167. return changes;
  168. }
  169. public setChanges (simpleAlterationObject : {[id : string] : any}) {
  170. for (let i = 0; i < this.setAlterations.length; i++) {
  171. this.setAlterations[i](this, simpleAlterationObject);
  172. }
  173. }
  174. public getShiny () {
  175. return this.shiny;
  176. }
  177. public setName (name : string) {
  178. this.name = name; // Don't restore. The only thing that changes names is player.
  179. }
  180. public getName () {
  181. return this.name;
  182. }
  183. public static storeNonUnique (thing : Thing) {
  184. if (Thing.things[thing.name] == undefined) {
  185. Thing.things[thing.name] = [thing];
  186. } else {
  187. Thing.things[thing.name].push(thing);
  188. }
  189. }
  190. public static getNonUnique (name : string) : Array<Thing> {
  191. return Thing.things[name] == undefined ? [] : Thing.things[name];
  192. }
  193. public static getOneThing (name : string) {
  194. let thing : Thing = Thing.getUnique(name);
  195. if (thing == undefined) {
  196. let things = Thing.getNonUnique(name);
  197. if (things.length > 0) {
  198. thing = things[0];
  199. }
  200. }
  201. return thing;
  202. }
  203. public static storeUnique (unique : Thing) {
  204. if (Thing.uniqueThings[unique.name] != undefined) {
  205. console.warn(unique.name, Thing.uniqueThings[unique.name], new Error("Unique Thing Already Exists"));
  206. } else {
  207. Thing.uniqueThings[unique.name] = unique;
  208. }
  209. }
  210. public static getUnique (name : string) {
  211. return Thing.uniqueThings[name];
  212. }
  213. public static getUniques () : Array<Thing> {
  214. let things = [];
  215. for (let name in Thing.uniqueThings) {
  216. things.push(Thing.uniqueThings[name]);
  217. }
  218. return things;
  219. }
  220. public getPrintedName() {
  221. return this.name;
  222. }
  223. public getPrintedDescription () {
  224. if (this.description == undefined) {
  225. return new Say ("You see nothing special about ", new SayThe(), this, ".");
  226. } else {
  227. return this.description;
  228. }
  229. }
  230. public static InsideRoomRelation : RelationOneToMany= new RelationOneToMany();
  231. public static PartRelation = new RelationOneToMany();
  232. public static CarryRelation = new RelationOneToMany();
  233. public static WieldRelation = new RelationOneToMany();
  234. public static WearRelation = new RelationOneToMany();
  235. public static EnclosedRelation = new RelationHandlerStrictOneToMany(Thing.InsideRoomRelation, Thing.PartRelation, Thing.CarryRelation, Thing.WieldRelation, Thing.WearRelation);
  236. public getPartOne () {
  237. return Thing.PartRelation.getLeft(this);
  238. }
  239. public getCarryOne () {
  240. return Thing.CarryRelation.getLeft(this);
  241. }
  242. public getWieldOne () {
  243. return Thing.WieldRelation.getLeft(this);
  244. }
  245. public getWearOne () {
  246. return Thing.WearRelation.getLeft(this);
  247. }
  248. public getEnclosedOne () : Thing | Room {
  249. return Thing.EnclosedRelation.getLeft(this);
  250. }
  251. public removeParts (partType? : any) {
  252. let parts = this.getParts(partType);
  253. for (let i = 0; i < parts.length; i++) {
  254. Thing.PartRelation.unsetRight(parts[i]);
  255. }
  256. }
  257. public getEnclosed (enclosedType? : any) {
  258. if (enclosedType != undefined) {
  259. return Thing.EnclosedRelation.getAllRightTypes(this, enclosedType);
  260. }
  261. return Thing.EnclosedRelation.getAllRight(this);
  262. }
  263. public getWorns (wornType? : any) {
  264. if (wornType != undefined) {
  265. return Thing.WearRelation.getRightType(this, wornType);
  266. }
  267. return Thing.WearRelation.getRight(this);
  268. }
  269. public getParts (partType? : any) {
  270. if (partType != undefined) {
  271. return Thing.PartRelation.getRightType(this, partType);
  272. }
  273. return Thing.PartRelation.getRight(this);
  274. }
  275. public getPartsByName (name : string) : Array<Thing> {
  276. let parts = this.getParts();
  277. return parts.filter((part : Thing) => {
  278. return (part.getName() == name);
  279. });
  280. }
  281. public getPart (partType? : any) {
  282. if (partType != undefined) {
  283. return Thing.PartRelation.getRightTypeOne(this, partType);
  284. }
  285. return Thing.PartRelation.getRight(this);
  286. }
  287. // Will usually return the room at which this is.
  288. // Will return the highest parent if that parent is out of world.
  289. public getHighestEnclosedOne () : Thing | Room {
  290. return Thing.EnclosedRelation.getLastLeft(this);
  291. }
  292. public getHighestEnclosedOneNotRoom () : Thing {
  293. let parent = Thing.EnclosedRelation.getLeft(this);
  294. if (parent != undefined) {
  295. let newParent = Thing.EnclosedRelation.getLeft(parent);
  296. while (newParent != undefined) {
  297. parent = newParent;
  298. newParent = Thing.EnclosedRelation.getLeft(parent);
  299. }
  300. return parent;
  301. }
  302. return this;
  303. }
  304. public getRoom () : Room {
  305. var partOf = <Thing | Room> Thing.EnclosedRelation.getLeft(this);
  306. if (partOf instanceof Room) {
  307. return partOf;
  308. } else if (partOf instanceof Thing) {
  309. return partOf.getRoom();
  310. }
  311. }
  312. public removeFromRoom () {
  313. this.getRoom().remove(this);
  314. }
  315. public isVisibleTo (thing : Thing) {
  316. // Either in the same room or part of /wielded / carried by person
  317. return (this.getRoom() == thing.getRoom() && this.visible);
  318. // TODO : Update this code for the cases where a creature is not visible but the observer can see them anyway
  319. }
  320. public isPlayer () {
  321. return false;
  322. }
  323. public addParts (...parts : Array<Thing>) {
  324. parts.forEach(part => {
  325. Thing.PartRelation.setRelation(this, part);
  326. });
  327. }
  328. public isUnique () {
  329. return this.unique;
  330. }
  331. public setCarried (thing : Thing) {
  332. Thing.CarryRelation.setRelation(this, thing);
  333. }
  334. public setWorn (thing : Thing) {
  335. Thing.WearRelation.setRelation(this, thing);
  336. }
  337. public setWielded (thing : Thing) {
  338. Thing.WieldRelation.setRelation(this, thing);
  339. }
  340. public unsetCarried (thing : Thing) {
  341. if (Thing.EnclosedRelation.getAllRight(this).indexOf(thing) != -1) {
  342. Thing.EnclosedRelation.unsetRight(thing);
  343. this.getRoom().place(thing);
  344. }
  345. }
  346. public break () {
  347. if (this.breaksTo != undefined) {
  348. let unique = Thing.getUnique(this.breaksTo);
  349. if (unique != undefined) {
  350. this.getRoom().place(unique);
  351. } else {
  352. let nonunique = Thing.getOneThing(this.breaksTo);
  353. if (nonunique != undefined) {
  354. this.getRoom().place(nonunique.clone())
  355. }
  356. }
  357. }
  358. this.destroy();
  359. }
  360. /**
  361. * So long as a thing is in a relation, it can't be garbage cleaned.
  362. * If you're throwing a thing away, do run destroy.
  363. * Warning: this will also destroy everything that's below it.
  364. */
  365. public destroy () {
  366. let relatedRight = Thing.EnclosedRelation.getAllRight(this);
  367. relatedRight.push(this);
  368. relatedRight.forEach(related => {
  369. Thing.EnclosedRelation.unset(related);
  370. });
  371. }
  372. }