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