Thing.ts 15 KB

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