/* ************************************************************************ * File: vio.c , Combat module. Part of DIKUMUD * * Usage: Violence system and messages. * * Copyright (C) 1990, 1991 - see 'license.doc' for complete information. * ************************************************************************* */ /* Thoughts on combat: damage() should only be called from a minimum of places, directly off the main loop if possible. also, damage() shouldn't need to handle 0 (non-) damage, nor messages for that matter. perhaps an action_message(ch,vict,type) for combat messages, and maybe even socials, if they can be combined hitting to subdue should be handled in hit() somewhere */ #include CONFIG #if HAVE_STRINGS_H #include <strings.h> #endif #if HAVE_STRING_H #include <string.h> #endif #include "structs.h" #include "utils.h" #include "comm.h" #include "db.h" #include "player.h" #include "skills.h" #include "event.h" #include "error.h" #include "proto.h" /* Structures */ struct char_data *combat_list = 0; /* head of l-list of fighting chars*/ struct char_data *combat_next_dude = 0; /* Next dude global trick */ /* External structures */ extern struct room_data *world; extern struct zone_data *zone_table; extern struct obj_data *object_list; extern struct obj_index_data *obj_index; extern struct str_app_type str_app[]; /* External procedures */ char *fread_string(FILE *f1); void stop_follower(struct char_data *ch); void forget(char *name, struct char_data *ch); void remember(char *name, struct char_data *ch); void death_stat_handler(struct char_data *ch); int to_hit_char(struct char_data *ch,struct char_data *victim,int adj); struct msg_trio { char *msg_attacker; char *msg_victim; char *msg_room; struct msg_trio *next; }; struct fight_msg { int type; int total; struct msg_trio *list; struct fight_msg *next; }; /* The Fight related routines */ void load_messages(void) { #if 0 FILE *f1; int i,type; struct message_type *messages; char chk[100]; if (!(f1 = fopen(MESS_FILE, "r"))){ perror("read messages"); exit(0); } 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; } fscanf(f1, " %s \n", chk); while(*chk == 'M') { fscanf(f1," %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("Too many combat messages."); exit(0); } CREATE(messages,struct 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_string(f1); messages->die_msg.victim_msg = fread_string(f1); messages->die_msg.room_msg = fread_string(f1); messages->miss_msg.attacker_msg = fread_string(f1); messages->miss_msg.victim_msg = fread_string(f1); messages->miss_msg.room_msg = fread_string(f1); messages->hit_msg.attacker_msg = fread_string(f1); messages->hit_msg.victim_msg = fread_string(f1); messages->hit_msg.room_msg = fread_string(f1); fscanf(f1, " %s \n", chk); } fclose(f1); #endif } void update_pos( struct char_data *victim ) { /* Handle hit point positions */ if ((GET_HIT(victim) > 0) && (GET_POS(victim) > POS_STUNNED)) ; else if (GET_HIT(victim) > 0 ) GET_POS(victim) = POS_STAND; 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; } /* start one char fighting another (yes, it is horrible, I know... ) */ void set_fighting(struct char_data *ch, struct char_data *vict) { if(ch->specials.fighting) { log("BUG: Character already fighting in set_fighting"); return; } ch->next_fighting = combat_list; combat_list = ch; /* if(IS_AFFECTED(ch,AFF_SLEEP)) affect_from_char(ch,SPELL_SLEEP);*/ ch->specials.fighting = vict; add_event(vict->in_room,EVENT_ATTACK,NULL,vict->id,ch->id); if(GET_POS(ch)<POS_STAND) /* Need a bodytype bitvector for positions? */ GET_POS(ch)=POS_STAND; /* VEHICLE, perhaps? */ } /* remove a char from the list of fighting chars */ void stop_fighting(struct char_data *ch) { struct char_data *tmp; if(!ch->specials.fighting) { log("BUG: Character not fighting in stop_fighting"); return; } if (ch == combat_next_dude) combat_next_dude = ch->next_fighting; if (combat_list == ch) combat_list = ch->next_fighting; else { for (tmp = combat_list; tmp && (tmp->next_fighting != ch); tmp = tmp->next_fighting); if (!tmp) { log("BUG: Char fighting not found Error (stop_fighting)"); return; } tmp->next_fighting = ch->next_fighting; } ch->next_fighting = 0; ch->specials.fighting = 0; update_pos(ch); } #define MAX_CORPSE_TIME 10 void make_corpse(struct char_data *ch) { struct obj_data *corpse, *o; struct obj_info_container *cont; char buf[MAX_STRING_LENGTH]; char *strdup(char *source); CREATE(corpse, struct obj_data, 1); clear_object(corpse); corpse->item_number = NOWHERE; corpse->in_room = NOWHERE; corpse->name = strdup("corpse"); sprintf(buf, "Corpse of %s is lying here.", (IS_NPC(ch) ? ch->player.short_descr : GET_NAME(ch))); corpse->description = strdup(buf); sprintf(buf, "Corpse of %s", (IS_NPC(ch) ? ch->player.short_descr : GET_NAME(ch))); corpse->short_description = strdup(buf); corpse->obj_flags.extra_flags = ITEM_TAKE; cont = (struct obj_info_container *)new_obj_info(ITEM_CONTAINER,corpse); cont->capacity = 0; /* You can't store stuff in a corpse */ cont->lock_state = CONT_CORPSE; /* corpse identifyer */ corpse->obj_flags.weight = GET_WEIGHT(ch)+IS_CARRYING_W(ch); /* corpse->obj_flags.timer = MAX_CORPSE_TIME; */ if(!IS_NPC(ch)&&IS_SET(world[ch->in_room].room_flags,ARENA)){ obj_to_room(corpse, ch->in_room); corpse->next = object_list; object_list = corpse; return; } /* create an 'empty' token corpse in the arena */ while((o=ch->carrying)) { obj_from_char(o); obj_to_obj(o,corpse); } obj_to_room(corpse, ch->in_room); save_char(ch); } /* When ch kills victim */ void change_alignment(struct char_data *ch, struct char_data *victim) { int align; struct obj_data *obj,*next; /* Increment the kill count */ ch->points.kills++; align = GET_ALIGNMENT(ch) - (GET_ALIGNMENT(victim)/ch->points.kills); if (align > 0) { if (align > 650) GET_ALIGNMENT(ch) = MIN(1000,GET_ALIGNMENT(ch) + ((align-650) >> 3)); else GET_ALIGNMENT(ch) >>= 1; } else { if (align < -650) GET_ALIGNMENT(ch) = MAX(-1000, GET_ALIGNMENT(ch) + ((align+650) >> 3)); else GET_ALIGNMENT(ch) >>= 1; } for(obj=ch->carrying;obj;obj=next) { next = obj->next_content; if(obj->equipped_as==UNEQUIPPED) continue; if(((IS_OBJ_STAT(obj, ITEM_ANTI_EVIL) && IS_EVIL(ch)) || (IS_OBJ_STAT(obj, ITEM_ANTI_GOOD) && IS_GOOD(ch)) || (IS_OBJ_STAT(obj, ITEM_ANTI_NEUTRAL) && IS_NEUTRAL(ch))) && (ch->in_room != NOWHERE)) { act("You are zapped by $p and instantly drop it.",FALSE,ch,obj,0,TO_CHAR); act("$n is zapped by $p and instantly drops it.",FALSE,ch,obj,0,TO_ROOM); obj=unequip_char(ch,obj); obj_to_room(obj,ch->in_room); } } } void death_cry(struct char_data *ch) { int was_in; struct obj_data *o; struct obj_info_exit *exit; act("Your blood freezes as you hear $n's death cry.", FALSE,ch,0,0,TO_ROOM); was_in = ch->in_room; for(o=world[was_in].contents;o;o=o->next_content) { if((exit= (struct obj_info_exit *) get_obj_info(o,ITEM_EXIT))) { if(exit->to_room!=was_in) { ch->in_room = exit->to_room; act("Your blood freezes as you hear someones death cry.", FALSE,ch,0,0,TO_ROOM); ch->in_room = was_in; } } } } extern struct mob_index_data *mob_index; void raw_kill(struct char_data *ch) { /* char buf[MAX_STRING_LENGTH];*/ if (ch->specials.fighting) stop_fighting(ch); death_cry(ch); make_corpse(ch); extract_char(ch); } void die(struct char_data *ch) { gain_exp(ch, -(GET_EXP(ch)/2)); add_event_room(ch->in_room,EVENT_DEATH,0,ch->id,ID_NOBODY); if(!IS_NPC(ch)) { raw_kill(ch); death_stat_handler(ch); return; } raw_kill(ch); } static char *dead_forever = " You feel your life force slowly escape from your body, and\n\r\ escape into the ethereal place of spirit-matter, separating into\n\r\ its constituent parts and mingling with the spirits of enemies,\n\r\ friends, lovers, great people, and the wicked...\n\r\n\r\ Rest In Peace.\n\r\n\r"; void death_stat_handler(struct char_data *ch) { /* Put in check for org-death handling */ if(!IS_NPC(ch)){ /* Clear out the outlaw flag REMOVE_BIT(ch->specials.act,ACT_OUTLAW);*/ if(!--ch->physical->abilities.con) { /* Mark character as gone, forever */ SET_BIT(ch->prefs->flags,PLR_DEAD); send_to_char(dead_forever,ch); if(ch->desc) { SEND_TO_Q("Do you wish to see your intrinsics? ",ch->desc); ch->desc->connected=CON_INTRIN; } else delete_char(ch); return; /* don't make a ghost */ } } /* !isnpc */ /* Make a ghost under certain conditions */ if(GET_INT(ch) > 7 && GET_CON(ch) > 6) { } } char *replace_string(char *str, char *weapon) { static char buf[256]; char *cp; cp = buf; for (; *str; str++) { if (*str == '#') { switch(*(++str)) { case 'W' : for (; *weapon; *(cp++) = *(weapon++)); break; default : *(cp++) = '#'; break; } } else { *(cp++) = *str; } *cp = 0; } /* For */ return(buf); } void dam_message(int dam, struct char_data *ch, struct char_data *victim, int w_type) { struct obj_data *wield; /* char *buf;*/ int dam_level; #if 0 static struct dam_weapon_type { char *to_room; char *to_char; char *to_victim; } dam_weapons[] = { {"$n misses $N with $s #W.", /* 0 */ "You miss $N with your #W.", "$n misses you with $s #W." }, {"$n tickles $N with $s #W.", /* 1.. 2 */ "You tickle $N as you #W $M.", "$n tickles you as $e #W you." }, {"$n barely #W $N.", /* 3.. 4 */ "You barely #W $N.", "$n barely #W you."}, {"$n #W $N.", /* 5.. 6 */ "You #W $N.", "$n #W you."}, {"$n #W $N hard.", /* 7..10 */ "You #W $N hard.", "$n #W you hard."}, {"$n #W $N very hard.", /* 11..14 */ "You #W $N very hard.", "$n #W you very hard."}, {"$n #W $N extremely hard.", /* 15..20 */ "You #W $N extremely hard.", "$n #W you extremely hard."}, {"$N staggers from a fearsome #W from $n.", /* 20..40 */ "$N staggers from your fearsome #W.", "You stagger from a fearsome #W from $n."}, {"$n massacres $N to small fragments with $s #W.", /* 40..60 */ "You massacre $N to small fragments with your #W.", "$n massacres you to small fragments with $s #W."}, /* 60..100 */ {"$N is enshrouded in a mist of blood after receiving $n's #W.", "$N is enshrouded in a mist of blood after receiving your #W.", "You are enshrouded in a mist of blood after $n's #W."}, {"$n nearly rips $N apart from the force of $s #W.", /* 100+ */ "You nearly rip $N apart from the force of your #W.", "$n nearly rips you apart from the force of $s #W."} }; #endif /* w_type -= TYPE_HIT; * Change to base of table with text */ wield = get_equip_used(ch,WIELD); if (dam == 0) { dam_level=0; } else if (dam <= 2) { dam_level=1; } else if (dam <= 4) { dam_level=2; } else if (dam <= 6) { dam_level=3; } else if (dam <= 10) { dam_level=4; } else if (dam <= 15) { dam_level=5; } else if (dam <= 20) { dam_level=6; } else if (dam <= 40) { dam_level=7; } else if (dam <= 60) { dam_level=8; } else if (dam <= 100) { dam_level=9; } else { dam_level=10; } #if 0 buf = replace_string(dam_weapons[dam_level].to_room, attack_hit_text[w_type].singular); act(buf, FALSE, ch, wield, victim, TO_NOTVICT); buf = replace_string(dam_weapons[dam_level].to_char, attack_hit_text[w_type].singular); act(buf, FALSE, ch, wield, victim, TO_CHAR); buf = replace_string(dam_weapons[dam_level].to_victim, attack_hit_text[w_type].singular); act(buf, FALSE, ch, wield, victim, TO_VICT); #endif } void damage(struct char_data *ch, struct char_data *victim, int dam, int attacktype) { char buf[MAX_STRING_LENGTH]; #if 0 struct message_type *messages; #endif struct obj_data *weapon; int max_hit,exp; int hit_limit(struct char_data *ch); if (GET_POS(victim)<=POS_DEAD){ fprintf(stderr,"BUG: damage() already dead failed by %s vs. %s.\n\r", GET_NAME(ch),GET_NAME(victim)); send_to_char("He's dead already, report this bug.\n\r",ch); send_to_char("You're dead. Report bug, please.\n\r",victim); return; } if (victim != ch) { /* if (IS_NPC(ch)&&IS_AFFECTED(ch, AFF_CHARM) && !IS_NPC(victim)&&(victim->specials.fighting!=ch)){ send_to_char("You cannot harm another player!\n\r",ch); return; }*/ #if 0 if (IS_NPC(ch) && IS_NPC(victim) && victim->master && !number(0,10) && IS_AFFECTED(victim, AFF_CHARM) && (victim->master->in_room == ch->in_room)) { if (ch->specials.fighting) stop_fighting(ch); hit(ch, victim->master, TYPE_UNDEFINED); return; } #endif } if (victim->master == ch) stop_follower(victim); dam=MIN(dam,300); dam=MAX(dam,0); GET_HIT(victim)-=dam; /* No exp for arena fighting */ gain_exp(ch,dam); update_pos(victim); weapon=get_equip_used(ch,WIELD); #if 0 if ((attacktype >= TYPE_HIT) && (attacktype <= TYPE_CRUSH)) { if (!weapon) { dam_message(dam, ch, victim, TYPE_HIT); } else { dam_message(dam, ch, victim, attacktype); } } else { /* if(!weapon) { log("Hit messages in fight.c with no weapon!"); return; }*/ 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,messages=fight_messages[i].msg;(j<nr)&&(messages);j++) messages=messages->next; if (dam != 0) { if (GET_POS(victim) == POS_DEAD) { act(messages->die_msg.attacker_msg, FALSE, ch, weapon, victim, TO_CHAR); act(messages->die_msg.victim_msg, FALSE, ch, weapon, victim, TO_VICT); act(messages->die_msg.room_msg, FALSE, ch, weapon, victim, TO_NOTVICT); } else { act(messages->hit_msg.attacker_msg, FALSE, ch, weapon, victim, TO_CHAR); act(messages->hit_msg.victim_msg, FALSE, ch, weapon, victim, TO_VICT); act(messages->hit_msg.room_msg, FALSE, ch, weapon, victim, TO_NOTVICT); } } else { /* Dam == 0 */ act(messages->miss_msg.attacker_msg, FALSE, ch, weapon, victim, TO_CHAR); act(messages->miss_msg.victim_msg, FALSE, ch, weapon, victim, TO_VICT); act(messages->miss_msg.room_msg, FALSE, ch, weapon, victim, TO_NOTVICT); } } } } #endif switch (GET_POS(victim)) { /* * Use send_to_char, because act() doesn't send * message if you are DEAD. */ 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.", 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.", victim); break; case POS_STUNNED: act("$n is stunned, but might regain consciousness again.", TRUE, victim, 0, 0, TO_ROOM); send_to_char("You're stunned, but you might regain consciousness again.", victim); break; case POS_DEAD: act("$n is dead! R.I.P.", TRUE, victim, 0, 0, TO_ROOM); send_to_char("You are dead! Sorry...", victim); break; default: /* >= POS_SLEEP */ max_hit=hit_limit(victim); if (dam > (max_hit/5)) act("Ouch! That Really did HURT!",FALSE, victim, 0, 0, TO_CHAR); if (GET_HIT(victim) < (max_hit/5)) { act("You wish that your wounds would stop BLEEDING so much!",FALSE,victim,0,0,TO_CHAR); } break; } if (!IS_NPC(victim) && !(victim->desc)) { if (!victim->specials.fighting) { act("$n is rescued by divine forces.", FALSE, victim, 0, 0, TO_ROOM); victim->specials.was_in_room = victim->in_room; char_from_room(victim); char_to_room(victim, 0,0); } } if (GET_POS(victim) < POS_STUNNED) if (ch->specials.fighting == victim) stop_fighting(ch); if (!AWAKE(victim)) if (victim->specials.fighting) stop_fighting(victim); if (GET_POS(victim) == POS_DEAD) { if (IS_NPC(victim) || victim->desc) /* Cannot get exp in the arena */ if(!IS_SET(world[ch->in_room].room_flags,ARENA)) { /* Calculate level-difference bonus */ exp = GET_EXP(victim)/3; exp = MAX(exp, 1); gain_exp(ch, exp); change_alignment(ch, victim); } /* if not in arena area */ if (!IS_NPC(victim) && /* No logging if arena 'death' */ !IS_SET(world[ch->in_room].room_flags,ARENA)) { sprintf(buf, "%s killed by %s at %s", GET_NAME(victim), (IS_NPC(ch) ? ch->player.short_descr : GET_NAME(ch)), world[victim->in_room].name); log(buf); } #if 0 if (IS_NPC(ch) /*&& !IS_NPC(victim)*/) {/* Ouch, this isn't */ forget(victim->player.name,ch);/* a perfect solution*/ } /* if */ /* to memory prob... */ #endif die(victim); } } void hit(struct char_data *ch, struct char_data *victim, int type) { struct obj_data *wielded = 0; struct obj_data *held = 0; struct obj_info_weapon *weapon; int w_type; int dam,hit_factor,dam_factor; extern byte backstab_mult[]; if (ch->in_room != victim->in_room) { log("BUG: NOT SAME ROOM WHEN FIGHTING!"); return; } held = get_equip_used(ch,HOLD); wielded = get_equip_used(ch,WIELD); if(!(weapon=(struct obj_info_weapon *)get_obj_info(wielded,ITEM_WEAPON))) { /* Okay, wielded item is not a weapon...hmmm */ log("BUG: Wielded item not a weapon - think about this later"); return; } /* if(wielded) { switch (weapon->damage_type) { case 0 : case 1 : case 2 : w_type = TYPE_WHIP; break; case 3 : w_type = TYPE_SLASH; break; case 4 : case 5 : case 6 : w_type = TYPE_CRUSH; break; case 7 : w_type = TYPE_BLUDGEON; break; case 8 : case 9 : case 10 : w_type = TYPE_CLAW; break; case 11 : w_type = TYPE_PIERCE; break; default : w_type = TYPE_HIT; break; } } else { if (IS_NPC(ch) && (ch->specials.attack_type >= TYPE_HIT)) w_type = ch->specials.attack_type; else w_type = TYPE_HIT; } */ /* Handle specific attack types */ switch(type) { case SKILL_BASH: case SKILL_KICK: hit_factor =-5; dam_factor = 5; break; default: hit_factor = 0; /* Weapon hit factor? */ dam_factor = 0; /* Weapon dam factor? */ break; } if (GET_POS(victim) > POS_STUNNED) { if (!(victim->specials.fighting)) set_fighting(victim, ch); } dam_factor += to_hit_char(ch,victim,hit_factor); if(!dam_factor) { /* Express the miss some other way */ act("You miss $N.",FALSE,ch,0,victim,TO_CHAR); act("$n misses you.",FALSE,ch,0,victim,TO_VICT); act("$n misses $N.",FALSE,ch,0,victim,TO_NOTVICT); /* if (type == SKILL_BACKSTAB) damage(ch, victim, 0, SKILL_BACKSTAB); else damage(ch, victim, 0, w_type);*/ } else { dam = str_app[GET_STR(ch)].todam; dam += GET_DAMROLL(ch)+dam_factor; if (!wielded) { if (IS_NPC(ch)) dam += dice(ch->specials.damnodice, ch->specials.damsizedice); else dam += number(0,2); /* Max. 2 dam with bare hands */ } else { dam += dice(weapon->dice_num, weapon->dice_size); } if (GET_POS(victim) < POS_STAND) dam *= 1+(POS_STAND-GET_POS(victim))/3; /* Position sitting x 1.33 */ /* Position resting x 1.66 */ /* Position sleeping x 2.00 */ /* Position stunned x 2.33 */ /* Position incap x 2.66 */ /* Position mortally x 3.00 */ act("You hit $N.",FALSE,ch,0,victim,TO_CHAR); act("$n hit you.",FALSE,ch,0,victim,TO_VICT); act("$n hits $N.",FALSE,ch,0,victim,TO_NOTVICT); dam = MAX(1, dam); /* Not less than 0 damage */ if (type == SKILL_BACKSTAB) { dam *= backstab_mult[10]; damage(ch, victim, dam, SKILL_BACKSTAB); } else { if (wielded && obj_index[wielded->item_number].func) { if(! (*obj_index[wielded->item_number].func) (wielded, ch, -(dam*1000+w_type) , (char *) victim)) damage(ch,victim,dam,w_type); } else damage(ch, victim, dam, w_type); } } } void lose_exp_by_flight(struct char_data *ch) { int lose; if(IS_NPC(ch) || IS_SET(world[ch->in_room].room_flags,ARENA)) return; lose=GET_MAX_HIT(ch->specials.fighting)-GET_HIT(ch->specials.fighting); gain_exp(ch,-lose); } /* control the fights going on */ void perform_violence(void) { struct char_data *ch; for (ch = combat_list; ch; ch=combat_next_dude) { combat_next_dude = ch->next_fighting; if(!ch->specials.fighting) { log("BUG: non-fighting character in combat list"); continue; } if(AWAKE(ch) && (ch->in_room==ch->specials.fighting->in_room)) { hit(ch, ch->specials.fighting, ch->specials.attack); } else { /* Not in same room */ stop_fighting(ch); } } } /* New generic hitting calculators */ int to_hit(struct char_data *ch, int vict_ac, int adj) { int hit,diceroll; struct char_skill_data *sk; if((sk=get_skill(ch,SKILL_OFFENSE))) hit = 20 - sk->learned/10; else hit = 20; hit -= str_app[GET_STR(ch)].tohit; hit -= GET_HITROLL(ch); hit -= vict_ac; diceroll = number(1,20)+adj; if((diceroll-hit)<0) return(0); else return(diceroll-hit+1); } int to_hit_char(struct char_data *ch,struct char_data *victim,int adj) { int victim_ac; extern struct dex_app_type dex_app[]; victim_ac = GET_AC(victim)/10; if (AWAKE(victim)) victim_ac += dex_app[GET_DEX(victim)].defensive; /* if(IS_EVIL(ch) && IS_AFFECTED(victim,AFF_PROTECT_EVIL)) victim_ac -= 1;*/ victim_ac = MAX(-10, victim_ac); /* -10 is lowest */ return(to_hit(ch,victim_ac,adj)); }