/* ************************************************************************
*  file: pulse.c , Things that happen regularly           Part of DIKUMUD *
*  Usage: Procedures controling gain and limit.          (orig. limits.c) *
*  Copyright (C) 1990, 1991 - see 'license.doc' for complete information. *
************************************************************************* */

#include CONFIG

#if HAVE_STRINGS_H

#include <strings.h>
#endif

#if HAVE_STRING_H
#include <string.h>
#endif

#include "structs.h"
#include "utils.h"
#include "skills.h"
#include "comm.h"
#include "db.h"
#include "event.h"
#include "error.h"
#include "proto.h"

extern struct char_data *character_list;
extern struct descriptor_data *descriptor_list;
extern struct obj_data *object_list;
extern struct room_data *world;
extern struct zone_data *zone_table;
extern char *spell_wear_off_msg[];

/* 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);



/* 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;

    max = (100);  /* + (graf(age(ch).year, 0,0,10,30,50,70,60)); */
    max = MAX(ch->physical->max_mana, max);
    return(max);
}


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

    max = (ch->physical->max_hit) +
	   (graf(age(ch).year, 2,4,17,14,8,4,3));


    /* Class/Level calculations */

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


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

    /* HERE SHOULD BE CON CALCULATIONS INSTEAD */
    max = graf(age(ch).year, 50,70,160,120,100,40,20);
    max = MAX(ch->physical->max_move, max);

    /* Class/Level calculations */

    /* Skill/Spell calculations */

    return (max);
}




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

    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 POS_SLEEP:
	    gain += gain;
	    break;
	case POS_REST:
	    gain+= (gain>>1);  /* Divide by 2 */
	    break;
	case POS_SIT:
	    gain += (gain>>2); /* Divide by 4 */
	    break;
    }

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

    if(!ch->physical->hunger || ch->physical->thirst)
	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;

    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 POS_SLEEP:
	    gain += (gain>>1); /* Divide by 2 */
	    break;
	case POS_REST:
	    gain+= (gain>>2);  /* Divide by 4 */
	    break;
	case POS_SIT:
	    gain += (gain>>3); /* Divide by 8 */
	    break;
    }

/*    gain >>= 1;*/
/*
    if (IS_AFFECTED(ch,AFF_POISON)) {
	gain >>= 2;
        damage(ch,ch,2,SKILL_POISON);
    }*/

    /* Weather affects hit points */
    if(!(world[ch->in_room].room_flags & INDOORS)) {
	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) {
            add_event_char(ch,EVENT_2COLD,0,ID_NOBODY,ID_NOBODY);
/*	    damage(ch,ch,-temp_dam/4,DAMAGE_FROSTBITE);*/
	} else if(temp_dam > 50) {
            add_event_char(ch,EVENT_2HOT,0,ID_NOBODY,ID_NOBODY);
/*	    damage(ch,ch,(temp_dam-50)/4+1,DAMAGE_HEATSTROKE);*/
        }
    }

    if(!ch->physical->hunger || !ch->physical->thirst)
	gain >>= 2;


    return (gain);
}



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

    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 POS_SLEEP:
	    gain += gain+2;
	    break;
	case POS_REST:
	    gain+= (gain>>1)+1;
	    break;
	case POS_SIT:
	    gain += (gain>>2);
	    break;
    }
/*
    if (IS_AFFECTED(ch,AFF_POISON))
	gain >>= 2;*/

    if(!ch->physical->hunger || ch->physical->thirst)
	gain >>= 2;

    return (gain);
}



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

    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;
    }

    ch->physical->max_hit += MAX(1, add_hp);
    if ((GET_LEVEL(ch) != 1) && (ch->physical->max_mana < 160)) {
 	   ch->physical->max_mana = ch->physical->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));
*/
}


void set_title(struct char_data *ch)
{
    char *def_title="the personage";
/*	if (GET_TITLE(ch))
	RECREATE(GET_TITLE(ch),char,strlen(READ_TITLE(ch))+1);
    else
	CREATE(GET_TITLE(ch),char,strlen(READ_TITLE(ch)));
*/
    if (GET_TITLE(ch))
	RECREATE(GET_TITLE(ch),char,strlen(def_title)+1);
    else
	CREATE(GET_TITLE(ch),char,strlen(def_title));
    strcpy(GET_TITLE(ch), def_title);
}



void gain_exp(struct char_data *ch, int gain)
{
    if (gain > 0 && GET_EXP(ch)<1000000) {

	GET_EXP(ch) += gain;
    }

    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;
    }
}


void gain_exp_regardless(struct char_data *ch, int gain)
{

    if (gain > 0) {
	GET_EXP(ch) += gain;
    }
    if (gain < 0) 
	GET_EXP(ch) += gain;
    if (GET_EXP(ch) < 0)
	GET_EXP(ch) = 0;
}

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

    switch(condition) {
        case DRUNK:
		newval = ch->physical->impairment;
                break;
        case FULL:
		newval = ch->physical->hunger;
                break;
        case THIRST:
		newval = ch->physical->thirst;
                break;
        case SLEEP:
		newval = ch->physical->exhaustion;
                break;
        default:
                log("BUG: Bad call to gain_condition");
		return;
    }


    if(newval==-1) /* No change */
	return;

    intoxicated=(ch->physical->impairment > 0);

    newval  += value;

    newval= MAX(0,newval);
    newval= MIN(24,newval);

    switch(condition){
	case FULL :
	    if(!(ch->physical->hunger=newval)) {
	        send_to_char("You are hungry.\n\r",ch);
                add_event_char(ch,EVENT_HUNGER,0,ID_NOBODY,ID_NOBODY);
            }
            break;
	case THIRST :
	    if(!(ch->physical->thirst=newval)) {
	        send_to_char("You are thirsty.\n\r",ch);
                add_event_char(ch,EVENT_THIRST,0,ID_NOBODY,ID_NOBODY);
            }
            break;
	case DRUNK :
	    if(!(ch->physical->impairment=newval) &&(intoxicated))
	        send_to_char("You are now sober.\n\r",ch);
            break;
        case SLEEP :
	    if(!(ch->physical->exhaustion=newval) && GET_POS(ch)!=POS_SLEEP) {
                send_to_char("You collapse from exhaustion!\n",ch);
                act("$n collapses from exhaustion!",TRUE,ch,0,0,TO_ROOM);
                GET_POS(ch)=POS_SLEEP;
	    } else if(ch->physical->exhaustion==1)
                send_to_char("You are absolutely exhausted.\n",ch);
            else if(ch->physical->exhaustion<4) {
                send_to_char("You are getting sleepy.\n",ch);
                /* we should be more exact about this event */
                add_event_char(ch,EVENT_SLEEPY,0,ID_NOBODY,ID_NOBODY);
            } else if(ch->physical->exhaustion>8 && GET_POS(ch)==POS_SLEEP) {
                send_to_char("You wake up naturally.\n",ch);
                act("$n blinks $s eyes and wakes up.",FALSE,ch,0,0,TO_ROOM);
                GET_POS(ch)=POS_REST;
                add_event_char(ch,EVENT_WAKE,0,ch->id,ID_NOBODY);
            }
            break;
	default :
            break;
    }

}


void check_idling()
{
    struct descriptor_data *d;
    struct char_data *ch;

    for(d=descriptor_list;d;d=d->next) {
        if (++d->idle > 32)
        {
            ch = d->character;
            close_socket(d);

            if(ch) {
                save_char(ch);
                extract_char_eq(ch);
                extract_char(ch);
            }
        } else if(d->idle > 25 && d->character) {
            switch(number(0,4)) {
                case 0:
                    send_to_char("Wake up, bub!\n\r",d->character);
                    break;
                case 1:
                    send_to_char("No loitering!\n\r",d->character);
                    break;
                case 2:
                    send_to_char("You feel idle.\n\r",d->character);
                    break;
                case 3:
                    send_to_char("Anybody home?\n\r",d->character);
                    break;
                case 4:
                    send_to_char("You are idle, miscreant!\n\r",d->character);
                    break;
            }
        } else if(d->idle > 10 && d->connected!=CON_PLYNG) {
            close_socket(d);
        } else if(d->idle > 2 && d->connected==CON_NME) {
            close_socket(d);
        } else if(d->idle > 4 && d->connected==CON_PLYNG && d->character) {
            if(number(0,20) > GET_LUCK(d->character)) {
                /* Play with them a bit... */
                /*(make encounter...)*/
            }
        }
    }
}





/* Update both PC's & NPC's and objects*/
void point_update( void )
{	
    struct char_data *i, *next_dude;
    struct obj_data *j, *next_thing/*, *jj, *next_thing2*/;
    struct obj_info *oi,*next_oi;
    struct obj_info_light *light;
    struct obj_info_container *cont;

    /* characters */
    for (i = character_list; i; i = next_dude) {
	next_dude = i->next;
	if (GET_POS(i) > POS_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) == POS_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) == POS_INCAP)
	    damage(i, i, 1, TYPE_SUFFERING);
	else if (GET_POS(i) == POS_MORTALLYW)
	    damage(i, i, 2, TYPE_SUFFERING);*/
	gain_condition(i,FULL,-1);
	gain_condition(i,DRUNK,-1);
	gain_condition(i,THIRST,-1);
        if(GET_POS(i) == POS_SLEEP)
            gain_condition(i,SLEEP,4);
        else if(GET_POS(i) == POS_REST)
            gain_condition(i,SLEEP,1);
        else
	    gain_condition(i,SLEEP,-1);
    } /* for */

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

        for(oi=j->info;oi && j;oi=next_oi) {
            next_oi = oi->next;
            switch(oi->obj_type) {
                case ITEM_CONTAINER:
                cont=(struct obj_info_container *)oi;
            	/* If this is a corpse */
            	if(cont->lock_state & CONT_CORPSE) {
            	    /* timer count down */
#if 0
                    < how to do this?? >
            	    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);
            		}
            		extract_obj(j);
            		j=NULL; /* so I don't need to use a goto... */
            	    }
#endif
            	}
            	break;
                
                case ITEM_LIGHT:
                light=(struct obj_info_light *)oi;
            	if(light->brightness>0) {
            	    if(light->hours_left>0) {
            		if(!--light->hours_left) {
            		    off_light(j);
            		}
            	    }
            	}
            	break;
            }
        }
    }
}

void affect_update( void )
{
    static struct affected_type *af, *next_af_dude;
    static struct char_data *i;

    for (i = character_list; i; i = i->next) {
        for (af = i->affected; af; af = next_af_dude) {
            next_af_dude = af->next;
            if (af->duration >= 1)
                af->duration--;
            else if (af->duration == -1)
                /* No action */
                af->duration = -1;  /* GODs only! unlimited */
            else {
                if ((af->type > 0) && (af->type <= 52)) /* It must be a spell */
                    if (!af->next || (af->next->type != af->type) ||
                      (af->next->duration > 0))
                        if (*spell_wear_off_msg[af->type]) {
                            send_to_char(spell_wear_off_msg[af->type], i);
                            send_to_char("\n\r", i);
                        }

                affect_remove(i, af);
            }
        }
/*
        if((GET_POS(i)==POS_FLY && !IS_AFFECTED(i,AFF_FLY))
                || (GET_POS(i)==POS_LEVITATE &&
                !IS_AFFECTED(i,AFF_FLY) &&
                !IS_AFFECTED(i,AFF_LEVITATE))) {

            GET_POS(i)=POS_STAND;
            if(world[i->in_room].sector_type==SECT_NO_GROUND) {
                check_fall(i);
            }
        }*/
    }
}

void char_pulse(int pulse)
{
    struct char_data *i,*next,*prev=NULL;
    struct obj_data *o;

    for(i=character_list;i;i=next) {
        next = i->next;

        /* This updates values for everybody in the character list which */
        /* control their ability to "speak" without overloading the system */
        if(i->specials.ovl_count > 0) {
            if(++(i->specials.ovl_timer) > OVL_PULSE) {
                i->specials.ovl_timer = 0;
                i->specials.ovl_count -= OVL_LIMIT;
            }
        }

        check_fall(i);

        if(i->specials.act & ACT_CLEANUP) {
            while(i->carrying) {
                o=i->carrying;
                if(o->equipped_as != UNEQUIPPED)
                    unequip_char(i,o);
                extract_obj(o);
            }
            char_from_room(i); /* Fetch from the void */
            if(!prev)
                character_list = i->next;
            else
                prev->next = i->next;
            free_char(i);
            continue;
        } else {
            prev=i;
        }
    }
}