1 /*
2 * This file is part of EasyRPG Player.
3 *
4 * EasyRPG Player is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * EasyRPG Player is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with EasyRPG Player. If not, see <http://www.gnu.org/licenses/>.
16 */
17 #include "algo.h"
18 #include "game_battler.h"
19 #include "game_actor.h"
20 #include "game_enemy.h"
21 #include "game_system.h"
22 #include "main_data.h"
23 #include "game_player.h"
24 #include "game_targets.h"
25 #include "game_battle.h"
26 #include "attribute.h"
27 #include "player.h"
28 #include "rand.h"
29 #include <lcf/rpg/skill.h>
30 #include <lcf/reader_util.h>
31
32 #include <algorithm>
33
34 namespace Algo {
35
IsRowAdjusted(lcf::rpg::SaveActor::RowType row,lcf::rpg::System::BattleCondition cond,bool offense)36 bool IsRowAdjusted(lcf::rpg::SaveActor::RowType row, lcf::rpg::System::BattleCondition cond, bool offense) {
37 return (cond == lcf::rpg::System::BattleCondition_surround
38 || (row != offense
39 && (cond == lcf::rpg::System::BattleCondition_none || cond == lcf::rpg::System::BattleCondition_initiative))
40 || (row == offense
41 && (cond == lcf::rpg::System::BattleCondition_back))
42 );
43 }
44
IsRowAdjusted(const Game_Battler & battler,lcf::rpg::System::BattleCondition cond,bool offense,bool allow_enemy)45 bool IsRowAdjusted(const Game_Battler& battler, lcf::rpg::System::BattleCondition cond, bool offense, bool allow_enemy) {
46 lcf::rpg::SaveActor::RowType row = lcf::rpg::SaveActor::RowType_front;
47 if (battler.GetType() == Game_Battler::Type_Ally) {
48 row = static_cast<const Game_Actor&>(battler).GetBattleRow();
49 }
50
51 if (battler.GetType() == Game_Battler::Type_Ally || allow_enemy) {
52 return IsRowAdjusted(row, cond, offense);
53 }
54 return false;
55 }
56
CalcToHitAgiAdjustment(int to_hit,const Game_Battler & source,const Game_Battler & target,Game_Battler::Weapon weapon)57 static int CalcToHitAgiAdjustment(int to_hit, const Game_Battler& source, const Game_Battler& target, Game_Battler::Weapon weapon) {
58 // NOTE: RPG_RT 2k3 has a bug where if the source is an actor with a selected weapon, the agi
59 // calculation calls a bespoke function which doesn't consider states which can cause half or double AGI.
60 const float src_agi = std::max(1, source.GetAgi(weapon));
61 const float tgt_agi = target.GetAgi();
62
63 return 100 - (100 - to_hit) * (1.0f + (tgt_agi / src_agi - 1.0f) / 2.0f);
64 }
65
CalcNormalAttackToHit(const Game_Battler & source,const Game_Battler & target,Game_Battler::Weapon weapon,lcf::rpg::System::BattleCondition cond,bool emulate_2k3_enemy_row_bug)66 int CalcNormalAttackToHit(const Game_Battler &source,
67 const Game_Battler &target,
68 Game_Battler::Weapon weapon,
69 lcf::rpg::System::BattleCondition cond,
70 bool emulate_2k3_enemy_row_bug) {
71 auto to_hit = source.GetHitChance(weapon);
72
73 // If target has rm2k3 state which grants 100% dodge.
74 if (target.EvadesAllPhysicalAttacks()) {
75 return 0;
76 }
77
78 // If target has Restriction "do_nothing", the attack always hits
79 if (!target.CanAct()) {
80 return 100;
81 }
82
83 // Modify hit chance for each state the source has
84 to_hit = (to_hit * source.GetHitChanceModifierFromStates()) / 100;
85
86 // Stop here if attacker ignores evasion.
87 if (source.GetType() == Game_Battler::Type_Ally
88 && static_cast<const Game_Actor&>(source).AttackIgnoresEvasion(weapon)) {
89 return to_hit;
90 }
91
92 // AGI adjustment.
93 to_hit = CalcToHitAgiAdjustment(to_hit, source, target, weapon);
94
95 // If target has physical dodge evasion:
96 if (target.GetType() == Game_Battler::Type_Ally
97 && static_cast<const Game_Actor&>(target).HasPhysicalEvasionUp()) {
98 to_hit -= 25;
99 }
100
101 // Defender row adjustment
102 if (Player::IsRPG2k3() && IsRowAdjusted(target, cond, false, emulate_2k3_enemy_row_bug)) {
103 to_hit -= 25;
104 }
105
106 return to_hit;
107 }
108
109
CalcSkillToHit(const Game_Battler & source,const Game_Battler & target,const lcf::rpg::Skill & skill)110 int CalcSkillToHit(const Game_Battler& source, const Game_Battler& target, const lcf::rpg::Skill& skill) {
111 auto to_hit = skill.hit;
112
113 // RPG_RT BUG: rm2k3 editor doesn't let you set the failure message for skills, and so you can't make them physical type anymore.
114 // Despite that, RPG_RT still checks the flag and run the below code?
115 if (skill.failure_message != 3 || SkillTargetsAllies(skill)) {
116 return to_hit;
117 }
118
119 // RPG_RT BUG: RPG_RT 2k3 does not check for "EvadesAllPhysicaAttacks() states here
120
121 // If target has Restriction "do_nothing", the attack always hits
122 if (!target.CanAct()) {
123 return 100;
124 }
125
126 // Modify hit chance for each state the source has
127 to_hit = (to_hit * source.GetHitChanceModifierFromStates()) / 100;
128
129 // Stop here if attacker ignores evasion.
130 if (source.GetType() == Game_Battler::Type_Ally
131 && static_cast<const Game_Actor&>(source).AttackIgnoresEvasion(Game_Battler::WeaponAll)) {
132 return to_hit;
133 }
134
135 // AGI adjustment.
136 to_hit = CalcToHitAgiAdjustment(to_hit, source, target, Game_Battler::WeaponAll);
137
138 // If target has physical dodge evasion:
139 if (target.GetType() == Game_Battler::Type_Ally
140 && static_cast<const Game_Actor&>(target).HasPhysicalEvasionUp()) {
141 to_hit -= 25;
142 }
143
144 return to_hit;
145 }
146
CalcCriticalHitChance(const Game_Battler & source,const Game_Battler & target,Game_Battler::Weapon weapon)147 int CalcCriticalHitChance(const Game_Battler& source, const Game_Battler& target, Game_Battler::Weapon weapon) {
148 auto crit_chance = static_cast<int>(source.GetCriticalHitChance(weapon) * 100.0);
149 if (target.GetType() == Game_Battler::Type_Ally && static_cast<const Game_Actor&>(target).PreventsCritical()) {
150 crit_chance = 0;
151 }
152 if (source.GetType() == target.GetType()) {
153 crit_chance = 0;
154 }
155 return crit_chance;
156 }
157
VarianceAdjustEffect(int base,int var)158 int VarianceAdjustEffect(int base, int var) {
159 // FIXME: RPG_RT 2k3 doesn't apply variance if negative attribute flips damage
160 if (var > 0 && (base > 0 || Player::IsLegacy())) {
161 int adj = std::max(1, var * base / 10);
162 return base + Rand::GetRandomNumber(0, adj) - adj / 2;
163 }
164 return base;
165 }
166
AdjustDamageForDefend(int dmg,const Game_Battler & target)167 int AdjustDamageForDefend(int dmg, const Game_Battler& target) {
168 if (target.IsDefending()) {
169 dmg /= 2;
170 if (target.HasStrongDefense()) {
171 dmg /= 2;
172 }
173 }
174 return dmg;
175 }
176
CalcNormalAttackEffect(const Game_Battler & source,const Game_Battler & target,Game_Battler::Weapon weapon,bool is_critical_hit,bool is_charged,bool apply_variance,lcf::rpg::System::BattleCondition cond,bool emulate_2k3_enemy_row_bug)177 int CalcNormalAttackEffect(const Game_Battler& source,
178 const Game_Battler& target,
179 Game_Battler::Weapon weapon,
180 bool is_critical_hit,
181 bool is_charged,
182 bool apply_variance,
183 lcf::rpg::System::BattleCondition cond,
184 bool emulate_2k3_enemy_row_bug)
185 {
186 const auto atk = source.GetAtk(weapon);
187 const auto def = target.GetDef();
188
189 // Base damage
190 auto dmg = std::max(0, atk / 2 - def / 4);
191
192 // Attacker row adjustment
193 if (Player::IsRPG2k3() && IsRowAdjusted(source, cond, true, false)) {
194 dmg = 125 * dmg / 100;
195 }
196
197 // Attacker weapon attribute adjustment
198 dmg = Attribute::ApplyAttributeNormalAttackMultiplier(dmg, source, target, weapon);
199
200 // Defender row adjustment
201 if (Player::IsRPG2k3() && IsRowAdjusted(target, cond, false, emulate_2k3_enemy_row_bug)) {
202 dmg = 75 * dmg / 100;
203 }
204
205 // Critical and charge adjustment
206 if (is_critical_hit) {
207 dmg *= 3;
208 } else if (is_charged) {
209 dmg *= 2;
210 }
211
212 if (apply_variance) {
213 dmg = VarianceAdjustEffect(dmg, 4);
214 }
215
216 return dmg;
217 }
218
CalcSkillEffect(const Game_Battler & source,const Game_Battler & target,const lcf::rpg::Skill & skill,bool apply_variance)219 int CalcSkillEffect(const Game_Battler& source,
220 const Game_Battler& target,
221 const lcf::rpg::Skill& skill,
222 bool apply_variance) {
223
224 auto effect = skill.power;
225 effect += skill.physical_rate * source.GetAtk() / 20;
226 effect += skill.magical_rate * source.GetSpi() / 40;
227
228 if (SkillTargetsEnemies(skill) && !skill.ignore_defense) {
229 effect -= skill.physical_rate * target.GetDef() / 40;
230 effect -= skill.magical_rate * target.GetSpi() / 80;
231 }
232
233 effect = std::max<int>(0, effect);
234
235 effect = Attribute::ApplyAttributeSkillMultiplier(effect, target, skill);
236
237 if (apply_variance) {
238 effect = VarianceAdjustEffect(effect, skill.variance);
239 }
240
241 return effect;
242 }
243
CalcSelfDestructEffect(const Game_Battler & source,const Game_Battler & target,bool apply_variance)244 int CalcSelfDestructEffect(const Game_Battler& source,
245 const Game_Battler& target,
246 bool apply_variance) {
247
248 auto effect = source.GetAtk() - target.GetDef() / 2;
249 effect = std::max<int>(0, effect);
250
251 if (apply_variance) {
252 effect = VarianceAdjustEffect(effect, 4);
253 }
254
255 return effect;
256 }
257
CalcSkillCost(const lcf::rpg::Skill & skill,int max_sp,bool half_sp_cost)258 int CalcSkillCost(const lcf::rpg::Skill& skill, int max_sp, bool half_sp_cost) {
259 const auto div = half_sp_cost ? 2 : 1;
260 return (Player::IsRPG2k3() && skill.sp_type == lcf::rpg::Skill::SpType_percent)
261 ? max_sp * skill.sp_percent / 100 / div
262 : (skill.sp_cost + static_cast<int>(half_sp_cost)) / div;
263 }
264
IsSkillUsable(const lcf::rpg::Skill & skill,bool require_states_persist)265 bool IsSkillUsable(const lcf::rpg::Skill& skill,
266 bool require_states_persist)
267 {
268 const auto in_battle = Game_Battle::IsBattleRunning();
269
270 if (skill.type == lcf::rpg::Skill::Type_escape) {
271 return !in_battle && Main_Data::game_system->GetAllowEscape() && Main_Data::game_targets->HasEscapeTarget() && !Main_Data::game_player->IsFlying();
272 }
273
274 if (skill.type == lcf::rpg::Skill::Type_teleport) {
275 return !in_battle && Main_Data::game_system->GetAllowTeleport() && Main_Data::game_targets->HasTeleportTargets() && !Main_Data::game_player->IsFlying();
276 }
277
278 if (skill.type == lcf::rpg::Skill::Type_switch) {
279 return in_battle ? skill.occasion_battle : skill.occasion_field;
280 }
281
282 if (in_battle) {
283 return true;
284 }
285
286 if (SkillTargetsEnemies(skill)) {
287 return false;
288 }
289
290 if (skill.affect_hp || skill.affect_sp) {
291 return true;
292 }
293
294 bool affects_state = false;
295 for (int i = 0; i < static_cast<int>(skill.state_effects.size()); ++i) {
296 const bool inflict = skill.state_effects[i];
297 if (inflict) {
298 const auto* state = lcf::ReaderUtil::GetElement(lcf::Data::states, i + 1);
299 if (state && (!require_states_persist || state->type == lcf::rpg::State::Persistence_persists)) {
300 affects_state = true;
301 break;
302 }
303 }
304 }
305
306 return affects_state;
307 }
308
GetNumberOfAttacks(int actor_id,const lcf::rpg::Item & weapon)309 int GetNumberOfAttacks(int actor_id, const lcf::rpg::Item& weapon) {
310 assert(weapon.type == lcf::rpg::Item::Type_weapon);
311 int hits = weapon.dual_attack ? 2 : 1;
312 if (Player::IsRPG2k3()) {
313 auto& cba = weapon.animation_data;
314 if (actor_id >= 1 && actor_id <= static_cast<int>(cba.size())) {
315 int cba_hits = cba[actor_id - 1].attacks + 1;
316 hits *= cba_hits;
317 }
318 }
319 return hits;
320 }
321
322 } // namespace Algo
323