/*
 * Coded by:
 *     Bill, Turrican and Deutha.
 */
#define SAVE_FILE "/save/bounty"
#define EXPIRY 60 * 60 * 8 * 60
#define BASE 40000
#define TAXES 60
class bounty {
   string short;
   string *victims;
   int value;
   int active;
   int time;
}
/*
 * These are existing bounties and bounties fulfilled but uncollected.
*/
mapping bounty, collect;
void create() {
   bounty  = ([ ]);
   collect = ([ ]);
   seteuid( "Room" );
   if ( file_size( SAVE_FILE +".o" ) > 0 )
      unguarded( (: restore_object, SAVE_FILE :) );
   call_out( "tidy_bounties", 120 );
} /* create() */
void save_file() { unguarded( (: save_object, SAVE_FILE :) ); }
void delete_bounty( string word ) {
   map_delete( bounty, word );
   save_file();
} /* delete_bounty() */
void tidy_bounties() {
   int now = time();
   string word;
   object thing;
   class bounty record;
/*
 * A bounty may only be fulfilled during a certain time before it expires.
*/
   foreach ( word, record in bounty ) {
      if( creatorp( word ) ) {
         log_file( "BOUNTY", "%s: bounty removed from %s (creator)\n",
               ctime( now ), word );
         map_delete( bounty, word );
         continue;
      }
      if (record && record->time + EXPIRY < now ) {
         if (record->active )
            log_file( "BOUNTY", "%s: bounty on %s of %d expired\n",
                  ctime( now ), word, record->value );
         else
            log_file( "BOUNTY", "%s: inactive bounty on %s cancelled\n",
                  ctime( now ), word );
         thing = find_player( word );
         if ( objectp( thing ) )
            tell_object( thing, "You feel that there is no longer "
                  "a price on your head.\n" );
         map_delete( bounty, word );
         continue;
      }
   }
/*
 * A bounty may only be collected during a certain time before it expires.
*/
   foreach ( word, record in collect )
      if ( !rank( word ) ) {
         map_delete( collect, word );
         continue;
      }
      if (record && record->time + EXPIRY < now ) {
         log_file( "BOUNTY", "%s: bounty of %d owed to %s expired\n",
               ctime( now ), record->value, word );
         thing = find_player( word );
         if ( objectp( thing ) )
            tell_object( thing, "You have the funniest feeling that "
                  "you just let a lot of money get away.\n" );
         map_delete( collect, word );
      }
   save_file();
} /* tidy_bounties() */
void clear_bounties() {
   if( !adminp( this_player( 1 ) ) )
      return 0;
   bounty  = ([ ]);
   collect = ([ ]);
   save_file();
} /* clear_bounties() */
void dest_me() { destruct( this_object() ); }
void register_kill( object victim, object *attackers ) {
   int now, reward;
   string killed, killer, *killers;
   object thing;
   class bounty record;
/*
 * Don't let someone get others into trouble or collect
 * their own bounties by committing suicide.
*/
   if ( member_array( victim, attackers ) != -1 )
      return;
   if ( creatorp(victim) || victim->query_property( "no bounties" ) )
      return;
/*
 * Ignore it all if it happens in a pub.
*/
   if ( !environment( victim ) )
      return;
   if ( environment( victim )->query_property( "bar brawling" ) )
      return;
   now = time();
/*
 * This will be okay so long as modifications
 * are via short() or pretty_short().
*/
   killed = (string)victim->query_short();
/*
 * If there is no bounty on the victim, set up bounties on the killers.
*/
   if ( undefinedp( bounty[ lower_case( killed ) ] ) ) {
/*
 * Temporarily, since I'm off on holiday.
 *    Deutha.
*/
      return;
      foreach ( thing in attackers ) {
         if ( !objectp( thing ) )
            continue;
         if ( creatorp(thing) )
            continue;
         if ( !userp( thing ) && !thing->query_property( "unique" ) )
            continue;
/*
 * Inhumations shouldn't lead to bounties.
*/
         if ( ( (object)thing->query_property( "inhumer" ) == victim ) &&
               ( (object)victim->query_property( "inhumee" ) == thing ) )
            continue;
         killer = (string)thing->query_short();
         if ( undefinedp( bounty[ lower_case( killer ) ] ) ) {
            record = new( class bounty );
            bounty[ lower_case( killer ) ] = record;
            record->short = killer;
            record->victims = ({ killed });
         } else {
            record = (class bounty)bounty[ lower_case( killer ) ];
            if ( strsrch( lower_case( killed ),
                  lower_case( implode( record->victims, ", " ) ) ) == -1 )
               record->victims += ({ killed });
         }
         record->time = now;
         if ( record->active ) {
            record->value = sizeof( record->victims ) * BASE;
            if ( sizeof( record->victims ) > 1 )
               call_out( "inform_bounty", 30, thing );
         }
      }
      save_file();
      return;
   }
/*
 * There is a bounty.  Is it active?
*/
   if ( !( ( (class bounty)bounty[ lower_case( killed ) ] )->active ) )
      return;
/*
 * Work out who the legitimate killers are.
*/
   killers = ({ });
   foreach ( thing in attackers ) {
      if ( !objectp( thing ) )
         continue;
      if ( creatorp(thing) )
         continue;
      if ( !userp( thing ) && !thing->query_property( "unique" ) &&
            !thing->query_property( "bounty hunter" ) )
         continue;
      killer = (string)thing->query_short();
      killers += ({ killer });
      call_out( "well_done", 30, thing, (string)victim->query_gender() );
   }
/*
 * If there are valid killers, distribute the bounties appropriately
 * and update the records.
*/
   if ( sizeof( killers ) ) {
      reward = ( (class bounty)bounty[ lower_case( killed ) ] )->value /
            sizeof( killers );
      foreach ( killer in killers ) {
/*
 * Killers with bounties themselves don't get paid.  This should also
 * limit some forms of possible abuse.
*/
         if ( !undefinedp( bounty[ lower_case( killer ) ] ) )
            continue;
         if ( undefinedp( collect[ lower_case( killer ) ] ) ) {
            record = new( class bounty );
            collect[ lower_case( killer ) ] = record;
            record->short = killer;
            record->victims = ({ killed });
         } else {
            record = (class bounty)collect[ lower_case( killer ) ];
            record->victims += ({ killed });
         }
         record->time = now;
         record->value += reward;
      }
      log_file( "BOUNTY", "%s: bounty on %s fulfilled by %s\n",
            ctime( now ), lower_case( killed ),
            query_multiple_short( map_array( killers,
            (: lower_case( $1 ) :) ) ) );
      map_delete( bounty, lower_case( killed ) );
   }
   save_file();
} /* register_kill() */
void inform_bounty( object thing ) {
   if ( !thing )
      return;
   if ( !userp( thing ) )
      return;
   tell_object( thing, "You feel the price on your head rise.\n" );
} /* inform_bounty() */
void well_done( object thing, int gender ) {
   if ( !thing )
      return;
   if ( !userp( thing ) )
      return;
   tell_object( thing, "You feel good about killing a wanted "+
         ({ "creature", "man", "woman" })[gender] +".\n" );
} /* well_done() */
/*
 * This is interfaced by a bounty office to allow people to report murders.
*/
string report_murder( string killer ) {
   string place;
   class bounty record;
   if ( undefinedp( bounty[ lower_case( killer ) ] ) )
      return "According to the records, \""+ killer +
            "\" has never killed anyone.";
   killer = lower_case( killer );
   record = (class bounty)bounty[ killer ];
   if ( record->active ) {
      place = (string)environment( this_player() )->query_property( "place" );
      if ( !place || ( place == "" ) )
         place = "default";
      return "There is already a bounty of "+
            (string)MONEY_H->money_value_string( record->value, place ) +
            " offered for the death of "+ record->short +".";
   }
   record->active = 1;
   record->value = sizeof( record->victims ) * BASE;
   call_out( "issue_warrant", 30, killer );
   save_file();
   if ( strsrch( lower_case( (string)this_player()->query_short() ),
         lower_case( implode( record->victims, ", " ) ) ) == -1 )
      return "Ah, a concerned citizen reporting a murder.  I'll issue "
            "a bounty notice for "+ record->short +" right away.";
   return "I'm sorry to hear that "+ record->short +" caused you "
         "harm.  I'll issue a bounty notice right away.";
} /* report_murder() */
void issue_warrant( string killer ) {
   object thing;
   thing = find_player( killer );
   if ( !thing )
      return;
   if ( !userp( thing ) )
      return;
   thing->remove_hide_invis( "hide" );
   tell_room( environment( thing ), "There is a small unspectacular flash "
         "of light and a small orange imp appears.  It promptly informs "+
         (string)thing->the_short() +" that a bounty has been placed on "+
         (string)thing->query_possessive() +" head.  The imp bows deeply, "
         " smiles evilly, and vanishes in an equally unspectacular flash "
         "of light.\n", thing );
   tell_object( thing, "There is a small unspectacular flash of light and "
         "a small orange imp appears.  It promptly informs you that a bounty "
         "has been placed on your head.  The imp bows deeply, smiles evilly, "
         "and vanishes in an equally unspectacular flash of light.\n" );
} /* issue_warrant() */
/*
 * This is interfaced by a bounty office to allow people to collect bounties.
*/
string collect_bounty() {
   int money;
   string collector, place;
   class bounty record;
   collector = lower_case( (string)this_player()->query_short() );
   if ( undefinedp( collect[ collector ] ) )
      return "According to the records, you've done nothing "
            "to deserve a bounty.";
   record = (class bounty)collect[ collector ];
   money = ( record->value * ( 100 - TAXES ) ) / 100;
   place = (string)environment( this_player() )->query_property( "place" );
   if ( !place || ( place == "" ) )
      place = "default";
   this_player()->adjust_money( (mixed *)MONEY_H->create_money_array( money,
         place ) );
   this_player()->set_title( "bounty", "Bounty Hunter" );
   map_delete( collect, collector );
   save_file();
   return "Ah yes, let's see.  Monies owed to you total "+
         (string)MONEY_H->money_value_string( record->value, place ) +
         ", minus death tax, inheritance tax, income tax, pension fund "
         "contribution, union dues, VAT, Save the Womble donations and "
         "administrative charges, which leaves you with the grand sum of "+
         (string)MONEY_H->money_value_string( money, place ) +
         ".  Nice doing business with you.";
} /* collect_bounty() */
int query_bounty( string word ) { return !undefinedp( bounty[ word ] ); }
int query_bounty_value( string word ) {
   if ( undefinedp( bounty[ word ] ) )
      return 0;
   return ( (class bounty)bounty[ word ] )->value;
} /* query_bounty_value() */
/*
 * Generate a list of bounties; creators see times as well.
*/
string query_all_bounties() {
   int cre;
   string list, word, place;
   class bounty record;
   cre = (int)creatorp(this_player(1));
   if ( !sizeof( bounty ) )
      return "There are no bounties currently offered.\n";
   place = (string)environment( this_player() )->query_property( "place" );
   if ( !place || ( place == "" ) )
      place = "default";
   list = "";
   foreach ( word, record in bounty )
      if ( record->active ) {
         list += sprintf( " -   %-15s for %s\n", record->short,
               (string)MONEY_H->money_value_string( record->value,
               place ) );
         if ( cre )
            list += sprintf( "               %-#60s\n               %s\n",
                  implode( record->victims, "\n" ),
                  ctime( record->time + EXPIRY ) );
      } else
         if ( cre )
            list += sprintf( " -   %-15s (inactive)\n"
                  "               %-#60s\n               %s\n",
                  record->short, implode( record->victims, "\n" ),
                  ctime( record->time + EXPIRY ) );
   if ( list == "" )
      return "There are no bounties currently offered.\n";
   return "The following bounters are currently offered:\n"+ list;
} /* query_all_bounties() */
/*
 * Only creators should get this function called for them.
*/
string query_all_collectors() {
   string list, word, place;
   class bounty record;
   if ( !sizeof( collect ) )
      return "There are no bounties awaiting collection.\n";
   place = (string)environment( this_player() )->query_property( "place" );
   if ( !place || ( place == "" ) )
      place = "default";
   list = "";
   foreach ( word, record in collect )
         list += sprintf( " -   %-15s owed %s\n               %s\n",
               record->short,
               (string)MONEY_H->money_value_string( record->value, place ),
               ctime( record->time + EXPIRY ) );
   return "The following bounties are awaiting collection:\n"+ list;
} /* query_all_collectors() */
string query_bounty_info( string word ) {
   string place;
   class bounty record;
   place = (string)environment( this_player() )->query_property( "place" );
   if ( !place || ( place == "" ) )
      place = "default";
   if ( undefinedp( bounty[ word ] ) )
      return 0;
   record = (class bounty)bounty[ word ];
   if ( !record->active )
      return 0;
   return "A bounty of "+
         (string)MONEY_H->money_value_string( record->value, place ) +
         " is offered for the death of "+ record->short +" for the murder of "+
         query_multiple_short( record->victims ) +".\n";
} /* query_bounty_info() */