/*    /lib/genetics.c
 *    from the Dead Souls LPC Library
 *    handles genetic characteristics
 *    created by Descartes of Borg 950122
 *    Version: @(#) genetics.c 1.4@(#)
 *    Last Modified: 96/11/11
 */

#include <vision.h>
#include <function.h>
#include <damage_types.h>
#include "include/genetics.h"

class blindness {
    int count;
    mixed end;
}

private class blindness Blind            = 0;
private int             CustomStats      = 15;
private int array       LightSensitivity = ({ 25, 75 });
private mapping         Resistance       = ([]);
private mapping         Stats            = ([]);
private static mapping  StatsBonus       = ([]);
private static int      VisionBonus      = 0;

// abstract methods
string GetName();
varargs void eventPrint(string message, mixed args...);
// end abstract methods

static void create() {
    Resistance = ([ "low" : 0, "medium" : 0, "high" : 0, "immune" : 0 ]);
    Resistance["none"] = ALL_DAMAGE;
}

int GetBlind() {
    if( Blind ) {
	return 1;
    }
    else {
	return 0;
    }
}

static void RemoveBlindness() {
    mixed val = Blind->end;

    Blind = 0;
    if( arrayp(val) ) {
	send_messages(val[0], val[1], this_object());
    }
    else if( functionp(val) && !(functionp(val) & FP_OWNER_DESTED) ) {
	evaluate(val, this_object());
    }
    else {
	eventPrint("You can see again.");
    }
}

varargs mixed eventBlind(object who, int amt, mixed end) {
    Blind = new(class blindness);
    Blind->count = amt;
    Blind->end = end;
    return 1;
}

mixed eventCustomizeStat(string stat, int amount) {
    if( amount < 1 ) return "That is not a valid amount.";
    if( amount > CustomStats )
	return "You do not have enough points to spend on customization.";
    if( !Stats[stat] ) return "You have no such stat.";
    if( Stats[stat]["level"] + amount > 100 )
	return "You cannot make a stat exceed 100.";
    Stats[stat]["level"] += amount;
    Stats[stat]["points"] = 0;
    CustomStats -= amount;
    return Stats[stat]["level"];
}

mixed eventRestoreSight(object who, int amt) {
    if( !Blind ) {
	return GetName() + " is not blind!";
    }
    Blind->count -= amt;
    if( Blind->count < 1 ) {
	RemoveBlindness();
	return 1;
    }
    return 0;
}

varargs void SetStat(string stat, int level, int classes) {
    if(!stat) return;
    if(level < 1) level = 1;
    if(!classes) classes = 1;
    Stats[stat] = ([ "points" : 0, "level" : level, "class" : classes ]);
}

varargs void AddStat(string stat, int base, int cls) {
    int level;
    if( userp(this_object()) ) level = 1;
    else level = GetLevel();
    if( !stat || cls < 1 || cls > 5 ) return;
    base += ((5 - cls) * random(10)) + (3 * (level + 1))/(cls * 4);
    if( userp(this_object()) && base > 90 ) base = 90;
    else if( base > 100 ) base = 100;
    SetStat(stat, base, cls);
}

mapping GetStat(string stat) {
    if(!stat) return 0;
    else return copy(Stats[stat]);
}

int GetStatClass(string stat) {
    if(!Stats[stat]) return 0;
    else return Stats[stat]["class"];
}

int GetBaseStatLevel(string stat) {
    if(!Stats[stat]) return 0;
    else return Stats[stat]["level"];
}

int GetStatLevel(string stat) {
    int x;

    x = (GetBaseStatLevel(stat) + GetStatBonus(stat));
    switch(stat) {
    case "coordination": case "wisdom":
	x -= GetAlcohol();
    }
    return x;
}

int AddStatPoints(string stat, int x) {
    int y;

    if( !Stats[stat] ) return 0;
    Stats[stat]["points"] += x;
    while( Stats[stat]["points"] < 0 ) {
	if( Stats[stat]["level"] == 1 ) Stats[stat]["points"] = 0;
	else {
	    int tmp;

	    tmp = --Stats[stat]["level"];
	    Stats[stat]["points"] += GetMaxStatPoints(stat, tmp);
	}
    }
    while(Stats[stat]["points"] > (y = GetMaxStatPoints(stat,
	  Stats[stat]["level"]))) {
	if(Stats[stat]["level"] >= GetLevel()*4) Stats[stat]["points"] = y;
	else {
	    Stats[stat]["level"]++;
	    Stats[stat]["points"] -= y;
	}
    }
    return Stats[stat]["level"];
}

string *GetStats() { return keys(Stats); }
mapping GetStatsMap() { return copy(Stats); }

int GetMaxStatPoints(string stat, int level) {
    if( !Stats[stat] ) return 0;
    else {
	int cl, x;

	if( !(cl = Stats[stat]["class"]) ) return level * 600;
	x = level;
	while( cl-- ) x *= level;
	return x * 600;
    }
}

void AddStatBonus(string stat, mixed f) {
    if(!StatsBonus[stat]) StatsBonus[stat] = ([]);
    StatsBonus[stat][previous_object()] = f;
}

varargs void RemoveStatBonus(string stat, object ob) {
    if(!StatsBonus[stat]) return;
    if(!ob) ob = previous_object();
    if(!ob || !StatsBonus[stat][ob]) return;
    map_delete(StatsBonus[stat], ob);
}

int GetStatBonus(string stat) {
    object *obs;
    int i, x = 0;

    if( !StatsBonus[stat] ) return 0;
    i = sizeof(obs = keys(StatsBonus[stat]));
    while(i--) {
	if( !obs[i] ) map_delete(StatsBonus[stat], obs[i]);
	else x += (int)evaluate(StatsBonus[stat][obs[i]], stat);
    }
    return x;
}

/* string SetResistance(int type, string level) 
 * int type - the type being set (you can do a bitwise | on them)
 * string level "none", "low", "medium", "high", and "immune"
 *
 * description
 * you can have a several levels of resistence for given types of damage
 * setting a resistence level here unsets any other levels of resistence
 */

varargs string SetResistance(int type, string level) {
    if(level == "none") Resistance["none"] |= type;
    else Resistance["none"] &= ~type;
    if(level == "low") Resistance["low"] |= type;
    else Resistance["low"] &= ~type;
    if(level == "medium") Resistance["medium"] |= type;
    else Resistance["medium"] &= ~type;
    if(level == "high") Resistance["high"] |= type;
    else Resistance["high"] &= ~type;
    if(level == "immune") Resistance["immune"] |= type;
    else Resistance["immune"] &= ~type;
}

string GetResistance(int type) {
    if(Resistance["low"] & type) return "low";
    else if(Resistance["medium"] & type) return "medium";
    else if(Resistance["high"] & type) return "high";
    else if(Resistance["immune"] & type) return "immune";
    else return "none";
}

mapping GetResistanceMap(){
    return copy(Resistance);
}

int GetCustomStats() { return CustomStats; }

varargs mixed GetEffectiveVision(mixed location, int raw_score) {
    int array l;
    object env, rider;
    int bonus = GetVisionBonus();
    int a, y, x = 0;

    if(raw_score && !intp(raw_score)){
	location = raw_score;
	raw_score = 0;
    }

    if(sizeof(get_livings(this_object())) && 
      rider = get_random_living(this_object())){
	if(rider->GetProperty("mount") == this_object() &&
	  environment(this_object())){
	    return rider->GetEffectiveVision(environment(this_object()));
	}
    }

    if(location){
	if(objectp(location)) env = location;
	if(stringp(location)){
	    if(!file_exists(location)) location += ".c";
	    if(!file_exists(location)) return 0;
	    env = load_object(location);
	    if(!env) return 0;
	}
    }
    if( Blind && !raw_score) {
	return VISION_BLIND;
    }
    if( !location ) {
	env = environment();
    }
    x = GetRadiantLight(0);
    a = env->GetAmbientLight();
    if(x) x = x/2;
    x += GetRadiantLight(a) + a;
    l = GetLightSensitivity();
    l[0] -= bonus;
    l[1] += bonus;

    if(raw_score && !location){
	return "Low: "+l[0]+", High: "+l[1];
    } 
    if( x >= l[0] && x <= l[1] ) return VISION_CLEAR;
    y = l[0]/3;
    if( x < y ) return VISION_TOO_DARK;
    if( x < (2*y) ) return VISION_DARK;
    if( x < l[0] ) return VISION_DIM;
    y = l[1]/3;
    if( x < (l[1] + y) ) return VISION_LIGHT;
    if( x < (l[1] + (2*y)) ) return VISION_BRIGHT;
    return VISION_TOO_BRIGHT;
}

int array GetLightSensitivity() {
    if( !LightSensitivity ) return ({ 25, 75 });
    else return LightSensitivity;
}

varargs static int array SetLightSensitivity(mixed array val...) {
    if( !val ) error("Null argument to SetLightSensitivity().\n");
    if( sizeof(val) == 1 ) val = val[0];
    if( sizeof(val) != 2 )
	error(sprintf("Invalid arguments to SetLightSensitivity(): %O\n",  val));
    return (LightSensitivity = val);
}

int AddVisionBonus(int x) {
    VisionBonus += x;
    return VisionBonus;
}

int GetVisionBonus() {
    return VisionBonus;
}

static void heart_beat() {
    if( Blind ) {
	Blind->count--;
	if( Blind->count < 1 ) {
	    RemoveBlindness();
	}
    }
}
