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 #include <algorithm>
19 #include <cassert>
20 #include <lcf/data.h>
21 #include "player.h"
22 #include "game_actors.h"
23 #include "game_enemyparty.h"
24 #include "game_message.h"
25 #include "game_party.h"
26 #include "game_switches.h"
27 #include "game_system.h"
28 #include "game_variables.h"
29 #include "game_interpreter_battle.h"
30 #include "game_screen.h"
31 #include "game_pictures.h"
32 #include "battle_animation.h"
33 #include "game_battle.h"
34 #include <lcf/reader_util.h>
35 #include "spriteset_battle.h"
36 #include "output.h"
37 #include "utils.h"
38 #include "rand.h"
39 
40 namespace Game_Battle {
41 	const lcf::rpg::Troop* troop = nullptr;
42 
43 	std::string background_name;
44 
45 	std::unique_ptr<Game_Interpreter_Battle> interpreter;
46 	/** Contains battle related sprites */
47 	std::unique_ptr<Spriteset_Battle> spriteset;
48 
49 	std::unique_ptr<BattleAnimation> animation_actors;
50 	std::unique_ptr<BattleAnimation> animation_enemies;
51 
52 	bool battle_running = false;
53 
54 	struct BattleTest battle_test;
55 }
56 
57 namespace {
58 	int terrain_id;
59 	lcf::rpg::System::BattleCondition battle_cond = lcf::rpg::System::BattleCondition_none;
60 	lcf::rpg::System::BattleFormation battle_form = lcf::rpg::System::BattleFormation_terrain;
61 }
62 
Init(int troop_id)63 void Game_Battle::Init(int troop_id) {
64 	// troop_id is guaranteed to be valid
65 	troop = lcf::ReaderUtil::GetElement(lcf::Data::troops, troop_id);
66 	assert(troop);
67 	Game_Battle::battle_running = true;
68 	Main_Data::game_party->ResetTurns();
69 
70 	Main_Data::game_enemyparty->ResetBattle(troop_id);
71 	Main_Data::game_actors->ResetBattle();
72 
73 	interpreter.reset(new Game_Interpreter_Battle(troop->pages));
74 	spriteset.reset(new Spriteset_Battle(background_name, terrain_id));
75 	spriteset->Update();
76 	animation_actors.reset();
77 	animation_enemies.reset();
78 
79 
80 	for (auto* actor: Main_Data::game_party->GetActors()) {
81 		actor->ResetEquipmentStates(true);
82 	}
83 }
84 
Quit()85 void Game_Battle::Quit() {
86 	if (!IsBattleRunning()) {
87 		return;
88 	}
89 
90 	interpreter.reset();
91 	spriteset.reset();
92 	animation_actors.reset();
93 	animation_enemies.reset();
94 
95 	Game_Battle::battle_running = false;
96 	terrain_id = 0;
97 
98 	std::vector<Game_Battler*> allies;
99 	Main_Data::game_party->GetBattlers(allies);
100 
101 	// Remove conditions which end after battle
102 	for (std::vector<Game_Battler*>::iterator it = allies.begin(); it != allies.end(); it++) {
103 		(*it)->RemoveBattleStates();
104 		(*it)->SetBattleAlgorithm(BattleAlgorithmRef());
105 	}
106 
107 	Main_Data::game_actors->ResetBattle();
108 	Main_Data::game_enemyparty->ResetBattle(0);
109 	Main_Data::game_pictures->OnBattleEnd();
110 }
111 
UpdateAnimation()112 void Game_Battle::UpdateAnimation() {
113 	if (animation_actors) {
114 		animation_actors->Update();
115 		if (animation_actors->IsDone()) {
116 			animation_actors.reset();
117 		}
118 	}
119 	if (animation_enemies) {
120 		animation_enemies->Update();
121 		if (animation_enemies->IsDone()) {
122 			animation_enemies.reset();
123 		}
124 	}
125 }
126 
UpdateGraphics()127 void Game_Battle::UpdateGraphics() {
128 	spriteset->Update();
129 }
130 
CheckWin()131 bool Game_Battle::CheckWin() {
132 	return !Main_Data::game_enemyparty->IsAnyActive();
133 }
134 
CheckLose()135 bool Game_Battle::CheckLose() {
136 	// If there are active characters, but all of them are in a state with Restriction "Do Nothing" and 0% recovery probability (including death), it's game over
137 	// Physical recovery doesn't matter in this case
138 	for (auto& actor : Main_Data::game_party->GetActors()) {
139 		if (!actor->IsHidden() && actor->CanActOrRecoverable()) {
140 			return false;
141 		}
142 	}
143 
144 	return true;
145 }
146 
GetSpriteset()147 Spriteset_Battle& Game_Battle::GetSpriteset() {
148 	assert(spriteset);
149 	return *spriteset;
150 }
151 
ShowBattleAnimation(int animation_id,std::vector<Game_Battler * > targets,bool only_sound,int cutoff,bool invert)152 int Game_Battle::ShowBattleAnimation(int animation_id, std::vector<Game_Battler*> targets, bool only_sound, int cutoff, bool invert) {
153 	const lcf::rpg::Animation* anim = lcf::ReaderUtil::GetElement(lcf::Data::animations, animation_id);
154 	if (!anim) {
155 		Output::Warning("ShowBattleAnimation Many: Invalid animation ID {}", animation_id);
156 		return 0;
157 	}
158 
159 	const auto main_type = targets.empty() ? Game_Battler::Type_Ally : targets.front()->GetType();
160 	std::vector<Game_Battler*> alt_targets;
161 
162 	for (auto iter = targets.begin(); iter != targets.end();) {
163 		if ((*iter)->GetType() == main_type) {
164 			++iter;
165 		} else {
166 			alt_targets.push_back(*iter);
167 			iter = targets.erase(iter);
168 		}
169 	}
170 
171 	auto& main_anim = main_type == Game_Battler::Type_Ally ? animation_actors : animation_enemies;
172 	auto& alt_anim = main_type == Game_Battler::Type_Ally ? animation_enemies : animation_actors;
173 
174 	main_anim.reset(new BattleAnimationBattle(*anim, std::move(targets), only_sound, cutoff, invert));
175 	auto main_frames = main_anim->GetFrames();
176 	main_frames = cutoff >= 0 ? std::min(main_frames, cutoff) : main_frames;
177 
178 	auto alt_frames = 0;
179 	if (!alt_targets.empty()) {
180 		alt_anim.reset(new BattleAnimationBattle(*anim, std::move(alt_targets), only_sound, cutoff, invert));
181 		auto alt_frames = alt_anim->GetFrames();
182 		alt_frames = cutoff >= 0 ? std::min(alt_frames, cutoff) : alt_frames;
183 	}
184 
185 	return std::max(main_frames, alt_frames);
186 }
187 
IsBattleAnimationWaiting()188 bool Game_Battle::IsBattleAnimationWaiting() {
189 	return bool(animation_actors) || bool(animation_enemies);
190 }
191 
UpdateAtbGauges()192 void Game_Battle::UpdateAtbGauges() {
193 	std::vector<Game_Battler*> battlers;
194 	Main_Data::game_enemyparty->GetBattlers(battlers);
195 	Main_Data::game_party->GetBattlers(battlers);
196 
197 	const auto use_2k3e_algo = Player::IsRPG2k3E();
198 
199 	int sum_agi = 0;
200 	for (auto* bat: battlers) {
201 		// RPG_RT uses dead and state restricted battlers to contribute to the sum.
202 		if (!bat->IsHidden()) {
203 			sum_agi += bat->GetAgi();
204 		}
205 	}
206 	sum_agi *= 100;
207 
208 	const int max_atb = Game_Battler::GetMaxAtbGauge();
209 
210 	for (auto* bat: battlers) {
211 		// RPG_RT always updates atb for non-hidden enemies, even if they can't act.
212 		// We don't update ATB for battlers with a pending battle algo
213 		if (!bat->GetBattleAlgorithm() && bat->Exists() && (bat->CanAct() || bat->GetType() == Game_Battler::Type_Enemy))
214 		{
215 			const auto agi = bat->GetAgi();
216 			auto increment = max_atb / (sum_agi / (agi + 1));
217 			if (use_2k3e_algo) {
218 				const auto cur_atb = bat->GetAtbGauge();
219 				const auto multiplier = std::max(1.0, static_cast<double>(275000 - cur_atb) / 55000.0);
220 				increment = Utils::RoundTo<int>(multiplier * increment);
221 			}
222 			bat->IncrementAtbGauge(increment);
223 		}
224 	}
225 }
226 
ChangeBackground(const std::string & name)227 void Game_Battle::ChangeBackground(const std::string& name) {
228 	background_name = name;
229 }
230 
GetBackground()231 const std::string& Game_Battle::GetBackground() {
232 	return background_name;
233 }
234 
GetTurn()235 int Game_Battle::GetTurn() {
236 	return Main_Data::game_party->GetTurns();
237 }
238 
CheckTurns(int turns,int base,int multiple)239 bool Game_Battle::CheckTurns(int turns, int base, int multiple) {
240 	if (multiple == 0) {
241 		return turns >= base && (turns - base) == 0;
242 	}
243 	else {
244 		return turns >= base && (turns - base) % multiple == 0;
245 	}
246 }
247 
GetInterpreter()248 Game_Interpreter& Game_Battle::GetInterpreter() {
249 	assert(interpreter);
250 	return *interpreter;
251 }
252 
GetInterpreterBattle()253 Game_Interpreter_Battle& Game_Battle::GetInterpreterBattle() {
254 	assert(interpreter);
255 	return *interpreter;
256 }
257 
SetTerrainId(int id)258 void Game_Battle::SetTerrainId(int id) {
259 	terrain_id = id;
260 }
261 
GetTerrainId()262 int Game_Battle::GetTerrainId() {
263 	return terrain_id;
264 }
265 
SetBattleCondition(lcf::rpg::System::BattleCondition cond)266 void Game_Battle::SetBattleCondition(lcf::rpg::System::BattleCondition cond) {
267 	battle_cond = cond;
268 }
269 
GetBattleCondition()270 lcf::rpg::System::BattleCondition Game_Battle::GetBattleCondition() {
271 	return battle_cond;
272 }
273 
SetBattleFormation(lcf::rpg::System::BattleFormation form)274 void Game_Battle::SetBattleFormation(lcf::rpg::System::BattleFormation form) {
275 	battle_form = form;
276 }
277 
GetBattleFormation()278 lcf::rpg::System::BattleFormation Game_Battle::GetBattleFormation() {
279 	return battle_form;
280 }
281 
HasDeathHandler()282 bool Game_Battle::HasDeathHandler() {
283 	// RPG Maker Editor always sets both death_handler and death_handler_unused chunks.
284 	// However, RPG_RT will only trigger death handler based on the death_handler chunk.
285 	auto& db = lcf::Data::battlecommands;
286 	return Player::IsRPG2k3() && db.death_handler;
287 }
288 
GetDeathHandlerCommonEvent()289 int Game_Battle::GetDeathHandlerCommonEvent() {
290 	auto& db = lcf::Data::battlecommands;
291 	if (HasDeathHandler()) {
292 		return db.death_event;
293 	}
294 	return 0;
295 }
296 
GetDeathHandlerTeleport()297 TeleportTarget Game_Battle::GetDeathHandlerTeleport() {
298 	auto& db = lcf::Data::battlecommands;
299 	if (HasDeathHandler() && db.death_teleport) {
300 		return TeleportTarget(db.death_teleport_id, db.death_teleport_x, db.death_teleport_y, db.death_teleport_face -1, TeleportTarget::eParallelTeleport);
301 	}
302 	return {};
303 }
304 
GetActiveTroop()305 const lcf::rpg::Troop* Game_Battle::GetActiveTroop() {
306 	return IsBattleRunning() ? troop : nullptr;
307 }
308 
309 static constexpr double grid_tables[4][8][8] = {
310 {
311 // In DynRPG 0x4CC16C
312 	{ 0.5, },
313 	{ 0.0, 1.0, },
314 	{ 0.0, 0.5, 1.0, },
315 	{ 0.0, 0.33, 0.66, 1.0, },
316 	{ 0.0, 0.25, 0.5, 0.75, 1.0, },
317 	{ 0.0, 0.0, 0.5, 0.5, 1.0, 1.0, },
318 	{ 0.0, 0.25, 0.33, 0.5, 0.66, 0.75, 1.0, },
319 	{ 0.0, 0.0, 0.33, 0.33, 0.66, 0.66, 1.0, 1.0, },
320 }, {
321 // In DynRPG 0x4CC36C
322 	{ 0.5, },
323 	{ 0.0, 1.0, },
324 	{ 0.0, 1.0, 0.5, },
325 	{ 0.0, 0.75, 0.25, 1.0, },
326 	{ 0.0, 0.5, 1.0, 0.25, 0.75, },
327 	{ 0.0, 0.4, 0.8, 0.2, 0.6, 1.0, },
328 	{ 0.0, 0.33, 0.66, 1.0, 0.25, 0.5, 1.0, },
329 	{ 0.0, 0.28, 0.56, 0.84, 0.14, 0.42, 0.7, 0.98, }
330 }, {
331 // In DynRPG 0x4CC35C
332 	{ 0.5, },
333 	{ 0.5, 0.5, },
334 	{ 0.0, 0.5, 1.0, },
335 	{ 0.0, 0.0, 1.0, 1.0, },
336 	{ 0.0, 0.33, 0.5, 0.66, 1.0, },
337 	{ 0.0, 0.0, 0.5, 0.5, 1.0, 1.0, },
338 	{ 0.0, 0.25, 0.33, 0.5, 0.66, 0.75, 1.0,},
339 	{ 0.0, 0.0, 0.33, 0.33, 0.66, 0.66, 1.0, 1.0, }
340 }, {
341 // In DynRPG 0x4CC76C
342 	{ },
343 	{ 24, },
344 	{ 48, 48, },
345 	{ 48, 48, },
346 	{ 56, 56, 56, 8, 8, },
347 	{ 56, 56, 56, 8, 8, },
348 	{ 64, 64, 64, 64, 16, 16, 16, },
349 	{ 64, 64, 64, 64, 16, 16, 16, 16 },
350 }};
351 
352 
CalculateBaseGridPosition(int party_idx,int party_size,int table_x,int table_y,lcf::rpg::System::BattleFormation form,int terrain_id)353 Point Game_Battle::CalculateBaseGridPosition(
354 		int party_idx,
355 		int party_size,
356 		int table_x,
357 		int table_y,
358 		lcf::rpg::System::BattleFormation form,
359 		int terrain_id)
360 {
361 	Point pos;
362 
363 	assert(party_idx >= 0);
364 	assert(party_idx < party_size);
365 	assert(party_size <= 8);
366 
367 	int grid_top_y = 112;
368 	double grid_elongation = 392;
369 	double grid_inclination = 16000;
370 	if (terrain_id > 0) {
371 		const auto* terrain = lcf::ReaderUtil::GetElement(lcf::Data::terrains, Game_Battle::GetTerrainId());
372 		if (terrain) {
373 			grid_top_y = terrain->grid_top_y;
374 			grid_elongation = static_cast<double>(terrain->grid_elongation);
375 			grid_inclination = static_cast<double>(terrain->grid_inclination);
376 		}
377 	} else if (form == lcf::rpg::System::BattleFormation_tight) {
378 		grid_top_y = 132;
379 		grid_elongation = 196;
380 		grid_inclination = 24000;
381 	}
382 
383 	const auto tdx = grid_tables[table_x][party_size - 1][party_idx];
384 	const auto tdy = grid_tables[table_y][party_size - 1][party_idx];
385 
386 	pos.x = static_cast<int>((1.0 - tdx) * (grid_inclination / 1000.0));
387 	pos.y = grid_top_y + static_cast<int>(std::sin(grid_elongation / 1000.0) * 120.0 * tdy);
388 
389 	return pos;
390 }
391 
392 
Calculate2k3BattlePosition(const Game_Enemy & enemy)393 Point Game_Battle::Calculate2k3BattlePosition(const Game_Enemy& enemy) {
394 	assert(troop);
395 
396 	const auto terrain_id = Game_Battle::GetTerrainId();
397 	const auto cond = Game_Battle::GetBattleCondition();
398 	const auto form = Game_Battle::GetBattleFormation();
399 	auto* sprite = enemy.GetBattleSprite();
400 
401 	int half_height = 0;
402 	int half_width = 0;
403 	if (sprite) {
404 		half_height = sprite->GetHeight() / 2;
405 		half_width = sprite->GetWidth() / 2;
406 	}
407 
408 	// Manual position case
409 	if (!troop->auto_alignment
410 			&& cond != lcf::rpg::System::BattleCondition_pincers
411 			&& cond != lcf::rpg::System::BattleCondition_surround) {
412 		auto position = enemy.GetOriginalPosition();
413 		if (cond == lcf::rpg::System::BattleCondition_back) {
414 			position.x = 320 - position.x;
415 		}
416 
417 		// RPG_RT only clamps Y position for enemies
418 		position.y = Utils::Clamp(position.y, half_height, 240 - half_height);
419 		return position;
420 	}
421 
422 	// Auto position case
423 
424 	int party_size = 0;
425 	int idx = 0;
426 	bool found_myself = false;
427 	// Position is computed based on this monster's index relative to party size
428 	// If monster is hidden -> use real party idx / real party size
429 	// If monster is visible -> use visible party idx / visible party size
430 	for (auto& e: Main_Data::game_enemyparty->GetEnemies()) {
431 		if (e == &enemy) {
432 			found_myself = true;
433 		}
434 		if (enemy.IsHidden() || !e->IsHidden()) {
435 			party_size += 1;
436 			idx += !(found_myself);
437 		}
438 	}
439 
440 	// RPG_RT has a bug where the pincer table is only used for enemies on terrain battles but not on non terrain battles.
441 	const int table_y_idx = (cond == lcf::rpg::System::BattleCondition_pincers && terrain_id >= 1) ? 2 : 1;
442 	const auto grid = CalculateBaseGridPosition(idx, party_size, 0, table_y_idx, form, terrain_id);
443 	const auto ti = grid_tables[3][party_size - 1][idx];
444 
445 	Point position;
446 
447 	const bool is_odd = (idx & 1);
448 
449 	switch (cond) {
450 		case lcf::rpg::System::BattleCondition_none:
451 		case lcf::rpg::System::BattleCondition_initiative:
452 			position.x = grid.x + ti + half_width;
453 			break;
454 		case lcf::rpg::System::BattleCondition_back:
455 			position.x = 320 - (grid.x + ti + half_width);
456 			break;
457 		case lcf::rpg::System::BattleCondition_surround:
458 			position.x = 160 + (is_odd ? 16 : -16);
459 			break;
460 		case lcf::rpg::System::BattleCondition_pincers:
461 			position.x = grid.x + half_width + 16;
462 			if (!is_odd) {
463 				position.x = 320 - position.x;
464 			}
465 			break;
466 	}
467 
468 	position.y = grid.y - half_height;
469 
470 	// RPG_RT only clamps Y position for enemies
471 	position.y = Utils::Clamp(position.y, half_height, 240 - half_height);
472 
473 	return position;
474 }
475 
Calculate2k3BattlePosition(const Game_Actor & actor)476 Point Game_Battle::Calculate2k3BattlePosition(const Game_Actor& actor) {
477 	assert(troop);
478 
479 	const auto terrain_id = Game_Battle::GetTerrainId();
480 	const auto cond = Game_Battle::GetBattleCondition();
481 	const auto form = Game_Battle::GetBattleFormation();
482 	auto* sprite = actor.GetBattleSprite();
483 
484 	int half_height = 0;
485 	int half_width = 0;
486 	if (sprite) {
487 		half_height = sprite->GetHeight() / 2;
488 		half_width = sprite->GetWidth() / 2;
489 	}
490 
491 	int row_x_offset = 0;
492 	if (actor.GetBattleRow() == Game_Actor::RowType::RowType_front) {
493 		row_x_offset = half_width;
494 	}
495 
496 	// Manual position case
497 	if (lcf::Data::battlecommands.placement == lcf::rpg::BattleCommands::Placement_manual) {
498 		auto position = actor.GetOriginalPosition();
499 
500 		if (cond == lcf::rpg::System::BattleCondition_back) {
501 			position.x = 320 - (position.x + row_x_offset);
502 		} else {
503 			position.x = position.x - row_x_offset;
504 		}
505 
506 		// RPG_RT doesn't clamp Y for actors
507 		position.x = Utils::Clamp(position.x, half_width, 320 - half_width);
508 		return position;
509 	}
510 
511 	const auto idx = Main_Data::game_party->GetActorPositionInParty(actor.GetId());
512 	const auto party_size = Main_Data::game_party->GetBattlerCount();
513 
514 	const int table_y_idx = (cond == lcf::rpg::System::BattleCondition_surround) ? 2 : 0;
515 	const auto grid = CalculateBaseGridPosition(idx, party_size, 0, table_y_idx, form, terrain_id);
516 
517 	Point position;
518 
519 	const bool is_odd = (idx & 1);
520 
521 	switch (cond) {
522 		case lcf::rpg::System::BattleCondition_none:
523 		case lcf::rpg::System::BattleCondition_initiative:
524 			position.x = 320 - (grid.x + half_width + row_x_offset);
525 			break;
526 		case lcf::rpg::System::BattleCondition_back:
527 			position.x = grid.x + 2 * half_width - row_x_offset;
528 			break;
529 		case lcf::rpg::System::BattleCondition_surround:
530 			position.x = grid.x + half_width + row_x_offset;
531 			if (!is_odd) {
532 				position.x = 320 - position.x;
533 			}
534 			break;
535 		case lcf::rpg::System::BattleCondition_pincers:
536 			position.x = 160 + (half_width / 2) - row_x_offset;
537 			break;
538 	}
539 
540 	position.y = grid.y - half_height;
541 
542 	// RPG_RT doesn't clamp Y for actors
543 	position.x = Utils::Clamp(position.x, half_width, 320 - half_width);
544 
545 	return position;
546 }
547 
548