Lyonesse/bin/
Lyonesse/doc/eng/
Lyonesse/doc/ita/
Lyonesse/lib/
Lyonesse/lib/buildings/
Lyonesse/lib/clans/
Lyonesse/lib/data/
Lyonesse/lib/etc/
Lyonesse/lib/house/
Lyonesse/lib/misc/
Lyonesse/lib/plralias/A-E/
Lyonesse/lib/plralias/F-J/
Lyonesse/lib/plralias/K-O/
Lyonesse/lib/plralias/P-T/
Lyonesse/lib/plralias/U-Z/
Lyonesse/lib/plralias/ZZZ/
Lyonesse/lib/plrobjs/A-E/
Lyonesse/lib/plrobjs/F-J/
Lyonesse/lib/plrobjs/K-O/
Lyonesse/lib/plrobjs/P-T/
Lyonesse/lib/plrobjs/U-Z/
Lyonesse/lib/plrobjs/ZZZ/
Lyonesse/lib/plrsave/A-E/
Lyonesse/lib/plrsave/F-J/
Lyonesse/lib/plrsave/K-O/
Lyonesse/lib/plrsave/P-T/
Lyonesse/lib/plrsave/U-Z/
Lyonesse/lib/plrsave/ZZZ/
Lyonesse/lib/ships/
Lyonesse/lib/stables/
Lyonesse/lib/text/help/
Lyonesse/lib/world/
Lyonesse/lib/world/bld/
Lyonesse/lib/world/ship/
Lyonesse/lib/world/shp/
Lyonesse/lib/world/wls/
Lyonesse/lib/world/wls/Life/
Lyonesse/lib/world/wls/Map/
Lyonesse/log/
/**************************************************************************
 * #   #   #   ##   #  #  ###   ##   ##  ###       http://www.lyonesse.it *
 * #    # #   #  #  ## #  #    #    #    #                                *
 * #     #    #  #  # ##  ##    #    #   ##   ## ##  #  #  ##             *
 * #     #    #  #  # ##  #      #    #  #    # # #  #  #  # #            *
 * ###   #     ##   #  #  ###  ##   ##   ###  #   #  ####  ##    Ver. 1.0 *
 *                                                                        *
 * -Based on CircleMud & Smaug-     Copyright (c) 2001-2002 by Mithrandir *
 *                                                                        *
 * ********************************************************************** */
/* ************************************************************************
*   File: limits.c                                      Part of CircleMUD *
*  Usage: limits & gain funcs for HMV, exp, hunger/thirst, idle time      *
*                                                                         *
*  All rights reserved.  See license.doc for complete information.        *
*                                                                         *
*  Copyright (C) 1993, 94 by the Trustees of the Johns Hopkins University *
*  CircleMUD is based on DikuMUD, Copyright (C) 1990, 1991.               *
************************************************************************ */

#include "conf.h"
#include "sysdep.h"

#include "structs.h"
#include "utils.h"
#include "spells.h"
#include "comm.h"
#include "db.h"
#include "handler.h"
#include "interpreter.h"


/* external variables */
extern int max_exp_gain;
extern int max_exp_loss;
extern int idle_rent_time;
extern int idle_max_level;
extern int idle_void;
extern int use_autowiz;
extern int min_wizlist_lev;
extern int free_rent;

/* external functions */
char	*title_male(int chclass, int level);
char	*title_female(int chclass, int level);
int		tot_exp_to_level(int level);
void	update_char_objects(CHAR_DATA *ch);
void	reboot_wizlists(void);

/* local functions */
int graf(int age, int p0, int p1, int p2, int p3, int p4, int p5, int p6);
void check_autowiz(CHAR_DATA *ch);

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


/*
 * The hit_limit, mana_limit, and move_limit functions are gone.  They
 * added an unnecessary level of complexity to the internal structure,
 * weren't particularly useful, and led to some annoying bugs.  From the
 * players' point of view, the only difference the removal of these
 * functions will make is that a character's age will now only affect
 * the HMV gain per tick, and _not_ the HMV maximums.
 */

/* manapoint gain pr. game hour */
int mana_gain(CHAR_DATA *ch)
{
	int gain;
	
	if (IS_NPC(ch))
	{
		/* Neat and fast */
		gain = GET_LEVEL(ch);
	}
	else
	{
		gain = graf(age(ch)->year, 4, 8, 12, 16, 12, 10, 8);
		
		/* Class calculations */
		
		/* Skill/Spell calculations */
		
		/* Position calculations    */
		switch (GET_POS(ch))
		{
		case POS_SLEEPING:
			gain *= 2;
			break;
		case POS_RESTING:
			gain += (gain / 2);	/* Divide by 2 */
			break;
		case POS_SITTING:
			gain += (gain / 4);	/* Divide by 4 */
			break;
		}
		
		if (IS_MAGIC_USER(ch) || IS_CLERIC(ch) || IS_SORCERER(ch))
		{
			if ( ch->in_obj && OBJ_FLAGGED(ch->in_obj, ITEM_FAST_MAGIC) )
				gain *= 10;
			else
				gain *= 2;
		}
		
		if ((GET_COND(ch, FULL) == 0) || (GET_COND(ch, THIRST) == 0))
			gain /= 4;
	}
	
	if (AFF_FLAGGED(ch, AFF_POISON))
		gain /= 4;
	
	return (gain);
}


/* Hitpoint gain pr. game hour */
int hit_gain(CHAR_DATA *ch)
{
	int gain;
	
	if (IS_NPC(ch))
	{
		/* Neat and fast */
		gain = GET_LEVEL(ch);
	}
	else
	{
		
		gain = graf(age(ch)->year, 8, 12, 20, 32, 16, 10, 4);
		
		/* Class/Level calculations */
		
		/* Skill/Spell calculations */
		
		/* Position calculations    */
		
		switch (GET_POS(ch))
		{
		case POS_SLEEPING:
			gain += (gain / 2);	/* Divide by 2 */
			break;
		case POS_RESTING:
			gain += (gain / 4);	/* Divide by 4 */
			break;
		case POS_SITTING:
			gain += (gain / 8);	/* Divide by 8 */
			break;
		}
		
		if (IS_MAGIC_USER(ch) || IS_CLERIC(ch) || IS_SORCERER(ch))
			gain /= 2;	/* Ouch. */
		
		if ((GET_COND(ch, FULL) == 0) || (GET_COND(ch, THIRST) == 0))
			gain /= 4;
	}
	
	if (AFF_FLAGGED(ch, AFF_REGENERATION))
		gain *= 2;

	if (AFF_FLAGGED(ch, AFF_POISON))
		gain /= 4;
	
	return (gain);
}



/* move gain pr. game hour */
int move_gain(CHAR_DATA *ch)
{
	int gain;
	
	if (IS_NPC(ch))
	{
		/* Neat and fast */
		gain = GET_LEVEL(ch);
	}
	else
	{
		gain = graf(age(ch)->year, 16, 20, 24, 20, 16, 12, 10);
		
		/* Class/Level calculations */
		
		/* Skill/Spell calculations */
		
		
		/* Position calculations    */
		switch (GET_POS(ch))
		{
		case POS_SLEEPING:
			gain += (gain / 2);	/* Divide by 2 */
			break;
		case POS_RESTING:
			gain += (gain / 4);	/* Divide by 4 */
			break;
		case POS_SITTING:
			gain += (gain / 8);	/* Divide by 8 */
			break;
		}
		
		if ((GET_COND(ch, FULL) == 0) || (GET_COND(ch, THIRST) == 0))
			gain /= 4;
	}
	
	if (AFF_FLAGGED(ch, AFF_POISON))
		gain /= 4;
	
	return (gain);
}



void set_title(CHAR_DATA *ch, char *title)
{
	if (title == NULL)
	{
		if (GET_SEX(ch) == SEX_FEMALE)
			title = title_female(GET_CLASS(ch), GET_LEVEL(ch));
		else
			title = title_male(GET_CLASS(ch), GET_LEVEL(ch));
	}
	
	if (strlen(title) > MAX_TITLE_LENGTH)
		title[MAX_TITLE_LENGTH] = '\0';
	
	if (GET_TITLE(ch) != NULL)
		free(GET_TITLE(ch));
	
	GET_TITLE(ch) = str_dup(title);
}


void check_autowiz(CHAR_DATA *ch)
{
#if defined(CIRCLE_UNIX) || defined(CIRCLE_WINDOWS)
  if (use_autowiz && GET_LEVEL(ch) >= LVL_IMMORT)
  {
    char buf[128];

#if defined(CIRCLE_UNIX)
    sprintf(buf, "nice ../bin/autowiz %d %s %d %s %d &", min_wizlist_lev,
	    WIZLIST_FILE, LVL_IMMORT, IMMLIST_FILE, (int) getpid());
#elif defined(CIRCLE_WINDOWS)
    sprintf(buf, "autowiz %d %s %d %s", min_wizlist_lev,
	    WIZLIST_FILE, LVL_IMMORT, IMMLIST_FILE);
#endif /* CIRCLE_WINDOWS */

    mudlog("Initiating autowiz.", CMP, LVL_IMMORT, FALSE);
    system(buf);
    reboot_wizlists();
  }
#endif /* CIRCLE_UNIX || CIRCLE_WINDOWS */
}


/* Killing same mobs XP gain limit function */

/* You can change formulas below if you wish */

#define GET_PERCENT(num, from_v) ((int)((float)(from_v) / 100 * (num)))

/* This formula is responsible for very slow increase of XP penalties   */
#define KILL_UNDER_MF_FORMULA(exp, num_of_kills)                        \
        (MAX(1, GET_PERCENT((101 - num_of_kills), exp)))

/* This formula is responsible for fast increase of XP penalties        */
#define KILL_ABOVE_MF_FORMULA(exp, num_of_kills)                        \
        (MAX(1, GET_PERCENT((float)100/((float)num_of_kills/2+1), exp)))

int kills_limit_xpgain(CHAR_DATA *ch, CHAR_DATA *victim, int exp)
{
	byte current_entry;
	int victim_vnum = GET_MOB_VNUM(victim);
	int new_exp;
	int i;
	
	/* Find corresponding to victim's vnum entry in array or create one */
	
	i = GET_KILLS_CURPOS(ch);
	while (i >= 1 && GET_KILLS_VNUM(ch, i) != victim_vnum)
		--i;

	if (!i)
	{
		if (!GET_KILLS_VNUM(ch, GET_KILLS_CURPOS(ch)))
			/* Array still isn't full - don't scan second part */
			i = GET_KILLS_CURPOS(ch);
		else
		{
			/* Array is full - must scan second part too */
			i = 100;
			while (i > GET_KILLS_CURPOS(ch) && GET_KILLS_VNUM(ch, i) != victim_vnum)
				--i;
		}
		if (i == GET_KILLS_CURPOS(ch))
		{
			/* We came back to search starting point: */
			/* New type of mob killed, add it to list */
			GET_KILLS_VNUM(ch, i)	= victim_vnum;
			GET_KILLS_AMOUNT(ch, i) = 0;
			if (GET_KILLS_CURPOS(ch) < 100)
				GET_KILLS_CURPOS(ch)++;
			else
				GET_KILLS_CURPOS(ch) = 1;
		}
	}
	current_entry = i;
	
	/* Ok, now we have current_entry, pointing to current mob's entry in array */
	/* Lets increment number of kills and reduce XP gain */
	
	if (GET_KILLS_AMOUNT(ch, current_entry) < 60000)
		++GET_KILLS_AMOUNT(ch, current_entry);
	
	if (GET_KILLS_AMOUNT(ch, current_entry) > GET_MOB_MAXFACTOR(victim))
	{
		new_exp = KILL_UNDER_MF_FORMULA(exp, GET_MOB_MAXFACTOR(victim));
		new_exp = KILL_ABOVE_MF_FORMULA(new_exp,
			(GET_KILLS_AMOUNT(ch, current_entry) - GET_MOB_MAXFACTOR(victim)));
	}
	else
	{
		if (GET_KILLS_AMOUNT(ch, current_entry) > 1)
			new_exp = KILL_UNDER_MF_FORMULA(exp, GET_KILLS_AMOUNT(ch, current_entry));
		else
			new_exp = exp; /* MOB was killed for first time, just give the EXP */
	}
	
	return (new_exp);
}


void gain_exp(CHAR_DATA *ch, int gain)
{
	int is_altered = FALSE;
	int num_levels = 0;
	char buf[128];
	
	if (!IS_NPC(ch) && ((GET_LEVEL(ch) < 1 || GET_LEVEL(ch) >= LVL_IMMORT - 1)))
		return;
	
	if (IS_NPC(ch))
	{
		GET_EXP(ch) += gain;
		return;
	}

	if (gain > 0)
	{
		gain = MIN(max_exp_gain, gain);	/* put a cap on the max gain per kill */
		GET_EXP(ch) += gain;
		while (GET_LEVEL(ch) < LVL_IMMORT &&
			GET_EXP(ch) >= tot_exp_to_level(GET_LEVEL(ch) + 1))
		{
			GET_LEVEL(ch) += 1;
			GET_TOT_LEVEL(ch) += 1;
			num_levels++;
			advance_level(ch);
			is_altered = TRUE;
		}
		
		if (is_altered)
		{
			sprintf(buf, "%s advanced %d level%s to level %d.",
				GET_NAME(ch), num_levels, num_levels == 1 ? "" : "s",
				GET_LEVEL(ch));
			mudlog(buf, BRF, MAX(LVL_IMMORT, GET_INVIS_LEV(ch)), TRUE);
			if (num_levels == 1)
				send_to_char("You rise a level!\r\n", ch);
			else
			{
				sprintf(buf, "You rise %d levels!\r\n", num_levels);
				send_to_char(buf, ch);
			}
			set_title(ch, NULL);
			check_autowiz(ch);
		}
	}
	else if (gain < 0)
	{
		gain = MAX(-max_exp_loss, gain);	/* Cap max exp lost per death */
		GET_EXP(ch) += gain;
		if (GET_EXP(ch) < 0)
			GET_EXP(ch) = 0;
	}
}


void gain_exp_regardless(CHAR_DATA *ch, int gain)
{
	int is_altered = FALSE;
	int num_levels = 0;
	
	GET_EXP(ch) += gain;
	if (GET_EXP(ch) < 0)
		GET_EXP(ch) = 0;
	
	if (!IS_NPC(ch))
	{
		while (GET_LEVEL(ch) < LVL_IMPL &&
			GET_EXP(ch) >= tot_exp_to_level(GET_LEVEL(ch) + 1))
		{
			GET_LEVEL(ch) += 1;
			GET_TOT_LEVEL(ch) += 1;
			num_levels++;
			advance_level(ch);
			is_altered = TRUE;
		}
		
		if (is_altered)
		{
			sprintf(buf, "%s advanced %d level%s to level %d.",
				GET_NAME(ch), num_levels, num_levels == 1 ? "" : "s",
				GET_LEVEL(ch));
			mudlog(buf, BRF, MAX(LVL_IMMORT, GET_INVIS_LEV(ch)), TRUE);
			if (num_levels == 1)
				send_to_char("You rise a level!\r\n", ch);
			else
			{
				sprintf(buf, "You rise %d levels!\r\n", num_levels);
				send_to_char(buf, ch);
			}
			set_title(ch, NULL);
			check_autowiz(ch);
		}
	}
}


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

  if (IS_NPC(ch) || 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) || PLR_FLAGGED(ch, PLR_WRITING))
    return;

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

}


void check_idling(CHAR_DATA *ch)
{
	if (++(ch->player.timer) > idle_void)
	{
		if (GET_WAS_IN(ch) == NULL && ch->in_room != NULL)
		{
			GET_WAS_IN(ch) = ch->in_room;
			if (FIGHTING(ch)) 
			{
				stop_fighting(FIGHTING(ch));
				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.\r\n", ch);
			save_char(ch, NULL);
			SaveCharObj(ch, FALSE);
			char_from_room(ch);
			char_to_room(ch, get_room(1));
		}
		else if (ch->player.timer > idle_rent_time)
		{
			if (ch->in_room != NULL)
				char_from_room(ch);
			char_to_room(ch, get_room(3));
			if (ch->desc)
			{
				STATE(ch->desc) = CON_DISCONNECT;
				/*
				 * For the 'if (d->character)' test in close_socket().
				 * -gg 3/1/98 (Happy anniversary.)
				 */
				ch->desc->character = NULL;
				ch->desc = NULL;
			}
			save_char(ch, NULL);
			SaveCharObj(ch, TRUE);
			sprintf(buf, "%s force-rented and extracted (idle).", GET_NAME(ch));
			mudlog(buf, CMP, LVL_GOD, TRUE);
			extract_char(ch);
		}
	}
}



/* Update PCs, NPCs, and objects */
void point_update(void)
{
	CHAR_DATA *i, *next_char;
	OBJ_DATA *j, *next_thing, *jj, *next_thing2;
	
	/* characters */
	for (i = character_list; i; i = next_char)
	{
		next_char = i->next;
		
		gain_condition(i, FULL, -1);
		gain_condition(i, DRUNK, -1);
		gain_condition(i, THIRST, -1);
		
		if (GET_POS(i) >= POS_STUNNED)
		{
			GET_HIT(i)	= MIN(GET_HIT(i) + hit_gain(i), GET_MAX_HIT(i));
			GET_MANA(i) = MIN(GET_MANA(i) + mana_gain(i), GET_MAX_MANA(i));
			GET_MOVE(i) = MIN(GET_MOVE(i) + move_gain(i), GET_MAX_MOVE(i));

			if (AFF_FLAGGED(i, AFF_POISON))
			{
				if (damage(i, i, 2, SPELL_POISON) == -1)
					continue;	/* Oops, they died. -gg 6/24/98 */
			}

			if (GET_POS(i) <= POS_STUNNED)
				update_pos(i);
		}
		else if (GET_POS(i) == POS_INCAP)
		{
			if (damage(i, i, 1, TYPE_SUFFERING) == -1)
				continue;
		}
		else if (GET_POS(i) == POS_MORTALLYW)
		{
			if (damage(i, i, 2, TYPE_SUFFERING) == -1)
				continue;
		}
		if (!IS_NPC(i))
		{
			update_char_objects(i);
			if (GET_LEVEL(i) < idle_max_level)
				check_idling(i);
		}
	}
	
	/* objects */
	for (j = first_object; j; j = next_thing)
	{
		next_thing = j->next;	/* Next in object list */
		
		/* If this is a corpse */
		if (IS_CORPSE(j))
		{
			/* timer count down */
			if (GET_OBJ_TIMER(j) > 0)
				GET_OBJ_TIMER(j)--;
			
			if (!GET_OBJ_TIMER(j))
			{
				if (j->carried_by)
					act("$p decays in your hands.", FALSE, j->carried_by, j, 0, TO_CHAR);
				else if ((j->in_room != NULL) && (j->in_room->people))
				{
					act("A quivering horde of maggots consumes $p.",
						TRUE, j->in_room->people, j, 0, TO_ROOM);
					act("A quivering horde of maggots consumes $p.",
						TRUE, j->in_room->people, j, 0, TO_CHAR);
				}
				for (jj = j->first_content; jj; jj = next_thing2)
				{
					next_thing2 = jj->next_content;	/* Next in inventory */
					obj_from_obj(jj);
					
					if (j->in_obj)
						jj = obj_to_obj(jj, j->in_obj);
					else if (j->carried_by)
						jj = obj_to_room(jj, j->carried_by->in_room);
					else if (j->in_room)
						jj = obj_to_room(jj, j->in_room);
					else
						core_dump();
				}
				extract_obj(j);
			}
		}

		if (GET_OBJ_TYPE(j) == ITEM_GOODS && GET_OBJ_TIMER(j) != -1)
		{
			// TODO - handle goods deterioration...
		}
		
		if ( get_real_obj_weight(j) > 0 && (j->in_room != NULL) && water_sector(SECT(j->in_room)) )
		{
			if (j->in_room->people)
			{
				act("$p sinks into the watery depths.", FALSE, j->in_room->people, j, 0, TO_ROOM);
				act("$p sinks into the watery depths.", FALSE, j->in_room->people, j, 0, TO_CHAR);
			}
			extract_obj(j);
		}
	}
}