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