Merge pull request #34 from NQNStudios/drain-sp
Improve target selection & fix range check for Drain SP Fix calref#737
This commit is contained in:
@@ -2043,6 +2043,30 @@ void combat_run_monst() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool can_drain_pc(const location& source, const cPlayer& pc, short range) {
|
||||||
|
return pc.main_status == eMainStatus::ALIVE && pc.cur_sp > 4 &&
|
||||||
|
(can_see_light(source,pc.combat_pos,sight_obscurity) < 5) &&
|
||||||
|
(dist(source,pc.combat_pos) <= range);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool can_drain_monst(const location& source, const cCreature& monst, short range) {
|
||||||
|
return monst.is_alive() && monst.mp > 4 &&
|
||||||
|
(can_see_light(source,monst.cur_loc,sight_obscurity) < 5) &&
|
||||||
|
(dist(source,monst.cur_loc) <= range);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get other monsters that are this monster's enemy
|
||||||
|
static std::vector<short> list_enemy_monsters(short m_num) {
|
||||||
|
std::vector<short> enemies;
|
||||||
|
|
||||||
|
for(short i = 0; i < univ.town.monst.size(); ++i){
|
||||||
|
if(i == m_num) continue;
|
||||||
|
if(!univ.town.monst[i].is_friendly(univ.town.monst[m_num])) enemies.push_back(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return enemies;
|
||||||
|
}
|
||||||
|
|
||||||
void do_monster_turn() {
|
void do_monster_turn() {
|
||||||
bool acted_yet, had_monst = false,printed_poison = false,printed_disease = false,printed_acid = false;
|
bool acted_yet, had_monst = false,printed_poison = false,printed_disease = false,printed_acid = false;
|
||||||
bool redraw_not_yet_done = true;
|
bool redraw_not_yet_done = true;
|
||||||
@@ -2286,6 +2310,9 @@ void do_monster_turn() {
|
|||||||
|
|
||||||
// Missile (except basic breath weapons)
|
// Missile (except basic breath weapons)
|
||||||
std::pair<eMonstAbil, uAbility> pick_abil;
|
std::pair<eMonstAbil, uAbility> pick_abil;
|
||||||
|
bool any_have_sp = false;
|
||||||
|
short drain_target;
|
||||||
|
location drain_targ_space;
|
||||||
if(!acted_yet) {
|
if(!acted_yet) {
|
||||||
// Go through all the monster's abilities and try to select one that's useable at this time
|
// Go through all the monster's abilities and try to select one that's useable at this time
|
||||||
for(auto& abil : cur_monst->abil) {
|
for(auto& abil : cur_monst->abil) {
|
||||||
@@ -2299,18 +2326,50 @@ void do_monster_turn() {
|
|||||||
break;
|
break;
|
||||||
pick_abil = abil;
|
pick_abil = abil;
|
||||||
break;
|
break;
|
||||||
|
// check if anyone can be drained, make sure the first target can be drained if drain sp is chosen
|
||||||
|
case eMonstAbil::DRAIN_SP:
|
||||||
|
any_have_sp = can_drain_pc(cur_monst->cur_loc, univ.party[target], abil.second.gen.range);
|
||||||
|
if(!any_have_sp){
|
||||||
|
for(int j = 0; j < 6; ++j){
|
||||||
|
if(j == target) continue;
|
||||||
|
if(can_drain_pc(cur_monst->cur_loc, univ.party[j], abil.second.gen.range)){
|
||||||
|
any_have_sp = true;
|
||||||
|
drain_target = j;
|
||||||
|
drain_targ_space = univ.party[j].combat_pos;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!any_have_sp){
|
||||||
|
std::vector<short> enemy_monst = list_enemy_monsters(i);
|
||||||
|
for(short j : enemy_monst){
|
||||||
|
if(can_drain_monst(cur_monst->cur_loc, univ.town.monst[j], abil.second.gen.range)){
|
||||||
|
any_have_sp = true;
|
||||||
|
drain_target = 100 + j;
|
||||||
|
drain_targ_space = univ.town.monst[j].cur_loc;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fallthrough to next case if any enemies have SP to drain
|
||||||
|
if(!any_have_sp) break;
|
||||||
|
BOOST_FALLTHROUGH;
|
||||||
case eMonstAbil::DAMAGE: case eMonstAbil::STATUS: case eMonstAbil::STATUS2: case eMonstAbil::FIELD:
|
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::PETRIFY: case eMonstAbil::DRAIN_XP: case eMonstAbil::KILL:
|
||||||
case eMonstAbil::STEAL_FOOD: case eMonstAbil::STEAL_GOLD: case eMonstAbil::STUN: case eMonstAbil::DAMAGE2:
|
case eMonstAbil::STEAL_FOOD: case eMonstAbil::STEAL_GOLD: case eMonstAbil::STUN: case eMonstAbil::DAMAGE2:
|
||||||
if(abil.second.gen.type == eMonstGen::TOUCH)
|
if(abil.second.gen.type == eMonstGen::TOUCH)
|
||||||
break; // We're looking for ranged attacks
|
break; // We're looking for ranged attacks
|
||||||
if(dist(cur_monst->cur_loc, targ_space) > abil.second.gen.range)
|
if(abil.first != eMonstAbil::DRAIN_SP && dist(cur_monst->cur_loc, targ_space) > abil.second.gen.range)
|
||||||
break; // Target not in range
|
break; // Target not in range
|
||||||
if(abil.second.gen.type == eMonstGen::SPIT && monst_adjacent(targ_space, i))
|
if(abil.second.gen.type == eMonstGen::SPIT && monst_adjacent(targ_space, i))
|
||||||
break; // Target adjacent; prefer melee attacks
|
break; // Target adjacent; prefer melee attacks
|
||||||
if(get_ran(1,1,1000) >= abil.second.gen.odds)
|
if(get_ran(1,1,1000) >= abil.second.gen.odds)
|
||||||
break;
|
break;
|
||||||
pick_abil = abil;
|
pick_abil = abil;
|
||||||
|
if(abil.first == eMonstAbil::DRAIN_SP){
|
||||||
|
target = drain_target;
|
||||||
|
targ_space = drain_targ_space;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case eMonstAbil::MISSILE_WEB:
|
case eMonstAbil::MISSILE_WEB:
|
||||||
if(dist(cur_monst->cur_loc, targ_space) > abil.second.special.extra1)
|
if(dist(cur_monst->cur_loc, targ_space) > abil.second.special.extra1)
|
||||||
@@ -2712,7 +2771,7 @@ void monster_attack(short who_att,iLiving* target) {
|
|||||||
switch(abil.first) {
|
switch(abil.first) {
|
||||||
case eMonstAbil::STUN: add_string_to_buf(" Stuns!"); break;
|
case eMonstAbil::STUN: add_string_to_buf(" Stuns!"); break;
|
||||||
case eMonstAbil::PETRIFY: add_string_to_buf(" Petrifying touch!"); break;
|
case eMonstAbil::PETRIFY: add_string_to_buf(" Petrifying touch!"); break;
|
||||||
case eMonstAbil::DRAIN_SP: add_string_to_buf(" Drains magic!"); break; // TODO: This has no effect on monsters
|
case eMonstAbil::DRAIN_SP: add_string_to_buf(" Drains magic!"); break;
|
||||||
case eMonstAbil::DRAIN_XP: add_string_to_buf(" Drains life!"); break;
|
case eMonstAbil::DRAIN_XP: add_string_to_buf(" Drains life!"); break;
|
||||||
case eMonstAbil::KILL: add_string_to_buf(" Killing touch!"); break;
|
case eMonstAbil::KILL: add_string_to_buf(" Killing touch!"); break;
|
||||||
case eMonstAbil::STEAL_FOOD:
|
case eMonstAbil::STEAL_FOOD:
|
||||||
@@ -2960,19 +3019,40 @@ void monst_fire_missile(short m_num,short bless,std::pair<eMonstAbil,uAbility> a
|
|||||||
proxy.gen.dmg = eDamageType::FIRE;
|
proxy.gen.dmg = eDamageType::FIRE;
|
||||||
monst_basic_abil(m_num, {eMonstAbil::DAMAGE, proxy}, target);
|
monst_basic_abil(m_num, {eMonstAbil::DAMAGE, proxy}, target);
|
||||||
} else {
|
} else {
|
||||||
if(abil.first == eMonstAbil::DRAIN_SP && pc_target != nullptr && pc_target->cur_sp < 4) {
|
if(abil.first == eMonstAbil::DRAIN_SP &&
|
||||||
// modify target if target has no sp
|
((pc_target != nullptr && pc_target->cur_sp < 4) ||
|
||||||
// TODO: What if it's a monster with no sp?
|
(m_target != nullptr && m_target->mp < 4))) {
|
||||||
|
// modify target if target has no sp.
|
||||||
|
// The first target should ALWAYS have sp, but multiple attacks on the same turn could drain it
|
||||||
|
bool found_new = false;
|
||||||
|
|
||||||
|
// Roll 8 times to pick a PC who has SP
|
||||||
for(short i = 0; i < 8; i++) {
|
for(short i = 0; i < 8; i++) {
|
||||||
int j = get_ran(1,0,5);
|
int j = get_ran(1,0,5);
|
||||||
cPlayer& pc = univ.party[j];
|
cPlayer& pc = univ.party[j];
|
||||||
if(pc.main_status == eMainStatus::ALIVE && pc.cur_sp > 4 &&
|
if(can_drain_pc(source, pc, abil.second.gen.range)) {
|
||||||
(can_see_light(source,pc.combat_pos,sight_obscurity) < 5) && (dist(source,pc.combat_pos) <= 8)) {
|
|
||||||
target = &pc;
|
target = &pc;
|
||||||
targ_space = pc.combat_pos;
|
targ_space = pc.combat_pos;
|
||||||
|
found_new = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(!found_new){
|
||||||
|
std::vector<short> m_enemies = list_enemy_monsters(m_num);
|
||||||
|
if(!m_enemies.empty()){
|
||||||
|
// Roll N + 2 times to pick an enemy monster who has SP, where N is the number of enemy monsters
|
||||||
|
// (This is inefficient but I'm trying to mirror how PC retargeting happens above.)
|
||||||
|
for(short i = 0; i < m_enemies.size() + 2; ++i){
|
||||||
|
int j = get_ran(1,0,m_enemies.size());
|
||||||
|
cCreature& monst = univ.town.monst[j];
|
||||||
|
if(can_drain_monst(source, monst, abil.second.gen.range)) {
|
||||||
|
target = &monst;
|
||||||
|
targ_space = monst.cur_loc;
|
||||||
|
found_new = true; // not necessary, but might as well
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
snd_num_t snd;
|
snd_num_t snd;
|
||||||
int path_type = 0;
|
int path_type = 0;
|
||||||
|
|||||||
@@ -370,14 +370,17 @@ short monst_pick_target(short which_m) {
|
|||||||
univ.town.monst[which_m].target = 6;
|
univ.town.monst[which_m].target = 6;
|
||||||
|
|
||||||
if(is_combat() && !cur_monst->is_friendly()) {
|
if(is_combat() && !cur_monst->is_friendly()) {
|
||||||
|
// First target the last PC who cast a spell
|
||||||
if(spell_caster < 6)
|
if(spell_caster < 6)
|
||||||
if((get_ran(1,1,5) < 5) && (monst_can_see(which_m,univ.party[spell_caster].combat_pos))
|
if((get_ran(1,1,5) < 5) && (monst_can_see(which_m,univ.party[spell_caster].combat_pos))
|
||||||
&& univ.party[spell_caster].main_status == eMainStatus::ALIVE)
|
&& univ.party[spell_caster].main_status == eMainStatus::ALIVE)
|
||||||
return spell_caster;
|
return spell_caster;
|
||||||
|
// Then target the last PC who fired a missile
|
||||||
if(missile_firer < 6)
|
if(missile_firer < 6)
|
||||||
if((get_ran(1,1,5) < 3) && (monst_can_see(which_m,univ.party[missile_firer].combat_pos))
|
if((get_ran(1,1,5) < 3) && (monst_can_see(which_m,univ.party[missile_firer].combat_pos))
|
||||||
&& univ.party[missile_firer].main_status == eMainStatus::ALIVE)
|
&& univ.party[missile_firer].main_status == eMainStatus::ALIVE)
|
||||||
return missile_firer;
|
return missile_firer;
|
||||||
|
// Or, keep target already stored
|
||||||
if(univ.town.monst[which_m].target < 6)
|
if(univ.town.monst[which_m].target < 6)
|
||||||
if((monst_can_see(which_m,univ.party[univ.town.monst[which_m].target].combat_pos))
|
if((monst_can_see(which_m,univ.party[univ.town.monst[which_m].target].combat_pos))
|
||||||
&& univ.party[univ.town.monst[which_m].target].main_status == eMainStatus::ALIVE)
|
&& univ.party[univ.town.monst[which_m].target].main_status == eMainStatus::ALIVE)
|
||||||
|
|||||||
Reference in New Issue
Block a user