/*    /lib/spell.c
 *    From Dead Souls LPMud
 *    An object all spells inherit.
 *    Created by Descartes of Borg 951027
 *    Version: @(#) spell.c 1.22@(#)
 *    Last modified: 96/12/17
 */

#include <lib.h>
#include <magic.h>
#include <damage_types.h>

inherit LIB_DAEMON;
inherit LIB_HELP;

private int          AutoDamage      = -1;            // perform dmg?
private int          AutoHeal        = -1;            // perform healing?
private string       Conjure         = 0;             // file to clone
private int array    Damage          = ({ 0, 0 });    // base, random
private mixed array  Messages        = ({});          // damage/heal messages
private int          DamageType      = MAGIC;         // damage type done
private int          Difficulty      = 0;             // 1-100 scale
private int array    Healing         = ({ 0, 0 });    // base, random
private int array    MagicCost       = ({ 0, 0 });    // base, random
private int          Morality        = 0;             // bad? good?
private string array Religions       = 0;             // limit who can cast
private int          RemoteTargets   = 0;             // can targets be remote
private int          RequiredMagic   = 0;             // min magic pts to cast
private int          RequiredStamina = 0;             // min stam pts to cast
private string array Rules           = ({});          // spell rules
private mapping      Skills          = ([]);          // skill requirements
private string       SpellName       = "";            // name of spell
private int          SpellType       = SPELL_HEALING; // spell type
private int array    StaminaCost     = ({ 0, 0 });    // base, random
private int		   TrainingModifier= 1;			// training factor
private string       Verb            = "cast";        // use what verb?

/* ********************* spell.c attributes ************************ */
int GetAutoDamage() {
    return AutoDamage;
}

static int SetAutoDamage(int x) {
    return (AutoDamage = x);
}

int GetAutoHeal() {
    return AutoHeal;
}

static int SetAutoHeal(int x) {
    return (AutoHeal = x);
}

string GetConjure() {
    return Conjure;
}

static string SetConjure(string str) {
    return (Conjure = str);
}

int GetDamage() {
    int tmp = 0;
    int damage = Damage[0];

    if( Damage[1] ) {
	damage += random(Damage[1]);
    }
    if(this_player()->GetSkill("magic attack")){
	tmp = (this_player()->GetSkill("magic attack")["level"]) * 2;
	tmp = tmp/this_player()->GetSkill("magic attack")["class"];
    }
    damage += tmp;
    return damage;
}

varargs static void SetDamage(int type, mixed array rest...) {
    DamageType = type;
    if( arrayp(rest[0]) ) {
	rest = rest[0];
    }
    Damage[0] = rest[0];
    if( sizeof(rest) == 2 ) {
	Damage[1] = rest[1];
    }
    return;
}

int GetDamageType() {
    return DamageType;
}

int GetDifficulty() {
    return Difficulty;
}

static int SetDifficulty(int x) {
    return (Difficulty = x);
}

string GetErrorMessage() {
    string rule = Rules[0];

    switch(rule) {
    case "":
	if( Verb == "pray" ) {
	    return "Just pray for it.";
	}
	else {
	    return "Simply cast it?";
	}

    case "LIV":
	if( Verb == "pray" ) {
	    return "Pray for it for whom?";
	}
	else {
	    return "Cast it on whom?";
	}

    case "OBJ": case "STR": 
	if( Verb == "pray" ) {
	    return "Pray for it for what?";
	}
	else {
	    return "Cast it on what?";
	}

    case "STR of LIV":
	if( Verb == "pray" ) {
	    return "Pray for it for whom against what?";
	}
	else {
	    return "Cast it on what of whom?";
	}

    case "for LIV": 
	return "Pray for it for whom?";

    case "for OBJ":
	return "Pray for it for what?";

    case "against STR":
	if( Verb == "pray" ) {
	    return "Pray against what?";
	}
	else {
	    return "Cast against what?";
	}

    case "against STR for LIV":
	return "Pray against what for whom?";
    }
    if( Verb == "pray" ) {
	return "Pray for it?";
    }
    return "Cast it?";
}

int GetHealing() {
    return Healing[0] + random(Healing[1]);
}

static varargs int array SetHealing(mixed args...) {
    Healing[0] = args[0];
    if( sizeof(args) == 2 ) {
	Healing[1] = args[1];
    }
    return Healing;
}

int GetMagicCost() {
    return MagicCost[0] + random(MagicCost[1]);
}

static varargs int array SetMagicCost(mixed args...) {
    MagicCost[0] = args[0];
    if( sizeof(args) == 2 ) {
	MagicCost[1] = args[1];
    }
    return MagicCost;
}

varargs string array GetMessage(int damage, int healing) {
    int max, div, i;

    if( damage < 1 ) {
	return Messages[0];
    }
    if( sizeof(Messages) == 2 ) {
	return Messages[1];
    }
    if( healing) {
	max = Healing[0] + Healing[1];
    }
    else {
	max = Damage[0] + Damage[1];
    }
    div = max/(sizeof(Messages) - 1);
    i = (damage/div) + 1;
    if( i >= sizeof(Messages) ) {
	i = sizeof(Messages)-1;
    }
    return Messages[i];
}

static mixed array SetMessages(mixed array messages) {
    return (Messages = messages);
}

int GetMorality() {
    return Morality;
}

static int SetMorality(int x) {
    return (Morality = x);
}

string array GetReligions() {
    return copy(Religions);
}

varargs static string array SetReligions(string array religions...) {
    return (Religions = religions);
}

int GetRemoteTargets() {
    return RemoteTargets;
}

static int SetRemoteTargets(int x) {
    return (RemoteTargets = x);
}

int GetRequiredMagic() {
    return RequiredMagic;
}

static int SetRequiredMagic(int x) {
    return (RequiredMagic = x);
}

int GetRequiredStamina() {
    return RequiredStamina;
}

static int SetRequiredStamina(int x) {
    return (RequiredStamina = x);
}

int GetRequiredSkill(string skill) {
    return Skills[skill];
}

string array GetRules() {
    return Rules;
}

varargs static string array SetRules(mixed args...) {
    if( !args ) {
	args = ({ "" });
    }
    else if( arrayp(args[0]) ) {
	args = args[0];
    }
    return (Rules = args);
}

string array GetSkills() {
    return keys(Skills);
}

static mapping SetSkills(mapping mp) {
    return (Skills = mp);
}

string GetSpell() {
    return SpellName;
}

static string SetSpell(string str) {
    return (SpellName = str);
}

int GetSpellType() {
    return SpellType;
}

static int SetSpellType(int x) {
    return (SpellType = x);
}

int GetStaminaCost() {
    return StaminaCost[0] + random(StaminaCost[1]);
}

static varargs int array SetStaminaCost(mixed args...) {
    StaminaCost[0] = args[0];
    if( sizeof(args) == 2 ) {
	StaminaCost[1] = args[1];
    }
    return StaminaCost;
}

varargs object array GetTargets(object who, mixed args...) {
    int count = sizeof(args);
    int attack = (SpellType == SPELL_COMBAT);
    object def;

    if( attack ) {
	def = who->GetCurrentEnemy();
    }
    else {
	def = who;
    }
    if( Verb == "cast" ) {
	if( !count) {
	    if( member_array("", Rules) == -1 ) {
		return 0;
	    }
	    else {
		if( def ) {
		    return ({ def });
		}
		else {
		    return 0;
		}
	    }
	}
	else if( count == 1 ) { // on LIV || on STR
	    if( objectp(args[0]) ) { // on LIV
		if( !living(args[0]) ) {
		    return 0;
		}
		if( member_array("LIV", Rules) == -1 ) {
		    return 0;
		}
		else {
		    return ({ args[0] });
		}
	    }
	    if( stringp(args[0]) ) { // on STR
		int which = member_array("against STR", Rules);

		if( which == -1 && member_array("STR", Rules) == -1 ) {
		    return 0;
		}
		else {
		    if( def ) {
			return ({ def });
		    }
		    else {
			return 0;
		    }
		}
	    }
	}
	if( objectp(args[1]) ) { // on STR of LIV
	    if( !living(args[1]) ) {
		return 0;
	    }
	    if( member_array("STR of LIV", Rules) == -1 ) {
		return 0;
	    }
	    else {
		return ({ args[1] });
	    }
	}
    }
    else {
	if( !count) {
	    if( member_array("", Rules) == -1 ) {
		return 0;
	    }
	    else {
		if( def ) {
		    return ({ def });
		}
		else {
		    return 0;
		}
	    }
	}
	else if( count == 1 ) { // against STR
	    if( objectp(args[0]) ) {
		if( !sizeof(({ "for LIV", "for OBJ" }) & Rules) ) {
		    return 0;
		}
		if( !living(args[0]) && member_array("for OBJ",Rules) == -1 ) {
		    return 0;
		}
		return ({ args[0] });
	    }
	    if( member_array("against STR", Rules) == -1 ) {
		return 0;
	    }
	    else {
		if( def ) {
		    return ({ def });
		}
		else {
		    return 0;
		}
	    }
	}
	if( objectp(args[1]) ) { // against STR for LIV
	    if( !living(args[1]) ) {
		return 0;
	    }
	    if( member_array("against STR for LIV", Rules) == -1 ) {
		return 0;
	    }
	    else {
		return ({ args[1] });
	    }
	}
    }
    return 0;
}

static int SetTrainingModifier(int modifier) {
    return TrainingModifier = modifier;
}

int GetTrainingModifier() {
    return TrainingModifier;
}

string GetVerb() {
    return Verb;
}

static string SetVerb(string verb) {
    return (Verb = verb);
}

/* ******************** spell.c modals *************************** */
static int CanAttack(object who, object array enemies, int power) {
    int i, maxi = sizeof(enemies);
    int hits = 0;
    int misses = 0;
    int hit_count = 0;
    int miss_count = 0;
    int hit_con = 0;
    int miss_con = 0;
    int bonus;

    if( !maxi ) {
	return -1;
    }
    for(i=0; i<maxi; i++) {
	mixed area;

	if( !enemies[i] ) {
	    continue;
	}
	if( !enemies[i]->eventPreAttack(who) ) {
	    enemies[i] = 0;
	    continue;
	}
	if( environment(enemies[i]) != environment(who) ) {
	    if( RemoteTargets ) {
		area = 0;
	    }
	    else {
		enemies[i] = 0;
		continue;
	    }
	}
	else {
	    area = environment(who);
	}
	moral_act(who, enemies[i], GetMorality());
	if( !enemies[i]->eventReceiveAttack(power, "magic", who) ) {
	    misses += enemies[i]->GetLevel();
	    miss_count++;
	    miss_con += enemies[i]->GetMagicResistance();
	    send_messages("repel", "$target_name $target_verb "
	      "$agent_possessive_noun magic attack.", who,
	      enemies[i], area);
	    enemies[i] = 0;
	}
	else {
	    hit_count++;
	    hits += enemies[i]->GetLevel();
	    hit_con += enemies[i]->GetMagicResistance();
	}
    }
    if( miss_count > 0 ) {
	bonus = who->GetCombatBonus(misses/miss_count);
	bonus *= GetTrainingModifier();
	foreach(string skill in GetSkills()) {
	    who->eventTrainSkill(skill, power/(hit_count+miss_count),
	      miss_con/miss_count, 0, bonus);
	}			 
    }
    if( hit_count < 1 ) {
	return -1;
    }
    bonus = who->GetCombatBonus(hits/hit_count);
    bonus *= GetTrainingModifier();	
    foreach(string skill in GetSkills()) {
	who->eventTrainSkill(skill, power/(hit_count+miss_count),
	  hit_con/hit_count, 1, bonus);
    }
    return 1;
}

varargs int CanCast(object who, int level, string limb, object array targets) {
    string array skills = GetSkills();
    int count = sizeof(skills);
    int cost = GetMagicCost();
    int x;

    if( Religions ) {
	if( member_array(who->GetReligion(1), Religions) == -1 ) {
	    who->eventPrint("Your deity does not have that kind of power."); 
	    return 0;
	}
    }
    if( cost > 0 ) {
	who->AddMagicPoints(-cost);
    }
    cost = GetStaminaCost();
    if( cost > 0 ) {
	who->AddStaminaPoints(-cost);
    }
    if( AutoHeal != -1 ) {
	int i, maxi = sizeof(targets);

	for(i=0; i<maxi; i++) {
	    int hp, max_hp;

	    if( !targets[i] ) {
		continue;
	    }
	    if( limb ) {
		if( member_array(limb, targets[i]->GetLimbs()) == -1 ) {
		    send_messages("have", "$target_name $target_verb no " +
		      limb + ".", who, targets[i]);
		    targets[i] = 0;
		    continue;
		}
	    }
	    hp = targets[i]->GetHealthPoints(limb);
	    max_hp = targets[i]->GetMaxHealthPoints(limb);
	    if( max_hp - hp < (Healing[0]+Healing[1])/10 + 1 ) {
		if( limb ) {
		    send_messages("", "$target_possessive_noun " + limb +
		      " needs no healing.", who, targets[i]);
		}
		else {
		    send_messages("need", "$target_name $target_verb no "
		      "healing.", who, targets[i]);
		}
		targets[i] = 0;
		continue;
	    }
	}
	if( !sizeof(filter(targets, (: $1 :))) ) {
	    return 0;
	}
    }	
    foreach(string skill in skills) {
	level += who->GetSkillLevel(skill);
    }
    level = level/(count+1);
    x = who->GetMagicChance(level/2 + random(level/2));
    if( x < GetDifficulty() ) { // Can't even cast it...
	foreach(string skill in skills) {
	    who->eventTrainSkill(skill, level, GetDifficulty(),
	      0, GetTrainingModifier());
	}
	who->eventPrint("You must have gotten the words wrong.");
	return 0;
    }
    if( AutoDamage != -1 ) {
	if( CanAttack(who, targets, x) == - 1 ) {
	    who->eventPrint("Your powers fail you.");
	    return 0;
	}
    }
    else {
	if( GetMorality() ) {
	    moral_act(who, 0, GetMorality());
	}
	foreach(string skill in skills) {
	    who->eventTrainSkill(skill, level, GetDifficulty(), 
	      1, GetTrainingModifier());
	}
    }
    return 1;
}

/* ******************** spell.c events *********************** */
/* -1 means this did nothing
 * otherwise return damage amount for SPELL_DAMAGE
 * or heal amount for SPELL_HEAL
 */
varargs int eventCast(object who, int level, mixed limb, object array targets) {
    if( GetConjure() ) {
	object ob = new(GetConjure());

	if( !ob ) {
	    who->eventPrint("An error occurred in conjuring.");
	    return 1;
	}
	send_messages(Messages[0][0], Messages[0][1], who, 0,environment(who));
	if( !ob->eventMove(who) ) {
	    send_messages("drop", "$agent_name could not carry " +
	      ob->GetShort() + " and $agent_verb it!", who, 0,
	      environment(who));
	    ob->eventMove(environment(who));
	}
	return 1;
    }
    if( AutoHeal != -1 ) {
	mapping messages = ([]);
	int total_healing = 0;

	foreach(object target in targets) {
	    string array tmp;
	    int healing;

	    if( !target ) {
		continue;
	    }
	    healing = (GetHealing() * level)/100;
	    healing = target->eventHealDamage(healing, AutoHeal, limb);
	    total_healing += healing;
	    tmp = GetMessage(healing, 1);
	    if( !messages[tmp[1]] ) {
		messages[tmp[1]] = ([ tmp[0] : ({ target }) ]);
	    }
	    else {
		if( !messages[tmp[1]][tmp[0]] ) {
		    messages[tmp[1]][tmp[0]] = ({ target });
		}
		else {
		    messages[tmp[1]][tmp[0]] += ({ target });
		}
	    }
	}
	foreach(string message, mapping tmp in messages) {
	    foreach(string verb, object array obs in tmp) {
		send_messages(verb, message, who, obs,
		  environment(who), ([ "$limb" : limb ]));
	    }
	}
	if( sizeof(targets) ) {
	    return total_healing/sizeof(targets);
	}
	else {
	    return 0;
	}
    }
    if( AutoDamage != -1 ) {
	mapping messages = ([]);
	int total_damage = 0;

	foreach(object target in targets) {
	    string array tmp;
	    int damage;

	    if( !target ) {
		continue;
	    }
	    damage = (GetDamage() * level)/100;
	    if(!limb) limb = "torso";
	    if(grepp(identify(limb),"leg") || grepp(identify(limb),"foot")) limb = "torso";
	    damage = target->eventReceiveDamage(who, GetDamageType(), damage,
	      AutoDamage, limb);
	    total_damage += damage;
	    tmp = GetMessage(damage);
	    if( !messages[tmp[1]] ) {
		messages[tmp[1]] = ([ tmp[0] : ({ target }) ]);
	    }
	    else {
		if( !messages[tmp[1]][tmp[0]] ) {
		    messages[tmp[1]][tmp[0]] = ({ target });
		}
		else {
		    messages[tmp[1]][tmp[0]] += ({ target });
		}
	    }
	}
	foreach(string message, mapping tmp in messages) {
	    foreach(string verb, object array obs in tmp) {
		send_messages(verb, message, who, obs,
		  environment(who), ([ "$limb" : limb ]));
	    }
	}
	if( sizeof(targets) ) {
	    return total_damage/sizeof(targets);
	}
	else {
	    return 0;
	}
    }
    return -1;
}

varargs mixed eventParse(object who, mixed array args...) {
    int count = sizeof(args);
    if(!who) who = this_player();

    if( count < 1 ) {
	if( member_array("", Rules) == -1 ) {
	    return GetErrorMessage();
	}
	return ({});
    }
    if( Verb == "cast" ) {
	if( count == 1 ) {
	    if( objectp(args[0]) ) {
		if( !living(args[0]) ) {
		    if( member_array("OBJ", Rules) == -1 ) {
			return GetErrorMessage();
		    }
		    return ({ args[0] });
		}
		if( sizeof(({ "OBJ", "LIV"}) & Rules) == 0 ) {
		    return GetErrorMessage();
		}
		return ({ args[0] });
	    }
	    if( stringp(args[0]) ) {
		int which = member_array("against STR", Rules);

		if( which == -1 && member_array("STR", Rules) == -1 ) {
		    return GetErrorMessage();
		}
		return ({ args[0] });
	    }
	}
	if( member_array("STR of LIV", Rules) == -1 ) {
	    return GetErrorMessage();
	}
	if( stringp(args[0]) && objectp(args[1]) && living(args[1]) ) {
	    return ({ args[0], args[1] });
	}
	return "Cast it on what of whom?";
    }
    else {
	if( count == 1 ) {
	    if( objectp(args[0]) ) {
		if( sizeof(({ "for OBJ", "for LIV" }) & Rules) ) {
		    if( !living(args[0]) ) {
			if( member_array("for OBJ", Rules) == -1 ) {
			    return GetErrorMessage();
			}
		    }
		    return ({ args[0] });
		}
		return GetErrorMessage();
	    }
	    if( member_array("against STR", Rules) == -1 ) {
		return GetErrorMessage();
	    }
	    return ({ args[0] });
	}
	if( member_array("against STR for LIV", Rules) == -1 ) {
	    return GetErrorMessage();
	}
	if( stringp(args[0]) && objectp(args[1]) && living(args[1]) ) {
	    return ({ args[0], args[1] });
	}
	return "Pray for it against what for whom?";
    }
}

/* ***************** spell.c driver applies ******************** */
static void create() {
    daemon::create();
    SetNoClean(1);
}
