diff --git a/src/boe.combat.cpp b/src/boe.combat.cpp index c784949b1..fe9746c87 100644 --- a/src/boe.combat.cpp +++ b/src/boe.combat.cpp @@ -592,7 +592,7 @@ void pc_attack(short who_att,short target) { move_to_zero(univ.party[who_att].status[eStatus::POISONED_WEAPON]); take_ap(4); - if((univ.town.monst[target].status[eStatus::MARTYRS_SHIELD] > 0 || univ.town.monst[target].spec_skill == MONSTER_PERMANENT_MARTYRS_SHIELD) + if((univ.town.monst[target].status[eStatus::MARTYRS_SHIELD] > 0 || univ.town.monst[target].abil[eMonstAbil::MARTYRS_SHIELD].active) && (store_hp - univ.town.monst[target].health > 0)) { add_string_to_buf(" Shares damage! "); damage_pc(who_att, store_hp - univ.town.monst[target].health, eDamageType::MAGIC,eRace::UNKNOWN,0); @@ -642,7 +642,7 @@ void pc_attack_weapon(short who_att,short target,short hit_adj,short dam_adj,cIt r1 = get_ran(1,1,100); if((univ.party[who_att].level >= which_m->level - 1) && univ.party[who_att].skills[eSkill::ASSASSINATION] >= which_m->level / 2 - && (which_m->spec_skill != MONSTER_SPLITS)) // Can't assassinate splitters + && (!which_m->abil[eMonstAbil::SPLITS].active)) // Can't assassinate splitters if(r1 < hit_chance[max(univ.party[who_att].skills[eSkill::ASSASSINATION] - which_m->level,0)]) { add_string_to_buf(" You assassinate."); spec_dam += r2; @@ -1962,8 +1962,7 @@ void do_monster_turn() { current_monst_tactic = 1; // this means flee - if( (((cur_monst->spec_skill > 0) && (cur_monst->spec_skill < 4)) - || (cur_monst->spec_skill == 20)) && // Archer? + if(cur_monst->abil[eMonstAbil::MISSILE].active && // Archer? (dist(cur_monst->cur_loc,targ_space) < 6) && !monst_adjacent(targ_space,i)) current_monst_tactic = 1; // this means flee @@ -1972,7 +1971,7 @@ void do_monster_turn() { // flee if((univ.town.monst[i].target != 6) && (((cur_monst->morale <= 0) - && cur_monst->spec_skill != MONSTER_MINDLESS && cur_monst->m_type != eRace::UNDEAD) + && !cur_monst->mindless && cur_monst->m_type != eRace::UNDEAD) || (current_monst_tactic == 1))) { if(cur_monst->morale < 0) cur_monst->morale++; @@ -1994,13 +1993,11 @@ void do_monster_turn() { // sprintf((char *)create_line,"%d: %d %d %d",i,cur_monst->breath,cur_monst->mu,cur_monst->cl); // add_string_to_buf((char *)create_line); - // Breathe (fire) - if( (cur_monst->breath > 0) - && (get_ran(1,1,8) < 4) && !acted_yet) { - //print_nums(cur_monst->breath,cur_monst->breath_type,dist(cur_monst->m_loc,targ_space) ); - if((target != 6) - && (dist(cur_monst->cur_loc,targ_space) <= 8)) { - acted_yet = monst_breathe(cur_monst,targ_space,cur_monst->breath_type); + // Basic breath weapons + if(cur_monst->abil[eMonstAbil::DAMAGE].active && cur_monst->abil[eMonstAbil::DAMAGE].gen.type == eMonstGen::BREATH + && !acted_yet && get_ran(1,1,1000) < cur_monst->abil[eMonstAbil::DAMAGE].gen.odds) { + if(target != 6 && dist(cur_monst->cur_loc,targ_space) <= cur_monst->abil[eMonstAbil::DAMAGE].gen.range) { + acted_yet = monst_breathe(cur_monst,targ_space,cur_monst->abil[eMonstAbil::DAMAGE]); had_monst = true; acted_yet = true; take_m_ap(4,cur_monst); @@ -2029,35 +2026,57 @@ void do_monster_turn() { } } - // Missile - if((abil_range[cur_monst->spec_skill] > 0) // breathing gas short range - && (get_ran(1,1,8) < abil_odds[cur_monst->spec_skill]) && !acted_yet) { - // Don't fire when adjacent, unless non-gaze magical attack - if((!monst_adjacent(targ_space,i) || - ((cur_monst->spec_skill > 7) && (cur_monst->spec_skill != MONSTER_GOOD_ARCHER) - && (cur_monst->spec_skill != MONSTER_ACID_SPIT))) - // TODO: ^ What about shooting spines? That's non-magical too, isn't it? - // TODO: ^ What about gaze attacks? Aren't they supposed to be excluded too? - // missile range - && (dist(cur_monst->cur_loc,targ_space) <= abil_range[cur_monst->spec_skill])) { - print_monst_name(cur_monst->number); - monst_fire_missile(i/*,cur_monst->skill*/,cur_monst->status[eStatus::BLESS_CURSE], - cur_monst->spec_skill,cur_monst->cur_loc,target); - - // Vapors don't count as action - // TODO: Except that this code makes them consume 3 ap. - // TODO: This is an odd choice of skills to consume 2 ap. - // Shouldn't it be something more like "all archery skills" or "all thrown missile skills"? - // (Cross-reference with PC action point consumption in similar situations.) - if(cur_monst->spec_skill == MONSTER_THROWS_RAZORDISKS || cur_monst->spec_skill == MONSTER_GOOD_ARCHER) - take_m_ap(2,cur_monst); - else if(cur_monst->spec_skill == MONSTER_HEAT_RAY) - take_m_ap(1,cur_monst); - else take_m_ap(3,cur_monst); - had_monst = true; - acted_yet = true; + // Missile (except basic breath weapons) + std::pair pick_abil; + if(!acted_yet) { + // Go through all the monster's abilities and try to select one that's useable at this time + for(auto& abil : cur_monst->abil) { + switch(abil.first) { + case eMonstAbil::MISSILE: + if(dist(cur_monst->cur_loc, targ_space) > abil.second.missile.range) + break; // Target not in range + if(abil.second.missile.type != eMonstMissile::SPINE && monst_adjacent(targ_space, i)) + break; // Target adjacent; prefer melee attacks + if(get_ran(1,1,1000) >= abil.second.missile.odds) + break; + pick_abil = abil; + break; + case eMonstAbil::DAMAGE: case eMonstAbil::STATUS: case eMonstAbil::STATUS2: case eMonstAbil::FIELD: + case eMonstAbil::PETRIFY: case eMonstAbil::DRAIN_SP: case eMonstAbil::DRAIN_XP: case eMonstAbil::KILL: + case eMonstAbil::STEAL_FOOD: case eMonstAbil::STEAL_GOLD: case eMonstAbil::STUN: + if(abil.second.gen.type == eMonstGen::TOUCH) + break; // We're looking for ranged attacks + if(dist(cur_monst->cur_loc, targ_space) > abil.second.gen.range) + break; // Target not in range + if(abil.second.gen.type != eMonstGen::RAY && abil.second.gen.type != eMonstGen::BREATH + && monst_adjacent(targ_space, i)) + break; // Target adjacent; prefer melee attacks + if(get_ran(1,1,1000) >= abil.second.gen.odds) + break; + pick_abil = abil; + break; + } + if(pick_abil.second.active) break; } } + if(pick_abil.second.active) { + print_monst_name(cur_monst->number); + monst_fire_missile(i/*,cur_monst->skill*/,cur_monst->status[eStatus::BLESS_CURSE], + pick_abil,cur_monst->cur_loc,target); + if(pick_abil.first == eMonstAbil::MISSILE) { + if(pick_abil.second.missile.type == eMonstMissile::ARROW || pick_abil.second.missile.type == eMonstMissile::BOLT) + take_m_ap(3,cur_monst); + else take_m_ap(2,cur_monst); + } else { + if(pick_abil.second.gen.type == eMonstGen::BREATH) + take_m_ap(4,cur_monst); + else if(pick_abil.second.gen.type == eMonstGen::SPIT) + take_m_ap(3,cur_monst); + else take_m_ap(2,cur_monst); + } + had_monst = true; + acted_yet = true; + } } // Special attacks // Attack pc @@ -2144,47 +2163,28 @@ void do_monster_turn() { } // Place fields for monsters that create them. Only done when monst sees foe - if((target != 6) && (can_see_light(cur_monst->cur_loc,targ_space,sight_obscurity) < 5)) { //// - if((cur_monst->radiate_1 == MONSTER_RADIATE_FIRE_FIELDS) && (get_ran(1,1,100) < cur_monst->radiate_2)) - place_spell_pattern(square,cur_monst->cur_loc,WALL_FIRE,7); - if((cur_monst->radiate_1 == MONSTER_RADIATE_ICE_FIELDS) && (get_ran(1,1,100) < cur_monst->radiate_2)) - place_spell_pattern(square,cur_monst->cur_loc,WALL_ICE,7); - if((cur_monst->radiate_1 == MONSTER_RADIATE_SHOCK_FIELDS) && (get_ran(1,1,100) < cur_monst->radiate_2)) - place_spell_pattern(square,cur_monst->cur_loc,WALL_FORCE,7); - if((cur_monst->radiate_1 == MONSTER_RADIATE_ANTIMAGIC_FIELDS) && (get_ran(1,1,100) < cur_monst->radiate_2)) - place_spell_pattern(square,cur_monst->cur_loc,FIELD_ANTIMAGIC,7); - if((cur_monst->radiate_1 == MONSTER_RADIATE_SLEEP_FIELDS) && (get_ran(1,1,100) < cur_monst->radiate_2)) - place_spell_pattern(square,cur_monst->cur_loc,CLOUD_SLEEP,7); - if((cur_monst->radiate_1 == MONSTER_RADIATE_STINKING_CLOUDS) && (get_ran(1,1,100) < cur_monst->radiate_2)) - place_spell_pattern(square,cur_monst->cur_loc,CLOUD_STINK,7); - if((cur_monst->radiate_1 == MONSTER_RADIATE_BLADE_FIELDS) && (get_ran(1,1,100) < cur_monst->radiate_2)) - place_spell_pattern(square,cur_monst->cur_loc,WALL_BLADES,7); - if((cur_monst->radiate_1 == MONSTER_SUMMON_5_PERCENT) && (get_ran(1,1,100) < 5)){ - if(summon_monster(cur_monst->radiate_2, - cur_monst->cur_loc,130,cur_monst->attitude)) { + if(target != 6 && can_see_light(cur_monst->cur_loc,targ_space,sight_obscurity) < 5) { + if(cur_monst->abil[eMonstAbil::RADIATE].active && get_ran(1,1,100) < cur_monst->abil[eMonstAbil::RADIATE].radiate.chance) + place_spell_pattern(square, cur_monst->cur_loc, cur_monst->abil[eMonstAbil::RADIATE].radiate.type, 7); + if(cur_monst->abil[eMonstAbil::SUMMON].active && get_ran(1,1,100) < cur_monst->abil[eMonstAbil::SUMMON].summon.chance) { + uAbility abil = cur_monst->abil[eMonstAbil::SUMMON]; + r1 = get_ran(1, abil.summon.min, abil.summon.max); + if(r1 && summon_monster(abil.summon.type, cur_monst->cur_loc,abil.summon.len,cur_monst->attitude)) { monst_spell_note(cur_monst->number,33); play_sound(61); + bool failed = false; + while(--r1 && !failed) { + failed = summon_monster(abil.summon.type, cur_monst->cur_loc,abil.summon.len,cur_monst->attitude); + } } } - if((cur_monst->radiate_1 == MONSTER_SUMMON_20_PERCENT) && (get_ran(1,1,100) < 20)){ - if(summon_monster(cur_monst->radiate_2, - cur_monst->cur_loc,130,cur_monst->attitude)) { - monst_spell_note(cur_monst->number,33); - play_sound(61); - } - } - if((cur_monst->radiate_1 == MONSTER_SUMMON_50_PERCENT) && (get_ran(1,1,100) < 50)){ - if(summon_monster(cur_monst->radiate_2, - cur_monst->cur_loc,130,cur_monst->attitude)) { - monst_spell_note(cur_monst->number,33); - play_sound(61); - } - } - if((cur_monst->radiate_1 == MONSTER_SPECIAL_ACTION) && !special_called && party_can_see_monst(i)) { + if(cur_monst->abil[eMonstAbil::SPECIAL].active && !special_called && party_can_see_monst(i)) { + uAbility abil = cur_monst->abil[eMonstAbil::SPECIAL]; short s1, s2, s3; special_called = true; - take_m_ap(1,cur_monst); - run_special(eSpecCtx::MONST_SPEC_ABIL,0,cur_monst->radiate_2,cur_monst->cur_loc,&s1,&s2,&s3); + // TODO: Is it good to allow only one monster to use a special ability per combat round? + run_special(eSpecCtx::MONST_SPEC_ABIL,0,abil.special.extra1,cur_monst->cur_loc,&s1,&s2,&s3); + take_m_ap(abil.special.extra2,cur_monst); } } @@ -2299,7 +2299,7 @@ void monster_attack_pc(short who_att,short target) { // Draw attacker frames if((is_combat()) && ((center_on_monst) || !monsters_going)) { - if(attacker->spec_skill != MONSTER_INVISIBLE) + if(!attacker->invisible) frame_space(attacker->cur_loc,0,attacker->x_width,attacker->y_width); frame_space(univ.party[target].combat_pos,1,1,1); } @@ -2364,94 +2364,56 @@ void monster_attack_pc(short who_att,short target) { damage_monst(who_att, 6, store_hp - univ.party[target].cur_health, 0, eDamageType::MAGIC,0); } - if((attacker->poison > 0) && (i == 0)) { - poison_pc(target,attacker->poison); - } - - // Gremlin - if((attacker->spec_skill == MONSTER_STEALS_FOOD) && (get_ran(1,0,2) < 2)) { - add_string_to_buf(" Steals food! "); + for(auto& abil : attacker->abil) { + if(!abil.second.active) continue; + if(getMonstAbilCategory(abil.first) != eMonstAbilCat::GENERAL) + continue; + if(abil.second.gen.type != eMonstGen::TOUCH) + continue; + if(abil.second.gen.range > 0 && get_ran(1,1,1000) <= abil.second.gen.range) + continue; + // Print message and possibly choose sound + snd_num_t snd = 0; + switch(abil.first) { + case eMonstAbil::STUN: add_string_to_buf(" Stuns!"); break; + case eMonstAbil::PETRIFY: add_string_to_buf(" Petrifying touch!"); break; + case eMonstAbil::DRAIN_SP: add_string_to_buf(" Drains magic!"); break; + case eMonstAbil::DRAIN_XP: add_string_to_buf(" Drains life!"); break; + case eMonstAbil::KILL: add_string_to_buf(" Killing touch!"); break; + case eMonstAbil::STEAL_FOOD: add_string_to_buf(" Steals food!"); snd = 26; break; + case eMonstAbil::STEAL_GOLD: add_string_to_buf(" Steals gold!"); break; // TODO: Pick a sound + case eMonstAbil::FIELD: break; // TODO: Invent messages? + case eMonstAbil::DAMAGE: + switch(abil.second.gen.dmg) { + case eDamageType::FIRE: add_string_to_buf(" Burning touch!"); break; + case eDamageType::COLD: add_string_to_buf(" Freezing touch!"); break; + case eDamageType::MAGIC: add_string_to_buf(" Shocking touch!"); break; + case eDamageType::UNBLOCKABLE: add_string_to_buf(" Eerie touch!"); break; + case eDamageType::POISON: add_string_to_buf(" Slimy touch!"); break; + case eDamageType::WEAPON: add_string_to_buf(" Drains stamina!"); break; + case eDamageType::UNDEAD: add_string_to_buf(" Chilling touch!"); break; + case eDamageType::DEMON: add_string_to_buf(" Unholy touch!"); break; + } + break; + case eMonstAbil::STATUS: case eMonstAbil::STATUS2: + switch(abil.second.gen.stat) { + case eStatus::POISON: add_string_to_buf(" Poisonous!"); break; + case eStatus::DISEASE: add_string_to_buf(" Causes disease!"); break; + case eStatus::DUMB: add_string_to_buf(" Dumbfounds!"); break; + case eStatus::WEBS: add_string_to_buf(" Webs!"); break; + case eStatus::ASLEEP: add_string_to_buf(" Sleeps!"); break; + case eStatus::PARALYZED: add_string_to_buf(" Paralysis touch!"); break; + case eStatus::ACID: add_string_to_buf(" Acid touch!"); break; + case eStatus::HASTE_SLOW: add_string_to_buf(" Slowing touch!"); break; + case eStatus::BLESS_CURSE: add_string_to_buf(" Cursing touch!"); break; + } + break; + } + if(snd > 0) play_sound(snd); print_buf(); - play_sound(26); - univ.party.food = (long) max(0, (short) (univ.party.food) - get_ran(1,0,10) - 10); + monst_basic_abil(who_att, abil, target); put_pc_screen(); } - - // Disease - if(((attacker->spec_skill == MONSTER_DISEASE_TOUCH)) - && (get_ran(1,0,2) < 2)) { - add_string_to_buf(" Causes disease! "); - print_buf(); - disease_pc(target,6); - } - - // Petrification touch - if(attacker->spec_skill == MONSTER_PETRIFICATION_TOUCH) { - add_string_to_buf(" Petrifying touch!"); - print_buf(); - petrify_pc(target,attacker->level / 4); - } - - // Undead xp drain - if(((attacker->spec_skill == MONSTER_XP_DRAINING_TOUCH) || (attacker->spec_skill == MONSTER_ICY_AND_DRAINING_TOUCH)) - && (pc_has_abil_equip(target,eItemAbil::LIFE_SAVING) == 24)) { - // Note: Icy+Draining Touch also enters another if block further down. - add_string_to_buf(" Drains life! "); - drain_pc(target,(attacker->level * 3) / 2); - put_pc_screen(); - } - - // Undead slow - // TODO: Why does life-saving protect from this? - if((attacker->spec_skill == MONSTER_SLOWING_TOUCH) && (get_ran(1,0,8) < 6) && (pc_has_abil_equip(target,eItemAbil::LIFE_SAVING) == 24)) { - add_string_to_buf(" Stuns! "); - slow_pc(target,2); - put_pc_screen(); - } - // Dumbfound target - if(attacker->spec_skill == MONSTER_DUMBFOUNDING_TOUCH) { - add_string_to_buf(" Dumbfounds! "); - dumbfound_pc(target,2); - put_pc_screen(); - } - - // Web target - if(attacker->spec_skill == MONSTER_WEB_TOUCH) { - add_string_to_buf(" Webs! "); - web_pc(target,5); - put_pc_screen(); - } - // Sleep target - if(attacker->spec_skill == MONSTER_SLEEP_TOUCH) { - add_string_to_buf(" Sleeps! "); - sleep_pc(target,6,eStatus::ASLEEP,-15); - put_pc_screen(); - } - // Paralyze target - if(attacker->spec_skill == MONSTER_PARALYSIS_TOUCH) { - add_string_to_buf(" Paralysis touch! "); - sleep_pc(target,500,eStatus::PARALYZED,-5); - put_pc_screen(); - } - // Acid touch - if(attacker->spec_skill == MONSTER_ACID_TOUCH) { - add_string_to_buf(" Acid touch! "); - acid_pc(target,(attacker->level > 20) ? 4 : 2); - } - - // Freezing touch - if(((attacker->spec_skill == MONSTER_ICY_TOUCH) || (attacker->spec_skill == MONSTER_ICY_AND_DRAINING_TOUCH)) - && (get_ran(1,0,8) < 6) && (pc_has_abil_equip(target,eItemAbil::LIFE_SAVING) == 24)) { - add_string_to_buf(" Freezing touch!"); - r1 = get_ran(3,1,10); - damage_pc(target,r1,eDamageType::COLD,eRace::UNKNOWN,0); - } - // Killing touch - if(attacker->spec_skill == MONSTER_DEATH_TOUCH) { - add_string_to_buf(" Killing touch!"); - r1 = get_ran(20,1,10); - damage_pc(target,r1,eDamageType::UNBLOCKABLE,eRace::UNKNOWN,0); - } } } else { @@ -2483,7 +2445,7 @@ void monster_attack_monster(short who_att,short attackee) { // Draw attacker frames if((is_combat()) && (center_on_monst || !monsters_going)) { - if(attacker->spec_skill != MONSTER_INVISIBLE) + if(!attacker->invisible) frame_space(attacker->cur_loc,0,attacker->x_width,attacker->y_width); frame_space(target->cur_loc,1,1,1); } @@ -2528,70 +2490,52 @@ void monster_attack_monster(short who_att,short attackee) { damaged_message(store_hp - target->health, attacker->a[i].type); - if((attacker->poison > 0) && (i == 0)) { - poison_monst(target,attacker->poison); - } - - // Undead slow - // TODO: This should not be associated with undead, should it? - if((attacker->spec_skill == MONSTER_SLOWING_TOUCH) && (get_ran(1,0,8) < 6)) { - add_string_to_buf(" Stuns! "); - slow_monst(target,2); - } - - // Web target - if(attacker->spec_skill == MONSTER_WEB_TOUCH) { - add_string_to_buf(" Webs! "); - web_monst(target,4); - } - // Sleep target - if(attacker->spec_skill == MONSTER_SLEEP_TOUCH) { - add_string_to_buf(" Sleeps! "); - charm_monst(target,-15,eStatus::ASLEEP,6); - } - // Dumbfound target - if(attacker->spec_skill == MONSTER_DUMBFOUNDING_TOUCH) { - add_string_to_buf(" Dumbfounds! "); - dumbfound_monst(target,2); - } - // Disease target - if(((attacker->spec_skill == MONSTER_DISEASE_TOUCH)) - && (get_ran(1,0,2) < 2)) { - add_string_to_buf(" Causes disease! "); + for(auto& abil : attacker->abil) { + if(!abil.second.active) continue; + if(getMonstAbilCategory(abil.first) != eMonstAbilCat::GENERAL) + continue; + if(abil.second.gen.type != eMonstGen::TOUCH) + continue; + if(abil.second.gen.range > 0 && get_ran(1,1,1000) <= abil.second.gen.range) + continue; + // Print message and possibly choose sound + snd_num_t snd = 0; + switch(abil.first) { + case eMonstAbil::STUN: add_string_to_buf(" Stuns!"); break; + case eMonstAbil::PETRIFY: add_string_to_buf(" Petrifying touch!"); break; + case eMonstAbil::DRAIN_SP: add_string_to_buf(" Drains magic!"); break; + case eMonstAbil::KILL: add_string_to_buf(" Killing touch!"); break; + case eMonstAbil::FIELD: break; // TODO: Invent messages? + case eMonstAbil::DAMAGE: + switch(abil.second.gen.dmg) { + case eDamageType::FIRE: add_string_to_buf(" Burning touch!"); break; + case eDamageType::COLD: add_string_to_buf(" Freezing touch!"); break; + case eDamageType::MAGIC: add_string_to_buf(" Shocking touch!"); break; + case eDamageType::UNBLOCKABLE: add_string_to_buf(" Eerie touch!"); break; + case eDamageType::POISON: add_string_to_buf(" Slimy touch!"); break; + case eDamageType::WEAPON: add_string_to_buf(" Drains stamina!"); break; + case eDamageType::UNDEAD: add_string_to_buf(" Chilling touch!"); break; + case eDamageType::DEMON: add_string_to_buf(" Unholy touch!"); break; + } + break; + case eMonstAbil::STATUS: case eMonstAbil::STATUS2: + switch(abil.second.gen.stat) { + case eStatus::POISON: add_string_to_buf(" Poisonous!"); break; + case eStatus::DISEASE: add_string_to_buf(" Causes disease!"); break; + case eStatus::DUMB: add_string_to_buf(" Dumbfounds!"); break; + case eStatus::WEBS: add_string_to_buf(" Webs!"); break; + case eStatus::ASLEEP: add_string_to_buf(" Sleeps!"); break; + case eStatus::PARALYZED: add_string_to_buf(" Paralysis touch!"); break; + case eStatus::ACID: add_string_to_buf(" Acid touch!"); break; + case eStatus::HASTE_SLOW: add_string_to_buf(" Slowing touch!"); break; + case eStatus::BLESS_CURSE: add_string_to_buf(" Cursing touch!"); break; + } + break; + } + if(snd > 0) play_sound(snd); print_buf(); - disease_monst(target,6); - } - // Paralyze target - if(attacker->spec_skill == MONSTER_PARALYSIS_TOUCH) { - add_string_to_buf(" Paralysis touch! "); - charm_monst(target,-5,eStatus::PARALYZED,500); - } - // Petrify target - if(attacker->spec_skill == MONSTER_PETRIFICATION_TOUCH) { - add_string_to_buf(" Petrification touch! "); - petrify_monst(target, attacker->level / 4); - } - - // Acid touch - if(attacker->spec_skill == MONSTER_ACID_TOUCH) { - add_string_to_buf(" Acid touch! "); - acid_monst(target,3); - } - - // Freezing touch - if(((attacker->spec_skill == MONSTER_ICY_TOUCH) || (attacker->spec_skill == MONSTER_ICY_AND_DRAINING_TOUCH)) - && (get_ran(1,0,8) < 6)) { - add_string_to_buf(" Freezing touch!"); - r1 = get_ran(3,1,10); - damage_monst(attackee,7,r1,0,eDamageType::COLD,0); - } - - // Death touch - if((attacker->spec_skill == MONSTER_DEATH_TOUCH) - && (get_ran(1,0,8) < 6)) { - add_string_to_buf(" Killing touch!"); - r1 = get_ran(20,1,10); - damage_monst(attackee,7,r1,0,eDamageType::UNBLOCKABLE,0); + monst_basic_abil(who_att, abil, attackee + 100); + put_pc_screen(); } } } @@ -2611,7 +2555,7 @@ void monster_attack_monster(short who_att,short attackee) { //short target; // 100 + - monster is target -void monst_fire_missile(short m_num,short bless,short level,location source,short target) { +void monst_fire_missile(short m_num,short bless,std::pair abil,location source,short target) { cCreature *m_target; short r1,r2,dam[40] = { 0,1,2,3,4, 6,8,7,0,0, 0,0,0,0,0, 0,0,0,0,0, @@ -2641,254 +2585,288 @@ void monst_fire_missile(short m_num,short bless,short level,location source,shor } draw_terrain(2); - if(level == MONSTER_BREATHES_SLEEP_CLOUDS) { - ASB("Creature breathes."); - run_a_missile(source,targ_space,0,0,44, - 0,0,100); - place_spell_pattern(radius2,targ_space,CLOUD_SLEEP,7); - } - else if(level == MONSTER_BREATHES_STINKING_CLOUDS) { - //play_sound(44); - if(target < 100) { // on PC - std::string create_line = " Breathes on " + univ.party[target].name + '.'; - add_string_to_buf(create_line); + if(abil.first == eMonstAbil::MISSILE) { + snd_num_t snd = 14; + switch(abil.second.missile.type) { + case eMonstMissile::ARROW: + case eMonstMissile::BOLT: + snd = 12; + if(target < 100) add_string_to_buf(" Shoots at " + univ.party[target].name + '.'); + else monst_spell_note(m_target->number, 12); + break; + case eMonstMissile::SPEAR: + if(target < 100) add_string_to_buf(" Throws spear at " + univ.party[target].name + '.'); + else monst_spell_note(m_target->number, 13); + break; + case eMonstMissile::RAZORDISK: + if(target < 100) add_string_to_buf(" Throws razordisk at " + univ.party[target].name + '.'); + else monst_spell_note(m_target->number, 15); + break; + case eMonstMissile::SPINE: + if(target < 100) add_string_to_buf(" Fires spines at " + univ.party[target].name + '.'); + else monst_spell_note(m_target->number, 32); + break; + case eMonstMissile::DART: + if(target < 100) add_string_to_buf(" Throws dart at " + univ.party[target].name + '.'); + else monst_spell_note(m_target->number, 53); + break; + case eMonstMissile::ROCK: + if(target < 100) add_string_to_buf(" Throws rock at " + univ.party[target].name + '.'); + else monst_spell_note(m_target->number, 14); + break; + case eMonstMissile::KNIFE: + if(target < 100) add_string_to_buf(" Throws knife at " + univ.party[target].name + '.'); + else monst_spell_note(m_target->number, 54); + break; } - else { // on monst - add_string_to_buf(" Breathes vapors."); - } - run_a_missile(source,targ_space,12,0,44, - 0,0,100); - scloud_space(targ_space.x,targ_space.y); - } - else if(level == MONSTER_SHOOTS_WEB) { - //play_sound(14); - if(target < 100) { // on PC - std::string create_line = " Throws web at " + univ.party[target].name + '.'; - add_string_to_buf(create_line); - } - else { // on monst - add_string_to_buf(" Throws web."); - } - run_a_missile(source,targ_space,8,0,14, - 0,0,100); - web_space(targ_space.x,targ_space.y); - } - else if(level == MONSTER_PARALYSIS_RAY) { - // TODO: This sound doesn't seem right? - play_sound(51); - if(target < 100) { // on PC - std::string create_line = " Fires ray at " + univ.party[target].name + '.'; - add_string_to_buf(create_line); - sleep_pc(target,100,eStatus::PARALYZED,0); - } - else { // on monst - add_string_to_buf(" Shoots a ray."); - charm_monst(m_target,0,eStatus::PARALYZED,100); - } - //run_a_missile(source,targ_space,8,0,14, - // 0,0,100); - //web_space(targ_space.x,targ_space.y); - } - else if(level == MONSTER_PETRIFICATION_RAY) { - //play_sound(43); - run_a_missile(source,targ_space,14,0,43,0,0,100); - if(target < 100) { // on PC - std::string create_line = " Gazes at " + univ.party[target].name + '.'; - add_string_to_buf(create_line); - petrify_pc(target, m_target->level / 4); - } - else { - // TODO: This might be relevant to the AFFECT_DEADNESS special when used on monsters - monst_spell_note(m_target->number,9); - petrify_monst(m_target, m_target->level / 4); - } - } - else if(level == MONSTER_SP_DRAIN_RAY) { - if(target < 100) { // pc - // modify target is target has no sp - if(univ.party[target].cur_sp < 4) { - for(i = 0; i < 8; i++) { - j = get_ran(1,0,5); - if(univ.party[j].main_status == eMainStatus::ALIVE && univ.party[j].cur_sp > 4 && - (can_see_light(source,univ.party[j].combat_pos,sight_obscurity) < 5) && (dist(source,univ.party[j].combat_pos) <= 8)) { - target = j; - i = 8; - targ_space = univ.party[target].combat_pos; - } - } - + if(abil.second.missile.type == eMonstMissile::ARROW || abil.second.missile.type == eMonstMissile::BOLT) + snd = 12; + if(abil.second.missile.pic < 0) play_sound(snd); + else run_a_missile(source, targ_space, abil.second.missile.pic, 1, snd, 0, 0, 100); + if((target < 100 && univ.party[target].status[eStatus::INVISIBLE] > 0) || m_target->invisible) { + if(get_ran(1,1,100) > hit_chance[univ.town.monst[m_num].level]) { + add_string_to_buf(" Can't find target!"); + return; } - run_a_missile(source,targ_space,8,0,43,0,0,100); - std::string create_line = " Drains " + univ.party[target].name + '.'; - add_string_to_buf(create_line); - univ.party[target].cur_sp = univ.party[target].cur_sp / 2; } - else { // on monst - run_a_missile(source,targ_space,8,0,43,0,0,100); - monst_spell_note(m_target->number,11); - if(m_target->mp >= 4) - m_target->mp = m_target->mp / 2; - else m_target->skill = 1; - } - } - else if(level == MONSTER_HEAT_RAY) { - run_a_missile(source,targ_space,13,0,51, - 0,0,100); - r1 = get_ran(7,1,6); - start_missile_anim(); - if(target < 100) { // pc - std::string create_line = " Hits " + univ.party[target].name + " with heat ray."; - add_string_to_buf(create_line); - damage_pc(target,r1,eDamageType::FIRE,eRace::UNKNOWN,0); - } - else { // on monst - add_string_to_buf(" Fires heat ray."); - damage_monst(target - 100,7,r1,0,eDamageType::FIRE,0); - } - do_explosion_anim(5,0); - end_missile_anim(); - handle_marked_damage(); - } - else if(level == MONSTER_ACID_SPIT) { - run_a_missile(source,targ_space,0,1,64, - 0,0,100); - //play_sound(64); - if(target < 100) { // pc - std::string create_line = " Spits acid on " + univ.party[target].name + '.'; - add_string_to_buf(create_line); - acid_pc(target,6); - } - else { // on monst - add_string_to_buf(" Spits acid."); - acid_monst(m_target,6); - } - } - else if(target < 100) { // missile on PC - std::string create_line; - switch(level) { - case MONSTER_THROWS_DARTS: case MONSTER_SHOOTS_ARROWS: case MONSTER_GOOD_ARCHER: - run_a_missile(source,targ_space,3,1,12,0,0,100); - create_line = " Shoots at " + univ.party[target].name + '.'; - break; - case MONSTER_THROWS_SPEARS: - run_a_missile(source,targ_space,5,1,14,0,0,100); - create_line = " Throws spear at " + univ.party[target].name + '.'; - break; - case MONSTER_THROWS_RAZORDISKS: - run_a_missile(source,targ_space,7,1,14,0,0,100); - create_line = " Throws razordisk at " + univ.party[target].name + '.'; - break; - case MONSTER_SHOOTS_SPINES: - run_a_missile(source,targ_space,5,1,14,0,0,100); - create_line = " Fires spines at " + univ.party[target].name + '.'; - break; - default: - run_a_missile(source,targ_space,12,1,14,0,0,100); - create_line = " Throws rock at " + univ.party[target].name + '.'; - break; - } - - add_string_to_buf(create_line); - - // Check sanctuary - if(univ.party[target].status[eStatus::INVISIBLE] > 0) { - r1 = get_ran(1,1,100); - if(r1 > hit_chance[level]) { - add_string_to_buf(" Can't find target! "); - } - return; - } - - r1 = get_ran(1,1,100) - 5 * min(10,bless) + 5 * univ.party[target].status[eStatus::BLESS_CURSE] - - 5 * (can_see_light(source, univ.party[target].combat_pos,sight_obscurity)); + int target_bless = target < 100 ? univ.party[target].status[eStatus::BLESS_CURSE] : m_target->status[eStatus::BLESS_CURSE]; + location target_pos = target < 100 ? univ.party[target].combat_pos : m_target->cur_loc; + int r1 = get_ran(1,1,100) - 5 * min(10,bless) + 5 * target_bless - 5 * can_see_light(source, target_pos, sight_obscurity); if(univ.party[target].parry < 100) r1 += 5 * univ.party[target].parry; - r2 = get_ran(dam[level],1,7) + min(10,bless); - - if(r1 <= hit_chance[dam[level] * 2]) { -// sprintf ((char *) create_line, " Hits %s.",(char *) univ.party[target].name); -// add_string_to_buf((char *) create_line); - if(damage_pc(target,r2,eDamageType::WEAPON,eRace::UNKNOWN,13)) { - // TODO: Uh, is something supposed to happen here!? + if(r1 <= hit_chance[abil.second.missile.skill]) { + r2 = get_ran(abil.second.missile.dice,1,abil.second.missile.sides) + min(10,bless); + if(target < 100) { + add_string_to_buf(" Hits " + univ.party[target].name + '.'); + // TODO: Should we pass in the monster's actual race here? + damage_pc(target,r2,eDamageType::WEAPON,eRace::UNKNOWN,13); + } else { + monst_spell_note(m_target->number, 16); + damage_monst(target - 100,7,r2,0,eDamageType::WEAPON,13); + } + } else { + if(target < 100) add_string_to_buf(" Misses " + univ.party[target].name + '.'); + else monst_spell_note(m_target->number,18); + } + } else { + if(abil.first == eMonstAbil::DRAIN_SP && target < 100 && univ.party[target].cur_sp < 4) { + // modify target is target has no sp + for(i = 0; i < 8; i++) { + j = get_ran(1,0,5); + if(univ.party[j].main_status == eMainStatus::ALIVE && univ.party[j].cur_sp > 4 && + (can_see_light(source,univ.party[j].combat_pos,sight_obscurity) < 5) && (dist(source,univ.party[j].combat_pos) <= 8)) { + target = j; + i = 8; + targ_space = univ.party[target].combat_pos; + } } } - else { - std::string create_line = " Misses " + univ.party[target].name + '.'; - add_string_to_buf(create_line); - } - - } - else { // missile on monst - switch(level) { - case MONSTER_THROWS_DARTS: case MONSTER_SHOOTS_ARROWS: case MONSTER_GOOD_ARCHER: - run_a_missile(source,targ_space,3,1,12,0,0,100); - monst_spell_note(m_target->number,12); + snd_num_t snd; + int path_type = 0; + switch(abil.second.gen.type) { + case eMonstGen::TOUCH: return; // never reached + case eMonstGen::RAY: + snd = 51; + if(target < 100) add_string_to_buf(" Fires ray at " + univ.party[target].name + '.'); + else monst_spell_note(m_target->number, 55); // TODO: Add this note break; - case MONSTER_THROWS_SPEARS: - run_a_missile(source,targ_space,5,1,14,0,0,100); - monst_spell_note(m_target->number,13); + case eMonstGen::GAZE: + snd = 43; + if(target < 100) add_string_to_buf(" Gazes at " + univ.party[target].name + '.'); + else monst_spell_note(m_target->number, 56); // TODO: Add this note break; - case MONST_THROWS_RAZORDISKS: - run_a_missile(source,targ_space,7,1,14,0,0,100); - monst_spell_note(m_target->number,15); + case eMonstGen::BREATH: + snd = 44; + if(target < 100) add_string_to_buf(" Breathes on " + univ.party[target].name + '.'); + else monst_spell_note(m_target->number, 57); // TODO: Add this note break; - case MONSTER_SHOOTS_SPINES: - run_a_missile(source,targ_space,5,1,14,0,0,100); - monst_spell_note(m_target->number,32); - break; - default: - run_a_missile(source,targ_space,12,1,14,0,0,100); - monst_spell_note(m_target->number,14); + case eMonstGen::SPIT: + // Special case for "spit web" -> "throws web" + if(abil.first == eMonstAbil::FIELD && abil.second.gen.fld == eFieldType::FIELD_WEB) { + snd = 14; + if(target < 100) add_string_to_buf(" Throws web at " + univ.party[target].name + '.'); + else monst_spell_note(m_target->number, 58); // TODO: Add this note + } else { + path_type = 1; + snd = 64; + if(target < 100) add_string_to_buf(" Spits at " + univ.party[target].name + '.'); + else monst_spell_note(m_target->number, 59); // TODO: Add this note + } break; } - r1 = get_ran(1,1,100) - 5 * min(10,bless) + 5 * m_target->status[eStatus::BLESS_CURSE] - - 5 * (can_see_light(source, m_target->cur_loc,sight_obscurity)); - r2 = get_ran(dam[level],1,7) + min(10,bless); - - if(r1 <= hit_chance[dam[level] * 2]) { -// monst_spell_note(m_target->number,16); - - damage_monst(target - 100,7,r2,0,eDamageType::WEAPON,13); - } - else { - monst_spell_note(m_target->number,18); - } + if(abil.second.gen.pic < 0) play_sound(snd); + else run_a_missile(source, targ_space, abil.second.gen.pic, path_type, snd, 0, 0, 100); + monst_basic_abil(m_num, abil, target); } } +void monst_basic_abil(short m_num, std::pair abil, short target) { + int i, r1; + cCreature* m_target; + location targ_space; + if(target == 6) + return; + if(target >= 100) { + m_target = &univ.town.monst[m_num]; + targ_space = univ.town.monst[target - 100].cur_loc; + if(univ.town.monst[target - 100].active == 0) + return; + } else { + targ_space = (is_combat()) ? univ.party[target].combat_pos : univ.town.p_loc; + if(univ.party[target].main_status != eMainStatus::ALIVE) + return; + } + switch(abil.first) { + case eMonstAbil::DAMAGE: + // Determine die size + i = 6; + if(abil.second.gen.type == eMonstGen::BREATH) + i = 8; + else if(abil.second.gen.type == eMonstGen::SPIT || abil.second.gen.type == eMonstGen::TOUCH) + i = 10; + r1 = get_ran(abil.second.gen.strength, 1, i); + start_missile_anim(); + if(target < 100) damage_pc(target, r1, abil.second.gen.dmg, eRace::UNKNOWN, 0); + else damage_monst(target - 100, 7, r1, 0, abil.second.gen.dmg, 0); + do_explosion_anim(5, 0); + end_missile_anim(); + handle_marked_damage(); + break; + case eMonstAbil::STUN: + if(target < 100 && pc_has_abil_equip(target, eItemAbil::LIFE_SAVING) < 24) + break; + case eMonstAbil::STATUS: case eMonstAbil::STATUS2: + switch(abil.second.gen.stat) { + case eStatus::PARALYZED: + case eStatus::ASLEEP: + if(abil.second.gen.type == eMonstGen::TOUCH) + i = abil.second.gen.stat == eStatus::ASLEEP ? -15 : -5; + else i = univ.town.monst[m_num].level / 2; + if(target < 100) sleep_pc(target, abil.second.gen.strength, abil.second.gen.stat, i); + else charm_monst(m_target, i, abil.second.gen.stat, abil.second.gen.strength); + break; + case eStatus::ACID: + if(target < 100) acid_pc(target, abil.second.gen.strength); + else acid_monst(m_target, abil.second.gen.strength); + break; + case eStatus::POISON: + if(target < 100) poison_pc(target, abil.second.gen.strength); + else poison_monst(m_target, abil.second.gen.strength); + break; + case eStatus::BLESS_CURSE: + if(target < 100) curse_pc(target, abil.second.gen.strength); + else curse_monst(m_target, abil.second.gen.strength); + break; + case eStatus::HASTE_SLOW: + if(target < 100) slow_pc(target, abil.second.gen.strength); + else slow_monst(m_target, abil.second.gen.strength); + break; + case eStatus::WEBS: + if(target < 100) web_pc(target, abil.second.gen.strength); + else web_monst(m_target, abil.second.gen.strength); + break; + case eStatus::DISEASE: + if(target < 100) disease_pc(target, abil.second.gen.strength); + else disease_monst(m_target, abil.second.gen.strength); + break; + case eStatus::DUMB: + if(target < 100) dumbfound_pc(target, abil.second.gen.strength); + else dumbfound_monst(m_target, abil.second.gen.strength); + break; + // These only work on PCs + case eStatus::INVULNERABLE: + case eStatus::MAGIC_RESISTANCE: + case eStatus::INVISIBLE: + case eStatus::MARTYRS_SHIELD: + if(target < 100) + univ.party[target].apply_status(abil.second.gen.stat, -abil.second.gen.strength); + break; + // This only works on monsters + case eStatus::CHARM: + if(target >= 100) + charm_monst(m_target, abil.second.gen.strength, eStatus::CHARM, univ.town.monst[m_num].attitude); + break; + // These three don't make sense in this context + case eStatus::MAIN: + case eStatus::POISONED_WEAPON: + case eStatus::FORCECAGE: + return; + } + break; + case eMonstAbil::PETRIFY: + i = univ.town.monst[m_num].level * abil.second.gen.strength / 100; + if(target < 100) petrify_pc(target, i); + else petrify_monst(m_target, i); + break; + case eMonstAbil::DRAIN_SP: + if(target < 100) { + add_string_to_buf(" Drains " + univ.party[target].name + '.'); + univ.party[target].cur_sp *= abil.second.gen.strength; + univ.party[target].cur_sp /= 100; + } else { + monst_spell_note(m_target->number,11); + // TODO: If mp < 4 it used to set monster's skill to 1. Should that be restored? + m_target->mp *= abil.second.gen.strength; + m_target->mp /= 100; + } + break; + case eMonstAbil::DRAIN_XP: + if(target < 100) { + if(pc_has_abil_equip(target, eItemAbil::LIFE_SAVING) < 24) break; + drain_pc(target, univ.town.monst[m_num].level * abil.second.gen.strength / 100); + } + break; + case eMonstAbil::KILL: + r1 = get_ran(10 * abil.second.gen.strength, 1, 10); + damage_target(target, r1, eDamageType::UNBLOCKABLE); + break; + case eMonstAbil::STEAL_FOOD: + univ.party.food = std::max(0, univ.party.food - get_ran(1,0,abil.second.gen.strength) - abil.second.gen.strength); + break; + case eMonstAbil::STEAL_GOLD: + univ.party.gold = std::max(0, univ.party.gold - get_ran(1,0,abil.second.gen.strength) - abil.second.gen.strength); + break; + case eMonstAbil::FIELD: + effect_pat_type pat; + switch(eSpellPat(abil.second.gen.strength)) { + case PAT_SINGLE: pat = single; break; + case PAT_SQ: pat = small_square; break; + case PAT_OPENSQ: pat = open_square; break; + case PAT_SMSQ: pat = square; break; + case PAT_PLUS: pat = t; break; + case PAT_RAD2: pat = radius2; break; + case PAT_RAD3: pat = radius3; break; + default: // either PAT_WALL or not a valid eSpellPat + pat = field[abil.second.gen.strength / 10]; + break; + } + place_spell_pattern(pat, targ_space, abil.second.gen.fld, 7); + break; + } +} -//dam_type; // 0 - fire 1 - cold 2 - magic -bool monst_breathe(cCreature *caster,location targ_space,short dam_type) { - short level; - miss_num_t missile_t[4] = {13,6,8,8}; - eDamageType type[4] = {eDamageType::FIRE, eDamageType::COLD, eDamageType::MAGIC, eDamageType::UNBLOCKABLE}; - location l; - +bool monst_breathe(cCreature *caster,location targ_space,uAbility abil) { draw_terrain(2); if((is_combat()) && (center_on_monst)) { frame_space(caster->cur_loc,0,caster->x_width,caster->y_width); } - //if(dam_type < 2) - l = caster->cur_loc; + + location l = caster->cur_loc; if((caster->direction < 4) && (caster->x_width > 1)) l.x++; - dam_type = caster->breath_type; - run_a_missile(l,targ_space,missile_t[dam_type],0,44,0,0,100); - // play_sound(44); - //else play_sound(64); - level = caster->breath; - //if(level > 10) - // play_sound(5); + if(abil.gen.pic >= 0) + run_a_missile(l,targ_space,abil.gen.pic,0,44,0,0,100); + else play_sound(44); monst_breathe_note(caster->number); - level = get_ran(caster->breath,1,8); + short level = get_ran(abil.gen.strength,1,8); if(overall_mode < MODE_COMBAT) level = level / 3; start_missile_anim(); - hit_space(targ_space,level,type[dam_type],1,1); + hit_space(targ_space,level,abil.gen.dmg,1,1); do_explosion_anim(5,0); end_missile_anim(); handle_marked_damage(); @@ -3861,47 +3839,33 @@ static void place_spell_pattern(effect_pat_type pat,location center,unsigned sho if(pat.pattern[i - center.x + 4][j - center.y + 4] > 0) monster_hit = true; effect = pat.pattern[i - center.x + 4][j - center.y + 4]; + which_m = &univ.town.monst[k]; + if(which_m->abil[eMonstAbil::RADIATE].active && effect == which_m->abil[eMonstAbil::RADIATE].radiate.type) + continue; switch(effect) { case FIELD_WEB: - which_m = &univ.town.monst[k]; web_monst(which_m,3); break; case WALL_FORCE: r1 = get_ran(3,1,6); - if(which_m->radiate_1 == MONSTER_RADIATE_SHOCK_FIELDS) - break; damage_monst(k, who_hit, r1,0, eDamageType::MAGIC,0); break; case WALL_FIRE: r1 = get_ran(2,1,6); - which_m = &univ.town.monst[k]; - if(which_m->radiate_1 == MONSTER_RADIATE_FIRE_FIELDS) - break; damage_monst(k, who_hit, r1,0, eDamageType::FIRE,0); break; case CLOUD_STINK: - which_m = &univ.town.monst[k]; - if(which_m->radiate_1 == MONSTER_RADIATE_STINKING_CLOUDS) - break; curse_monst(which_m,get_ran(1,1,2)); break; case WALL_ICE: - which_m = &univ.town.monst[k]; r1 = get_ran(3,1,6); - if(which_m->radiate_1 == MONSTER_RADIATE_ICE_FIELDS) - break; damage_monst(k, who_hit, r1,0, eDamageType::COLD,0); break; case WALL_BLADES: r1 = get_ran(6,1,8); - if(which_m->radiate_1 == MONSTER_RADIATE_BLADE_FIELDS) - break; damage_monst(k, who_hit, r1,0, eDamageType::WEAPON,0); break; case CLOUD_SLEEP: - which_m = &univ.town.monst[k]; - if(which_m->radiate_1 == MONSTER_RADIATE_SLEEP_FIELDS) - break; charm_monst(which_m,0,eStatus::ASLEEP,3); break; case OBJECT_BLOCK: diff --git a/src/boe.combat.h b/src/boe.combat.h index 97d5965dc..849dc6ca1 100644 --- a/src/boe.combat.h +++ b/src/boe.combat.h @@ -25,8 +25,9 @@ void combat_run_monst(); void do_monster_turn(); void monster_attack_pc(short who_att,short target); void monster_attack_monster(short who_att,short attackee); -void monst_fire_missile(short m_num,short bless,short level,location source,short target); -bool monst_breathe(cCreature *caster,location targ_space,short dam_type); +void monst_fire_missile(short m_num,short bless,std::pair abil,location source,short target); +void monst_basic_abil(short m_num, std::pair abil, short target); +bool monst_breathe(cCreature *caster,location targ_space,uAbility dam_type); bool monst_cast_mage(cCreature *caster,short targ); bool monst_cast_priest(cCreature *caster,short targ); void damage_target(short target,short dam,eDamageType type); diff --git a/src/boe.graphutil.cpp b/src/boe.graphutil.cpp index a1eafa385..c1ae0b972 100644 --- a/src/boe.graphutil.cpp +++ b/src/boe.graphutil.cpp @@ -190,7 +190,7 @@ void draw_monsters() { } if(is_town() || is_combat()) { for(i = 0; i < univ.town->max_monst(); i++) - if(univ.town.monst[i].active != 0 && univ.town.monst[i].spec_skill != MONSTER_INVISIBLE) + if(univ.town.monst[i].active != 0 && !univ.town.monst[i].invisible) if(point_onscreen(center,univ.town.monst[i].cur_loc) && party_can_see_monst(i)) { check_if_monst_seen(univ.town.monst[i].number, univ.town.monst[i].cur_loc); where_draw.x = univ.town.monst[i].cur_loc.x - center.x + 4; diff --git a/src/boe.infodlg.cpp b/src/boe.infodlg.cpp index 12b3c7a39..f78927bac 100644 --- a/src/boe.infodlg.cpp +++ b/src/boe.infodlg.cpp @@ -357,7 +357,7 @@ static void put_monst_info(cDialog& me, const cCreature& store_m) { short abil,i; cPict& pic = dynamic_cast(me["pic"]); - if(store_m.spec_skill == MONSTER_INVISIBLE) + if(store_m.invisible) pic.setPict(400,PIC_MONST);// TODO: should probably be PICT_BLANK? else if(store_m.picture_num < 1000) pic.setPict(store_m.picture_num,PIC_MONST); @@ -382,12 +382,13 @@ static void put_monst_info(cDialog& me, const cCreature& store_m) { store_text = get_m_name(store_m.number); me["name"].setText(store_text); - // TODO: More descriptive ability descriptions, taking into account potential variation - abil = store_m.spec_skill; - str = get_str("monster-abilities",abil + 1); - me["abil1"].setText(str); - str = get_str("monster-abilities",store_m.radiate_1 + 50); - me["abil2"].setText(str); + i = 1; + for(auto& abil : store_m.abil) { + if(i > 2) break; // TODO: Support showing more than just the first two abilities + std::string id = "abil" + std::to_string(i); + me[id].setText(abil.second.to_string(abil.first)); + i++; + } for(i = 0; i < 3; i++) { if(store_m.a[i] > 0) { @@ -409,7 +410,6 @@ static void put_monst_info(cDialog& me, const cCreature& store_m) { me["ap"].setTextToNum(store_m.speed); me["mage"].setTextToNum(store_m.mu); me["priest"].setTextToNum(store_m.cl); - me["poison"].setTextToNum(store_m.poison); // Immunities dynamic_cast(me["magic-res"]).setState(store_m.magic_res == RESIST_HALF ? led_red : led_off); dynamic_cast(me["magic-imm"]).setState(store_m.magic_res == RESIST_ALL ? led_red : led_off); diff --git a/src/boe.items.cpp b/src/boe.items.cpp index 0430e2a15..37e51c38e 100644 --- a/src/boe.items.cpp +++ b/src/boe.items.cpp @@ -833,7 +833,7 @@ void set_town_attitude(short lo,short hi,short att) { univ.town.monst[i].mobility = 1; // If a "guard", give a power boost - if(univ.scenario.scen_monsters[num].spec_skill == MONSTER_GUARD) { + if(univ.scenario.scen_monsters[num].guard) { univ.town.monst[i].active = 2; univ.town.monst[i].health *= 3; univ.town.monst[i].status[eStatus::HASTE_SLOW] = 8; diff --git a/src/boe.monster.cpp b/src/boe.monster.cpp index d0fa5933a..a51b621f4 100644 --- a/src/boe.monster.cpp +++ b/src/boe.monster.cpp @@ -224,7 +224,7 @@ void do_monsters() { l1 = univ.town.monst[i].cur_loc; l2 = (univ.town.monst[i].target <= 6) ? univ.town.p_loc : univ.town.monst[target - 100].cur_loc; - if(univ.town.monst[i].morale < 0 && univ.town.monst[i].spec_skill != MONSTER_MINDLESS + if(univ.town.monst[i].morale < 0 && !univ.town.monst[i].mindless && univ.town.monst[i].m_type != eRace::UNDEAD) { acted_yet = flee_party(i,l1,l2); if(get_ran(1,0,10) < 6) @@ -280,6 +280,8 @@ bool monst_hate_spot(short which_m,location *good_loc) { location prospect,loc; loc = univ.town.monst[which_m].cur_loc; + bool have_radiate = univ.town.monst[which_m].abil[eMonstAbil::RADIATE].active; + eFieldType which_radiate = univ.town.monst[which_m].abil[eMonstAbil::RADIATE].radiate.type; bool hate_spot = false; // Hate barriers if(univ.town.is_fire_barr(loc.x,loc.y) || univ.town.is_force_barr(loc.x,loc.y)) hate_spot = true; @@ -288,39 +290,39 @@ bool monst_hate_spot(short which_m,location *good_loc) { // Hate blade wall? else if(univ.town.is_blade_wall(loc.x,loc.y)) { hate_spot = true; - if(univ.town.monst[which_m].radiate_1 == MONSTER_RADIATE_BLADE_FIELDS) hate_spot = false; - else if(univ.town.monst[which_m].spec_skill == MONSTER_INVULNERABILITY) hate_spot = false; + if(have_radiate && which_radiate == eFieldType::WALL_BLADES) hate_spot = false; + else if(univ.town.monst[which_m].invuln) hate_spot = false; } // Hate ice wall? else if(univ.town.is_ice_wall(loc.x,loc.y)) { hate_spot = true; - if(univ.town.monst[which_m].radiate_1 == MONSTER_RADIATE_ICE_FIELDS) hate_spot = false; + if(have_radiate && which_radiate == eFieldType::WALL_ICE) hate_spot = false; else if(univ.town.monst[which_m].cold_res == RESIST_ALL) hate_spot = false; } // Hate fire wall? else if(univ.town.is_fire_wall(loc.x,loc.y)) { hate_spot = true; - if(univ.town.monst[which_m].radiate_1 == MONSTER_RADIATE_FIRE_FIELDS) hate_spot = false; + if(have_radiate && which_radiate == eFieldType::WALL_FIRE) hate_spot = false; else if(univ.town.monst[which_m].fire_res == RESIST_ALL) hate_spot = false; } // Note: Monsters used to enter shock walls even if they were merely resistant to magic // Hate shock wall? else if(univ.town.is_force_wall(loc.x,loc.y)) { hate_spot = true; - if(univ.town.monst[which_m].radiate_1 == MONSTER_RADIATE_SHOCK_FIELDS) hate_spot = false; + if(have_radiate && which_radiate == eFieldType::WALL_FORCE) hate_spot = false; else if(univ.town.monst[which_m].magic_res == RESIST_ALL) hate_spot = false; } // Hate stink cloud? else if(univ.town.is_scloud(loc.x,loc.y)) { hate_spot = true; - if(univ.town.monst[which_m].radiate_1 == MONSTER_RADIATE_STINKING_CLOUDS) hate_spot = false; + if(have_radiate && which_radiate == eFieldType::CLOUD_STINK) hate_spot = false; else if(univ.town.monst[which_m].magic_res == RESIST_ALL) hate_spot = false; else if(univ.town.monst[which_m].magic_res == RESIST_HALF) hate_spot = false; } // Hate sleep cloud? else if(univ.town.is_sleep_cloud(loc.x,loc.y)) { hate_spot = true; - if(univ.town.monst[which_m].radiate_1 == MONSTER_RADIATE_SLEEP_FIELDS) hate_spot = false; + if(have_radiate && which_radiate == eFieldType::CLOUD_SLEEP) hate_spot = false; else if(univ.town.monst[which_m].magic_res == RESIST_ALL) hate_spot = false; else if(univ.town.monst[which_m].magic_res == RESIST_HALF) hate_spot = false; } @@ -815,11 +817,14 @@ void monst_inflict_fields(short which_monst) { return; which_m = &univ.town.monst[which_monst]; + bool have_radiate = which_m->abil[eMonstAbil::RADIATE].active; + eFieldType which_radiate = which_m->abil[eMonstAbil::RADIATE].radiate.type; for(i = 0; i < univ.town.monst[which_monst].x_width; i++) for(j = 0; j < univ.town.monst[which_monst].y_width; j++) if(univ.town.monst[which_monst].active > 0) { where_check.x = univ.town.monst[which_monst].cur_loc.x + i; where_check.y = univ.town.monst[which_monst].cur_loc.y + j; + // TODO: If the goal is to damage the monster by any fields it's on, why all the break statements? if(univ.town.is_quickfire(where_check.x,where_check.y)) { r1 = get_ran(2,1,8); damage_monst(which_monst,7,r1,0,eDamageType::FIRE,0); @@ -827,30 +832,30 @@ void monst_inflict_fields(short which_monst) { } if(univ.town.is_blade_wall(where_check.x,where_check.y)) { r1 = get_ran(6,1,8); - if(univ.town.monst[which_monst].radiate_1 != MONSTER_RADIATE_BLADE_FIELDS) + if(have_radiate && which_radiate != eFieldType::WALL_BLADES) damage_monst(which_monst,7,r1,0,eDamageType::WEAPON,0); break; } if(univ.town.is_force_wall(where_check.x,where_check.y)) { r1 = get_ran(3,1,6); - if(univ.town.monst[which_monst].radiate_1 != MONSTER_RADIATE_SHOCK_FIELDS) + if(have_radiate && which_radiate != eFieldType::WALL_FORCE) damage_monst(which_monst,7,r1,0,eDamageType::MAGIC,0); break; } if(univ.town.is_sleep_cloud(where_check.x,where_check.y)) { - if(univ.town.monst[which_monst].radiate_1 != MONSTER_RADIATE_SLEEP_FIELDS) + if(have_radiate && which_radiate != eFieldType::CLOUD_SLEEP) charm_monst(which_m,0,eStatus::ASLEEP,3); break; } if(univ.town.is_ice_wall(where_check.x,where_check.y)) { r1 = get_ran(3,1,6); - if(univ.town.monst[which_monst].radiate_1 != MONSTER_RADIATE_ICE_FIELDS) + if(have_radiate && which_radiate != eFieldType::WALL_ICE) damage_monst(which_monst,7,r1,0,eDamageType::COLD,0); break; } if(univ.town.is_scloud(where_check.x,where_check.y)) { r1 = get_ran(1,2,3); - if(univ.town.monst[which_monst].radiate_1 != MONSTER_RADIATE_STINKING_CLOUDS) + if(have_radiate && which_radiate != eFieldType::CLOUD_STINK) curse_monst(which_m,r1); break; } @@ -863,7 +868,7 @@ void monst_inflict_fields(short which_monst) { } if(univ.town.is_fire_wall(where_check.x,where_check.y)) { r1 = get_ran(2,1,6); - if(univ.town.monst[which_monst].radiate_1 != MONSTER_RADIATE_FIRE_FIELDS) + if(have_radiate && which_radiate != eFieldType::WALL_FIRE) damage_monst(which_monst,7,r1,0,eDamageType::FIRE,0); break; } @@ -926,7 +931,7 @@ bool monst_check_special_terrain(location where_check,short mode,short which_mon // nasty barriers if((which_m->mu > 0) || (which_m->cl > 0)) mage = true; - if(which_m->spec_skill == MONSTER_MINDLESS) + if(which_m->mindless) guts = 20; else guts = get_ran(1,1,(which_m->level / 2)); guts += which_m->health / 20; @@ -937,28 +942,31 @@ bool monst_check_special_terrain(location where_check,short mode,short which_mon if((univ.town.is_antimagic(where_check.x,where_check.y)) && (mage)) return false; - if(univ.town.is_fire_wall(where_check.x,where_check.y) && which_m->radiate_1 != MONSTER_RADIATE_FIRE_FIELDS) { + bool have_radiate = which_m->abil[eMonstAbil::RADIATE].active; + eFieldType which_radiate = which_m->abil[eMonstAbil::RADIATE].radiate.type; + if(univ.town.is_fire_wall(where_check.x,where_check.y) && !(have_radiate && which_radiate == eFieldType::WALL_FIRE)) { if(guts < 3) return false; } - if(univ.town.is_force_wall(where_check.x,where_check.y) && which_m->radiate_1 != MONSTER_RADIATE_SHOCK_FIELDS) { + if(univ.town.is_force_wall(where_check.x,where_check.y) && !(have_radiate && which_radiate == eFieldType::WALL_FORCE)) { if(guts < 4) return false; } - if(univ.town.is_ice_wall(where_check.x,where_check.y) && which_m->radiate_1 != MONSTER_RADIATE_ICE_FIELDS) { + if(univ.town.is_ice_wall(where_check.x,where_check.y) && !(have_radiate && which_radiate == eFieldType::WALL_ICE)) { if(guts < 5) return false; } - if(univ.town.is_sleep_cloud(where_check.x,where_check.y) && which_m->radiate_1 != MONSTER_RADIATE_SLEEP_FIELDS) { + if(univ.town.is_sleep_cloud(where_check.x,where_check.y) && !(have_radiate && which_radiate == eFieldType::CLOUD_SLEEP)) { if(guts < 8) return false; } - if(univ.town.is_blade_wall(where_check.x,where_check.y) && which_m->radiate_1 != MONSTER_RADIATE_BLADE_FIELDS) { + if(univ.town.is_blade_wall(where_check.x,where_check.y) && !(have_radiate && which_radiate == eFieldType::WALL_BLADES)) { if(guts < 8) return false; } if(univ.town.is_quickfire(where_check.x,where_check.y)) { if(guts < 8) return false; } - if(univ.town.is_scloud(where_check.x,where_check.y) && which_m->radiate_1 != MONSTER_RADIATE_STINKING_CLOUDS) { + if(univ.town.is_scloud(where_check.x,where_check.y) && !(have_radiate && which_radiate == eFieldType::CLOUD_STINK)) { if(guts < 4) return false; } - if(univ.town.is_web(where_check.x,where_check.y) && which_m->m_type != eRace::BUG) { + if(univ.town.is_web(where_check.x,where_check.y) && which_m->m_type != eRace::BUG + && !(have_radiate && which_radiate == eFieldType::FIELD_WEB)) { if(guts < 3) return false; } if(univ.town.is_fire_barr(where_check.x,where_check.y)) { @@ -1056,7 +1064,7 @@ bool monst_check_special_terrain(location where_check,short mode,short which_mon case eDamageType::POISON: return univ.town.monst[which_monst].poison_res == RESIST_ALL; default: - return univ.town.monst[which_monst].spec_skill == MONSTER_INVULNERABILITY; + return univ.town.monst[which_monst].invuln; } // TODO: Should it check any other terrain specials? } @@ -1093,7 +1101,7 @@ void forced_place_monster(mon_num_t which,location where) { } void magic_adjust(cCreature *which_m,short *how_much) { - if(which_m->spec_skill == MONSTER_ABSORB_SPELLS) { + if(which_m->abil[eMonstAbil::ABSORB_SPELLS].active) { *how_much = 0; if(32767 - which_m->health > 3) which_m->health = 32767; @@ -1197,7 +1205,8 @@ void dumbfound_monst(cCreature *which_m,short how_much) { } -// Also used for sleep and paralyze, which_statys is 0 means charm +// Also used for sleep and paralyze. +// For charm, amount is the resulting attitude of the charmed monster; if 0, attitude is 2. void charm_monst(cCreature *which_m,short penalty,eStatus which_status,short amount) { short r1; @@ -1223,7 +1232,7 @@ void charm_monst(cCreature *which_m,short penalty,eStatus which_status,short amo r1 -= 25; if(which_status == eStatus::PARALYZED) r1 -= 15; - if(which_status == eStatus::ASLEEP && which_m->spec_skill == MONSTER_BREATHES_SLEEP_CLOUDS) + if(which_status == eStatus::ASLEEP && which_m->abil[eMonstAbil::FIELD].active && which_m->abil[eMonstAbil::FIELD].gen.fld == eFieldType::CLOUD_SLEEP) return; if(r1 > charm_odds[which_m->level / 2]) { @@ -1232,7 +1241,8 @@ void charm_monst(cCreature *which_m,short penalty,eStatus which_status,short amo } else { if(which_status == eStatus::CHARM) { - which_m->attitude = 2; + if(amount == 0 || amount > 3) amount = 2; + which_m->attitude = amount; monst_spell_note(which_m->number,23); } else if(which_status == eStatus::FORCECAGE) { which_m->status[eStatus::FORCECAGE] = 8; @@ -1260,7 +1270,7 @@ void record_monst(cCreature *which_m) { ASB("Capture Soul: Monster is too big."); } // TODO: Are these two sounds right? - else if(r1 > charm_odds[which_m->level / 2] || which_m->spec_skill == MONSTER_SPLITS + else if(r1 > charm_odds[which_m->level / 2] || which_m->abil[eMonstAbil::SPLITS].active || which_m->m_type == eRace::IMPORTANT) { monst_spell_note(which_m->number,10); play_sound(68); diff --git a/src/boe.specials.cpp b/src/boe.specials.cpp index 6bca031fe..e882e8605 100644 --- a/src/boe.specials.cpp +++ b/src/boe.specials.cpp @@ -1463,7 +1463,7 @@ bool damage_monst(short which_m, short who_hit, short how_much, short how_much_s // Absorb damage? if((dam_type == eDamageType::FIRE || dam_type == eDamageType::MAGIC || dam_type == eDamageType::COLD) - && victim->spec_skill == MONSTER_ABSORB_SPELLS) { + && victim->abil[eMonstAbil::ABSORB_SPELLS].active) { if(32767 - victim->health > how_much) victim->health = 32767; else victim->health += how_much; @@ -1478,7 +1478,7 @@ bool damage_monst(short which_m, short who_hit, short how_much, short how_much_s how_much /= 2; // Invulnerable? - if(victim->spec_skill == MONSTER_INVULNERABILITY) + if(victim->invuln) how_much = how_much / 10; @@ -1523,7 +1523,7 @@ bool damage_monst(short which_m, short who_hit, short how_much, short how_much_s victim->health = -1; // splitting monsters - if(victim->spec_skill == MONSTER_SPLITS && victim->health > 0){ + if(victim->abil[eMonstAbil::SPLITS].active && victim->health > 0){ where_put = find_clear_spot(victim->cur_loc,1); if(where_put.x > 0) if((which_spot = place_monster(victim->number,where_put)) < 90) { @@ -1636,8 +1636,8 @@ void kill_monst(cCreature *which_m,short who_killed,eMainStatus type) { if(which_m->special_on_kill >= 0) run_special(eSpecCtx::KILL_MONST,2,which_m->special_on_kill,which_m->cur_loc,&s1,&s2,&s3); - if(which_m->radiate_1 == MONSTER_DEATH_TRIGGERS) - run_special(eSpecCtx::KILL_MONST,0,which_m->radiate_2,which_m->cur_loc,&s1,&s2,&s3); + if(which_m->abil[eMonstAbil::DEATH_TRIGGER].active) + run_special(eSpecCtx::KILL_MONST,0,which_m->abil[eMonstAbil::DEATH_TRIGGER].special.extra1,which_m->cur_loc,&s1,&s2,&s3); if((!in_scen_debug) && ((which_m->summoned >= 100) || (which_m->summoned == 0))) { // no xp for party-summoned monsters xp = which_m->level * 2; diff --git a/src/boe.text.cpp b/src/boe.text.cpp index c1bdd825a..d4e207222 100644 --- a/src/boe.text.cpp +++ b/src/boe.text.cpp @@ -1118,6 +1118,12 @@ void monst_spell_note(mon_num_t number,short which_mess) { case 52: msg = " " + msg + " is trapped!"; break; + case 53: + msg = " Throws dart at " + msg + '.'; + break; + case 54: + msg = " Throws knife at " + msg + '.'; + break; } if(which_mess > 0) diff --git a/src/boe.town.cpp b/src/boe.town.cpp index f3475a48d..67ca6d2e3 100644 --- a/src/boe.town.cpp +++ b/src/boe.town.cpp @@ -361,7 +361,7 @@ void start_town_mode(short which_town, short entry_dir) { // Flush excess doomguards and viscous goos for(i = 0; i < univ.town->max_monst(); i++) - if((univ.town.monst[i].spec_skill == MONSTER_SPLITS) && + if((univ.town.monst[i].abil[eMonstAbil::SPLITS].active) && (univ.town.monst[i].number != univ.town->creatures(i).number)) univ.town.monst[i].active = 0; diff --git a/src/classes/creatlist.cpp b/src/classes/creatlist.cpp index 9dc551353..a6e72c6b9 100644 --- a/src/classes/creatlist.cpp +++ b/src/classes/creatlist.cpp @@ -39,7 +39,7 @@ void cPopulation::assign(size_t n, const cCreature& other, const cMonster& base, static_cast(dudes[n]) = base; // Now set up extra stuff dudes[n].active = 1; // TODO: Is this right? - if(dudes[n].spec_skill == MONSTER_INVISIBLE) dudes[n].picture_num = 0; + if(dudes[n].invisible) dudes[n].picture_num = 0; dudes[n].m_health /= easy ? 2 : 1; dudes[n].m_health *= difficulty_adjust; dudes[n].health = dudes[n].m_health; diff --git a/src/classes/monster.cpp b/src/classes/monster.cpp index 2db260055..20228fc8b 100644 --- a/src/classes/monster.cpp +++ b/src/classes/monster.cpp @@ -15,6 +15,10 @@ #include "classes.h" #include "oldstructs.h" #include "fileio.hpp" +#include "spell.hpp" + +static uAbility test; +static_assert(&test.active == &test.missile.active, "uAbility union has incorrect layout"); void cMonster::append(legacy::monster_record_type& old){ level = old.level; @@ -32,11 +36,69 @@ void cMonster::append(legacy::monster_record_type& old){ speed = old.speed; mu = old.mu; cl = old.cl; - breath = old.breath; - breath_type = old.breath_type; treasure = old.treasure; - spec_skill = old.spec_skill; - poison = old.poison; + invisible = mindless = invuln = guard = false; + abil.clear(); + switch(old.spec_skill) { + case 1: addAbil(eMonstAbilTemplate::THROWS_DARTS); break; + case 2: addAbil(eMonstAbilTemplate::SHOOTS_ARROWS); break; + case 3: addAbil(eMonstAbilTemplate::THROWS_SPEARS); break; + case 4: addAbil(eMonstAbilTemplate::THROWS_ROCKS1); break; + case 5: addAbil(eMonstAbilTemplate::THROWS_ROCKS1); break; + case 6: addAbil(eMonstAbilTemplate::THROWS_ROCKS1); break; + case 7: addAbil(eMonstAbilTemplate::THROWS_RAZORDISKS); break; + case 8: addAbil(eMonstAbilTemplate::RAY_PETRIFY); break; + case 9: addAbil(eMonstAbilTemplate::RAY_SP_DRAIN); break; + case 10: addAbil(eMonstAbilTemplate::RAY_HEAT); break; + case 11: invisible = true; break; + case 12: addAbil(eMonstAbilTemplate::SPLITS); break; + case 13: mindless = true; break; + case 14: addAbil(eMonstAbilTemplate::BREATH_FOUL); break; + case 15: addAbil(eMonstAbilTemplate::TOUCH_ICY); break; + case 17: addAbil(eMonstAbilTemplate::TOUCH_ICY_DRAINING); break; + case 16: addAbil(eMonstAbilTemplate::TOUCH_XP_DRAIN); break; + case 18: addAbil(eMonstAbilTemplate::TOUCH_STUN); break; + case 19: addAbil(eMonstAbilTemplate::SHOOTS_WEB); break; + case 20: addAbil(eMonstAbilTemplate::GOOD_ARCHER); break; + case 21: addAbil(eMonstAbilTemplate::TOUCH_STEAL_FOOD); break; + case 22: addAbil(eMonstAbilTemplate::MARTYRS_SHIELD); break; + case 23: addAbil(eMonstAbilTemplate::RAY_PARALYSIS); break; + case 24: addAbil(eMonstAbilTemplate::TOUCH_DUMB); break; + case 25: addAbil(eMonstAbilTemplate::TOUCH_DISEASE); break; + case 26: addAbil(eMonstAbilTemplate::ABSORB_SPELLS); break; + case 27: addAbil(eMonstAbilTemplate::TOUCH_WEB); break; + case 28: addAbil(eMonstAbilTemplate::TOUCH_SLEEP); break; + case 29: addAbil(eMonstAbilTemplate::TOUCH_PARALYSIS); break; + case 30: addAbil(eMonstAbilTemplate::TOUCH_PETRIFY); break; + case 31: addAbil(eMonstAbilTemplate::TOUCH_ACID); break; + case 32: addAbil(eMonstAbilTemplate::BREATH_SLEEP); break; + case 33: addAbil(eMonstAbilTemplate::SPIT_ACID); break; + case 34: addAbil(eMonstAbilTemplate::SHOOTS_SPINES); break; + case 35: addAbil(eMonstAbilTemplate::TOUCH_DEATH); break; + case 36: invuln = true; break; + case 37: guard = true; break; + } + switch(old.radiate_1) { + case 1: addAbil(eMonstAbilTemplate::RADIATE_FIRE, old.radiate_2); break; + case 2: addAbil(eMonstAbilTemplate::RADIATE_ICE, old.radiate_2); break; + case 3: addAbil(eMonstAbilTemplate::RADIATE_SHOCK, old.radiate_2); break; + case 4: addAbil(eMonstAbilTemplate::RADIATE_ANTIMAGIC, old.radiate_2); break; + case 5: addAbil(eMonstAbilTemplate::RADIATE_SLEEP, old.radiate_2); break; + case 6: addAbil(eMonstAbilTemplate::RADIATE_STINK, old.radiate_2); break; + case 10: addAbil(eMonstAbilTemplate::SUMMON_5, old.radiate_2); break; + case 11: addAbil(eMonstAbilTemplate::SUMMON_20, old.radiate_2); break; + case 12: addAbil(eMonstAbilTemplate::SUMMON_50, old.radiate_2); break; + case 15: addAbil(eMonstAbilTemplate::DEATH_TRIGGERS, old.radiate_2); break; + } + if(old.poison > 0) addAbil(eMonstAbilTemplate::TOUCH_POISON, old.poison); + if(old.breath > 0) { + switch(old.breath_type) { + case 0: addAbil(eMonstAbilTemplate::BREATH_FIRE, old.breath); break; + case 1: addAbil(eMonstAbilTemplate::BREATH_FROST, old.breath); break; + case 2: addAbil(eMonstAbilTemplate::BREATH_ELECTRICITY, old.breath); break; + case 3: addAbil(eMonstAbilTemplate::BREATH_DARKNESS, old.breath); break; + } + } corpse_item = old.corpse_item; corpse_item_chance = old.corpse_item_chance; if(old.immunities & 2) @@ -57,14 +119,248 @@ void cMonster::append(legacy::monster_record_type& old){ poison_res = RESIST_HALF; x_width = old.x_width; y_width = old.y_width; - radiate_1 = old.radiate_1; - radiate_2 = old.radiate_2; default_attitude = old.default_attitude; summon_type = old.summon_type; default_facial_pic = old.default_facial_pic; picture_num = old.picture_num; if(picture_num == 122) picture_num = 119; see_spec = -1; + see_str1 = see_str2 = -1; + see_sound = 0; + ambient_sound = 0; +} + +void cMonster::addAbil(eMonstAbilTemplate what, int param) { + switch(what) { + // Missiles: {true, type, missile pic, dice, sides, skill, range, odds} + case eMonstAbilTemplate::THROWS_DARTS: + abil[eMonstAbil::MISSILE].missile = {true, eMonstMissile::DART, 1, 1, 7, 2, 6, 500}; + break; + case eMonstAbilTemplate::SHOOTS_ARROWS: + abil[eMonstAbil::MISSILE].missile = {true, eMonstMissile::ARROW, 3, 2, 7, 4, 8, 750}; + break; + case eMonstAbilTemplate::THROWS_SPEARS: + abil[eMonstAbil::MISSILE].missile = {true, eMonstMissile::ARROW, 5, 3, 7, 6, 8, 625}; + break; + case eMonstAbilTemplate::THROWS_ROCKS1: + abil[eMonstAbil::MISSILE].missile = {true, eMonstMissile::ROCK, 12, 4, 7, 8, 10, 625}; + break; + case eMonstAbilTemplate::THROWS_ROCKS2: + abil[eMonstAbil::MISSILE].missile = {true, eMonstMissile::ROCK, 12, 6, 7, 12, 10, 500}; + break; + case eMonstAbilTemplate::THROWS_ROCKS3: + abil[eMonstAbil::MISSILE].missile = {true, eMonstMissile::ROCK, 12, 8, 7, 16, 10, 500}; + break; + case eMonstAbilTemplate::THROWS_RAZORDISKS: + abil[eMonstAbil::MISSILE].missile = {true, eMonstMissile::RAZORDISK, 7, 7, 7, 14, 8, 625}; + break; + case eMonstAbilTemplate::THROWS_KNIVES: + abil[eMonstAbil::MISSILE].missile = {true, eMonstMissile::KNIFE, 10, 2, 7, 2, 6, 500}; + break; + case eMonstAbilTemplate::GOOD_ARCHER: + abil[eMonstAbil::MISSILE].missile = {true, eMonstMissile::ARROW, 3, 8, 7, 16, 10, 875}; + break; + case eMonstAbilTemplate::SHOOTS_SPINES: + abil[eMonstAbil::MISSILE].missile = {true, eMonstMissile::SPINE, 5, 6, 7, 12, 9, 625}; + break; + case eMonstAbilTemplate::CROSSBOWMAN: + abil[eMonstAbil::MISSILE].missile = {true, eMonstMissile::BOLT, 3, 10, 7, 16, 10, 875}; + break; + case eMonstAbilTemplate::SLINGER: + abil[eMonstAbil::MISSILE].missile = {true, eMonstMissile::ROCK, 12, 2, 7, 3, 8, 750}; + break; + // Magical missiles: {true, type, missile pic, strength, range, odds} + case eMonstAbilTemplate::RAY_PETRIFY: + abil[eMonstAbil::PETRIFY].gen = {true, eMonstGen::GAZE, -1, 25, 6, 625}; + break; + case eMonstAbilTemplate::RAY_SP_DRAIN: + abil[eMonstAbil::DRAIN_SP].gen = {true, eMonstGen::GAZE, 8, 50, 8, 625}; + break; + case eMonstAbilTemplate::RAY_HEAT: + abil[eMonstAbil::DAMAGE].gen = {true, eMonstGen::RAY, 13, 7, 6, 625}; + abil[eMonstAbil::DAMAGE].gen.dmg = eDamageType::FIRE; + break; + case eMonstAbilTemplate::RAY_PARALYSIS: + abil[eMonstAbil::STATUS].gen = {true, eMonstGen::RAY, -1, 100, 6, 750}; + abil[eMonstAbil::STATUS].gen.stat = eStatus::PARALYZED; + break; + case eMonstAbilTemplate::BREATH_FIRE: + abil[eMonstAbil::DAMAGE].gen = {true, eMonstGen::BREATH, 13, param, 8, 375}; + abil[eMonstAbil::DAMAGE].gen.dmg = eDamageType::UNBLOCKABLE; + break; + case eMonstAbilTemplate::BREATH_FROST: + abil[eMonstAbil::DAMAGE].gen = {true, eMonstGen::BREATH, 6, param, 8, 375}; + abil[eMonstAbil::DAMAGE].gen.dmg = eDamageType::UNBLOCKABLE; + break; + case eMonstAbilTemplate::BREATH_ELECTRICITY: + abil[eMonstAbil::DAMAGE].gen = {true, eMonstGen::BREATH, 8, param, 8, 375}; + abil[eMonstAbil::DAMAGE].gen.dmg = eDamageType::UNBLOCKABLE; + break; + case eMonstAbilTemplate::BREATH_DARKNESS: + abil[eMonstAbil::DAMAGE].gen = {true, eMonstGen::BREATH, 8, param, 8, 375}; + abil[eMonstAbil::DAMAGE].gen.dmg = eDamageType::UNBLOCKABLE; + break; + case eMonstAbilTemplate::BREATH_FOUL: + abil[eMonstAbil::FIELD].gen = {true, eMonstGen::BREATH, 12, PAT_SINGLE, 6, 375}; + abil[eMonstAbil::FIELD].gen.fld = eFieldType::CLOUD_STINK; + break; + case eMonstAbilTemplate::BREATH_SLEEP: + abil[eMonstAbil::FIELD].gen = {true, eMonstGen::BREATH, 0, PAT_RAD2, 8, 750}; + abil[eMonstAbil::FIELD].gen.fld = eFieldType::CLOUD_SLEEP; + break; + case eMonstAbilTemplate::SPIT_ACID: + abil[eMonstAbil::STATUS].gen = {true, eMonstGen::SPIT, 0, 6, 6, 500}; + abil[eMonstAbil::STATUS].gen.stat = eStatus::ACID; + break; + case eMonstAbilTemplate::SHOOTS_WEB: + abil[eMonstAbil::FIELD].gen = {true, eMonstGen::SPIT, 8, PAT_SINGLE, 4, 375}; + abil[eMonstAbil::FIELD].gen.fld = eFieldType::FIELD_WEB; + break; + // Touch abilities + case eMonstAbilTemplate::TOUCH_POISON: + abil[eMonstAbil::STATUS2].gen = {true, eMonstGen::TOUCH, -1, param}; + abil[eMonstAbil::STATUS2].gen.stat = eStatus::POISON; + break; + case eMonstAbilTemplate::TOUCH_ACID: + abil[eMonstAbil::STATUS].gen = {true, eMonstGen::TOUCH, -1, level > 20 ? 4 : 2}; + abil[eMonstAbil::STATUS].gen.stat = eStatus::ACID; + break; + case eMonstAbilTemplate::TOUCH_DISEASE: + abil[eMonstAbil::STATUS].gen = {true, eMonstGen::TOUCH, -1, 6, 667}; + abil[eMonstAbil::STATUS].gen.stat = eStatus::DISEASE; + break; + case eMonstAbilTemplate::TOUCH_WEB: + abil[eMonstAbil::STATUS].gen = {true, eMonstGen::TOUCH, -1, 5}; + abil[eMonstAbil::STATUS].gen.stat = eStatus::WEBS; + break; + case eMonstAbilTemplate::TOUCH_SLEEP: + abil[eMonstAbil::STATUS].gen = {true, eMonstGen::TOUCH, -1, 6}; + abil[eMonstAbil::STATUS].gen.stat = eStatus::ASLEEP; + break; + case eMonstAbilTemplate::TOUCH_DUMB: + abil[eMonstAbil::STATUS].gen = {true, eMonstGen::TOUCH, -1, 2}; + abil[eMonstAbil::STATUS].gen.stat = eStatus::DUMB; + break; + case eMonstAbilTemplate::TOUCH_PARALYSIS: + abil[eMonstAbil::STATUS].gen = {true, eMonstGen::TOUCH, -1, 500}; + abil[eMonstAbil::STATUS].gen.stat = eStatus::PARALYZED; + break; + case eMonstAbilTemplate::TOUCH_PETRIFY: + abil[eMonstAbil::PETRIFY].gen = {true, eMonstGen::TOUCH, -1, 25}; + break; + case eMonstAbilTemplate::TOUCH_DEATH: + abil[eMonstAbil::KILL].gen = {true, eMonstGen::TOUCH, -1, 2, 667}; + break; + case eMonstAbilTemplate::TOUCH_ICY: + case eMonstAbilTemplate::TOUCH_ICY_DRAINING: + abil[eMonstAbil::DAMAGE].gen = {true, eMonstGen::TOUCH, -1, 3, 667}; + abil[eMonstAbil::DAMAGE].gen.dmg = eDamageType::COLD; + if(what == eMonstAbilTemplate::TOUCH_ICY) break; + case eMonstAbilTemplate::TOUCH_XP_DRAIN: + abil[eMonstAbil::DRAIN_XP].gen = {true, eMonstGen::TOUCH, -1, 150}; + break; + case eMonstAbilTemplate::TOUCH_STUN: + abil[eMonstAbil::STUN].gen = {true, eMonstGen::TOUCH, -1, 2, 667}; + abil[eMonstAbil::STUN].gen.stat = eStatus::HASTE_SLOW; + break; + case eMonstAbilTemplate::TOUCH_STEAL_FOOD: + abil[eMonstAbil::STEAL_FOOD].gen = {true, eMonstGen::TOUCH, -1, 10, 667}; + break; + case eMonstAbilTemplate::TOUCH_STEAL_GOLD: + abil[eMonstAbil::STEAL_GOLD].gen = {true, eMonstGen::TOUCH, -1, 10, 667}; + break; + // Misc abilities + case eMonstAbilTemplate::SPLITS: + abil[eMonstAbil::SPLITS].special = {true, 100, 0}; + break; + case eMonstAbilTemplate::MARTYRS_SHIELD: + abil[eMonstAbil::MARTYRS_SHIELD].special = {true, 0, 0}; + break; + case eMonstAbilTemplate::ABSORB_SPELLS: + abil[eMonstAbil::ABSORB_SPELLS].special = {true, 100, 0}; + break; + case eMonstAbilTemplate::SUMMON_5: + abil[eMonstAbil::SUMMON].summon = {true, static_cast(param), 1, 1, 130, 5}; + break; + case eMonstAbilTemplate::SUMMON_20: + abil[eMonstAbil::SUMMON].summon = {true, static_cast(param), 1, 1, 130, 20}; + break; + case eMonstAbilTemplate::SUMMON_50: + abil[eMonstAbil::SUMMON].summon = {true, static_cast(param), 1, 1, 130, 50}; + break; + case eMonstAbilTemplate::SPECIAL: + abil[eMonstAbil::SPECIAL].special = {true, param, 1}; + break; + case eMonstAbilTemplate::DEATH_TRIGGERS: + abil[eMonstAbil::DEATH_TRIGGER].special = {true, param, 0}; + break; + // Radiate abilities + case eMonstAbilTemplate::RADIATE_FIRE: + abil[eMonstAbil::RADIATE].radiate = {true, eFieldType::WALL_FIRE, param}; + break; + case eMonstAbilTemplate::RADIATE_ICE: + abil[eMonstAbil::RADIATE].radiate = {true, eFieldType::WALL_ICE, param}; + break; + case eMonstAbilTemplate::RADIATE_SHOCK: + abil[eMonstAbil::RADIATE].radiate = {true, eFieldType::WALL_FORCE, param}; + break; + case eMonstAbilTemplate::RADIATE_ANTIMAGIC: + abil[eMonstAbil::RADIATE].radiate = {true, eFieldType::FIELD_ANTIMAGIC, param}; + break; + case eMonstAbilTemplate::RADIATE_SLEEP: + abil[eMonstAbil::RADIATE].radiate = {true, eFieldType::CLOUD_SLEEP, param}; + break; + case eMonstAbilTemplate::RADIATE_STINK: + abil[eMonstAbil::RADIATE].radiate = {true, eFieldType::CLOUD_STINK, param}; + break; + case eMonstAbilTemplate::RADIATE_BLADE: + abil[eMonstAbil::RADIATE].radiate = {true, eFieldType::WALL_BLADES, param}; + break; + case eMonstAbilTemplate::RADIATE_WEB: + abil[eMonstAbil::RADIATE].radiate = {true, eFieldType::FIELD_WEB, param}; + break; + // Advanced abilities + case eMonstAbilTemplate::CUSTOM_MISSILE: + abil[eMonstAbil::MISSILE].active = true; + break; + case eMonstAbilTemplate::CUSTOM_DAMAGE: + abil[eMonstAbil::DAMAGE].active = true; + break; + case eMonstAbilTemplate::CUSTOM_STATUS: + abil[eMonstAbil::STATUS].active = true; + break; + case eMonstAbilTemplate::CUSTOM_FIELD: + abil[eMonstAbil::FIELD].active = true; + break; + case eMonstAbilTemplate::CUSTOM_PETRIFY: + abil[eMonstAbil::PETRIFY].active = true; + break; + case eMonstAbilTemplate::CUSTOM_SP_DRAIN: + abil[eMonstAbil::DRAIN_SP].active = true; + break; + case eMonstAbilTemplate::CUSTOM_XP_DRAIN: + abil[eMonstAbil::DRAIN_XP].active = true; + break; + case eMonstAbilTemplate::CUSTOM_KILL: + abil[eMonstAbil::KILL].active = true; + break; + case eMonstAbilTemplate::CUSTOM_STEAL_FOOD: + abil[eMonstAbil::STEAL_FOOD].active = true; + break; + case eMonstAbilTemplate::CUSTOM_STEAL_GOLD: + abil[eMonstAbil::STEAL_GOLD].active = true; + break; + case eMonstAbilTemplate::CUSTOM_STUN: + abil[eMonstAbil::STUN].active = true; + break; + case eMonstAbilTemplate::CUSTOM_STATUS2: + abil[eMonstAbil::STATUS2].active = true; + case eMonstAbilTemplate::CUSTOM_RADIATE: + abil[eMonstAbil::RADIATE].active = true; + case eMonstAbilTemplate::CUSTOM_SUMMON: + abil[eMonstAbil::SUMMON].active = true; + break; + } } cMonster::cMonster(){ @@ -198,293 +494,233 @@ std::istream& operator >> (std::istream& in, eDirection& e) { return in; } -cMonster::cAbility::operator std::string(){ +std::ostream& operator << (std::ostream& out, eFieldType e) { + return out << (int)e; +} + +std::istream& operator >> (std::istream& in, eFieldType& e) { + int i; + in >> i; + if(i >= 0 && i <= 24) + e = (eFieldType)i; + else if(i == 33) e = eFieldType::FIELD_SMASH; + else e = eFieldType::FIELD_DISPEL; + return in; +} + +std::ostream& operator << (std::ostream& out, eDamageType e) { + return out << (int)e; +} + +std::istream& operator >> (std::istream& in, eDamageType& e) { + int i; + in >> i; + if(i >= 0 && i < 8) + e = (eDamageType)i; + else e = eDamageType::MARKED; + return in; +} + +std::ostream& operator << (std::ostream& out, eMonstAbil e) { + return out << (int)e; +} + +std::istream& operator >> (std::istream& in, eMonstAbil& e) { + int i; + in >> i; + if(i > 0 && i <= int(eMonstAbil::SUMMON)) + e = (eMonstAbil)i; + else e = eMonstAbil::NO_ABIL; + return in; +} + +std::ostream& operator << (std::ostream& out, eMonstGen e) { + return out << (int)e; +} + +std::istream& operator >> (std::istream& in, eMonstGen& e) { + int i; + in >> i; + if(i >= 0 && i <= int(eMonstGen::SPIT)) + e = (eMonstGen)i; + else e = eMonstGen::TOUCH; + return in; +} + +std::ostream& operator << (std::ostream& out, eMonstMissile e) { + return out << (int)e; +} + +std::istream& operator >> (std::istream& in, eMonstMissile& e) { + int i; + in >> i; + if(i >= 0 && i <= int(eMonstMissile::BOLT)) + e = (eMonstMissile)i; + else e = eMonstMissile::ARROW; + return in; +} + +std::string uAbility::to_string(eMonstAbil key) const { std::ostringstream sout; short i = 0; - switch(abil){ - case MONST_NO_ABIL: - break; - case MONST_THROWS_DARTS: - sout << "Throws darts (" << extra1 << 'd' << extra2 << ')'; - break; - case MONST_SHOOTS_ARROWS: - sout << "Shoots arrows (" << extra1 << 'd' << extra2 << ')'; - break; - case MONST_THROWS_SPEARS: - sout << "Throws spears (" << extra1 << 'd' << extra2 << ')'; - break; - case MONST_THROWS_ROCKS: - sout << "Throws rocks (" << extra1 << 'd' << extra2 << ')'; - break; - case MONST_THROWS_RAZORDISKS: - sout << "Throws razordisks (" << extra1 << 'd' << extra2 << ')'; - break; - case MONST_GOOD_ARCHER: - sout << "Good archer (" << extra1 << 'd' << extra2 << ')'; - break; - case MONST_SHOOTS_SPINES: - sout << "Shoots spines (" << extra1 << 'd' << extra2 << ')'; - break; - case MONST_THROWS_KNIVES: - sout << "Throws knives (" << extra1 << 'd' << extra2 << ')'; - break; - case MONST_DAMAGE_RAY: - case MONST_DRAIN_XP_DAMAGE_RAY: - case MONST_DAMAGE_TOUCH: - case MONST_DRAIN_XP_DAMAGE_TOUCH: - switch(eDamageType(extra1)) { - case eDamageType::WEAPON: - sout << "Health drain"; + switch(getMonstAbilCategory(key)){ + case eMonstAbilCat::INVALID: break; + case eMonstAbilCat::MISSILE: + switch(missile.type) { + case eMonstMissile::DART: + sout << "Throws darts (" << missile.dice << 'd' << missile.sides << ')'; break; - case eDamageType::FIRE: - sout << "Heat"; + case eMonstMissile::ARROW: + sout << "Shoots arrows (" << missile.dice << 'd' << missile.sides << ')'; break; - case eDamageType::POISON: - sout << "Pain"; + case eMonstMissile::BOLT: + sout << "Shoots bolts (" << missile.dice << 'd' << missile.sides << ')'; break; - case eDamageType::MAGIC: - sout << "Shock"; + case eMonstMissile::SPEAR: + sout << "Throws spears (" << missile.dice << 'd' << missile.sides << ')'; break; - case eDamageType::UNBLOCKABLE: - sout << "Wounding"; + case eMonstMissile::ROCK: + sout << "Throws rocks (" << missile.dice << 'd' << missile.sides << ')'; break; - case eDamageType::COLD: - sout << "Icy"; + case eMonstMissile::RAZORDISK: + sout << "Throws razordisks (" << missile.dice << 'd' << missile.sides << ')'; break; - case eDamageType::UNDEAD: - case eDamageType::DEMON: - sout << "Unholy"; + case eMonstMissile::SPINE: + sout << "Shoots spines (" << missile.dice << 'd' << missile.sides << ')'; break; - default: - sout << "*ERROR INVALID DAMAGE TYPE*"; - } - if(abil == MONST_DRAIN_XP_DAMAGE_RAY || abil == MONST_DRAIN_XP_DAMAGE_TOUCH) - sout << ", draining"; - if(abil == MONST_DAMAGE_RAY || abil == MONST_DRAIN_XP_DAMAGE_RAY) - sout << " ray"; - else sout << " touch"; - break; - case MONST_STATUS_RAY: - case MONST_STATUS_TOUCH: - switch((eStatus)extra1){ - case eStatus::BLESS_CURSE: - sout << "Curse"; - break; - case eStatus::POISON: - sout << "Poison"; - i = 1; - break; - case eStatus::HASTE_SLOW: - sout << "Slowing"; - break; - case eStatus::WEBS: - sout << "Glue"; - i = 1; - break; - case eStatus::DISEASE: - sout << "Infectious"; - i = 1; - break; - case eStatus::DUMB: - sout << "Dumbfounding"; - break; - case eStatus::ASLEEP: - sout << "Sleep"; - break; - case eStatus::PARALYZED: - sout << "Paralysis"; - break; - case eStatus::ACID: - sout << "Acid"; - i = 1; - break; - default: // Poisoned weapon, invulnerable, magic resistance, sanctuary, martyr's shield, or invalid - sout << "*ERROR BAD OR INVALID STATUS TYPE*"; - } - if(abil == MONST_STATUS_RAY) - if(i == 1) sout << " spit"; - else sout << " ray"; - else sout << " touch"; - break; - case MONST_PETRIFY_RAY: - sout << "Petrification ray"; - break; - case MONST_DRAIN_SP_RAY: - sout << "Spell point drain ray"; - break; - case MONST_DRAIN_XP_RAY: - sout << "Experience draining ray"; - break; - case MONST_KILL_RAY: - sout << "Death ray"; - break; - case MONST_STEAL_FOOD_RAY: - sout << "Steals food from afar"; - break; - case MONST_STEAL_GOLD_RAY: - sout << "Steals gold from afar"; - break; - break; - case MONST_PETRIFY_TOUCH: - sout << "Petrification touch"; - break; - case MONST_DRAIN_SP_TOUCH: - sout << "Spell point draining touch"; - break; - case MONST_DRAIN_XP_TOUCH: - sout << "Experience draining touch"; - break; - break; - case MONST_KILL_TOUCH: - sout << "Death touch"; - break; - case MONST_STEAL_FOOD_TOUCH: - sout << "Steals food when hits"; - break; - case MONST_STEAL_GOLD_TOUCH: - sout << "Steals gold when hits"; - break; - case MONST_SUMMON_ONE: - // TODO: Store the name of the summoned monster in the class so it can be used here. -// sout << "Summons " << univ.scenario.scen_monsters[extra1].m_name << "s (" << extra2 <<"% chance)"; - break; - case MONST_SUMMON_TYPE: - sout << "Summons "; - switch(extra1){ - case 0: - sout << "wildlife"; - break; - case 1: - sout << "weak aid"; - break; - case 2: - sout << "strong aid"; - break; - case 3: - sout << "powerful aid"; - break; - case 4: - sout << "friends"; + case eMonstMissile::KNIFE: + sout << "Throws knives (" << missile.dice << 'd' << missile.sides << ')'; break; } - sout << " (" << extra2 << "% chance)"; break; - case MONST_SUMMON_SPECIES: - sout << "Summons "; - switch((eRace)extra1){ - case eRace::HUMAN: - sout << "Humans"; - break; - case eRace::NEPHIL: - sout << "Nephilim"; - break; - case eRace::SLITH: - sout << "Slithzerikai"; - break; - case eRace::VAHNATAI: - sout << "Vahnatai"; - break; - case eRace::REPTILE: - sout << "reptiles"; - break; - case eRace::BEAST: - sout << "beasts"; - break; - case eRace::HUMANOID: - sout << "humanoids"; - break; - case eRace::DEMON: - sout << "demons"; - break; - case eRace::UNDEAD: - sout << "undead"; - break; - case eRace::GIANT: - sout << "giants"; - break; - case eRace::SLIME: - sout << "slimes"; - break; - case eRace::STONE: - sout << "golems"; - break; - case eRace::BUG: - sout << "bugs"; - break; - case eRace::DRAGON: - sout << "Dragons"; - break; - case eRace::MAGICAL: - sout << "magical creatures"; - break; - case eRace::PLANT: - sout << "plants"; - break; - case eRace::BIRD: - sout << "birds"; - break; - default: // Important, Mage, Priest, or invalid - sout << "*ERROR INVALID RACE*"; + case eMonstAbilCat::GENERAL: + if(key == eMonstAbil::FIELD && gen.type == eMonstGen::SPIT && gen.fld == eFieldType::FIELD_WEB) { + sout << "Throws webs"; + } else { + switch(key) { + case eMonstAbil::STUN: sout << "Stunning"; break; + case eMonstAbil::PETRIFY: sout << "Petrifying"; break; + case eMonstAbil::DRAIN_SP: sout << "Spell point drain"; break; + case eMonstAbil::DRAIN_XP: sout << "Experience drain"; break; + case eMonstAbil::KILL: sout << "Death"; break; + case eMonstAbil::STEAL_FOOD: sout << "Steals food"; break; + case eMonstAbil::STEAL_GOLD: sout << "Steals gold!"; break; + case eMonstAbil::FIELD: + switch(gen.fld) { + case eFieldType::CLOUD_SLEEP: sout << "Sleep"; break; + case eFieldType::CLOUD_STINK: sout << "Foul"; break; + case eFieldType::WALL_FIRE: sout << "Fiery"; break; + case eFieldType::WALL_FORCE: sout << "Charged"; break; + case eFieldType::WALL_ICE: sout << "Frosted"; break; + case eFieldType::WALL_BLADES: sout << "Thorny"; break; + case eFieldType::FIELD_ANTIMAGIC: sout << "Null"; break; + case eFieldType::FIELD_WEB: sout << "Web"; break; + case eFieldType::FIELD_QUICKFIRE: sout << "Incendiary"; break; + } + break; + case eMonstAbil::DAMAGE: + switch(gen.dmg) { + case eDamageType::FIRE: sout << "Heat"; break; + case eDamageType::COLD: sout << "Icy"; break; + case eDamageType::MAGIC: sout << "Shock"; break; + case eDamageType::UNBLOCKABLE: sout << "Wounding"; break; + case eDamageType::POISON: sout << "Pain"; break; + case eDamageType::WEAPON: sout << "Stamina drain"; break; + case eDamageType::DEMON: sout << "Unholy"; break; + case eDamageType::UNDEAD: sout << "Necrotic"; break; + } + break; + case eMonstAbil::STATUS: case eMonstAbil::STATUS2: + switch(gen.stat) { + case eStatus::POISON: sout << "Poison"; break; + case eStatus::DISEASE: sout << "Infectious"; break; + case eStatus::DUMB: sout << "Dumbfounding"; break; + case eStatus::WEBS: sout << "Glue"; break; + case eStatus::ASLEEP: sout << "Sleep"; break; + case eStatus::PARALYZED: sout << "Paralysis"; break; + case eStatus::ACID: sout << "Acid"; break; + case eStatus::HASTE_SLOW: sout << "Slowing"; break; + case eStatus::BLESS_CURSE: sout << "Curse"; break; + } + break; + } + switch(gen.type) { + case eMonstGen::RAY: sout << " ray"; break; + case eMonstGen::TOUCH: sout << " touch"; break; + case eMonstGen::GAZE: sout << " gaze"; break; + case eMonstGen::BREATH: sout << " breath"; break; + case eMonstGen::SPIT: sout << " spit"; break; + } + } + if(key == eMonstAbil::DAMAGE) { + sout << " (" << gen.strength << ")"; + } else if(key == eMonstAbil::STATUS || key == eMonstAbil::STATUS2 || key == eMonstAbil::STUN) { + sout << " (" << gen.strength; + switch(gen.type) { + case eMonstGen::RAY: sout << "d6"; break; + case eMonstGen::TOUCH: sout << "d10"; break; + case eMonstGen::GAZE: sout << "d6"; break; + case eMonstGen::BREATH: sout << "d8"; break; + case eMonstGen::SPIT: sout << "d10"; break; + } + sout << ")"; + } else if(key == eMonstAbil::FIELD && gen.strength != PAT_SINGLE) { + sout << " ("; + switch(eSpellPat(gen.strength)) { + case PAT_SINGLE: break; + case PAT_SMSQ: sout << "small "; + case PAT_SQ: sout << "square"; break; + case PAT_OPENSQ: sout << "open square"; break; + case PAT_PLUS: sout << "plus"; break; + case PAT_RAD2: sout << "small circle"; break; + case PAT_RAD3: sout << "big circle"; break; + case PAT_WALL: sout << "line"; break; + } + sout << ")"; + } else if(key == eMonstAbil::KILL) { + sout << " (" << gen.strength * 20 << "d10)"; + } else if(key == eMonstAbil::STEAL_FOOD || key == eMonstAbil::STEAL_GOLD) { + sout << " (" << gen.strength << '-' << gen.strength * 2 << ")"; } - sout << " (" << extra2 << "% chance)"; break; - case MONST_SUMMON_RANDOM: - sout << "Summons aid" << " (" << extra2 << "% chance)"; + case eMonstAbilCat::SPECIAL: + switch(key) { + case eMonstAbil::SPLITS: + sout << "Splits when hit (" << special.extra1 << "% chance)"; + break; + case eMonstAbil::MARTYRS_SHIELD: + sout << "Permanent martyr's shield"; + break; + case eMonstAbil::ABSORB_SPELLS: + sout << "Absorbs spells"; + break; + case eMonstAbil::SPECIAL: + case eMonstAbil::DEATH_TRIGGER: + sout << "Unusual ability"; + break; + } break; - case MONST_MASS_SUMMON: - sout << "Summons aid" << " (" << extra2 << "% chance)"; + case eMonstAbilCat::SUMMON: + // TODO: Somehow look up the name of the monster to be summoned + sout << "Summons " << summon.min << '-' << summon.max << ' ' << "monst-name" << "s (" << summon.chance << "% chance)"; break; - case MONST_SPLITS: - sout << "Splits when hit" << " (" << extra2 << "% chance)"; - break; - case MONST_FIELD_MISSILE: - // TODO: Fill these in - sout << "MONST_FIELD_MISSILE"; - break; - case MONST_MARTYRS_SHIELD: - sout << "Permanent Martyr's shield"; - break; - case MONST_ABSORB_SPELLS: - sout << "Absorbs spells"; - break; - case MONST_INVULNERABLE: - sout << "Invulnerable"; - break; - case MONST_RADIATE: + case eMonstAbilCat::RADIATE: sout << "Radiates "; - switch(extra1){ // TODO: Fill these in + switch(radiate.type) { + case eFieldType::WALL_BLADES: sout << "blade fields"; break; + case eFieldType::WALL_FIRE: sout << "fire fields"; break; + case eFieldType::WALL_FORCE: sout << "shock fields"; break; + case eFieldType::WALL_ICE: sout << "ice fields"; break; + case eFieldType::CLOUD_STINK: sout << "stinking clouds"; break; + case eFieldType::CLOUD_SLEEP: sout << "sleep fields"; break; + case eFieldType::FIELD_ANTIMAGIC: sout << "antimagic fields"; break; + case eFieldType::FIELD_WEB: sout << "webs"; break; } - sout << " (" << extra2 << "% chance)"; - break; - case MONST_CALL_LOCAL_SPECIAL: - case MONST_CALL_GLOBAL_SPECIAL: - sout << "Unusual ability"; break; } return sout.str(); } -std::string cMonster::getAbil1Name() { - return (std::string) abil1; -} - -std::string cMonster::getAbil2Name() { - return (std::string) abil2; -} - -bool cMonster::hasAbil(eMonstAbil what, unsigned short& x1, unsigned short& x2){ - if(abil1.abil == what){ - x1 = abil1.extra1; - x2 = abil1.extra2; - return true; - }else if(abil2.abil == what){ - x1 = abil2.extra1; - x2 = abil2.extra2; - return true; - } - return false; -} - void cMonster::writeTo(std::ostream& file) const { file << "MONSTER " << maybe_quote_string(m_name) << '\n'; file << "LEVEL " << int(level) << '\n'; @@ -497,11 +733,7 @@ void cMonster::writeTo(std::ostream& file) const { file << "MAGE " << int(mu) << '\n'; file << "PRIEST " << int(cl) << '\n'; file << "RACE " << m_type << '\n'; - file << "BREATH " << int(breath_type) << ' ' << int(breath) << '\n'; file << "TREASURE" << int(treasure) << '\n'; - file << "ABIL 1 " << int(spec_skill) << " 0 0\n"; - file << "ABIL 2 " << int(radiate_1) << ' ' << int(radiate_2) << " 0\n"; - file << "POISON " << int(poison) << '\n'; file << "CORPSEITEM " << corpse_item << ' ' << corpse_item_chance << '\n'; file << "IMMUNE " << magic_res << '\t' << fire_res << '\t' << cold_res << '\t' << poison_res << '\n'; file << "SIZE " << int(x_width) << ' ' << int(y_width) << '\n'; @@ -510,6 +742,46 @@ void cMonster::writeTo(std::ostream& file) const { file << "PORTRAIT " << default_facial_pic << '\n'; file << "PICTURE " << picture_num << '\n'; file << "SOUND " << ambient_sound << '\n'; + file << '\f'; + for(auto& abil : abil) { + if(!abil.second.active || abil.first == eMonstAbil::NO_ABIL) continue; + file << "ABIL " << abil.first << '\n'; + switch(getMonstAbilCategory(abil.first)) { + case eMonstAbilCat::INVALID: continue; + case eMonstAbilCat::MISSILE: + file << "TYPE " << abil.second.missile.type << ' ' << abil.second.missile.pic << '\n'; + file << "DAMAGE " << abil.second.missile.dice << ' ' << abil.second.missile.sides << '\n'; + file << "SKILL " << abil.second.missile.skill << '\n'; + file << "RANGE " << abil.second.missile.range << '\n'; + file << "CHANCE " << abil.second.missile.odds << '\n'; + break; + case eMonstAbilCat::GENERAL: + file << "TYPE " << abil.second.gen.type << ' ' << abil.second.gen.pic << '\n'; + file << "DAMAGE " << abil.second.gen.strength << '\n'; + file << "RANGE " << abil.second.gen.range << '\n'; + file << "CHANCE " << abil.second.gen.odds << '\n'; + if(abil.first == eMonstAbil::DAMAGE) + file << "EXTRA " << abil.second.gen.dmg << '\n'; + else if(abil.first == eMonstAbil::FIELD) + file << "EXTRA " << abil.second.gen.fld << '\n'; + else if(abil.first == eMonstAbil::STATUS || abil.first == eMonstAbil::STATUS2 || abil.first == eMonstAbil::STUN) + file << "EXTRA " << abil.second.gen.stat; + break; + case eMonstAbilCat::RADIATE: + file << "TYPE " << abil.second.radiate.type << '\n'; + file << "CHANCE " << abil.second.radiate.chance << '\n'; + break; + case eMonstAbilCat::SUMMON: + file << "TYPE " << abil.second.summon.type << '\n'; + file << "HOWMANY " << abil.second.summon.min << ' ' << abil.second.summon.max << '\n'; + file << "CHANCE " << abil.second.summon.chance << '\n'; + break; + case eMonstAbilCat::SPECIAL: + file << "EXTRA " << abil.second.special.extra1 << ' ' << abil.second.special.extra2 << '\n'; + break; + } + file << '\f'; + } } void cMonster::readFrom(std::istream& file) { @@ -518,10 +790,13 @@ void cMonster::readFrom(std::istream& file) { see_str1 = -1; see_str2 = -1; see_spec = -1; - while(file) { - std::string cur; + std::istringstream bin; + std::string cur; + getline(file, cur, '\f'); + bin.str(cur); + while(bin) { int temp1, temp2, temp3; - getline(file, cur); + getline(bin, cur); std::istringstream line(cur); line >> cur; if(cur == "MONSTER") { @@ -534,17 +809,6 @@ void cMonster::readFrom(std::istream& file) { a[which].dice = temp1; a[which].sides = temp2; a[which].type = temp3; - } else if(cur == "BREATH") { - line >> temp1 >> temp2; - breath_type = temp1; - breath = temp2; - } else if(cur == "ABIL") { - int which; - line >> which >> temp1 >> temp2 >> temp3; - if(which == 1) - spec_skill = temp1; - else if(which == 2) - radiate_1 = temp1, radiate_2 = temp2; } else if(cur == "SIZE") { line >> temp1 >> temp2; x_width = temp1; @@ -584,14 +848,75 @@ void cMonster::readFrom(std::istream& file) { cl = temp1; else if(cur == "TREASURE") treasure = temp1; - else if(cur == "POISON") - poison = temp1; else if(cur == "ATTITUDE") default_attitude = temp1; else if(cur == "SUMMON") summon_type = temp1; } } + while(file) { + getline(file, cur, '\f'); + bin.str(cur); + getline(bin, cur); + std::istringstream line(cur); + line >> cur; + if(cur == "ABIL") { + eMonstAbil key; + uAbility abil; + line >> key; + eMonstAbilCat cat = getMonstAbilCategory(key); + if(cat == eMonstAbilCat::INVALID) continue; + while(bin) { + getline(bin, cur); + line.str(cur); + line >> cur; + if(cur == "TYPE") { + if(cat == eMonstAbilCat::MISSILE) + line >> abil.missile.type >> abil.missile.pic; + else if(cat == eMonstAbilCat::GENERAL) + line >> abil.gen.type >> abil.gen.pic; + else if(cat == eMonstAbilCat::RADIATE) + line >> abil.radiate.type; + else if(cat == eMonstAbilCat::SUMMON) + line >> abil.summon.type; + } else if(cur == "DAMAGE") { + if(cat == eMonstAbilCat::MISSILE) + line >> abil.missile.dice >> abil.missile.sides; + else if(cat == eMonstAbilCat::GENERAL) + line >> abil.gen.strength; + } else if(cur == "SKILL") { + if(cat == eMonstAbilCat::MISSILE) + line >> abil.missile.skill; + } else if(cur == "RANGE") { + if(cat == eMonstAbilCat::MISSILE) + line >> abil.missile.range; + else if(cat == eMonstAbilCat::GENERAL) + line >> abil.gen.range; + } else if(cur == "CHANCE") { + if(cat == eMonstAbilCat::MISSILE) + line >> abil.missile.odds; + else if(cat == eMonstAbilCat::GENERAL) + line >> abil.gen.odds; + else if(cat == eMonstAbilCat::RADIATE) + line >> abil.radiate.chance; + else if(cat == eMonstAbilCat::SUMMON) + line >> abil.summon.chance; + } else if(cur == "EXTRA") { + if(key == eMonstAbil::DAMAGE) + line >> abil.gen.dmg; + else if(key == eMonstAbil::FIELD) + line >> abil.gen.fld; + else if(key == eMonstAbil::STATUS || key == eMonstAbil::STATUS2 || key == eMonstAbil::STUN) + line >> abil.gen.stat; + else if(cat == eMonstAbilCat::SPECIAL) + line >> abil.special.extra1 >> abil.special.extra2; + } else if(cur == "HOWMANY") { + if(cat == eMonstAbilCat::SUMMON) + line >> abil.summon.min >> abil.summon.max; + } + } + } + } } void cCreature::writeTo(std::ostream& file) const { diff --git a/src/classes/monster.h b/src/classes/monster.h index bf1ee9861..7a12096a4 100644 --- a/src/classes/monster.h +++ b/src/classes/monster.h @@ -26,76 +26,31 @@ namespace legacy { class cScenario; class cUniverse; -/* Attack Types */ +enum class eMonstAbilTemplate { + // Non-magical missiles + THROWS_DARTS, SHOOTS_ARROWS, THROWS_SPEARS, THROWS_ROCKS1, THROWS_ROCKS2, THROWS_ROCKS3, + THROWS_RAZORDISKS, THROWS_KNIVES, GOOD_ARCHER, SHOOTS_SPINES, CROSSBOWMAN, SLINGER, + // Magical missiles + RAY_PETRIFY, RAY_SP_DRAIN, RAY_HEAT, RAY_PARALYSIS, + BREATH_FIRE, BREATH_FROST, BREATH_ELECTRICITY, BREATH_DARKNESS, BREATH_FOUL, BREATH_SLEEP, + SPIT_ACID, SHOOTS_WEB, + // Touch abilities + TOUCH_POISON, TOUCH_ACID, TOUCH_DISEASE, TOUCH_WEB, TOUCH_SLEEP, TOUCH_DUMB, TOUCH_PARALYSIS, + TOUCH_PETRIFY, TOUCH_DEATH, TOUCH_XP_DRAIN, TOUCH_ICY, TOUCH_ICY_DRAINING, TOUCH_STUN, TOUCH_STEAL_FOOD, TOUCH_STEAL_GOLD, + // Misc abilities + SPLITS, MARTYRS_SHIELD, ABSORB_SPELLS, SUMMON_5, SUMMON_20, SUMMON_50, SPECIAL, DEATH_TRIGGERS, + // Radiate abilities + RADIATE_FIRE, RADIATE_ICE, RADIATE_SHOCK, RADIATE_ANTIMAGIC, RADIATE_SLEEP, RADIATE_STINK, RADIATE_BLADE, RADIATE_WEB, + // Advanced abilities + CUSTOM_MISSILE, CUSTOM_DAMAGE, CUSTOM_STATUS, CUSTOM_FIELD, CUSTOM_PETRIFY, CUSTOM_SP_DRAIN, CUSTOM_XP_DRAIN, + CUSTOM_KILL, CUSTOM_STEAL_FOOD, CUSTOM_STEAL_GOLD, CUSTOM_STUN, CUSTOM_STATUS2, CUSTOM_RADIATE, CUSTOM_SUMMON, +}; -#define MONSTER_ATTACK_SWINGS 0 -#define MONSTER_ATTACK_CLAWS 1 -#define MONSTER_ATTACK_BITES 2 -#define MONSTER_ATTACK_SLIMES 3 -#define MONSTER_ATTACK_PUNCHES 4 -#define MONSTER_ATTACK_STINGS 5 -#define MONSTER_ATTACK_CLUBS 6 -#define MONSTER_ATTACK_BURNS 7 -#define MONSTER_ATTACK_HARMS 8 -#define MONSTER_ATTACK_STABS 9 +enum class eMonstMelee {SWING, CLAW, BITE, SLIME, PUNCH, STING, CLUB, BURN, HARM, STAB}; +enum class eMonstMissile {DART, ARROW, SPEAR, ROCK, RAZORDISK, SPINE, KNIFE, BOLT}; -#define MONSTER_NO_SPECIAL_ABILITY 0 -#define MONSTER_THROWS_DARTS 1 //1-6 -#define MONSTER_SHOOTS_ARROWS 2 //2-12 -#define MONSTER_THROWS_SPEARS 3 //3-18 -#define MONSTER_THROWS_ROCKS1 4 //4-24 damages -#define MONSTER_THROWS_ROCKS2 5 //5-30 damages -#define MONSTER_THROWS_ROCKS3 6 //6-36 damages -#define MONSTER_THROWS_RAZORDISKS 7 //4-24 -#define MONSTER_PETRIFICATION_RAY 8 -#define MONSTER_SP_DRAIN_RAY 9 //spell points drain ray -#define MONSTER_HEAT_RAY 10 -#define MONSTER_INVISIBLE 11 -#define MONSTER_SPLITS 12 -#define MONSTER_MINDLESS 13 -#define MONSTER_BREATHES_STINKING_CLOUDS 14 -#define MONSTER_ICY_TOUCH 15 -#define MONSTER_XP_DRAINING_TOUCH 16 -#define MONSTER_ICY_AND_DRAINING_TOUCH 17 -#define MONSTER_SLOWING_TOUCH 18 -#define MONSTER_SHOOTS_WEB 19 -#define MONSTER_GOOD_ARCHER 20 //7-42 -#define MONSTER_STEALS_FOOD 21 -#define MONSTER_PERMANENT_MARTYRS_SHIELD 22 -#define MONSTER_PARALYSIS_RAY 23 -#define MONSTER_DUMBFOUNDING_TOUCH 24 -#define MONSTER_DISEASE_TOUCH 25 -#define MONSTER_ABSORB_SPELLS 26 -#define MONSTER_WEB_TOUCH 27 -#define MONSTER_SLEEP_TOUCH 28 -#define MONSTER_PARALYSIS_TOUCH 29 -#define MONSTER_PETRIFICATION_TOUCH 30 -#define MONSTER_ACID_TOUCH 31 -#define MONSTER_BREATHES_SLEEP_CLOUDS 32 -#define MONSTER_ACID_SPIT 33 -#define MONSTER_SHOOTS_SPINES 34 //7-42 -#define MONSTER_DEATH_TOUCH 35 -#define MONSTER_INVULNERABILITY 36 -#define MONSTER_GUARD 37 - -/* Create Monsters/Fields */ - -#define MONSTER_NO_RADIATE 0 -#define MONSTER_RADIATE_FIRE_FIELDS 1 -#define MONSTER_RADIATE_ICE_FIELDS 2 -#define MONSTER_RADIATE_SHOCK_FIELDS 3 -#define MONSTER_RADIATE_ANTIMAGIC_FIELDS 4 -#define MONSTER_RADIATE_SLEEP_FIELDS 5 -#define MONSTER_RADIATE_STINKING_CLOUDS 6 -#define MONSTER_RADIATE_BLADE_FIELDS 7 - -#define MONSTER_SUMMON_5_PERCENT 10 //5 percent chance -#define MONSTER_SUMMON_20_PERCENT 11 //20 percent chance -#define MONSTER_SUMMON_50_PERCENT 12 //50 percent chance - -#define MONSTER_SPECIAL_ACTION 14 -#define MONSTER_DEATH_TRIGGERS 15 //death triggers global special +enum class eMonstGen {RAY, TOUCH, GAZE, BREATH, SPIT}; // Directions! enum eDirection { @@ -117,6 +72,42 @@ inline eDirection& operator++ (eDirection& me, int) { enum eResistType {RESIST_NONE, RESIST_HALF, RESIST_ALL, RESIST_DOUBLE}; +union uAbility { + bool active; + struct { + bool active; + eMonstMissile type; + miss_num_t pic; + int dice, sides, skill, range, odds; + } missile; + struct { + bool active; + eMonstGen type; + miss_num_t pic; + int strength, range, odds; + union { + eDamageType dmg; + eStatus stat; + eFieldType fld; + }; + } gen; + struct { + bool active; + mon_num_t type; + int min, max, len, chance; + } summon; + struct { + bool active; + eFieldType type; + int chance; + } radiate; + struct { + bool active; + int extra1, extra2; + } special; + std::string to_string(eMonstAbil myKey) const; +}; + class cMonster { public: struct cAttack{ @@ -125,11 +116,6 @@ public: operator int() const; cAttack& operator=(int n); }; - struct cAbility{ - eMonstAbil abil; - unsigned short extra1, extra2; - operator std::string(); - }; unsigned char level; std::string m_name; short m_health; @@ -140,20 +126,20 @@ public: unsigned char speed; unsigned char mu; unsigned char cl; - unsigned char breath; - unsigned char breath_type; unsigned char treasure; - unsigned char spec_skill; // TODO: Delete in favour of cAbility - unsigned char poison; + std::map abil; item_num_t corpse_item; short corpse_item_chance; int magic_res : 2; int fire_res : 2; int cold_res : 2; int poison_res : 2; + bool mindless : 1; + bool invuln : 1; + bool invisible : 1; + bool guard : 1; + char : 4; unsigned char x_width,y_width; - unsigned char radiate_1; // TODO: Delete in favour of cAbility - unsigned short radiate_2; // I THINK this is the extra field for the second ability TODO: Delete in favour of cAbility unsigned char default_attitude; unsigned char summon_type; pic_num_t default_facial_pic; @@ -161,13 +147,9 @@ public: str_num_t see_str1, see_str2; snd_num_t see_sound, ambient_sound; // ambient_sound has a chance of being played every move spec_num_t see_spec; -private: - cAbility abil1, abil2; public: - std::string getAbil1Name(); - std::string getAbil2Name(); - bool hasAbil(eMonstAbil what, unsigned short& x1, unsigned short& x2); + void addAbil(eMonstAbilTemplate what, int param = 0); void append(legacy::monster_record_type& old); cMonster(); void writeTo(std::ostream& file) const; @@ -214,7 +196,15 @@ std::ostream& operator << (std::ostream& out, eRace e); std::istream& operator >> (std::istream& in, eRace& e); std::ostream& operator << (std::ostream& out, eMonstAbil e); std::istream& operator >> (std::istream& in, eMonstAbil& e); +std::ostream& operator << (std::ostream& out, eMonstMissile e); +std::istream& operator >> (std::istream& in, eMonstMissile& e); +std::ostream& operator << (std::ostream& out, eMonstGen e); +std::istream& operator >> (std::istream& in, eMonstGen& e); std::ostream& operator << (std::ostream& out, eDirection e); std::istream& operator >> (std::istream& in, eDirection& e); +std::ostream& operator << (std::ostream& out, eDamageType e); +std::istream& operator >> (std::istream& in, eDamageType& e); +std::ostream& operator << (std::ostream& out, eFieldType e); +std::istream& operator >> (std::istream& in, eFieldType& e); std::ostream& operator<<(std::ostream& out, const cMonster::cAttack& att); #endif diff --git a/src/classes/simpletypes.h b/src/classes/simpletypes.h index f9908d864..b5fe5e0c8 100644 --- a/src/classes/simpletypes.h +++ b/src/classes/simpletypes.h @@ -134,54 +134,51 @@ inline bool isStatusNegative(eStatus stat) { /* Special Ability a.k.a spec_skill */ -enum eMonstAbil { - MONST_NO_ABIL = 0, - // Missile abilities (extra1 = number of sided dice; extra2 = number of sides) - MONST_THROWS_DARTS = 10, - MONST_SHOOTS_ARROWS, - MONST_THROWS_SPEARS, - MONST_THROWS_ROCKS, - MONST_THROWS_RAZORDISKS, - MONST_GOOD_ARCHER, - MONST_SHOOTS_SPINES, - MONST_THROWS_KNIVES, - // Ray abilities (extra1 = type of damage / status where applicable) - MONST_DAMAGE_RAY = 20, - MONST_STATUS_RAY, - MONST_PETRIFY_RAY, - MONST_DRAIN_SP_RAY, - MONST_DRAIN_XP_RAY, - MONST_DRAIN_XP_DAMAGE_RAY, - MONST_KILL_RAY, - MONST_STEAL_FOOD_RAY, - MONST_STEAL_GOLD_RAY, - // Touch abilities (extra1 = type of damage / status where applicable) - MONST_DAMAGE_TOUCH = 30, - MONST_STATUS_TOUCH, - MONST_PETRIFY_TOUCH, - MONST_DRAIN_SP_TOUCH, - MONST_DRAIN_XP_TOUCH, - MONST_DRAIN_XP_DAMAGE_TOUCH, - MONST_KILL_TOUCH, - MONST_STEAL_FOOD_TOUCH, - MONST_STEAL_GOLD_TOUCH, - // Summon abilities (extra1 = which monster / type / species; extra2 = % chance) - MONST_SUMMON_ONE = 40, - MONST_SUMMON_TYPE, - MONST_SUMMON_SPECIES, - MONST_SUMMON_RANDOM, - MONST_MASS_SUMMON, - // Misc abilities (extra1 = field / special #; extra2 = % chance for radiate only) - MONST_SPLITS = 50, - MONST_FIELD_MISSILE, - MONST_MARTYRS_SHIELD, - MONST_ABSORB_SPELLS, - MONST_INVULNERABLE, - MONST_RADIATE, - MONST_CALL_LOCAL_SPECIAL, - MONST_CALL_GLOBAL_SPECIAL, +enum class eMonstAbil { + NO_ABIL, + MISSILE, + + DAMAGE, + STATUS, + FIELD, + PETRIFY, + DRAIN_SP, + DRAIN_XP, + KILL, + STEAL_FOOD, + STEAL_GOLD, + STUN, + STATUS2, + + SPLITS, + MARTYRS_SHIELD, + ABSORB_SPELLS, + SPECIAL, + DEATH_TRIGGER, + + RADIATE, + SUMMON, }; +enum class eMonstAbilCat { + INVALID, MISSILE, GENERAL, SUMMON, RADIATE, SPECIAL +}; + +inline eMonstAbilCat getMonstAbilCategory(eMonstAbil what) { + if(what == eMonstAbil::NO_ABIL) + return eMonstAbilCat::SPECIAL; + if(what == eMonstAbil::MISSILE) + return eMonstAbilCat::MISSILE; + if(what >= eMonstAbil::DAMAGE && what <= eMonstAbil::STATUS2) + return eMonstAbilCat::GENERAL; + if(what >= eMonstAbil::SPLITS && what <= eMonstAbil::DEATH_TRIGGER) + return eMonstAbilCat::SPECIAL; + if(what == eMonstAbil::RADIATE) + return eMonstAbilCat::RADIATE; + if(what == eMonstAbil::SUMMON) + return eMonstAbilCat::SUMMON; + return eMonstAbilCat::INVALID; +} /* Terrains Special Properties : scenario.ter_types[i].special */ //complete diff --git a/src/classes/spell.hpp b/src/classes/spell.hpp index e3ef3204d..ed9db5e6b 100644 --- a/src/classes/spell.hpp +++ b/src/classes/spell.hpp @@ -17,6 +17,8 @@ enum eSpellSelect {SELECT_NO, SELECT_ACTIVE, SELECT_ANY}; // This one is meant for indexing a bit field enum eSpellWhen {WHEN_COMBAT = 1, WHEN_TOWN = 2, WHEN_OUTDOORS = 4}; +enum eSpellPat {PAT_SINGLE, PAT_SQ, PAT_SMSQ, PAT_OPENSQ, PAT_RAD2, PAT_RAD3, PAT_PLUS, PAT_WALL}; + class cSpell { static std::map dictionary; friend const cSpell& operator*(eSpell spell_num);