Rewrite the implementation of monster abilities

- Abilities are much more generic, but also much more complex; a set of "templates" is provided to try to make it more accessible
- There is now a possibility for monsters to charm each other
This commit is contained in:
2015-01-14 22:33:59 -05:00
parent f282c06bfa
commit 399572adf6
14 changed files with 1221 additions and 926 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -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<eMonstAbil,uAbility> abil,location source,short target);
void monst_basic_abil(short m_num, std::pair<eMonstAbil,uAbility> 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);

View File

@@ -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;

View File

@@ -357,7 +357,7 @@ static void put_monst_info(cDialog& me, const cCreature& store_m) {
short abil,i;
cPict& pic = dynamic_cast<cPict&>(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<cLed&>(me["magic-res"]).setState(store_m.magic_res == RESIST_HALF ? led_red : led_off);
dynamic_cast<cLed&>(me["magic-imm"]).setState(store_m.magic_res == RESIST_ALL ? led_red : led_off);

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;

View File

@@ -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)

View File

@@ -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;

View File

@@ -39,7 +39,7 @@ void cPopulation::assign(size_t n, const cCreature& other, const cMonster& base,
static_cast<cMonster&>(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;

View File

@@ -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<mon_num_t>(param), 1, 1, 130, 5};
break;
case eMonstAbilTemplate::SUMMON_20:
abil[eMonstAbil::SUMMON].summon = {true, static_cast<mon_num_t>(param), 1, 1, 130, 20};
break;
case eMonstAbilTemplate::SUMMON_50:
abil[eMonstAbil::SUMMON].summon = {true, static_cast<mon_num_t>(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 {

View File

@@ -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<eMonstAbil, uAbility> 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

View File

@@ -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

View File

@@ -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<eSpell,cSpell> dictionary;
friend const cSpell& operator*(eSpell spell_num);