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