/***************************************************************************
 *  Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer,        *
 *  Michael Seifert, Hans Henrik Strfeldt, Tom Madsen, and Katja Nyboe.    *
 *                                                                         *
 *  Merc Diku Mud improvments copyright (C) 1992, 1993 by Michael          *
 *  Chastain, Michael Quan, and Mitchell Tse.                              *
 *                                                                         *
 *  In order to use any part of this Merc Diku Mud, you must comply with   *
 *  both the original Diku license in 'license.doc' as well the Merc       *
 *  license in 'license.txt'.  In particular, you may not remove either of *
 *  these copyright notices.                                               *
 *                                                                         *
 *  Much time and thought has gone into this software and you are          *
 *  benefitting.  We hope that you share your changes too.  What goes      *
 *  around, comes around.                                                  *
 ***************************************************************************/

/***************************************************************************
 *  ROM 2.4 is copyright 1993-1998 Russ Taylor                             *
 *  ROM has been brought to you by the ROM consortium                      *
 *      Russ Taylor (rtaylor@hypercube.org)                                *
 *      Gabrielle Taylor (gtaylor@hypercube.org)                           *
 *      Brian Moore (zump@rom.org)                                         *
 *  By using this code, you have agreed to follow the terms of the         *
 *  ROM license, in the file Rom24/doc/rom.license                         *
 ***************************************************************************/

// DragonBall Arena 2 has been written by:
//   Matt Brown (Antor), arkaine@sympatico.ca, 2000-2002
// Please follow all previous licenses. Enjoy!

#include <sys/types.h>
#include <sys/time.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include "merc.h"
#include "interp.h"
#include "recycle.h"

// Locals
void          UpdateSkills    args ( ( CHAR_DATA *pCh ) );
long long int ApplyMisc (CHAR_DATA *pCh, long long int llValue);
long long int ApplyCharge (CHAR_DATA *pCh, long long int llValue, int nSn);

/*
 * Lookup a skill by name.
 */
int skill_lookup (const char *name)
{
    int sn;

    for (sn = 0; sn < MAX_SKILL; sn++)
    {
        if (skill_table[sn].name == NULL)
            break;
        if (LOWER (name[0]) == LOWER (skill_table[sn].name[0])
            && !str_prefix (name, skill_table[sn].name))
            return sn;
    }

    return -1;
}

/* skill_driver:
 * Does a skill. Checks for targets, shows messages, performs the skill.
 * Returns true if the character completes the skill.
 */
bool skill_driver (CHAR_DATA * ch, char *argument, int sn)
{
    char arg1[MAX_INPUT_LENGTH];
    char arg2[MAX_INPUT_LENGTH];
    void *vo;
    int target, sn_target;
    CHAR_DATA *victim;
    OBJ_DATA *obj;

	if (sn < 1)
		return FALSE;

    if (ch->position < skill_table[sn].minimum_position)
    {
        sendch ("You can't do that in your current position.\n\r", ch);
        return FALSE;
    }


	if (get_skill(ch, sn) < 1) {
		sendch ("What?\n\r", ch);
		return FALSE;
	}

    argument = one_argument (argument, arg1);
    one_argument (argument, arg2);

    // Switch them if needed
    if (!str_cmp(arg1, "release")) {
        char t[MAX_INPUT_LENGTH];
        sprintf(t, arg2);
        sprintf(arg2, arg1);
        sprintf(arg1, t);
    }
    
    /*
     * Locate targets.
     */
    victim = NULL;
    obj = NULL;
    vo = NULL;
    target = TARGET_NONE;

	sn_target = skill_table[sn].target;
	// Look for skills whose targets switch at certain skill levels
	if (sn_target == TAR_HYBRID100) {
		if (get_skill(ch, sn) > 99 && !str_cmp(arg1, "all"))
			sn_target = TAR_AREA_OFF;
		else
			sn_target = TAR_CHAR_OFFENSIVE;
	}

    switch (sn_target)
    {
        default:
            logstr (LOG_BUG, "skill_driver: bad target for sn %d.", sn);
            return FALSE;

        case TAR_IGNORE:
        case TAR_AREA_OFF:
			vo = NULL;
			target = TARGET_NONE;
            break;

        case TAR_CHAR_OFFENSIVE:
            if (arg1[0] == '\0')
            {
                if ((victim = ch->fighting) == NULL)
                {
                    sendch ("Direct that at whom?\n\r", ch);
                    return FALSE;
                }
            }
            else
            {
                if ((victim = get_char_room (ch, NULL, arg1)) == NULL)
                {
                    sendch ("They aren't here.\n\r", ch);
                    return FALSE;
                }
            }

			if (ch == victim)
			{
				sendch( "You can't do that to yourself.\n\r", ch );
				return FALSE;
			}

            if (IS_NPC (victim) && victim->fighting != NULL && !is_same_group (ch, victim->fighting)) {
                sendch ("Kill stealing is not permitted.\n\r", ch);
                return FALSE;
            }

            if (!IS_NPC (ch))
            {

                if (is_safe (ch, victim) && victim != ch)
                {
                    sendch ("Not on that target.\n\r", ch);
                    return FALSE;
                }
                check_killer (ch, victim);
            }

            if (IS_AFFECTED (ch, AFF_CHARM) && ch->master == victim)
            {
                sendch ("You can't do that on your own follower.\n\r", ch);
                return FALSE;
            }
            vo = (void *) victim;
            target = TARGET_CHAR;
            break;

        case TAR_CHAR_DEFENSIVE:
            if (arg1[0] == '\0')
            {
                victim = ch;
            }
            else
            {
                if ((victim = get_char_room (ch, NULL, arg1)) == NULL)
                {
                    sendch ("They aren't here.\n\r", ch);
                    return FALSE;
                }
            }
			vo = (void *) victim;
            target = TARGET_CHAR;
            break;

        case TAR_CHAR_SELF:
            if (arg1[0] != '\0' && !is_name (arg1, ch->name))
            {
                sendch ("You cannot do that on another.\n\r", ch);
                return FALSE;
            }
    		vo = (void *) ch;
            target = TARGET_CHAR;
            break;

        case TAR_OBJ_INV:
            if (arg1[0] == '\0')
            {
                sendch ("What object should that be done on?\n\r", ch);
                return FALSE;
            }

            if ((obj = get_obj_carry (ch, arg1, ch)) == NULL)
            {
                sendch ("You are not carrying that.\n\r", ch);
                return FALSE;
            }
			vo = (void *) obj;
            target = TARGET_OBJ;
            break;

        case TAR_OBJ_CHAR_OFF:
            if (arg1[0] == '\0')
            {
                if ((victim = ch->fighting) == NULL)
                {
                    sendch ("Do that on whom or what?\n\r", ch);
                    return FALSE;
                }

                target = TARGET_CHAR;
            }
            else if ((victim = get_char_room (ch, NULL, arg1)) != NULL)
            {
                target = TARGET_CHAR;
            }

            if (target == TARGET_CHAR)
            {                    /* check the sanity of the attack */
                if (is_safe_spell (ch, victim, FALSE) && victim != ch)
                {
                    sendch ("Not on that target.\n\r", ch);
                    return FALSE;
                }

                if (IS_AFFECTED (ch, AFF_CHARM) && ch->master == victim)
                {
                    sendch ("You can't do that on your own follower.\n\r", ch);
                    return FALSE;

                }

                if (!IS_NPC (ch))
                    check_killer (ch, victim);
				vo = (void *) victim;
            }
            else if ((obj = get_obj_here (ch, NULL, arg1)) != NULL)
            {
				vo = (void *) obj;
                target = TARGET_OBJ;
            }
            else
            {
                sendch ("You don't see that here.\n\r", ch);
                return FALSE;
            }
            break;

        case TAR_OBJ_CHAR_DEF:
            if (arg1[0] == '\0')
            {
				vo = (void *) ch;
                target = TARGET_CHAR;
            }
            else if ((victim = get_char_room (ch, NULL, arg1)) != NULL)
            {
				vo = (void *) victim;
                target = TARGET_CHAR;
            }
            else if ((obj = get_obj_carry (ch, arg1, ch)) != NULL)
            {

				vo = (void *) obj;
                target = TARGET_OBJ;
            }
            else
            {
                sendch ("You don't see that here.\n\r", ch);
                return FALSE;
            }
            break;
    }

    if (IS_EXHAUSTED(ch)) {
        sendch ("You're too exhausted.\n\r", ch);
        return FALSE;
    }

    // Hard coded checks for skills
    if (sn == gsn_hyperpunch) {
        if (get_eq_char(ch, WEAR_WIELD) != NULL) {
            sendch("You cannot hyperpunch with a weapon.\n\r", ch);
            return FALSE;
        }
    }
    else if (sn == gsn_energy_slash) {
        if (get_eq_char(ch, WEAR_WIELD) == NULL) {
            sendch("You need a weapon to energy slash with.\n\r", ch);
            return FALSE;
        }
    }

    // Use a ki_loss value based on the skill

    // Ki loss.
    // If its a charge type skill, make the initial loss much greater
    if (skill_table[sn].type == SKILL_CHARGE)
        ki_loss(ch, skill_table[sn].ki_mod*5);
    else
        ki_loss(ch, skill_table[sn].ki_mod);

    // Message if the skill is started
	if (skill_table[sn].msg_immediate1)
		act(skill_table[sn].msg_immediate1,ch,NULL,NULL,TO_CHAR);
	if (skill_table[sn].msg_immediate2)
		act(skill_table[sn].msg_immediate2,ch,NULL,NULL,TO_ROOM);

	// Make the char wait, and get the skill ready to fire (or fire it)
	if (skill_table[sn].type == SKILL_DELAY) {
		ch->wait_skill = skill_table[sn].wait;
		ch->wait_skill_sn = sn;
		ch->wait_skill_vo = vo;
		ch->wait_skill_target = target;
	}
	else if (skill_table[sn].type == SKILL_IMM) {
		wait (ch, skill_table[sn].wait);
		(*skill_table[sn].skill_fun) (ch, vo, target);
	}
    else if (skill_table[sn].type == SKILL_CHARGE) {
        ch->wait_skill = 0;
        ch->charge = 1;
        ch->wait_skill_sn = sn;
        ch->wait_skill_vo = vo;
        ch->wait_skill_target = target;
    }

    if ((sn_target == TAR_CHAR_OFFENSIVE
         || (sn_target == TAR_OBJ_CHAR_OFF
             && target == TARGET_CHAR))
	    && victim != ch
        && victim->master != ch
        && victim->fighting == NULL
        && IS_AWAKE(victim)
        && !IS_AFFECTED (victim, AFF_CALM) ) {
        check_killer (victim, ch);
        begin_combat (victim, ch);
    }
    else if (sn_target == TAR_AREA_OFF) {
		CHAR_DATA *vch;

        for (vch = ch->in_room->people; vch; vch = vch->next_in_room) {
            if (IS_NPC(vch)
                && vch->fighting == NULL
                && !IS_AFFECTED (vch, AFF_CALM)
                && !IS_AFFECTED (vch, AFF_CHARM)
                && IS_AWAKE (vch)
                && !IS_SET (vch->act, ACT_WIMPY)
                && !is_same_group(vch, ch)
                && can_see (vch, ch) ) {
                check_killer (vch, ch);
                begin_combat (vch, ch);
                break;
            }
        }
    }

    // Immediate "charged" attack
    if (skill_table[sn].type == SKILL_CHARGE &&
        (!str_cmp(arg1, "release") || !str_cmp(arg2, "release")) ) {
        // Pulled from do_release
        act("You release!",ch,NULL,NULL,TO_CHAR);
        act("$n releases!",ch,NULL,NULL,TO_ROOM);

        if (skill_table[ch->wait_skill_sn].msg_delay1)
            act(skill_table[ch->wait_skill_sn].msg_delay1,ch,NULL,NULL,TO_CHAR);
        if (skill_table[ch->wait_skill_sn].msg_delay2)
            act(skill_table[ch->wait_skill_sn].msg_delay2,ch,NULL,NULL,TO_ROOM);

        if (skill_table[ch->wait_skill_sn].skill_fun)
            (*skill_table[ch->wait_skill_sn].skill_fun) (ch, ch->wait_skill_vo, ch->wait_skill_target);

        ch->charge = 0;
        ch->wait_skill_sn = 0;
        ch->wait_skill_vo = NULL;
        ch->wait_skill_target = 0;
        wait (ch, 3 * PULSE_SECOND);
    }

    return TRUE;
}


void do_skills (CHAR_DATA * ch, char *argument) {
    BUFFER *buffer;
    char buf[MAX_STRING_LENGTH],
         buf2[MAX_STRING_LENGTH],
		 buf3[MAX_STRING_LENGTH];
	char buf_temp[MAX_STRING_LENGTH];
    bool found=FALSE, bColumn=TRUE, bOtherFound=FALSE;
    int i;

    if (IS_NPC (ch))
        return;

    sprintf(buf, "Statistics:\n\r  ");
    for (i = 0; i < MAX_STATS; i++) {
        if (ch->perm_stat[i] > 0) {
			sprintf (buf_temp, "%-20s %3d.%-2.2d          ", stat_table[i], ch->perm_stat[i],
				     100*ch->pcdata->nStatProgress[i]/(ch->perm_stat[i]*3 + 1800));
			strcat (buf, buf_temp);
			if ((bColumn=!bColumn))
				strcat (buf, "\n\r  ");
        }
    }
	if (!bColumn)
		strcat (buf, "\n\r");

    sprintf(buf2, "\n\rSkills:\n\r  ");
    bColumn = TRUE;
    for (i = 0; i < MAX_SKILL; i++) {
        if (skill_table[i].name == NULL)
            break;
        if (ch->pcdata->learned[i] > 0 && skill_table[i].bCanImprove) {
            found = TRUE;
			sprintf (buf_temp, "%-20s %3d.%-2.2d         ", skill_table[i].name, ch->pcdata->learned[i],
				     100*ch->pcdata->nSkillProgress[i]/(3240 + ch->pcdata->learned[i]*36));
			strcat (buf2, buf_temp);
			if ((bColumn=!bColumn))
				strcat (buf2, "\n\r  ");
        }
    }
    if (!found)
        sprintf (buf2, "\n\rSkills:\n\r  No skills found.\n\r");
    else if (!bColumn)
	    strcat (buf2, "\n\r");

    // Other skills that dont show any skill level
	sprintf(buf3, "\n\rOther Skills:\n\r  ");
    bColumn = TRUE;
    for (i = 0; i < MAX_SKILL; i++) {
        if (skill_table[i].name == NULL)
            break;
        if (ch->pcdata->learned[i] > 0 && !skill_table[i].bCanImprove) {
            bOtherFound = TRUE;
			sprintf (buf_temp, "%-20s                 ", skill_table[i].name);
			strcat (buf3, buf_temp);
			if ((bColumn=!bColumn))
				strcat (buf3, "\n\r  ");
        }
    }
    if (!bColumn)    
	    strcat (buf3, "\n\r");

    buffer = new_buf ();
	add_buf (buffer, buf);
	if (bOtherFound && !found)
		add_buf (buffer, buf3);
	else {
	    add_buf (buffer, buf2);
		if (bOtherFound)
           add_buf (buffer, buf3);
	}
	page_to_char (buf_string (buffer), ch);
    free_buf (buffer);
}


// If the multiplier is negative, its easier.
// For example, nMultiplier =  5 : 5 times harder to learn
//              nMultiplier = -4 : 4 times easier to learn
void ImproveSkill (CHAR_DATA *pCh, int nSn, bool bSuccess, float nMultiplier, int nViDifficulty) {
    char szBuf[MAX_STRING_LENGTH];
	int nGain, nNext;

    if (IS_NPC (pCh))
        return;

	if (nSn < 0
		|| nSn >= MAX_SKILL
        || pCh->pcdata->learned[nSn] <= 0
		|| !skill_table[nSn].bCanImprove
        || !skill_table[nSn].bCanLearn)
        return;
        
    if (nViDifficulty < pCh->nDifficulty / 4)
        return;

    // could all the counts (teach, train, chaos) be added in here.
    // pass a flag in ie IMPROVE_PENALTY_TEACH and automatically
    // increment the necessary counter?
    if (IS_SET(pCh->in_room->room_flags, ROOM_CHAOS) && pCh->fighting) {
        nMultiplier += pCh->pcdata->nChaosCount / 70;
        ++pCh->pcdata->nChaosCount;
    }

    // This does not increase linearally, since its based on int. As
    // int increases, skills have to become harder to learn (so, it
    // actually seems linear)
    // The magic number is 360, which is the number of 5 second actions
    // to make up half an hour.  We're aiming for a skill to go up every
    // half an hour.  So, nNext / nGain = 360. Also, gains are based on
    // intelligence, so nNext must increase to offest increasing
    // intelligence.  Since stats are equal to skills in level, if
    // intelligence is equal to the skill, nNext / nGain will equal 360.
    nNext = 3240 + pCh->pcdata->learned[nSn]*36;

    nGain = 9 + get_curr_stat (pCh, STAT_INT) / 10;
	if (nMultiplier > 0)
		nGain = UMAX(1, nGain / nMultiplier); // Don't want it to totally destroy any gains
	else
		nGain *= -1 * nMultiplier;

    // Change the gain based on relative difficulties
	//nGain = URANGE(nGain / 3, nViDifficulty * nGain / pCh->nDifficulty, nGain * 2);
    // Change the gain based on current power use
	nGain = UMAX(nGain / 10, pCh->nCurPl * nGain / 100);
	// Make all skills increase at the same rate, regardless of speed
    nGain = (nGain * skill_table[nSn].wait) / (5 * PULSE_SECOND);
    // Limit the gain
    nGain = UMIN(nGain, nNext / 50);
    // Get a reward?
    if (pCh->pcdata->nReward > 0)
        nGain *= 2;

    if (pCh->pcdata->nSkillTick[nSn] + nGain > (1800 * PULSE_SECOND * nNext) / PULSE_TICK) {
        nGain = ((1800 * PULSE_SECOND * nNext) / PULSE_TICK) - pCh->pcdata->nSkillTick[nSn];
        pCh->pcdata->nSkillTick[nSn] = (1800 * PULSE_SECOND * nNext) / PULSE_TICK;
    }

	pCh->pcdata->nSkillProgress[nSn] += nGain;

	if (pCh->pcdata->nSkillProgress[nSn] < nNext)
		return;

	if (bSuccess)
        sprintf (szBuf, "{CHoning your skills, your %s improves!{x\n\r", skill_table[nSn].name);
    else
        sprintf (szBuf, "{CThrough trial and error, your %s improves!{x\n\r", skill_table[nSn].name);
    sendch (szBuf, pCh);
    pCh->pcdata->learned[nSn]++;
	pCh->pcdata->nSkillProgress[nSn] = 0;
	ResetDiff(pCh);
    UpdateSkills (pCh);
    return;
}

// If the multiplier is negative, its easier.
// For example, nMultiplier =  5 : 5 times harder to learn
//              nMultiplier = -4 : 4 times easier to learn
void ImproveStat (CHAR_DATA *pCh, int nStat, bool bSuccess, float nMultiplier, int nViDifficulty) {
    char szBuf[MAX_STRING_LENGTH];
	int nGain, nNext;

    if (IS_NPC (pCh))
        return;

	if (nStat < 0
		|| nStat >= MAX_STATS)
        return;
    
    if (nViDifficulty < pCh->nDifficulty / 4)
        return;

    if (IS_SET(pCh->in_room->room_flags, ROOM_CHAOS) && pCh->fighting) {
        nMultiplier += pCh->pcdata->nChaosCount / 70;
        ++pCh->pcdata->nChaosCount;
    }

	// This increases almost linearally, since int doesn't effect it.
    // Skills, on the other hand, increase based on int, so they have to
    // become harder to offset increasing int
    nNext = pCh->perm_stat[nStat]*3 + 1800;

    nGain = pc_race_table[pCh->race].stat_gain[nStat];
	if (nMultiplier > 0)
		nGain = UMAX(1, nGain / nMultiplier); // Don't want it to totally destroy any gains
	else
		nGain *= -1 * nMultiplier;


    // Change the gain based on relative difficulties
	//nGain = URANGE(nGain / 3, nViDifficulty * nGain / pCh->nDifficulty, nGain * 2);
    // Change the gain based on current power use
	nGain = UMAX(nGain / 10, pCh->nCurPl * nGain / 100);
	// Limit the gain
	nGain = UMIN(nGain, nNext / 50);
    // Get a reward?
    if (pCh->pcdata->nReward > 0)
        nGain *= 2;

	pCh->pcdata->nStatProgress[nStat] += nGain;

	if (pCh->pcdata->nStatProgress[nStat] < nNext)
		return;

    if (bSuccess)
        sprintf (szBuf, "{CWith hard work, your %s improves!{x\n\r", stat_table[nStat]);
    else
        sprintf (szBuf, "{CThrough trial and error, your %s improves!{x\n\r", stat_table[nStat]);
    sendch (szBuf, pCh);
    pCh->perm_stat[nStat]++;
	if (nStat == STAT_STR) {
        pCh->max_hit += HP_STR;
		pCh->pcdata->perm_hit += HP_STR;
        pCh->max_ki += KI_STR;
		pCh->pcdata->perm_ki += KI_STR;
	}
    else if (nStat == STAT_WIL) {
        pCh->max_hit += HP_WIL;
		pCh->pcdata->perm_hit += HP_WIL;
        pCh->max_ki += KI_WIL;
		pCh->pcdata->perm_ki += KI_WIL;
    }
	pCh->pcdata->nStatProgress[nStat] = 0;
	ResetDiff(pCh);
    UpdateSkills (pCh);
    return;
}

void UpdateSkills (CHAR_DATA *pCh) {
    char szBuf[MAX_STRING_LENGTH];
    int nSn;

    if (IS_NPC(pCh))
        return;
    for (nSn = 0; nSn < MAX_SKILL; ++nSn) {
        if (pCh->pcdata->learned[nSn] < 1 && MeetsPrereq(pCh, nSn)) {
            pCh->pcdata->learned[nSn] = 1;
            sprintf(szBuf, "{CWith your new insights, you now know %s!{x\n\r", skill_table[nSn].name);
            sendch (szBuf, pCh);
        }
    }
}

void ResetDiff (CHAR_DATA *pCh) {
    int i, nCount = 0;
	pCh->nDifficulty = 0;
    pCh->nTrueDiff = 0;

    for (i = 0; i < MAX_SKILL; ++i) {
		// Limit the number of skills NPCs get, to protect against
		// artificially high difficult ratings
		if (IS_NPC(pCh) && ++nCount > get_curr_stat (pCh, STAT_INT) + 1)
			break;
		pCh->nDifficulty += get_skill (pCh, i);
	}
	pCh->nDifficulty += get_curr_stat (pCh, STAT_STR);
	pCh->nDifficulty += get_curr_stat (pCh, STAT_DEX);
	pCh->nDifficulty += get_curr_stat (pCh, STAT_CHA);
	pCh->nDifficulty += get_curr_stat (pCh, STAT_INT);
	pCh->nDifficulty += get_curr_stat (pCh, STAT_WIL);
    pCh->llPl = pCh->nDifficulty * pCh->nDifficulty;

    for (i = 0; i < MAX_SKILL; ++i) {
		if (IS_NPC(pCh) && ++nCount > pCh->perm_stat[STAT_INT] + 1)
			break;
		pCh->nTrueDiff += get_skill (pCh, i);
	}
	pCh->nTrueDiff += pCh->perm_stat[STAT_STR];
	pCh->nTrueDiff += pCh->perm_stat[STAT_DEX];
	pCh->nTrueDiff += pCh->perm_stat[STAT_CHA];
	pCh->nTrueDiff += pCh->perm_stat[STAT_INT];
	pCh->nTrueDiff += pCh->perm_stat[STAT_WIL];
    pCh->llTruePl = pCh->nTrueDiff * pCh->nTrueDiff;

    UpdateSkills (pCh);
}



/*
// Total of all skills and statistics
void ResetDifficulty (CHAR_DATA *pCh) {
    int i, nCount = 0;
	pCh->nDifficulty = 0;

    for (i = 0; i < MAX_SKILL; ++i) {
		// Limit the number of skills NPCs get, to protect against
		// artificially high difficult ratings
		if (IS_NPC(pCh) && ++nCount > get_curr_stat (pCh, STAT_INT) + 1)
			break;
		pCh->nDifficulty += get_skill (pCh, i);
	}

	pCh->nDifficulty += get_curr_stat (pCh, STAT_STR);
	pCh->nDifficulty += get_curr_stat (pCh, STAT_DEX);
	pCh->nDifficulty += get_curr_stat (pCh, STAT_CHA);
	pCh->nDifficulty += get_curr_stat (pCh, STAT_INT);
	pCh->nDifficulty += get_curr_stat (pCh, STAT_WIL);
}

// Total based on skills and statistics.
// Skills are worth 50 pl each, plus the summation of each number from 1 to 20 * skill_level (1 + 2 + 3 + 4 + ... + 20 * lvl).
// Stats are worth the same as skills
// The formulas used are based on (n/2)(n+1)
void ResetPl (CHAR_DATA *pCh) {
    int i, nCount = 0;

	// Maximum powerlevel, including affects
	pCh->llPl = 0;
	for (i = 0; i < MAX_SKILL; ++i) {
		// Limit the number of skills NPCs get, to protect against
		// artificially high powerlevels
		if (IS_NPC(pCh) && ++nCount > get_curr_stat (pCh, STAT_INT) + 1)
			break;
		pCh->llPl += 50 + (10 * get_skill (pCh, i)) * (20 * get_skill (pCh, i) + 1);
	}
	pCh->llPl += 50 + (10 * get_curr_stat (pCh, STAT_STR)) * (20 * get_curr_stat (pCh, STAT_STR) + 1);
	pCh->llPl += 50 + (10 * get_curr_stat (pCh, STAT_DEX)) * (20 * get_curr_stat (pCh, STAT_DEX) + 1);
	pCh->llPl += 50 + (10 * get_curr_stat (pCh, STAT_CHA)) * (20 * get_curr_stat (pCh, STAT_CHA) + 1);
	pCh->llPl += 50 + (10 * get_curr_stat (pCh, STAT_INT)) * (20 * get_curr_stat (pCh, STAT_INT) + 1);
	pCh->llPl += 50 + (10 * get_curr_stat (pCh, STAT_WIL)) * (20 * get_curr_stat (pCh, STAT_WIL) + 1);

	// Find permanent, unadultered powerlevel
    pCh->llTruePl = 0;
	for (i = 0; i < MAX_SKILL; ++i) {
		if (IS_NPC(pCh) && ++nCount > pCh->perm_stat[STAT_INT] + 1)
			break;
		pCh->llTruePl += 50 + (10 * get_skill (pCh, i)) * (20 * get_skill (pCh, i) + 1);
	}
	pCh->llTruePl += 50 + (10 * pCh->perm_stat[STAT_STR]) * (20 * pCh->perm_stat[STAT_STR] + 1);
	pCh->llTruePl += 50 + (10 * pCh->perm_stat[STAT_DEX]) * (20 * pCh->perm_stat[STAT_DEX] + 1);
	pCh->llTruePl += 50 + (10 * pCh->perm_stat[STAT_CHA]) * (20 * pCh->perm_stat[STAT_CHA] + 1);
	pCh->llTruePl += 50 + (10 * pCh->perm_stat[STAT_INT]) * (20 * pCh->perm_stat[STAT_INT] + 1);
	pCh->llTruePl += 50 + (10 * pCh->perm_stat[STAT_WIL]) * (20 * pCh->perm_stat[STAT_WIL] + 1);
}
*/

// ch_gsn is the gsn of the skill the character is using to attack with
// vi_gsn is the gsn he is defending with
// multiplier is used against the victim's chance (therefore, high multiplier makes it harder
//     to hit)
bool check_hit (CHAR_DATA *ch, CHAR_DATA *victim, int ch_gsn, int vi_gsn, float multiplier, bool bLearn) {
    CHAR_DATA *vch;
    char buf[MAX_STRING_LENGTH];
    int ch_roll, vi_roll;
    
    if (bLearn) {
        ImproveSkill (ch, ch_gsn, TRUE, 1, victim->nDifficulty);
        ImproveStat (ch, STAT_DEX, TRUE, 2, victim->nDifficulty); // 2 since it's called twice as often (to hit and dodge)

        ImproveSkill (victim, vi_gsn, TRUE, 1, ch->nDifficulty);
        ImproveStat (victim, STAT_DEX, TRUE, 2, ch->nDifficulty);
    }

    ch_roll = Hitroll (ch, ch_gsn, TRUE);
    vi_roll = Armour (victim, vi_gsn, TRUE);

    for (vch = ch->in_room->people; vch; vch = vch->next_in_room) {
        if (!IS_NPC(vch) && IS_IMMORTAL(vch) && IS_SET(vch->act, PLR_COMBATINFO)) {
            sprintf(buf, "{WCI:hit  [%s] %d -vs- %d [%s]{x\n\r", NAME(ch), ch_roll, vi_roll, NAME(victim));
		    sendch(buf, vch);
        }
    }

    ki_loss (victim, 6);

	if (ch_roll > vi_roll)
		return TRUE;

    if (vi_gsn == gsn_dodge) {
        act ("{2You dodge!{x", victim, NULL, NULL, TO_CHAR);
        act ("{3$n dodges!{x", victim, NULL, NULL, TO_ROOM);
    }
    else if (vi_gsn == gsn_shield_block) {
        act ("{2You block with a shield!{x", victim, NULL, NULL, TO_CHAR);
        act ("{3$n blocks with a shield!{x", victim, NULL, NULL, TO_ROOM);
    }
    else if (vi_gsn == gsn_parry) {
        act ("{2You parry!{x", victim, NULL, NULL, TO_CHAR);
        act ("{3$n parries!{x", victim, NULL, NULL, TO_ROOM);
    }
    else {
        act ("{2You defend!{x", victim, NULL, NULL, TO_CHAR);
        act ("{3$n defends!{x", victim, NULL, NULL, TO_ROOM);
    }

    return FALSE;
}

bool check_kihit (CHAR_DATA *ch, CHAR_DATA *victim, int ch_gsn, int vi_gsn, float multiplier, bool bLearn) {
   CHAR_DATA *vch;
    char buf[MAX_STRING_LENGTH];
    int ch_roll, vi_roll;

    if (bLearn) {
        ImproveSkill (ch, ch_gsn, TRUE, 1, victim->nDifficulty);
        ImproveStat (ch, STAT_INT, TRUE, 2, victim->nDifficulty); // 2 since its used twice as often (to hit and dodge)

        ImproveSkill (victim, vi_gsn, TRUE, 1, ch->nDifficulty);
        ImproveStat (victim, STAT_INT, TRUE, 2, ch->nDifficulty);
    }

    ch_roll = Hitroll (ch, ch_gsn, FALSE);
    vi_roll = Armour (victim, vi_gsn, FALSE);

    for (vch = ch->in_room->people; vch; vch = vch->next) {
        if (!IS_NPC(vch) && IS_IMMORTAL(vch) && IS_SET(vch->act, PLR_COMBATINFO)) {
            sprintf(buf, "{WCI:ki  [%s] %d -vs- %d [%s]{x\n\r", NAME(ch), ch_roll, vi_roll, NAME(victim));
		    sendch(buf, vch);
        }
    }

    ki_loss (victim, 6);

	if (ch_roll > vi_roll)
        return TRUE;

    if (vi_gsn == gsn_dodge) {
        act ("{2You dodge!{x", victim, NULL, NULL, TO_CHAR);
        act ("{3$n dodges!{x", victim, NULL, NULL, TO_ROOM);
    }
    else if (vi_gsn == gsn_shield_block) {
        act ("{2You block with a shield!{x", victim, NULL, NULL, TO_CHAR);
        act ("{3$n blocks with a shield!{x", victim, NULL, NULL, TO_ROOM);
    }
    else if (vi_gsn == gsn_parry) {
        act ("{2You parry!{x", victim, NULL, NULL, TO_CHAR);
        act ("{3$n parries!{x", victim, NULL, NULL, TO_ROOM);
    }
    else {
        act ("{2You defend!{x", victim, NULL, NULL, TO_CHAR);
        act ("{3$n defends!{x", victim, NULL, NULL, TO_ROOM);
    }

    return FALSE;
}

int Hitroll (CHAR_DATA *pCh, int nSn, bool bPhysical) {
    int nRoll, nRand;
    nRand  = number_range(1,50);
    // Critical miss
    if (nRand <= 3)
        return 0;
    nRoll  = nRand;
    nRoll += get_curr_stat(pCh, bPhysical ? STAT_DEX : STAT_INT);
    nRoll += get_skill(pCh, nSn);
    nRoll += GET_HITROLL(pCh);
    nRoll += (pCh->balance - 5) * 2;
    nRoll -= (100 - pCh->nCurPl) / 10;
    if (nSn == gsn_heart_shot)
        nRoll -= 2;
    else if (nSn == gsn_eye_gouge)
        nRoll -= 5;
    if (IS_AFFECTED (pCh, AFF_BLIND))
        nRoll -= 5;
    if (IS_AFFECTED (pCh, AFF_FLYING))
        nRoll += 5;
    if (pCh->stance == STANCE_OFFEN)
        nRoll += 4;
    else if (pCh->stance == STANCE_DEFEN)
        nRoll -= 4;
	if (!IS_AWAKE (pCh))
        nRoll -= 5;
    else if (pCh->position < POS_FIGHTING)
        nRoll -= 2;
    if (IS_EXHAUSTED(pCh))
        nRoll -= 10;
    if (pCh->charge > 0)
        nRoll = (pCh->charge * nRoll) / skill_table[nSn].wait;
    if (pCh->charge == 1) // Immediately released skill -- deduction
        nRoll /= 4;
    // Critical:
    if (nRand == 50)
        nRoll *= 3;
    return nRoll;
}

int Damroll (CHAR_DATA *pCh, int nLow, int nHigh, int nSn, bool bPhysical) {
    int nDam;
    nDam  = number_range(nLow, nHigh);
    nDam += get_curr_stat(pCh, bPhysical ? STAT_STR : STAT_WIL) + get_skill(pCh, nSn);
    nDam += GET_DAMROLL(pCh);
    nDam += pCh->balance - 5;
    nDam -= (100 - pCh->nCurPl) / 10;
    /*
    if (sn == gsn_throat_shot ||
        sn == gsn_sweep ||
        sn == gsn_eye_gouge)          nDam -= 15;
    else if (sn == gsn_knee)          nDam -= 3;
    else if (sn == gsn_elbow)         nDam -= 7;
    else if (sn == gsn_heart_shot)    nDam += 10;
    else if (sn == gsn_power_bomb)    nDam += 15;
    else if (sn == gsn_spirit_bomb ||
             sn == gsn_death_ball)    nDam += 10;
    else if (sn == gsn_finalflash ||
             sn == gsn_kamehameha)    nDam += 5;
    else if (sn == gsn_scattershot ||
             sn == gsn_solarflare)    nDam -= 5;
             */
    if (pCh->stance == STANCE_OFFEN)
        nDam += 4;
    else if (pCh->stance == STANCE_DEFEN)
        nDam -= 4;
	if (!IS_AWAKE (pCh))
        nDam -= 5;
    else if (pCh->position < POS_FIGHTING)
        nDam -= 2;
    if (IS_EXHAUSTED(pCh))
        nDam -= 10;
    if (pCh->charge > 0)
        nDam = (pCh->charge * nDam) / skill_table[nSn].wait;
    if (pCh->charge == 1) // Immediately released skill -- deduction
        nDam /= 4;
    // Critical:
    if (number_range(1,50) == 1)
        nDam *= 2;
    nDam = UMAX(1, nDam);
    return nDam;
}

int Armour (CHAR_DATA *pCh, int nSn, bool bPhysical) {
    int nArmour = 25;
    nArmour += get_curr_stat(pCh, bPhysical ? STAT_DEX : STAT_INT);
    nArmour += get_skill(pCh, nSn);
    //nArmour += GET_ARMROLL(pCh);
    nArmour += (pCh->balance - 5) * 2;
    nArmour -= (100 - pCh->nCurPl) / 10;
    if (IS_AFFECTED (pCh, AFF_BLIND))
        nArmour -= 5;
    if (IS_AFFECTED (pCh, AFF_FLYING))
        nArmour += 5;
    if (pCh->stance == STANCE_OFFEN)
        nArmour -= 4;
    else if (pCh->stance == STANCE_DEFEN)
        nArmour += 4;
	if (!IS_AWAKE (pCh))
        nArmour -= 5;
    else if (pCh->position < POS_FIGHTING)
        nArmour -= 2;
    if (IS_EXHAUSTED(pCh))
        nArmour -= 10;
    return nArmour;
}

int Absorb (CHAR_DATA *pCh, int nSn, int nDamType, bool bPhysical) {
    int nAbsorb = 0;
    switch (nDamType) {
        case DAM_NONE:                                         break;
        case DAM_PIERCE: nAbsorb += pCh->armor[AC_PIERCE] / 5; break;
        case DAM_BASH:   nAbsorb += pCh->armor[AC_BASH]   / 5; break;
        case DAM_SLASH:  nAbsorb += pCh->armor[AC_SLASH]  / 5; break;
        default:         nAbsorb += pCh->armor[AC_EXOTIC] / 5; break;
    }
    if (pCh->stance == STANCE_OFFEN)
        nAbsorb -= 4;
    else if (pCh->stance == STANCE_DEFEN)
        nAbsorb += 4;
    if (IS_AFFECTED (pCh, AFF_SANCTUARY))
        nAbsorb += 15;
    if ((IS_AFFECTED (pCh, AFF_PROTECT_EVIL) && IS_EVIL (pCh))
        || (IS_AFFECTED (pCh, AFF_PROTECT_GOOD) && IS_GOOD (pCh)))
        nAbsorb += 10;
	if (!IS_AWAKE (pCh))
        nAbsorb -= 5;
    else if (pCh->position < POS_FIGHTING)
        nAbsorb -= 2;
    return nAbsorb;
}

/*
long long int ApplyMisc (CHAR_DATA *pCh, long long int llValue) {
    llValue = llValue * pCh->balance / 5;
	if (!IS_AWAKE (pCh))
        llValue /= 4;
    else if (pCh->position < POS_FIGHTING)
        llValue /= 2;
    if (IS_EXHAUSTED(pCh))
        llValue /= 50;
    llValue = pCh->nCurPl * llValue / 100;

    return llValue;
}

long long int ApplyCharge (CHAR_DATA *pCh, long long int llValue, int nSn) {
    if (pCh->charge > 0)
        llValue = (pCh->charge * llValue) / skill_table[nSn].wait;
    if (pCh->charge == 1) // Immediately released skill -- deduction
        llValue /= 4;
    return llValue;
}

// Returns a calculated value for chance to hit in melee based on powerlevel,
// stats, skill and other conditions, for some skill
long long int get_attackhit (CHAR_DATA *ch, int sn) {
    long long int value;

    value  = get_curr_stat(ch, STAT_DEX);
    value += get_skill(ch, sn) / 2;
    value += GET_HITROLL(ch);

    if (sn == gsn_heart_shot)
        value /= 2;
    else if (sn == gsn_eye_gouge)
        value /= 10;

    if (IS_AFFECTED (ch, AFF_BLIND))
        value -= value / 5;

    if (IS_AFFECTED (ch, AFF_FLYING))
        value += value / 4;

    if (ch->stance == STANCE_OFFEN)
        value += value / 4;
    else if (ch->stance == STANCE_DEFEN)
        value -= value / 4;
    else if (ch->stance == STANCE_KAMIK)
        value += 1 + get_skill(ch, gsn_kamikaze);

    value = ApplyMisc (ch, value);

    return value;
}


long long int get_attackdam (CHAR_DATA *ch, int sn) {
    long long int value;

    value  = get_curr_stat(ch, STAT_STR);
    value += get_skill(ch, sn) / 2;
    value += GET_DAMROLL(ch);

    if (sn == gsn_throat_shot ||
        sn == gsn_sweep ||
        sn == gsn_eye_gouge)
        value = 1;
    else if (sn == gsn_knee)
        value /= 2;
    else if (sn == gsn_elbow)
        value /= 5;
    else if (sn == gsn_heart_shot)
        value *= 2;

    if (ch->stance == STANCE_OFFEN)
        value += value / 4;
    else if (ch->stance == STANCE_DEFEN)
        value -= value / 4;
    else if (ch->stance == STANCE_KAMIK)
        value += 1 + get_skill(ch, gsn_kamikaze);

    value = ApplyMisc (ch, value);
    value = UMAX(1, value);

    return value;
}

long long int get_attackabsorb (CHAR_DATA *ch, int sn, int dam_type) {
    long long int value = 0;

    switch (dam_type) {
        case DAM_NONE:                                  break;
        case DAM_PIERCE: value += ch->armor[AC_PIERCE] / 5; break;
        case DAM_BASH:   value += ch->armor[AC_BASH] / 5;   break;
        case DAM_SLASH:  value += ch->armor[AC_SLASH] / 5;  break;
        default:         value += ch->armor[AC_EXOTIC] / 5; break;
    }

    if (ch->stance == STANCE_OFFEN)
        value -= value / 4;
    else if (ch->stance == STANCE_DEFEN)
        value += value / 4;
    else if (ch->stance == STANCE_KAMIK)
        value = 0;

    if (IS_AFFECTED (ch, AFF_SANCTUARY))
        value *= 4;

    if ((IS_AFFECTED (ch, AFF_PROTECT_EVIL) && IS_EVIL (ch))
        || (IS_AFFECTED (ch, AFF_PROTECT_GOOD) && IS_GOOD (ch)))
        value += value / 4;

    value = ApplyMisc (ch, value);

    return value;
}

long long int get_attackdodge (CHAR_DATA *ch, int sn) {
    long long int value;

    value  = get_curr_stat(ch, STAT_DEX);
    value += get_skill(ch, sn) / 2;

    if (IS_AFFECTED (ch, AFF_BLIND))
        value -= value / 5;

    if (IS_AFFECTED (ch, AFF_FLYING))
        value += value / 4;
    
	if (IS_AFFECTED (ch, AFF_SANCTUARY))
        value *= 4;

    if (ch->stance == STANCE_OFFEN)
        value -= value / 4;
    else if (ch->stance == STANCE_DEFEN)
        value += value / 4;
    else if (ch->stance == STANCE_KAMIK)
        value -= 3 * value / 4;

    value = ApplyMisc (ch, value);

    return value;
}

long long int get_kihit (CHAR_DATA *ch, int sn) {
    long long int value;

    value  = get_curr_stat(ch, STAT_INT);
    value += get_skill(ch, sn) / 2;
    value += GET_HITROLL(ch);

    if (IS_AFFECTED (ch, AFF_BLIND))
        value -= value / 5;

    if (IS_AFFECTED (ch, AFF_FLYING))
        value += value / 4;

    if (sn == gsn_scattershot)
        value /= 2;

    value = ApplyCharge (ch, value, sn);
	value = ApplyMisc (ch, value);

    return value;
}

long long int get_kidam (CHAR_DATA *ch, int sn) {
    long long int value;

    value  = get_curr_stat(ch,STAT_WIL);
    value += get_skill(ch, sn) / 2;

    if (sn == gsn_power_bomb)
        value *= 4;
    else if (sn == gsn_spirit_bomb || sn == gsn_death_ball)
        value *= 3;
    else if (sn == gsn_finalflash || sn == gsn_kamehameha)
        value *= 2;
    else if (sn == gsn_scattershot || sn == gsn_solarflare)
        value /= 5;

    value = ApplyCharge (ch, value, sn);
    value = ApplyMisc (ch, value);
	value = UMAX(1, value);

    return value;
}

long long int get_kiabsorb (CHAR_DATA *ch, int sn, int dam_type) {
    long long int value = 0;

    // Armor!
    switch (dam_type) {
        case DAM_NONE:                                      break;
        case DAM_PIERCE: value += ch->armor[AC_PIERCE] / 5; break;
        case DAM_BASH:   value += ch->armor[AC_BASH] / 5;   break;
        case DAM_SLASH:  value += ch->armor[AC_SLASH] / 5;  break;
        default:         value += ch->armor[AC_EXOTIC] / 5; break;
    }
    
	if (IS_AFFECTED (ch, AFF_SANCTUARY))
        value *= 4;

    value = ApplyMisc (ch, value);

    return value;
}
*/