Flesh out acid as a real damage type

This commit is contained in:
2025-05-08 18:17:18 -05:00
parent 01608064f2
commit 005d40806c
12 changed files with 54 additions and 28 deletions

View File

@@ -21,10 +21,9 @@ enum class eDamageType {
COLD = 5,
UNDEAD = 6,
DEMON = 7,
// Acid is treated as magic damage but needs a value to prevent it using a zap explosion
ACID = -1,
ACID = 8,
// Keep these two last
SPECIAL = 8, // Completely unblockable damage from assassination skill
SPECIAL = 9, // Completely unblockable damage from assassination skill
MARKED = 10,
};

View File

@@ -371,7 +371,7 @@ std::istream& operator >> (std::istream& in, eFieldType& e) {
// MARK: eDamageType
cEnumLookup dmg_names = {
"weap", "fire", "poison", "magic", "weird", "cold", "undead", "demon", "spec",
"weap", "fire", "poison", "magic", "weird", "cold", "undead", "demon", "acid", "spec",
};
std::ostream& operator << (std::ostream& out, eDamageType e) {

View File

@@ -118,7 +118,9 @@ std::map<std::string,std::vector<std::string>> feature_flags = {
{"talk-go-back", {"StackV1"}},
// Bugs required for several VoDT test replays to run faithfully
{"empty-wandering-monster-bug", {"fixed"}},
{"too-many-extra-wandering-monsters-bug", {"fixed"}}
{"too-many-extra-wandering-monsters-bug", {"fixed"}},
// Game balance
{"magic-resistance", {"fixed"}} // Resist Magic used to not help with magic damage!
};
struct cParseEntrance {

View File

@@ -2363,12 +2363,9 @@ short damage_pc(cPlayer& which_pc,short how_much,eDamageType damage_type,eRace t
int boom_type = boom_gr[damage_type];
// Acid doesn't actually have its own damage type in classic BoE
if(damage_type == eDamageType::ACID)
damage_type = eDamageType::MAGIC;
// armor
if(damage_type == eDamageType::WEAPON || damage_type == eDamageType::UNDEAD || damage_type == eDamageType::DEMON) {
static std::set<eDamageType> armor_resist_damage = { eDamageType::WEAPON, eDamageType::UNDEAD, eDamageType::DEMON };
if(armor_resist_damage.count(damage_type)) {
how_much -= minmax(-5,5,which_pc.status[eStatus::BLESS_CURSE]);
for(short i = 0; i < cPlayer::INVENTORY_SIZE; i++) {
const cItem& item = which_pc.items[i];
@@ -2416,6 +2413,10 @@ short damage_pc(cPlayer& which_pc,short how_much,eDamageType damage_type,eRace t
}
short prot_from_dmg = which_pc.get_prot_level(eItemAbil::DAMAGE_PROTECTION,int(damage_type));
// Acid damage used to be magic damage, so magic protection counts as acid protection:
if(damage_type == eDamageType::ACID){
prot_from_dmg += which_pc.get_prot_level(eItemAbil::DAMAGE_PROTECTION,int(eDamageType::MAGIC));
}
if(prot_from_dmg > 0) {
// TODO: Why does this not depend on the ability strength if it's not weapon damage?
if(damage_type == eDamageType::WEAPON) how_much -= prot_from_dmg;
@@ -2447,8 +2448,13 @@ short damage_pc(cPlayer& which_pc,short how_much,eDamageType damage_type,eRace t
how_much = 0;
// Mag. res helps w. fire and cold
// TODO: Why doesn't this help with magic damage!?
if(damage_type == eDamageType::FIRE || damage_type == eDamageType::COLD) {
static std::set<eDamageType> magic_resist_damage = { eDamageType::FIRE, eDamageType::COLD };
// Now it also helps with MAGIC:
if(has_feature_flag("magic-resistance", "fixed")){
magic_resist_damage.insert(eDamageType::MAGIC);
magic_resist_damage.insert(eDamageType::ACID);
}
if(magic_resist_damage.count(damage_type)) {
int magic_res = which_pc.status[eStatus::MAGIC_RESISTANCE];
if(magic_res > 0)
how_much /= 2;
@@ -2458,7 +2464,8 @@ short damage_pc(cPlayer& which_pc,short how_much,eDamageType damage_type,eRace t
// major resistance
short full_prot = which_pc.get_prot_level(eItemAbil::FULL_PROTECTION);
if((damage_type == eDamageType::FIRE || damage_type == eDamageType::POISON || damage_type == eDamageType::MAGIC || damage_type == eDamageType::COLD)
std::set<eDamageType> major_resist_damage = { eDamageType::FIRE, eDamageType::POISON, eDamageType::MAGIC, eDamageType::ACID, eDamageType::COLD};
if(major_resist_damage.count(damage_type)
&& (full_prot > 0))
how_much = how_much / ((full_prot >= 7) ? 4 : 2);

View File

@@ -338,7 +338,7 @@ bool check_special_terrain(location where_check,eSpecCtx mode,cPlayer& which_pc,
break;
if(mode == eSpecCtx::OUT_MOVE && out_boat_there(where_check))
break;
if(ter_flag3 > 0 && ter_flag3 < 8)
if(ter_flag3 > 0 && ter_flag3 < int(eDamageType::SPECIAL))
dam_type = (eDamageType) ter_flag3;
else dam_type = eDamageType::WEAPON;
r1 = get_ran(ter_flag2,1,ter_flag1);
@@ -356,6 +356,10 @@ bool check_special_terrain(location where_check,eSpecCtx mode,cPlayer& which_pc,
add_string_to_buf(" You feel cold!");
pic_type = 4;
break;
case eDamageType::ACID:
add_string_to_buf(" It burns!");
pic_type = 6;
break;
case eDamageType::SPECIAL:
dam_type = eDamageType::UNBLOCKABLE;
BOOST_FALLTHROUGH;
@@ -1472,16 +1476,13 @@ short damage_monst(cCreature& victim, short who_hit, short how_much, eDamageType
int boom_type = boom_gr[dam_type];
// Acid doesn't actually have its own damage type in classic BoE
if(dam_type == eDamageType::ACID)
dam_type = eDamageType::MAGIC;
if(dam_type < eDamageType::SPECIAL) {
how_much = percent(how_much, victim.resist[dam_type]);
}
// Absorb damage?
if((dam_type == eDamageType::FIRE || dam_type == eDamageType::MAGIC || dam_type == eDamageType::COLD)
std::set<eDamageType> absorbable_damage = { eDamageType::FIRE, eDamageType::MAGIC, eDamageType::COLD, eDamageType::ACID };
if(absorbable_damage.count(dam_type)
&& victim.abil[eMonstAbil::ABSORB_SPELLS].active && get_ran(1,1,1000) <= victim.abil[eMonstAbil::ABSORB_SPELLS].special.extra1) {
add_check_overflow(victim.health, how_much);
ASB(" Magic absorbed.");
@@ -1491,7 +1492,7 @@ short damage_monst(cCreature& victim, short who_hit, short how_much, eDamageType
// Saving throw
if((dam_type == eDamageType::FIRE || dam_type == eDamageType::COLD) && get_ran(1,0,20) <= victim.level)
how_much /= 2;
if(dam_type == eDamageType::MAGIC && (get_ran(1,0,24) <= victim.level))
if((dam_type == eDamageType::MAGIC || dam_type == eDamageType::ACID) && (get_ran(1,0,24) <= victim.level))
how_much /= 2;
// Invulnerable?
@@ -1501,8 +1502,13 @@ short damage_monst(cCreature& victim, short who_hit, short how_much, eDamageType
how_much /= 10;
// Mag. res helps w. fire and cold
// TODO: Why doesn't this help with magic damage!?
if(dam_type == eDamageType::FIRE || dam_type == eDamageType::COLD) {
static std::set<eDamageType> magic_resist_damage = { eDamageType::FIRE, eDamageType::COLD };
// Now it also helps with MAGIC:
if(has_feature_flag("magic-resistance", "fixed")){
magic_resist_damage.insert(eDamageType::MAGIC);
magic_resist_damage.insert(eDamageType::ACID);
}
if(magic_resist_damage.count(dam_type)) {
int magic_res = victim.status[eStatus::MAGIC_RESISTANCE];
if(magic_res > 0)
how_much /= 2;

View File

@@ -1037,6 +1037,7 @@ std::string cItem::getAbilName() const {
case eDamageType::WEAPON: sout << "Enhanced"; break;
case eDamageType::UNDEAD: sout << "Necrotic"; break;
case eDamageType::DEMON: sout << "Unholy"; break;
case eDamageType::ACID: sout << "Acid"; break;
case eDamageType::SPECIAL:
case eDamageType::UNBLOCKABLE: sout << "Dark"; break;
case eDamageType::MARKED: break; // Invalid
@@ -1079,6 +1080,7 @@ std::string cItem::getAbilName() const {
case eDamageType::MAGIC: sout << "in sparks"; break;
case eDamageType::POISON: sout << "into slime"; break;
case eDamageType::WEAPON: sout << "in shrapnel"; break;
case eDamageType::ACID: sout << "with acid"; break;
case eDamageType::SPECIAL:
case eDamageType::UNBLOCKABLE: sout << "in darkness"; break;
case eDamageType::UNDEAD: sout.str("Implodes"); break;
@@ -1117,6 +1119,7 @@ std::string cItem::getAbilName() const {
case eDamageType::DEMON: sout << "Demon"; break;
case eDamageType::UNDEAD: sout << "Undead"; break;
case eDamageType::POISON: sout << "Poison"; break;
case eDamageType::ACID: sout << "Acid"; break;
case eDamageType::SPECIAL:
case eDamageType::UNBLOCKABLE: sout << "Darkness"; break;
case eDamageType::MARKED: break; // Invalid

View File

@@ -395,7 +395,7 @@ std::map<eMonstAbil,uAbility>::iterator cMonster::addAbil(eMonstAbilTemplate wha
}
cMonster::cMonster(){
for(int i = 0; i <= 8; i++) {
for(int i = 0; i <= int(eDamageType::SPECIAL); i++) {
eDamageType dmg = eDamageType(i);
resist[dmg] = 100;
}
@@ -560,6 +560,7 @@ std::string uAbility::to_string(eMonstAbil key) const {
case eDamageType::FIRE: sout << "Fiery"; break;
case eDamageType::COLD: sout << "Icy"; break;
case eDamageType::MAGIC: sout << "Shock"; break;
case eDamageType::ACID: sout << "Acid"; break;
case eDamageType::SPECIAL:
case eDamageType::UNBLOCKABLE: sout << "Wounding"; break;
case eDamageType::POISON: sout << "Pain"; break;

View File

@@ -595,7 +595,7 @@ void writeMonstersToXml(ticpp::Printer&& data, cScenario& scenario) {
data.CloseElement("attacks");
data.OpenElement("immunity");
for(int i = 0; i < 8; i++) {
for(int i = 0; i < int(eDamageType::SPECIAL); i++) {
eDamageType dmg = eDamageType(i);
if(monst.resist[dmg] != 100)
data.PushElement(boost::lexical_cast<std::string>(dmg), monst.resist[dmg]);

View File

@@ -88,6 +88,7 @@ extern rectangle terrain_rects[256];
unsigned char small_what_drawn[64][64];
extern bool small_any_drawn;
// These are at the bottom of edbuttons.png:
static short get_small_icon(ter_num_t ter){
short icon = -1;
switch(scenario.ter_types[ter].special){
@@ -108,6 +109,9 @@ static short get_small_icon(ter_num_t ter){
case eDamageType::POISON:
icon = 17;
break;
case eDamageType::ACID:
icon = 24; // green with black spots, doesn't seem to be used elsewhere
break;
case eDamageType::MAGIC:
icon = 20;
break;

View File

@@ -221,6 +221,7 @@ static void set_pattern(cTilemap& map, const effect_pat_type& pat) {
case eDamageType::WEAPON: clr = Colours::MAROON; break;
case eDamageType::FIRE: clr = Colours::RED; break;
case eDamageType::POISON: clr = Colours::GREEN; break;
case eDamageType::ACID: clr = Colours::LIGHT_GREEN; break; // Distinct enough from green?
case eDamageType::MAGIC: clr = Colours::PURPLE; break;
case eDamageType::UNBLOCKABLE: clr = Colours::LIGHT_BLUE; break;
case eDamageType::COLD: clr = Colours::BLUE; break;
@@ -975,8 +976,8 @@ short choose_field_type(short cur, cDialog* parent, bool includeSpec) {
}
pic_num_t choose_damage_type(short cur, cDialog* parent, bool allow_spec) {
static const char*const damageNames[] = {"Weapon", "Fire", "Poison", "Magic", "Weird", "Cold", "Undead", "Demon", "Unblockable"};
static const std::vector<pic_num_t> pics = {3,0,2,1,5,4,3,3,1};
static const char*const damageNames[] = {"Weapon", "Fire", "Poison", "Magic", "Weird", "Cold", "Undead", "Demon", "Acid", "Unblockable"};
static const std::vector<pic_num_t> pics = {3,0,2,1,5,4,3,3,6,1};
short prev = cur;
if(cur < 0 || cur >= pics.size()) cur = 0;
cPictChoice pic_dlg(pics.begin(), pics.end() - !allow_spec, PIC_BOOM, parent);

View File

@@ -68,7 +68,7 @@ TEST_CASE("Initialization sanity test for monster") {
CHECK(monst.abil.empty());
CHECK(monst.corpse_item == 0);
CHECK(monst.corpse_item_chance == 0);
CHECK(monst.resist.size() == 10);
CHECK(monst.resist.size() == 11);
CHECK(monst.resist[eDamageType::WEAPON] == 100);
CHECK(monst.resist[eDamageType::FIRE] == 100);
CHECK(monst.resist[eDamageType::POISON] == 100);
@@ -77,6 +77,7 @@ TEST_CASE("Initialization sanity test for monster") {
CHECK(monst.resist[eDamageType::UNBLOCKABLE] == 100);
CHECK(monst.resist[eDamageType::UNDEAD] == 100);
CHECK(monst.resist[eDamageType::DEMON] == 100);
CHECK(monst.resist[eDamageType::ACID] == 100);
CHECK(monst.resist[eDamageType::MARKED] == 100);
CHECK_FALSE(monst.mindless);
CHECK_FALSE(monst.invuln);

View File

@@ -132,7 +132,8 @@ TEST_CASE("Saving monster types") {
scen.scen_monsters[1].resist[eDamageType::COLD] = 30;
scen.scen_monsters[1].resist[eDamageType::UNDEAD] = 35;
scen.scen_monsters[1].resist[eDamageType::DEMON] = 40;
scen.scen_monsters[1].resist[eDamageType::SPECIAL] = 45;
scen.scen_monsters[1].resist[eDamageType::ACID] = 45;
scen.scen_monsters[1].resist[eDamageType::SPECIAL] = 50;
in_and_out("resistance", scen);
CHECK(scen.scen_monsters[1].resist[eDamageType::WEAPON] == 5);
CHECK(scen.scen_monsters[1].resist[eDamageType::FIRE] == 10);
@@ -142,6 +143,7 @@ TEST_CASE("Saving monster types") {
CHECK(scen.scen_monsters[1].resist[eDamageType::COLD] == 30);
CHECK(scen.scen_monsters[1].resist[eDamageType::UNDEAD] == 35);
CHECK(scen.scen_monsters[1].resist[eDamageType::DEMON] == 40);
CHECK(scen.scen_monsters[1].resist[eDamageType::ACID] == 45);
// This one should not be saved, so we expect it to revert to default
CHECK(scen.scen_monsters[1].resist[eDamageType::SPECIAL] == 100);
}