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 "bitmap.h"
19 #include <lcf/rpg/animation.h>
20 #include "output.h"
21 #include "game_battle.h"
22 #include "game_system.h"
23 #include "game_screen.h"
24 #include "game_map.h"
25 #include "main_data.h"
26 #include "filefinder.h"
27 #include "cache.h"
28 #include "battle_animation.h"
29 #include "baseui.h"
30 #include "spriteset_battle.h"
31 #include "player.h"
32 #include "options.h"
33 #include "drawable_mgr.h"
34 
BattleAnimation(const lcf::rpg::Animation & anim,bool only_sound,int cutoff)35 BattleAnimation::BattleAnimation(const lcf::rpg::Animation& anim, bool only_sound, int cutoff) :
36 	animation(anim), only_sound(only_sound)
37 {
38 	num_frames = GetRealFrames() * 2;
39 	if (cutoff >= 0 && cutoff < num_frames) {
40 		num_frames = cutoff;
41 	}
42 
43 	SetZ(Priority_BattleAnimation);
44 
45 	StringView name = animation.animation_name;
46 	BitmapRef graphic;
47 
48 	if (name.empty()) return;
49 
50 	if (animation.large) {
51 		FileRequestAsync* request = AsyncHandler::RequestFile("Battle2", name);
52 		request->SetGraphicFile(true);
53 		request_id = request->Bind(&BattleAnimation::OnBattle2SpriteReady, this);
54 		request->Start();
55 	} else {
56 		FileRequestAsync* request = AsyncHandler::RequestFile("Battle", name);
57 		request->SetGraphicFile(true);
58 		request_id = request->Bind(&BattleAnimation::OnBattleSpriteReady, this);
59 		request->Start();
60 	}
61 }
62 
Update()63 void BattleAnimation::Update() {
64 	if (!IsDone() && (frame & 1) == 0) {
65 		// Lookup any timed SFX (SE/flash/shake) data for this frame
66 		for (auto& timing: animation.timings) {
67 			if (timing.frame == GetRealFrame() + 1) {
68 				ProcessAnimationTiming(timing);
69 			}
70 		}
71 	}
72 
73 	UpdateScreenFlash();
74 	UpdateTargetFlash();
75 
76 	SetFlashEffect(Main_Data::game_screen->GetFlashColor());
77 
78 	frame++;
79 }
80 
OnBattleSpriteReady(FileRequestResult * result)81 void BattleAnimation::OnBattleSpriteReady(FileRequestResult* result) {
82 	BitmapRef bitmap = Cache::Battle(result->file);
83 	SetBitmap(bitmap);
84 	SetSrcRect(Rect(0, 0, 0, 0));
85 }
86 
OnBattle2SpriteReady(FileRequestResult * result)87 void BattleAnimation::OnBattle2SpriteReady(FileRequestResult* result) {
88 	BitmapRef bitmap = Cache::Battle2(result->file);
89 	SetBitmap(bitmap);
90 	SetSrcRect(Rect(0, 0, 0, 0));
91 }
92 
DrawAt(Bitmap & dst,int x,int y)93 void BattleAnimation::DrawAt(Bitmap& dst, int x, int y) {
94 	if (IsDone()) {
95 		return;
96 	}
97 
98 	const lcf::rpg::AnimationFrame& anim_frame = animation.frames[GetRealFrame()];
99 
100 	std::vector<lcf::rpg::AnimationCellData>::const_iterator it;
101 	for (it = anim_frame.cells.begin(); it != anim_frame.cells.end(); ++it) {
102 		const lcf::rpg::AnimationCellData& cell = *it;
103 
104 		if (!cell.valid) {
105 			// Skip unused cells (they are created by deleting cells in the
106 			// animation editor, resulting in gaps)
107 			continue;
108 		}
109 
110 		SetX(invert ? x - cell.x : cell.x + x);
111 		SetY(cell.y + y);
112 		int sx = cell.cell_id % 5;
113 		if (invert) sx = 4 - sx;
114 		int sy = cell.cell_id / 5;
115 		int size = animation.large ? 128 : 96;
116 		SetSrcRect(Rect(sx * size, sy * size, size, size));
117 		SetOx(size / 2);
118 		SetOy(size / 2);
119 		SetTone(Tone(cell.tone_red * 128 / 100,
120 			cell.tone_green * 128 / 100,
121 			cell.tone_blue * 128 / 100,
122 			cell.tone_gray * 128 / 100));
123 		SetOpacity(255 * (100 - cell.transparency) / 100);
124 		SetZoomX(cell.zoom / 100.0);
125 		SetZoomY(cell.zoom / 100.0);
126 		SetFlipX(invert);
127 		Sprite::Draw(dst);
128 	}
129 
130 	if (anim_frame.cells.empty()) {
131 		// Draw an empty sprite when no cell is available in the animation
132 		SetSrcRect(Rect(0, 0, 0, 0));
133 		Sprite::Draw(dst);
134 	}
135 }
136 
ProcessAnimationFlash(const lcf::rpg::AnimationTiming & timing)137 void BattleAnimation::ProcessAnimationFlash(const lcf::rpg::AnimationTiming& timing) {
138 	if (IsOnlySound()) {
139 		return;
140 	}
141 
142 	if (timing.flash_scope == lcf::rpg::AnimationTiming::FlashScope_target) {
143 		target_flash_timing = &timing - animation.timings.data();
144 	} else if (timing.flash_scope == lcf::rpg::AnimationTiming::FlashScope_screen) {
145 		screen_flash_timing = &timing - animation.timings.data();
146 	}
147 }
148 
ProcessAnimationTiming(const lcf::rpg::AnimationTiming & timing)149 void BattleAnimation::ProcessAnimationTiming(const lcf::rpg::AnimationTiming& timing) {
150 	// Play the SE.
151 	Main_Data::game_system->SePlay(timing.se);
152 	if (IsOnlySound()) {
153 		return;
154 	}
155 
156 	// Flash.
157 	ProcessAnimationFlash(timing);
158 
159 	// Shake (only happens in battle).
160 	if (Game_Battle::IsBattleRunning()) {
161 		switch (timing.screen_shake) {
162 		case lcf::rpg::AnimationTiming::ScreenShake_nothing:
163 			break;
164 		case lcf::rpg::AnimationTiming::ScreenShake_target:
165 			// FIXME: Estimate, see below for screen shake.
166 			ShakeTargets(3, 5, 32);
167 			break;
168 		case lcf::rpg::AnimationTiming::ScreenShake_screen:
169 			Game_Screen* screen = Main_Data::game_screen.get();
170 			// FIXME: This is not proven accurate. Screen captures show that
171 			// the shake effect lasts for 16 animation frames (32 real frames).
172 			// The maximum offset observed was 6 or 7, which makes these numbers
173 			// seem reasonable.
174 			screen->ShakeOnce(3, 5, 32);
175 			break;
176 		}
177 	}
178 }
179 
CalculateFlashPower(int frames,int power)180 static int CalculateFlashPower(int frames, int power) {
181 	// This algorithm was determined numerically by measuring the flash
182 	// power for each frame of battle animation flashs.
183 	int f = 7 - ((frames + 1) / 2);
184 	return std::min(f * power / 6, 31);
185 }
186 
UpdateFlashGeneric(int timing_idx,int & r,int & g,int & b,int & p)187 void BattleAnimation::UpdateFlashGeneric(int timing_idx, int& r, int& g, int& b, int& p) {
188 	r = 0; g = 0; b = 0; p = 0;
189 
190 	if (timing_idx >= 0) {
191 		auto& timing = animation.timings[timing_idx];
192 		int start_frame = (timing.frame - 1) * 2;
193 		int delta_frames = GetFrame() - start_frame;
194 		if (delta_frames <= 10) {
195 			r = timing.flash_red;
196 			g = timing.flash_green;
197 			b = timing.flash_blue;
198 			p = CalculateFlashPower(delta_frames, timing.flash_power);
199 		}
200 	}
201 }
202 
UpdateScreenFlash()203 void BattleAnimation::UpdateScreenFlash() {
204 	int r, g, b, p;
205 	UpdateFlashGeneric(screen_flash_timing, r, g, b, p);
206 	Main_Data::game_screen->FlashOnce(r, g, b, p, 0);
207 }
208 
UpdateTargetFlash()209 void BattleAnimation::UpdateTargetFlash() {
210 	int r, g, b, p;
211 	UpdateFlashGeneric(target_flash_timing, r, g, b, p);
212 	FlashTargets(r, g, b, p);
213 }
214 
215 // For handling the vertical position.
216 // (The first argument should be an lcf::rpg::Animation::Position,
217 // but the position member is an int, so take an int.)
CalculateOffset(int pos,int target_height)218 static int CalculateOffset(int pos, int target_height) {
219 	switch (pos) {
220 	case lcf::rpg::Animation::Position_down:
221 		return target_height / 2;
222 	case lcf::rpg::Animation::Position_up:
223 		return -(target_height / 2);
224 	default:
225 		return 0;
226 	}
227 }
228 
229 /////////
230 
BattleAnimationMap(const lcf::rpg::Animation & anim,Game_Character & target,bool global)231 BattleAnimationMap::BattleAnimationMap(const lcf::rpg::Animation& anim, Game_Character& target, bool global) :
232 	BattleAnimation(anim), target(target), global(global)
233 {
234 }
235 
Draw(Bitmap & dst)236 void BattleAnimationMap::Draw(Bitmap& dst) {
237 	if (IsOnlySound()) {
238 		return;
239 	}
240 
241 	if (global) {
242 		DrawGlobal(dst);
243 	} else {
244 		DrawSingle(dst);
245 	}
246 }
247 
DrawGlobal(Bitmap & dst)248 void BattleAnimationMap::DrawGlobal(Bitmap& dst) {
249 	auto rect = Main_Data::game_screen->GetScreenEffectsRect();
250 
251 	for (int y = -1; y < 2; ++y) {
252 		for (int x = -1; x < 2; ++x) {
253 			DrawAt(dst, rect.width * x + rect.x, rect.height * y + rect.y);
254 		}
255 	}
256 }
257 
DrawSingle(Bitmap & dst)258 void BattleAnimationMap::DrawSingle(Bitmap& dst) {
259 	//If animation is targeted on the screen
260 	if (animation.scope == lcf::rpg::Animation::Scope_screen) {
261 		DrawAt(dst, SCREEN_TARGET_WIDTH / 2, SCREEN_TARGET_HEIGHT / 2);
262 		return;
263 	}
264 	const int character_height = 24;
265 	int vertical_center = target.GetScreenY(false, false) - character_height / 2;
266 	int offset = CalculateOffset(animation.position, character_height);
267 	DrawAt(dst, target.GetScreenX(), vertical_center + offset);
268 }
269 
FlashTargets(int r,int g,int b,int p)270 void BattleAnimationMap::FlashTargets(int r, int g, int b, int p) {
271 	target.Flash(r, g, b, p, 0);
272 }
273 
ShakeTargets(int,int,int)274 void BattleAnimationMap::ShakeTargets(int /* str */, int /* spd */, int /* time */) {
275 }
276 
277 /////////
278 
BattleAnimationBattle(const lcf::rpg::Animation & anim,std::vector<Game_Battler * > battlers,bool only_sound,int cutoff_frame,bool set_invert)279 BattleAnimationBattle::BattleAnimationBattle(const lcf::rpg::Animation& anim, std::vector<Game_Battler*> battlers, bool only_sound, int cutoff_frame, bool set_invert) :
280 	BattleAnimation(anim, only_sound, cutoff_frame), battlers(std::move(battlers))
281 {
282 	invert = set_invert;
283 }
284 
Draw(Bitmap & dst)285 void BattleAnimationBattle::Draw(Bitmap& dst) {
286 	if (IsOnlySound())
287 		return;
288 	if (animation.scope == lcf::rpg::Animation::Scope_screen) {
289 		DrawAt(dst, SCREEN_TARGET_WIDTH / 2, SCREEN_TARGET_HEIGHT / 3);
290 		return;
291 	}
292 
293 	for (auto* battler: battlers) {
294 		const Sprite_Battler* sprite = battler->GetBattleSprite();
295 		int offset = 0;
296 		if (sprite) {
297 			if (sprite->GetBitmap()) {
298 				offset = CalculateOffset(animation.position, sprite->GetHeight());
299 			} else {
300 				offset = CalculateOffset(animation.position, GetAnimationCellHeight() / 2);
301 			}
302 		}
303 		DrawAt(dst, battler->GetBattlePosition().x, battler->GetBattlePosition().y + offset);
304 	}
305 }
FlashTargets(int r,int g,int b,int p)306 void BattleAnimationBattle::FlashTargets(int r, int g, int b, int p) {
307 	for (auto& battler: battlers) {
308 		battler->Flash(r, g, b, p, 0);
309 	}
310 }
311 
ShakeTargets(int str,int spd,int time)312 void BattleAnimationBattle::ShakeTargets(int str, int spd, int time) {
313 	for (auto& battler: battlers) {
314 		battler->ShakeOnce(str, spd, time);
315 	}
316 }
317 
BattleAnimationBattler(const lcf::rpg::Animation & anim,std::vector<Game_Battler * > battlers,bool only_sound,int cutoff_frame,bool set_invert)318 BattleAnimationBattler::BattleAnimationBattler(const lcf::rpg::Animation& anim, std::vector<Game_Battler*> battlers, bool only_sound, int cutoff_frame, bool set_invert) :
319 	BattleAnimation(anim, only_sound, cutoff_frame), battlers(std::move(battlers))
320 {
321 	invert = set_invert;
322 }
323 
Draw(Bitmap & dst)324 void BattleAnimationBattler::Draw(Bitmap& dst) {
325 	if (IsOnlySound())
326 		return;
327 	if (animation.scope == lcf::rpg::Animation::Scope_screen) {
328 		DrawAt(dst, SCREEN_TARGET_WIDTH / 2, SCREEN_TARGET_HEIGHT / 3);
329 		return;
330 	}
331 
332 	for (auto* battler: battlers) {
333 		SetFlashEffect(battler->GetFlashColor());
334 		DrawAt(dst, battler->GetDisplayX(), battler->GetDisplayY());
335 	}
336 }
337 
FlashTargets(int r,int g,int b,int p)338 void BattleAnimationBattler::FlashTargets(int r, int g, int b, int p) {
339 	for (auto& battler: battlers) {
340 		battler->Flash(r, g, b, p, 0);
341 	}
342 }
343 
ShakeTargets(int str,int spd,int time)344 void BattleAnimationBattler::ShakeTargets(int str, int spd, int time) {
345 	for (auto& battler: battlers) {
346 		battler->ShakeOnce(str, spd, time);
347 	}
348 }
349 
ProcessAnimationTiming(const lcf::rpg::AnimationTiming & timing)350 void BattleAnimationBattler::ProcessAnimationTiming(const lcf::rpg::AnimationTiming& timing) {
351 	// Play the SE.
352 	Main_Data::game_system->SePlay(timing.se);
353 	if (IsOnlySound()) {
354 		return;
355 	}
356 
357 	// Flash.
358 	ProcessAnimationFlash(timing);
359 }
360 
ProcessAnimationFlash(const lcf::rpg::AnimationTiming & timing)361 void BattleAnimationBattler::ProcessAnimationFlash(const lcf::rpg::AnimationTiming& timing) {
362 	if (IsOnlySound()) {
363 		return;
364 	}
365 
366 	if (timing.flash_scope == lcf::rpg::AnimationTiming::FlashScope_screen) {
367 		target_flash_timing = &timing - animation.timings.data();
368 	}
369 }
370 
UpdateScreenFlash()371 void BattleAnimationBattler::UpdateScreenFlash() {
372 	int r, g, b, p;
373 	UpdateFlashGeneric(screen_flash_timing, r, g, b, p);
374 	if (r > 0 || g > 0 || b > 0 || p > 0) {
375 		Main_Data::game_screen->FlashOnce(r, g, b, p, 0);
376 	}
377 }
378 
UpdateTargetFlash()379 void BattleAnimationBattler::UpdateTargetFlash() {
380 	int r, g, b, p;
381 	UpdateFlashGeneric(target_flash_timing, r, g, b, p);
382 	if (r > 0 || g > 0 || b > 0 || p > 0) {
383 		FlashTargets(r, g, b, p);
384 	}
385 }
386 
SetFrame(int frame)387 void BattleAnimation::SetFrame(int frame) {
388 	// Reset pending flash.
389 	int real_frame = frame / 2;
390 	screen_flash_timing = -1;
391 	target_flash_timing = -1;
392 	for (auto& timing: animation.timings) {
393 		if (timing.frame > real_frame + 1) {
394 			break;
395 		}
396 		ProcessAnimationFlash(timing);
397 	}
398 
399 	this->frame = frame;
400 }
401 
SetInvert(bool inverted)402 void BattleAnimation::SetInvert(bool inverted) {
403 	invert = inverted;
404 }
405