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 "audio.h"
20 #include "game_character.h"
21 #include "game_map.h"
22 #include "game_player.h"
23 #include "game_switches.h"
24 #include "game_system.h"
25 #include "input.h"
26 #include "main_data.h"
27 #include "game_message.h"
28 #include "drawable.h"
29 #include "player.h"
30 #include "utils.h"
31 #include "util_macro.h"
32 #include "output.h"
33 #include "rand.h"
34 #include <cmath>
35 #include <cassert>
36
Game_Character(Type type,lcf::rpg::SaveMapEventBase * d)37 Game_Character::Game_Character(Type type, lcf::rpg::SaveMapEventBase* d) :
38 _type(type), _data(d)
39 {
40 }
41
~Game_Character()42 Game_Character::~Game_Character() {
43 }
44
SanitizeData(StringView name)45 void Game_Character::SanitizeData(StringView name) {
46 SanitizeMoveRoute(name, data()->move_route, data()->move_route_index, "move_route_index");
47 }
48
SanitizeMoveRoute(StringView name,const lcf::rpg::MoveRoute & mr,int32_t & idx,StringView chunk_name)49 void Game_Character::SanitizeMoveRoute(StringView name, const lcf::rpg::MoveRoute& mr, int32_t& idx, StringView chunk_name) {
50 const auto n = static_cast<int32_t>(mr.move_commands.size());
51 if (idx < 0 || idx > n) {
52 idx = n;
53 Output::Warning("{} {}: Save Data invalid {}={}. Fixing ...", TypeToStr(_type), name, chunk_name, idx);
54 }
55 }
56
MoveTo(int map_id,int x,int y)57 void Game_Character::MoveTo(int map_id, int x, int y) {
58 data()->map_id = map_id;
59 // RPG_RT does not round the position for this function.
60 SetX(x);
61 SetY(y);
62 SetRemainingStep(0);
63 }
64
GetJumpHeight() const65 int Game_Character::GetJumpHeight() const {
66 if (IsJumping()) {
67 int jump_height = (GetRemainingStep() > SCREEN_TILE_SIZE / 2 ? SCREEN_TILE_SIZE - GetRemainingStep() : GetRemainingStep()) / 8;
68 return (jump_height < 5 ? jump_height * 2 : jump_height < 13 ? jump_height + 4 : 16);
69 }
70 return 0;
71 }
72
GetScreenX(bool apply_shift) const73 int Game_Character::GetScreenX(bool apply_shift) const {
74 int x = GetSpriteX() / TILE_SIZE - Game_Map::GetDisplayX() / TILE_SIZE + TILE_SIZE;
75
76 if (Game_Map::LoopHorizontal()) {
77 x = Utils::PositiveModulo(x, Game_Map::GetWidth() * TILE_SIZE);
78 }
79 x -= TILE_SIZE / 2;
80
81 if (apply_shift) {
82 x += Game_Map::GetWidth() * TILE_SIZE;
83 }
84
85 return x;
86 }
87
GetScreenY(bool apply_shift,bool apply_jump) const88 int Game_Character::GetScreenY(bool apply_shift, bool apply_jump) const {
89 int y = GetSpriteY() / TILE_SIZE - Game_Map::GetDisplayY() / TILE_SIZE + TILE_SIZE;
90
91 if (apply_jump) {
92 y -= GetJumpHeight();
93 }
94
95 if (Game_Map::LoopVertical()) {
96 y = Utils::PositiveModulo(y, Game_Map::GetHeight() * TILE_SIZE);
97 }
98
99 if (apply_shift) {
100 y += Game_Map::GetHeight() * TILE_SIZE;
101 }
102
103 return y;
104 }
105
GetScreenZ(bool apply_shift) const106 int Game_Character::GetScreenZ(bool apply_shift) const {
107 int z = 0;
108
109 if (IsFlying()) {
110 z = Priority_EventsFlying;
111 } else if (GetLayer() == lcf::rpg::EventPage::Layers_same) {
112 z = Priority_Player;
113 } else if (GetLayer() == lcf::rpg::EventPage::Layers_below) {
114 z = Priority_EventsBelow;
115 } else if (GetLayer() == lcf::rpg::EventPage::Layers_above) {
116 z = Priority_EventsAbove;
117 }
118
119 // For events on the screen, this should be inside a 0-40 range
120 z += GetScreenY(apply_shift, false) >> 3;
121
122 return z;
123 }
124
Update()125 void Game_Character::Update() {
126 if (!IsActive() || IsProcessed()) {
127 return;
128 }
129 SetProcessed(true);
130
131 if (IsStopping()) {
132 this->UpdateNextMovementAction();
133 }
134 UpdateFlash();
135
136 if (IsStopping()) {
137 if (GetStopCount() == 0 || IsMoveRouteOverwritten() ||
138 ((Main_Data::game_system->GetMessageContinueEvents() || !Game_Map::GetInterpreter().IsRunning()) && !IsPaused())) {
139 SetStopCount(GetStopCount() + 1);
140 }
141 } else if (IsJumping()) {
142 static const int jump_speed[] = {8, 12, 16, 24, 32, 64};
143 auto amount = jump_speed[GetMoveSpeed() -1 ];
144 this->UpdateMovement(amount);
145 } else {
146 int amount = 1 << (1 + GetMoveSpeed());
147 this->UpdateMovement(amount);
148 }
149
150 this->UpdateAnimation();
151 }
152
UpdateMovement(int amount)153 void Game_Character::UpdateMovement(int amount) {
154 SetRemainingStep(GetRemainingStep() - amount);
155 if (GetRemainingStep() <= 0) {
156 SetRemainingStep(0);
157 SetJumping(false);
158
159 auto& move_route = GetMoveRoute();
160 if (IsMoveRouteOverwritten() && GetMoveRouteIndex() >= static_cast<int>(move_route.move_commands.size())) {
161 SetMoveRouteRepeated(true);
162 SetMoveRouteIndex(0);
163 if (!move_route.repeat) {
164 // If the last command of a move route is a move or jump,
165 // RPG_RT cancels the entire move route immediately.
166 CancelMoveRoute();
167 }
168 }
169 }
170
171 SetStopCount(0);
172 }
173
UpdateAnimation()174 void Game_Character::UpdateAnimation() {
175 const auto speed = Utils::Clamp(GetMoveSpeed(), 1, 6);
176
177 if (IsSpinning()) {
178 const auto limit = GetSpinAnimFrames(speed);
179
180 IncAnimCount();
181
182 if (GetAnimCount() >= limit) {
183 SetFacing((GetFacing() + 1) % 4);
184 SetAnimCount(0);
185 }
186 return;
187 }
188
189 if (IsAnimPaused() || IsJumping()) {
190 ResetAnimation();
191 return;
192 }
193
194 if (!IsAnimated()) {
195 return;
196 }
197
198 const auto stationary_limit = GetStationaryAnimFrames(speed);
199 const auto continuous_limit = GetContinuousAnimFrames(speed);
200
201 if (IsContinuous()
202 || GetStopCount() == 0
203 || data()->anim_frame == lcf::rpg::EventPage::Frame_left || data()->anim_frame == lcf::rpg::EventPage::Frame_right
204 || GetAnimCount() < stationary_limit - 1) {
205 IncAnimCount();
206 }
207
208 if (GetAnimCount() >= continuous_limit
209 || (GetStopCount() == 0 && GetAnimCount() >= stationary_limit)) {
210 IncAnimFrame();
211 return;
212 }
213 }
214
UpdateFlash()215 void Game_Character::UpdateFlash() {
216 Flash::Update(data()->flash_current_level, data()->flash_time_left);
217 }
218
UpdateMoveRoute(int32_t & current_index,const lcf::rpg::MoveRoute & current_route,bool is_overwrite)219 void Game_Character::UpdateMoveRoute(int32_t& current_index, const lcf::rpg::MoveRoute& current_route, bool is_overwrite) {
220 if (current_route.move_commands.empty()) {
221 return;
222 }
223
224 if (is_overwrite && !IsMoveRouteOverwritten()) {
225 return;
226 }
227
228 const auto num_commands = static_cast<int>(current_route.move_commands.size());
229 // Invalid index could occur from a corrupted save game.
230 // Player, Vehicle, and Event all check for and fix this, but we still assert here in
231 // case any bug causes this to happen still.
232 assert(current_index >= 0);
233 assert(current_index <= num_commands);
234
235 const auto start_index = current_index;
236
237 while (true) {
238 if (!IsStopping() || IsStopCountActive()) {
239 return;
240 }
241
242 //Move route is finished
243 if (current_index >= num_commands) {
244 if (is_overwrite) {
245 SetMoveRouteRepeated(true);
246 }
247 if (!current_route.repeat) {
248 if (is_overwrite) {
249 CancelMoveRoute();
250 }
251 return;
252 }
253 current_index = 0;
254 if (current_index == start_index) {
255 return;
256 }
257 }
258
259 using Code = lcf::rpg::MoveCommand::Code;
260 const auto& move_command = current_route.move_commands[current_index];
261 const auto prev_direction = GetDirection();
262 const auto prev_facing = GetFacing();
263 const auto saved_index = current_index;
264 const auto cmd = static_cast<Code>(move_command.command_id);
265
266 if (cmd >= Code::move_up && cmd <= Code::move_forward) {
267 switch (cmd) {
268 case Code::move_up:
269 case Code::move_right:
270 case Code::move_down:
271 case Code::move_left:
272 case Code::move_upright:
273 case Code::move_downright:
274 case Code::move_downleft:
275 case Code::move_upleft:
276 SetDirection(static_cast<Game_Character::Direction>(cmd));
277 break;
278 case Code::move_random:
279 TurnRandom();
280 break;
281 case Code::move_towards_hero:
282 TurnTowardHero();
283 break;
284 case Code::move_away_from_hero:
285 TurnAwayFromHero();
286 break;
287 case Code::move_forward:
288 break;
289 default:
290 break;
291 }
292 Move(GetDirection());
293
294 if (IsStopping()) {
295 // Move failed
296 if (current_route.skippable) {
297 SetDirection(prev_direction);
298 SetFacing(prev_facing);
299 } else {
300 return;
301 }
302 }
303 if (cmd == Code::move_forward) {
304 SetFacing(prev_facing);
305 }
306
307 SetMaxStopCountForStep();
308 } else if (cmd >= Code::face_up && cmd <= Code::face_away_from_hero) {
309 switch (cmd) {
310 case Code::face_up:
311 SetDirection(Up);
312 break;
313 case Code::face_right:
314 SetDirection(Right);
315 break;
316 case Code::face_down:
317 SetDirection(Down);
318 break;
319 case Code::face_left:
320 SetDirection(Left);
321 break;
322 case Code::turn_90_degree_right:
323 Turn90DegreeRight();
324 break;
325 case Code::turn_90_degree_left:
326 Turn90DegreeLeft();
327 break;
328 case Code::turn_180_degree:
329 Turn180Degree();
330 break;
331 case Code::turn_90_degree_random:
332 Turn90DegreeLeftOrRight();
333 break;
334 case Code::face_random_direction:
335 TurnRandom();
336 break;
337 case Code::face_hero:
338 TurnTowardHero();
339 break;
340 case Code::face_away_from_hero:
341 TurnAwayFromHero();
342 break;
343 default:
344 break;
345 }
346 SetFacing(GetDirection());
347 SetMaxStopCountForTurn();
348 SetStopCount(0);
349 } else {
350 switch (cmd) {
351 case Code::wait:
352 SetMaxStopCountForWait();
353 SetStopCount(0);
354 break;
355 case Code::begin_jump:
356 if (!BeginMoveRouteJump(current_index, current_route)) {
357 // Jump failed
358 if (current_route.skippable) {
359 SetDirection(prev_direction);
360 SetFacing(prev_facing);
361 } else {
362 current_index = saved_index;
363 return;
364 }
365 }
366 break;
367 case Code::end_jump:
368 break;
369 case Code::lock_facing:
370 SetFacingLocked(true);
371 break;
372 case Code::unlock_facing:
373 SetFacingLocked(false);
374 break;
375 case Code::increase_movement_speed:
376 SetMoveSpeed(min(GetMoveSpeed() + 1, 6));
377 break;
378 case Code::decrease_movement_speed:
379 SetMoveSpeed(max(GetMoveSpeed() - 1, 1));
380 break;
381 case Code::increase_movement_frequence:
382 SetMoveFrequency(min(GetMoveFrequency() + 1, 8));
383 break;
384 case Code::decrease_movement_frequence:
385 SetMoveFrequency(max(GetMoveFrequency() - 1, 1));
386 break;
387 case Code::switch_on: // Parameter A: Switch to turn on
388 Main_Data::game_switches->Set(move_command.parameter_a, true);
389 ++current_index; // In case the current_index is already 0 ...
390 Game_Map::SetNeedRefresh(true);
391 Game_Map::Refresh();
392 // If page refresh has reset the current move route, abort now.
393 if (current_index == 0) {
394 return;
395 }
396 --current_index;
397 break;
398 case Code::switch_off: // Parameter A: Switch to turn off
399 Main_Data::game_switches->Set(move_command.parameter_a, false);
400 ++current_index; // In case the current_index is already 0 ...
401 Game_Map::SetNeedRefresh(true);
402 Game_Map::Refresh();
403 // If page refresh has reset the current move route, abort now.
404 if (current_index == 0) {
405 return;
406 }
407 --current_index;
408 break;
409 case Code::change_graphic: // String: File, Parameter A: index
410 MoveRouteSetSpriteGraphic(ToString(move_command.parameter_string), move_command.parameter_a);
411 break;
412 case Code::play_sound_effect: // String: File, Parameters: Volume, Tempo, Balance
413 if (move_command.parameter_string != "(OFF)" && move_command.parameter_string != "(Brak)") {
414 lcf::rpg::Sound sound;
415 sound.name = ToString(move_command.parameter_string);
416 sound.volume = move_command.parameter_a;
417 sound.tempo = move_command.parameter_b;
418 sound.balance = move_command.parameter_c;
419
420 Main_Data::game_system->SePlay(sound);
421 }
422 break;
423 case Code::walk_everywhere_on:
424 SetThrough(true);
425 data()->route_through = true;
426 break;
427 case Code::walk_everywhere_off:
428 SetThrough(false);
429 data()->route_through = false;
430 break;
431 case Code::stop_animation:
432 SetAnimPaused(true);
433 break;
434 case Code::start_animation:
435 SetAnimPaused(false);
436 break;
437 case Code::increase_transp:
438 SetTransparency(GetTransparency() + 1);
439 break;
440 case Code::decrease_transp:
441 SetTransparency(GetTransparency() - 1);
442 break;
443 default:
444 break;
445 }
446 }
447 ++current_index;
448
449 if (current_index == start_index) {
450 return;
451 }
452 } // while (true)
453 }
454
455
MakeWay(int from_x,int from_y,int to_x,int to_y)456 bool Game_Character::MakeWay(int from_x, int from_y, int to_x, int to_y) {
457 return Game_Map::MakeWay(*this, from_x, from_y, to_x, to_y);
458 }
459
Move(int dir)460 bool Game_Character::Move(int dir) {
461 if (!IsStopping()) {
462 return true;
463 }
464
465 bool move_success = false;
466
467 SetDirection(dir);
468 UpdateFacing();
469
470 const auto x = GetX();
471 const auto y = GetY();
472 const auto dx = GetDxFromDirection(dir);
473 const auto dy = GetDyFromDirection(dir);
474
475 if (dx && dy) {
476 // For diagonal movement, RPG_RT trys vert -> horiz and if that fails, then horiz -> vert.
477 move_success = (MakeWay(x, y, x, y + dy) && MakeWay(x, y + dy, x + dx, y + dy))
478 || (MakeWay(x, y, x + dx, y) && MakeWay(x + dx, y, x + dx, y + dy));
479 } else if (dx) {
480 move_success = MakeWay(x, y, x + dx, y);
481 } else if (dy) {
482 move_success = MakeWay(x, y, x, y + dy);
483 }
484
485 if (!move_success) {
486 return false;
487 }
488
489 const auto new_x = Game_Map::RoundX(x + dx);
490 const auto new_y = Game_Map::RoundY(y + dy);
491
492 SetX(new_x);
493 SetY(new_y);
494 SetRemainingStep(SCREEN_TILE_SIZE);
495
496 return true;
497 }
498
Turn90DegreeLeft()499 void Game_Character::Turn90DegreeLeft() {
500 SetDirection(GetDirection90DegreeLeft(GetDirection()));
501 }
502
Turn90DegreeRight()503 void Game_Character::Turn90DegreeRight() {
504 SetDirection(GetDirection90DegreeRight(GetDirection()));
505 }
506
Turn180Degree()507 void Game_Character::Turn180Degree() {
508 SetDirection(GetDirection180Degree(GetDirection()));
509 }
510
Turn90DegreeLeftOrRight()511 void Game_Character::Turn90DegreeLeftOrRight() {
512 if (Rand::ChanceOf(1,2)) {
513 Turn90DegreeLeft();
514 } else {
515 Turn90DegreeRight();
516 }
517 }
518
GetDirectionToHero()519 int Game_Character::GetDirectionToHero() {
520 int sx = DistanceXfromPlayer();
521 int sy = DistanceYfromPlayer();
522
523 if ( std::abs(sx) > std::abs(sy) ) {
524 return (sx > 0) ? Left : Right;
525 } else {
526 return (sy > 0) ? Up : Down;
527 }
528 }
529
GetDirectionAwayHero()530 int Game_Character::GetDirectionAwayHero() {
531 int sx = DistanceXfromPlayer();
532 int sy = DistanceYfromPlayer();
533
534 if ( std::abs(sx) > std::abs(sy) ) {
535 return (sx > 0) ? Right : Left;
536 } else {
537 return (sy > 0) ? Down : Up;
538 }
539 }
540
TurnTowardHero()541 void Game_Character::TurnTowardHero() {
542 SetDirection(GetDirectionToHero());
543 }
544
TurnAwayFromHero()545 void Game_Character::TurnAwayFromHero() {
546 SetDirection(GetDirectionAwayHero());
547 }
548
TurnRandom()549 void Game_Character::TurnRandom() {
550 SetDirection(Rand::GetRandomNumber(0, 3));
551 }
552
Wait()553 void Game_Character::Wait() {
554 SetStopCount(0);
555 SetMaxStopCountForWait();
556 }
557
BeginMoveRouteJump(int32_t & current_index,const lcf::rpg::MoveRoute & current_route)558 bool Game_Character::BeginMoveRouteJump(int32_t& current_index, const lcf::rpg::MoveRoute& current_route) {
559 int jdx = 0;
560 int jdy = 0;
561
562 for (++current_index; current_index < static_cast<int>(current_route.move_commands.size()); ++current_index) {
563 using Code = lcf::rpg::MoveCommand::Code;
564 const auto& move_command = current_route.move_commands[current_index];
565 const auto cmd = static_cast<Code>(move_command.command_id);
566 if (cmd >= Code::move_up && cmd <= Code::move_forward) {
567 switch (cmd) {
568 case Code::move_up:
569 case Code::move_right:
570 case Code::move_down:
571 case Code::move_left:
572 case Code::move_upright:
573 case Code::move_downright:
574 case Code::move_downleft:
575 case Code::move_upleft:
576 SetDirection(move_command.command_id);
577 break;
578 case Code::move_random:
579 TurnRandom();
580 break;
581 case Code::move_towards_hero:
582 TurnTowardHero();
583 break;
584 case Code::move_away_from_hero:
585 TurnAwayFromHero();
586 break;
587 case Code::move_forward:
588 break;
589 default:
590 break;
591 }
592 jdx += GetDxFromDirection(GetDirection());
593 jdy += GetDyFromDirection(GetDirection());
594 }
595
596 if (cmd >= Code::face_up && cmd <= Code::face_away_from_hero) {
597 switch (cmd) {
598 case Code::face_up:
599 SetDirection(Up);
600 break;
601 case Code::face_right:
602 SetDirection(Right);
603 break;
604 case Code::face_down:
605 SetDirection(Down);
606 break;
607 case Code::face_left:
608 SetDirection(Left);
609 break;
610 case Code::turn_90_degree_right:
611 Turn90DegreeRight();
612 break;
613 case Code::turn_90_degree_left:
614 Turn90DegreeLeft();
615 break;
616 case Code::turn_180_degree:
617 Turn180Degree();
618 break;
619 case Code::turn_90_degree_random:
620 Turn90DegreeLeftOrRight();
621 break;
622 case Code::face_random_direction:
623 TurnRandom();
624 break;
625 case Code::face_hero:
626 TurnTowardHero();
627 break;
628 case Code::face_away_from_hero:
629 TurnAwayFromHero();
630 break;
631 default:
632 break;
633 }
634 }
635
636 if (cmd == Code::end_jump) {
637 int new_x = GetX() + jdx;
638 int new_y = GetY() + jdy;
639
640 auto rc = Jump(new_x, new_y);
641 if (rc) {
642 SetMaxStopCountForStep();
643 }
644 // Note: outer function increment will cause the end jump to pass after the return.
645 return rc;
646 }
647 }
648
649 // Commands finished with no end jump. Back up the index by 1 to allow outer loop increment to work.
650 --current_index;
651
652 // Jump is skipped
653 return true;
654 }
655
Jump(int x,int y)656 bool Game_Character::Jump(int x, int y) {
657 if (!IsStopping()) {
658 return true;
659 }
660
661 auto begin_x = GetX();
662 auto begin_y = GetY();
663 const auto dx = x - begin_x;
664 const auto dy = y - begin_y;
665
666 if (std::abs(dy) >= std::abs(dx)) {
667 SetDirection(dy >= 0 ? Down : Up);
668 } else {
669 SetDirection(dx >= 0 ? Right : Left);
670 }
671
672 SetJumping(true);
673
674 if (dx != 0 || dy != 0) {
675 if (!IsFacingLocked()) {
676 SetFacing(GetDirection());
677 }
678
679 // FIXME: Remove dependency on jump from within Game_Map::MakeWay?
680 // RPG_RT passes INT_MAX into from_x to tell it to skip self tile checks, which is hacky..
681 if (!MakeWay(begin_x, begin_y, x, y)) {
682 SetJumping(false);
683 return false;
684 }
685 }
686
687 // Adjust positions for looping maps. jump begin positions
688 // get set off the edge of the map to preserve direction.
689 if (Game_Map::LoopHorizontal()
690 && (x < 0 || x >= Game_Map::GetWidth()))
691 {
692 const auto old_x = x;
693 x = Game_Map::RoundX(x);
694 begin_x += x - old_x;
695 }
696
697 if (Game_Map::LoopVertical()
698 && (y < 0 || y >= Game_Map::GetHeight()))
699 {
700 auto old_y = y;
701 y = Game_Map::RoundY(y);
702 begin_y += y - old_y;
703 }
704
705 SetBeginJumpX(begin_x);
706 SetBeginJumpY(begin_y);
707 SetX(x);
708 SetY(y);
709 SetJumping(true);
710 SetRemainingStep(SCREEN_TILE_SIZE);
711
712 return true;
713 }
714
DistanceXfromPlayer() const715 int Game_Character::DistanceXfromPlayer() const {
716 int sx = GetX() - Main_Data::game_player->GetX();
717 if (Game_Map::LoopHorizontal()) {
718 if (std::abs(sx) > Game_Map::GetWidth() / 2) {
719 if (sx > 0)
720 sx -= Game_Map::GetWidth();
721 else
722 sx += Game_Map::GetWidth();
723 }
724 }
725 return sx;
726 }
727
DistanceYfromPlayer() const728 int Game_Character::DistanceYfromPlayer() const {
729 int sy = GetY() - Main_Data::game_player->GetY();
730 if (Game_Map::LoopVertical()) {
731 if (std::abs(sy) > Game_Map::GetHeight() / 2) {
732 if (sy > 0)
733 sy -= Game_Map::GetHeight();
734 else
735 sy += Game_Map::GetHeight();
736 }
737 }
738 return sy;
739 }
740
ForceMoveRoute(const lcf::rpg::MoveRoute & new_route,int frequency)741 void Game_Character::ForceMoveRoute(const lcf::rpg::MoveRoute& new_route,
742 int frequency) {
743 if (!IsMoveRouteOverwritten()) {
744 original_move_frequency = GetMoveFrequency();
745 }
746
747 SetPaused(false);
748 SetStopCount(0xFFFF);
749 SetMoveRouteIndex(0);
750 SetMoveRouteRepeated(false);
751 SetMoveFrequency(frequency);
752 SetMoveRouteOverwritten(true);
753 SetMoveRoute(new_route);
754 if (frequency != original_move_frequency) {
755 SetMaxStopCountForStep();
756 }
757
758 if (GetMoveRoute().move_commands.empty()) {
759 CancelMoveRoute();
760 return;
761 }
762 }
763
CancelMoveRoute()764 void Game_Character::CancelMoveRoute() {
765 if (IsMoveRouteOverwritten()) {
766 SetMoveFrequency(original_move_frequency);
767 SetMaxStopCountForStep();
768 }
769 SetMoveRouteOverwritten(false);
770 SetMoveRouteRepeated(false);
771 }
772
GetSpriteX() const773 int Game_Character::GetSpriteX() const {
774 int x = GetX() * SCREEN_TILE_SIZE;
775
776 if (IsMoving()) {
777 int d = GetDirection();
778 if (d == Right || d == UpRight || d == DownRight)
779 x -= GetRemainingStep();
780 else if (d == Left || d == UpLeft || d == DownLeft)
781 x += GetRemainingStep();
782 } else if (IsJumping()) {
783 x -= ((GetX() - GetBeginJumpX()) * GetRemainingStep());
784 }
785
786 return x;
787 }
788
GetSpriteY() const789 int Game_Character::GetSpriteY() const {
790 int y = GetY() * SCREEN_TILE_SIZE;
791
792 if (IsMoving()) {
793 int d = GetDirection();
794 if (d == Down || d == DownRight || d == DownLeft)
795 y -= GetRemainingStep();
796 else if (d == Up || d == UpRight || d == UpLeft)
797 y += GetRemainingStep();
798 } else if (IsJumping()) {
799 y -= (GetY() - GetBeginJumpY()) * GetRemainingStep();
800 }
801
802 return y;
803 }
804
IsInPosition(int x,int y) const805 bool Game_Character::IsInPosition(int x, int y) const {
806 return ((GetX() == x) && (GetY() == y));
807 }
808
GetOpacity() const809 int Game_Character::GetOpacity() const {
810 return Utils::Clamp((8 - GetTransparency()) * 32 - 1, 0, 255);
811 }
812
IsAnimated() const813 bool Game_Character::IsAnimated() const {
814 auto at = GetAnimationType();
815 return !IsAnimPaused()
816 && at != lcf::rpg::EventPage::AnimType_fixed_graphic
817 && at != lcf::rpg::EventPage::AnimType_step_frame_fix;
818 }
819
IsContinuous() const820 bool Game_Character::IsContinuous() const {
821 auto at = GetAnimationType();
822 return
823 at == lcf::rpg::EventPage::AnimType_continuous ||
824 at == lcf::rpg::EventPage::AnimType_fixed_continuous;
825 }
826
IsSpinning() const827 bool Game_Character::IsSpinning() const {
828 return GetAnimationType() == lcf::rpg::EventPage::AnimType_spin;
829 }
830
GetBushDepth() const831 int Game_Character::GetBushDepth() const {
832 if ((GetLayer() != lcf::rpg::EventPage::Layers_same) || IsJumping() || IsFlying()) {
833 return 0;
834 }
835
836 return Game_Map::GetBushDepth(GetX(), GetY());
837 }
838
Flash(int r,int g,int b,int power,int frames)839 void Game_Character::Flash(int r, int g, int b, int power, int frames) {
840 data()->flash_red = r;
841 data()->flash_green = g;
842 data()->flash_blue = b;
843 data()->flash_current_level = power;
844 data()->flash_time_left = frames;
845 }
846
847 // Gets Character
GetCharacter(int character_id,int event_id)848 Game_Character* Game_Character::GetCharacter(int character_id, int event_id) {
849 switch (character_id) {
850 case CharPlayer:
851 // Player/Hero
852 return Main_Data::game_player.get();
853 case CharBoat:
854 return Game_Map::GetVehicle(Game_Vehicle::Boat);
855 case CharShip:
856 return Game_Map::GetVehicle(Game_Vehicle::Ship);
857 case CharAirship:
858 return Game_Map::GetVehicle(Game_Vehicle::Airship);
859 case CharThisEvent:
860 // This event
861 return Game_Map::GetEvent(event_id);
862 default:
863 // Other events
864 return Game_Map::GetEvent(character_id);
865 }
866 }
867
ReverseDir(int dir)868 int Game_Character::ReverseDir(int dir) {
869 constexpr static char reversed[] =
870 { Down, Left, Up, Right, DownLeft, UpLeft, UpRight, DownRight };
871 return reversed[dir];
872 }
873
SetMaxStopCountForStep()874 void Game_Character::SetMaxStopCountForStep() {
875 SetMaxStopCount(GetMaxStopCountForStep(GetMoveFrequency()));
876 }
877
SetMaxStopCountForTurn()878 void Game_Character::SetMaxStopCountForTurn() {
879 SetMaxStopCount(GetMaxStopCountForTurn(GetMoveFrequency()));
880 }
881
SetMaxStopCountForWait()882 void Game_Character::SetMaxStopCountForWait() {
883 SetMaxStopCount(GetMaxStopCountForWait(GetMoveFrequency()));
884 }
885
UpdateFacing()886 void Game_Character::UpdateFacing() {
887 // RPG_RT only does the IsSpinning() check for Game_Event. We did it for all types here
888 // in order to avoid a virtual call and because normally with RPG_RT, spinning
889 // player or vehicle is impossible.
890 if (IsFacingLocked() || IsSpinning()) {
891 return;
892 }
893 const auto dir = GetDirection();
894 const auto facing = GetFacing();
895 if (dir >= 4) /* is diagonal */ {
896 // [UR, DR, DL, UL] -> [U, D, D, U]
897 const auto f1 = ((dir + (dir >= 6)) % 2) * 2;
898 // [UR, DR, DL, UL] -> [R, R, L, L]
899 const auto f2 = (dir / 2) - (dir < 6);
900 if (facing != f1 && facing != f2) {
901 // Reverse the direction.
902 SetFacing((facing + 2) % 4);
903 }
904 } else {
905 SetFacing(dir);
906 }
907 }
908
909