Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enemy AI changes: Change weighting of priorities and add special check #2378

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 69 additions & 4 deletions src/game_enemy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "game_battle.h"
#include "game_enemy.h"
#include "game_party.h"
#include "game_enemyparty.h"
#include "game_switches.h"
#include <lcf/reader_util.h>
#include "output.h"
Expand Down Expand Up @@ -218,10 +219,11 @@ const lcf::rpg::EnemyAction* Game_Enemy::ChooseRandomAction() {

int total = 0;
for (auto it = valid.begin(); it != valid.end();) {
if (actions[*it].rating < highest_rating - 9) {
const lcf::rpg::EnemyAction& action = actions[*it];
if (action.rating < highest_rating - 9 || (action.kind == action.Kind_skill && !IsSkillUsableForAI(action.skill_id))) {
it = valid.erase(it);
} else {
total += actions[*it].rating;
total += 10 - (highest_rating - action.rating);
++it;
}
}
Expand All @@ -233,8 +235,9 @@ const lcf::rpg::EnemyAction* Game_Enemy::ChooseRandomAction() {
int which = Rand::GetRandomNumber(0, total - 1);
for (std::vector<int>::const_iterator it = valid.begin(); it != valid.end(); ++it) {
const lcf::rpg::EnemyAction& action = actions[*it];
if (which >= action.rating) {
which -= action.rating;
int weight = 10 - (highest_rating - action.rating);
if (which >= weight) {
which -= weight;
continue;
}

Expand All @@ -244,3 +247,65 @@ const lcf::rpg::EnemyAction* Game_Enemy::ChooseRandomAction() {
return nullptr;
}

bool Game_Enemy::IsSkillUsableForAI(int skill_id) const {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RPG_RT does this by calling a function called RPG_Battler::IsSkillEffectiveOn which does a set of checks on each battler. The logic you have here is close but missing some edge cases. Also I need to do more testing to be sure.

const lcf::rpg::Skill* skill = lcf::ReaderUtil::GetElement(lcf::Data::skills, skill_id);

if (!skill) {
Output::Warning("IsSkillUsable: Invalid skill ID {}", skill_id);
return false;
}

// Skills which targets the user, one ally or all allies have special checks
// while skills which targets one enemy or all enemies are always accepted
if (skill->scope == lcf::rpg::Skill::Scope_self || skill->scope == lcf::rpg::Skill::Scope_ally || skill->scope == lcf::rpg::Skill::Scope_party) {
// Skills which affects at least one stat or the attribute ranks are
// always accepted regardless of power and hit rate
if (skill->affect_hp ||
skill->affect_sp ||
skill->affect_attack ||
skill->affect_defense ||
skill->affect_spirit ||
skill->affect_agility ||
(skill->affect_attr_defence && skill->attribute_effects.size() > 0))
{
return true;
}
// Skills which heals states only are only used if there are targets
// who have at least one state inflicted which are affected by the
// skill
if (!skill->reverse_state_effect && skill->state_effects.size() > 0) {
if (skill->scope == lcf::rpg::Skill::Scope_self) {
for (int i = 0; i < static_cast<int>(skill->state_effects.size()); i++) {
if (!skill->state_effects[i]) {
continue;
}
auto state_id = i + 1;
if (HasState(state_id)) return true;
}
} else {
std::vector<Game_Battler*> deadenemies;
std::vector<Game_Battler*> enemies;

Main_Data::game_enemyparty->GetDeadBattlers(deadenemies);
Main_Data::game_enemyparty->GetActiveBattlers(enemies);

for (int i = 0; i < static_cast<int>(skill->state_effects.size()); i++) {
if (!skill->state_effects[i]) {
continue;
}
auto state_id = i + 1;
if (state_id == lcf::rpg::State::kDeathID) {
if (deadenemies.size() > 0) return true;
} else {
for (auto& enemy: enemies) {
if (enemy->HasState(state_id)) return true;
}
}
}
}
}
return false;
}

return true;
}
8 changes: 8 additions & 0 deletions src/game_enemy.h
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,14 @@ class Game_Enemy final : public Game_Battler
const lcf::rpg::EnemyAction* ChooseRandomAction();
bool IsInParty() const override;

/**
* Checks if the AI wants to use the skill.
*
* @param skill_id ID of skill to check.
* @return true if the AI wants to use that skill.
*/
bool IsSkillUsableForAI(int skill_id) const;

protected:
const lcf::rpg::Enemy* enemy = nullptr;
const lcf::rpg::TroopMember* troop_member = nullptr;
Expand Down
53 changes: 52 additions & 1 deletion src/scene_battle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -618,7 +618,58 @@ void Scene_Battle::CreateEnemyActionSkill(Game_Enemy* enemy, const lcf::rpg::Ene
enemy->SetBattleAlgorithm(std::make_shared<Game_BattleAlgorithm::Skill>(enemy, Main_Data::game_party->GetRandomActiveBattler(), *skill));
break;
case lcf::rpg::Skill::Scope_ally:
enemy->SetBattleAlgorithm(std::make_shared<Game_BattleAlgorithm::Skill>(enemy, Main_Data::game_enemyparty->GetRandomActiveBattler(), *skill));
Game_Battler* preferred_target;
preferred_target = Main_Data::game_enemyparty->GetRandomActiveBattler();
// Check skill for preferred ally
if (skill->affect_hp ||
skill->affect_sp ||
skill->affect_attack ||
skill->affect_defense ||
skill->affect_spirit ||
skill->affect_agility ||
(skill->affect_attr_defence && skill->attribute_effects.size() > 0))
{
// Do not change the preferred target here
} else {
// Choose a preferred target if this skill heals only
// states without affecting anything
if (!skill->reverse_state_effect && skill->state_effects.size() > 0) {
std::vector<Game_Battler*> deadenemies;
std::vector<Game_Battler*> enemies;
std::vector<Game_Battler*> targetpool;
targetpool.clear();

Main_Data::game_enemyparty->GetDeadBattlers(deadenemies);
Main_Data::game_enemyparty->GetActiveBattlers(enemies);

for (int i = 0; i < static_cast<int>(skill->state_effects.size()); i++) {
if (!skill->state_effects[i]) {
continue;
}
auto state_id = i + 1;
if (state_id == lcf::rpg::State::kDeathID) {
for (auto& enemy: deadenemies) {
if (std::find(targetpool.begin(), targetpool.end(), enemy) == targetpool.end()) {
targetpool.push_back(enemy);
}
}
} else {
for (auto& enemy: enemies) {
if (enemy->HasState(state_id)) {
if (std::find(targetpool.begin(), targetpool.end(), enemy) == targetpool.end()) {
targetpool.push_back(enemy);
}
}
}
}
}

if (targetpool.size() > 0) {
preferred_target = targetpool[Rand::GetRandomNumber(0, targetpool.size() - 1)];
}
}
}
enemy->SetBattleAlgorithm(std::make_shared<Game_BattleAlgorithm::Skill>(enemy, preferred_target, *skill));
break;
case lcf::rpg::Skill::Scope_enemies:
enemy->SetBattleAlgorithm(std::make_shared<Game_BattleAlgorithm::Skill>(enemy, Main_Data::game_party.get(), *skill));
Expand Down