Part two of the above post.
ISSUES:-I haven't tested any room code that references itself using this_object() yet.
-Warmboots will destroy instance objects and send anyone in an instanced room to the furnace. Ouch.
-Any teleporting abilities should use 'living.c->eventMoveLiving()' if they want instances to work properly with them.
-Not sure what kind of effect 'reclaim_objects()' will have on instance objects and instanced rooms.
-Only interactive living objects are allowed to enter instance rooms. This was done as a simple way of diabling NPCs from accidentally generating instances if they wandered into one. This means pets and non-player followers won't be able to follow without some additional modifications to 'instance_server.c->CreateInstance()'.
-Originally the player would simply be sent to an instance based on their InstanceID string. However this left a vulnerability for malicious players to use. They could repeatedly change their party name and enter new instanced room in order to build up a large number of instances. To stop this, I added the 'InstanceObject', a simple reference to the last instance the player entered. As long as they are moving within an instance room this will be left untouched. The system checks this object first to see if a currently existing instance exists and defaults to the InstanceID string only when that fails. This reference is reset when they enter a non-instance room. In this way the player can only create and enter a new instance after they have left an old one.
-Instances are cleaned up as soon as all players leave by entering a non-instanced room. None of the rooms are cleaned up until then.
-Haven't tested to see if the reaper will cleanup the rooms while players are in them. I tried to fend off the reaper by simply making instances a container and placing the room instances within it (the eventMove functoin has been overriden to allow instanced rooms to be moved). Perhaps I could have just made the rooms daemons. I don't really know. Any advice?
-The instance server destroys an instance when all memebers leave it. The only way for it to know is the players' 'eventMoveLiving' function is called. This function will inform the instance when a member leaves it by entering a non-instanced room. If the player(s) were to be teleported out in some fashion that does not call this, the instance would never know and could continue to exist. This could be exploited by malicious players.
To a degree this has been combated in the server daemon. It occasionally checks some of the instances and passes through all of the belonging memebers. If they are no longer active, existant, or in an instanced room. The instance will be destroyed. With some modification this feature may be helpful for fixing the issue of a large build up of empty instance rooms.
-Malicious player may be able to quit while in an instance (thus stopping it from auto-destructing it), log back into a non-instanced room, change their party name, and re-enter a new instance. Doing this repeatedly will build up a large bumber of instances.
-All instanced rooms are stored within the same instance regardless of where they are or how the player entered. Because no rooms are deleted until all members of the instance leave, it may be possible to build up a rather large collection of rooms before anything gets cleaned up if the number of concurrent instanced rooms is high.
-All instances are registered and created when a player enters an instance room and the server has no record of their party or name. It is possible for malicious players to contantly change their party status and move into different instance rooms in order to build up a large number of instances.
-The 'master copy' of the room to be instanced is never stored anywhere so it is likely to be destrcuted by the reaper at one point or another. All of the referencing in the instances is dones with the filename however so this isn;t an issue most of the time. However, if you want that room to stick around for some additional features like a global room registery in the server you'll need to rework the 'eventMove' override in instance_room.c and allow non-instanced rooms to be moved. From there, you should be able to setup some kind of inventory within the
instance_server.c/*
By: SLUGGY
Distributed under WTFPL. No warranties.
2009
Daemon that allocates and manages instanced areas.
*/
#include <lib.h>
#include <daemons.h>
#include <rooms.h>
#include <instancerooms.h>
inherit LIB_DAEMON;
static mixed AddPlayer(object player);
void SetDomain(string name);
void CleanInstances();
//'Players' tracks all players that are in an instance and to what instance they currently belong
//private mapping Players = ([]);
//'Instances' tracks all instances running and references them by hash IDs. These ids are generated
//for each idividual that enters and instance room as well as for the first member of a party. In the
//case of an individual, their name is the key, for a party, the party name plus the domainname is the key.
//In this way, party members can all enter the same instance if they are at the same domain.
private mapping Instances = ([]);
//'Owners' keeps another list of each instance id like above, but this one simply stores a reference
//to all players currently within that instance.
//private mapping Owners = ([]);
private int CalloutHandle;
private int StartRange = 0;
static void create()
{
::create();
//TODO: setup a callout here to periodically check for empty instances and clean them up when need be
CalloutHandle = call_out("CleanInstances",CLEANUP_FREQU);
}
void init()
{
}
void CleanInstances()
{
int count = StartRange;
int max = sizeof(values(Instances));
string* cleanList = ({});
//TODO: There could probably be quite a few optimiztions made here
//build a list of rooms to clean
foreach(string key in keys(Instances))
{
object inst = Instances[key];
//NOTE: This instance may self destruct after this point,
if(inst->InstanceClean())
{
cleanList += ({key});
}
StartRange++;
if(StartRange > max)
{
StartRange = 0;
}
count++;
if(count > INSTANCE_CLEAN_CHUNK)
{
break;
}
}
//now cleanup the list
foreach(string key2 in cleanList)
{
//this will call RemoveInstance() for us.
Instances[key2]->eventDestruct();
}
remove_call_out(CalloutHandle);
CalloutHandle = call_out("CleanInstances",CLEANUP_FREQU);
}
int RemoveInstance(string name)
{
map_delete(Instances,name);
return 1;
}
/*
Create a new clone of a given master copy room (it's filename). Register it under the instance given.
This function assumes a copy doesn't already exist for this instance object.
*/
object CreateInstanceRoom(string masterRoom,object instance)
{
object room;
if(!masterRoom || !stringp(masterRoom)) return 0;
if(!instance || !objectp(instance)) return 0;
if( (room = new(masterRoom)) )
{
room->Register(masterRoom,instance);
}
else{
error("Failed to create room instance object for " + masterRoom + ". instance_server->CreateInstanceRoom().");
}
return room;
}
/*
If a particular clone of a room belonging to the instance exists return it, otherwise
create a new one, register it with the instance and return that.
*/
object GetInstanceRoom(string masterRoom,object instance)
{
mapping roomMap;
if(!masterRoom || !stringp(masterRoom)) return 0;
if(!instance || !objectp(instance)) return 0;
roomMap = instance->GetRoomsMap();
if(roomMap && mapp(roomMap) )
{
foreach(string str in keys(roomMap))
{
if(str == masterRoom)
{
return roomMap[str];
}
}
}
return CreateInstanceRoom(masterRoom,instance);
}
/*
Return an instance object if the player belongs to one. Othwerwise, zero.
*/
object PlayerBelongsToInstance(object player)
{
object instance;
if(!player || !objectp(player)) return 0;
if( (instance = player->GetInstanceObject()) ) return instance;
else{
string id = player->GetInstanceID(1);
if(member_array(id,keys(Instances)) == -1)
{
return 0;
}
return Instances[id];
}
return 0;
}
/*
Create a new instance and register it under the player's party name or actual name.
This function assumes that it has already been confirmed that the instance doesn't exist.
*/
object CreateInstance(object player)
{
string id;
object instance;
if(!player || !objectp(player) || !interactive(player)) return 0;
if(sizeof(keys(Instances)) > MAX_INSTANCE_ALLOCATION)
{
//TODO: print the date here
string temp = "Instance allocation limit was hit by " + player->GetName() + "(" + player->GetInstanceID() + ").";
tc(temp);
tell_object(player,INSTANCE_UNAVAILABLE_MSG);
return 0;
}
if(! (instance = new(OBJ_INSTANCE)) )
{
error("Failed to create instance object. Player: " + player->GetName() + ", Party: " + player->GetParty() + ", InstanceID: " + player->GetInstanceID(1) + ".");
return 0;
}
id = player->GetInstanceID(1);
instance->SetInstanceID(id);
player->Register(instance);
Instances[id] = instance;
return instance;
}
/*
If an instance to which the player is registered exists return it, otherwise
create a new one and return that.
*/
object GetInstance(object player)
{
object instance;
if( (instance = PlayerBelongsToInstance(player)) ) return instance;
return CreateInstance(player);
}
instance.c/*
By: SLUGGY
Distributed under WTFPL. No warranties.
2009
Manages all of the rooms and players within a single instance.
*/
#include <lib.h>
#include <rooms.h>
#include <instancerooms.h>
inherit LIB_DAEMON;
inherit LIB_CONTAINER;
//this will either be set to the party name of the first person to enter
//or the name of a single person that was in no party
private string RegisteredName = 0;
//[master room's filename : cloned object]
private mapping Rooms = ([]);
//references to all players that are part of this instance
private object* Owners = ({});
static void create()
{
::create();
//just in case this is derived by something that might be cleaned :O
SetNoClean(1);
}
void init()
{
}
int SetInstanceID(string name)
{
if(name && stringp(name) && name != "")
{
RegisteredName = name;
return 1;
}
return 0;
}
int eventDestruct()
{
/*
if(checkFlag)
{
foreach(object room in values(Rooms))
{
if(room && objectp(room))
{
//TODO: make absolutely sure that all players are gone from the room
// if not, then we need to cancel the destruct and re-add these
// players to the 'Owners' list.
//return 0;
}
}
}
*/
INSTANCE_SERVER_D->RemoveInstance(RegisteredName);
return ::eventDestruct();
//destruct(this_object());
}
int AddPlayer(object player)
{
if(player && objectp(player))
{
if(member_array(player,Owners) == -1) Owners += ({ player });
return 1;
}
return 0;
}
int RemovePlayer(object player)
{
if(player && objectp(player))
{
if(member_array(player,Owners) != -1)
{
Owners -= ({ player });
}
if(sizeof(Owners) < 1)
{
eventDestruct();
}
return 1;
}
return 0;
}
object* GetOwners()
{
return Owners;
}
int AddRoom(string masterName,object room)
{
if(!masterName || !stringp(masterName) || !sizeof(masterName)) return 0;
if(room && objectp(room) && room->IsInstanceableRoom() && room->IsInstanced())
{
if(member_array(masterName,keys(Rooms)) != -1)
{
//entry already exists
return 1;
}
Rooms[masterName] = room;
//room->eventMove(this_object()); //put the instanced-room in the instance's inventory
return 1;
}
return 0;
}
int RemoveRoom(string masterName)
{
if(!masterName || !stringp(masterName) || !sizeof(masterName)) return 0;
if(member_array(masterName,keys(Rooms)) == -1) return 0;
//TODO: make absolutely sure that all players are gone from the room
// if not, then we need to cancel the destruct and re-add these
// players to the 'Owners' list.
map_delete(Rooms,masterName);
//room->eventDestruct();
return 1;
}
mapping GetRoomsMap()
{
return Rooms;
}
int IsInstance()
{
return 1;
}
int InstanceClean()
{
if(!sizeof(Owners))
{
return 1;
}
foreach(object room in values(Rooms))
{
foreach(object dude in livings(room))
{
//if there are any players in any rooms then we shouldn't allow cleanup
if(interactive(dude)) {return 0;}
}
}
return 1;
}
instance_room.c/*
By: SLUGGY
Distributed under WTFPL. No warranties.
2009
Instanced room. This room is set to not be cleaned by the GC, stores a reference to it's instance master
and registers itself with that master. All exits generate additional rooms within the instance.
*/
#include <lib.h>
#include <rooms.h>
inherit LIB_ROOM;
private object InstanceObject;
private int InstanceActive = 0;
private int PartyAllowed = 1;
static void create()
{
::create();
SetNoClean(1);
SetNoReplace(1);
}
static void init()
{
::init();
}
int eventDestruct()
{
if(this_object())
{move_object(ROOM_FURNACE);}
::eventDestruct();
}
int SetPartyAllowed(int flag)
{
return (PartyAllowed = flag);
}
int GetPartyAllowed()
{
return PartyAllowed;
}
int Register(string masterRoom,object instance)
{
if(!instance || !objectp(instance))
{
error("Invalid instance master passed to " + this_object()->GetSHort() + ". instance_room.c->Register().");
return 0;
}
move_object(instance);
InstanceObject = instance;
InstanceActive = 1;
InstanceObject->AddRoom(masterRoom,this_object());
return 1;
}
int Unregister()
{
if(!InstanceObject || !objectp(InstanceObject))
{
error("Master InstanceObject reference in " + this_object()->GetShort() + " is invalid. instance_room.c->Unregister().");
return 0;
}
//NOTE: After this point, the instance may have self-destructed so do not try to access it again.
InstanceObject->RemoveRoom(this_object());
InstanceActive = 0;
eventDestruct();
return 1;
}
int IsInstanced()
{
return InstanceActive;
}
int IsInstanceableRoom()
{
return 1;
}
/*
int eventMove(object dest)
{
if(dest && objectp(dest) && dest->IsInstance())
{
if(IsInstanced())
{
move_object(dest);
return 1;
}
}
return 0;
}
*/
living_instance.c/*
By: SLUGGY
Distributed under WTFPL. No warranties.
2009
Provides a few utility functions used by the stock 'living.c' file for checking
if a creature is entering an instancable room as well as storing instance data.
*/
#include <lib.h>
#include <instancerooms.h>
private object InstanceObject = 0;
void SetInstanceObject(object inst)
{
InstanceObject = inst;
}
object GetInstanceObject()
{
return InstanceObject;
}
string GetInstanceID(int partyAllowed)
{
if(partyAllowed)
{
string party = this_object()->GetParty();
if(party && stringp(party) && sizeof(party) > 0)
{
return party;
}
}
return this_object()->GetName();
}
int Register(object instance)
{
if(!instance || !objectp(instance))
{
error("Invalid instance master passed to " + this_object()->GetName() + ". livinginstance.c->Register().");
return 0;
}
InstanceObject = instance;
InstanceObject->AddPlayer(this_object());
return 1;
}
int Unregister()
{
if(InstanceObject && objectp(InstanceObject))
{
//NOTE: After this point, the instance may have self-destructed so do not try to access it again.
InstanceObject->RemovePlayer(this_object());
InstanceObject = 0;
return 1;
}
return 0;
}
/*
Called by living.c->eventMoveLiving(). This function should return true if the player
is moved to a cloned instance room and false if not. And value of false signifies to that
caller that it can continue as normal and move the player into a non-instanced room. Otherwise
it will just return this function's return value.
*/
varargs int eventRegisterInstance(mixed dest, string omsg, string imsg, mixed dir)
{
object player = this_object();
object inst;
object room;
string instID;
//if this destination isn't an instance-room we can unregister any previous instance master
//and return false to let the player move into the room normally
if(!dest->IsInstanceableRoom())
{
Unregister();
return 0;
}
//Make sure this isn't a recursive call due to the below call to 'eventMoveLiving'. if that is
//the case, then this destination will be a clone rather than the master copy and as such we simply
//want the player to enter the destination provided to the function that called this one.
if(dest->IsInstanced())
{
return 0;
}
//TODO: Should check the room's flag to see if parties are allowed to instance together
if( (inst = INSTANCE_SERVER_D->GetInstance(player)) )
{
room = INSTANCE_SERVER_D->GetInstanceRoom((string)dest,inst);
if(!room || !objectp(room))
{
error("Failed to create or obtain instance of a room. livinginstance->eventEnterInstance().");
tell_object(player,INSTANCE_FAILED_MSG);
return 1;
}
if(!player->GetInstanceObject())
{
player->Register(inst);
}
return player->eventMoveLiving(room,omsg,imsg,dir);
}
else{
error("Could not obtain instance.");
tell_object(player,INSTANCE_FAILED_MSG);
return 1; //we still return 1 so player still can't enter master copy of room
}
//this lets the calling function, living->eventMoveLiving(), know that it can should not
//proceed as normal so that the player isn't moved into the master copy of the room by mistake.
return 1;
}
instancerooms.hNOTE: Don't forget to change the directories to where you want them. Also remember that single empty line you need at the end of the file after the #endif

That one always gets me.
/*
By: SLUGGY
Distributed under WTFPL. No warranties.
2009
*/
#ifndef __INSTANCEROOMS_H
#define __INSTANCEROOMS_H
#define INSTANCE_SERVER_D "/lib/instances/instance_server.c"
#define OBJ_INSTANCE "/lib/instances/instance"
#define LIB_INSTANCEROOM "/lib/instances/instance_room"
#define MAX_INSTANCE_ALLOCATION 50
#define INSTANCE_CLEAN_CHUNK 5
//attempt cleanup approx every fifteen minutes
#define CLEANUP_FREQU 900
#define INSTANCE_UNAVAILABLE_MSG "%^BLACK%^The server is too busy to provide an instance right now. Please wait a bit before trying to enter again. Thank you.%^RESET%^"
#define INSTANCE_FAILED_MSG "%^BLACK%^There was a problem creating an instance for you. Please inform mud staff (ERROR: instance #1). Thank you. %^RESET%^"
#define INSTANCEROOM_FAILED_MSG "%^BLACK%^There was a problem creating a room instance for you. Please inform mud staff (ERROR: instance #2). Thank you. %^RESET%^"
#endif
If I forgot anything lemme know. I sometimes do dumb things like that.