/*
....[@@@..[@@@..............[@.................. MUD++ is a written from
....[@..[@..[@..[@..[@..[@@@@@....[@......[@.... scratch multi-user swords and
....[@..[@..[@..[@..[@..[@..[@..[@@@@@..[@@@@@.. sorcery game written in C++.
....[@......[@..[@..[@..[@..[@....[@......[@.... This server is an ongoing
....[@......[@..[@@@@@..[@@@@@.................. development project. All
................................................ contributions are welcome.
....Copyright(C).1995.Melvin.Smith.............. Enjoy.
------------------------------------------------------------------------------
Melvin Smith (aka Fusion) msmith@hom.net
MUD++ development mailing list mudpp@van.ml.org
------------------------------------------------------------------------------
room.cc
*/
#include "config.h"
#include "io.h"
#include "string.h"
#include "llist.h"
#include "description.h"
#include "repop.h"
#include "hash.h"
#include "server.h"
#include "area.h"
#include "object.h"
#include "npc.h"
#include "pc.h"
//#include "shop.h"
#include "room.h"
#include "global.h"
const bitType sector_list[] =
{
{0,0},
{"ground", SECTOR_GROUND },
{"air", SECTOR_AIR },
{"water", SECTOR_WATER },
{"underwater", SECTOR_UNDERWATER },
{"uw", SECTOR_UNDERWATER },
{0,0}
};
const bitType room_bit_list[] =
{
{0,0},
{"dark", ROOM_DARK },
{"safe", ROOM_SAFE },
{"no-summon", ROOM_NOSUMMON },
{"no-leave", ROOM_NOLEAVE },
{"death-trap", ROOM_DEATHTRAP },
{"drain", ROOM_DRAIN },
{"dt", ROOM_DEATHTRAP },
{"shield", ROOM_SHIELD },
{"fast-mana", ROOM_FASTMANA },
{"fast-hp", ROOM_FASTHP },
{"no-mana", ROOM_NOMANA },
{"no-hp", ROOM_NOHP },
{"indoors", ROOM_INDOORS },
{0,0}
};
int Room::total_count = 0;
Room::~Room()
{
Repop *pRepop;
Char *ch;
Object *obj;
LList<Object> tList = objects;
int i;
total_count--;
repops.reset();
while( ( pRepop = repops.remove() ) )
{
if( pRepop->getPtr() )
pRepop->getPtr()->setRepop( 0 );
pRepop->fordelete();
}
chars.reset();
while( ( ch = chars.remove() ) )
{
// Caller should verify whether there are PCs in the room because
// we killem all.
ch->fromWorld();
ch->fordelete();
}
inv.reset();
while( ( obj = inv.remove() ) )
{
obj->fromWorld();
obj->fordelete();
}
for ( i =0; i < MAX_DIR; i++)
{
if ( exits[i] )
exits[i]->fordelete();
}
}
const String & Room::getScope() const
{
return area->getKey();
}
Room * lookupRoom( const String & x )
{
return lookupRoom( Index( x ) );
}
Room * lookupRoom( const char * x )
{
return lookupRoom( Index( x ) );
}
Room * lookupRoom( const Index & x )
{
Area *area;
LList<Area> tlist = areas;
for_each( tlist, area )
if( area->getKey() == x.getScope() )
break;
if( area )
return area->lookupRoom( x.getKey() );
return 0;
}
void Room::addRepop( Repop * x )
{
repops.addAfterCurrent( x );
// Ugg, this is ugly, just because I am too lazy to write a
// 'tail' into the LList class. Will have to do that soon.
// For now this is so repops will add onto the list in order.
repops.next();
}
void Room::addRepop( int slot, Repop * x )
{
if( slot > 1 )
{
repops.reset();
while( slot-- > 2 )
repops.next();
repops.addAfterCurrent( x );
}
else repops.addTop( x );
}
Repop * Room::rmRepop( int slot )
{
int i = 0;
Repop * ptrRepop;
if( slot < 1 )
return 0;
for_each( repops, ptrRepop )
{
i++;
if( i == slot )
{
repops.remove( ptrRepop );
return ptrRepop;
}
}
return 0;
}
void Room::repop()
{
Repop *pRepop;
NPC *npc;
NPC *npcLast = 0;
Object *obj = 0;
Object *objLast = 0;
Object *objLastImmediate = 0;
const Object *objProto;
const NPC *npcProto;
if ( TgUpdate() )
return;
for_each( repops, pRepop )
{
// This allows things like resetting a sword inside a stone
// or other cool things. I don't do it with NPC's because
// of possibility of a thief stealing something everytime
// a NPC got re-equipped. No re-equips.
if( pRepop->getPtr() )
{
if( pRepop->type == 'M' )
npcLast = 0;
else if( pRepop->type == 'O' )
objLast = (Object *)pRepop->getPtr();
continue;
}
switch( pRepop->type )
{
default: continue;
// M - load NPC
// G - give Object to last NPC
// E - equip last NPC with Object
// O - load Object to room
// P - put Object in last Object loaded with 'O' code
// N - nest object in 'immediate' last object loaded
case 'M': // Load NPC, set npcLast to this one.
npcProto = lookupNPC( pRepop->index );
if( npcProto )
{
/* if( npcProto->isShopKeeper() )
npc = new ShopKeeper( *(ShopKeeper *)npcProto );
else
*/
npc = new NPC( *npcProto );
if ( npc->TgCreated(this,pRepop ) )
{
npcLast = 0;
break;
}
npc->toWorld();
npcLast = npc;
addCharInv( npc );
npc->setRepop( pRepop );
pRepop->setPtr( npc );
}
break;
case 'O': // Load object, set objLast to this one.
objProto = lookupObj( pRepop->index );
if( objProto )
{
obj = objProto->clone();
if ( obj->TgCreated(this, pRepop) )
{
objLast = 0;
break;
}
obj->toWorld();
objLast = objLastImmediate = obj;
addObjInv( obj );
obj->setRepop( pRepop );
pRepop->setPtr( obj );
}
break;
case 'E': // Check for last NPC loaded. Equip object
if( !npcLast )
continue;
objProto = lookupObj( pRepop->index );
if( objProto )
{
obj = objProto->clone();
if ( obj->TgCreated(this, pRepop) )
{
objLast = 0;
break;
}
obj->toWorld();
npcLast->addObjInv( obj );
// val = wear position
npcLast->equip( obj, pRepop->val );
obj->setRepop( pRepop );
pRepop->setPtr( obj );
objLast = objLastImmediate = obj;
}
break;
case 'G': // Check for last NPC loaded. Give object to him.
if( !npcLast )
continue;
objProto = lookupObj( pRepop->index );
if( objProto )
{
obj = objProto->clone();
if ( obj->TgCreated(this, pRepop) )
{
objLast = 0;
break;
}
obj->toWorld();
npcLast->addObjInv( obj );
Cout << "Giving " << obj->getShort()
<< " to " << npcLast->getShort()
<< endl;
obj->setRepop( pRepop );
pRepop->setPtr( obj );
objLast = objLastImmediate = obj;
}
else
{
Cout << "Repop lookup failed for "
<< pRepop->index.asString() << endl;
}
break;
case 'P': // Put object in last object.
// Doesn't change objLast
if( !objLast )
continue;
objProto = lookupObj( pRepop->index );
if( objProto )
{
obj = objProto->clone();
if ( obj->TgCreated(this, pRepop) )
{
objLast = 0;
break;
}
obj->toWorld();
objLast->addObjInv( obj );
obj->setRepop( pRepop );
pRepop->setPtr( obj );
objLastImmediate = obj;
}
break;
case 'N': // Nest object in immediate last object loaded.
// Doesn't change objLast
if( !objLastImmediate )
continue;
objProto = lookupObj( pRepop->index );
if( objProto )
{
obj = objProto->clone();
if ( obj->TgCreated(this, pRepop) )
{
objLast = 0;
break;
}
obj->toWorld();
objLastImmediate->addObjInv( obj );
obj->setRepop( pRepop );
pRepop->setPtr( obj );
objLastImmediate = obj;
}
break;
}
}
}
const String & Room::getExtraDesc( const char * x )
{
static String retNULL;
Description * d;
for_each( ed, d )
if( d->isName( x ) )
return d->getDesc();
return retNULL;
}
void Room::addObjInv( Object * obj )
{
obj->toRoom( this );
inv.add( obj );
}
void Room::rmObjInv( Object * obj )
{
obj->fromRoom();
inv.remove( obj );
}
void Room::addCharInv( Char *ch )
{
ch->toRoom( this );
chars.add( ch );
}
void Room::rmCharInv( Char *ch )
{
ch->fromRoom();
chars.remove( ch );
}
void Room::out( const char *str )
{
Char *ch;
for_each( chars, ch )
ch->out( str );
}
void Room::outAllExcept( const String & str, Thing * t1, Thing * t2 )
{
Char * ch, * c1 = 0, * c2 = 0;
c1 = ( Char * ) t1;
c2 = ( Char * ) t2;
for_each( chars, ch )
if( ch != t1 && ch != t2 )
ch->out( str );
}
void Room::outAllCharExcept( const char *str, Char *ch1, Char *ch2 )
{
Char *ch;
for_each( chars, ch )
if( ch != ch1 && ch != ch2 )
ch->out( str );
}
void Room::interp( const String & in, Char * n, Char * N, Object * o, Object * O )
{
Char *ch;
for_each( chars, ch )
if( ch != n && ch != N )
ch->interp( in, n, N, o, O );
}
int Room::readFrom( StaticInput &in )
{
int err;
char buf[ BUF*2 ];
if( !in )
in.error("StaticInput stream not open.");
if( *in.getword( buf ) != '{' )
in.error( "Room::readFrom() - no '{' found");
name = in.getstring( buf );
desc = in.getstring( buf );
/*unused*/ in.getnum();
/*unused*/ in.getnum();
sector = ::lookupSector( in.getword( buf ) );
terrain = in.getnum();
in.getbitfield( room_bits );
while(1)
{
in.getword( buf );
switch( *buf )
{
// Door block
// Syntax example: <dir> <name> <flags> <key-index> <to_room> <desc>
// D {
// 4 coffin 1|2|32 thalos:key-3 keroon:room-1
// }
case 'D':
{
in.getword( buf );
if( !*buf == '{' )
in.error( "Door block not found for symbol 'D'" );
int dir = in.getnum();
String doorName = in.getstring( buf );
int flags = in.getnum();
Index kIndex = in.getword( buf );
Index rIndex = in.getword( buf );
String str = in.getstring( buf );
if( dir > 5 )
in.error( "Room::readFrom - direction out of range.\n" );
exits[ dir ] = new Exit( doorName, rIndex, kIndex, flags, str );
if( *in.getword( buf ) != '}' )
in.error( "Room::readFrom - door block invalid." );
break;
}
break;
case 'R':
{
Repop *pRepop = new Repop;
if( ( err = pRepop->readFrom( in ) ) == 0 )
addRepop( pRepop );
else
{
Cout << "Invalid repop code, skipping.\n";
pRepop->fordelete();
}
}
break;
case 'E':
{
// Extra description block
Description *d = new Description;
if( ( err = d->readFrom( in ) ) == 0 )
ed.addTop( d );
else
{
Cout << "Invalid extra description block, skipping.\n";
delete d;
}
}
break;
// add full 'Trig' keyword
case 'T':
{
in.getword(buf);
struct TriggerData * td = new (struct TriggerData);
td->type = lookupBit( rtrig_types, in.getword( buf ) );
td->fun = lookupRtrig( td->type, in.getword(buf) );
if ( (td->type == 0) || (td->fun == NULL) )
{
delete td;
in.error("Room:readFrom - unrecognized trigger data");
continue;
}
addTrigger(td);
in.getword(buf);
}
break;
default:
return 1;
}
}
return 1;
}
int Room::writeTo( Output & outf ) const
{
String strExitName;
if( !outf )
return 0;
outf << '{' << endl;
outf << name << '~' << endl;
outf << desc << '~' << endl;
outf << 0 << ' ' << 0 << ' '
<< lookupSectorName( sector ) << ' ' << terrain << endl;
outf.putbitfield( room_bits, MAX_ROOM_BIT_FIELDS );
outf << endl;
/* write the doors */
for( int i = 0; i < MAX_DIR; i++ )
{
if( exits[i] )
{
if( !(bool)exits[i]->getName() )
strExitName = "none";
else
strExitName = exits[i]->getName();
outf << "D{" << i << " "
<< strExitName << "~"
<< exits[i]->original_flags[1] << " "
<< exits[i]->key.asString() << " "
<< exits[i]->room_index.asString() << " "
<< exits[i]->desc << "~}\n";
}
}
// end doors
// write the repops
Repop *pRepop;
for_each( repops, pRepop )
{
outf << "R";
pRepop->writeTo( outf );
outf << endl;
}
struct TriggerData * tg;
for_each( triggers, tg )
outf << "Trig{ " << lookupBitName(rtrig_types, tg->type) <<
" " << lookupRtrigName(tg->type, tg->fun) << "}" << endl;
outf << '}' << endl;
return 1;
}
// Destroy ExitIndex, create Exit and link to the real room.
void Room::hardLink()
{
for( int i = 0; i < MAX_DIR; i++ )
if( exits[ i ] )
exits[ i ]->hardLink();
}
Exit * Room::getExit( int dir )
{
if( dir >= MAX_DIR )
return 0;
return exits[ dir ];
}
Room *Room::toRoom( int dir )
{
if( dir >= MAX_DIR )
return 0;
//Need to finish this
if( !exits[ dir ] )
return 0;
if( !exits[ dir ]->room_ptr )
{
Cout << "Room::toRoom " << getName() << ":exit points to NULL room.\n";
return 0;
// exit(0);
}
return exits[ dir ]->room_ptr;
}
void Room::addExit( Exit *pExit, int dir )
{
if( dir < MAX_DIR )
exits[ dir ] = pExit;
}
void Room::deleteExit( int dir )
{
if( dir >= 0 && dir < MAX_DIR )
{
exits[ dir ]->fordelete();
exits[ dir ] = 0;
}
}
Char *Room::getChar( const String & str )
{
Char * ch;
countedThing cI(str);
parseCompositeName( ch, chars, cI );
return ch;
}
Char * Room::getChar( countedThing & cI )
{
Char * ch;
parseCompositeName( ch, chars, cI );
return ch;
}
PC *Room::getPC( const String & str )
{
Char *ch;
for_each( chars, ch )
{
if( ch->isNPC() )
continue;
if( ch->isName( str ) )
break;
}
return (PC*)ch;
}
NPC * Room::getNPC( const String & str )
{
Char *ch;
for_each( chars, ch )
{
if( ch->isPC() )
continue;
if( ch->isName( str ) )
return (NPC*)ch;
}
return 0;
}
Object * Room::getObj( const String & str )
{
countedThing cI(str);
Object * obj;
parseCompositeName( obj, inv, cI );
return obj;
}
Object * Room::getObj( countedThing & cI )
{
Object * obj;
parseCompositeName( obj, inv, cI );
return obj;
}
void Room::describeItself( String & str )
{
String str2;
int i;
Exit * pExit;
struct TriggerData * td;
for( i = 1; room_bit_list[i].name; i++ )
if( roomBitIsSet( room_bit_list[i].val ) )
str2 << room_bit_list[i].name << ' ';
// Display room and return
str << "ROOM\n\r"
<< "Area: " << getArea()->getName() << "\n\r"
<< "Index: " << getKey() << "\n\r"
<< "Room bits: " << str2 << "\n\r"
<< "Room affects: " << "\n\r"
<< "Sector: " << lookupSectorName( getSector() ) << "\n\r"
<< "Name: " << getName() << "\n\r"
<< "Room text:\n\r" << getDesc()
;
for( i = 0; i < MAX_DIR; i++ )
{
if( !( pExit = getExit( i ) ) )
continue;
str2.clr();
for( int j = 1; exit_bits[j].name; j++ )
if( pExit->exitBitSet( exit_bits[j].val ) )
str2 << exit_bits[j].name << ' ';
// Modify later to use -->, <-- and <--> for doors
str.sprintfAdd( "%5s: %-20s %-15s [%s]\n\r",
lookupDirName( i ),
pExit->getIndex().asString().chars(),
pExit->getName().chars(),
str2.chars()
);
}
for_each( triggers, td)
{
str << "Trigger " << lookupBitName( rtrig_types, td->type) <<
" " << lookupRtrigName( td->type, td->fun ) << "\n\r";
}
}
bool Room::TgUpdate()
{
if ( !IS_SET(trigger_bits, RTRIG_UPDATE) )
return false;
return ((rtrigT_update)findTrigger(RTRIG_UPDATE ))
(this);
}
bool Room::TgDroppedItem( Char * caller, Object * what )
{
if ( !IS_SET(trigger_bits, RTRIG_DROPPED_ITEM) )
return false;
return ((rtrigT_dropped_item)findTrigger(RTRIG_DROPPED_ITEM ))
(this,caller,what);
}
bool Room::TgPickedItem( Char * caller, Object * what )
{
if ( !IS_SET(trigger_bits, RTRIG_PICKED_ITEM) )
return false;
return ((rtrigT_picked_item)findTrigger(RTRIG_PICKED_ITEM ))
(this,caller,what);
}
bool Room::TgStrangeCmd(Char * caller, const String & cmd, const String & args)
{
if ( !IS_SET(trigger_bits, RTRIG_STRANGE_CMD) )
return false;
return ((rtrigT_strange_cmd)findTrigger(RTRIG_STRANGE_CMD ))
(this,caller,cmd,args);
}
bool Room::TgSaidIn( Char * who, const char * what )
{
if ( !IS_SET(trigger_bits, RTRIG_SAID_IN) )
return false;
return ((rtrigT_said_in)findTrigger(RTRIG_SAID_IN ))
(this, who, what);
}
bool Room::TgSaidIn( Char * who, const String & what )
{
return TgSaidIn(who, what.chars() );
}
bool Room::TgLeft( Char * who, Room * to )
{
if ( !IS_SET(trigger_bits, RTRIG_LEFT) )
return false;
return ((rtrigT_left)findTrigger(RTRIG_LEFT ))
(this,who,to);
}
bool Room::TgEntered( Char * who, Room * to )
{
if ( !IS_SET(trigger_bits, RTRIG_ENTERED) )
return false;
return ((rtrigT_entered)findTrigger(RTRIG_ENTERED ))
(this,who,to);
}
bool Room::TgSocialedIn( Char * who,Char * target,const Social * social)
{
if ( !IS_SET(trigger_bits, RTRIG_SOCIALED_IN) )
return false;
return ((rtrigT_socialed_in)findTrigger(RTRIG_SOCIALED_IN ))
(this,who,target,social);
}