LOP/
LOP/area/
LOP/boards/
LOP/channels/
LOP/clans/
LOP/classes/
LOP/color/
LOP/councils/
LOP/deity/
LOP/races/
LOP/src/specials/
/*****************************************************************************
 * DikuMUD (C) 1990, 1991 by:                                                *
 *   Sebastian Hammer, Michael Seifert, Hans Henrik Staefeldt, Tom Madsen,   *
 *   and Katja Nyboe.                                                        *
 *---------------------------------------------------------------------------*
 * MERC 2.1 (C) 1992, 1993 by:                                               *
 *   Michael Chastain, Michael Quan, and Mitchell Tse.                       *
 *---------------------------------------------------------------------------*
 * SMAUG 1.4 (C) 1994, 1995, 1996, 1998 by: Derek Snider.                    *
 *   Team: Thoric, Altrag, Blodkai, Narn, Haus, Scryn, Rennard, Swordbearer, *
 *         gorog, Grishnakh, Nivek, Tricops, and Fireblade.                  *
 *---------------------------------------------------------------------------*
 * SMAUG 1.7 FUSS by: Samson and others of the SMAUG community.              *
 *                    Their contributions are greatly appreciated.           *
 *---------------------------------------------------------------------------*
 * LoP (C) 2006, 2007, 2008 by: the LoP team.                                *
 *---------------------------------------------------------------------------*
 *                        "Special procedure" module                         *
 *****************************************************************************/

#include <stdio.h>
#if !defined(WIN32)
  #include <dlfcn.h>
#else
  #include <windows.h>
  #define dlsym( handle, name ) ( (void*)GetProcAddress( (HINSTANCE) (handle), (name) ) )
  #define dlerror() GetLastError()
#endif
#include "h/mud.h"

void increase_gold( CHAR_DATA *ch, int amount );
void decrease_gold( CHAR_DATA *ch, int amount );
NM ch_ret spell_remove_curse( int sn, int level, CHAR_DATA *ch, void *vo );

#define SPECFUN_FILE SYSTEM_DIR "specfuns.dat"
typedef struct specfun_list SPEC_LIST;
struct specfun_list
{
   SPEC_LIST *next, *prev;
   char *name;
};

/* The following special functions are available for mobiles. */
DECLARE_SPEC_FUN( spec_breath_any );
DECLARE_SPEC_FUN( spec_breath_acid );
DECLARE_SPEC_FUN( spec_breath_fire );
DECLARE_SPEC_FUN( spec_breath_frost );
DECLARE_SPEC_FUN( spec_breath_gas );
DECLARE_SPEC_FUN( spec_breath_lightning );
DECLARE_SPEC_FUN( spec_cast_adept );
DECLARE_SPEC_FUN( spec_cast_cleric );
DECLARE_SPEC_FUN( spec_cast_mage );
DECLARE_SPEC_FUN( spec_cast_undead );
DECLARE_SPEC_FUN( spec_fido );
DECLARE_SPEC_FUN( spec_janitor );
DECLARE_SPEC_FUN( spec_poison );
DECLARE_SPEC_FUN( spec_thief );

SPEC_LIST *first_specfun;
SPEC_LIST *last_specfun;

void free_specfuns( void )
{
   SPEC_LIST *specfun, *next_specfun;

   for( specfun = first_specfun; specfun; specfun = next_specfun )
   {
      next_specfun = specfun->next;
      UNLINK( specfun, first_specfun, last_specfun, next, prev ); 
      STRFREE( specfun->name );
      DISPOSE( specfun );
   }
}

/*
 * Simple load function - no OLC support for now.
 * This is probably something you DONT want builders playing with.
 */
void load_specfuns( void )
{
   SPEC_LIST *specfun;
   FILE *fp;
   char *word;

   first_specfun = last_specfun = NULL;

   if( !( fp = fopen( SPECFUN_FILE, "r" ) ) )
   {
      bug( "%s: can't read %s, exiting.", __FUNCTION__, SPECFUN_FILE );
      perror( SPECFUN_FILE );
      exit( 1 );
   }
   else
   {
      for( ; ; )
      {
         if( feof( fp ) )
            break;

         word = fread_word( fp );
         if( !str_cmp( word, "$" ) )
            break;

         CREATE( specfun, SPEC_LIST, 1 );
         specfun->name = STRALLOC( word );
         LINK( specfun, first_specfun, last_specfun, next, prev );
      }
      fclose( fp );
      fp = NULL;
   }
   return;
}

/* Simple validation function to be sure a function can be used on mobs */
bool validate_spec_fun( char *name )
{
   SPEC_LIST *specfun;

   for( specfun = first_specfun; specfun; specfun = specfun->next )
   {
      if( !str_cmp( specfun->name, name ) )
         return true;
   }
   return false;
}

/* Given a name, return the appropriate spec_fun. */
SPEC_FUN *spec_lookup( char *name )
{
   void *funHandle;
#if !defined(WIN32)
   const char *error;
#else
   DWORD error;
#endif

   funHandle = dlsym( sysdata.dlHandle, name );
   if( ( error = dlerror() ) )
   {
      bug( "%s: Error locating function %s in symbol table.", __FUNCTION__, name );
      return NULL;
   }
   return ( SPEC_FUN * ) funHandle;
}

/* if a spell casting mob is hating someone... try and summon them */
void summon_if_hating( CHAR_DATA *ch )
{
   CHAR_DATA *victim;
   char buf[MSL], name[MIL];
   bool found = false;

   if( ch->position <= POS_SLEEPING )
      return;

   if( ch->fighting || ch->fearing || !ch->hating || xIS_SET( ch->in_room->room_flags, ROOM_SAFE ) )
      return;

   /* if player is close enough to hunt... don't summon */
   if( ch->hunting )
      return;

   one_argument( ch->hating->name, name );

   /* make sure the char exists - works even if player quits */
   for( victim = first_char; victim; victim = victim->next )
   {
      if( !str_cmp( ch->hating->name, victim->name ) )
      {
         found = true;
         break;
      }
   }

   if( !found )
      return;
   if( ch->in_room == victim->in_room )
      return;
   snprintf( buf, sizeof( buf ), "summon %s%s", !is_npc( victim ) ? "0." : "", name );
   do_cast( ch, buf );
   return;
}

bool is_fighting( CHAR_DATA *ch )
{
   if( ch && ( ch->position == POS_FIGHTING || ch->position == POS_EVASIVE
   || ch->position == POS_DEFENSIVE || ch->position == POS_AGGRESSIVE
   || ch->position == POS_BERSERK ) )
      return true;
   return false;
}

/* Core procedure for dragons. */
bool dragon( CHAR_DATA *ch, char *fspell_name )
{
   CHAR_DATA *victim, *v_next;
   int sn;

   if( !is_fighting( ch ) )
      return false;

   for( victim = ch->in_room->first_person; victim; victim = v_next )
   {
      v_next = victim->next_in_room;
      if( who_fighting( victim ) == ch && number_bits( 2 ) == 0 )
         break;
   }

   if( !victim )
      return false;

   if( ( sn = skill_lookup( fspell_name ) ) < 0 )
      return false;
   ( *skill_table[sn]->spell_fun ) ( sn, ch->level, ch, victim );
   return true;
}

/* Special procedures for mobiles. */
bool spec_breath_any( CHAR_DATA *ch )
{
   if( !is_fighting( ch ) )
      return false;

   switch( number_bits( 3 ) )
   {
      case 0:
         return spec_breath_fire( ch );
      case 1:
      case 2:
         return spec_breath_lightning( ch );
      case 3:
         return spec_breath_gas( ch );
      case 4:
         return spec_breath_acid( ch );
      case 5:
      case 6:
      case 7:
         return spec_breath_frost( ch );
   }

   return false;
}

bool spec_breath_acid( CHAR_DATA *ch )
{
   return dragon( ch, "acid breath" );
}

bool spec_breath_fire( CHAR_DATA *ch )
{
   return dragon( ch, "fire breath" );
}

bool spec_breath_frost( CHAR_DATA *ch )
{
   return dragon( ch, "frost breath" );
}

bool spec_breath_gas( CHAR_DATA *ch )
{
   int sn;

   if( !is_fighting( ch ) )
      return false;

   if( ( sn = skill_lookup( "gas breath" ) ) < 0 )
      return false;
   ( *skill_table[sn]->spell_fun ) ( sn, ch->level, ch, NULL );
   return true;
}

bool spec_breath_lightning( CHAR_DATA *ch )
{
   return dragon( ch, "lightning breath" );
}

bool spec_cast_adept( CHAR_DATA *ch )
{
   CHAR_DATA *victim, *v_next;

   if( !is_awake( ch ) || ch->fighting )
      return false;

   for( victim = ch->in_room->first_person; victim; victim = v_next )
   {
      v_next = victim->next_in_room;
      if( victim != ch && can_see( ch, victim ) && number_bits( 1 ) == 0 )
         break;
   }

   if( !victim )
      return false;

   switch( number_bits( 3 ) )
   {
      case 0:
         act( AT_MAGIC, "$n utters the word 'suah'.", ch, NULL, NULL, TO_ROOM );
         spell_smaug( skill_lookup( "cure blindness" ), ch->level, ch, victim );
         return true;

      case 1:
         act( AT_MAGIC, "$n utters the word 'nran'.", ch, NULL, NULL, TO_ROOM );
         spell_smaug( skill_lookup( "cure light" ), ch->level, ch, victim );
         return true;

      case 2:
         act( AT_MAGIC, "$n utters the word 'nyrcs'.", ch, NULL, NULL, TO_ROOM );
         spell_smaug( skill_lookup( "cure poison" ), ch->level, ch, victim );
         return true;

      case 3:
         act( AT_MAGIC, "$n utters the word 'naimad'.", ch, NULL, NULL, TO_ROOM );
         spell_smaug( skill_lookup( "cure serious" ), ch->level, ch, victim );
         return true;

      case 4:
         act( AT_MAGIC, "$n utters the word 'gorog'.", ch, NULL, NULL, TO_ROOM );
         spell_remove_curse( skill_lookup( "remove curse" ), ch->level, ch, victim );
         return true;

      case 5:
         act( AT_MAGIC, "$n utters the word 'rogo'.", ch, NULL, NULL, TO_ROOM );
         spell_smaug( skill_lookup( "cure critical" ), ch->level, ch, victim );
         return true;

      case 6:
         act( AT_MAGIC, "$n utters the word 'floro'.", ch, NULL, NULL, TO_ROOM );
         spell_smaug( skill_lookup( "heal" ), ch->level, ch, victim );
         return true;
   }

   return false;
}

bool spec_cast_cleric( CHAR_DATA *ch )
{
   CHAR_DATA *victim, *v_next;
   char *spell;
   int sn;

   summon_if_hating( ch );
   if( !is_fighting( ch ) )
      return false;

   for( victim = ch->in_room->first_person; victim; victim = v_next )
   {
      v_next = victim->next_in_room;
      if( who_fighting( victim ) == ch && number_bits( 2 ) == 0 )
         break;
   }

   if( !victim || victim == ch )
      return false;

   for( ;; )
   {
      int min_level;

      switch( number_bits( 4 ) )
      {
         case 0:
            min_level = 0;
            spell = "cause light";
            break;

         case 1:
            min_level = 3;
            spell = "cause serious";
            break;

         case 2:
            min_level = 6;
            spell = "earthquake";
            break;

         case 3:
            min_level = 7;
            spell = "blindness";
            break;

         case 4:
            min_level = 9;
            spell = "cause critical";
            break;

         case 5:
            min_level = 10;
            spell = "dispel evil";
            break;

         case 6:
            min_level = 12;
            spell = "curse";
            break;

         default:
            min_level = 16;
            spell = "dispel magic";
            break;
      }

      if( ch->level >= min_level )
         break;
   }

   if( ( sn = skill_lookup( spell ) ) < 0 )
      return false;
   ( *skill_table[sn]->spell_fun ) ( sn, ch->level, ch, victim );
   return true;
}

bool spec_cast_mage( CHAR_DATA *ch )
{
   CHAR_DATA *victim, *v_next;
   char *spell;
   int sn;

   summon_if_hating( ch );
   if( !is_fighting( ch ) )
      return false;

   for( victim = ch->in_room->first_person; victim; victim = v_next )
   {
      v_next = victim->next_in_room;
      if( who_fighting( victim ) && number_bits( 2 ) == 0 )
         break;
   }

   if( !victim || victim == ch )
      return false;

   for( ;; )
   {
      int min_level;

      switch( number_bits( 4 ) )
      {
         case 0:
            min_level = 0;
            spell = "energy drain";
            break;

         default:
            min_level = 15;
            spell = "fireball";
            break;
      }

      if( ch->level >= min_level )
         break;
   }

   if( ( sn = skill_lookup( spell ) ) < 0 )
      return false;
   ( *skill_table[sn]->spell_fun ) ( sn, ch->level, ch, victim );
   return true;
}

bool spec_cast_undead( CHAR_DATA *ch )
{
   CHAR_DATA *victim, *v_next;
   char *spell;
   int sn;

   summon_if_hating( ch );
   if( !is_fighting( ch ) )
      return false;

   for( victim = ch->in_room->first_person; victim; victim = v_next )
   {
      v_next = victim->next_in_room;
      if( who_fighting( victim ) == ch && number_bits( 2 ) == 0 )
         break;
   }

   if( !victim || victim == ch )
      return false;

   for( ;; )
   {
      int min_level;

      switch( number_bits( 4 ) )
      {
         case 0:
            min_level = 0;
            spell = "curse";
            break;

         case 1:
            min_level = 13;
            spell = "blindness";
            break;

         case 2:
            min_level = 14;
            spell = "poison";
            break;

         case 3:
            min_level = 15;
            spell = "energy drain";
            break;

         default:
            min_level = 40;
            spell = "gate";
            break;
      }

      if( ch->level >= min_level )
         break;
   }

   if( ( sn = skill_lookup( spell ) ) < 0 )
      return false;
   ( *skill_table[sn]->spell_fun ) ( sn, ch->level, ch, victim );
   return true;
}

bool spec_fido( CHAR_DATA *ch )
{
   OBJ_DATA *corpse, *c_next, *obj, *obj_next;

   if( !is_awake( ch ) )
      return false;

   for( corpse = ch->in_room->first_content; corpse; corpse = c_next )
   {
      c_next = corpse->next_content;
      if( corpse->item_type != ITEM_CORPSE_NPC )
         continue;

      act( AT_ACTION, "$n savagely devours a corpse.", ch, NULL, NULL, TO_ROOM );
      for( obj = corpse->first_content; obj; obj = obj_next )
      {
         obj_next = obj->next_content;
         obj_from_obj( obj );
         obj_to_room( obj, ch->in_room );
      }
      extract_obj( corpse );
      return true;
   }

   return false;
}

bool spec_janitor( CHAR_DATA *ch )
{
   OBJ_DATA *trash, *trash_next;

   if( !is_awake( ch ) )
      return false;

   for( trash = ch->in_room->first_content; trash; trash = trash_next )
   {
      trash_next = trash->next_content;
      if( is_obj_stat( trash, ITEM_PROTOTYPE ) && ( !is_npc( ch ) || !xIS_SET( ch->act, ACT_PROTOTYPE ) ) )
         continue;
      if( xIS_SET( trash->wear_flags, ITEM_NO_TAKE ) || is_obj_stat( trash, ITEM_BURIED ) )
         continue;
      if( trash->item_type == ITEM_DRINK_CON || trash->item_type == ITEM_TRASH
      || trash->cost < 10 || ( trash->pIndexData->vnum == OBJ_VNUM_SHOPPING_BAG && !trash->first_content ) )
      {
         act( AT_ACTION, "$n picks up some trash.", ch, NULL, NULL, TO_ROOM );
         obj_from_room( trash );
         obj_to_char( trash, ch );
         return true;
      }
   }
   return false;
}

bool spec_poison( CHAR_DATA *ch )
{
   CHAR_DATA *victim;

   if( !is_fighting( ch ) )
      return false;

   if( !( victim = who_fighting( ch ) ) || number_percent( ) > 2 * ch->level )
      return false;

   act( AT_HIT, "You bite $N!", ch, NULL, victim, TO_CHAR );
   act( AT_ACTION, "$n bites $N!", ch, NULL, victim, TO_NOTVICT );
   act( AT_POISON, "$n bites you!", ch, NULL, victim, TO_VICT );
   spell_smaug( gsn_poison, ch->level, ch, victim );
   return true;
}

bool spec_thief( CHAR_DATA *ch )
{
   CHAR_DATA *victim, *v_next;
   int gold;

   if( ch->position != POS_STANDING )
      return false;

   for( victim = ch->in_room->first_person; victim; victim = v_next )
   {
      v_next = victim->next_in_room;

      if( is_npc( victim ) || get_trust( victim ) >= PERM_IMM || number_bits( 2 ) != 0 || !can_see( ch, victim ) )
         continue;
      if( victim->gold <= 0 )
         continue;
      if( is_awake( victim ) && number_range( 0, ch->level ) == 0 )
      {
         act( AT_ACTION, "You discover $n's hands in your sack of gold!", ch, NULL, victim, TO_VICT );
         act( AT_ACTION, "$N discovers $n's hands in $S sack of gold!", ch, NULL, victim, TO_NOTVICT );
         return true;
      }
      else
      {
         gold = ( victim->gold / number_range( 10, 100 ) );
         if( gold <= 0 )
            return false;
         decrease_gold( victim, gold );
         gold = ( gold / 2 );
         increase_gold( ch, gold );
         return true;
      }
   }

   return false;
}