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 #include <algorithm>
20 #include "bitmap.h"
21 #include "cache.h"
22 #include "input.h"
23 #include "game_enemyparty.h"
24 #include "game_party.h"
25 #include "game_actor.h"
26 #include "game_system.h"
27 #include "game_battle.h"
28 #include "player.h"
29 #include "font.h"
30 #include "output.h"
31 #include "window_battlestatus.h"
32
Window_BattleStatus(int ix,int iy,int iwidth,int iheight,bool enemy)33 Window_BattleStatus::Window_BattleStatus(int ix, int iy, int iwidth, int iheight, bool enemy) :
34 Window_Selectable(ix, iy, iwidth, iheight), mode(ChoiceMode_All), enemy(enemy) {
35
36 SetBorderX(4);
37
38 SetContents(Bitmap::Create(width - 8, height - 16));
39
40 if (Player::IsRPG2k3() && lcf::Data::battlecommands.window_size == lcf::rpg::BattleCommands::WindowSize_small) {
41 height = 68;
42 menu_item_height = 14;
43 actor_face_height = 17;
44 SetBorderY(5);
45 SetContents(Bitmap::Create(width - 8, height - 10));
46 }
47
48 index = -1;
49
50 if (lcf::Data::battlecommands.battle_type == lcf::rpg::BattleCommands::BattleType_gauge) {
51 // Simulate a borderless window
52 // Doing it this way for gauge style makes the implementation on
53 // scene-side easier
54 border_x = 0;
55 border_y = 0;
56 SetContents(Bitmap::Create(width, height));
57 SetOpacity(0);
58 }
59
60 Refresh();
61 }
62
Refresh()63 void Window_BattleStatus::Refresh() {
64 contents->Clear();
65
66 if (enemy) {
67 item_max = Main_Data::game_enemyparty->GetBattlerCount();
68 }
69 else {
70 item_max = Main_Data::game_party->GetBattlerCount();
71 }
72
73 item_max = std::min(item_max, 4);
74
75 for (int i = 0; i < item_max; i++) {
76 // The party only contains valid battlers
77 const Game_Battler* actor;
78 if (enemy) {
79 actor = &(*Main_Data::game_enemyparty)[i];
80 }
81 else {
82 actor = &(*Main_Data::game_party)[i];
83 }
84
85 if (!enemy && lcf::Data::battlecommands.battle_type == lcf::rpg::BattleCommands::BattleType_gauge) {
86 DrawActorFace(*static_cast<const Game_Actor*>(actor), 80 * i, actor_face_height);
87 }
88 else {
89 int y = menu_item_height / 8 + i * menu_item_height;
90
91 DrawActorName(*actor, 4, y);
92 if (Player::IsRPG2k()) {
93 int hpdigits = (actor->MaxHpValue() >= 1000) ? 4 : 3;
94 int spdigits = (actor->MaxSpValue() >= 1000) ? 4 : 3;
95 DrawActorState(*actor, (hpdigits < 4 && spdigits < 4) ? 86 : 80, y);
96 DrawActorHp(*actor, 178 - hpdigits * 6 - spdigits * 6, y, hpdigits, true);
97 DrawActorSp(*actor, 220 - spdigits * 6, y, spdigits, false);
98 } else {
99 if (lcf::Data::battlecommands.battle_type == lcf::rpg::BattleCommands::BattleType_traditional) {
100 DrawActorState(*actor, 84, y);
101 DrawActorHpValue(*actor, 136 + 4 * 6, y);
102 } else {
103 DrawActorState(*actor, 80, y);
104 }
105 }
106 }
107 }
108
109 RefreshGauge();
110 }
111
RefreshGauge()112 void Window_BattleStatus::RefreshGauge() {
113 if (Player::IsRPG2k3()) {
114 if (lcf::Data::battlecommands.battle_type == lcf::rpg::BattleCommands::BattleType_alternative) {
115 if (lcf::Data::battlecommands.window_size == lcf::rpg::BattleCommands::WindowSize_small) {
116 contents->ClearRect(Rect(192, 0, 45, 58));
117 } else {
118 contents->ClearRect(Rect(192, 0, 45, 64));
119 }
120 }
121
122 for (int i = 0; i < item_max; ++i) {
123 // The party always contains valid battlers
124 Game_Battler* actor;
125 if (enemy) {
126 actor = &(*Main_Data::game_enemyparty)[i];
127 }
128 else {
129 actor = &(*Main_Data::game_party)[i];
130 }
131
132 if (!enemy && lcf::Data::battlecommands.battle_type == lcf::rpg::BattleCommands::BattleType_gauge) {
133 BitmapRef system2 = Cache::System2();
134 if (system2) {
135 // Clear number and gauge drawing area
136 contents->ClearRect(Rect(40 + 80 * i, actor_face_height, 8 * 4, 48));
137
138 // Number clearing removed part of the face, but both, clear and redraw
139 // are needed because some games don't have face graphics that are huge enough
140 // to clear the number area (e.g. Ara Fell)
141 DrawActorFace(*static_cast<const Game_Actor*>(actor), 80 * i, actor_face_height);
142
143 int x = 32 + i * 80;
144 int y = actor_face_height;
145
146 // Left Gauge
147 contents->Blit(x, y, *system2, Rect(0, 32, 16, 48), Opacity::Opaque());
148 x += 16;
149
150 // Center
151 const auto fill_x = x;
152 contents->StretchBlit(Rect(x, y, 25, 48), *system2, Rect(16, 32, 16, 48), Opacity::Opaque());
153 x += 25;
154
155 // Right
156 contents->Blit(x, y, *system2, Rect(32, 32, 16, 48), Opacity::Opaque());
157
158 // HP
159 DrawGaugeSystem2(fill_x, y, actor->GetHp(), actor->GetMaxHp(), 0);
160 // SP
161 DrawGaugeSystem2(fill_x, y + 16, actor->GetSp(), actor->GetMaxSp(), 1);
162 // Gauge
163 DrawGaugeSystem2(fill_x, y + 16 * 2, actor->GetAtbGauge(), actor->GetMaxAtbGauge(), 2);
164
165 // Numbers
166 x = 40 + 80 * i;
167 DrawNumberSystem2(x, y, actor->GetHp());
168 DrawNumberSystem2(x, y + 12 + 4, actor->GetSp());
169 }
170 }
171 else {
172 int y = menu_item_height / 8 + i * menu_item_height;
173
174 if (lcf::Data::battlecommands.battle_type == lcf::rpg::BattleCommands::BattleType_alternative) {
175 // RPG_RT Bug (?): Gauge hidden when selected due to transparency (wrong color when rendering)
176 if (lcf::Data::battlecommands.transparency == lcf::rpg::BattleCommands::Transparency_opaque || (menu_item_height / 8 + index * menu_item_height != y)) {
177 DrawGauge(*actor, 202 - 10, y - 2, lcf::Data::battlecommands.transparency == lcf::rpg::BattleCommands::Transparency_opaque ? 96 : 255);
178 }
179 int hpdigits = (actor->MaxHpValue() >= 1000) ? 4 : 3;
180 int spdigits = (actor->MaxSpValue() >= 1000) ? 4 : 3;
181 DrawActorHp(*actor, 178 - hpdigits * 6 - spdigits * 6, y, hpdigits, true);
182 DrawActorSp(*actor, 220 - spdigits * 6, y, spdigits, false);
183 } else {
184 DrawGauge(*actor, 156, y - 2);
185 }
186 }
187 }
188 }
189 }
190
DrawGaugeSystem2(int x,int y,int cur_value,int max_value,int which)191 void Window_BattleStatus::DrawGaugeSystem2(int x, int y, int cur_value, int max_value, int which) {
192 BitmapRef system2 = Cache::System2();
193 assert(system2);
194
195 if (max_value == 0) {
196 return;
197 }
198
199 int gauge_x;
200 if (cur_value == max_value) {
201 gauge_x = 16;
202 }
203 else {
204 gauge_x = 0;
205 }
206
207 int gauge_width = 25;
208
209 if (max_value > 0) {
210 gauge_width = 25 * cur_value / max_value;
211 }
212
213 contents->StretchBlit(Rect(x, y, gauge_width, 16), *system2, Rect(48 + gauge_x, 32 + 16 * which, 16, 16), Opacity::Opaque());
214 }
215
DrawNumberSystem2(int x,int y,int value)216 void Window_BattleStatus::DrawNumberSystem2(int x, int y, int value) {
217 BitmapRef system2 = Cache::System2();
218 assert(system2);
219
220 bool handle_zero = false;
221
222 if (value >= 1000) {
223 contents->Blit(x, y, *system2, Rect((value / 1000) * 8, 80, 8, 16), Opacity::Opaque());
224 value %= 1000;
225 if (value < 100) {
226 handle_zero = true;
227 }
228 }
229 if (handle_zero || value >= 100) {
230 handle_zero = false;
231 contents->Blit(x + 8, y, *system2, Rect((value / 100) * 8, 80, 8, 16), Opacity::Opaque());
232 value %= 100;
233 if (value < 10) {
234 handle_zero = true;
235 }
236 }
237 if (handle_zero || value >= 10) {
238 contents->Blit(x + 8 * 2, y, *system2, Rect((value / 10) * 8, 80, 8, 16), Opacity::Opaque());
239 value %= 10;
240 }
241
242 contents->Blit(x + 8 * 3, y, *system2, Rect(value * 8, 80, 8, 16), Opacity::Opaque());
243 }
244
ChooseActiveCharacter()245 int Window_BattleStatus::ChooseActiveCharacter() {
246 int old_index = index < 0 ? 0 : index;
247 index = -1;
248 for (int i = 0; i < item_max; i++) {
249 int new_index = (old_index + i) % item_max;
250 if ((*Main_Data::game_party)[new_index].IsAtbGaugeFull()) {
251 index = new_index;
252 return index;
253 }
254 }
255
256 if (index != old_index)
257 UpdateCursorRect();
258
259 return index;
260 }
261
SetChoiceMode(ChoiceMode new_mode)262 void Window_BattleStatus::SetChoiceMode(ChoiceMode new_mode) {
263 mode = new_mode;
264 }
265
Update()266 void Window_BattleStatus::Update() {
267 // Window Selectable update logic skipped on purpose
268 // (breaks up/down-logic)
269 Window_Base::Update();
270
271 int old_item_max = item_max;
272 if (enemy) {
273 item_max = Main_Data::game_enemyparty->GetBattlerCount();
274 } else {
275 item_max = Main_Data::game_party->GetBattlerCount();
276 }
277
278 if (item_max != old_item_max) {
279 Refresh();
280 } else if (Player::IsRPG2k3()) {
281 RefreshGauge();
282 }
283
284 if (active && index >= 0) {
285 if (Input::IsRepeated(Input::DOWN) || Input::IsTriggered(Input::SCROLL_DOWN)) {
286 Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cursor));
287 for (int i = 1; i < item_max; i++) {
288 int new_index = (index + i) % item_max;
289 if (IsChoiceValid((*Main_Data::game_party)[new_index])) {
290 index = new_index;
291 break;
292 }
293 }
294 }
295 if (Input::IsRepeated(Input::UP) || Input::IsTriggered(Input::SCROLL_UP)) {
296 Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cursor));
297 for (int i = item_max - 1; i > 0; i--) {
298 int new_index = (index + i) % item_max;
299 if (IsChoiceValid((*Main_Data::game_party)[new_index])) {
300 index = new_index;
301 break;
302 }
303 }
304 }
305 }
306
307 UpdateCursorRect();
308 }
309
UpdateCursorRect()310 void Window_BattleStatus::UpdateCursorRect() {
311 if (lcf::Data::battlecommands.battle_type == lcf::rpg::BattleCommands::BattleType_gauge) {
312 SetCursorRect(Rect());
313 return;
314 }
315
316 if (index < 0)
317 SetCursorRect(Rect());
318 else
319 SetCursorRect(Rect(0, index * menu_item_height, contents->GetWidth(), menu_item_height));
320 }
321
IsChoiceValid(const Game_Battler & battler) const322 bool Window_BattleStatus::IsChoiceValid(const Game_Battler& battler) const {
323 switch (mode) {
324 case ChoiceMode_All:
325 return true;
326 case ChoiceMode_Alive:
327 return !battler.IsDead();
328 case ChoiceMode_Dead:
329 return battler.IsDead();
330 case ChoiceMode_Ready:
331 return battler.IsAtbGaugeFull();
332 case ChoiceMode_None:
333 return false;
334 default:
335 assert(false && "Invalid Choice");
336 return false;
337 }
338 }
339
RefreshActiveFromValid()340 void Window_BattleStatus::RefreshActiveFromValid() {
341 std::vector<Game_Battler*> battlers;
342 if (enemy) {
343 Main_Data::game_enemyparty->GetBattlers(battlers);
344 } else {
345 Main_Data::game_party->GetBattlers(battlers);
346 }
347
348 for (size_t i = 0; i < battlers.size(); ++i) {
349 auto* battler = battlers[i];
350 if (IsChoiceValid(*battler)) {
351 if (!GetActive() || GetIndex() < 0) {
352 SetIndex(i);
353 SetActive(true);
354 }
355 return;
356 }
357 SetIndex(-1);
358 SetActive(false);
359 }
360 UpdateCursorRect();
361 }
362