///
///
///
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;
public lastActionTurn = -1;
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;
// TODO: Make sure you never bring back a fodder Room - these disappear between games and we don't want to lose Things in them.
this.addGetAlterations((thing : Thing) => {
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().filter((room : Room) => { return !(room instanceof RoomRandomFodder);});
let room : Room;
if (rooms.length <= 0) {
// Find Closest visited room
rooms = Region.InRelation.getLeft(thing.getRoom()).getRooms().filter(room => {return room.visited && !room.fodder && room.placed;}).sort((rooma, roomb) => {
if (rooma instanceof RoomRandom && roomb instanceof RoomRandom) {
let da = rooma.getDistanceTo( thing.getRoom());
let db = roomb.getDistanceTo( thing.getRoom());
return da - db;
}
return 0;
});
}
if (rooms.length > 0) {
room = rooms[0]; // freshest room
} else {
room = PlayBegins.startingRoom; // back to square one
}
return {
Location : room.getName()
}
} else {
// put at closest room
let rooms = Region.InRelation.getLeft(thing.getRoom()).getRooms().filter(room => {return !room.fodder && room.placed;}).sort((rooma, roomb) => {
if (rooma instanceof RoomRandom && roomb instanceof RoomRandom) {
let da = rooma.getDistanceTo( thing.getRoom());
let db = roomb.getDistanceTo( thing.getRoom());
return da - db;
}
return 0;
});
return {
Location : rooms[0].getName()
}
}
} 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 getEnclosed (enclosedType? : any) {
if (enclosedType != undefined) {
return Thing.EnclosedRelation.getAllRightTypes(this, enclosedType);
}
return Thing.EnclosedRelation.getAllRight(this);
}
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);
});
}
}