/* ************************************************************************
*  file: limits.c , Limit and gain control module.        Part of DIKUMUD *
*  Usage: Procedures controling gain and limit.                           *
*  Copyright (C) 1990, 1991 - see 'license.doc' for complete information. *
************************************************************************* */

#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <malloc.h>
#include <stdlib.h>

#include "structs.h"
#include "limits.h"
#include "utils.h"
#include "spells.h"
#include "comm.h"
#include "db.h"

char *titles[]={
  "the Dog", "the Donkey", "the Cow", "the Pig", "the Chicken",
   "the Horse", "the Squirrel", "the Mouse", "the Goat", "the Turkey",
   "the Mole", "the Rat", "the Goose", "the Kangaroo", "the Snake"
};

#define READ_TITLE(ch) titles[number(0,14)]

extern struct char_data *character_list;
extern struct obj_data *object_list;
extern struct room_data *world;
extern struct index_data *mob_index;

/* External procedures */

void update_pos( struct char_data *victim );                 /* in fight.c */
void damage(struct char_data *ch, struct char_data *victim,  /*    do      */
            int damage, int weapontype);
struct time_info_data age(struct char_data *ch);

int mana_limit(struct char_data *ch)
{
  return(ch->points.max_mana + 1);
}
int hit_limit(struct char_data *ch)
{
  return (ch->points.max_hit + 1);
}
int move_limit(struct char_data *ch)
{
  return (ch->points.max_move + 1);
}
int mana_gain(struct char_data *ch)
{
  int n,gain;

  if(IS_NPC(ch)) {
    /* Neat and fast */
    gain = GET_LEVEL(ch);
  } else {
    gain = (GET_CON(ch)+GET_WIS(ch))/3;
    switch (GET_POS(ch)) {
      case POSITION_SLEEPING:
        gain += gain;
        if(GET_CON(ch) > 100)
          gain+=GET_CON(ch);
        if(IS_AFFECTED(ch,AFF_HYPERREGEN))
          gain<<=1;
        break;
      case POSITION_RESTING:
        gain+= (gain>>1);  /* Divide by 2 */
        break;
      case POSITION_SITTING:
        gain += (gain>>2); /* Divide by 4 */
        break;
    }
    if((GET_COND(ch,FULL)==0)||(GET_COND(ch,THIRST)==0))
      gain >>= 2;
    else if((GET_COND(ch,DRUNK)) >= 8)
      gain <<= 1;
    if(IS_AFFECTED(ch,AFF_POISON))
      gain = GET_MAX_MANA(ch)/(-336);
  }
  return (gain);
}


int hit_gain(struct char_data *ch)
/* Hitpoint gain pr. game hour */
{
  int gain, n;

  if(IS_NPC(ch)) {
    gain = GET_LEVEL(ch);
    if(IS_AFFECTED(ch,AFF_REGEN))
      gain <<= 2;
  } else {
    gain = (GET_STR(ch) + GET_CON(ch))/5;
    switch (GET_POS(ch)) {
      case POSITION_SLEEPING:
        gain += (gain>>1);
        if(GET_CON(ch) > 90)
          gain+=GET_CON(ch)-90;
        if(IS_AFFECTED(ch,AFF_HYPERREGEN))
          gain<<=1;
        break;
      case POSITION_RESTING:
        gain+= (gain>>2);
        break;
      case POSITION_SITTING:
        gain += (gain>>3);
        break;
    }
    if((GET_COND(ch,FULL)==0)||(GET_COND(ch,THIRST)==0))
      gain >>= 2;
    else if((GET_COND(ch,DRUNK)) >= 8)
      gain <<= 1;
    if(IS_AFFECTED(ch,AFF_POISON))
      gain = GET_MAX_HIT(ch)/(-168);
  }
  if(IS_AFFECTED(ch,AFF_REGEN))
    gain <<= 1;
  return (gain);
}

int move_gain(struct char_data *ch)
{
  int gain;

  if(IS_NPC(ch)) {
    return(GET_LEVEL(ch));  
  } else {
    gain = (GET_DEX(ch) + GET_CON(ch))/2;
    switch (GET_POS(ch)) {
      case POSITION_SLEEPING:
        gain += (gain>>1); /* Divide by 2 */
        break;
      case POSITION_RESTING:
        gain+= (gain>>2);  /* Divide by 4 */
        break;
      case POSITION_SITTING:
        gain += (gain>>3); /* Divide by 8 */
        break;
    }
  }

  if (IS_AFFECTED(ch,AFF_POISON))
    gain >>= 2;

  if((GET_COND(ch,FULL)==0)||(GET_COND(ch,THIRST)==0))
    gain >>= 2;

  return (gain);
}


/* Gain maximum in various points */
void advance_level(struct char_data *ch)
{
  int addx;

  addx = (GET_CON(ch)/8);
  addx += ((GET_LEVEL(ch) < 2) ? 2 : number(1,3));
  ch->points.max_hit += MAX(1, addx);
  addx = (GET_INT(ch)/8);
  addx += ((GET_LEVEL(ch) < 2) ? 2 : number(1,3));
  ch->points.max_mana += MAX(1, addx);
  addx = (GET_DEX(ch)/8);
  addx += 8;
  ch->points.max_move += MAX(1, addx);
  ch->specials.spells_to_learn += (9+GET_WIS(ch))/10;
}  

void set_title(struct char_data *ch)
{
  char *s;

  s=READ_TITLE(ch);
  if (GET_TITLE(ch))
    RECREATE(GET_TITLE(ch),char,strlen(s)+1);
  else
    CREATE(GET_TITLE(ch),char,strlen(s)+1);
  strcpy(GET_TITLE(ch), s);
}
void gain_exp(struct char_data *ch, int gain)
{
  int t, confactor;


  if(gain > 0){
   
/* SLUG_CHANGE 11-9-96  */
    ch->specials.metadata[ch->specials.metadata[MAX_META_SAMPLES]] += gain;
    t=GET_EXP(ch);
    if((t > 0)&&((t+gain) < 0)){
      send_to_char("Hey dogbreath, your experience is at the maximum!\n\r",ch);
      GET_EXP(ch) = 2147000000;
      return;
    }
  }
  if(IS_NPC(ch) || ((GET_LEVEL(ch)<IMO) && (GET_LEVEL(ch) > 0))) {
    if(gain > 0){ 
      GET_EXP(ch) += gain;
      if(!IS_NPC(ch) && IS_SET(ch->specials.act,PLR_AUTOCNVRT))
        if(GET_EXP(ch) >= 1000000){
          GET_META(ch) += GET_EXP(ch)/1000000;
          GET_EXP(ch)  %= 1000000;
        }
    } else if (gain < 0) {
      gain = abs(gain);
      if(GET_EXP(ch) > gain)
        GET_EXP(ch) -= gain;
      else
        GET_EXP(ch) = 0;
    }
  }
}
void gain_condition(struct char_data *ch,int condition,int value)
{
  bool intoxicated;

  intoxicated=(GET_COND(ch, DRUNK) > 0);
  GET_COND(ch,condition) += value;
  GET_COND(ch,condition) = MAX(0,GET_COND(ch,condition));
  GET_COND(ch,condition) = MIN(GET_GUT(ch),GET_COND(ch,condition));
  if(GET_COND(ch,condition))
    return;

  switch(condition){
    case FULL :
    {
      send_to_char("You are hungry.\n\r",ch);
      return;
    }
    case THIRST :
    {
      send_to_char("You are thirsty.\n\r",ch);
      return;
    }
    case DRUNK :
    {
      if(intoxicated)
        send_to_char("You are now sober.\n\r",ch);
      return;
    }
    default : break;
  }
}


void check_idling(struct char_data *ch)
{
  if (++(ch->specials.timer) > 8)
    if (ch->specials.was_in_room == NOWHERE && ch->in_room != NOWHERE) {
      ch->specials.was_in_room = ch->in_room;
      if (ch->specials.fighting) {
        stop_fighting(ch->specials.fighting);
        stop_fighting(ch);
      }
      act("$n disappears into the void.", TRUE, ch, 0, 0, TO_ROOM);
      send_to_char("You have been idle, and are pulled into a void.\n\r", ch);
      char_from_room(ch);
      char_to_room(ch, 1);  /* Into room number 0 */
    } else if (ch->specials.timer > 99) {
      if (ch->desc)
        close_socket(ch->desc);
      do_rent(ch,0,0);
    }
}

/* Update both PC's & NPC's and objects*/
void point_update( void )
{  
  void update_char_objects( struct char_data *ch ); /* handler.c */
  void extract_obj(struct obj_data *obj); /* handler.c */
  struct char_data *i, *ni, *next_dude, *vict;
  struct obj_data *j, *next_thing, *jj, *next_thing2, *jjj;
  char *adp;
  int d1,d2,d3;
  int bdam,door,newroom;

  /* characters */
  for (i = character_list; i; i = next_dude) {
    next_dude = i->next;
    if (GET_POS(i) > POSITION_STUNNED) {
      d1=hit_limit(i)-GET_HIT(i);
      if(d1 > 0){
        d2=hit_gain(i);
        d3=MIN(d1,d2);
        GET_HIT(i) += d3;
      }
      d1=mana_limit(i)-GET_MANA(i);
      if(d1 > 0){
        d2=mana_gain(i);
        d3=MIN(d1,d2);
        GET_MANA(i) += d3;
        GET_COND(i,FULL) = MAX(0,GET_COND(i,FULL)-d3/100);
      }
      d1=move_limit(i)-GET_MOVE(i);
      if(d1 > 0){
        d2=move_gain(i);
        d3=MIN(d1,d2);
        GET_MOVE(i) += d3;
        GET_COND(i,THIRST) = MAX(0,GET_COND(i,THIRST)-d3/100);
      }
    } else if (GET_POS(i) == POSITION_STUNNED) {
      GET_HIT(i)  = MIN(GET_HIT(i)  + hit_gain(i),  hit_limit(i));
      GET_MANA(i) = MIN(GET_MANA(i) + mana_gain(i), mana_limit(i));
      GET_MOVE(i) = MIN(GET_MOVE(i) + move_gain(i), move_limit(i));
    } else if  (GET_POS(i) == POSITION_INCAP) {
      damage(i, i, 1, TYPE_SUFFERING);
    } else if (!IS_NPC(i) && (GET_POS(i) == POSITION_MORTALLYW))
      damage(i, i, 2, TYPE_SUFFERING);
    if (!IS_NPC(i)) {
      update_char_objects(i);
      if (GET_LEVEL(i) < (IMO+1)){
        update_pos(i);
        check_idling(i);
      }
    }
    gain_condition(i,FULL,-1);
    gain_condition(i,DRUNK,-1);
    gain_condition(i,THIRST,-1);
    update_pos(i);
    if(GET_POS(i) == POSITION_DEAD) die(i);
 /* SLUG_CHANGE 11-9-96 Increment front of queue index */   
    i->specials.metadata[MAX_META_SAMPLES]++;
    i->specials.metadata[MAX_META_SAMPLES] %= MAX_META_SAMPLES; 
    i->specials.metadata[i->specials.metadata[MAX_META_SAMPLES]] = 0;
/* SLUG_CHANGE 11-13-96 increment char tic counter */
    i->specials.connect_tics++;
  } /* for */
  /* objects */
  for(j = object_list; j ; j = next_thing){
    next_thing = j->next; /* Next in object list */
    if(IS_SET(j->obj_flags.extra_flags,ITEM_POOFSOON)){
      if(j->obj_flags.timer)
       --j->obj_flags.timer;
      if(j->obj_flags.timer <= 0){
        if(GET_ITEM_TYPE(j) == ITEM_BOMB) {
          if(i=j->carried_by){
            act("A bomb in your inventory explodes, OUCH!!",
              FALSE,i,j,0,TO_CHAR);
            act("A bomb carried by $n EXPLODES, causing bodily harm!!",
              FALSE,i,j,0,TO_NOTVICT);
            if(!IS_SET(world[i->in_room].room_flags,LAWFUL))
            if(IS_NPC(i) || (GET_LEVEL(i) < IMO)){
              GET_HIT(i) -= MIN(j->obj_flags.value[0],GET_HIT(i)-1);
              update_pos(i);
            }
            extract_obj(j);
          } else if (j->in_room != NOWHERE) {
            if(!IS_SET(world[j->in_room].room_flags,LAWFUL)){
              for(i=world[j->in_room].people;i;i=ni){
                ni=i->next_in_room;
                if(IS_NPC(i)){
                  if(IS_SET(i->specials.act,ACT_SMART)){
                    if(GET_POS(i) < POSITION_STANDING)
                      do_stand(i,0,0);
                    if(j->obj_flags.value[0] > (GET_HIT(i)>>4)){
                      if(number(0,4))
                        defuser(i,0,0);
                      else
                        do_flee(i,0,0);
                    }
                  }
                }
              }
              for(i=world[j->in_room].people;i;i=ni){
                ni=i->next_in_room;
                if(IS_NPC(i) || (GET_LEVEL(i) < IMO)){
                  if(!IS_NPC(i))
                    send_to_char("You are hurt!\n\r",i);
                  bdam = MIN((j->obj_flags.value[0])>>1,GET_HIT(i)-1);
                  if((jjj=i->equipment[WEAR_SHIELD])&&
                     (jjj->obj_flags.value[1]==SHIELD_BOMB))
                    bdam>>=2;
                  GET_HIT(i) -= bdam;
                  update_pos(i);
                }
              }
              send_to_room("An explosion rocks the room!\n\r",j->in_room);
              for(door=0;door<=5;door++) 
                if((world[j->in_room].dir_option[door])&&
                   ((newroom=world[j->in_room].dir_option[door]->to_room) > 0))
                  send_to_room("You hear a nearby explosion.\n\r",newroom);
            } else {
              send_to_room("Your hear a loud POP!\n\r",j->in_room);
            }
            extract_obj(j);
          }
        } else if (j->carried_by){
          act("It seems that $p has vanished!",
            FALSE,j->carried_by,j,0,TO_CHAR);
          extract_obj(j);
        } else if ((j->in_room != NOWHERE) && (world[j->in_room].people)){
          act("Miraculously, $p vanishes.",
            TRUE, world[j->in_room].people, j, 0, TO_ROOM);
          extract_obj(j);
        }
      }
    } else if((GET_ITEM_TYPE(j)==ITEM_CONTAINER)&&(j->obj_flags.value[3])){
      /* timer count down */
      if (j->obj_flags.timer > 0) j->obj_flags.timer--;
      if (!j->obj_flags.timer) {
        if (j->carried_by)
          act("The $p decays in your hands.",FALSE,j->carried_by,j,0,TO_CHAR);
        else if ((j->in_room != NOWHERE) && (world[j->in_room].people)){
          act("The $p dries up and blows away.",
            TRUE, world[j->in_room].people, j, 0, TO_ROOM);
          act("The $p dries up and blows away.",
            TRUE, world[j->in_room].people, j, 0, TO_CHAR);
        }
        for(jj = j->contains; jj; jj = next_thing2) {
          next_thing2 = jj->next_content; /* Next in inventory */
          obj_from_obj(jj);
          if (j->in_obj)
            obj_to_obj(jj,j->in_obj);
          else if (j->carried_by)
            obj_to_char(jj,j->carried_by);
          else if (j->in_room != NOWHERE)
            obj_to_room(jj,j->in_room);
          else
            assert(FALSE);
        }
        extract_obj(j);
      }
    }
  }
}
void recover_in_rent(struct char_data *ch)
{
  int t,dhp,dma,dmo;
  char buf[256];

  t=(ch->specials.xxx)/15;
  ch->specials.xxx=0;
  sprintf(buf,"You were gone %d ticks.\n\r",t);
  send_to_char(buf,ch);
  if(t < 120) return;
  t<<=2;
  dhp = hit_gain(ch);  if(dhp < 0) dhp=0;
  dma = mana_gain(ch); if(dma < 0) dma=0;
  dmo = move_gain(ch); if(dmo < 0) dmo=0;
  GET_HIT(ch)  = MIN(GET_HIT(ch)  + t*dhp,  hit_limit(ch));
  GET_MANA(ch) = MIN(GET_MANA(ch) + t*dma, mana_limit(ch));
  GET_MOVE(ch) = MIN(GET_MOVE(ch) + t*dmo, move_limit(ch));
  send_to_char("You feel refreshed from your long nap.\n\r",ch);
}