/* ************************************************************************
*  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 "structs.h"
#include "limits.h"
#include "utils.h"
#include "spells.h"
#include "comm.h"
#include "db.h"
#include "config.h"

#define READ_TITLE(ch) (GET_SEX(ch) == SEX_MALE ?   \
	titles[GET_CLASS(ch)-1][GET_LEVEL(ch)].title_m :  \
	titles[GET_CLASS(ch)-1][GET_LEVEL(ch)].title_f)


extern struct char_data *character_list;
extern struct obj_data *object_list;
extern struct title_type titles[4][28];
extern struct room_data *world;
extern struct zone_data *zone_table;

/* 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);
void extract_obj(struct obj_data *obj); /* handler.c */
struct obj_data *unequip_char(struct char_data *ch,int pos);


/* When age < 15 return the value p0 */
/* When age in 15..29 calculate the line between p1 & p2 */
/* When age in 30..44 calculate the line between p2 & p3 */
/* When age in 45..59 calculate the line between p3 & p4 */
/* When age in 60..79 calculate the line between p4 & p5 */
/* When age >= 80 return the value p6 */
int graf(int age, int p0, int p1, int p2, int p3, int p4, int p5, int p6)
{

	if (age < 15)
		return(p0);                               /* < 15   */
	else if (age <= 29) 
		return (int) (p1+(((age-15)*(p2-p1))/15));  /* 15..29 */
	else if (age <= 44)
		return (int) (p2+(((age-30)*(p3-p2))/15));  /* 30..44 */
	else if (age <= 59)
		return (int) (p3+(((age-45)*(p4-p3))/15));  /* 45..59 */
	else if (age <= 79)
		return (int) (p4+(((age-60)*(p5-p4))/20));  /* 60..79 */
	else
		return(p6);                               /* >= 80 */
}


/* The three MAX functions define a characters Effective maximum */
/* Which is NOT the same as the ch->points.max_xxxx !!!          */
int mana_limit(struct char_data *ch)
{
	int max;

	if (!IS_NPC(ch)) {
		max = (100);  /* + (graf(age(ch).year, 0,0,10,30,50,70,60)); */
		max = MAX(ch->points.max_mana, max);
 	}
	else {
		max = 100;
	}
	return(max);
}


int hit_limit(struct char_data *ch)
{
	int max;

	if (!IS_NPC(ch))
		max = (ch->points.max_hit) +
		      (graf(age(ch).year, 2,4,17,14,8,4,3));
	else 
		max = (ch->points.max_hit);


/* Class/Level calculations */

/* Skill/Spell calculations */
	
  return (max);
}


int move_limit(struct char_data *ch)
{
	int max;

	if (!IS_NPC(ch)) {
		/* HERE SHOULD BE CON CALCULATIONS INSTEAD */
		max = graf(age(ch).year, 50,70,160,120,100,40,20);
		max = MAX(ch->points.max_move, max);
	}
	else {
		max = ch->points.max_move;
	}
/* Class/Level calculations */

/* Skill/Spell calculations */

  return (max);
}




/* manapoint gain pr. game hour */
int mana_gain(struct char_data *ch)
{
	int gain;

	if(IS_NPC(ch)) {
		/* Neat and fast */
		gain = GET_LEVEL(ch);
	} else {
		gain = graf(age(ch).year, 2,4,6,8,6,5,8);

		/* Local energy calculation */
		gain = (zone_table[world[ch->in_room].zone].conditions.free_energy*gain)/10000;

		/* Class calculations */

		/* Skill/Spell calculations */

		/* Position calculations    */
		switch (GET_POS(ch)) {
			case POSITION_SLEEPING:
				gain += gain;
				break;
			case POSITION_RESTING:
				gain+= (gain>>1);  /* Divide by 2 */
				break;
			case POSITION_SITTING:
				gain += (gain>>2); /* Divide by 4 */
				break;
		}

		if ((GET_CLASS(ch) == CLASS_MAGIC_USER) || (GET_CLASS(ch) == CLASS_CLERIC)
		    ||IS_SET(ch->specials.act,PLR_ISMULTIMU)||IS_SET(ch->specials.act,PLR_ISMULTICL))
			gain += gain;
	}

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

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

	if(zone_table[world[ch->in_room].zone].conditions.free_energy <= 0)
		gain=0;
	else if(gain <= 1)
		gain=2;

        /* Room mana gains should be absolute */
	if((world[ch->in_room].mana_alignment==MANA_ALL_ALIGNS) ||
		(world[ch->in_room].mana_alignment==MANA_GOOD &&
			IS_GOOD(ch)) ||
		(world[ch->in_room].mana_alignment==MANA_NEUTRAL &&
			IS_NEUTRAL(ch)) ||
		(world[ch->in_room].mana_alignment==MANA_EVIL &&
			IS_EVIL(ch)))
		gain += world[ch->in_room].mana;
 
	if(GET_MANA(ch)+gain > mana_limit(ch))
		gain=mana_limit(ch)-GET_MANA(ch);

	if(GET_MANA(ch)+gain < 0)
		gain = -GET_MANA(ch);

	zone_table[world[ch->in_room].zone].conditions.free_energy -= gain;

  return (gain);
}


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

	if(IS_NPC(ch)) {
		gain = GET_LEVEL(ch);
		/* Neat and fast ...not sure if I like it though --Sman*/
	} else {

		gain = graf(age(ch).year, 2,5,10,18,6,4,2);

		/* Class/Level calculations */

		/* Skill/Spell calculations */

		/* Position calculations    */

		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 (((GET_CLASS(ch) == CLASS_MAGIC_USER) || (GET_CLASS(ch) == CLASS_CLERIC))&&
		   (!IS_SET(ch->specials.act,PLR_ISMULTIWA)&&!IS_SET(ch->specials.act,PLR_ISMULTITH)))
			gain >>= 1;
  }

	if (IS_AFFECTED(ch,AFF_POISON)) {
		gain >>= 2;
	  	damage(ch,ch,2,SPELL_POISON);
	}

	/* Weather effects hit points */
	if(!(world[ch->in_room].room_flags & INDOORS) && !IS_TRUSTED(ch)) {
		temp_dam=(ch->specials.warmth/10 +
			zone_table[world[ch->in_room].zone].conditions.temp);
		if(IS_NPC(ch)) /* NPC's shouldn't be so affected */
			if(temp_dam > 25)
				temp_dam -= 15;
			else
				temp_dam += 15;
		if(temp_dam <-3)
			damage(ch,ch,-temp_dam/4,DAMAGE_FROSTBITE);
		else if(temp_dam > 50)
			damage(ch,ch,(temp_dam-50)/4+1,DAMAGE_HEATSTROKE);
	}

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

  return (gain);
}



int move_gain(struct char_data *ch)
/* move gain pr. game hour */
{
	int gain;

	if(IS_NPC(ch)) {
		return(GET_LEVEL(ch));	
		/* Neat and fast */
	} else {
		gain = graf(age(ch).year, 12,18,22,21,14,10,6);

		/* Class/Level calculations */

		/* Skill/Spell calculations */


		/* Position calculations    */
		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 add_hp, i;
	int add_mana;

	extern struct wis_app_type wis_app[];
	extern struct con_app_type con_app[];

	
	add_hp = con_app[GET_CON(ch)].hitp;

	switch(GET_CLASS(ch)) {

		case CLASS_MAGIC_USER : {
			add_hp += number(3, 8);
			add_mana = number(GET_LEVEL(ch),(int)(1.5*GET_LEVEL(ch))); 
			add_mana = MIN(add_mana, 10);
		} break;

		case CLASS_CLERIC : {
			add_hp += number(5, 10);
			add_mana = number(GET_LEVEL(ch),(int)(1.5*GET_LEVEL(ch)));
			add_mana = MIN(add_mana, 10);
		} break;

		case CLASS_THIEF : {
			add_hp += number(7,13);
			add_mana = 0;
		} break;

		case CLASS_WARRIOR : {
			add_hp += number(10,15);
			add_mana = 0;
		} break;
	}

	if (IS_SET(ch->specials.act,PLR_ISMULTITH)||
	    IS_SET(ch->specials.act,PLR_ISMULTICL)||
	    IS_SET(ch->specials.act,PLR_ISMULTIWA)||
	    IS_SET(ch->specials.act,PLR_ISMULTIMU)){
		add_hp /= 2;
	} /* Dual classed chars only gain 1/2 hp */

	ch->points.max_hit += MAX(1, add_hp);
        if ((GET_LEVEL(ch) != 1) && (ch->points.max_mana < 160)) {
   	   ch->points.max_mana = ch->points.max_mana + (sh_int)add_mana;
  	} /* if */

	if (GET_CLASS(ch) == CLASS_MAGIC_USER || GET_CLASS(ch) == CLASS_CLERIC)
		ch->specials.spells_to_learn += MAX(2, wis_app[GET_WIS(ch)].bonus);
	else
		ch->specials.spells_to_learn += MIN(2,MAX(1, wis_app[GET_WIS(ch)].bonus));

	if (IS_TRUSTED(ch)){
		for (i = 0; i < 3; i++)
			ch->specials.conditions[i] = -1;
	} else if( !IS_NPC(ch) && ch->specials.conditions[0]== -1) {
		for(i=0;i<3;i++){
			ch->specials.conditions[i]=1;
		}
	}
}


void set_title(struct char_data *ch)
{
	if (GET_TITLE(ch))
		RECREATE(GET_TITLE(ch),char,strlen(READ_TITLE(ch))+1);
	else
		CREATE(GET_TITLE(ch),char,strlen(READ_TITLE(ch)));

	strcpy(GET_TITLE(ch), READ_TITLE(ch));
}



void gain_exp(struct char_data *ch, int gain)
{
	int i;
	bool is_altered = FALSE;

	if (IS_NPC(ch) || (!IS_TRUSTED(ch) && GET_LEVEL(ch) > 0 )){
		if (gain > 0 && GET_EXP(ch)<MAX_EXP) {

			gain = MIN((titles[GET_CLASS(ch)-1][GET_LEVEL(ch)+1].exp
			       -titles[GET_CLASS(ch)-1][GET_LEVEL(ch)].exp)/2,
				gain);

			if (!IS_NPC(ch)){ /* Dual class gain 1/2 exp */
				if (IS_SET(ch->specials.act,PLR_ISMULTITH)||
				    IS_SET(ch->specials.act,PLR_ISMULTIMU)||
				    IS_SET(ch->specials.act,PLR_ISMULTIWA)||
				    IS_SET(ch->specials.act,PLR_ISMULTICL)){
					gain /= 3; 
				}
			}

			GET_EXP(ch) += gain;
			if (!IS_NPC(ch)) {
				for (i = 0; titles[GET_CLASS(ch)-1][i].exp <= GET_EXP(ch); i++) {
					if (i > GET_LEVEL(ch)) {
						send_to_char("You raise a level\n\r", ch);
						GET_LEVEL(ch) = i;
						advance_level(ch);
						is_altered = TRUE;
					}
				}
			}
		}

		if (gain < 0) {
			gain = MAX(-500000, gain);  /* Never loose more than 1/2 mil */
			GET_EXP(ch) += gain;
			if (GET_EXP(ch) < 0)
				GET_EXP(ch) = 0;
		}

		if (is_altered)
			set_title(ch);
	}
}


void gain_exp_regardless(struct char_data *ch, int gain)
{
	int i;
  bool is_altered = FALSE;

	if (!IS_NPC(ch)) {
		if (gain > 0) {
			GET_EXP(ch) += gain;
			for (i = 0; (i<=LV_ARCHITECT) && (titles[GET_CLASS(ch)-1][i].exp <= GET_EXP(ch)); i++) {
				if (i > GET_LEVEL(ch)) {
					send_to_char("You raise a level\n\r", ch);
					GET_LEVEL(ch) = i;
					advance_level(ch);
					is_altered = TRUE;
				}
			}
		}
		if (gain < 0) 
			GET_EXP(ch) += gain;
		if (GET_EXP(ch) < 0)
			GET_EXP(ch) = 0;
	}
	if (is_altered)
		set_title(ch);
}

void gain_condition(struct char_data *ch,int condition,int value)
{
	bool intoxicated;

	if(GET_COND(ch, condition)==-1) /* No change */
		return;

	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(24,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)
{
	char buf[MAX_STRING_LENGTH];
	int i;
	struct obj_data *obj;

	if (++(ch->specials.timer) > 8 || !ch->desc)
		if (ch->specials.was_in_room == NOWHERE && ch->in_room != NOWHERE)
		{
			ch->specials.was_in_room = ch->in_room;
			if (ch->specials.fighting)
			{	/* This makes the falacious assumption that if
				*  A is fighting B, then B is fighting A, when
				*  in fact, B could be fighting C, or perhaps
				*  nobody at all (timing is important).
				*
				* 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,0);  /* Into room number 0 */
			/* Swiftest: I added the following two lines */
			save_char(ch,NOWHERE);
			save_obj2(ch);
		}
		else if (ch->specials.timer > 48)
		{
			if (ch->in_room != NOWHERE)
				char_from_room(ch);
			/* Swiftest: want to put all link deads in
			limbo instead of extract... 
			if (ch->specials.was_in_room != NOWHERE)
				char_to_room(ch, ch->specials.was_in_room,0);
			else
				char_to_room(ch, 1,0);
			*/
			char_to_room(ch,1,0);
			if (ch->desc)
				close_socket(ch->desc);
			ch->desc = 0;
			/* Swiftest: Want to save their equipment..
			extract_char(ch);
			So I added the following instead. Hope it works...
			*/
			save_char(ch,NOWHERE);
			save_obj2(ch);
#if CONFIG_AUTORENT
			/* Working on autorent here...*/
			sprintf(buf,"mv crash/%s.crash oldcrash",ch->player.name);
			system(buf);
			/*--** Need to eliminate items before extract **--*/
			while(ch->carrying)
				extract_obj(ch->carrying);
			for(i=0;i<MAX_WEAR;i++)
				if(ch->equipment[i]) {
					obj=unequip_char(ch,i);
					extract_obj(obj);
				}
			extract_char(ch);
#endif
		}
}





/* Update both PC's & NPC's and objects*/
void point_update( void )
{	
	void update_char_objects( struct char_data *ch ); /* handler.c */
	struct char_data *i, *next_dude;
  struct obj_data *j, *next_thing, *jj, *next_thing2;

  /* characters */
	for (i = character_list; i; i = next_dude) {
		next_dude = i->next;
		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_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));
			update_pos( 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) < LV_GREATER)
				check_idling(i);
		}
		gain_condition(i,FULL,-1);
		gain_condition(i,DRUNK,-1);
		gain_condition(i,THIRST,-1);
	} /* for */

  /* objects */
	for(j = object_list; j ; j = next_thing){
		next_thing = j->next; /* Next in object list */

  	/* If this is a corpse */
		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("$p decay in your hands.", FALSE, j->carried_by, j, 0, TO_CHAR);
				else if ((j->in_room != NOWHERE) && (world[j->in_room].people)){
					act("A quivering hoard of maggots consume $p.",
					  TRUE, world[j->in_room].people, j, 0, TO_ROOM);
					act("A quivering hoard of maggots consume $p.",
					  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_room(jj,j->carried_by->in_room);
					else if (j->in_room != NOWHERE)
						obj_to_room(jj,j->in_room);
					else
						assert(FALSE);
				}
				extract_obj(j);
			}
		}
	}
}