DRY spellcasting rules, be clear why can't cast

This is a big refactor of complicated code, and needs extensive
testing.
This commit is contained in:
2025-05-16 09:03:39 -05:00
parent 945b82e033
commit bf77407eec
5 changed files with 192 additions and 111 deletions

View File

@@ -39,7 +39,7 @@ poison a monster twice, it will do well over twice the damage the first spell wo
caused. If one fear spell doesn't make a monster flee, the next one will have a much
better chance of working. Casting two light spells makes the light last twice as long.</p>
<p>Mage spells require great delicacy of movement to cast. For this reason, they cannot be
cast when when armor with total encumbrance of more than 1 is being worn. High defense
cast during combat when when armor with total encumbrance of more than 1 is being worn. High defense
skill can sometimes offset this effect, but it never will when any single item has an
encumbrance value of more than 2.</p>
<p>Mage Spells and Encumbrance: If you are wearing armor with a total encumbrance of

View File

@@ -3445,15 +3445,18 @@ void increase_age(bool eating_trigger_autosave) {
}
else {
if(univ.party.age % 50 == 0) {
for(cPlayer& pc : univ.party)
for(cPlayer& pc : univ.party){
// Bonus HP wears off
if(pc.main_status == eMainStatus::ALIVE && pc.cur_health > pc.max_health)
pc.cur_health--; // Bonus HP wears off
pc.cur_health--;
}
univ.party.heal(1);
}
}
if(is_out()) {
if(univ.party.age % 80 == 0) {
univ.party.restore_sp(2);
// Enlightenment wears off
for(cPlayer& pc : univ.party)
if(pc.status[eStatus::DUMB] < 0)
pc.status[eStatus::DUMB]++;
@@ -3462,8 +3465,10 @@ void increase_age(bool eating_trigger_autosave) {
else {
if(univ.party.age % 40 == 0) {
for(cPlayer& pc : univ.party) {
// Bonus SP wears off
if(pc.main_status == eMainStatus::ALIVE && pc.cur_sp > pc.max_sp)
pc.cur_sp--; // Bonus SP wears off
pc.cur_sp--;
// Enlightenment wears off
if(pc.status[eStatus::DUMB] < 0)
pc.status[eStatus::DUMB]++;
}

View File

@@ -4486,35 +4486,24 @@ void end_combat() {
bool combat_cast_mage_spell() {
short store_sp;
eSpell spell_num;
cMonster get_monst;
if(univ.current_pc().traits[eTrait::ANAMA]) {
add_string_to_buf("Cast: You're an Anama!");
return false;
}
short store_sp = univ.current_pc().cur_sp;
if(univ.town.is_antimagic(univ.current_pc().combat_pos.x,univ.current_pc().combat_pos.y)) {
add_string_to_buf(" Not in antimagic field.");
return false;
}
store_sp = univ.current_pc().cur_sp;
if(univ.current_pc().cur_sp == 0)
add_string_to_buf("Cast: No spell points.");
else if(univ.current_pc().skill(eSkill::MAGE_SPELLS) == 0)
add_string_to_buf("Cast: No mage skill.");
else if(univ.current_pc().total_encumbrance(hit_chance) > 1) {
add_string_to_buf("Cast: Too encumbered.");
take_ap(6);
give_help(40,0);
return true;
}
else {
eCastStatus status = pc_can_cast_spell(univ.current_pc(), eSkill::MAGE_SPELLS);
if(status != CAST_OK){
print_cast_status(status, eSkill::MAGE_SPELLS);
if(status == NO_CAST_ENCUMBERED){
// Oops, trying to cast a mage spell while encumbered takes your AP!
take_ap(6);
give_help(40,0);
return true;
}
}else{
if(!spell_forced)
spell_num = pick_spell(univ.cur_pc,eSkill::MAGE_SPELLS);
spell_num = pick_spell(univ.cur_pc,eSkill::MAGE_SPELLS, true);
else {
if(!repeat_cast_ok(eSkill::MAGE_SPELLS))
return false;
@@ -4566,8 +4555,8 @@ bool combat_cast_mage_spell() {
draw_terrain(2);
combat_immed_mage_cast(univ.cur_pc,spell_num);
}
put_pc_screen();
}
put_pc_screen();
combat_posing_monster = current_working_monster = -1;
// Did anything actually get cast?
if(store_sp == univ.current_pc().cur_sp)
@@ -4725,60 +4714,51 @@ void combat_immed_mage_cast(short current_pc, eSpell spell_num, bool freebie) {
}
bool combat_cast_priest_spell() {
short store_sp;
eSpell spell_num;
if(univ.town.is_antimagic(univ.current_pc().combat_pos.x,univ.current_pc().combat_pos.y)) {
add_string_to_buf(" Not in antimagic field.");
return false;
}
if(!spell_forced)
spell_num = pick_spell(univ.cur_pc,eSkill::PRIEST_SPELLS);
else {
if(!repeat_cast_ok(eSkill::PRIEST_SPELLS))
return false;
spell_num = univ.current_pc().last_cast[eSkill::PRIEST_SPELLS];
}
store_sp = univ.current_pc().cur_sp;
if(univ.current_pc().cur_sp == 0) {
add_string_to_buf("Cast: No spell points.");
return false;
} else if(univ.current_pc().skill(eSkill::PRIEST_SPELLS) == 0) {
add_string_to_buf("Cast: No priest skill.");
return false;
}
if(univ.current_pc().traits[eTrait::PACIFIST] && spell_num != eSpell::NONE && !(*spell_num).peaceful) {
add_string_to_buf("Cast: You're a pacifist!");
return false;
}
if(spell_num == eSpell::NONE) return false;
combat_posing_monster = current_working_monster = univ.cur_pc;
if(spell_num != eSpell::NONE) {
print_spell_cast(spell_num,eSkill::PRIEST_SPELLS);
if((*spell_num).refer == REFER_YES) {
take_ap(5);
draw_terrain(2);
do_priest_spell(univ.cur_pc,spell_num);
}
else if((*spell_num).refer == REFER_TARGET) {
start_spell_targeting(spell_num);
}
else if((*spell_num).refer == REFER_FANCY) {
start_fancy_spell_targeting(spell_num);
}
short store_sp = univ.current_pc().cur_sp;
eCastStatus status = pc_can_cast_spell(univ.current_pc(), eSkill::PRIEST_SPELLS);
if(status != CAST_OK){
print_cast_status(status, eSkill::PRIEST_SPELLS);
}else{
if(!spell_forced)
spell_num = pick_spell(univ.cur_pc,eSkill::PRIEST_SPELLS, true);
else {
take_ap(5);
draw_terrain(2);
combat_immed_priest_cast(univ.cur_pc, spell_num);
if(!repeat_cast_ok(eSkill::MAGE_SPELLS))
return false;
spell_num = univ.current_pc().last_cast[eSkill::PRIEST_SPELLS];
}
if(univ.current_pc().traits[eTrait::PACIFIST] && spell_num != eSpell::NONE && !(*spell_num).peaceful) {
add_string_to_buf("Cast: You're a pacifist!");
return false;
}
if(spell_num == eSpell::NONE) return false;
combat_posing_monster = current_working_monster = univ.cur_pc;
if(spell_num != eSpell::NONE) {
print_spell_cast(spell_num,eSkill::PRIEST_SPELLS);
if((*spell_num).refer == REFER_YES) {
take_ap(5);
draw_terrain(2);
do_priest_spell(univ.cur_pc,spell_num);
}
else if((*spell_num).refer == REFER_TARGET) {
start_spell_targeting(spell_num);
}
else if((*spell_num).refer == REFER_FANCY) {
start_fancy_spell_targeting(spell_num);
}
else {
take_ap(5);
draw_terrain(2);
combat_immed_priest_cast(univ.cur_pc, spell_num);
}
}
put_pc_screen();
}
combat_posing_monster = current_working_monster = -1;
// Did anything actually get cast?
if(store_sp == univ.current_pc().cur_sp)

View File

@@ -471,7 +471,7 @@ void cast_spell(eSkill type) {
eSpell spell;
if((is_town()) && (univ.town.is_antimagic(univ.party.town_loc.x,univ.party.town_loc.y))) {
add_string_to_buf(" Not in antimagic field.");
add_string_to_buf("Cast: Not in antimagic field.");
return;
}
@@ -1570,26 +1570,58 @@ void dispel_fields(short i,short j,short mode) {
break_force_cage(loc(i,j));
}
bool pc_can_cast_spell(const cPlayer& pc,eSkill type) {
extern std::array<short, 51> hit_chance;
eCastStatus pc_can_cast_spell(const cPlayer& pc,const eSkill type) {
if(type == eSkill::MAGE_SPELLS && pc.traits[eTrait::ANAMA]) {
return NO_CAST_ANAMA;
}
if(pc.skill(type) == 0) {
return NO_CAST_SKILL;
}
if(pc.cur_sp == 0) {
return NO_CAST_SP;
}
if(is_combat() && univ.town.is_antimagic(pc.combat_pos.x,pc.combat_pos.y)) {
return NO_CAST_ANTIMAGIC;
}
if(is_combat() && type == eSkill::MAGE_SPELLS && pc.total_encumbrance(hit_chance) > 1) {
return NO_CAST_ENCUMBERED;
}
if(type == eSkill::MAGE_SPELLS && pc_can_cast_spell(pc, eSpell::LIGHT))
return true;
return CAST_OK;
if(type == eSkill::PRIEST_SPELLS && pc_can_cast_spell(pc, eSpell::HEAL_MINOR))
return true;
return CAST_OK;
// If they can't cast the most basic level 1 spell, let's just make sure they can't cast any spells.
// Find a spell they definitely know, and see if they can cast that.
if(type == eSkill::MAGE_SPELLS && pc.mage_spells.any()) {
for(int i = 0; i < 62; i++)
if(pc.mage_spells[i])
return pc_can_cast_spell(pc, eSpell(i));
if(type == eSkill::MAGE_SPELLS && pc.mage_spells.any()){
for(int i = 0; i < 62; i++){
if(pc.mage_spells[i]){
if(pc_can_cast_spell(pc, eSpell(i))) return CAST_OK;
break;
}
}
}
if(type == eSkill::PRIEST_SPELLS && pc.priest_spells.any()) {
for(int i = 0; i < 62; i++)
if(pc.priest_spells[i])
return pc_can_cast_spell(pc, eSpell(i + 100));
if(type == eSkill::PRIEST_SPELLS && pc.priest_spells.any()){
for(int i = 0; i < 62; i++){
if(pc.priest_spells[i]){
if(pc_can_cast_spell(pc, eSpell(i + 100))) return CAST_OK;
}
}
}
// If we get this far, either they don't know any spells (very unlikely) or they can't cast any of the spells they know.
return false;
if(pc.status[eStatus::DUMB] > 0){
return NO_CAST_DUMBFOUNDED;
}
if(pc.status[eStatus::PARALYZED] != 0){
return NO_CAST_PARALYZED;
}
if(pc.status[eStatus::ASLEEP] > 0){
return NO_CAST_ASLEEP;
}
return NO_CAST_UNKNOWN;
}
bool pc_can_cast_spell(const cPlayer& pc,eSpell spell_num) {
@@ -1665,7 +1697,7 @@ static void draw_caster_buttons(cDialog& me, const eSkill store_situation) {
}
}
else {
if(pc_can_cast_spell(univ.party[i],store_situation)) {
if(pc_can_cast_spell(univ.party[i],store_situation) == CAST_OK) {
me[id].show();
}
else {
@@ -2038,10 +2070,51 @@ static bool finish_pick_spell(cDialog& me, bool spell_toast, const short store_s
return true;
}
eCastStatus check_can_cast(const cPlayer& pc, eSkill type) {
return CAST_OK;
}
void print_cast_status(eCastStatus status, eSkill type, std::string pc_name) {
std::string prefix = "Cast";
// When multiple PCs are checked, explain why each one can't cast.
if(!pc_name.empty()) prefix += " (" + pc_name + ")";
prefix += ": ";
switch(status){
case NO_CAST_ANAMA:
add_string_to_buf(prefix + "You're an Anama!");
break;
case NO_CAST_SKILL:
if(type == eSkill::MAGE_SPELLS) add_string_to_buf(prefix + "No mage skill.");
else add_string_to_buf(prefix + "No priest skill.");
break;
case NO_CAST_ENCUMBERED:
add_string_to_buf(prefix + "Too encumbered.");
break;
case NO_CAST_SP:
add_string_to_buf(prefix + "No spell points.");
break;
case NO_CAST_ANTIMAGIC:
add_string_to_buf(prefix + "Not in antimagic field.");
break;
case NO_CAST_DUMBFOUNDED:
add_string_to_buf(prefix + "You're dumbfounded!");
break;
case NO_CAST_PARALYZED:
add_string_to_buf(prefix + "You're paralyzed!");
break;
case NO_CAST_ASLEEP:
add_string_to_buf(prefix + "You're asleep!");
break;
case NO_CAST_UNKNOWN:
add_string_to_buf(prefix + "You can't!");
break;
}
}
//short pc_num; // if 6, anyone
//short type; // 0 - mage 1 - priest
//short situation; // 0 - out 1 - town 2 - combat
eSpell pick_spell(short pc_num,eSkill type) { // 70 - no spell OW spell num
eSpell pick_spell(short pc_num,const eSkill type, bool check_done) { // 70 - no spell OW spell num
using namespace std::placeholders;
eSpell default_spell = type == eSkill::MAGE_SPELLS ? store_mage : store_priest;
short former_target = store_spell_target;
@@ -2053,11 +2126,21 @@ eSpell pick_spell(short pc_num,eSkill type) { // 70 - no spell OW spell num
if(pc_num == 6) { // See if can keep same caster
can_choose_caster = true;
if(!pc_can_cast_spell(univ.party[pc_casting],type)) {
using namespace std::placeholders;
eCastStatus same_caster_status = pc_can_cast_spell(univ.party[pc_casting],type);
// If the party is in an antimagic field, individual statuses won't matter
if(same_caster_status == NO_CAST_ANTIMAGIC){
print_cast_status(NO_CAST_ANTIMAGIC, type);
}
else if(same_caster_status != CAST_OK) {
auto iter = std::find_if(univ.party.begin(), univ.party.end(), [type](const cPlayer& who) {
return pc_can_cast_spell(who, type);
eCastStatus status = pc_can_cast_spell(who, type);
if(status == CAST_OK) return true;
if(status >= NO_CAST_SP && status <= NO_CAST_ASLEEP)
print_cast_status(status, type, who.name);
return false;
});
if(iter == univ.party.end()) {
add_string_to_buf("Cast: Nobody can.");
return eSpell::NONE;
@@ -2070,21 +2153,12 @@ eSpell pick_spell(short pc_num,eSkill type) { // 70 - no spell OW spell num
pc_casting = pc_num;
}
if(!can_choose_caster) {
if(type == eSkill::MAGE_SPELLS && univ.party[pc_casting].traits[eTrait::ANAMA]) {
add_string_to_buf("Cast: You're an Anama!");
if(!can_choose_caster && !check_done) {
eCastStatus status = check_can_cast(univ.party[pc_casting], type);
if(status != CAST_OK){
print_cast_status(status, type);
return eSpell::NONE;
}
if(univ.party[pc_num].skill(type) == 0) {
if(type == eSkill::MAGE_SPELLS) add_string_to_buf("Cast: No mage skill.");
else add_string_to_buf("Cast: No priest skill.");
return eSpell::NONE;
}
if(univ.party[pc_casting].cur_sp == 0) {
add_string_to_buf("Cast: No spell points.");
return eSpell::NONE;
}
}
// If in combat, make the spell being cast this PCs most recent spell

View File

@@ -25,14 +25,36 @@ bool cast_spell_on_space(location where, eSpell spell);
void crumble_wall(location where);
void do_mindduel(short pc_num,cCreature *monst);
void dispel_fields(short i,short j,short mode);
// Reasons the player can't cast any Mage or Priest spells
enum eCastStatus {
CAST_OK,
// Immutable reasons, shouldn't be printed when multiple PCs are checked (peace mode)
NO_CAST_SKILL,
NO_CAST_ANAMA,
// Should only be printed once when multiple PCs are checked (peace mode)
NO_CAST_ANTIMAGIC,
// Should be printed for each character when multiple PCs are checked (peace mode)
NO_CAST_SP,
NO_CAST_ENCUMBERED,
NO_CAST_DUMBFOUNDED,
NO_CAST_PARALYZED,
NO_CAST_ASLEEP,
// Fallthrough: If there are any ways this can be reached, they should be given their own enum values!
NO_CAST_UNKNOWN,
// Pacifism is intentionally not included here because it applies only to specific spells,
// and disables them in the spell picker or when you use the item.
};
eCastStatus pc_can_cast_spell(const cPlayer& pc,const eSkill type);
bool pc_can_cast_spell(const cPlayer& pc,eSpell spell_num);
bool pc_can_cast_spell(const cPlayer& pc,eSkill spell_num);
eSpell pick_spell(short pc_num,eSkill type);
void print_cast_status(eCastStatus status, eSkill type, std::string pc_name = "");
eSpell pick_spell(short pc_num, eSkill type, bool check_done = false);
void start_town_targeting(eSpell s_num,short who_c,bool freebie,eSpellPat pat = PAT_SINGLE);
void do_alchemy();
eAlchemy alch_choice(short pc_num);
bool pick_pc_graphic(short pc_num,short mode,cDialog* parent_num);
bool pick_pc_name(short pc_num,cDialog* parent) ;
bool pick_pc_name(short pc_num,cDialog* parent);
bool has_trapped_monst();
mon_num_t pick_trapped_monst();
bool flying() ;