*********************************************************** * Improved do_consider commad by Russell T. Brown * * July 18, 2000 * *********************************************************** Ever wish the consider command actually gave an accurate message about a mobs toughness? I did, so today I whipped up this handy little combat simulation. It basically predicts the outcome of combat (1 on 1 only) based on hitpoints, armor class, hit probability and average damage inflicted per round of both characters involved. This is my first code contribution to the list, so I hope it's okay to just post it directly to the list. I do not have a current version of the stock mud to create a patch, so I will just provide instructions to drop the stuff in by hand (which i think is easier anyway.) Feel free to email me if you have questions or just want to tell me how great (or crappy) this code is! Standard Disclaimer: Use this code at your own risk, if something bad happens its not my fault (blah, blah, blah). If you use this code and want to give me credit great! Who am I? Russell T. Brown (russelltbrown@netzero.net), aka Walker of Mosaic (bb10.betterbox.net port: 8000). If you dont want to list my name in the credits of your mud that is okay too, but please leave the in code comments intact in case you give the code to someone else. Okay, this should be a fairly simple addition, only affecting two files: act.informative.c and fight.c. I guess that I should say i am going to assume some minimum level of coding ability and familiarity with circlemud code. If my instructions go over your head, you probably shouldnt be mucking around in the code anyway. (but if they do email me and I will see what I can do for you.) If you do not have oasis OLC installed you will need to replace the macros GET_NDD and GET_SDD with the actual variable names or define your own versions, they are normally found in genmob.h. Here is the step-by-step: 1. In act.informative.c add the following line with the external function declarations. extern int sim_combat(struct char_data *ch, struct char_data *vict); 2. Also in act.informative.c replace the existing do_consider with this one. Actually only a couple of lines have changed, I just provided the whold function for easy replacement. Look for my comments (marked with rtb) in the fuction if you want to edit it instead. ACMD(do_consider) { struct char_data *victim; int diff; one_argument(argument, buf); if (!(victim = get_char_vis(ch, buf, FIND_CHAR_ROOM))) { send_to_char("Consider killing who?\r\n", ch); return; } if (victim == ch) { send_to_char("Easy! Very easy indeed!\r\n", ch); return; } /* delete this if statement if you have a pk mud and want the * improved consider to work on other players -rtb */ if (!IS_NPC(victim)) { send_to_char("Would you like to borrow a cross and a shovel?\r\n", ch); return; } /* okay this is the main change to do_consider, replace subtraction * statement with a call to sim_combat (found in fight.c) -rtb */ diff = sim_combat(ch, victim); /* I also muliplied the numeric values in this if-elseif construct * by a factor of 10, since diff now contains the difference in * combat rounds that ch and victim would take to kill each other. * Tweak these numbers as you see fit for your mud. -rtb */ if (diff <= -100) send_to_char("Now where did that chicken go?\r\n", ch); else if (diff <= -50) send_to_char("You could do it with a needle!\r\n", ch); else if (diff <= -20) send_to_char("Easy.\r\n", ch); else if (diff <= -10) send_to_char("Fairly easy.\r\n", ch); else if (diff == 0) send_to_char("The perfect match!\r\n", ch); else if (diff <= 10) send_to_char("You would need some luck!\r\n", ch); else if (diff <= 20) send_to_char("You would need a lot of luck!\r\n", ch); else if (diff <= 30) send_to_char("You would need a lot of luck and great equipment!\r\n", ch); else if (diff <= 50) send_to_char("Do you feel lucky, punk?\r\n", ch); else if (diff <= 100) send_to_char("Are you mad!?\r\n", ch); else /* removed the last if so no upper limit to diff - rtb */ send_to_char("You ARE mad!\r\n", ch); } 3. Okay that is all the changes to act.informative.c, save and exit the file. 4. In fight.c add the following line to the #includes so that you can use GET_SDD and GET_NDD. I guess this assumes the use of oasis, if you dont have it you will have to work around it. #include "genmob.h" 5. in fight.c add the following lines with the local function declarations. You may not need to add compute_thaco if you already have a similar function in your mud, read comments in step 6 for details. int compute_thaco(struct char_data *ch); int sim_combat(struct char_data *ch, struct char_data *victim); int calc_ave_damage(struct char_data *ch); int fact(int num); 6. Also in fight.c add in the function compute_thaco at the end of the file. Note that the purpose of compute_thaco is to return a char's or mob's thaco with all modifiers already factored in, if you have a similar function already, by all means use that so that you are certain that the thaco is calculated correctly for your mud. The function included is for Mosaic(my mud) and is sleightly altered from the way stock circle calculates the thaco. The function is based on a block of code found in the middle of the function hit in stock circle (v3.0 bpl 16) so compare the function with that block to make certain it is calculating thaco correctly for YOUR mud. /* this function returns a chars thaco with modifiers included */ int compute_thaco(struct char_data *ch) { int comp_thaco; /* Calculate the THAC0 of the attacker */ if (!IS_NPC(ch)) comp_thaco = thaco((int) GET_CLASS(ch), (int) GET_LEVEL(ch)); else /* THAC0 for monsters is set in the HitRoll */ comp_thaco = 20; comp_thaco -= str_app[GET_STR(ch)].tohit; comp_thaco -= GET_HITROLL(ch); comp_thaco -= MAX(0, MIN(5, (int) ((GET_INT(ch) - 14) / 2))); comp_thaco -= MAX(0, MIN(5, (int) ((GET_WIS(ch) - 14) / 2))); return comp_thaco; } 7. Also in fight.c drop in sim_combat at the end of the file. It should work as-is with stock circle and compute_thaco from step 6. if using your own thaco function replace the two calls to compute_thaco as appropriate. Note there is a sprintf and send_to_char near the end of the function that I added for testing purposes, if you dont want your players to see that info, comment it out. /* This function is to provide a more realistic assessment of a mob's * combat strength vs that of a player. I basically wanted a consider * command that took more than the mob's level into accout, since not * all 10th lvl mobs are the same difficulty, especially on a mud with * multiple builders. Note that is is a prediction and not 100% acturate * give the randomness of combat and the amount of integer division * performed below. However I think thats good, since a player should * have a rough idea how though a mob is but can not know its stats * completely. -rtb 7/18/2k */ int sim_combat(struct char_data *ch, struct char_data *vict) { int ch_dam, ch_hp, ch_thaco, ch_ac, ch_hitprob, ch_erk; int vict_dam, vict_hp, vict_thaco, vict_ac, vict_hitprob, vict_erk; /* get combat info for ch */ ch_thaco = compute_thaco(ch); ch_ac = compute_armor_class(ch)/ 10; ch_dam = calc_ave_damage(ch); ch_hp = GET_HIT(ch); /* get combat info for vict */ vict_thaco = compute_thaco(vict); vict_ac = compute_armor_class(vict)/ 10; vict_dam = calc_ave_damage(vict); vict_hp = GET_HIT(vict); /* calc ch_hitprob: predicted times char will hit out of 20 tries */ ch_hitprob = 21 + vict_ac - ch_thaco; ch_hitprob = MIN(19, MAX(1, ch_hitprob)); /* calc ch_erk: char's estimated rounds to kill vict */ ch_erk = vict_hp / ch_dam; ch_erk = (ch_erk * 20) / ch_hitprob; /* calc vict_hitprob: predicted times vict will hit out of 20 tries */ vict_hitprob = 21 + ch_ac - vict_thaco; vict_hitprob = MIN(19, MAX(1, ch_hitprob)); /* calc vict_erk: vict's estimated rounds to kill ch */ vict_erk = ch_hp / vict_dam; vict_erk = (vict_erk * 20) / vict_hitprob; /* I added this send_to_char for testing only, you may want to comment it out after you know it is working */ sprintf(buf, "You could kill %s in %d rounds.\r\n" "%s could kill you in %d rounds.\r\n", GET_NAME(vict), ch_erk, GET_NAME(vict), vict_erk); send_to_char(buf, ch); /* now compare ch_erk to vict_erk to predict winner */ return (ch_erk - vict_erk); } 8. Also in fight.c add the function fact at the end of the file. This is a simple function to calcuate the factorial of a number and is called from calc_ave_damage (step 9). I would have preferred using a standard c library function, but there doesnt seem to be one (or at least I couldn't find it). If anyone knows a standard function for factiorials, let me know. /* calculate factorial of a number */ int fact(int num) { int i, sum; sum = 0; if (num == 0) return 0; else { for(i = 1; (i <= num); i++) sum += i; return sum; } } 9. Also in fight.c add the function calc_ave_damage at the end of the file. The purpose of calc_ave_damage is to calculate the average damage a character can do in one round and return that value. This actually turned out to be the most complex part of the whole addition. It is fairly straight foreword for stock code where both mobs and chars only have one attack per round, but starts to get fairly complex fast with multiple attacks for both mobs and chars. Any way I have included two versions, first the stock version and then the version that is specific for my mud. The specific version shows at least how i dealt with 2 weapon style (dual wield), mulitple attack skills and two handed weapons, among other things. 11. After adding in calc_ave_damage recompile and cross your fingers. If you have any problems, email me and I will see if I can point you towards fixing them. /* stock circle version: Note this should work but has not been * thoroughly tested since it wont work for my very non-stock mud. It * does compile without errors -rtb 7/18/2k * if GET_SDD & GET_NDD are undefined, #include "genmob.h" up top. * calculate the average damage per hit/round that a char can inflict. */ int calc_ave_damage(struct char_data *ch) { struct obj_data *weap; int damage = 0; weap = GET_EQ(ch, WEAR_WIELD); if (IS_MOB(ch)) { if (weap && GET_OBJ_TYPE(weap) == ITEM_WEAPON) { damage = fact(GET_OBJ_VAL(weap, 2))/GET_OBJ_VAL(weap, 2); damage *= GET_OBJ_VAL(weap, 1); } else { damage = fact(GET_SDD(ch))/GET_SDD(ch); damage *= GET_NDD(ch); } damage += GET_DAMROLL(ch); } else { /* for PCs */ if (weap && GET_OBJ_TYPE(weap) == ITEM_WEAPON) { damage = fact(GET_OBJ_VAL(weap, 2))/GET_OBJ_VAL(weap, 2); damage *= GET_OBJ_VAL(weap, 1); damage += GET_DAMROLL(ch); } else /* no weapons used, calc with 1d2 damage */ damage = 1 + GET_DAMROLL(ch); } /* okay now return damage, it contains ave damage per round, assuming a hit. hit/miss ratio is taken care of in sim_combat */ return(damage); } 12. The following is the actual version that is running on Mosaic (my mud). It is provided purely for insight for such things as multiple attacks, 2 weapon style (dual wield), handling mobs with weapons. On Mosaic mobs can have multiple attacks, up to MAX_MOB_ATTACKS, which is currently set to 4. Each attack has its own attack type, and damage dice. If a mob has a weapon, the weapon damage and type will only be substituted for attacks with type hit (0), thus you can have a warrior with 2 attacks hit(1d4)/kick(1d6) and if he has a sword will slash for the sword damage and kick for 1d6 each round. We also have equipment positions for prime, second, and both hands for weapons (plus 3rd + 4th hands for some races). /* mosaic specific version: this is NOT stock code. * calculate the average damage per hit/round that a char can inflict */ int mosaic_calc_ave_damage(struct char_data *ch) { struct obj_data *weap, *weap2; int i, damage, single_hit, weapon_config; damage = 0; if (IS_MOB(ch)) { /* double weapon returns 0-3 based on the weapons wielded */ weapon_config = double_weapon(ch); /* on mosaic, mobs can have multiple attacks so a loop is used */ for(i = 0; (i < MAX_MOB_ATTACKS); i++) { if ((GET_ATTACK(ch, i) == -1) || (GET_SDD(ch, i) == 0)) /* remaining attacks are not used skip out of loop */ i = MAX_MOB_ATTACKS; else if ((GET_ATTACK(ch, i) == 0) && (weapon_config != 0)) { /* attack type hit, replace with weapon damage */ if (weapon_config == 3) /* 2 handed weapon used */ weap = GET_EQ(ch, WEAR_BOTH_HANDS); else if (weapon_config == 2) { /* 2 weapons used: */ if ((i == 0) || (i == 2)) /* for 1st & 3rd attacks */ weap = GET_EQ(ch, WEAR_PRIME_HAND); /* use weapon in prime hand */ else /* for 2nd & 4th attacks */ weap = GET_EQ(ch, WEAR_SECOND_HAND);/* use weapon in second hand */ } else /* weapon_config == 1 */ weap = GET_EQ(ch, WEAR_PRIME_HAND); damage += (fact(GET_OBJ_VAL(weap, 2))/GET_OBJ_VAL(weap, 2)) * GET_OBJ_VAL(weap, 1); damage += GET_DAMROLL(ch); } else { /* attack type other than hit or no weapon equiped, use regular attack damage */ damage += (fact(GET_SDD(ch, i))/GET_SDD(ch, i)) * GET_NDD(ch, i); damage += GET_DAMROLL(ch); } } } else { /* player's section */ /* calc ave damage for single hit with main weapon */ weap = GET_EQ(ch, WEAR_PRIME_HAND); if (weap && GET_OBJ_TYPE(weap) == ITEM_WEAPON) { /* single handed weapon */ single_hit = fact(GET_OBJ_VAL(weap, 2))/GET_OBJ_VAL(weap, 2); single_hit *= GET_OBJ_VAL(weap, 1); single_hit += GET_DAMROLL(ch); /* check for second weapon */ if (GET_SKILL(ch, SKILL_2WEAPON_STYLE)) { weap2 = GET_EQ(ch, WEAR_SECOND_HAND); if (weap2 && GET_OBJ_TYPE(weap2) == ITEM_WEAPON) { damage = fact(GET_OBJ_VAL(weap2, 2))/GET_OBJ_VAL(weap2, 2); damage *= GET_OBJ_VAL(weap2, 1); damage += GET_DAMROLL(ch); /* multiply damage by skill% since char gets second weapon attack roughly that percent of rounds */ damage *= GET_SKILL(ch, SKILL_2WEAPON_STYLE); } } } else { weap = GET_EQ(ch, WEAR_BOTH_HANDS); if (weap && GET_OBJ_TYPE(weap) == ITEM_WEAPON) { /* two handed weapon */ single_hit = fact(GET_OBJ_VAL(weap, 2))/GET_OBJ_VAL(weap, 2); single_hit *= GET_OBJ_VAL(weap, 1); single_hit += GET_DAMROLL(ch); /* check for second weapon, yep some chars got more than 2 hands */ if (GET_SKILL(ch, SKILL_2WEAPON_STYLE)) { weap2 = GET_EQ(ch, WEAR_THIRD_HAND); if (weap2 && GET_OBJ_TYPE(weap2) == ITEM_WEAPON) { damage = fact(GET_OBJ_VAL(weap2, 2))/GET_OBJ_VAL(weap2, 2); damage *= GET_OBJ_VAL(weap2, 1); damage += GET_DAMROLL(ch); /* multiply damage by skill% since char gets second weapon attack roughly that percent of rounds */ damage *= GET_SKILL(ch, SKILL_2WEAPON_STYLE); } } } else { /* no weapons used, calc with 1d2 damage */ single_hit = 1 + GET_DAMROLL(ch); } } /* damage contains ave of second weapon attack if any, single_hit contains ave of primary weapon attack */ damage += single_hit; /* add first attack */ if ((GET_RACE(ch) == RACE_QUICKLING) || AFF_FLAGGED(ch, AFF_HASTE)) damage += single_hit; /* add another attack for hasted chars */ /* now add % of average damage for each multiple attack skill */ damage += (single_hit) * GET_SKILL(ch, SKILL_SECOND_ATTACK); damage += (single_hit) * GET_SKILL(ch, SKILL_THIRD_ATTACK); damage += (single_hit) * GET_SKILL(ch, SKILL_FOURTH_ATTACK); damage += (single_hit) * GET_SKILL(ch, SKILL_FIFTH_ATTACK); } /* okay now return damage, it contains ave damage per round, assuming all hits. hit/miss ratio is taken care of in sim_combat */ /* that was actually more complex than i expected -rtb */ return(damage); }