/// /// /// interface ThingOptions { name? : string; properName? : string; description? : Say | any; unique? : boolean; image? : SayImage | string; shiny? : boolean; breakable? : boolean; resistance? : number; breaksTo? : string; } // A thing is something that exists in the World class Thing implements Printable { protected name : string; public properlyNamed : boolean = false; public scenery : boolean = false; public fixedInPlace : boolean = false; public animated : boolean = false; public visible : boolean = true; public unique : boolean = false; public image : SayImage; protected shiny : boolean = false; public breakable = false; public breakableOn = 5; public breaksTo; protected setAlterations : Array<(thisObject : Thing, simpleAlterationObject : {[id : string] : any}) => void> = []; protected getAlterations : Array<(thisObject : Thing) => {[id : string] : any}> = []; public clone : (includeChanges? : boolean) => Thing = function () { throw new Error("Non-unique Objects can't be cloned."); }; public cloneOptions : ThingOptions; public description : Say | string | (() => Say | string); public static uniqueThings : {[id : string] : Thing} = {}; public static things : {[id : string] : Array} = {}; // This is not safe. // public duplicate () { // return new ( this.constructor)( { // name : this.name, // properName : this.properlyNamed ? this.name : undefined, // description : this.description, // unique : this.unique, // image : this.image // }); // } public constructor (options? : ThingOptions) { options = options == undefined ? {} : options; this.breakable = options.breakable === true; this.breakableOn = options.resistance == undefined ? 5 : options.resistance; this.breaksTo = options.breaksTo; if (options.properName != undefined) { this.name = options.properName; this.properlyNamed = true; } else if (options.name != undefined) { this.name = options.name; } else { this.name = ( this.constructor).name; // If there is no name, use Class as name } if (options.description != undefined) { if (options.description instanceof Say) { this.description = options.description; } else { this.description = new Say(options.description); } } if (options.unique) { Thing.storeUnique(this); this.unique = true; } else { Thing.storeNonUnique(this); this.cloneOptions = options; this.clone = (includeChanges? : boolean) => { let cons = eval(( this.constructor).name); let newThing = new cons(this.cloneOptions); if (includeChanges == undefined || includeChanges) { newThing.setChanges(this.getChanges()); } return newThing; }; } if (options.image != undefined) { if (options.image instanceof SayImage) { this.image = options.image; } else { this.image = new SayImage(options.image); } } this.shiny = options.shiny == true; this.addGetAlterations((thing : Thing) => { function getClosestRoom (currentRoom : RoomRandom, rooms : Array) { if (currentRoom instanceof RoomRandom && rooms.length > 0) { rooms.sort((a : RoomRandom, b : RoomRandom) => { if (!(a instanceof RoomRandom)) return -1; if (!(b instanceof RoomRandom)) return 1; let da = a.getDistanceTo( currentRoom); let db = b.getDistanceTo( currentRoom); return da - db; }); return { Location : rooms[0].getName() } } } if (Thing.EnclosedRelation.getLeft(thing) == thing.getRoom() && thing.getRoom() != undefined) { if (thing.getRoom().fodder) { if (thing.isPlayer()) { // put at closest remembered room let rooms = WorldState.getRememberedRoomsAsRooms(); let currentRoom = thing.getRoom(); return getClosestRoom( currentRoom, > rooms); } else { // put at closest room let rooms = thing.getRoom().getConnectedRooms(); let currentRoom = thing.getRoom(); let foundRoom = getClosestRoom( currentRoom, > rooms); if (foundRoom != undefined) { return foundRoom; } else { rooms = ( Region.InRelation.getLeft(thing.getRoom())).getRooms(); return getClosestRoom( currentRoom, > rooms); } } } else { return { Location: thing.getRoom().getName() } } } }); this.addSetAlterations((thing : Thing, changes) => { if (changes.Location != undefined) { let room = Room.getRoom(changes.Location); if (room != undefined) { room.place(thing); } else { console.error("Unable to place ", thing, " at room ", changes.Location); } } }); } public addGetAlterations (newGet) { this.getAlterations.push(newGet); } /** * This adds a function to run over when loading from a save file. * Always remember that save files are NOT SAFE. Ideally we should check for invalid information, * but at least check if they exist, because if you added something new, old saves will not have them. * It's okay to break on bad information because if someone decided to hack their save, them they should * deal with the issues. * @param newSet */ public addSetAlterations (newSet) { this.setAlterations.push(newSet); } public getChanges () : {[id : string] : any}{ let changes = {}; for (let i = 0; i < this.getAlterations.length; i++) { let change = this.getAlterations[i](this); for (let key in change) { changes[key] = change[key]; } } return changes; } public setChanges (simpleAlterationObject : {[id : string] : any}) { for (let i = 0; i < this.setAlterations.length; i++) { this.setAlterations[i](this, simpleAlterationObject); } } public getShiny () { return this.shiny; } public setName (name : string) { this.name = name; // Don't restore. The only thing that changes names is player. } public getName () { return this.name; } public static storeNonUnique (thing : Thing) { if (Thing.things[thing.name] == undefined) { Thing.things[thing.name] = [thing]; } else { Thing.things[thing.name].push(thing); } } public static getNonUnique (name : string) : Array { return Thing.things[name] == undefined ? [] : Thing.things[name]; } public static getOneThing (name : string) { let thing : Thing = Thing.getUnique(name); if (thing == undefined) { let things = Thing.getNonUnique(name); if (things.length > 0) { thing = things[0]; } } return thing; } public static storeUnique (unique : Thing) { if (Thing.uniqueThings[unique.name] != undefined) { console.warn(unique.name, Thing.uniqueThings[unique.name], new Error("Unique Thing Already Exists")); } else { Thing.uniqueThings[unique.name] = unique; } } public static getUnique (name : string) { return Thing.uniqueThings[name]; } public static getUniques () : Array { let things = []; for (let name in Thing.uniqueThings) { things.push(Thing.uniqueThings[name]); } return things; } public getPrintedName() { return this.name; } public getPrintedDescription () { if (this.description == undefined) { return new Say ("You see nothing special about ", new SayThe(), this, "."); } else { return this.description; } } public static InsideRoomRelation : RelationOneToMany= new RelationOneToMany(); public static PartRelation = new RelationOneToMany(); public static CarryRelation = new RelationOneToMany(); public static WieldRelation = new RelationOneToMany(); public static WearRelation = new RelationOneToMany(); public static EnclosedRelation = new RelationHandlerStrictOneToMany(Thing.InsideRoomRelation, Thing.PartRelation, Thing.CarryRelation, Thing.WieldRelation, Thing.WearRelation); public getPartOne () { return Thing.PartRelation.getLeft(this); } public getCarryOne () { return Thing.CarryRelation.getLeft(this); } public getWieldOne () { return Thing.WieldRelation.getLeft(this); } public getWearOne () { return Thing.WearRelation.getLeft(this); } public getEnclosedOne () : Thing | Room { return Thing.EnclosedRelation.getLeft(this); } public removeParts (partType? : any) { let parts = this.getParts(partType); for (let i = 0; i < parts.length; i++) { Thing.PartRelation.unsetRight(parts[i]); } } public getWorns (wornType? : any) { if (wornType != undefined) { return Thing.WearRelation.getRightType(this, wornType); } return Thing.WearRelation.getRight(this); } public getParts (partType? : any) { if (partType != undefined) { return Thing.PartRelation.getRightType(this, partType); } return Thing.PartRelation.getRight(this); } public getPartsByName (name : string) : Array { let parts = this.getParts(); return parts.filter((part : Thing) => { return (part.getName() == name); }); } public getPart (partType? : any) { if (partType != undefined) { return Thing.PartRelation.getRightTypeOne(this, partType); } return Thing.PartRelation.getRight(this); } // Will usually return the room at which this is. // Will return the highest parent if that parent is out of world. public getHighestEnclosedOne () : Thing | Room { return Thing.EnclosedRelation.getLastLeft(this); } public getHighestEnclosedOneNotRoom () : Thing { let parent = Thing.EnclosedRelation.getLeft(this); if (parent != undefined) { let newParent = Thing.EnclosedRelation.getLeft(parent); while (newParent != undefined) { parent = newParent; newParent = Thing.EnclosedRelation.getLeft(parent); } return parent; } return this; } public getRoom () : Room { var partOf = Thing.EnclosedRelation.getLeft(this); if (partOf instanceof Room) { return partOf; } else if (partOf instanceof Thing) { return partOf.getRoom(); } } public removeFromRoom () { this.getRoom().remove(this); } public isVisibleTo (thing : Thing) { // Either in the same room or part of /wielded / carried by person return (this.getRoom() == thing.getRoom() && this.visible); // TODO : Update this code for the cases where a creature is not visible but the observer can see them anyway } public isPlayer () { return false; } public addParts (...parts : Array) { parts.forEach(part => { Thing.PartRelation.setRelation(this, part); }); } public isUnique () { return this.unique; } public setCarried (thing : Thing) { Thing.CarryRelation.setRelation(this, thing); } public setWorn (thing : Thing) { Thing.WearRelation.setRelation(this, thing); } public setWielded (thing : Thing) { Thing.WieldRelation.setRelation(this, thing); } public unsetCarried (thing : Thing) { if (Thing.EnclosedRelation.getAllRight(this).indexOf(thing) != -1) { Thing.EnclosedRelation.unsetRight(thing); this.getRoom().place(thing); } } public break () { if (this.breaksTo != undefined) { let unique = Thing.getUnique(this.breaksTo); if (unique != undefined) { this.getRoom().place(unique); } else { let nonunique = Thing.getOneThing(this.breaksTo); if (nonunique != undefined) { this.getRoom().place(nonunique.clone()) } } } this.destroy(); } /** * So long as a thing is in a relation, it can't be garbage cleaned. * If you're throwing a thing away, do run destroy. * Warning: this will also destroy everything that's below it. */ public destroy () { let relatedRight = Thing.EnclosedRelation.getAllRight(this); relatedRight.push(this); relatedRight.forEach(related => { Thing.EnclosedRelation.unset(related); }); } }