/**
 * A basic fence for thieves and others to sell stolen items to.
 * @change Partly rewritten by Sandoz, December 2001.
 */
#include <shop.h>
inherit NPC_OBJ;
#define TIMEOUT (60*60*24*2)
#define SKILL "general.valueing"
class offer {
    object who;
    int amount;
    int offer_time;
    object *objects;
}
class offer this_offer;
object cont;
string fence_type, *stole_from_fence;
mapping old_offers;
int query_timeout() { return TIMEOUT; }
object query_cont() { return cont; }
/** @ignore yes */
mapping query_old_offers() { return old_offers; }
void whisper( object ob, string message ) {
    do_command("whisper " + message + " to " + ob->query_name() );
} /* whisper() */
/**
 * This method creates a container for the fence to use.
 * Override it in your fence NPC if you wish.
 */
void check_cont() {
    if( !cont || ENV(cont) != TO ) {
        cont = (object)ARMOURY_H->request_item("large hessian sack", 85 );
        cont->add_property("nosteal", 1 );
        cont->move(TO);
        do_command(":produces "+cont->a_short()+" from somewhere.");
    }
    if( cont->query_loc_weight() > 2 * cont->query_max_weight() / 3 ) {
        INV(cont)->move("/room/rubbish");
        old_offers = ([ ]);
    }
} /* check_cont() */
void create() {
    do_setup++;
    ::create();
    do_setup--;
    if( !do_setup )
        TO->setup();
    check_cont();
    set_respond_to_with( ({
        ({ ({"y", "yes", "Y", "Yes", "ok", "okay"}) }) , "#do_yes",
        ({ ({"n", "no", "N", "No"}) }) , "#do_no" }) );
    old_offers = ([ ]);
    stole_from_fence = ({ });
    set_wimpy(90);
} /* create() */
void reset() {
    call_out("clean_cont", 12 );
} /* reset() */
void clean_cont() {
    // Don't dest anything if we're in the middle of dealing with someone.
    if( this_offer )
        return;
    check_cont();
    INV(cont)->move("/room/rubbish");
    old_offers = ([ ]);
} /* clean_cont() */
/**
 * This method sets the speciality of the fence.
 * This can be one of the following: weapons, jewellery,
 * armour, clothing, gems or misc.
 * @param str the speciality of the fence to use
 */
void set_fence_type( string str ) { fence_type = str; }
/** @ignore yes */
int query_cost( object thing, object buyer ) {
    return thing->query_value_at(TO);
} /* query_cost() */
/** @ignore yes */
string cost_string( object thing, string place, object buyer ) {
    return MONEY_H->money_value_string( query_cost( thing, buyer ), place );
} /* cost_string() */
/**
 * This method will work out the type of object being fenced.
 * @param ob the object to test
 * @return the object type
 */
string query_item_type(object ob) {
    if( member_array("weapons", ob->query_plurals() ) != -1 )
        return "weapons";
    if( member_array("jewellery", ob->query_plurals() ) != -1 ||
        ob->query_property("shop type") == "jewellers" )
        return "jewellery";
    if( member_array("armours", ob->query_plurals() ) != -1 )
        return "armour";
    if( member_array("clothes", ob->query_plurals() ) != -1 )
        return "clothing";
    if( member_array("gems", ob->query_plurals() ) != -1 )
        return "gems";
    return "misc";
} /* query_item_type() */
/**
 * This method returns the value of an item based on
 * the client's valueing skills.
 * @param ob the object to get the value for
 * @param player the client
 * @param type the type of the object
 * @return the value of the object
 */
int judge_value( object ob, object player, string type ) {
    int value, variance, skill;
    value = query_cost( ob, TO );
    skill = player->query_skill_bonus( SKILL+( type != "" ? "."+type : "" ) ) || 1;
    variance = to_int( value / ( 1 + ( sqrt( to_float(skill) ) / 8 ) ) );
    return value + random(variance) - random(variance);
} /* judge_value() */
/**
 * This method sets the place, or the currency area this
 * fence operates in.  This is used for currency calculations.
 * @param where the place where we operate in
 */
void set_place( string where ) { add_property("place", where ); }
/**
 * This method will return the place we are in,
 * which is used in money strings etc.
 * @return the place we are in
 */
string query_place() {
    return query_property("place") || "default";
} /* query_place() */
/**
 * This method will return the money string for the given value.
 * @param amt the amount of money to get the money string for
 * @return the money string for the given amount of money
 */
string cost_str( int amt ) {
    return MONEY_H->money_value_string( amt, query_place() );
} /* cost_str() */
int scaled_value( int n ) {
    int profit;
    profit = 5 + to_int( sqrt( to_float( n / 5 ) ) );
    if( profit > MAX_PROFIT / 3 )
        profit = MAX_PROFIT / 3;
    return n * ( 100 - profit ) / 100;
} /* scaled_value() */
int do_fail( object player ) {
    whisper( player, "Well, that was a waste of time.");
    this_offer = 0;
    return 1;
} /* do_fail() */
int do_fence( object *obs ) {
    int offer, their_skill;
    float f;
    string type, skill;
    object ob, *offered, *not_speciality, *tmp;
    check_cont();
    tell_object( TP, "You ask "+the_short()+" about fencing "+
        query_multiple_short(obs)+".\n");
    tell_room( ENV(TO), TP->the_short()+" asks "+the_short()+" about fencing "+
        query_multiple_short(obs)+".\n", TP );
    if( TP->query_property(query_name()+"_killer") &&
        ( TP->query_property(query_name()+"_killer") + TIMEOUT ) < time() ) {
        do_command("peer "+TP->query_name() );
        init_command("lsay Oh sod off you!  I wouldn't buy anything from you "
            "even if I was offered a kingdom!", 1 );
        TP->add_failed_mess( TO, "" );
        return 0;
    }
    if( this_offer && this_offer->who && ENV( this_offer->who ) == ENV(TO) &&
        this_offer->offer_time > time() - 60 ) {
        whisper( TP, ( this_offer->who != TP ? "Sorry, I'm already helping "+
            (this_offer->who)->the_short()+"." : "I'm still waiting for a "
            "response from you!") );
        TP->add_succeeded_mess( TO, "", offered );
        return 1;
    }
    if( !TP->query_visible(TO) ) {
        TP->add_failed_mess( TO, "$D doesn't seem to notice you.\n", offered );
        return 0;
    }
    TP->add_succeeded_mess( TO, "", offered );
    if( member_array( TP->query_name(), stole_from_fence ) != -1 ) {
        do_command("peer "+TP->query_name() );
        init_command("lsay Piss off scumbag!", 1 );
        return 1;
    }
    if( sizeof(obs) > MAX_OBS ) {
        whisper( TP, "Sorry, I can't handle that many items at once.");
        this_offer = 0;
        return 1;
    }
    tell_room( ENV(TO), the_short()+" studies "+
        query_multiple_short( obs, "the" )+".\n");
    if( sizeof( tmp = filter( obs, (: $1->query_keep() :) ) ) ) {
        whisper( TP, "You can't sell "+strip_colours(
            query_multiple_short(tmp, "the") )+" because you are keeping "+
            ({"it","them"})[query_group(tmp)]+".");
        if( !sizeof( obs -= tmp ) )
            return do_fail(TP);
    }
    not_speciality = ({ });
    this_offer = new( class offer );
    this_offer->objects = ({ });
    this_offer->who = TP;
    this_offer->offer_time = time();
    if( sizeof( tmp = filter( obs, (: ENV($1) != this_offer->who :) ) ) ) {
        whisper( TP, "Do you think I'm blind?  I can see that you don't "
            "have "+strip_colours( query_multiple_short(tmp) )+".");
        if( !sizeof( obs -= tmp ) )
            return do_fail(TP);
    }
    if( sizeof( tmp = filter( obs, (: $1->query_property("money") :) ) ) ) {
        whisper( TP, "You can't sell money, are you daft?");
        if( !sizeof( obs -= tmp ) )
            return do_fail(TP);
    }
    if( sizeof( tmp = filter( obs, (: $1->query_worn_by() :) ) ) ) {
        whisper( TP, "I'm sorry, but you can't fence "+strip_colours(
            query_multiple_short(tmp, "the") )+" because you're wearing "+
            ({"it","them"})[query_group(tmp)]+".");
        if( !sizeof( obs -= tmp ) )
            return do_fail(TP);
    }
    if( sizeof( tmp = filter( obs, (: $1->query_wielded() :) ) ) ) {
        whisper( TP, "You can't fence "+strip_colours(
            query_multiple_short(tmp, "the") )+" because you're holding "+
            ({"it","them"})[query_group(tmp)]+".");
        if( !sizeof( obs -= tmp ) )
            return do_fail(TP);
    }
    if( sizeof( tmp = filter( obs, (: $1->do_not_sell() :) ) ) ) {
        whisper( TP, "You can't sell "+strip_colours(
            query_multiple_short(tmp, "the") )+".");
        if( !sizeof( obs -= tmp ) )
            return do_fail(TP);
    }
    if( sizeof( tmp = filter( obs, (: $1->move(cont) :) ) ) ) {
        whisper( TP, "I can't take "+strip_colours(
            query_multiple_short(tmp, "the") )+" from you for some reason, "
            "so you can't fence "+({"it","them"})[query_group(tmp)]+".");
        if( !sizeof( obs -= tmp ) )
            return do_fail(TP);
    }
    TCRE("sandoz", sprintf("%s fencing:\n%-*#s", TP->short(), 79, implode(
        map( obs, (: $1->short(0)+" ("+query_item_type($1)+")" :) ), "\n") ) );
    foreach( ob in obs ) {
      type = query_item_type(ob);
      if( !offer = judge_value( ob, TP, type ) ) {
          whisper( TP, ob->the_short()+" isn't worth anything.");
          if( !ob->move(TP) )
              whisper( TP, "So you can have it back.");
          else {
              ob->move(environment());
              whisper( TP, "So I'll just put it down here.");
          }
          continue;
      }
      // are we a fence for this type of object?
      if( type != fence_type ) {
          offer -= offer / 5;
          not_speciality += ({ ob });
      }
      // now do the profit (just like a shop)
      offer = (int)scaled_value(offer) || 1;
      if( ob->query_property("fenced") ) {
          if( ob->query_property("fenced") == (string)TP->query_name() ) {
              do_command("'Hey, that "+ob->short(0)+" is mine you scoundrel!");
              init_command("'Think I'm stupid do you?", 2 );
              init_command("'You'd better leave, and quickly!", 4 );
              stole_from_fence += ({ TP->query_name() });
              return 1;
          } else {
              do_command("'Mmm, that looks familiar.");
              do_command(":frowns in thought");
              do_command("'Oh well.");
              this_offer->amount -= ( offer * 3 ) / 4;
          }
      }
      // Remember our previous offers.
      if( old_offers[ob] )
          offer = old_offers[ob];
      else
          old_offers[ob] = offer;
      // now see if we can fleece them
      skill = SKILL + ( type != "" ? "."+type : "" );
      their_skill = TP->query_skill_bonus(skill) || 2;
      // reduce the offer dependant on their valueing skill
      f = sqrt( to_float( their_skill / 2 ) ) / 3;
      offer -= to_int( offer / ( f < 2 ? 2 : f ) );
      this_offer->amount += offer;
      this_offer->objects += ({ ob });
    }
    if( sizeof(not_speciality) )
        whisper( TP, "Well, "+strip_colours( query_multiple_short(
            not_speciality, "the" ) )+" "+({"isn","aren"})
            [query_group(not_speciality)]+"'t really my speciality.");
    if( !sizeof( this_offer->objects ) ) {
        whisper( TP, "Well, that was a waste of time.");
        this_offer = 0;
        return 1;
    }
    // Tell em how much and wait for their response
    whisper( TP, "I'll give you "+cost_str(this_offer->amount)+" for "+
        strip_colours( query_multiple_short( this_offer->objects, "the" ) )+
        ", what do you think?");
    return 1;
} /* do_fence() */
// Now they've given a response.  Either stop or take their goods
// and give them money.
void do_yes( object person ) {
    object money, *selling, ob;
    mixed m_arr;
    if( !this_offer || person != this_offer->who )
        return;
    whisper( person, "You've got a deal.");
    selling = ({ });
    foreach( ob in this_offer->objects ) {
        ob->add_property("fenced", person->query_name(), 86400 );
        selling += ({ ob });
    }
    m_arr = MONEY_H->create_money_array( this_offer->amount, query_place() );
    money = clone_object( MONEY_OBJ );
    money->set_money_array( m_arr );
    if( money->move(person) ) {
        whisper( person, "You're too heavily burdened to accept all that "
            "money, so I'll just put it "+( ENV(TO)->query_property("here") ?
            ENV(TO)->query_property("here") : "on the floor" )+".");
        money->move(ENV(TO));
    } else {
        tell_object( person, the_short()+" gives you "+
            cost_str(this_offer->amount)+".\n");
        tell_room( ENV(TO), the_short()+" gives some coins to "+
            person->the_short()+".\n", ({ person }) );
    }
    this_offer = 0;
} /* do_yes() */
void do_no( object person ) {
    object ob;
    if( !this_offer || person != this_offer->who )
        return;
    whisper( person, "Ok, have it your own way then.");
    foreach( ob in this_offer->objects )
      if( ob->move(person) )
          ob->move(ENV(TO));
    tell_object( person, the_short()+" returns "+
        query_multiple_short( this_offer->objects, "the" )+" to you.\n");
    tell_room( ENV(TO), the_short()+" returns the items to "+
        person->the_short()+".\n", person );
    this_offer = 0;
} /* do_no() */
int attack_by( object ob ) {
    object tmp;
    if( this_offer && this_offer->who && ENV(this_offer->who) == ENV(TO) &&
        this_offer->offer_time > time() - 60 ) {
        do_command("'Hey! I'm trying to do business here!");
        foreach( tmp in this_offer->objects ) {
          if( tmp->move(this_offer->who) )
              tmp->move(ENV(TO));
        }
        whisper( this_offer->who, "Hey, you'd better take these back.");
        tell_object( this_offer->who, the_short()+" returns " +
            query_multiple_short(this_offer->objects, "the")+" to you.\n");
        tell_room( ENV(TO), the_short()+" gives some items to "+
            (this_offer->who)->the_short()+".\n", ({ this_offer->who }) );
        this_offer = 0;
    }
    return ::attack_by(ob);
} /* attack_by() */
void event_exit( object ob, string message, object to ) {
    object tmp;
    if( ob && ob == cont )
        ob->remove_property("nosteal");
    if( ob && this_offer && this_offer->who && this_offer->who == ob &&
        ENV(ob) == ENV(TO) ) {
        foreach( tmp in this_offer->objects ) {
          if( tmp->move(this_offer->who) )
              tmp->move(ENV(TO));
        }
        whisper( this_offer->who, "You might want to take these back before "
            "you go.");
        tell_object( this_offer->who, the_short()+" returns " +
            query_multiple_short(this_offer->objects, "the")+" to you.\n");
        tell_room( ENV(TO), the_short()+" returns some items to "+
            (this_offer->who)->the_short()+".\n", ({ this_offer->who }) );
        this_offer = 0;
    }
    return ::event_exit( ob, message, to );
} /* event_exit() */
void init() {
    ::init();
    TP->add_command("fence", TO,
        "<indirect:object:me> to <direct:object'person'>",
        (: do_fence($1) :) );
} /* init() */