From 356042790262d352215459efb026ee96da723f6d Mon Sep 17 00:00:00 2001 From: kinther Date: Tue, 19 Aug 2025 13:18:53 -0700 Subject: [PATCH] Add basic combat skills --- src/class.c | 49 +++++++++++++++- src/fight.c | 142 +++++++++++++++++++++++++++++++++++---------- src/spell_parser.c | 5 ++ src/spells.h | 5 ++ 4 files changed, 168 insertions(+), 33 deletions(-) diff --git a/src/class.c b/src/class.c index abc6067..55e2b69 100644 --- a/src/class.c +++ b/src/class.c @@ -946,6 +946,7 @@ void do_start(struct char_data *ch) SET_SKILL(ch, SPELL_FLY, 5); SET_SKILL(ch, SPELL_ENCHANT_WEAPON, 5); SET_SKILL(ch, SPELL_CLONE, 5); + SET_SKILL(ch, SKILL_UNARMED, 5); break; case CLASS_CLERIC: @@ -980,6 +981,7 @@ void do_start(struct char_data *ch) SET_SKILL(ch, SPELL_HARM, 5); SET_SKILL(ch, SPELL_GROUP_HEAL, 5); SET_SKILL(ch, SPELL_REMOVE_CURSE, 5); + SET_SKILL(ch, SKILL_SHIELD_USE, 5); break; case CLASS_THIEF: @@ -988,6 +990,9 @@ void do_start(struct char_data *ch) SET_SKILL(ch, SKILL_STEAL, 5); SET_SKILL(ch, SKILL_BACKSTAB, 5); SET_SKILL(ch, SKILL_PICK_LOCK, 5); + SET_SKILL(ch, SKILL_UNARMED, 5); + SET_SKILL(ch, SKILL_SHIELD_USE, 5); + SET_SKILL(ch, SKILL_PIERCING_WEAPONS, 5); break; case CLASS_WARRIOR: @@ -995,7 +1000,11 @@ void do_start(struct char_data *ch) SET_SKILL(ch, SKILL_RESCUE, 5); SET_SKILL(ch, SKILL_BANDAGE, 5); SET_SKILL(ch, SKILL_BASH, 5); - SET_SKILL(ch, SKILL_WHIRLWIND, 5); + SET_SKILL(ch, SKILL_UNARMED, 5); + SET_SKILL(ch, SKILL_SLASHING_WEAPONS, 5); + SET_SKILL(ch, SKILL_PIERCING_WEAPONS, 5); + SET_SKILL(ch, SKILL_BLUDGEONING_WEAPONS, 5); + SET_SKILL(ch, SKILL_SHIELD_USE, 5); break; case CLASS_BARBARIAN: @@ -1003,6 +1012,9 @@ void do_start(struct char_data *ch) SET_SKILL(ch, SKILL_RESCUE, 5); SET_SKILL(ch, SKILL_BANDAGE, 5); SET_SKILL(ch, SKILL_WHIRLWIND, 5); + SET_SKILL(ch, SKILL_UNARMED, 5); + SET_SKILL(ch, SKILL_PIERCING_WEAPONS, 5); + SET_SKILL(ch, SKILL_BLUDGEONING_WEAPONS, 5); break; case CLASS_RANGER: @@ -1011,6 +1023,10 @@ void do_start(struct char_data *ch) SET_SKILL(ch, SKILL_BANDAGE, 5); SET_SKILL(ch, SKILL_TRACK, 5); SET_SKILL(ch, SKILL_BASH, 5); + SET_SKILL(ch, SKILL_UNARMED, 5); + SET_SKILL(ch, SKILL_SLASHING_WEAPONS, 5); + SET_SKILL(ch, SKILL_PIERCING_WEAPONS, 5); + SET_SKILL(ch, SKILL_SHIELD_USE, 5); break; case CLASS_BARD: @@ -1019,6 +1035,9 @@ void do_start(struct char_data *ch) SET_SKILL(ch, SKILL_BANDAGE, 5); SET_SKILL(ch, SKILL_TRACK, 5); SET_SKILL(ch, SKILL_PICK_LOCK, 5); + SET_SKILL(ch, SKILL_UNARMED, 5); + SET_SKILL(ch, SKILL_PIERCING_WEAPONS, 5); + SET_SKILL(ch, SKILL_SHIELD_USE, 5); break; case CLASS_DRUID: @@ -1027,6 +1046,9 @@ void do_start(struct char_data *ch) SET_SKILL(ch, SPELL_LOCATE_OBJECT, 5); SET_SKILL(ch, SKILL_BANDAGE, 5); SET_SKILL(ch, SKILL_TRACK, 5); + SET_SKILL(ch, SKILL_UNARMED, 5); + SET_SKILL(ch, SKILL_PIERCING_WEAPONS, 5); + SET_SKILL(ch, SKILL_SHIELD_USE, 5); break; } @@ -1197,6 +1219,7 @@ void init_spell_levels(void) spell_level(SPELL_FLY, CLASS_MAGIC_USER, 1); spell_level(SPELL_ENCHANT_WEAPON, CLASS_MAGIC_USER, 1); spell_level(SPELL_CLONE, CLASS_MAGIC_USER, 1); + spell_level(SKILL_UNARMED, CLASS_MAGIC_USER, 1); /* CLERICS */ spell_level(SPELL_CURE_LIGHT, CLASS_CLERIC, 1); @@ -1230,6 +1253,7 @@ void init_spell_levels(void) spell_level(SPELL_HARM, CLASS_CLERIC, 1); spell_level(SPELL_GROUP_HEAL, CLASS_CLERIC, 1); spell_level(SPELL_REMOVE_CURSE, CLASS_CLERIC, 1); + spell_level(SKILL_SHIELD_USE, CLASS_CLERIC, 1); /* THIEVES */ spell_level(SKILL_SNEAK, CLASS_THIEF, 1); @@ -1238,20 +1262,29 @@ void init_spell_levels(void) spell_level(SKILL_STEAL, CLASS_THIEF, 1); spell_level(SKILL_HIDE, CLASS_THIEF, 1); spell_level(SKILL_TRACK, CLASS_THIEF, 1); + spell_level(SKILL_UNARMED, CLASS_THIEF, 1); + spell_level(SKILL_PIERCING_WEAPONS, CLASS_THIEF, 1); + spell_level(SKILL_SHIELD_USE, CLASS_THIEF, 1); /* WARRIORS */ spell_level(SKILL_KICK, CLASS_WARRIOR, 1); spell_level(SKILL_RESCUE, CLASS_WARRIOR, 1); spell_level(SKILL_BANDAGE, CLASS_WARRIOR, 1); - spell_level(SKILL_TRACK, CLASS_WARRIOR, 1); spell_level(SKILL_BASH, CLASS_WARRIOR, 1); - spell_level(SKILL_WHIRLWIND, CLASS_WARRIOR, 1); + spell_level(SKILL_UNARMED, CLASS_WARRIOR, 1); + spell_level(SKILL_SLASHING_WEAPONS, CLASS_WARRIOR, 1); + spell_level(SKILL_PIERCING_WEAPONS, CLASS_WARRIOR, 1); + spell_level(SKILL_BLUDGEONING_WEAPONS, CLASS_WARRIOR, 1); + spell_level(SKILL_SHIELD_USE, CLASS_WARRIOR, 1); /* BARBARIANS */ spell_level(SKILL_KICK, CLASS_BARBARIAN, 1); spell_level(SKILL_RESCUE, CLASS_BARBARIAN, 1); spell_level(SKILL_BANDAGE, CLASS_BARBARIAN, 1); spell_level(SKILL_WHIRLWIND, CLASS_BARBARIAN, 1); + spell_level(SKILL_UNARMED, CLASS_BARBARIAN, 1); + spell_level(SKILL_SLASHING_WEAPONS, CLASS_BARBARIAN, 1); + spell_level(SKILL_BLUDGEONING_WEAPONS, CLASS_BARBARIAN, 1); /* RANGERS */ spell_level(SKILL_SNEAK, CLASS_RANGER, 1); @@ -1259,6 +1292,10 @@ void init_spell_levels(void) spell_level(SKILL_BANDAGE, CLASS_RANGER, 1); spell_level(SKILL_TRACK, CLASS_RANGER, 1); spell_level(SKILL_BASH, CLASS_RANGER, 1); + spell_level(SKILL_UNARMED, CLASS_RANGER, 1); + spell_level(SKILL_SLASHING_WEAPONS, CLASS_RANGER, 1); + spell_level(SKILL_PIERCING_WEAPONS, CLASS_RANGER, 1); + spell_level(SKILL_SHIELD_USE, CLASS_RANGER, 1); /* BARDS */ spell_level(SPELL_ARMOR, CLASS_BARD, 1); @@ -1266,6 +1303,9 @@ void init_spell_levels(void) spell_level(SKILL_BANDAGE, CLASS_BARD, 1); spell_level(SKILL_TRACK, CLASS_BARD, 1); spell_level(SKILL_PICK_LOCK, CLASS_BARD, 1); + spell_level(SKILL_UNARMED, CLASS_BARD, 1); + spell_level(SKILL_PIERCING_WEAPONS, CLASS_BARD, 1); + spell_level(SKILL_SHIELD_USE, CLASS_BARD, 1); /* DRUIDS */ spell_level(SPELL_DETECT_INVIS, CLASS_DRUID, 1); @@ -1273,6 +1313,9 @@ void init_spell_levels(void) spell_level(SPELL_LOCATE_OBJECT, CLASS_DRUID, 1); spell_level(SKILL_BANDAGE, CLASS_DRUID, 1); spell_level(SKILL_TRACK, CLASS_DRUID, 1); + spell_level(SKILL_UNARMED, CLASS_DRUID, 1); + spell_level(SKILL_PIERCING_WEAPONS, CLASS_DRUID, 1); + spell_level(SKILL_SHIELD_USE, CLASS_DRUID, 1); } /* This is the exp given to implementors -- it must always be greater than the diff --git a/src/fight.c b/src/fight.c index 7feec0d..8913c08 100644 --- a/src/fight.c +++ b/src/fight.c @@ -64,6 +64,53 @@ static void solo_gain(struct char_data *ch, struct char_data *victim); static char *replace_string(const char *str, const char *weapon_singular, const char *weapon_plural); static int compute_thaco(struct char_data *ch, struct char_data *vict); +/* Map the current attack (unarmed/weapon damage type) to SKILL_* constant. */ +static int weapon_family_skill_num(struct char_data *ch, struct obj_data *wielded, int w_type) { + /* Unarmed? */ + if (!wielded || GET_OBJ_TYPE(wielded) != ITEM_WEAPON) + return SKILL_UNARMED; + + /* NOTE: w_type here is TYPE_HIT + GET_OBJ_VAL(wielded, 3) or mob attack type + TYPE_HIT. + Adjust the cases below to match your game's TYPE_* values. */ + switch (w_type) { + /* --- Piercing family --- */ + case TYPE_STAB: + case TYPE_PIERCE: + case TYPE_WHIP: + case TYPE_STING: + return SKILL_PIERCING_WEAPONS; + + /* --- Slashing family --- */ + case TYPE_SLASH: + case TYPE_MAUL: + case TYPE_CLAW: + return SKILL_SLASHING_WEAPONS; + + /* --- Bludgeoning family --- */ + case TYPE_BLUDGEON: + case TYPE_CRUSH: + case TYPE_THRASH: + case TYPE_POUND: + return SKILL_BLUDGEONING_WEAPONS; + + /* Fallback */ + default: + return SKILL_UNARMED; + } +} + +/* Map SKILL_* constants to the strings your gain_skill(name, failure) expects. */ +static const char *skill_name_for_gain(int skillnum) { + switch (skillnum) { + case SKILL_UNARMED: return "unarmed"; + case SKILL_PIERCING_WEAPONS: return "piercing weapons"; + case SKILL_SLASHING_WEAPONS: return "slashing weapons"; + case SKILL_BLUDGEONING_WEAPONS: return "bludgeoning weapons"; + case SKILL_SHIELD_USE: return "shield use"; + default: return "unarmed"; + } +} + #define IS_WEAPON(type) (((type) >= TYPE_HIT) && ((type) < TYPE_SUFFERING)) /* The Fight related routines */ @@ -776,7 +823,8 @@ static int compute_thaco(struct char_data *ch, struct char_data *victim) void hit(struct char_data *ch, struct char_data *victim, int type) { struct obj_data *wielded = GET_EQ(ch, WEAR_WIELD); - int w_type, victim_ac, calc_thaco, dam, diceroll; + int w_type, victim_ac, calc_thaco, diceroll; + int dam; /* Check that the attacker and victim exist */ if (!ch || !victim) return; @@ -812,51 +860,84 @@ void hit(struct char_data *ch, struct char_data *victim, int type) /* report for debugging if necessary */ if (CONFIG_DEBUG_MODE >= NRM) - send_to_char(ch, "\t1Debug:\r\n \t2Thaco: \t3%d\r\n \t2AC: \t3%d\r\n \t2Diceroll: \t3%d\tn\r\n", + send_to_char(ch, "\t1Debug:\r\n \t2Thaco: \t3%d\r\n \t2AC: \t3%d\r\n \t2Diceroll: \t3%d\tn\r\n", calc_thaco, victim_ac, diceroll); - /* 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)) + /* ----------------------------------------------------------- + * To-hit & Shield bonuses: + * - Natural 20 = auto hit, 1 = auto miss (keep unchanged) + * - Otherwise, modify the *roll* with: + * + (attacker skill / 10) // attack bonus + * - (defender shield / 10) // shield reduces attacker's roll + * Bonuses < 1 count as zero. + * ----------------------------------------------------------- */ + if (diceroll == 20 || !AWAKE(victim)) { dam = TRUE; - else if (diceroll == 1) + } else if (diceroll == 1) { dam = FALSE; - else - dam = (calc_thaco - diceroll <= victim_ac); + } else { + int d_adj = diceroll; - if (!dam) + /* Attacker’s family skill */ + int atk_skillnum = weapon_family_skill_num(ch, wielded, w_type); + int atk_skill = (atk_skillnum > 0) ? GET_SKILL(ch, atk_skillnum) : 0; + int atk_bonus = atk_skill / 10; /* e.g., 0..10 */ + if (atk_bonus < 1) atk_bonus = 0; /* ignore < 1 */ + + /* Defender shield */ + int sh_bonus = 0; + if (GET_EQ(victim, WEAR_SHIELD)) { + int sh_skill = GET_SKILL(victim, SKILL_SHIELD_USE); + sh_bonus = sh_skill / 10; + if (sh_bonus < 1) sh_bonus = 0; + } + + d_adj += atk_bonus; + d_adj -= sh_bonus; + + /* NOTE: do not force auto-1/20 from adjusted roll; we keep raw auto logic above. */ + dam = (calc_thaco - d_adj <= victim_ac); + + if (CONFIG_DEBUG_MODE >= NRM) { + send_to_char(ch, " \t2Atk bonus: \t3%d\t2 Shield red: \t3%d\t2 Adj roll: \t3%d\tn\r\n", + atk_bonus, sh_bonus, d_adj); + } + } + + /* Skill gains: once per swing, after hit/miss known */ + { + /* Attacker gains in the family skill used */ + int atk_skillnum = weapon_family_skill_num(ch, wielded, w_type); + const char *atk_skill_name = skill_name_for_gain(atk_skillnum); + gain_skill(ch, (char *)atk_skill_name, dam ? FALSE : TRUE); + + /* Defender gains in shield use if wearing a shield */ + if (GET_EQ(victim, WEAR_SHIELD)) { + /* If miss → shield succeeded (failure=FALSE). If hit → shield failed (failure=TRUE). */ + gain_skill(victim, "shield use", dam ? TRUE : FALSE); + } + } + + if (!dam) { /* the attacker missed the victim */ - damage(ch, victim, 0, type == SKILL_BACKSTAB ? SKILL_BACKSTAB : w_type); - else { + damage(ch, victim, 0, (type == SKILL_BACKSTAB) ? SKILL_BACKSTAB : w_type); + } else { /* okay, we know the guy has been hit. now calculate damage. * Start with the damage bonuses: the damroll and strength apply */ dam = str_app[STRENGTH_APPLY_INDEX(ch)].todam; dam += GET_DAMROLL(ch); - /* Maybe holding arrow? */ + /* Weapon or bare hands? */ 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)); } else { - /* If no weapon, add bare hand damage instead */ - if (IS_NPC(ch)) - dam += dice(ch->mob_specials.damnodice, ch->mob_specials.damsizedice); - else - dam += rand_number(0, 2); /* Max 2 bare hand damage for players */ + if (IS_NPC(ch)) + dam += dice(ch->mob_specials.damnodice, ch->mob_specials.damsizedice); + else + dam += rand_number(0, 2); /* Max 2 bare hand damage for players */ } - /* 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. */ + /* Position-based damage multiplier (unchanged) */ if (GET_POS(victim) < POS_FIGHTING) dam *= 1 + (POS_FIGHTING - GET_POS(victim)) / 3; @@ -873,6 +954,7 @@ void hit(struct char_data *ch, struct char_data *victim, int type) hitprcnt_mtrigger(victim); } + /* control the fights going on. Called every 2 seconds from comm.c. */ void perform_violence(void) { diff --git a/src/spell_parser.c b/src/spell_parser.c index f835d79..c1d2e1b 100644 --- a/src/spell_parser.c +++ b/src/spell_parser.c @@ -929,5 +929,10 @@ void mag_assign_spells(void) { skillo(SKILL_TRACK, "track"); skillo(SKILL_WHIRLWIND, "whirlwind"); skillo(SKILL_BANDAGE, "bandage"); + skillo(SKILL_UNARMED, "unarmed fighting"); + skillo(SKILL_SHIELD_USE, "shield use"); + skillo(SKILL_PIERCING_WEAPONS, "piercing weapons"); + skillo(SKILL_SLASHING_WEAPONS, "slashing weapons"); + skillo(SKILL_BLUDGEONING_WEAPONS, "bludgeoning weapons"); } diff --git a/src/spells.h b/src/spells.h index 4bcb8ec..e40a59e 100644 --- a/src/spells.h +++ b/src/spells.h @@ -111,6 +111,11 @@ #define SKILL_STEAL 139 /* Reserved Skill[] DO NOT CHANGE */ #define SKILL_TRACK 140 /* Reserved Skill[] DO NOT CHANGE */ #define SKILL_BANDAGE 141 /* Reserved Skill[] DO NOT CHANGE */ +#define SKILL_UNARMED 142 /* Reserved Skill[] DO NOT CHANGE */ +#define SKILL_SHIELD_USE 143 /* Reserved Skill[] DO NOT CHANGE */ +#define SKILL_PIERCING_WEAPONS 144 /* Reserved Skill[] DO NOT CHANGE */ +#define SKILL_SLASHING_WEAPONS 145 /* Reserved Skill[] DO NOT CHANGE */ +#define SKILL_BLUDGEONING_WEAPONS 146 /* Reserved Skill[] DO NOT CHANGE */ /* New skills may be added here up to MAX_SKILLS (200) */