/************************************************************************** * # # # ## # # ### ## ## ### http://www.lyonesse.it * * # # # # # ## # # # # # * * # # # # # ## ## # # ## ## ## # # ## * * # # # # # ## # # # # # # # # # # # * * ### # ## # # ### ## ## ### # # #### ## Ver. 1.0 * * * * -Based on CircleMud & Smaug- Copyright (c) 2001-2002 by Mithrandir * * * * ********************************************************************** */ /* ************************************************************************ * File: fight.c Part of CircleMUD * * Usage: Combat system * * * * All rights reserved. See license.doc for complete information. * * * * Copyright (C) 1993, 94 by the Trustees of the Johns Hopkins University * * CircleMUD is based on DikuMUD, Copyright (C) 1990, 1991. * ************************************************************************ */ #include "conf.h" #include "sysdep.h" #include "structs.h" #include "utils.h" #include "comm.h" #include "handler.h" #include "interpreter.h" #include "db.h" #include "spells.h" #include "screen.h" #include "constants.h" #include "clan.h" /* Structures */ CHAR_DATA *combat_list = NULL; /* head of l-list of fighting chars */ CHAR_DATA *next_combat_list = NULL; /* External structures */ extern MESSAGE_LIST fight_messages[MAX_MESSAGES]; extern CLAN_POLITIC_DATA politics_data; extern int pk_allowed; /* see config.c */ extern int max_exp_gain; /* see config.c */ extern int max_exp_loss; /* see config.c */ extern int max_npc_corpse_time, max_pc_corpse_time; /* External procedures */ ACMD(do_flee); ACMD(do_say); OBJ_DATA *get_missile_cont( CHAR_DATA *ch, int type ); char *fread_action(FILE *fl, int nr); int backstab_mult(int level); int thaco(int ch_class, int level); int ok_damage_shopkeeper(CHAR_DATA *ch, CHAR_DATA *victim); int wild_track(CHAR_DATA *ch, CHAR_DATA *vict, bool silent); int find_first_step(ROOM_DATA *src, ROOM_DATA *target); int kills_limit_xpgain(CHAR_DATA *ch, CHAR_DATA *victim, int exp); void start_hunting(CHAR_DATA *ch, CHAR_DATA *vict); void diag_char_to_char(CHAR_DATA * i, CHAR_DATA * ch); void stop_memorizing(CHAR_DATA *ch); void weapon_spells(CHAR_DATA *ch, CHAR_DATA *vict, OBJ_DATA *wpn); void brag(CHAR_DATA *ch, CHAR_DATA *victim); /* local functions */ void perform_group_gain(CHAR_DATA *ch, int base, CHAR_DATA *victim); void dam_message(int dam, CHAR_DATA *ch, CHAR_DATA *victim, int w_type); void appear(CHAR_DATA *ch); void load_messages(void); void check_killer(CHAR_DATA *ch, CHAR_DATA *vict); void make_corpse(CHAR_DATA *ch); void change_alignment(CHAR_DATA *ch, CHAR_DATA *victim); void death_cry(CHAR_DATA *ch); void raw_kill(CHAR_DATA *ch); void die(CHAR_DATA *ch, CHAR_DATA *killer); void group_gain(CHAR_DATA *ch, CHAR_DATA *victim); void solo_gain(CHAR_DATA *ch, CHAR_DATA *victim); char *replace_string(const char *str, const char *weapon_singular, const char *weapon_plural); void perform_violence(void); int compute_armor_class(CHAR_DATA *ch); int compute_thaco(CHAR_DATA *ch, CHAR_DATA *vict); int get_weapon_prof(CHAR_DATA *ch, OBJ_DATA *wield); /* Weapon attack texts */ ATTACK_HIT_TYPE attack_hit_text[] = { {"hit", "hits"}, /* 0 */ {"sting", "stings"}, {"whip", "whips"}, {"slash", "slashes"}, {"bite", "bites"}, {"bludgeon", "bludgeons"}, /* 5 */ {"crush", "crushes"}, {"pound", "pounds"}, {"claw", "claws"}, {"maul", "mauls"}, {"thrash", "thrashes"}, /* 10 */ {"pierce", "pierces"}, {"blast", "blasts"}, {"punch", "punches"}, {"stab", "stabs"} /* 14 */ }; /* The Fight related routines */ void appear(CHAR_DATA *ch) { if (affected_by_spell(ch, SPELL_INVISIBLE)) affect_from_char(ch, SPELL_INVISIBLE); REMOVE_BIT(AFF_FLAGS(ch), AFF_INVISIBLE | AFF_HIDE); if (GET_LEVEL(ch) < LVL_IMMORT) act("$n slowly fades into existence.", FALSE, ch, 0, 0, TO_ROOM); else act("You feel a strange presence as $n appears, seemingly from nowhere.", FALSE, ch, 0, 0, TO_ROOM); } int compute_armor_class(CHAR_DATA *ch) { OBJ_DATA *wielded = GET_EQ(ch, WEAR_WIELD); int armorclass = GET_AC(ch); if (AWAKE(ch)) armorclass += dex_app[GET_DEX(ch)].defensive * 10; if (IS_WARRIOR(ch)) if (wielded && GET_OBJ_TYPE(wielded) == ITEM_WEAPON) armorclass += wpn_prof[get_weapon_prof(ch, wielded)].to_ac * 10; return (MAX(-100, armorclass)); /* -100 is lowest */ } void load_messages(void) { FILE *fl; MESSAGE_TYPE *messages; char chk[128]; int i, type; if (!(fl = fopen(MESS_FILE, "r"))) { log("SYSERR: Error reading combat message file %s: %s", MESS_FILE, strerror(errno)); exit(1); } for (i = 0; i < MAX_MESSAGES; i++) { fight_messages[i].a_type = 0; fight_messages[i].number_of_attacks = 0; fight_messages[i].msg = 0; } fgets(chk, 128, fl); while (!feof(fl) && (*chk == '\n' || *chk == '*')) fgets(chk, 128, fl); while (*chk == 'M') { fgets(chk, 128, fl); sscanf(chk, " %d\n", &type); for (i = 0; (i < MAX_MESSAGES) && (fight_messages[i].a_type != type) && (fight_messages[i].a_type); i++); if (i >= MAX_MESSAGES) { log("SYSERR: Too many combat messages. Increase MAX_MESSAGES and recompile."); exit(1); } CREATE(messages, MESSAGE_TYPE, 1); fight_messages[i].number_of_attacks++; fight_messages[i].a_type = type; messages->next = fight_messages[i].msg; fight_messages[i].msg = messages; messages->die_msg.attacker_msg = fread_action(fl, i); messages->die_msg.victim_msg = fread_action(fl, i); messages->die_msg.room_msg = fread_action(fl, i); messages->miss_msg.attacker_msg = fread_action(fl, i); messages->miss_msg.victim_msg = fread_action(fl, i); messages->miss_msg.room_msg = fread_action(fl, i); messages->hit_msg.attacker_msg = fread_action(fl, i); messages->hit_msg.victim_msg = fread_action(fl, i); messages->hit_msg.room_msg = fread_action(fl, i); messages->god_msg.attacker_msg = fread_action(fl, i); messages->god_msg.victim_msg = fread_action(fl, i); messages->god_msg.room_msg = fread_action(fl, i); fgets(chk, 128, fl); while (!feof(fl) && (*chk == '\n' || *chk == '*')) fgets(chk, 128, fl); } fclose(fl); } void update_pos(CHAR_DATA *victim) { if ((GET_HIT(victim) > 0) && (GET_POS(victim) > POS_STUNNED)) return; else if ( GET_HIT(victim) > 0 && !AFF_FLAGGED(victim, AFF_PARALYSIS) ) GET_POS(victim) = POS_STANDING; else if (GET_HIT(victim) <= -11) GET_POS(victim) = POS_DEAD; else if (GET_HIT(victim) <= -6) GET_POS(victim) = POS_MORTALLYW; else if (GET_HIT(victim) <= -3) GET_POS(victim) = POS_INCAP; else GET_POS(victim) = POS_STUNNED; } /* * clan_at_war * * FALSE if ch and vict are NOT members of clans at war * TRUE if ch and vict are members of clans at war or ch or vict are NPCs */ bool clan_at_war(CHAR_DATA *ch, CHAR_DATA *vict) { if ( IS_NPC(ch) || IS_NPC(vict) ) return (TRUE); /* Check to see if ch & victim are in clans, and enemies */ if (GET_CLAN(ch) != -1 && GET_CLAN(vict) != -1 && GET_CLAN(ch) != GET_CLAN(vict)) { if (politics_data.diplomacy[GET_CLAN(ch)][GET_CLAN(vict)] < -450) return (TRUE); } return (FALSE); } /* check to prevent pkilling */ bool pkill_ok(CHAR_DATA *ch, CHAR_DATA *opponent) { if ( ch == opponent ) return (TRUE); if ( !pk_allowed && !IS_NPC(ch) && !IS_NPC(opponent) && !clan_at_war(ch, opponent) ) return (FALSE); return (TRUE); } void check_killer(CHAR_DATA *ch, CHAR_DATA *vict) { char buf[256]; if (PLR_FLAGGED(vict, PLR_KILLER) || PLR_FLAGGED(vict, PLR_THIEF)) return; if (PLR_FLAGGED(ch, PLR_KILLER) || IS_NPC(ch) || IS_NPC(vict) || ch == vict) return; SET_BIT(PLR_FLAGS(ch), PLR_KILLER); sprintf(buf, "PC Killer bit set on %s for initiating attack on %s at %s.", GET_NAME(ch), GET_NAME(vict), ROOM_NAME(vict)); mudlog(buf, BRF, LVL_IMMORT, TRUE); send_to_char("If you want to be a PLAYER KILLER, so be it...\r\n", ch); } /* start one char fighting another (yes, it is horrible, I know... ) */ void set_fighting(CHAR_DATA *ch, CHAR_DATA *vict) { if (ch == vict) return; if (FIGHTING(ch)) { core_dump(); return; } ch->next_fighting = combat_list; combat_list = ch; if (AFF_FLAGGED(ch, AFF_SLEEP)) affect_from_char(ch, SPELL_SLEEP); FIGHTING(ch) = vict; GET_POS(ch) = POS_FIGHTING; /* handle ppl using furniture */ if ( ch->in_obj ) char_from_obj(ch); if ( vict->in_obj ) char_from_obj(vict); /* check for memorizing chars.. */ if ( !IS_NPC(vict) && AFF_FLAGGED(vict, AFF_MEMORIZING) ) stop_memorizing(vict); if (!pkill_ok(ch, vict)) check_killer(ch, vict); } /* remove a char from the list of fighting chars */ void stop_fighting(CHAR_DATA *ch) { CHAR_DATA *temp; if (ch == next_combat_list) next_combat_list = ch->next_fighting; REMOVE_FROM_LIST(ch, combat_list, next_fighting); ch->next_fighting = NULL; FIGHTING(ch) = NULL; GET_POS(ch) = POS_STANDING; update_pos(ch); } void make_corpse(CHAR_DATA *ch) { OBJ_DATA *corpse, *o; char bufcor[MAX_STRING_LENGTH]; int i; corpse = create_obj(); corpse->item_number = NOTHING; corpse->in_room = NULL; sprintf( bufcor, "corpse %s", IS_NPC(ch) ? GET_PC_NAME(ch) : unknown_name(ch) ); corpse->name = str_dup(bufcor); sprintf(buf2, "The corpse of %s is lying here.", unknown_name(ch)); corpse->description = str_dup(buf2); sprintf(buf2, "the corpse of %s", unknown_name(ch)); corpse->short_description = str_dup(buf2); GET_OBJ_TYPE(corpse) = ITEM_CONTAINER; GET_OBJ_WEAR(corpse) = ITEM_WEAR_TAKE; GET_OBJ_EXTRA(corpse) = ITEM_NODONATE; GET_OBJ_VAL(corpse, 0) = 0; /* You can't store stuff in a corpse */ GET_OBJ_VAL(corpse, 3) = 1; /* corpse identifier */ GET_OBJ_WEIGHT(corpse) = GET_WEIGHT(ch) + IS_CARRYING_W(ch); GET_OBJ_RENT(corpse) = 100000; if (IS_NPC(ch)) { GET_OBJ_TIMER(corpse) = max_npc_corpse_time; GET_OBJ_VAL(corpse, 1) = 0; } else { GET_OBJ_TIMER(corpse) = max_pc_corpse_time; GET_OBJ_VAL(corpse, 1) = GET_IDNUM(ch); } /* transfer character's inventory to the corpse */ corpse->first_content = ch->first_carrying; corpse->last_content = ch->last_carrying; for (o = corpse->first_content; o; o = o->next_content) o->in_obj = corpse; object_list_new_owner(corpse, NULL); /* transfer character's equipment to the corpse */ for (i = 0; i < NUM_WEARS; i++) { if (GET_EQ(ch, i)) obj_to_obj(unequip_char(ch, i), corpse); } /* make gold coins from gold value (Mob only) */ if (IS_NPC(ch) && GET_GOLD(ch) > 0) create_amount(GET_GOLD(ch), NULL, NULL, corpse, NULL, FALSE); ch->first_carrying = NULL; ch->last_carrying = NULL; IS_CARRYING_N(ch) = 0; IS_CARRYING_W(ch) = 0; obj_to_room(corpse, ch->in_room); } /* When ch kills victim */ void change_alignment(CHAR_DATA *ch, CHAR_DATA *victim) { /* * new alignment change algorithm: if you kill a monster with alignment A, * you move 1/16th of the way to having alignment -A. Simple and fast. */ GET_ALIGNMENT(ch) += (-GET_ALIGNMENT(victim) - GET_ALIGNMENT(ch)) / 16; } void death_cry(CHAR_DATA *ch) { EXIT_DATA *pexit; act("Your blood freezes as you hear $n's death cry.", FALSE, ch, 0, 0, TO_ROOM); for ( pexit = ch->in_room->first_exit; pexit; pexit = pexit->next ) { if ( valid_exit( pexit ) ) send_to_room("Your blood freezes as you hear someone's death cry.\r\n", pexit->to_room); } } void raw_kill(CHAR_DATA *ch) { if (FIGHTING(ch)) stop_fighting(ch); while (ch->affected) affect_remove(ch, ch->affected); death_cry(ch); if (IS_NPC(ch)) { make_corpse(ch); extract_char(ch); } else { char_to_ghost(ch); send_to_char("You feel yourself being pulled into a bright light...\r\n", ch); char_from_room(ch); char_to_room(ch, get_room(10001)); GET_HIT(ch) = GET_MAX_HIT(ch); GET_MANA(ch) = GET_MAX_MANA(ch); GET_MOVE(ch) = GET_MAX_MOVE(ch); update_pos(ch); look_at_room(ch, 0); SET_BIT(PLR_FLAGS(ch), PLR_GHOST); } } void die(CHAR_DATA *ch, CHAR_DATA *killer) { if (killer && ch != killer) { /* aggiorniamo i contatori delle uccisioni */ if (!IS_NPC(ch)) { if (IS_NPC(killer)) GET_MOB_DEATHS(ch)++; else { GET_PLR_DEATHS(ch)++; GET_PLR_KILLS(killer)++; } } else if (!IS_NPC(killer)) GET_MOB_KILLS(killer)++; } gain_exp(ch, -(GET_EXP(ch) / 5)); if (!IS_NPC(ch)) REMOVE_BIT(PLR_FLAGS(ch), PLR_KILLER | PLR_THIEF); raw_kill(ch); } void perform_group_gain(CHAR_DATA *ch, int base, CHAR_DATA *victim) { int share, exp_after_lim; if (!IS_NPC(ch) && IS_NPC(victim)) exp_after_lim = kills_limit_xpgain(ch, victim, base); share = MIN(max_exp_gain, MAX(1, exp_after_lim)); if (share > 1) { sprintf(buf2, "You receive your share of experience -- %d points.\r\n", share); send_to_char(buf2, ch); } else send_to_char("You receive your share of experience -- one measly little point!\r\n", ch); gain_exp(ch, share); change_alignment(ch, victim); } void group_gain(CHAR_DATA *ch, CHAR_DATA *victim) { CHAR_DATA *k; FOLLOW_DATA *f; int tot_members, base, tot_gain; if (!(k = ch->master)) k = ch; if (AFF_FLAGGED(k, AFF_GROUP) && (k->in_room == ch->in_room)) tot_members = 1; else tot_members = 0; for (f = k->followers; f; f = f->next) { if (AFF_FLAGGED(f->follower, AFF_GROUP) && f->follower->in_room == ch->in_room) tot_members++; } /* round up to the next highest tot_members */ tot_gain = (GET_EXP(victim) / 3) + tot_members - 1; /* prevent illegal xp creation when killing players */ if (!IS_NPC(victim)) tot_gain = MIN(max_exp_loss * 2 / 3, tot_gain); if (tot_members >= 1) base = MAX(1, tot_gain / tot_members); else base = 0; if (AFF_FLAGGED(k, AFF_GROUP) && k->in_room == ch->in_room) perform_group_gain(k, base, victim); for (f = k->followers; f; f = f->next) { if (AFF_FLAGGED(f->follower, AFF_GROUP) && f->follower->in_room == ch->in_room) perform_group_gain(f->follower, base, victim); } } void solo_gain(CHAR_DATA *ch, CHAR_DATA *victim) { int exp; //exp = MIN(max_exp_gain, GET_EXP(victim) / 3); exp = GET_EXP(victim) / 3; /* Limit up XP gain according on amount of kills of current mob */ if (!IS_NPC(ch) && IS_NPC(victim)) exp = kills_limit_xpgain(ch, victim, exp); exp = MIN(max_exp_gain, exp); /* Calculate level-difference bonus */ if (IS_NPC(ch)) exp += MAX(0, (exp * MIN(4, (GET_LEVEL(victim) - GET_LEVEL(ch)))) / 8); else exp += MAX(0, (exp * MIN(8, (GET_LEVEL(victim) - GET_LEVEL(ch)))) / 8); exp = MAX(exp, 1); if (exp > 1) { sprintf(buf2, "You receive %d experience points.\r\n", exp); send_to_char(buf2, ch); } else send_to_char("You receive one lousy experience point.\r\n", ch); gain_exp(ch, exp); change_alignment(ch, victim); } char *replace_string(const char *str, const char *weapon_singular, const char *weapon_plural) { static char buf[256]; char *cp = buf; for (; *str; str++) { if (*str == '#') { switch (*(++str)) { case 'W': for (; *weapon_plural; *(cp++) = *(weapon_plural++)); break; case 'w': for (; *weapon_singular; *(cp++) = *(weapon_singular++)); break; default: *(cp++) = '#'; break; } } else *(cp++) = *str; *cp = 0; } /* For */ return (buf); } /* message for doing damage with a weapon */ void dam_message(int dam, CHAR_DATA *ch, CHAR_DATA *victim, int w_type) { char *buf; int msgnum; static struct dam_weapon_type { const char *to_room; const char *to_char; const char *to_victim; } dam_weapons[] = { /* use #w for singular (i.e. "slash") and #W for plural (i.e. "slashes") */ { "$n tries to #w $N, but misses.", /* 0: 0 */ "You try to #w $N, but miss.", "$n tries to #w you, but misses." }, { "$n tickles $N as $e #W $M.", /* 1: 1..2 */ "You tickle $N as you #w $M.", "$n tickles you as $e #W you." }, { "$n barely #W $N.", /* 2: 3..4 */ "You barely #w $N.", "$n barely #W you." }, { "$n #W $N.", /* 3: 5..6 */ "You #w $N.", "$n #W you." }, { "$n #W $N hard.", /* 4: 7..10 */ "You #w $N hard.", "$n #W you hard." }, { "$n #W $N very hard.", /* 5: 11..14 */ "You #w $N very hard.", "$n #W you very hard." }, { "$n #W $N extremely hard.", /* 6: 15..19 */ "You #w $N extremely hard.", "$n #W you extremely hard." }, { "$n massacres $N to small fragments with $s #w.", /* 7: 19..23 */ "You massacre $N to small fragments with your #w.", "$n massacres you to small fragments with $s #w." }, { "$n OBLITERATES $N with $s deadly #w!!", /* 8: > 23 */ "You OBLITERATE $N with your deadly #w!!", "$n OBLITERATES you with $s deadly #w!!" } }; w_type -= TYPE_HIT; /* Change to base of table with text */ if (dam == 0) msgnum = 0; else if (dam <= 2) msgnum = 1; else if (dam <= 4) msgnum = 2; else if (dam <= 6) msgnum = 3; else if (dam <= 10) msgnum = 4; else if (dam <= 14) msgnum = 5; else if (dam <= 19) msgnum = 6; else if (dam <= 23) msgnum = 7; else msgnum = 8; /* damage message to onlookers */ buf = replace_string(dam_weapons[msgnum].to_room, attack_hit_text[w_type].singular, attack_hit_text[w_type].plural); act(buf, FALSE, ch, NULL, victim, TO_NOTVICT); /* damage message to damager */ send_to_char(CCYEL(ch, C_CMP), ch); buf = replace_string(dam_weapons[msgnum].to_char, attack_hit_text[w_type].singular, attack_hit_text[w_type].plural); act(buf, FALSE, ch, NULL, victim, TO_CHAR); send_to_char(CCNRM(ch, C_CMP), ch); /* damage message to damagee */ send_to_char(CCRED(victim, C_CMP), victim); buf = replace_string(dam_weapons[msgnum].to_victim, attack_hit_text[w_type].singular, attack_hit_text[w_type].plural); act(buf, FALSE, ch, NULL, victim, TO_VICT | TO_SLEEP); send_to_char(CCNRM(victim, C_CMP), victim); } /* * message for doing damage with a spell or skill * C3.0: Also used for weapon damage on miss and death blows */ int skill_message(int dam, CHAR_DATA *ch, CHAR_DATA *vict, int attacktype) { MESSAGE_TYPE *msg; OBJ_DATA *weap = GET_EQ(ch, WEAR_WIELD); int i, j, nr; for (i = 0; i < MAX_MESSAGES; i++) { if (fight_messages[i].a_type == attacktype) { nr = dice(1, fight_messages[i].number_of_attacks); for (j = 1, msg = fight_messages[i].msg; (j < nr) && msg; j++) msg = msg->next; if (!IS_NPC(vict) && (GET_LEVEL(vict) >= LVL_IMMORT)) { act(msg->god_msg.attacker_msg, FALSE, ch, weap, vict, TO_CHAR); act(msg->god_msg.victim_msg, FALSE, ch, weap, vict, TO_VICT); act(msg->god_msg.room_msg, FALSE, ch, weap, vict, TO_NOTVICT); } else if (dam != 0) { /* * Don't send redundant color codes for TYPE_SUFFERING & other types * of damage without attacker_msg. */ if (GET_POS(vict) == POS_DEAD) { if (msg->die_msg.attacker_msg) { send_to_char(CCYEL(ch, C_CMP), ch); act(msg->die_msg.attacker_msg, FALSE, ch, weap, vict, TO_CHAR); send_to_char(CCNRM(ch, C_CMP), ch); } send_to_char(CCRED(vict, C_CMP), vict); act(msg->die_msg.victim_msg, FALSE, ch, weap, vict, TO_VICT | TO_SLEEP); send_to_char(CCNRM(vict, C_CMP), vict); act(msg->die_msg.room_msg, FALSE, ch, weap, vict, TO_NOTVICT); } else { if (msg->hit_msg.attacker_msg) { send_to_char(CCYEL(ch, C_CMP), ch); act(msg->hit_msg.attacker_msg, FALSE, ch, weap, vict, TO_CHAR); send_to_char(CCNRM(ch, C_CMP), ch); } send_to_char(CCRED(vict, C_CMP), vict); act(msg->hit_msg.victim_msg, FALSE, ch, weap, vict, TO_VICT | TO_SLEEP); send_to_char(CCNRM(vict, C_CMP), vict); act(msg->hit_msg.room_msg, FALSE, ch, weap, vict, TO_NOTVICT); } } else if (ch != vict) /* Dam == 0 */ { if (msg->miss_msg.attacker_msg) { send_to_char(CCYEL(ch, C_CMP), ch); act(msg->miss_msg.attacker_msg, FALSE, ch, weap, vict, TO_CHAR); send_to_char(CCNRM(ch, C_CMP), ch); } send_to_char(CCRED(vict, C_CMP), vict); act(msg->miss_msg.victim_msg, FALSE, ch, weap, vict, TO_VICT | TO_SLEEP); send_to_char(CCNRM(vict, C_CMP), vict); act(msg->miss_msg.room_msg, FALSE, ch, weap, vict, TO_NOTVICT); } return (1); } } return (0); } /* * Alert: As of bpl14, this function returns the following codes: * < 0 Victim died. * = 0 No damage. * > 0 How much damage done. */ int damage(CHAR_DATA *ch, CHAR_DATA *victim, int dam, int attacktype) { ROOM_DATA *vict_room = NULL; bool missile = FALSE; if (GET_POS(victim) <= POS_DEAD) { /* This is "normal"-ish now with delayed extraction. -gg 3/15/2001 */ if (PLR_FLAGGED(victim, PLR_NOTDEADYET) || MOB_FLAGGED(victim, MOB_NOTDEADYET)) return (-1); log("SYSERR: Attempt to damage corpse '%s' in room #%d by '%s'.", GET_NAME(victim), victim->in_room->number, GET_NAME(ch)); die(victim, NULL); return (-1); /* -je, 7/7/92 */ } if (ch->in_room != victim->in_room) { missile = TRUE; vict_room = victim->in_room; } /* peaceful rooms */ if (ch != victim && ROOM_FLAGGED(ch->in_room, ROOM_PEACEFUL)) { send_to_char("This room just has such a peaceful, easy feeling...\r\n", ch); return (0); } /* shopkeeper protection */ if (!ok_damage_shopkeeper(ch, victim)) return (0); /* You can't damage an immortal! */ if (IS_IMMORTAL(victim)) dam = 0; if (victim != ch && !missile) { /* Start the attacker fighting the victim */ if (GET_POS(ch) > POS_STUNNED && (FIGHTING(ch) == NULL)) set_fighting(ch, victim); /* Start the victim fighting the attacker */ if (GET_POS(victim) > POS_STUNNED && (FIGHTING(victim) == NULL)) { set_fighting(victim, ch); if (MOB_FLAGGED(victim, MOB_MEMORY) && !IS_NPC(ch)) remember(victim, ch); } } /* If you attack a pet, it hates your guts */ if (victim->master == ch) stop_follower(victim); /* If the attacker is invisible, he becomes visible */ if (AFF_FLAGGED(ch, AFF_INVISIBLE | AFF_HIDE)) appear(ch); /* Cut damage in half if victim has sanct, to a minimum 1 */ if (AFF_FLAGGED(victim, AFF_SANCTUARY) && dam >= 2) dam /= 2; /* Check for PK if this is not a PK MUD */ if ( !pkill_ok(ch, victim) ) { check_killer(ch, victim); if (PLR_FLAGGED(ch, PLR_KILLER) && (ch != victim)) dam = 0; } /* Set the maximum damage per round and subtract the hit points */ dam = MAX(MIN(dam, 200), 0); GET_HIT(victim) -= dam; /* Being hit destroys equipment... */ if (dam > 0) check_damage_obj(victim, NULL, 7); update_pos(victim); /* * skill_message sends a message from the messages file in lib/misc. * dam_message just sends a generic "You hit $n extremely hard.". * skill_message is preferable to dam_message because it is more * descriptive. * * If we are _not_ attacking with a weapon (i.e. a spell), always use * skill_message. If we are attacking with a weapon: If this is a miss or a * death blow, send a skill_message if one exists; if not, default to a * dam_message. Otherwise, always send a dam_message. */ if (attacktype != -1) { if (!IS_WEAPON(attacktype) || missile) skill_message(dam, ch, victim, attacktype); else { if (GET_POS(victim) == POS_DEAD || dam == 0) { if (!skill_message(dam, ch, victim, attacktype)) dam_message(dam, ch, victim, attacktype); } else dam_message(dam, ch, victim, attacktype); } } /* Use send_to_char -- act() doesn't send message if you are DEAD. */ switch (GET_POS(victim)) { case POS_MORTALLYW: act("$n is mortally wounded, and will die soon, if not aided.", TRUE, victim, 0, 0, TO_ROOM); send_to_char("You are mortally wounded, and will die soon, if not aided.\r\n", victim); break; case POS_INCAP: act("$n is incapacitated and will slowly die, if not aided.", TRUE, victim, 0, 0, TO_ROOM); send_to_char("You are incapacitated an will slowly die, if not aided.\r\n", victim); break; case POS_STUNNED: act("$n is stunned, but will probably regain consciousness again.", TRUE, victim, 0, 0, TO_ROOM); send_to_char("You're stunned, but will probably regain consciousness again.\r\n", victim); break; case POS_DEAD: act("$n is dead! R.I.P.", FALSE, victim, 0, 0, TO_ROOM); send_to_char("You are dead! Sorry...\r\n", victim); break; default: /* >= POSITION SLEEPING */ if (dam > (GET_MAX_HIT(victim) / 4)) send_to_char("That really did HURT!\r\n", victim); if (GET_HIT(victim) < (GET_MAX_HIT(victim) / 4)) { sprintf(buf2, "%sYou wish that your wounds would stop BLEEDING so much!%s\r\n", CCRED(victim, C_SPR), CCNRM(victim, C_SPR)); send_to_char(buf2, victim); if (ch != victim && MOB_FLAGGED(victim, MOB_WIMPY)) do_flee(victim, NULL, 0, 0); } if (!IS_NPC(victim) && GET_WIMP_LEV(victim) && (victim != ch) && GET_HIT(victim) < GET_WIMP_LEV(victim) && GET_HIT(victim) > 0) { send_to_char("You wimp out, and attempt to flee!\r\n", victim); do_flee(victim, NULL, 0, 0); } break; } /* Help out poor linkless people who are attacked */ if (victim != ch && !IS_NPC(victim) && !(victim->desc) && GET_POS(victim) > POS_STUNNED) { do_flee(victim, NULL, 0, 0); if (!FIGHTING(victim)) { act("$n is rescued by divine forces.", FALSE, victim, 0, 0, TO_ROOM); GET_WAS_IN(victim) = victim->in_room; char_from_room(victim); char_to_room(victim, get_room(0)); } } /* stop someone from fighting if they're stunned or worse */ if (GET_POS(victim) <= POS_STUNNED && FIGHTING(victim) != NULL) stop_fighting(victim); /* Uh oh. Victim died. */ if (GET_POS(victim) == POS_DEAD) { if (ch != victim && (IS_NPC(victim) || victim->desc)) { if (AFF_FLAGGED(ch, AFF_GROUP)) group_gain(ch, victim); else solo_gain(ch, victim); } if (ch != victim && !IS_NPC(victim)) { sprintf(buf2, "%s killed by %s at %s", GET_NAME(victim), GET_NAME(ch), ROOM_NAME(victim)); mudlog(buf2, BRF, LVL_IMMORT, TRUE); if (IS_NPC(ch)) brag(ch, victim); if (MOB_FLAGGED(ch, MOB_MEMORY)) forget(ch, victim); } die(victim, ch); return (-1); } /* mob reaction to a ranged attack */ else if (victim != ch && missile) { if (IS_NPC(victim) && !FIGHTING(victim) && GET_POS(victim) > POS_STUNNED) { /* can remember so charge! */ if (MOB_FLAGGED(victim, MOB_MEMORY)) { remember(victim, ch); sprintf(buf, "$n bellows in pain!"); act(buf, FALSE, victim, 0, 0, TO_ROOM); if (GET_POS(victim) == POS_STANDING) { int dir, moved = 0; if (IN_WILD(victim)) dir = wild_track(ch, victim, TRUE); else dir = find_first_step(victim->in_room, ch->in_room); if (dir != NOWHERE) { if (!do_simple_move(victim, dir, 1)) act("$n stumbles while trying to run!", FALSE, victim, 0, 0, TO_ROOM); else moved = 1; } if (!moved && MOB_FLAGGED(victim, MOB_HUNTER)) start_hunting(victim, ch); } } /* can't remember so try to run away if not already fleed */ else { if (vict_room == victim->in_room) do_flee(victim, NULL, 0, 0); } } } return (dam); } /* * Calculate the THAC0 of the attacker. * * 'victim' currently isn't used but you could use it for special cases like * weapons that hit evil creatures easier or a weapon that always misses * attacking an animal. */ int compute_thaco(CHAR_DATA *ch, CHAR_DATA *victim) { int calc_thaco; if (!IS_NPC(ch)) calc_thaco = thaco(GET_CLASS(ch), GET_LEVEL(ch)); else /* THAC0 for monsters is set in the HitRoll */ calc_thaco = 20; calc_thaco -= str_app[GET_STR(ch)].tohit; calc_thaco -= GET_HITROLL(ch); calc_thaco -= (GET_INT(ch) - 13) / 1.5; /* Intelligence helps! */ calc_thaco -= (GET_WIS(ch) - 13) / 1.5; /* So does wisdom */ return (calc_thaco); } void hit(CHAR_DATA *ch, CHAR_DATA *victim, int type) { OBJ_DATA *wielded = GET_EQ(ch, WEAR_WIELD); int w_type, victim_ac, calc_thaco, dam, diceroll, ret; int wpnprof; /* Do some sanity checking, just in case.. */ if ( !ch || !victim ) return; /* Do some sanity checking, in case someone flees, etc. */ if (ch->in_room != victim->in_room) { if (FIGHTING(ch) && FIGHTING(ch) == victim) stop_fighting(ch); return; } /* Find the weapon type (for display purposes only) */ if (wielded && GET_OBJ_TYPE(wielded) == ITEM_WEAPON) w_type = GET_OBJ_VAL(wielded, 3) + TYPE_HIT; else { if (IS_NPC(ch) && GET_ATTACK(ch) != 0) w_type = GET_ATTACK(ch) + TYPE_HIT; else w_type = TYPE_HIT; } /* Weapon proficiencies */ if (IS_WARRIOR(ch) && wielded && GET_OBJ_TYPE(wielded) == ITEM_WEAPON) wpnprof = get_weapon_prof(ch, wielded); else wpnprof = 0; /* Calculate chance of hit. Lower THAC0 is better for attacker. */ calc_thaco = compute_thaco(ch, victim); calc_thaco -= wpn_prof[wpnprof].to_hit; /* Calculate the raw armor including magic armor. Lower AC is better for defender. */ victim_ac = compute_armor_class(victim) / 10; if ( RIDING(victim) ) victim_ac -= 5; /* roll the die and take your chances... */ diceroll = number(1, 20); /* * Decide whether this is a hit or a miss. * * Victim asleep = hit, otherwise: * 1 = Automatic miss. * 2..19 = Checked vs. AC. * 20 = Automatic hit. */ if (diceroll == 20 || !AWAKE(victim)) dam = TRUE; else if (diceroll == 1) dam = FALSE; else dam = (calc_thaco - diceroll <= victim_ac); if (!dam) { damage(ch, victim, 0, type == SKILL_BACKSTAB ? SKILL_BACKSTAB : w_type); return; } /* okay, we know the guy has been hit. now calculate damage. */ /* Start with the damage bonuses: the damroll and strength apply */ dam = str_app[GET_STR(ch)].todam; dam += GET_DAMROLL(ch); /* Maybe holding arrow? */ if (wielded && GET_OBJ_TYPE(wielded) == ITEM_WEAPON) { /* Add weapon-based damage if a weapon is being wielded */ dam += dice(GET_OBJ_VAL(wielded, 1), GET_OBJ_VAL(wielded, 2)); dam += wpn_prof[wpnprof].to_dam; } else { /* If no weapon, add bare hand damage instead */ if (IS_NPC(ch)) dam += dice(GET_NDD(ch), GET_SDD(ch)); else dam += number(0, 2); /* Max 2 bare hand damage for players */ } if ( RIDING(ch) ) dam += MAX(10, GET_DAMROLL(ch)/10); /* * Include a damage multiplier if victim isn't ready to fight: * * Position sitting 1.33 x normal * Position resting 1.66 x normal * Position sleeping 2.00 x normal * Position stunned 2.33 x normal * Position incap 2.66 x normal * Position mortally 3.00 x normal * * Note, this is a hack because it depends on the particular * values of the POSITION_XXX constants. */ if (GET_POS(victim) < POS_FIGHTING) dam *= 1 + (POS_FIGHTING - GET_POS(victim)) / 3; /* at least 1 hp damage min per hit */ dam = MAX(1, dam); if (type == SKILL_BACKSTAB) ret = damage(ch, victim, dam * backstab_mult(GET_LEVEL(ch)), SKILL_BACKSTAB); else ret = damage(ch, victim, dam, w_type); /* fireshield case */ if ( ret != -1 && type >= TYPE_HIT ) { if (AFF_FLAGGED(victim, AFF_FIRESHIELD) && !AFF_FLAGGED(ch, AFF_FIRESHIELD)) damage(victim, ch, dam / 2, SPELL_FIREBALL); } /* check for weapon being damaged */ if (wielded && GET_OBJ_TYPE(wielded) == ITEM_WEAPON) { /* call for a magical effect from the weapon */ if (FIGHTING(ch) && victim && FIGHTING(ch) == victim) weapon_spells(ch, victim, wielded); /* check for weapon being damaged */ check_damage_obj(ch, wielded, 3); } } bool valid_target(CHAR_DATA *ch, CHAR_DATA *vict) { if ( !ch || !vict ) return (FALSE); if ( MOB_FLAGGED(ch, MOB_NOTDEADYET) ) return (FALSE); if ( MOB_FLAGGED(vict, MOB_NOTDEADYET) ) return (FALSE); if ( PLR_FLAGGED(ch, PLR_NOTDEADYET) ) return (FALSE); if ( PLR_FLAGGED(vict, PLR_NOTDEADYET) ) return (FALSE); return (TRUE); } /* control the fights going on. Called every 2 seconds from comm.c. */ void perform_violence(void) { CHAR_DATA *ch; OBJ_DATA *wielded; sh_int num_of_attacks = 1, loop_attacks; for (ch = combat_list; ch; ch = next_combat_list) { next_combat_list = ch->next_fighting; if (FIGHTING(ch) == NULL || ch->in_room != FIGHTING(ch)->in_room || !valid_target(ch, FIGHTING(ch))) { stop_fighting(ch); continue; } if (IS_NPC(ch)) { if (GET_MOB_WAIT(ch) > 0) { GET_MOB_WAIT(ch) -= PULSE_VIOLENCE; continue; } GET_MOB_WAIT(ch) = 0; if (GET_POS(ch) < POS_FIGHTING) { GET_POS(ch) = POS_FIGHTING; act("$n scrambles to $s feet!", TRUE, ch, 0, 0, TO_ROOM); } } if (GET_POS(ch) < POS_FIGHTING) { send_to_char("You can't fight while sitting!!\r\n", ch); continue; } /* reset to 1 attack */ num_of_attacks = 1; if (IS_WARRIOR(ch)) { wielded = GET_EQ(ch, WEAR_WIELD); if (wielded && GET_OBJ_TYPE(wielded) == ITEM_WEAPON) num_of_attacks += wpn_prof[get_weapon_prof(ch, wielded)].num_of_attacks; } else num_of_attacks += MAX(1, ((int) GET_LEVEL(ch) / 10) - 1); for (loop_attacks = 0; loop_attacks < num_of_attacks && valid_target(ch, FIGHTING(ch)); loop_attacks++) hit(ch, FIGHTING(ch), TYPE_UNDEFINED); if (valid_target(ch, FIGHTING(ch))) diag_char_to_char(FIGHTING(ch), ch); if (MOB_FLAGGED(ch, MOB_SPEC) && GET_MOB_SPEC(ch) && !MOB_FLAGGED(ch, MOB_NOTDEADYET)) { char actbuf[MAX_INPUT_LENGTH] = ""; (GET_MOB_SPEC(ch)) (ch, ch, 0, actbuf); } } } /* Weapon Proficiecies */ int get_weapon_prof(CHAR_DATA *ch, OBJ_DATA *wield) { int bonus = 0, learned = 0, type = -1; /* No mobs and non-warriors here */ if (!IS_WARRIOR(ch)) return (bonus); /* Check for proficiencies only if characters could have learned it */ if (GET_LEVEL(ch) < PROF_LVL_START) return (bonus); switch (GET_OBJ_VAL(wield, 3) + TYPE_HIT) { case TYPE_SLASH: type = SKILL_WEAPON_SWORDS; break; case TYPE_STING: case TYPE_PIERCE: case TYPE_STAB: type = SKILL_WEAPON_DAGGERS; break; case TYPE_THRASH: case TYPE_WHIP: type = SKILL_WEAPON_WHIPS; break; case TYPE_CLAW: type = SKILL_WEAPON_TALONOUS_ARMS; break; case TYPE_BLUDGEON: case TYPE_MAUL: case TYPE_POUND: case TYPE_CRUSH: type = SKILL_WEAPON_BLUDGEONS; break; case TYPE_HIT: case TYPE_PUNCH: case TYPE_BITE: case TYPE_BLAST: type = SKILL_WEAPON_EXOTICS; break; default: type = -1; break; } if (type != -1) { learned = GET_SKILL(ch, type); if (learned == 0) bonus = 10; else if (learned <= 20) bonus = 1; else if (learned <= 40) bonus = 2; else if (learned <= 60) bonus = 3; else if (learned <= 80) bonus = 4; else if (learned <= 85) bonus = 5; else if (learned <= 90) bonus = 6; else if (learned <= 95) bonus = 7; else if (learned <= 99) bonus = 8; else bonus = 9; } return (bonus); } /* ******************************************************************** */ /* Ranged Weapons functions */ /* ******************************************************************** */ void strike_missile(CHAR_DATA *ch, CHAR_DATA *tch, OBJ_DATA *missile, int dir, int attacktype) { ROOM_DATA *vict_room = NULL; int dam, alive; char victname[MAX_STRING_LENGTH]; dam = dice(GET_OBJ_VAL(missile, 1), GET_OBJ_VAL(missile, 2)); dam += GET_OBJ_VAL(missile, 3); strcpy(victname, PERS(tch, ch)); vict_room = tch->in_room; alive = damage(ch, tch, dam, attacktype); send_to_char("&b&2You hit!&0\r\n", ch); sprintf(buf, "%s flies in from the %s and %s %s.", missile->short_description, dirs[rev_dir[dir]], (alive == -1 ? "kills" : "strikes"), victname); send_to_room(buf, vict_room); if (alive >= 0 ) { sprintf(buf, "&b&3$P flies in from the %s and hits YOU!&0", dirs[rev_dir[dir]]); act(buf, FALSE, tch, 0, missile, TO_CHAR); } return; } void miss_missile(CHAR_DATA *ch, CHAR_DATA *tch, OBJ_DATA *missile, int dir, int attacktype) { sprintf(buf, "$P flies in from the %s and hits the ground!", dirs[rev_dir[dir]]); act(buf, FALSE, tch, 0, missile, TO_ROOM); act(buf, FALSE, tch, 0, missile, TO_CHAR); send_to_char("You missed!\r\n", ch); } void fire_missile(CHAR_DATA *ch, char *arg1, OBJ_DATA *missile, int pos, int range, EXIT_DATA *pexit) { ROOM_DATA *nextroom; CHAR_DATA *vict; bool shot = FALSE; int attacktype, distance, dir; if ( !pexit ) return; if ( !*arg1 ) { send_to_char("Shoot who?\r\n", ch ); return; } dir = pexit->vdir; if (ROOM_FLAGGED(ch->in_room, ROOM_PEACEFUL)) { send_to_char("This room just has such a peaceful, easy feeling...\r\n", ch); return; } if ((nextroom = pexit->to_room) == NULL || !valid_exit(pexit)) { send_to_char("Can't find your target!\r\n", ch); return; } for (distance = 1; nextroom != NULL && distance <= range; distance++) { for (vict = nextroom->people; vict; vict = vict->next_in_room) { if ((isname(arg1, take_name(vict, ch))) && (CAN_SEE(ch, vict))) break; } if (vict) { if (missile && ROOM_FLAGGED(vict->in_room, ROOM_PEACEFUL)) { send_to_char("Nah. Leave them in peace.\r\n", ch); return; } switch (GET_OBJ_TYPE(missile)) { case ITEM_MISSILE: act("$n aims and fires!", TRUE, ch, 0, 0, TO_ROOM); send_to_char("You aim and fire!\r\n", ch); switch ( GET_OBJ_VAL(missile, 0) ) { default: attacktype = TYPE_UNDEFINED; break; case 1: attacktype = SKILL_WEAPON_BOW; break; case 2: attacktype = SKILL_WEAPON_SLING; break; case 3: attacktype = SKILL_WEAPON_CROSSBOW; break; } break; default: attacktype = TYPE_UNDEFINED; break; } if (attacktype != TYPE_UNDEFINED) shot = success(ch, vict, attacktype, 0); else shot = FALSE; if (shot) strike_missile(ch, vict, missile, dir, attacktype); else miss_missile(ch, vict, missile, dir, attacktype); extract_obj(missile); WAIT_STATE(ch, PULSE_VIOLENCE); return; } nextroom = find_room(pexit->to_room, dir); } send_to_char("Can't find your target!\r\n", ch); /* put the missile back into container.. sigh! */ if (missile) { OBJ_DATA *cont; cont = get_missile_cont( ch, GET_OBJ_VAL(missile, 0) ); if ( !cont ) extract_obj(missile); else { obj_from_obj(missile); obj_to_obj(missile, cont); } } }