1
1

Thing.ts 14 KB

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