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

Fixes for skills and attributes #1526

Merged
merged 8 commits into from Dec 11, 2018
4 changes: 2 additions & 2 deletions src/game_actor.cpp
Expand Up @@ -84,7 +84,7 @@ int Game_Actor::GetId() const {
return actor_id;
}

bool Game_Actor::UseItem(int item_id) {
bool Game_Actor::UseItem(int item_id, const Game_Battler* source) {
const RPG::Item* item = ReaderUtil::GetElement(Data::items, item_id);
if (!item) {
Output::Warning("UseItem: Can't use invalid item %d", item_id);
Expand All @@ -110,7 +110,7 @@ bool Game_Actor::UseItem(int item_id) {
return true;
}

return Game_Battler::UseItem(item_id);
return Game_Battler::UseItem(item_id, source);
}

bool Game_Actor::IsItemUsable(int item_id) const {
Expand Down
2 changes: 1 addition & 1 deletion src/game_actor.h
Expand Up @@ -73,7 +73,7 @@ class Game_Actor : public Game_Battler {
* @param item_id ID if item to use
* @return true if item affected anything
*/
bool UseItem(int item_id) override;
bool UseItem(int item_id, const Game_Battler* source) override;

/**
* Checks if the actor is permitted to use the item at all.
Expand Down
24 changes: 3 additions & 21 deletions src/game_battlealgorithm.cpp
Expand Up @@ -625,24 +625,6 @@ Game_Battler* Game_BattleAlgorithm::AlgorithmBase::GetTarget() const {
return *current_target;
}

float Game_BattleAlgorithm::AlgorithmBase::GetAttributeMultiplier(const std::vector<bool>& attributes_set) const {
float multiplier = 0;
int attributes_applied = 0;
for (unsigned int i = 0; i < attributes_set.size(); i++) {
if (attributes_set[i]) {
multiplier += GetTarget()->GetAttributeModifier(i + 1);
attributes_applied++;
}
}

if (attributes_applied > 0) {
multiplier /= (attributes_applied * 100);
return multiplier;
}

return 1.0;
}

void Game_BattleAlgorithm::AlgorithmBase::SetTarget(Game_Battler* target) {
targets.clear();

Expand Down Expand Up @@ -891,7 +873,7 @@ bool Game_BattleAlgorithm::Normal::Execute() {
Output::Warning("Algorithm Normal: Invalid weapon animation ID %d", weapon->animation_id);
}

multiplier = GetAttributeMultiplier(weapon->attribute_set);
multiplier = GetTarget()->GetAttributeMultiplier(weapon->attribute_set);
}

} else {
Expand Down Expand Up @@ -1104,7 +1086,7 @@ bool Game_BattleAlgorithm::Skill::Execute() {
}

if (this->healing) {
float mul = GetAttributeMultiplier(skill.attribute_effects);
float mul = GetTarget()->GetAttributeMultiplier(skill.attribute_effects);
if (mul < 0.5f) {
// Determined via testing, the heal is always at least 50%
mul = 0.5f;
Expand Down Expand Up @@ -1148,7 +1130,7 @@ bool Game_BattleAlgorithm::Skill::Execute() {
effect -= GetTarget()->GetDef() * skill.physical_rate / 40;
effect -= GetTarget()->GetSpi() * skill.magical_rate / 80;
}
effect *= GetAttributeMultiplier(skill.attribute_effects);
effect *= GetTarget()->GetAttributeMultiplier(skill.attribute_effects);

if (effect < 0) {
effect = 0;
Expand Down
1 change: 0 additions & 1 deletion src/game_battlealgorithm.h
Expand Up @@ -371,7 +371,6 @@ class AlgorithmBase {
std::string GetAttributeShiftMessage(const std::string& attribute) const;

void ApplyActionSwitches();
float GetAttributeMultiplier(const std::vector<bool>& attributes_set) const;

void Reset();

Expand Down
98 changes: 83 additions & 15 deletions src/game_battler.cpp
Expand Up @@ -162,6 +162,36 @@ int Game_Battler::GetAttributeRate(int attribute_id, int rate) const {
return 0;
}

float Game_Battler::GetAttributeMultiplier(const std::vector<bool>& attributes_set) const {
constexpr auto min_mod = std::numeric_limits<int>::min();
int physical = min_mod;
int magical = min_mod;

float multiplier = 0;
int attributes_applied = 0;
for (unsigned int i = 0; i < attributes_set.size(); i++) {
if (attributes_set[i]) {
auto* attr = ReaderUtil::GetElement(Data::attributes, i + 1);
if (attr) {
if (attr->type == RPG::Attribute::Type_physical) {
physical = std::max(physical, GetAttributeModifier(i + 1));
} else {
magical = std::max(magical, GetAttributeModifier(i + 1));
}
}
}
}

if (physical == min_mod) {
physical = 100;
}
if (magical == min_mod) {
magical = 100;
}

return float(physical * magical) / 10000.0;
}

bool Game_Battler::IsSkillUsable(int skill_id) const {
const RPG::Skill* skill = ReaderUtil::GetElement(Data::skills, skill_id);

Expand Down Expand Up @@ -203,7 +233,7 @@ bool Game_Battler::IsSkillUsable(int skill_id) const {
return true;
}

bool Game_Battler::UseItem(int item_id) {
bool Game_Battler::UseItem(int item_id, const Game_Battler* source) {
const RPG::Item* item = ReaderUtil::GetElement(Data::items, item_id);
if (!item) {
Output::Warning("UseItem: Can't use item with invalid ID %d", item_id);
Expand Down Expand Up @@ -252,27 +282,36 @@ bool Game_Battler::UseItem(int item_id) {
return true;
}

switch (item->type) {
case RPG::Item::Type_weapon:
case RPG::Item::Type_shield:
case RPG::Item::Type_armor:
case RPG::Item::Type_helmet:
case RPG::Item::Type_accessory:
return item->use_skill && UseSkill(item->skill_id);
case RPG::Item::Type_special:
return UseSkill(item->skill_id);
bool do_skill = (item->type == RPG::Item::Type_special)
|| (item->use_skill && (
item->type == RPG::Item::Type_weapon
|| item->type == RPG::Item::Type_shield
|| item->type == RPG::Item::Type_armor
|| item->type == RPG::Item::Type_helmet
|| item->type == RPG::Item::Type_accessory
)
);

if (do_skill) {
auto* skill = ReaderUtil::GetElement(Data::skills, item->skill_id);
if (skill == nullptr) {
Output::Warning("UseItem: Can't use item %d skill with invalid ID %d", item->ID, item->skill_id);
return false;
}
UseSkill(item->skill_id, source);
}

return false;
}

bool Game_Battler::UseSkill(int skill_id) {
bool Game_Battler::UseSkill(int skill_id, const Game_Battler* source) {
const RPG::Skill* skill = ReaderUtil::GetElement(Data::skills, skill_id);
if (!skill) {
Output::Warning("UseSkill: Can't use skill with invalid ID %d", skill_id);
return false;
}

bool cure_hp_percentage = false;
bool was_used = false;

if (skill->type == RPG::Skill::Type_normal || skill->type >= RPG::Skill::Type_subskill) {
Expand All @@ -285,6 +324,26 @@ bool Game_Battler::UseSkill(int skill_id) {
return false;
}

// Calculate effect:
float mul = GetAttributeMultiplier(skill->attribute_effects);
if (mul < 0.5f) {
// Determined via testing, the heal is always at least 50%
mul = 0.5f;
}

int effect = skill->power;
if (source != nullptr) {
effect += source->GetAtk() * skill->physical_rate / 20 +
source->GetSpi() * skill->magical_rate / 40;
}
effect *= mul;

effect += (effect * Utils::GetRandomNumber(-skill->variance * 10, skill->variance * 10) / 100);

if (effect < 0)
effect = 0;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I really don't like to see this logic duplicated here and in the battle code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We should refactor this skill and item effect logic in some common place. I will do this only after battle PRs are merged.

// Cure states
for (int i = 0; i < (int)skill->state_effects.size(); i++) {
if (skill->state_effects[i]) {
if (skill->state_effect) {
Expand All @@ -294,19 +353,28 @@ bool Game_Battler::UseSkill(int skill_id) {
else {
was_used |= HasState(Data::states[i].ID);
RemoveState(Data::states[i].ID);

// If Death is cured and HP is not selected, we set a bool so it later heals HP percentage
if (i == 0 && !skill->affect_hp) {
cure_hp_percentage = true;
}
}
}
}

// Skills only increase hp and sp outside of battle
if (skill->power > 0 && skill->affect_hp && !HasFullHp() && !IsDead()) {
if (effect > 0 && skill->affect_hp && !HasFullHp() && !IsDead()) {
was_used = true;
ChangeHp(effect);
}
else if (effect > 0 && cure_hp_percentage) {
was_used = true;
ChangeHp(skill->power);
ChangeHp(GetMaxHp() * effect / 100);
}

if (skill->power > 0 && skill->affect_sp && !HasFullSp() && !IsDead()) {
if (effect > 0 && skill->affect_sp && !HasFullSp() && !IsDead()) {
was_used = true;
ChangeSp(skill->power);
ChangeSp(effect);
}

} else if (skill->type == RPG::Skill::Type_teleport || skill->type == RPG::Skill::Type_escape) {
Expand Down
13 changes: 11 additions & 2 deletions src/game_battler.h
Expand Up @@ -166,6 +166,14 @@ class Game_Battler {
*/
virtual int GetAttributeModifier(int attribute_id) const = 0;

/**
* Gets the final effect multiplier when a skill/attack is targeting this battler.
*
* @param attributes set for the incoming action
* @return effect multiplier
*/
float GetAttributeMultiplier(const std::vector<bool>& attributes_set) const;

/**
* Gets the characters name
*
Expand Down Expand Up @@ -344,7 +352,7 @@ class Game_Battler {
* @param item_id ID if item to use
* @return true if item affected anything
*/
virtual bool UseItem(int item_id);
virtual bool UseItem(int item_id, const Game_Battler* source);

/**
* Applies the effects of a skill.
Expand All @@ -353,9 +361,10 @@ class Game_Battler {
* Does not reduce the MP, use Game_Party->UseSkill for this.
*
* @param skill_id ID of skill to use
* @param source battler who threw the skill
* @return true if skill affected anything
*/
virtual bool UseSkill(int skill_id);
virtual bool UseSkill(int skill_id, const Game_Battler* source);

/**
* Calculates the Skill costs including all modifiers.
Expand Down