/* /lib/combat.c * from the Dead Souls LPC Library * combat events and data * created by Descartes of Borg 950124 * Version: @(#) combat.c 1.40@(#) * Last modified: 96/11/17 */ #include <lib.h> #include <rounds.h> #include <daemons.h> #include <position.h> #include <damage_types.h> #include <function.h> inherit LIB_RACE; inherit LIB_CLASSES; inherit LIB_COMBATMSG; private int Wimpy; private string WimpyCommand; private static int cParalyzed, tNextRound; private static string TargetLimb, Party; private static object CurrentEnemy; private static function fParalyzed, fNextRound; private static object *Hostiles, *Enemies, *SpecialTargets; string GetName(); mixed GetProperty(string key); string array AddChannel(mixed val); string array RemoveChannel(mixed val); int eventForce(mixed args); int eventExecuteAttack(mixed target); int eventWeaponRound(mixed target, mixed val); void eventWeaponAttack(object target, object weapon, int num); int eventMeleeRound(mixed target, function f); void eventMeleeAttack(object target, string limb); int eventMagicRound(mixed target, function f); int eventWimpy(); static void create() { race::create(); classes::create(); Hostiles = ({}); Enemies = ({}); CurrentEnemy = SpecialTargets = 0; Party = 0; fParalyzed = 0; fNextRound = 0; cParalyzed = 0; tNextRound = ROUND_UNDEFINED; Wimpy = 0.20; WimpyCommand = "go out"; } /* ***************** /lib/combat.c data functions ***************** */ object array GetEnemies() { return Enemies; } int AddEnemy(object ob) { if( !ob || (member_array(ob, Enemies) != -1) ) { return 0; } if( !living(ob) ) { return 0; } Enemies += ({ ob }); return 1; } int RemoveEnemy(object ob) { if( !ob || (member_array(ob, Enemies) == -1) ) { return 0; } Enemies -= ({ ob }); return 1; } object SetCurrentEnemy(object ob) { if( !ob ) { return (CurrentEnemy = 0); } if( !living(ob) ) { return CurrentEnemy; } if( member_array(ob, Enemies) == -1 ) { AddEnemy(ob); } return (CurrentEnemy = ob); } static object ResetCurrentEnemy() { object array obs; obs = filter(GetEnemies(), (: $1 && environment() == environment($1) :)); if( !sizeof(obs) ) { return 0; } return SetCurrentEnemy(obs[random(sizeof(obs))]); } object GetCurrentEnemy() { return CurrentEnemy; } private static void SortEnemies() { if( !sizeof(Enemies = filter(Enemies, (: ($1 && living($1)) :))) ) { Hostiles = ({}); CurrentEnemy = 0; return; } Hostiles = (Hostiles & Enemies); } int AddHostile(object ob) { if( !ob || (member_array(ob, Hostiles) != -1) ) { return 0; } if( !living(ob) ) { return 0; } Hostiles += ({ ob }); return 1; } int RemoveHostile(object ob) { if( !ob || (member_array(ob, Hostiles) == -1) ) { return 0; } Hostiles -= ({ ob }); return 1; } object array GetHostiles() { return Hostiles; } object array GetSpecialTarget() { return SpecialTargets; } varargs int SetParalyzed(int count, function f) { if(count < 1) { count = cParalyzed = 0; fParalyzed = 0; } else { fParalyzed = f; cParalyzed = count; } return count; } int GetParalyzed() { return cParalyzed; } string SetParty(string str) { if( file_name(previous_object()) != PARTY_D ) return Party; if( str ) AddChannel(str); else RemoveChannel(Party); return (Party = str); } string GetParty() { return Party; } varargs int SetAttack(mixed target, function callback, int type) { int i; if( objectp(target) ) target = ({ target }); if( target ) { if( member_array(this_object(), target) != -1 ) return 0; if( !GetCurrentEnemy() ) call_out((: eventExecuteAttack :), 0, target); i = sizeof(target); while(i--) if( AddEnemy(target[i]) ) AddHostile(target[i]); SpecialTargets = target; } fNextRound = callback; tNextRound = (type || ROUND_UNDEFINED); return 1; } int GetLevel() { return classes::GetLevel(); } int GetInCombat() { return sizeof(filter(GetEnemies(), (: $1 && (environment($1) == environment()) :))); } int GetBaseStatLevel(string stat) { return race::GetBaseStatLevel(stat); } float SetWimpy(float wimpy) { return (Wimpy = wimpy); } float GetWimpy() { return Wimpy; } string SetWimpyCommand(string cmd) { return (WimpyCommand = cmd); } string GetWimpyCommand() { return WimpyCommand; } int GetMaxCarry() { return race::GetMaxCarry(); } int GetMagicChance(int val) { val = GetStatLevel("intelligence")/2 + (3*val)/2; return val + GetLuck(); } int GetMagicResistance() { int val = GetStatLevel("wisdom")/2 + (3*GetSkillLevel("magic defense"))/2; return val + GetLuck(); } int GetCombatChance(int val) { val = val + random((val * GetMobility())/250); val = (val/(3- visibility()) + GetLuck()); if( GetBlind() ) { return val/10; } else { return val; } } int GetDefenseChance(int val) { val = (val * GetMobility())/50; val = (val/(3- visibility()) + (GetLuck()/2)); if( GetBlind() ) { return val/10; } else { return val; } } int GetCombatBonus(int level) { int diff = level - GetLevel(); if( diff >= 6 && diff < 16 ) { return 4; } else if( diff >= 1 && diff < 6 ) { return 3; } else if( diff >= -9 && diff < 1 ) { return 2; } else if( diff >= -20 && diff < -9 ) { return 1; } else { return 0; } } static int GetDamage(int power, string skill) { int x = GetSkillLevel(skill); if( power < 1 ) { return 0; } else if( power > 100 ) { power = 100; } if( x < 1 ) { x = 1; } else if( x > 100 ) { x = 100; } x = (x * (power/2 + random(power/2))/10); x += (GetLuck()/2) + GetStatLevel("strength")/8; if( x < 1 ) { // negative luck or cursed strength return 1; } return x; } int CanWeapon(object target, string type, int hands, int num) { string limb = target->GetRandomLimb(TargetLimb); int chance = (7*GetSkillLevel(type+" attack") + 3*GetStatLevel("coordination"))/10; int div = 2; int x, y; if(hands > 1) { if(GetSkillLevel("multi-hand")) { chance = (chance/2) + (GetSkillLevel("multi-hand")/75)*(chance/2); } else { /* If you are really strong you can use multihand a bit */ chance *= GetStatLevel("strength")/300; div += (hands-1); } } if(num > 1) { if(GetSkillLevel("multi-weapon")) { chance = (chance/2) + (GetSkillLevel("multi-weapon")/75)*(chance/2); } else { /* If you are really coordinated you can use multiweap a bit */ chance *= GetStatLevel("coordination")/300; div += (num-1); } } chance = GetCombatChance(chance/div); x = random(chance); y = random(10); if( x <= y ) { if( x > y/2 ) { TargetLimb = target->GetRandomLimb(0); } else { TargetLimb = 0; } } else { TargetLimb = limb; } return chance; } int CanMelee(object target) { string limb = target->GetRandomLimb(TargetLimb); int chance = ( 7*GetSkillLevel("melee attack") + 3*GetStatLevel("coordination") )/10; int y = random(10); int x; chance = GetCombatChance(chance/2); x = random(chance); if( x <= y ) { if( x > y/2 ) { TargetLimb = target->GetRandomLimb(0); } else { TargetLimb = 0; } } else { TargetLimb = limb; } return chance; } static int Destruct() { if( GetParty() ) PARTY_D->eventLeaveParty(this_object()); return 1; } /* ***************** /lib/combat.c events ***************** */ varargs int eventDie(object agent) { object ob; int x; x = race::eventDie(agent); if( x != 1 ) { return x; } foreach(ob in GetEnemies()) { if( ob ) { ob->eventEnemyDied(this_object()); } } environment()->eventLivingDied(this_object(), agent); Enemies = ({}); return 1; } int eventExecuteAttack(mixed target) { object array weapons; function f = fNextRound; int type = tNextRound; int position = GetPosition(); fNextRound = 0; tNextRound = ROUND_UNDEFINED; if( position == POSITION_LYING || position == POSITION_SITTING ) { eventPrint("You can't fight unless you are standing up!"); return 0; } if( arrayp(target) ) { if( !f || (functionp(f) & FP_OWNER_DESTED) ) { return 0; /* built in only handles 1 targ */ } target = filter(target, function(object ob) { if( !ob ) { return 0; } if( !ob->eventPreAttack(this_object()) ) { return 0; } return 1; }); if( !sizeof(target) ) { return 0; } } else if( !target->eventPreAttack(this_object()) ) { return 0; } switch(type) { case ROUND_UNDEFINED: if( functionp(f) && !(functionp(f) & FP_OWNER_DESTED) ) { return evaluate(f, target); } if( sizeof(weapons = GetWielded()) ) { return eventWeaponRound(target, weapons); } else { return eventMeleeRound(target, 0); } case ROUND_MAGIC: return eventMagicRound(target, f); case ROUND_MELEE: return eventMeleeRound(target, functionp(f) ? f : 0); case ROUND_WEAPON: return eventWeaponRound(target, functionp(f) ? f : GetWielded()); case ROUND_OTHER: if( functionp(f) && !(functionp(f) & FP_OWNER_DESTED) ) { return evaluate(f); } else { return 0; } default: return 0; } return 0; } int eventWeaponRound(mixed target, mixed val) { object array weapons = 0; function f = 0; if( arrayp(val) ) { weapons = val; } else if( functionp(val) && !(functionp(val) & FP_OWNER_DESTED) ) { f = val; } else { return 0; } if( f ) { evaluate(f, target); } else { int count = sizeof(weapons); foreach(object weapon in weapons) { if( !target ) { break; } eventWeaponAttack(target, weapon, count); } } return target->GetDying(); } void eventWeaponAttack(object target, object weapon, int num){ string weapon_type = weapon->GetWeaponType(); int hands = weapon->GetHands(); int level = target->GetLevel(); int bonus = GetCombatBonus(level); int power, pro, con; if( target->GetDying() ) { return; } pro = CanWeapon(target, weapon_type, hands, num); power = random(pro); con = target->GetDefenseChance(target->GetSkillLevel(weapon_type + " defense")); if( !TargetLimb ) { // If the thing stood still, I still missed eventTrainSkill(weapon_type + " attack", pro, 0, 0, bonus); if( hands > 1 ) { eventTrainSkill("multi-hand", pro, 0, 0, bonus); } if( num > 1 ) { eventTrainSkill("multi-weapon", pro, 0, 0, bonus); } SendWeaponMessages(target, -2, weapon, TargetLimb); } else if( !target->eventReceiveAttack(power, weapon_type, this_object()) ) { // Target avoided the attack eventTrainSkill(weapon_type + " attack", pro, con, 0, bonus); if( hands > 1 ) { eventTrainSkill("multi-hand", pro, con, 0, bonus); } if( num > 1 ) { eventTrainSkill("multi-weapon", pro, con, 0, bonus); } SendWeaponMessages(target, -1, weapon, TargetLimb); } else { // I hit, but how hard did I hit? int damage_type, damage, weapon_damage, actual_damage; eventTrainSkill(weapon_type + " attack", pro*2, con, 1, bonus); damage_type = weapon->GetDamageType(); damage = (weapon->eventStrike(target) * pro)/100; damage = GetDamage(damage, weapon_type + " attack"); actual_damage = target->eventReceiveDamage(this_object(), damage_type, damage, 0, TargetLimb); if( actual_damage < 1 ) { actual_damage = 0; } weapon_damage = damage - actual_damage; if( weapon_damage > 0 ) { weapon->eventReceiveDamage(this_object(), BLUNT, weapon_damage, 0, TargetLimb); } if( !target->GetDying() ) { SendWeaponMessages(target, actual_damage, weapon, TargetLimb); } else { eventPrint(possessive_noun(target) + " death is now on your " "head."); target->eventPrint(GetName() + " is your murderer."); environment()->eventPrint(possessive_noun(target) + " death is now on " + possessive_noun(this_object()) + " head.", ({ this_object(), target })); } } } int eventMeleeRound(mixed target, function f) { string array limbs = GetLimbs() - ({ GetTorso() }); int count = sizeof(limbs); int attacks; if( count < 1 ) { return 0; } if( !f || (functionp(f) & FP_OWNER_DESTED) ) { attacks = 1 + random(GetSkillLevel("melee attack"))/50; while( attacks-- ) { if( target->GetDying() ) { break; } eventMeleeAttack(target, limbs[random(count)]); } } else { evaluate(f, target, limbs[random(count)]); } return target->GetDying(); } void eventMeleeAttack(object target, string limb) { string array limbs; int pro, con; int chance; if( target->GetDying() ) { return; } pro = CanMelee(target); con = target->GetDefenseChance(target->GetSkillLevel("melee defense")); chance = random(pro); if( !TargetLimb ) { // I *really* missed SendMeleeMessages(target, -2); eventTrainSkill("melee attack", pro, 0, 0, GetCombatBonus(target->GetLevel())); } else if( !target->eventReceiveAttack(chance, "melee", this_object()) ) { // Enemy dodged my attack SendMeleeMessages(target, -1); eventTrainSkill("melee attack", pro, con, 0, GetCombatBonus(target->GetLevel())); } else { int x; // I hit, how hard? eventTrainSkill("melee attack", pro, con, 1, GetCombatBonus(target->GetLevel())); x = GetDamage(3*chance/4, "melee attack"); x = target->eventReceiveDamage(this_object(), BLUNT, x, 0, TargetLimb); if( !target->GetDying() ) { SendMeleeMessages(target, (x > 0) ? x : 0, TargetLimb); } else { eventPrint(possessive_noun(target) + " death is now " "on your head."); target->eventPrint(GetName() + " is your murderer."); environment()->eventPrint(possessive_noun(target) + " death is now on " + possessive_noun(this_object()) + " head.", ({ this_object(), target })); } } } int eventMagicRound(mixed target, function f) { evaluate(f, target); return target->GetDying(); } mixed eventBite(object target) { int pro = CanMelee(target); int con = target->GetDefenseChance(target->GetSkillLevel("melee defense")); int x = random(pro); if( environment() != environment(target) ) { eventPrint(target->GetName() + " has gone away."); return 1; } if( TargetLimb ) { if( target->eventReceiveAttack(x, "melee", this_object()) ) { x = GetDamage(pro/4, "melee attack"); x = target->eventReceiveDamage(this_object(), KNIFE, x, 0, TargetLimb); if( x < 1 ) { target->eventPrint(possessive_noun(this_object()) + " bite " "is nothing more than a pinch."); eventPrint("Your bite is nothing more than a pinch."); environment()->eventPrint(possessive_noun(this_object()) + " bite is nothing more than a " "pinch.", ({ target, this_object() })); } else { target->eventPrint(GetName() + " bites you in the " + TargetLimb + "!"); eventPrint("You bite " + target->GetName() + " in the " + TargetLimb + "!"); environment()->eventPrint(GetName() + " bites " + target->GetName() + "in the " + TargetLimb + "!", ({ target, this_object() })); } eventTrainSkill("melee attack", pro, con, 1, GetCombatBonus(target->GetLevel())); } else { target->eventPrint("You avoid " + possessive_noun(this_object()) + " bite."); eventPrint(target->GetName() + " avoids your bite."); environment()->eventPrint(target->GetName() + " avoids " + possessive_noun(this_object()) + " bite.", ({ this_object(), target })); eventTrainSkill("melee attack", pro, con, 0, GetCombatBonus(target->GetLevel())); } } else { eventPrint("You flounder about like a buffoon."); environment()->eventPrint(GetName() + " flounders about like a " "buffoon.", this_object()); } return 1; } int eventPreAttack(object agent) { if( agent == this_object() ) { return 0; } if( environment()->GetProperty("no attack") ) { return 0; } if( GetDying() ) { return 0; } if( playerp(this_object()) && playerp(agent) ) { // No PK if( !environment()->CanAttack( agent, this_object() ) ) { return 0; } } if( AddEnemy(agent) ) { AddHostile(agent); } return 1; } varargs int eventReceiveAttack(int speed, string def, object agent) { int x, y, pro, level, bonus; if( !agent ) { agent = previous_object(); } if( !living(agent) ) { level = 1; bonus = 1; } else { level = agent->GetLevel(); bonus = GetCombatBonus(level); } if( AddEnemy(agent) ) { AddHostile(agent); } if( def == "magic" ) { pro = GetMagicResistance(); if( (x = random(pro)) > speed ) { eventTrainSkill("magic defense", pro, speed, 1, bonus); return 0; } else { eventTrainSkill("magic defense", pro, speed, 0, bonus); return 1; } } else { pro = GetDefenseChance(GetSkillLevel(def + " defense")); x = random(pro = pro/2); if( x > speed ) { eventTrainSkill(def + " defense", pro, speed, 1, bonus); return 0; } else { eventTrainSkill(def + " defense", pro, speed, 0, bonus); return 1; } } } void eventKillEnemy(object ob) { int level; if( !ob ) return; level = ob->GetLevel(); if( member_array(ob, GetHostiles()) == -1 ) { int x; eventTrainSkill("murder", GetLevel(), level, 1,GetCombatBonus(level)); x = (int)ob->GetMorality(); if( x > 0 ) x = -x; else if( GetMorality() > 200 ) x = 100; else x = 0; eventMoralAct(x); } } void eventDestroyEnemy(object ob) { int level; if( !ob ) return; level = ob->GetLevel(); eventTrainSkill("faith", GetLevel(), level, 1, GetCombatBonus(level)); } void eventEnemyDied(object ob) { if( !ob ) return; Enemies -= ({ ob }); Hostiles -= ({ ob }); } varargs int eventReceiveDamage(object agent, int type, int x, int internal, mixed limbs) { int hp; x = race::eventReceiveDamage(agent, type, x, internal, limbs); if( !Wimpy ) return x; if( (hp = GetHealthPoints()) < 1 ) return x; if( Wimpy < percent(hp, GetMaxHealthPoints()) ) return x; call_out((: eventWimpy :), 0); return x; } mixed eventTurn(object who) { int defense; if( !GetUndead() ) { return 0; } if( GetProperty("no turn") ) { if( !who ) { return 0; } else { int x = GetProperty("no turn"); environment(who)->eventPrint("The power of the undead " "turns on " + who->GetName() + ".", who); who->eventPrint("The power of the undead turns on you."); if( x > random(100) + 1 ) { who->eventDie(this_object()); } else { who->eventReceiveDamage(this_object(), MAGIC, random(50), 1); } return 0; } } if( !who ) { race::eventTurn(who); return 1; } defense = GetMagicResistance(); if( who->GetSkillLevel("faith") < defense ) { who->eventPrint("You writhe in pain."); environment(who)->eventPrint(who->GetName() + " writhes in pain.", who); who->eventReceiveDamage(this_object(), MAGIC, random(defense), 1); eventTrainSkill("magic defense", defense, who->GetSkillLevel("faith"), 1, GetCombatBonus(who->GetLevel())); return 0; } race::eventTurn(who); return 1; } int eventWimpy() { object env = environment(); string dir, cmd; if( !env || !GetInCombat() ) { return 0; } cmd = WimpyCommand || "go out"; if( (sscanf(cmd, "go %s", dir) && !((string)env->GetExit(dir))) || (sscanf(cmd, "enter %s", dir) && !((string)env->GetEnter(dir))) ) { string *tmp; tmp = filter((string *)environment()->GetExits(), (: !((string)environment()->GetDoor($1)) :)); if( !sizeof(tmp) ) { tmp = filter((string *)environment()->GetEnters(), (: !((string)environment()->GetDoor($1)) :)); if( !sizeof(tmp) ) { eventPrint("You need to escape, but you have nowhere to go!"); return 0; } cmd = "enter " + tmp[random(sizeof(tmp))]; } else cmd = "go " + tmp[random(sizeof(tmp))]; } return eventForce(cmd); } static void heart_beat() { race::heart_beat(); if( GetSleeping() || GetDying() ) { return; } if( cParalyzed > 0 ) { cParalyzed--; if( cParalyzed < 1 ) { function f; f = fParalyzed; fParalyzed = 0; if( functionp(f) && !(functionp(f) & FP_OWNER_DESTED) ) { evaluate(f); } else { eventPrint("You can move again."); } } return; } if( sizeof(Enemies) ) { object ob; SortEnemies(); if( SpecialTargets ) { foreach(object target in SpecialTargets) { object tmp; if( objectp(SetCurrentEnemy(target)) ) { break; } } eventExecuteAttack(SpecialTargets); SpecialTargets = 0; } else if( ob = ResetCurrentEnemy() ) { eventExecuteAttack(ob); } } else if( tNextRound != ROUND_UNDEFINED && functionp(fNextRound) ) { function f; f = fNextRound; tNextRound = ROUND_UNDEFINED; evaluate(f); } }