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