diff --git a/include/class.hpp b/include/class.hpp index b04412e2..cea13cb4 100644 --- a/include/class.hpp +++ b/include/class.hpp @@ -14,6 +14,7 @@ #include "chars.hpp" #include "objects.hpp" +#include "races.hpp" #include "structs.hpp" #include "sysdep.hpp" @@ -132,12 +133,9 @@ extern ClassDef classes[NUM_CLASSES]; #define IS_WARRIOR(ch) \ (VALID_CLASS(ch) ? GET_CLASS(ch) == CLASS_WARRIOR || classes[(int)GET_CLASS(ch)].subclass_of == CLASS_WARRIOR \ : false) -#define IS_SPELLCASTER(ch) \ - (VALID_CLASS(ch) ? GET_CLASS(ch) == CLASS_SORCERER || classes[(int)GET_CLASS(ch)].subclass_of == CLASS_SORCERER || \ - GET_CLASS(ch) == CLASS_CLERIC || classes[(int)GET_CLASS(ch)].subclass_of == CLASS_CLERIC || \ - GET_CLASS(ch) == CLASS_ANTI_PALADIN || GET_CLASS(ch) == CLASS_PALADIN || \ - GET_CLASS(ch) == CLASS_RANGER || GET_CLASS(ch) == CLASS_SHAMAN || GET_CLASS(ch) == CLASS_BARD \ - : false) + +#define IS_SPELLCASTER_CLASS(ch) (VALID_CLASS(ch) ? classes[(int)GET_CLASS(ch)].magical : false) +#define IS_SPELLCASTER(ch) (IS_SPELLCASTER_CLASS(ch) || IS_SPELLCASTER_RACE(ch)) /* Is this character prohibited from wearing this equipment due to a class * restriction? Would return true if, for example, a warrior tried to wear @@ -147,6 +145,7 @@ extern ClassDef classes[NUM_CLASSES]; ? classes[(int)GET_CLASS(ch)].nowear_flag && OBJ_FLAGGED(obj, classes[(int)GET_CLASS(ch)].nowear_flag) \ : 0) + enum level_action { LEVEL_GAIN, LEVEL_LOSE }; void init_classes(void); diff --git a/include/races.hpp b/include/races.hpp index 29b536dd..88de41e3 100644 --- a/include/races.hpp +++ b/include/races.hpp @@ -91,6 +91,9 @@ #define RACE_DICEFACTOR(race) (VALID_RACENUM(race) ? races[race].dice_factor : 100) #define RACE_COPPERFACTOR(race) (VALID_RACENUM(race) ? races[race].copper_factor : 100) #define RACE_ACFACTOR(race) (VALID_RACENUM(race) ? races[race].ac_factor : 100) + +#define IS_SPELLCASTER_RACE(ch) (VALID_RACE(ch) ? races[(int)GET_RACE(ch)].magical : false) + struct RaceDef { const char *name; /* The basic name, uncapitalized and uncolored. */ const char *names; /* Additional names for searching purposes. */ @@ -99,6 +102,7 @@ struct RaceDef { const char *plainname; /* The name with capitalization but no colors. */ bool playable; /* Available to mortals? */ bool humanoid; /* Is it humanoid? */ + bool magical; /* Does it have racial spells? */ int racealign; /* Is it considered a good or evil race? */ int def_size; /* The default size for a member of this race. */ int def_align; /* Default alignment */ @@ -149,6 +153,7 @@ extern RaceDef races[NUM_RACES]; extern const char *race_align_abbrevs[]; void init_races(void); +void assign_race_skills(void); int parse_race(CharData *ch, CharData *vict, char *arg); int race_by_menu(char arg); void send_race_menu(DescriptorData *d); diff --git a/include/skills.hpp b/include/skills.hpp index 7e2f682c..a1073e48 100644 --- a/include/skills.hpp +++ b/include/skills.hpp @@ -48,6 +48,7 @@ struct SkillDef { int pages; /* base number of pages for spell in spellbook */ int quest; /* weather the spell is a quest spell or not */ const char *wearoff; + int min_race_level[NUM_RACES]; }; extern SkillDef skills[TOP_SKILL_DEFINE + 1]; @@ -57,7 +58,9 @@ extern SkillDef skills[TOP_SKILL_DEFINE + 1]; int level_to_circle(int level); int circle_to_level(int circle); #define IS_QUEST_SPELL(spellnum) (skills[(spellnum)].quest) -#define SKILL_LEVEL(ch, skillnum) (skills[(skillnum)].min_level[(int)GET_CLASS(ch)]) +#define SKILL_LEVEL(ch, skillnum) \ + ((skills[(skillnum)].min_level[(int)GET_CLASS(ch)] <= skills[(skillnum)].min_race_level[(int)GET_RACE(ch)]) ? \ + skills[(skillnum)].min_level[(int)GET_CLASS(ch)] : skills[(skillnum)].min_race_level[(int)GET_RACE(ch)]) #define SPELL_CIRCLE(ch, spellnum) (level_to_circle(SKILL_LEVEL(ch, spellnum))) #define CIRCLE_ABBR(ch, spellnum) (circle_abbrev[SPELL_CIRCLE((ch), (spellnum))]) #define SKILL_IS_TARGET(skill, tartype) \ @@ -79,6 +82,7 @@ void improve_skill(CharData *ch, int skill); void improve_skill_offensively(CharData *ch, CharData *victim, int skill); void update_skills(CharData *ch); void skill_assign(int skillnum, int class_code, int level); +void race_skill_assign(int skillnum, int race_code, int level); int talent_type(int skill_num); bool get_spell_assignment_circle(CharData *ch, int spell, int *circle_assignment, int *level_assignment); diff --git a/src/act.informative.cpp b/src/act.informative.cpp index 65aaddf3..8be1f0a0 100644 --- a/src/act.informative.cpp +++ b/src/act.informative.cpp @@ -3563,8 +3563,8 @@ ACMD(do_spells) { } } - /* Is this character in a class with spells? */ - if (MEM_MODE(tch) == MEM_NONE) { + /* Is this character in a class or a race with spells? */ + if (!IS_SPELLCASTER(tch)) { if (tch == ch) char_printf(ch, "You don't know any spells.\n"); else { @@ -3574,6 +3574,7 @@ ACMD(do_spells) { return; } + /* Collect and count the spells known by the victim. */ memset(&circle_spells, 0, sizeof(circle_spells)); @@ -3582,13 +3583,21 @@ ACMD(do_spells) { i = skill_sort_info[k]; if (!IS_SPELL(i)) continue; - if (skills[i].min_level[GET_CLASS(tch)] < LVL_IMMORT && GET_SKILL(tch, i) > 0) { + if (skills[i].min_level[GET_CLASS(tch)] < LVL_IMMORT && GET_SKILL(tch, i) > 0 && skills[i].min_level[GET_CLASS(tch)] <= skills[i].min_race_level[GET_RACE(tch)]) { circle = (skills[i].min_level[GET_CLASS(tch)] - 1) / 8; for (j = 0; circle_spells[circle][j] && j < MAX_SPELLS_PER_CIRCLE && circle < max_vis_circle; ++j) ; if (!xcircle || xcircle == circle + 1) numspellsknown++; circle_spells[circle][j] = i; + + } else if (skills[i].min_race_level[GET_RACE(tch)] < LVL_IMMORT && GET_SKILL(tch, i) > 0) { + circle = (skills[i].min_race_level[GET_RACE(tch)] - 1) / 8; + for (j = 0; circle_spells[circle][j] && j < MAX_SPELLS_PER_CIRCLE && circle < max_vis_circle; ++j) + ; + if (!xcircle || xcircle == circle + 1) + numspellsknown++; + circle_spells[circle][j] = i; } } diff --git a/src/act.other.cpp b/src/act.other.cpp index 9b111623..7286f262 100644 --- a/src/act.other.cpp +++ b/src/act.other.cpp @@ -646,7 +646,7 @@ ACMD(do_shapechange) { char_to_room(mob, ch->in_room); /* Transfer hover slot items to new mob */ - if GET_EQ (ch, WEAR_HOVER) { + if GET_EQ(ch, WEAR_HOVER) { obj = GET_EQ(ch, WEAR_HOVER); unequip_char(ch, WEAR_HOVER); diff --git a/src/cleric.cpp b/src/cleric.cpp index 5d518d63..4b7d36f8 100644 --- a/src/cleric.cpp +++ b/src/cleric.cpp @@ -164,7 +164,7 @@ bool cleric_ai_action(CharData *ch, CharData *victim) { case SPELL_SILENCE: for (victim = world[ch->in_room].people; victim; victim = next_victim) { next_victim = victim->next_in_room; - if (IS_SPELLCASTER(victim) && FIGHTING(victim) == ch && + if ((IS_SPELLCASTER(victim)) && FIGHTING(victim) == ch && !has_effect(victim, &mob_cleric_hindrances[i]) && GET_LEVEL(victim) < (GET_LEVEL(ch) + 20)) { if (mob_cast(ch, victim, nullptr, mob_cleric_hindrances[i].spell)) return true; diff --git a/src/db.cpp b/src/db.cpp index 9bee79bc..fa20f6a7 100644 --- a/src/db.cpp +++ b/src/db.cpp @@ -557,10 +557,13 @@ void boot_db(void) { log(" Skills."); init_skills(); - + log("Assigning skills and spells to classes."); assign_class_skills(); + log("Assigning skills and spells to races."); + assign_race_skills(); + /* Command sorting needs to happen before many other loading * activities, because sorting the commands initializes the * num_of_cmds variable. diff --git a/src/races.cpp b/src/races.cpp index 2f4131d0..87aea336 100644 --- a/src/races.cpp +++ b/src/races.cpp @@ -42,7 +42,7 @@ void set_init_height_weight(CharData *ch); * this arrangement: * * name, names, displayname, fullname, plainname, - * playable, humanoid, racealign, def_size, def_align, \ + * playable, humanoid, magical, racealign, def_size, def_align, \ * bonus_damroll, bonus_hitroll, * def_lifeforce, def_composition, * mweight_lo, mweight_hi, mheight_lo, mheight_hi, \ @@ -63,6 +63,7 @@ RaceDef races[NUM_RACES] = { "Human", /* name as displayed in medit vsearch stat and enlightenment */ true, /* playable? */ true, /* humanoid? */ + false, /* casts race spells? */ RACE_ALIGN_GOOD, /* race alignment */ SIZE_MEDIUM, /* default size */ 0, /* default alignment */ @@ -98,6 +99,7 @@ RaceDef races[NUM_RACES] = { "Elf", /* name as displayed in medit vsearch stat and enlightenment */ true, /* playable? */ true, /* humanoid? */ + true, /* casts race spells? */ RACE_ALIGN_GOOD, /* race alignment */ SIZE_MEDIUM, /* default size */ 1000, /* default alignment */ @@ -133,6 +135,7 @@ RaceDef races[NUM_RACES] = { "Gnome", /* name as displayed in medit vsearch stat and enlightenment */ true, /* playable? */ true, /* humanoid? */ + false, /* casts race spells? */ RACE_ALIGN_GOOD, /* race alignment */ SIZE_SMALL, /* default size */ 1000, /* default alignment */ @@ -168,6 +171,7 @@ RaceDef races[NUM_RACES] = { "Dwarf", /* name as displayed in medit vsearch stat and enlightenment */ true, /* playable? */ true, /* humanoid? */ + false, /* casts race spells? */ RACE_ALIGN_GOOD, /* race alignment */ SIZE_MEDIUM, /* default size */ 1000, /* default alignment */ @@ -203,6 +207,7 @@ RaceDef races[NUM_RACES] = { "Troll", /* name as displayed in medit vsearch stat and enlightenment */ true, /* playable? */ true, /* humanoid? */ + false, /* casts race spells? */ RACE_ALIGN_EVIL, /* race alignment */ SIZE_LARGE, /* default size */ -1000, /* default alignment */ @@ -238,6 +243,7 @@ RaceDef races[NUM_RACES] = { "Drow", /* name as displayed in medit vsearch stat and enlightenment */ true, /* playable? */ true, /* humanoid? */ + false, /* casts race spells? */ RACE_ALIGN_EVIL, /* race alignment */ SIZE_MEDIUM, /* default size */ -1000, /* default alignment */ @@ -273,6 +279,7 @@ RaceDef races[NUM_RACES] = { "Duergar", /* name as displayed in medit vsearch stat and enlightenment */ true, /* playable? */ true, /* humanoid? */ + false, /* casts race spells? */ RACE_ALIGN_EVIL, /* race alignment */ SIZE_MEDIUM, /* default size */ -1000, /* default alignment */ @@ -308,6 +315,7 @@ RaceDef races[NUM_RACES] = { "Ogre", /* name as displayed in medit vsearch stat and enlightenment */ true, /* playable? */ true, /* humanoid? */ + false, /* casts race spells? */ RACE_ALIGN_EVIL, /* race alignment */ SIZE_LARGE, /* default size */ -1000, /* default alignment */ @@ -343,6 +351,7 @@ RaceDef races[NUM_RACES] = { "Orc", /* name as displayed in medit vsearch stat and enlightenment */ true, /* playable? */ true, /* humanoid? */ + false, /* casts race spells? */ RACE_ALIGN_EVIL, /* race alignment */ SIZE_MEDIUM, /* default size */ -1000, /* default alignment */ @@ -378,6 +387,7 @@ RaceDef races[NUM_RACES] = { "Half-Elf", /* name as displayed in medit vsearch stat and enlightenment */ true, /* playable? */ true, /* humanoid? */ + false, /* casts race spells? */ RACE_ALIGN_GOOD, /* race alignment */ SIZE_MEDIUM, /* default size */ 1000, /* default alignment */ @@ -413,6 +423,7 @@ RaceDef races[NUM_RACES] = { "Barbarian", /* name as displayed in medit vsearch stat and enlightenment */ true, /* playable? */ true, /* humanoid? */ + false, /* casts race spells? */ RACE_ALIGN_GOOD, /* race alignment */ SIZE_LARGE, /* default size */ 0, /* default alignment */ @@ -448,6 +459,7 @@ RaceDef races[NUM_RACES] = { "Halfling", /* name as displayed in medit vsearch stat and enlightenment */ true, /* playable? */ true, /* humanoid? */ + false, /* casts race spells? */ RACE_ALIGN_GOOD, /* race alignment */ SIZE_SMALL, /* default size */ 1000, /* default alignment */ @@ -483,6 +495,7 @@ RaceDef races[NUM_RACES] = { "Plant", /* name as displayed in medit vsearch stat and enlightenment */ false, /* playable? */ false, /* humanoid? */ + false, /* casts race spells? */ RACE_ALIGN_GOOD, /* race alignment */ SIZE_MEDIUM, /* default size */ 0, /* default alignment */ @@ -518,6 +531,7 @@ RaceDef races[NUM_RACES] = { "Humanoid", /* name as displayed in medit vsearch stat and enlightenment */ false, /* playable? */ true, /* humanoid? */ + false, /* casts race spells? */ RACE_ALIGN_GOOD, /* race alignment */ SIZE_MEDIUM, /* default size */ 0, /* default alignment */ @@ -553,6 +567,7 @@ RaceDef races[NUM_RACES] = { "Animal", /* name as displayed in medit vsearch stat and enlightenment */ false, /* playable? */ false, /* humanoid? */ + false, /* casts race spells? */ RACE_ALIGN_GOOD, /* race alignment */ SIZE_MEDIUM, /* default size */ 0, /* default alignment */ @@ -588,6 +603,7 @@ RaceDef races[NUM_RACES] = { "General Dragon", /* name as displayed in medit vsearch stat and enlightenment */ false, /* playable? */ false, /* humanoid? */ + false, /* casts race spells? */ RACE_ALIGN_GOOD, /* race alignment */ SIZE_GARGANTUAN, /* default size */ 0, /* default alignment */ @@ -623,6 +639,7 @@ RaceDef races[NUM_RACES] = { "Giant", /* name as displayed in medit vsearch stat and enlightenment */ false, /* playable? */ true, /* humanoid? */ + false, /* casts race spells? */ RACE_ALIGN_GOOD, /* race alignment */ SIZE_HUGE, /* default size */ 0, /* default alignment */ @@ -658,6 +675,7 @@ RaceDef races[NUM_RACES] = { "Other", /* name as displayed in medit vsearch stat and enlightenment */ false, /* playable? */ false, /* humanoid? */ + false, /* casts race spells? */ RACE_ALIGN_GOOD, /* race alignment */ SIZE_MEDIUM, /* default size */ 0, /* default alignment */ @@ -693,6 +711,7 @@ RaceDef races[NUM_RACES] = { "Goblin", /* name as displayed in medit vsearch stat and enlightenment */ false, /* playable? */ true, /* humanoid? */ + false, /* casts race spells? */ RACE_ALIGN_EVIL, /* race alignment */ SIZE_SMALL, /* default size */ -500, /* default alignment */ @@ -728,6 +747,7 @@ RaceDef races[NUM_RACES] = { "Demon", /* name as displayed in medit vsearch stat and enlightenment */ false, /* playable? */ true, /* humanoid? */ + false, /* casts race spells? */ RACE_ALIGN_EVIL, /* race alignment */ SIZE_LARGE, /* default size */ -1000, /* default alignment */ @@ -763,6 +783,7 @@ RaceDef races[NUM_RACES] = { "Brownie", /* name as displayed in medit vsearch stat and enlightenment */ false, /* playable? */ true, /* humanoid? */ + false, /* casts race spells? */ RACE_ALIGN_GOOD, /* race alignment */ SIZE_SMALL, /* default size */ 500, /* default alignment */ @@ -798,6 +819,7 @@ RaceDef races[NUM_RACES] = { "Fire Dragon", /* name as displayed in medit vsearch stat and enlightenment */ false, /* playable? */ false, /* humanoid? */ + false, /* casts race spells? */ RACE_ALIGN_GOOD, /* race alignment */ SIZE_GARGANTUAN, /* default size */ 0, /* default alignment */ @@ -833,6 +855,7 @@ RaceDef races[NUM_RACES] = { "Frost Dragon", /* name as displayed in medit vsearch stat and enlightenment */ false, /* playable? */ false, /* humanoid? */ + false, /* casts race spells? */ RACE_ALIGN_GOOD, /* race alignment */ SIZE_GARGANTUAN, /* default size */ 0, /* default alignment */ @@ -868,6 +891,7 @@ RaceDef races[NUM_RACES] = { "Acid Dragon", /* name as displayed in medit vsearch stat and enlightenment */ false, /* playable? */ false, /* humanoid? */ + false, /* casts race spells? */ RACE_ALIGN_GOOD, /* race alignment */ SIZE_GARGANTUAN, /* default size */ 0, /* default alignment */ @@ -903,6 +927,7 @@ RaceDef races[NUM_RACES] = { "Lightning Dragon", /* name as displayed in medit vsearch stat and enlightenment */ false, /* playable? */ false, /* humanoid? */ + false, /* casts race spells? */ RACE_ALIGN_GOOD, /* race alignment */ SIZE_GARGANTUAN, /* default size */ 0, /* default alignment */ @@ -938,6 +963,7 @@ RaceDef races[NUM_RACES] = { "Gas Dragon", /* name as displayed in medit vsearch stat and enlightenment */ false, /* playable? */ false, /* humanoid? */ + false, /* casts race spells? */ RACE_ALIGN_GOOD, /* race alignment */ SIZE_GARGANTUAN, /* default size */ 0, /* default alignment */ @@ -973,6 +999,7 @@ RaceDef races[NUM_RACES] = { "Fire Dragonborn", /* name as displayed in medit vsearch stat and enlightenment */ true, /* playable? */ true, /* humanoid? */ + false, /* casts race spells? */ RACE_ALIGN_GOOD, /* race alignment */ SIZE_MEDIUM, /* default size */ 0, /* default alignment */ @@ -1008,6 +1035,7 @@ RaceDef races[NUM_RACES] = { "Frost Dragonborn", /* name as displayed in medit vsearch stat and enlightenment */ true, /* playable? */ true, /* humanoid? */ + false, /* casts race spells? */ RACE_ALIGN_GOOD, /* race alignment */ SIZE_MEDIUM, /* default size */ 0, /* default alignment */ @@ -1043,6 +1071,7 @@ RaceDef races[NUM_RACES] = { "Acid Dragonborn", /* name as displayed in medit vsearch stat and enlightenment */ true, /* playable? */ true, /* humanoid? */ + false, /* casts race spells? */ RACE_ALIGN_GOOD, /* race alignment */ SIZE_MEDIUM, /* default size */ 0, /* default alignment */ @@ -1078,6 +1107,7 @@ RaceDef races[NUM_RACES] = { "Lightning Dragonborn", /* name as displayed in medit vsearch stat and enlightenment */ true, /* playable? */ true, /* humanoid? */ + false, /* casts race spells? */ RACE_ALIGN_GOOD, /* race alignment */ SIZE_MEDIUM, /* default size */ 0, /* default alignment */ @@ -1113,6 +1143,7 @@ RaceDef races[NUM_RACES] = { "Gas Dragonborn", /* name as displayed in medit vsearch stat and enlightenment */ true, /* playable? */ true, /* humanoid? */ + false, /* casts race spells? */ RACE_ALIGN_GOOD, /* race alignment */ SIZE_MEDIUM, /* default size */ 0, /* default alignment */ @@ -1148,6 +1179,7 @@ RaceDef races[NUM_RACES] = { "Sverfneblin", /* name as displayed in medit vsearch stat and enlightenment */ true, /* playable? */ true, /* humanoid? */ + false, /* casts race spells? */ RACE_ALIGN_EVIL, /* race alignment */ SIZE_SMALL, /* default size */ -1000, /* default alignment */ @@ -1183,6 +1215,7 @@ RaceDef races[NUM_RACES] = { "Seelie Faerie", /* name as displayed in medit vsearch stat and enlightenment */ true, /* playable? */ true, /* humanoid? */ + false, /* casts race spells? */ RACE_ALIGN_GOOD, /* race alignment */ SIZE_TINY, /* default size */ 1000, /* default alignment */ @@ -1219,6 +1252,7 @@ RaceDef races[NUM_RACES] = { "Unseelie Faerie", /* name as displayed in medit vsearch stat and enlightenment */ true, /* playable? */ true, /* humanoid? */ + false, /* casts race spells? */ RACE_ALIGN_EVIL, /* race alignment */ SIZE_TINY, /* default size */ -1000, /* default alignment */ @@ -1254,6 +1288,7 @@ RaceDef races[NUM_RACES] = { "Nymph", /* name as displayed in medit vsearch stat and enlightenment */ true, /* playable? */ true, /* humanoid? */ + false, /* casts race spells? */ RACE_ALIGN_GOOD, /* race alignment */ SIZE_MEDIUM, /* default size */ 1000, /* default alignment */ @@ -1289,6 +1324,7 @@ RaceDef races[NUM_RACES] = { "Arborean", /* name as displayed in medit vsearch stat and enlightenment */ false, /* playable? */ true, /* humanoid? */ + false, /* casts race spells? */ RACE_ALIGN_GOOD, /* race alignment */ SIZE_LARGE, /* default size */ 0, /* default alignment */ @@ -1383,9 +1419,13 @@ void init_races(void) { PERM_EFF(RACE_TROLL, EFF_ULTRAVISION); /* - * Add race skills to the switch below. + * Add innate race skills to the switch below. + * Primarily used for NPCs. + * These skills are available at level 1 at the skill level declared. * If a constant value is declared, the skill will always reset back to that value. * Use 'proficiency' or 'ROLL_SKILL_PROF' instead. + * The only spells that should be assigned here are the breath weapon spells. + * Skills and spells intended to be unlocked beyond level 1 must be added in the assign_race_skills() function below. */ for (race = 0; race < NUM_RACES; ++race) { memset(races[race].skills, 0, sizeof(races[race].skills)); @@ -1395,9 +1435,6 @@ void init_races(void) { ADD_SKILL(SKILL_DOORBASH, 1000); ADD_SKILL(SKILL_BODYSLAM, 1000); break; - case RACE_ELF: - ADD_SKILL(SKILL_SLASHING, ROLL_SKILL_PROF); - break; case RACE_TROLL: ADD_SKILL(SKILL_DOORBASH, 1000); ADD_SKILL(SKILL_BODYSLAM, 1000); @@ -1425,7 +1462,7 @@ void init_races(void) { ADD_SKILL(SKILL_ROAR, ROLL_SKILL_PROF); break; case RACE_DRAGONBORN_FIRE: - ADD_SKILL(SKILL_BREATHE_FIRE, ROLL_SKILL_PROF); + ADD_SKILL(SPELL_FIRE_BREATH, 1000); break; case RACE_DRAGON_FROST: ADD_SKILL(SKILL_BREATHE_FROST, ROLL_SKILL_PROF); @@ -1433,7 +1470,7 @@ void init_races(void) { ADD_SKILL(SKILL_ROAR, ROLL_SKILL_PROF); break; case RACE_DRAGONBORN_FROST: - ADD_SKILL(SKILL_BREATHE_FROST, ROLL_SKILL_PROF); + ADD_SKILL(SPELL_FROST_BREATH, 1000); break; case RACE_DRAGON_ACID: ADD_SKILL(SKILL_BREATHE_ACID, ROLL_SKILL_PROF); @@ -1441,7 +1478,7 @@ void init_races(void) { ADD_SKILL(SKILL_ROAR, ROLL_SKILL_PROF); break; case RACE_DRAGONBORN_ACID: - ADD_SKILL(SKILL_BREATHE_ACID, ROLL_SKILL_PROF); + ADD_SKILL(SPELL_ACID_BREATH, 1000); break; case RACE_DRAGON_LIGHTNING: ADD_SKILL(SKILL_BREATHE_LIGHTNING, ROLL_SKILL_PROF); @@ -1449,7 +1486,7 @@ void init_races(void) { ADD_SKILL(SKILL_ROAR, ROLL_SKILL_PROF); break; case RACE_DRAGONBORN_LIGHTNING: - ADD_SKILL(SKILL_BREATHE_LIGHTNING, ROLL_SKILL_PROF); + ADD_SKILL(SPELL_LIGHTNING_BREATH, 1000); break; case RACE_DRAGON_GAS: ADD_SKILL(SKILL_BREATHE_GAS, ROLL_SKILL_PROF); @@ -1457,7 +1494,7 @@ void init_races(void) { ADD_SKILL(SKILL_ROAR, ROLL_SKILL_PROF); break; case RACE_DRAGONBORN_GAS: - ADD_SKILL(SKILL_BREATHE_GAS, ROLL_SKILL_PROF); + ADD_SKILL(SPELL_GAS_BREATH, 1000); break; case RACE_DEMON: ADD_SKILL(SKILL_BREATHE_FIRE, ROLL_SKILL_PROF); @@ -1484,6 +1521,102 @@ void init_races(void) { #undef PERM_EFF } +/* This is where to add race skills which unlock beyond level 1, or spells that should be castable through normal means. */ + +#define race_spell_assign(spellnum, race_num, level) race_skill_assign(spellnum, race_num, level) +#define race_chant_assign(chantnum, race_num, level) race_skill_assign(chantnum, race_num, level) +#define race_song_assign(songnum, race_num, level) race_skill_assign(songnum, race_num, level) + + void assign_race_skills(void) { + + /* From here on down, the races are listed in alphabetical order. + * Within each race, skills should be sorted by level and spells + * tend to be grouped by circle. */ + + /* ANIMAL */ + + /* ARBOREAN */ + + /* BARBARIAN */ + + /* BROWNIE */ + + /* DEMON */ + + /* DRAGON - ACID */ + + /* DRAGON - FIRE */ + + /* DRAGON - GAS */ + + /* DRAGON - GENERAL */ + + /* DRAGON - ICE */ + + /* DRAGON - LIGHTNING */ + + /* DRAGONBORN - ACID */ + race_skill_assign(SKILL_BREATHE_ACID, RACE_DRAGONBORN_ACID, 1); + + /* DRAGONBORN - FIRE */ + race_skill_assign(SKILL_BREATHE_FIRE, RACE_DRAGONBORN_FIRE, 1); + + /* DRAGONBORN - FROST */ + race_skill_assign(SKILL_BREATHE_FROST, RACE_DRAGONBORN_FROST, 1); + + /* DRAGONBORN - GAS */ + race_skill_assign(SKILL_BREATHE_GAS, RACE_DRAGONBORN_GAS, 1); + + /* DRAGONBORN - LIGHTNING */ + race_skill_assign(SKILL_BREATHE_LIGHTNING, RACE_DRAGONBORN_LIGHTNING, 1); + + /* DROW */ + race_skill_assign(SKILL_SLASHING, RACE_DROW, 1); + + /* DWARF */ + + /* DUERGAR */ + + /* ELF */ + race_skill_assign(SKILL_SLASHING, RACE_ELF, 1); + + race_spell_assign(SPELL_MAGIC_MISSILE, RACE_ELF, CIRCLE_1); + + /* GIANT */ + + /* GNOME */ + + /* GOBLIN */ + + /* HALF-ELF */ + + /* HALFLING */ + + /* HUMAN */ + + /* HUMANOID */ + + /* NYMPH */ + + /* OGRE */ + + /* ORC */ + + /* OTHER */ + + /* PLANT */ + + /* SEELIE FAERIE */ + + /* SVERFNEBLIN */ + + /* TROLL */ + + /* UNSEELIE FAERIE */ + + } + + /* parse_race * * Identifies a race from a string. Will do partial matches. diff --git a/src/rogue.cpp b/src/rogue.cpp index defb1aba..3f57192c 100644 --- a/src/rogue.cpp +++ b/src/rogue.cpp @@ -188,7 +188,7 @@ bool bard_ai_action(CharData *ch, CharData *victim) { switch (mob_bard_hindrances[i].spell) { case SPELL_INSANITY: case SPELL_SILENCE: - if (IS_SPELLCASTER(victim2) && FIGHTING(victim2) == ch && !has_effect(victim2, &mob_bard_hindrances[i]) && + if ((IS_SPELLCASTER(victim2)) && FIGHTING(victim2) == ch && !has_effect(victim2, &mob_bard_hindrances[i]) && GET_LEVEL(victim2) < (GET_LEVEL(ch) + 20)) { if (mob_cast(ch, victim2, nullptr, mob_bard_hindrances[i].spell)) return true; diff --git a/src/skills.cpp b/src/skills.cpp index fe0ffbd9..c1fb738e 100644 --- a/src/skills.cpp +++ b/src/skills.cpp @@ -248,9 +248,9 @@ void update_skills(CharData *ch) { * skill of SKILL_SPHERE_GENERIC. */ - if (skills[skill].min_level[(int)GET_CLASS(ch)] <= GET_LEVEL(ch) && + if ((skills[skill].min_level[(int)GET_CLASS(ch)] <= GET_LEVEL(ch) || skills[skill].min_race_level[(int)GET_RACE(ch)] <= GET_LEVEL(ch)) && !(skills[skill].humanoid && !(IS_HUMANOID(ch) || creature_allowed_skill(ch, skill)))) { - /* This skill is available because of your class, and you meet + /* This skill is available because of your class or your race beyond level 1, and you meet * the humanoid requirement, if any. */ /* This is a talent that you do have, or could have. */ @@ -284,25 +284,40 @@ void update_skills(CharData *ch) { } } - } else if ((prof = racial_skill_proficiency(skill, GET_RACE(ch), GET_LEVEL(ch)))) { + } else if (prof = racial_skill_proficiency(skill, GET_RACE(ch), GET_LEVEL(ch))) { /* This skill is available because of your race. */ - if (prof == ROLL_SKILL_PROF) { - /* This skill improves as you gain levels. So we only want to give - * you a "pre-improved" value if you're just now gaining it - - * such as a spawned mob, or a person being switched to the race. */ - if (GET_SKILL(ch, skill) == 0) - SET_SKILL(ch, skill, roll_mob_skill(GET_LEVEL(ch))); - } else { - /* This skill has been given a static amount. Probably 1000. */ - SET_SKILL(ch, skill, prof); - } + /* This is a talent that you do have, or could have. */ + if (skills[skill].quest == false) { + if (prof == ROLL_SKILL_PROF) { + /* This skill/spell you get because your level is high enough. + * So: ensure that you have it. */ + if (GET_SKILL(ch, skill) <= 0) { + /* You don't have it, so set the starting value. Individual + * spells and languages don't actually improve, so the value + * is 1000. */ + if (IS_SPELL(skill)) + SET_SKILL(ch, skill, 1000); + /* Barehand and safe fall don't improve either, though with + * some improvements to the mud, they could. */ + else if (skill == SKILL_BAREHAND || skill == SKILL_SAFEFALL) + SET_SKILL(ch, skill, 1000); + else + /* Skills, chants, and songs do improve. You get the low + * starting value. */ + SET_SKILL(ch, skill, !IS_NPC(ch) ? 50 : roll_mob_skill(GET_LEVEL(ch))); + } + } else { + /* This skill has been given a static amount. Probably 1000. */ + SET_SKILL(ch, skill, prof); + } - /* Again, remember the spell-sphere skills. */ - if (IS_SPELL(skill)) { - for (i = 0; i < NUM_SPHERE_SKILLS; i++) { - if (spherecheck[i] == skills[skill].sphere || !spherecheck[i]) { - spherecheck[i] = skills[skill].sphere; - break; + /* Again, remember the spell-sphere skills. */ + if (IS_SPELL(skill)) { + for (i = 0; i < NUM_SPHERE_SKILLS; i++) { + if (spherecheck[i] == skills[skill].sphere || !spherecheck[i]) { + spherecheck[i] = skills[skill].sphere; + break; + } } } } @@ -344,6 +359,8 @@ void dskill(int skill, char *name, int max_mana, int min_mana, int mana_change, for (i = 0; i < NUM_CLASSES; i++) skills[skill].min_level[i] = LVL_IMMORT; + for (i = 0; i < NUM_RACES; i++) + skills[skill].min_race_level[i] = LVL_IMMORT; skills[skill].lowest_level = LVL_IMMORT; skills[skill].mana_max = max_mana; @@ -372,6 +389,8 @@ void clear_skill(int skill) { for (i = 0; i < NUM_CLASSES; i++) skills[skill].min_level[i] = LVL_IMPL + 1; + for (i = 0; i < NUM_RACES; i++) + skills[skill].min_race_level[i] = LVL_IMPL + 1; skills[skill].lowest_level = LVL_IMPL + 1; skills[skill].mana_max = 0; @@ -1634,3 +1653,28 @@ bool get_spell_assignment_circle(CharData *ch, int spell, int *circle_assignment } return false; } + + +void race_skill_assign(int skillnum, int race_num, int level) { + int okay = true; + + if (skillnum < 0 || skillnum > TOP_SKILL_DEFINE) { + log("SYSERR: attempting assign to illegal talent num {:d}", skillnum); + return; + } + + if (race_num < 0 || race_num >= NUM_RACES) { + log("SYSERR: assigning '{}' to illegal race_num {:d}", skill_name(skillnum), race_num); + okay = false; + } + + if (level < 1 || level > LVL_IMPL) { + log("SYSERR: assigning '{}' to illegal level {:d}", skill_name(skillnum), level); + okay = false; + } + + if (okay) { + skills[skillnum].min_race_level[race_num] = level; + skills[skillnum].lowest_level = std::min(skills[skillnum].lowest_level, level); + } +} diff --git a/src/spell_mem.cpp b/src/spell_mem.cpp index 6d23cf1e..1fd14c0b 100644 --- a/src/spell_mem.cpp +++ b/src/spell_mem.cpp @@ -523,7 +523,7 @@ ACMD(do_study) { if (!ch) return; - if (MEM_MODE(ch) == MEM_NONE && GET_LEVEL(ch) < LVL_IMMORT) { + if (!IS_SPELLCASTER(ch) && GET_LEVEL(ch) < LVL_IMMORT) { char_printf(ch, "You don't know any spells.\n"); return; } diff --git a/src/spell_parser.cpp b/src/spell_parser.cpp index ab4790de..44f13546 100644 --- a/src/spell_parser.cpp +++ b/src/spell_parser.cpp @@ -1306,7 +1306,7 @@ ACMD(do_cast) { } /* Can the caster actually cast this spell? */ - if (GET_LEVEL(ch) < SINFO.min_level[(int)GET_CLASS(ch)] || !GET_SKILL(ch, spellnum)) { + if ((GET_LEVEL(ch) < SINFO.min_level[(int)GET_CLASS(ch)] && GET_LEVEL(ch) < SINFO.min_race_level[(int)GET_RACE(ch)]) || !GET_SKILL(ch, spellnum)) { if (subcmd == SCMD_CHANT) char_printf(ch, "You do not know that chant!\n"); else if (subcmd == SCMD_PERFORM) diff --git a/src/warrior.cpp b/src/warrior.cpp index 954653b7..59fdd562 100644 --- a/src/warrior.cpp +++ b/src/warrior.cpp @@ -130,7 +130,7 @@ bool warrior_ai_action(CharData *ch, CharData *victim) { * warrior opponent.) */ if (CAN_SEE(ch, victim) && - roll > (80 - GET_SKILL(ch, SKILL_BASH) / 2 - (classes[(int)GET_CLASS(victim)].magical ? 20 : 0)) && + roll > (80 - GET_SKILL(ch, SKILL_BASH) / 2 - (IS_SPELLCASTER_CLASS(victim) ? 20 : 0)) && GET_SKILL(ch, SKILL_BASH) && GET_EQ(ch, WEAR_SHIELD) && GET_POS(victim) >= POS_STANDING && i <= 2 && i > -1 && !MOB_FLAGGED(victim, MOB_NOBASH)) { do_bash(ch, GET_NAME(victim), 0, SCMD_BASH);