/* ....[@@@..[@@@..............[@.................. 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); }