/*
....[@@@..[@@@..............[@.................. 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
------------------------------------------------------------------------------
object.cc
*/
#include "config.h"
#include "io.h"
#include "string.h"
#include "index.h"
#include "hash.h"
#include "room.h"
#include "llist.h"
#include "repop.h"
#include "bit.h"
#include "spell.h"
#include "affect.h"
#include "combat.h"
#include "char.h"
#include "area.h"
#include "object.h"
#include "global.h"
// All wear locations are not possible for example
// if you are double wielding, then no light or shield
const bitType wear_bit_list[] =
{
{0, 0 },
{"head", WEAR_HEAD },
{"neck", WEAR_NECK },
{"back", WEAR_BACK },
{"body", WEAR_BODY },
{"arms", WEAR_ARMS },
{"wrist", WEAR_WRIST },
{"hands", WEAR_HANDS },
{"held", WEAR_HOLD },
{"finger", WEAR_FINGER },
{"waist", WEAR_WAIST },
{"legs", WEAR_LEGS },
{"feet", WEAR_FEET },
{"shield", WEAR_SHIELD },
{"wield", WEAR_WIELD },
{0, 0}
};
const bitType obj_bit_list[] =
{
{0, 0 },
{"invisible", OBJ_INVISIBLE },
{"hidden", OBJ_HIDDEN },
{"notake", OBJ_NOTAKE },
{"nodrop", OBJ_NODROP },
{"edible", OBJ_EDIBLE },
{"nosave", OBJ_NOSAVE },
{"good", OBJ_GOOD },
{"evil", OBJ_EVIL },
{"antigood", OBJ_ANTIGOOD },
{"antievil", OBJ_ANTIEVIL },
{"antineutral", OBJ_ANTINEUTRAL },
{"magic", OBJ_MAGIC },
{"glow", OBJ_GLOW },
{"sanctuary", OBJ_SANCTUARY },
{"poisoned", OBJ_POISONED },
{0, 0 }
};
// I have disabled unused objects for now - Artur
const bitType obj_type_list[] =
{
{0, 0 },
// {"trash", ITEM_TRASH },
// {"jewel", ITEM_JEWEL },
{"gold", ITEM_GOLD },
{"container", ITEM_CONTAINER },
{"liquid-container",ITEM_LIQUID_CONTAINER },
{"armor", ITEM_ARMOR },
// {"cloth", ITEM_CLOTH },
{"weapon", ITEM_WEAPON },
// {"wand", ITEM_WAND },
// {"orb", ITEM_ORB },
{"corpse", ITEM_CORPSE },
// {"energy", ITEM_ENERGY },
// {"scroll", ITEM_SCROLL },
{"potion", ITEM_POTION },
{"food", ITEM_FOOD },
// {"key", ITEM_KEY },
// {"staff", ITEM_STAFF },
// {"compass", ITEM_COMPASS },
{0, 0 }
};
const bitType dam_types[] =
{
{0, 0 },
{"punch", PUNCH },
{"kick", KICK },
{"crush", CRUSH },
{"pierce", PIERCE },
{"slash", SLASH },
{"whip", WHIP },
{"claw", CLAW },
{0, 0 }
};
const char *wear_pos_list[] =
{
"undefined",
"<worn on head>",
"<worn on face>",
"<worn on neck>",
"<worn on neck>",
"<worn on back>",
"<worn on body>",
"<worn on arms>",
"<worn on left wrist>",
"<worn on right wrist>",
"<worn on hands>",
"<held in left>",
"<held in right>",
"<worn on left finger>",
"<worn on right finger>",
"<worn on waist>",
"<worn on legs>",
"<worn on feet>",
"<shield left>",
"<shield right>",
"<wielded left>",
"<wielded right>",
0
};
int Object::total_count = 0;
Object::~Object()
{
total_count--;
// Extract and destroy all objects inside this one
// Add a bug check here later if non-containers have inv
// deleting obj destroys any objects inside obj if it is container
Object *obj;
while( ( obj = inv.remove() ) )
{
obj->extract();
obj->fordelete();
}
}
// Remove all links between object and world, repop, etc.
// After extract we can safely do anything with object that
// we want including deleting it. This could all be in the
// destructor but my opinion on self-removing destructor methods
// is they cause more bugs than they are worth.
void Object::extract()
{
if( in_room )
in_room->inv.remove( this );
else if( in_obj )
in_obj->rmObjInv( this );
else if( in_char )
in_char->inv.remove( this );
if( repop )
repop->setPtr( 0 );
objects.remove( this );
}
void Object::setObjBit( const String & x )
{
int bit = getObjBit( x );
if( bit )
SET_BIT( obj_bits, bit );
}
void Object::clrObjBit( const String & x )
{
int bit = getObjBit( x );
if( bit )
CLR_BIT( obj_bits, bit );
}
void Object::toggleObjBit( const String & x )
{
int bit = getObjBit( x );
if( bit )
TOGGLE_BIT( obj_bits, bit );
}
void Object::setWearBit( const String & x )
{
int bit = getWearBit( x );
if( bit )
SET_BIT( wear_bits, bit );
}
void Object::clrWearBit( const String & x )
{
int bit = getWearBit( x );
if( bit )
CLR_BIT( wear_bits, bit );
}
void Object::toggleWearBit( const String & x )
{
int bit = getWearBit( x );
if( bit )
TOGGLE_BIT( wear_bits, bit );
}
// There is no longer Proto and persistent write & read
// They are in same fun, prototype objects just do not have most
// of transient data
int Object::readFrom( StaticInput &in )
{
String str;
char buf[ BUF ];
Object * obj;
if( *in.getword( buf ) != '{' )
in.error( "Object::ReadFromFile() - expected '{' not found." );
setName( in.getstring( buf ) );
setShort( in.getstring( buf ) );
setLong( in.getstring( buf ) );
// str = in.getword( buf );
// obj_type = lookupObjType( str );
in.getword( buf );
size = lookupSize( buf );
in.getnum(); // spare
in.getnum(); // spare
readFromTypeSpecific(in);
in.getbitfield( obj_bits );
in.getbitfield( wear_bits );
in.getnum(); // security for now
cost = in.getnum();
weight = in.getnum();
/*level = */ in.getnum();
// Persistent values
timer = in.getnum();
wear_pos = in.getnum();
// end
const SpellType *stp;
Spell *sp;
Modifier *mod;
Affect *aff;
struct TriggerData * td;
int modAmt;
int modLoc;
int spellLevel;
while( 1 )
{
switch( *in.getword( buf ) )
{
case '}': return 0;
// Affect
case 'A': in.getword( buf );
in.getstring( buf );
in.getnum();
in.getword( buf );
continue;
// Spell
case 'S': in.getword( buf );
in.getstring( buf );
spellLevel = in.getnum();
if( ( stp = lookupSpell( buf ) ) )
{
sp = new Spell( stp, spellLevel );
spell_list.add( sp );
}
in.getword( buf );
continue;
// Modifier
case 'M': in.getword( buf );
in.getword( buf );
modAmt = in.getnum();
if( ( modLoc = lookupModType( buf ) ) != -1 )
{
mod = new Modifier( modLoc, modAmt );
mod_list.addTop( mod );
}
in.getword( buf );
continue;
case 'O': obj = Object::createObject( lookupObjType(in.getword(buf)) );
if ( obj == NULL )
in.error("Wrong object type.\n\r");
obj->readFrom( in );
addObjInv( obj );
continue;
// add check for 'Trig' not 'T'
case 'T': in.getword(buf);
td = new (struct TriggerData);
td->type = lookupBit( otrig_types, in.getword( buf ) );
td->fun = lookupOtrig( td->type, in.getword(buf) );
if ( (td->type == 0) || (td->fun == NULL) )
{
delete td;
in.error("Object::ReadFromFile - unrecognized trigger data");
continue;
}
addTrigger(td);
in.getword(buf); // '}'
continue;
default:
Cout << "[" << buf << "]\n";
in.error( "Object::ReadFromFile() - expected '}' at end of Object." );
}
}
return 0;
}
int Object::writeTo( Output &outf ) const
{
outf << "{" << endl;
outf << name << TERM_CHAR << endl;
outf << shortdesc << TERM_CHAR << endl;
outf << longdesc << TERM_CHAR << endl;
// outf << lookupObjTypeName( obj_type ) << endl;
outf << lookupSizeName( size ) <<' '<< 0 <<' '<< 0 << endl;
writeToTypeSpecific(outf);
outf.putbitfield( obj_bits, MAX_OBJ_BIT_FIELDS );
outf << endl;
outf.putbitfield( wear_bits, MAX_WEAR_BIT_FIELDS );
outf << endl;
outf << 1 /* security */ << " " << cost << " " << weight << " "
<< 1 /*level*/ << " " << endl;
// Write persistent data
outf << timer << ' ' << wear_pos << endl;
// end
Spell *sp;
for_each( spell_list, sp )
outf << "S{" << sp->getName() << "~ " << sp->getLevel() << "}\n";
Modifier *mod;
for_each( mod_list, mod )
outf << "M{" << mod->getName() << ' ' << mod->getAmt() << "}\n";
Affect *aff;
for_each( aff_list, aff )
outf << "A{~ -1}\n";
struct TriggerData * tg;
for_each( triggers, tg )
outf << "Trig{ " << lookupBitName(otrig_types, tg->type) <<
" " << lookupOtrigName(tg->type, tg->fun) << "}" << endl;
Object * obj;
for_each( inv, obj )
{
outf << "O " << obj->typeName() << endl;
obj->writeTo( outf );
}
outf << "}" << endl;
return 1;
}
bool Object::isWearable() const
{
for( int i = 0; i < MAX_WEAR_BIT_FIELDS; i++ )
if( wear_bits[ i ] )
return true;
return false;
}
void Object::setWearPos( int pos )
{
if( pos <= EQ_MAX )
wear_pos = pos;
}
void Object::fromChar()
{
#ifdef DEBUG
if( !inChar() )
{
Cout << "BUG: Object::fromChar - not carried.\n";
return;
}
#endif
in_char = 0;
}
void Object::toChar( Char *ch )
{
#ifdef DEBUG
if( inRoom() || inChar() || inObj() )
{
Cout << "BUG: Object::toChar - already owned.\n";
return;
}
#endif
in_char = ch;
}
void Object::fromRoom()
{
#ifdef DEBUG
if( !inRoom() )
{
Cout << "BUG: Object::fromRoom - not in room.\n";
return;
}
#endif
in_room = 0;
}
void Object::toRoom( Room * x )
{
#ifdef DEBUG
if( inRoom() || inChar() || inObj() )
{
Cout << "BUG: Object::toRoom - already owned.\n";
return;
}
#endif
in_room = x;
}
// Leave to caller to assert obj isObject
void Object::toObj( Object * obj )
{
#ifdef DEBUG
if( inRoom() || inChar() || inObj() )
{
Cout << "BUG: Object::toObj - already owned.\n";
return;
}
#endif
in_obj = obj;
}
void Object::fromObj()
{
#ifdef DEBUG
if( !inObj() )
{
Cout << "BUG: Object::fromObj - not in object.\n";
return;
}
#endif
in_obj = 0;
}
void Object::addObjInv( Object * obj )
{
obj->toObj( this );
weight += obj->getWeight();
inv.add( obj );
}
Object * Object::getObjInv( const String & str )
{
Object * obj;
countedThing cI(str);
parseCompositeName( obj, inv, cI );
return obj;
}
// Get first object of type otype (OBJ_FOOD, etc.)
Object * Object::getObjInv( int otype )
{
Object * obj;
for_each( inv, obj )
if (obj->isType(otype))
break;
return obj;
}
Object * Object::getObjInv( countedThing & cI )
{
Object * obj;
parseCompositeName( obj, inv, cI );
return obj;
}
void Object::rmObjInv( Object * obj )
{
obj->fromObj();
weight -= obj->getWeight();
inv.remove( obj );
}
Spell * Object::getSpell1()
{
spell_list.reset();
if( spell_list.peek() )
return spell_list.peek();
return 0;
}
Spell * Object::getSpell2()
{
spell_list.reset();
if( spell_list.peek() )
spell_list.next();
if( spell_list.peek() )
return spell_list.peek();
return 0;
}
Spell * Object::getSpell3()
{
return 0;
}
Spell * Object::getSpell4()
{
return 0;
}
void Object::cast( MudObject * target )
{
Spell * sp;
for_each( spell_list, sp )
if( sp->objCanCast() )
sp->cast( this, target );
}
void Object::reportVals( String & ) const
{
return;
}
char * Object::setVals( const String & )
{
return "No such value.\n\r";
}
void Object::describeItself( String & str )
{
int i;
String str2;
String str3;
struct TriggerData * td;
for( i = 1; obj_bit_list[i].name; i++ )
if( objBitSet( obj_bit_list[i].val ) )
str2 << obj_bit_list[i].name << ' ';
for( i = 1; wear_bit_list[i].name; i++ )
if( wearBitSet( wear_bit_list[i].val ) )
str3 << wear_bit_list[i].name << ' ';
str << "OBJECT\n\r"
<< "Keywords: " << getName() << "\n\r"
<< "Shortdesc: " << getShort() << "\n\r"
<< "Longdesc: " << getLong() << "\n\r"
<< "Type: " << typeName() << "\n\r"
<< "Size: " << lookupSizeName( getSize() ) << "\n\r"
<< "Cost: " << getCost() << "\n\r"
<< "Obj-bits: " << str2 << "\n\r"
<< "Wear-bits: " << str3 << "\n\r";
if( getSpell1() )
str << "Spell1: " << getSpell1()->getName() << "\n\r";
if( getSpell2() )
str << "Spell2: " << getSpell2()->getName() << "\n\r";
if( getSpell3() )
str << "Spell3: " << getSpell3()->getName() << "\n\r";
if( getSpell4() )
str << "Spell4: " << getSpell4()->getName() << "\n\r";
reportVals(str);
for_each( triggers, td)
{
str << "Trigger " << lookupBitName( otrig_types, td->type) <<
" " << lookupOtrigName( td->type, td->fun ) << "\n\r";
}
}
const char *getWearPosName( int pos )
{
return wear_pos_list[pos];
}
Object * lookupObj( const Index & x )
{
Object *obj;
Area *area;
LList<Area> tlist = areas;
tlist.reset();
while( ( area = tlist.peek() ) )
{
tlist.next();
// No scope means global search
if( ! (bool) x.getScope() )
if( ( obj = area->lookupObj( x.getKey() ) ) )
return obj;
if( area->getKey() == x.getScope() )
break;
}
if( area )
return area->lookupObj( x.getKey() );
return 0;
}
Object * lookupObj( const String & x )
{
return lookupObj( Index( x ) );
}
Object * lookupObj( const char * x )
{
return lookupObj( Index( x ) );
}
bool Object::TgCreated( Room * where, Repop * by )
{
if ( !IS_SET(trigger_bits, OTRIG_CREATED) )
return false;
return ((otrigT_created)findTrigger(OTRIG_CREATED ))
(this, where, by);
}
bool Object::TgUpdate()
{
if ( !IS_SET(trigger_bits, OTRIG_UPDATE) )
return false;
return ((otrigT_update)findTrigger(OTRIG_UPDATE ))
(this);
}
bool Object::TgTimerOut()
{
if ( !IS_SET(trigger_bits, OTRIG_TIMER_OUT) )
return false;
return ((otrigT_timer_out)findTrigger(OTRIG_TIMER_OUT ))
(this);
}
bool Object::TgPicked( Char *caller, Room * from )
{
if ( !IS_SET(trigger_bits, OTRIG_PICKED) )
return false;
return ((otrigT_picked)findTrigger(OTRIG_PICKED ))
(this,caller,from);
}
bool Object::TgDropped( Char *caller, Room * to )
{
if ( !IS_SET(trigger_bits, OTRIG_DROPPED) )
return false;
return ((otrigT_dropped)findTrigger(OTRIG_DROPPED ))
(this,caller,to);
}
bool Object::TgGiven(Char * caller, Char * to)
{
if ( !IS_SET(trigger_bits, OTRIG_GIVEN) )
return false;
return ((otrigT_given)findTrigger(OTRIG_GIVEN ))
(this,caller,to);
}
bool Object::TgWorn(Char * caller)
{
if ( !IS_SET(trigger_bits, OTRIG_WORN) )
return false;
return ((otrigT_worn)findTrigger(OTRIG_WORN ))
(this,caller);
}
bool Object::TgRemoved(Char * caller)
{
if ( !IS_SET(trigger_bits, OTRIG_REMOVED) )
return false;
return ((otrigT_removed)findTrigger(OTRIG_REMOVED))
(this,caller);
}
bool Object::TgUsed(Char * caller, MudObject * target)
{
if ( !IS_SET(trigger_bits, OTRIG_USED) )
return false;
return ((otrigT_used)findTrigger(OTRIG_USED))
(this,caller,target);
}
bool Object::TgLookedAt( Char * caller )
{
if ( !IS_SET(trigger_bits, OTRIG_LOOKED_AT) )
return false;
return ((otrigT_looked_at)findTrigger(OTRIG_LOOKED_AT ))
(this, caller);
}
bool Object::TgItemPutInto( Char * caller, Object * what )
{
if ( !IS_SET(trigger_bits, OTRIG_ITEM_PUT_INTO) )
return false;
return ((otrigT_item_put_into)findTrigger(OTRIG_ITEM_PUT_INTO ))
(this,caller,what);
}
bool Object::TgItemGetFrom( Char * caller, Object * what )
{
if ( !IS_SET(trigger_bits, OTRIG_ITEM_GET_FROM) )
return false;
return ((otrigT_item_get_from)findTrigger(OTRIG_ITEM_GET_FROM ))
(this,caller,what);
}
// STATIC
Object * Object::createObject( int type )
{
switch (type)
{
case ITEM_WEAPON: return new ObjWeapon();
case ITEM_ARMOR: return new ObjArmor();
case ITEM_CONTAINER: return new ObjContainer();
case ITEM_FOOD: return new ObjFood();
case ITEM_CORPSE: return new ObjCorpse();
case ITEM_GOLD: return new ObjGold();
case ITEM_LIQUID_CONTAINER: return new ObjLiquidContainer();
default: return NULL;
}
}
// Classes derived from Object class
/*
// superclass call at end
int ObjTemplate::readFromTypeSpecific( StaticInput & in )
{
return Object::readFromTypeSpecific(in);
}
int ObjTemplate::writeToTypeSpecific( Output & outf ) const
{
return Object::writeToTypeSpecific(outf);
}
// superclass call at beginning
void ObjTemplate::reportVals( String & str ) const
{
Object::reportVals(str);
}
// superclass call at end
char * ObjTemplate::setVals( const String & arg )
{
Object::setVals(str);
}
*/
int ObjWeapon::readFromTypeSpecific( StaticInput & in )
{
char buf[BUF];
in.getword( buf );
dam_type = ::getDamType( buf );
if( dam_type == NONE )
{
Cout << "BUG: Invalid dam type " << buf << " for " << getShort() << endl;
}
min_dam = in.getnum();
max_dam = in.getnum();
return Object::readFromTypeSpecific(in);
}
int ObjWeapon::writeToTypeSpecific( Output & outf ) const
{
outf << getDamTypeName( dam_type ) << ' ' << min_dam << ' ' << max_dam << endl;
return Object::writeToTypeSpecific( outf );
}
void ObjWeapon::reportVals( String & str ) const
{
Object::reportVals(str);
str << "DamType: " << getDamTypeName( dam_type ) <<
"Min: " << min_dam << "Max: " << max_dam << endl;
}
char * ObjWeapon::setVals( const String & str )
{
String arg1;
String arg2;
int i;
str.startArgs();
arg1 = str.getArg();
arg2 = str.getArgRest();
if ( arg1 == "DamType" )
{
i = ::getDamType( arg2.chars() );
if ( i == NONE )
{
return "Wrong damage type.\n\r";
}
dam_type = i;
return "DamType set.\n\r";
}
else if ( arg1 == "Min" )
{
if ( !arg2.isNumber() )
return "Must be number.\n\r";
i = arg2.asInt();
if ( i < 0 )
return "Must be positive.\n\r";
min_dam = i;
return "Min damage set.\n\r";
}
else if ( arg1 == "Max" )
{
if ( !arg2.isNumber() )
return "Must be number.\n\r";
i = arg2.asInt();
if ( i < 0 )
return "Must be positive.\n\r";
max_dam = i;
return "Max damage set.\n\r";
}
return Object::setVals(str);
}
int ObjFood::readFromTypeSpecific( StaticInput & in )
{
food_worth = in.getnum();
return Object::readFromTypeSpecific(in);
}
int ObjFood::writeToTypeSpecific( Output & outf ) const
{
outf << food_worth << endl;
return Object::writeToTypeSpecific(outf);
}
void ObjFood::reportVals( String & str ) const
{
Object::reportVals(str);
str << "Worth (in food units): " << food_worth << endl;
}
char * ObjFood::setVals( const String & str )
{
String arg1;
String arg2;
int i;
str.startArgs();
arg1 = str.getArg();
arg2 = str.getArgRest();
if ( arg1 == "Worth" )
{
if ( !arg2.isNumber() )
return "This value must be a number.\n\r";
i = arg2.asInt();
if ( i < 0 )
return "Must be postive.\n\r";
food_worth = i;
return "Food worth set.\n\r";
}
return Object::setVals(str);
}
int ObjLiquidContainer::readFromTypeSpecific( StaticInput & in )
{
amount = in.getnum();
return Object::readFromTypeSpecific(in);
}
int ObjLiquidContainer::writeToTypeSpecific( Output & outf ) const
{
outf << amount << endl;
return Object::writeToTypeSpecific(outf);
}
void ObjLiquidContainer::reportVals( String & str ) const
{
Object::reportVals(str);
str << "Amount of liquid: " << amount << endl;
}
char * ObjLiquidContainer::setVals( const String & str )
{
String arg1;
String arg2;
int i;
str.startArgs();
arg1 = str.getArg();
arg2 = str.getArgRest();
if ( arg1 == "Amount" )
{
if ( !arg2.isNumber() )
return "This value must be a number.\n\r";
i = arg2.asInt();
if ( i < 0 )
return "Must be postive.\n\r";
amount = i;
return "Amount of liquid set.\n\r";
}
return Object::setVals(str);
}