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 
18 // Headers
19 #define _USE_MATH_DEFINES
20 #include <cmath>
21 #include <algorithm>
22 #include <lcf/data.h>
23 #include <lcf/rpg/enemy.h>
24 #include "game_battle.h"
25 #include "game_enemy.h"
26 #include "game_party.h"
27 #include "game_switches.h"
28 #include <lcf/reader_util.h>
29 #include "output.h"
30 #include "utils.h"
31 #include "player.h"
32 #include "attribute.h"
33 #include "rand.h"
34 
Game_Enemy(const lcf::rpg::TroopMember * member)35 Game_Enemy::Game_Enemy(const lcf::rpg::TroopMember* member)
36 	: troop_member(member)
37 {
38 	if (troop_member == nullptr) {
39 		return;
40 	}
41 	Transform(troop_member->enemy_id);
42 
43 	hp = GetMaxHp();
44 	sp = GetMaxSp();
45 	ResetBattle();
46 
47 	SetHidden(troop_member->invisible);
48 	SetBattlePosition(GetOriginalPosition());
49 }
50 
MaxHpValue() const51 int Game_Enemy::MaxHpValue() const {
52 	auto& val = lcf::Data::system.easyrpg_max_enemy_hp;
53 	if (val == -1) {
54 		return Player::IsRPG2k() ? 9999 : 99999;
55 	}
56 	return val;
57 }
58 
MaxSpValue() const59 int Game_Enemy::MaxSpValue() const {
60 	auto& val = lcf::Data::system.easyrpg_max_enemy_sp;
61 	if (val == -1) {
62 		return 9999;
63 	}
64 	return val;
65 }
66 
MaxStatBattleValue() const67 int Game_Enemy::MaxStatBattleValue() const {
68 	auto& val = lcf::Data::system.easyrpg_max_stat_battle_value;
69 	if (val == -1) {
70 		return 9999;
71 	}
72 	return val;
73 }
74 
MaxStatBaseValue() const75 int Game_Enemy::MaxStatBaseValue() const {
76 	auto& val = lcf::Data::system.easyrpg_max_stat_base_value;
77 	if (val == -1) {
78 		return 999;
79 	}
80 	return val;
81 }
82 
GetStateProbability(int state_id) const83 int Game_Enemy::GetStateProbability(int state_id) const {
84 	int rate = 1; // Enemies have only B as the default state rank
85 
86 	if (state_id >= 1 && state_id <= (int)enemy->state_ranks.size()) {
87 		rate = enemy->state_ranks[state_id - 1];
88 	}
89 
90 	return GetStateRate(state_id, rate);
91 }
92 
GetBaseAttributeRate(int attribute_id) const93 int Game_Enemy::GetBaseAttributeRate(int attribute_id) const {
94 	int rate = 2; // C - default
95 
96 	if (attribute_id >= 1 && attribute_id <= (int)enemy->attribute_ranks.size()) {
97 		rate = enemy->attribute_ranks[attribute_id - 1];
98 	}
99 
100 	return rate;
101 }
102 
SetHp(int _hp)103 int Game_Enemy::SetHp(int _hp) {
104 	hp = Utils::Clamp(_hp, 0, GetMaxHp());
105 	return hp;
106 }
107 
SetSp(int _sp)108 int Game_Enemy::SetSp(int _sp) {
109 	sp = Utils::Clamp(_sp, 0, GetMaxSp());
110 	return sp;
111 }
112 
GetOriginalPosition() const113 Point Game_Enemy::GetOriginalPosition() const {
114 	return { troop_member->x, troop_member->y };
115 }
116 
makeDummyEnemy()117 static lcf::rpg::Enemy makeDummyEnemy() {
118 	lcf::rpg::Enemy enemy;
119 	enemy.ID = 1;
120 	return enemy;
121 }
122 
Transform(int new_enemy_id)123 void Game_Enemy::Transform(int new_enemy_id) {
124 	enemy = lcf::ReaderUtil::GetElement(lcf::Data::enemies, new_enemy_id);
125 
126 	if (!enemy) {
127 		// Some games (e.g. Battle 5 in Embric) have invalid monsters in the battle.
128 		// This case will fail in RPG Maker and the game will exit with an error message.
129 		// Create a warning instead and continue the battle.
130 		Output::Warning("Invalid enemy ID {}", new_enemy_id);
131 		// This generates an invisible monster with 0 HP
132 		static auto dummy = makeDummyEnemy();
133 		enemy = &dummy;
134 	}
135 
136 	auto* sprite = GetEnemyBattleSprite();
137 	if (sprite) {
138 		sprite->Refresh();
139 	}
140 }
141 
GetHitChance(Weapon) const142 int Game_Enemy::GetHitChance(Weapon) const {
143 	return enemy->miss ? 70 : 90;
144 }
145 
GetCriticalHitChance(Weapon) const146 float Game_Enemy::GetCriticalHitChance(Weapon) const {
147 	return enemy->critical_hit ? (1.0f / enemy->critical_hit_chance) : 0.0f;
148 }
149 
GetFlyingOffset() const150 int Game_Enemy::GetFlyingOffset() const {
151 	// 2k does not support flying, albeit mentioned in the help file
152 	if (!Player::IsRPG2k3() || !IsFlying()) {
153 		return 0;
154 	}
155 
156 	const auto frame = GetBattleFrameCounter();
157 	auto offset = Utils::RoundTo<int>(std::sin(2 * M_PI * static_cast<double>(frame) / 256.0) * 4.0);
158 	return offset;
159 }
160 
UpdateBattle()161 void Game_Enemy::UpdateBattle() {
162 	if (blink_timer > 0) --blink_timer;
163 	if (death_timer > 0) --death_timer;
164 	if (explode_timer > 0) --explode_timer;
165 
166 	Game_Battler::UpdateBattle();
167 }
168 
ResetBattle()169 void Game_Enemy::ResetBattle() {
170 	Game_Battler::ResetBattle();
171 
172 	blink_timer = 0;
173 	death_timer = 0;
174 	explode_timer = 0;
175 }
176