1 /**
2  ** Animate.cc - Animated game objects.
3  **
4  ** Written: 7/27/2000 - JSF
5  **/
6 
7 /*
8 Copyright (C) 2000  Jeffrey S. Freedman
9 Copyright (C) 2000-2013 The Exult team
10 
11 This program is free software; you can redistribute it and/or
12 modify it under the terms of the GNU General Public License
13 as published by the Free Software Foundation; either version 2
14 of the License, or (at your option) any later version.
15 
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 GNU General Public License for more details.
20 
21 You should have received a copy of the GNU General Public License
22 along with this program; if not, write to the Free Software
23 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
24 */
25 
26 #ifdef HAVE_CONFIG_H
27 #  include <config.h>
28 #endif
29 
30 #include "animate.h"
31 #include "gamewin.h"
32 #include "game.h"
33 #include "gameclk.h"
34 #include "Audio.h"
35 #include "actors.h"         /* Only need this for Object_sfx. */
36 #include "dir.h"
37 #include "Flex.h"
38 #include <map>
39 #include <string>
40 #include "aniinf.h"
41 #include "sfxinf.h"
42 #include "AudioMixer.h"
43 #include "array_size.h"
44 
45 using std::ostream;
46 using std::rand;
47 
48 using namespace Pentagram;
49 
Get_sfx_out_of_range(Game_window * gwin,Tile_coord const & opos)50 static inline bool Get_sfx_out_of_range(
51     Game_window *gwin,
52     Tile_coord const &opos
53 ) {
54 	TileRect size = gwin->get_win_tile_rect();
55 	Tile_coord apos(size.x + size.w / 2, size.y + size.h / 2, gwin->get_camera_actor()->get_lift());
56 
57 	return apos.square_distance_screen_space(opos) > (MAX_SOUND_FALLOFF * MAX_SOUND_FALLOFF);
58 }
59 
60 /*
61  *  Play SFX.
62  */
63 
Play(Game_object * obj,int sfx,int delay)64 void Object_sfx::Play(Game_object *obj, int sfx, int delay) {
65 	auto *osfx = new Object_sfx(obj, sfx);
66 
67 	if (!delay) {
68 		// Start right now -- so that usecode sounds will play when intended
69 		// (e.g., books). We *really* don't want to call handle_event here
70 		// since it can delete the object (e.g., if it is out of range),
71 		// resulting in undefined behavior.
72 		Game_object *outer = obj->get_outermost();
73 		osfx->last_pos = outer->get_center_tile();
74 
75 		bool halt = Get_sfx_out_of_range(gwin, osfx->last_pos);
76 
77 		if (!halt && osfx->channel == -1 && sfx > -1) {    // First time?
78 			// Start playing.
79 			int volume = AUDIO_MAX_VOLUME;  // Set volume based on distance.
80 			osfx->channel = Audio::get_ptr()->play_sound_effect(sfx, osfx->last_pos, volume, 0);
81 		}
82 		delay = 100;
83 	}
84 	gwin->get_tqueue()->add(Game::get_ticks() + delay, osfx, gwin);
85 }
86 
Object_sfx(Game_object * o,int s)87 Object_sfx::Object_sfx(Game_object *o, int s)
88 	: obj(weak_from_obj(o)), sfx(s), channel(-1) {
89 }
90 
stop_playing()91 void Object_sfx::stop_playing() {
92 	if (channel >= 0) {
93 		Audio::get_ptr()->stop_sound_effect(channel);
94 		channel = -1;
95 	}
96 }
97 
stop()98 void Object_sfx::stop() {
99 	while (gwin->get_tqueue()->remove(this))
100 		;
101 	stop_playing();
102 	delete this;
103 }
104 
dequeue()105 void Object_sfx::dequeue() {
106 	Time_sensitive::dequeue();
107 	if (!in_queue()) {
108 		stop_playing();
109 		delete this;
110 	}
111 }
112 
handle_event(unsigned long curtime,uintptr udata)113 void Object_sfx::handle_event(
114     unsigned long curtime,      // Current time of day.
115     uintptr udata          // Game window.
116 ) {
117 	const int delay = 100;      // Guessing this will be enough.
118 
119 	//AudioMixer *mixer = AudioMixer::get_instance();
120 	//bool active = channel != -1 ? mixer->isPlaying(channel) : false;
121 
122 	Game_object *outer;
123 	Game_object_shared obj_ptr = obj.lock();
124 	if (obj_ptr) {
125 		outer = obj_ptr->get_outermost();
126 		last_pos = outer->get_center_tile();
127 	} else
128 		outer = nullptr;
129 
130 	/*
131 	if (outer->is_pos_invalid())// || (distance >= 0 && !active))
132 	    {   // Quitting time.
133 	    stop();
134 	    return;
135 	    }
136 	*/
137 
138 	bool halt = Get_sfx_out_of_range(gwin, last_pos);
139 
140 	if (!halt && channel == -1 && sfx > -1) {    // First time?
141 		// Start playing.
142 		int volume = AUDIO_MAX_VOLUME;  // Set volume based on distance.
143 		channel = Audio::get_ptr()->play_sound_effect(sfx, last_pos, volume, 0);
144 	} else if (channel != -1) {
145 		if (halt) {
146 			Audio::get_ptr()->stop_sound_effect(channel);
147 			channel = -1;
148 		} else {
149 			channel = Audio::get_ptr()->update_sound_effect(channel, last_pos);
150 		}
151 	}
152 
153 	if (channel != -1)
154 		gwin->get_tqueue()->add(curtime + delay - (curtime % delay), this, udata);
155 	else
156 		stop();
157 }
158 
159 /*
160  *  Stop playing the sound effect if needed.
161  */
stop()162 void Shape_sfx::stop() {
163 	for (size_t i = 0; i < array_size(channel); i++) {
164 		if (channel[i] >= 0) {
165 			Audio::get_ptr()->stop_sound_effect(channel[i]);
166 			channel[i] = -1;
167 		}
168 	}
169 }
170 
set_looping()171 inline void Shape_sfx::set_looping(
172 ) {
173 	looping = sfxinf ? (sfxinf->get_sfx_range() == 1
174 	                    && sfxinf->get_chance() == 100
175 	                    && !sfxinf->play_horly_ticks())
176 	          : false;
177 }
178 
179 /*
180  *  Update distance/direction information. Also starts playing
181  *  the sound effect if needed.
182  */
update(bool play)183 void Shape_sfx::update(
184     bool play
185 ) {
186 	if (obj->is_pos_invalid()) {
187 		// Not on map.
188 		stop();
189 		return;
190 	}
191 
192 	if (!sfxinf)
193 		return;
194 
195 	if (looping)
196 		play = true;
197 
198 	AudioMixer *mixer = AudioMixer::get_instance();
199 
200 	bool active[2] = {false, false};
201 	for (size_t i = 0; i < array_size(channel); i++) {
202 		if (channel[i] != -1)
203 			active[i] = mixer->isPlaying(channel[i]);
204 		if (!active[i] && channel[i] != -1) {
205 			Audio::get_ptr()->stop_sound_effect(channel[i]);
206 			channel[i] = -1;
207 		}
208 	}
209 	// If neither channel is playing, and we are not going to
210 	// play anything now, we have nothing to do.
211 	if (!play && channel[0] == -1 && channel[1] == -1)
212 		return;
213 
214 	int sfxnum[2] = { -1, -1};
215 	if (play && channel[0] == -1) {
216 		if (!sfxinf->time_to_play())
217 			return;
218 		sfxnum[0] = sfxinf->get_next_sfx(last_sfx);
219 	}
220 	int rep[2] = {looping ? -1 : 0, 0};
221 	if (play && channel[1] == -1 && sfxinf->play_horly_ticks()) {
222 		Game_clock *gclock = Game_window::get_instance()->get_clock();
223 		if (gclock->get_minute() == 0) {
224 			// Play sfx->extra every hour for reps = hour
225 			int reps = gclock->get_hour() % 12;
226 			rep[1] = (reps ? reps : 12) - 1;
227 			sfxnum[1] = sfxinf->get_extra_sfx();
228 		}
229 	}
230 
231 	dir = 0;
232 	int volume = AUDIO_MAX_VOLUME;  // Set volume based on distance.
233 	bool halt = Get_sfx_out_of_range(gwin, obj->get_center_tile());
234 
235 	if (play && halt)
236 		play = false;
237 
238 	for (size_t i = 0; i < array_size(channel); i++)
239 		if (play && channel[i] == -1 && sfxnum[i] > -1)     // First time?
240 			// Start playing.
241 			channel[i] = Audio::get_ptr()->play_sound_effect(sfxnum[i], obj, volume, rep[i]);
242 		else if (channel[i] != -1) {
243 			if (halt) {
244 				Audio::get_ptr()->stop_sound_effect(channel[i]);
245 				channel[i] = -1;
246 			} else {
247 				channel[i] = Audio::get_ptr()->update_sound_effect(channel[i], obj);
248 			}
249 		}
250 }
251 
252 /*
253  *  Create appropriate animator.
254  */
255 
create(Game_object * ob)256 Animator *Animator::create(
257     Game_object *ob         // Animated object.
258 ) {
259 	int frames = ob->get_num_frames();
260 	const Shape_info &info = ob->get_info();
261 	if (!info.is_animated())    // Assume it's just SFX.
262 		return new Sfx_animator(ob);
263 	else if (frames > 1)
264 		return new Frame_animator(ob);
265 	else
266 		return new Wiggle_animator(ob);
267 }
268 
269 
270 /*
271  *  When we delete, better remove from queue.
272  */
273 
~Animator()274 Animator::~Animator(
275 ) {
276 	if (gwin->get_tqueue()) {
277 		while (gwin->get_tqueue()->remove(this))
278 			;
279 	}
280 	if (objsfx) {
281 		objsfx->stop();
282 		delete objsfx;
283 	}
284 }
285 
286 /*
287  *  Start animation.
288  */
289 
start_animation()290 void Animator::start_animation(
291 ) {
292 	// Clean out old entry if there.
293 	gwin->get_tqueue()->remove(this);
294 	gwin->get_tqueue()->add(Game::get_ticks() + 20, this, gwin);
295 	animating = true;
296 }
297 
298 /*
299  *  Retrieve current frame
300  */
301 
get_framenum()302 int Animator::get_framenum() {
303 	return obj->get_framenum();
304 }
305 
306 /*
307  *  Create a frame animator.
308  */
309 
Frame_animator(Game_object * o)310 Frame_animator::Frame_animator(
311     Game_object *o
312 ) : Animator(o) {
313 	Initialize();
314 }
315 
316 /*
317  *  Initialize a frame animator.
318  */
Initialize()319 void Frame_animator::Initialize() {
320 	last_shape = obj->get_shapenum();
321 	// Catch rotated objects here.
322 	last_frame = obj->get_framenum() & ~(1 << 5);
323 	int rotflag = obj->get_framenum() & (1 << 5);
324 
325 	ShapeID shp(last_shape, last_frame);
326 	aniinf = obj->get_info().get_animation_info_safe(last_shape,
327 	         shp.get_num_frames());
328 	int cnt = aniinf->get_frame_count();
329 	if (cnt < 0)
330 		nframes = shp.get_num_frames();
331 	else
332 		nframes = cnt;
333 	if (nframes == shp.get_num_frames())
334 		first_frame = 0;
335 	else
336 		first_frame = last_frame - (last_frame % nframes);
337 	// Ensure proper bounds.
338 	if (first_frame + nframes >= shp.get_num_frames())
339 		nframes = shp.get_num_frames() - first_frame;
340 	assert(nframes > 0);
341 
342 	frame_counter = aniinf->get_frame_delay();
343 
344 	if (aniinf->get_type() == Animation_info::FA_TIMESYNCHED)
345 		created = currpos = last_frame % nframes;
346 	else
347 		created = currpos = last_frame - first_frame;
348 	// Add rotate flag back.
349 	first_frame |= rotflag;
350 	last_frame |= rotflag;
351 }
352 
353 /*
354  *  Retrieve current frame
355  */
356 
get_next_frame()357 int Frame_animator::get_next_frame() {
358 	// Re-init if it's outside the range.
359 	// ++++++Should we do this for the other cases (jsf)?
360 	// ++++++Seeing if it breaks anything (marzo)
361 	int curframe = obj->get_framenum();
362 	if (curframe < first_frame ||
363 	        curframe >= first_frame + nframes)
364 		Initialize();
365 
366 	if (nframes == 1)   // No reason to do anything else.
367 		return first_frame;
368 
369 	int framenum;
370 	switch (aniinf->get_type()) {
371 	case Animation_info::FA_HOURLY:
372 		framenum = gclock->get_hour() % nframes;
373 		break;
374 
375 	case Animation_info::FA_NON_LOOPING:
376 		currpos++;
377 		if (currpos >= nframes)
378 			currpos = nframes - 1;
379 		framenum = first_frame + currpos;
380 		break;
381 
382 	case Animation_info::FA_TIMESYNCHED: {
383 		unsigned int ticks = Game::get_ticks();
384 		const int delay = 100;
385 		currpos = (ticks / (delay * aniinf->get_frame_delay())) + created;
386 		currpos %= nframes;
387 		framenum = first_frame + currpos;
388 		break;
389 	}
390 
391 	case Animation_info::FA_LOOPING:
392 	default: {
393 		int chance = aniinf->get_freeze_first_chance();
394 		if (currpos || chance == 100
395 		        || (chance && rand() % 100 < chance)) {
396 			currpos++;
397 			currpos %= nframes;
398 			int rec = aniinf->get_recycle();
399 			if (!currpos && nframes >= rec)
400 				currpos = (nframes - rec) % nframes;
401 		}
402 		framenum = first_frame + currpos;
403 		break;
404 	}
405 
406 	case Animation_info::FA_RANDOM_FRAMES:
407 		currpos = rand() % nframes;
408 		framenum = first_frame + currpos;
409 		break;
410 	}
411 
412 	return framenum;
413 }
414 
415 /*
416  *  Animation.
417  */
418 
handle_event(unsigned long curtime,uintptr udata)419 void Frame_animator::handle_event(
420     unsigned long curtime,      // Current time of day.
421     uintptr udata          // Game window.
422 ) {
423 	const int delay = 100;
424 	auto *gwin = reinterpret_cast<Game_window *>(udata);
425 
426 	if (!--frame_counter) {
427 		frame_counter = aniinf->get_frame_delay();
428 		bool dirty_first = gwin->add_dirty(obj);
429 		int framenum = get_next_frame();
430 		obj->change_frame(last_frame = framenum);
431 		if (!dirty_first && !gwin->add_dirty(obj)) {
432 			// No longer on screen.
433 			animating = false;
434 			// Stop playing sound.
435 			if (objsfx)
436 				objsfx->stop();
437 			return;
438 		}
439 	}
440 
441 	if (objsfx) {
442 		// Sound effect?
443 		bool play;
444 		if (frame_counter != aniinf->get_frame_delay())
445 			play = false;
446 		else if (aniinf->get_sfx_delay() < 0) {
447 			// Only in synch with animation.
448 			if (aniinf->get_freeze_first_chance() < 100)
449 				// Not in (frozen) first frame.
450 				play = (currpos == 1);
451 			else
452 				play = (currpos == 0);
453 		} else if (aniinf->get_sfx_delay() > 1)
454 			// Skip (sfx_delay-1) frames.
455 			play = (currpos % aniinf->get_sfx_delay()) == 0;
456 		else
457 			// Continuous.
458 			play = true;
459 		objsfx->update(play);
460 	}
461 
462 	// Add back to queue for next time.
463 	if (animating)
464 		// Ensure all animations are synched
465 		gwin->get_tqueue()->add(curtime + delay - (curtime % delay), this, udata);
466 }
467 
468 /*
469  *  Create a pure SFX player.
470  */
471 
Sfx_animator(Game_object * o)472 Sfx_animator::Sfx_animator(
473     Game_object *o
474 ) : Animator(o) {
475 }
476 
477 /*
478  *  Play SFX.
479  */
480 
handle_event(unsigned long curtime,uintptr udata)481 void Sfx_animator::handle_event(
482     unsigned long curtime,      // Current time of day.
483     uintptr udata          // Game window.
484 ) {
485 	const int delay = 100;      // Guessing this will be enough.
486 
487 	auto *gwin = reinterpret_cast<Game_window *>(udata);
488 	TileRect rect = gwin->clip_to_win(gwin->get_shape_rect(obj));
489 	if (rect.w <= 0 || rect.h <= 0) {
490 		// No longer on screen.
491 		animating = false;
492 		// Stop playing sound.
493 		if (objsfx)
494 			objsfx->stop();
495 		return;
496 	}
497 
498 	if (objsfx)     // Sound effect?
499 		objsfx->update(true);
500 	// Add back to queue for next time.
501 	if (animating)
502 		gwin->get_tqueue()->add(curtime + delay - (curtime % delay), this, udata);
503 }
504 
505 /*
506  *  Create a field frame animator.
507  */
508 
Field_frame_animator(Game_object * o)509 Field_frame_animator::Field_frame_animator(
510     Game_object *o
511 ) : Frame_animator(o), activated(true) {
512 }
513 
514 /*
515  *  Animation.
516  */
517 
handle_event(unsigned long curtime,uintptr udata)518 void Field_frame_animator::handle_event(
519     unsigned long curtime,      // Current time of day.
520     uintptr udata          // Game window.
521 ) {
522 	Frame_animator::handle_event(curtime, udata);
523 	if (activated && rand() % 10 == 0) // Check for damage?
524 		obj->activate(0);
525 }
526 
527 /*
528  *  Animation.
529  */
530 
handle_event(unsigned long curtime,uintptr udata)531 void Wiggle_animator::handle_event(
532     unsigned long curtime,      // Current time of day.
533     uintptr udata          // Game window.
534 ) {
535 	const int delay = 100;      // Delay between frames.
536 	auto *gwin = reinterpret_cast<Game_window *>(udata);
537 	if (!gwin->add_dirty(obj)) {
538 		// No longer on screen.
539 		animating = false;
540 		return;
541 	}
542 	Tile_coord t = obj->get_tile(); // Get current position.
543 	int newdx = rand() % 3;
544 	int newdy = rand() % 3;
545 	t.tx += -deltax + newdx;
546 	t.ty += -deltay + newdy;
547 	deltax = newdx;
548 	deltay = newdy;
549 	obj->Game_object::move(t.tx, t.ty, t.tz);
550 	// Add back to queue for next time.
551 	if (animating)
552 		gwin->get_tqueue()->add(curtime + delay, this, udata);
553 }
554 
555 /*
556  *  Create at given position.
557  */
558 
Animated_object(int shapenum,int framenum,unsigned int tilex,unsigned int tiley,unsigned int lft)559 Animated_object::Animated_object(
560     int shapenum,
561     int framenum,
562     unsigned int tilex, unsigned int tiley,
563     unsigned int lft
564 ) : Terrain_game_object(shapenum, framenum, tilex, tiley, lft) {
565 	animator = Animator::create(this);
566 }
567 
568 /*
569  *  When we delete, better remove from queue.
570  */
571 
~Animated_object()572 Animated_object::~Animated_object(
573 ) {
574 	delete animator;
575 }
576 
577 /*
578  *  Render.
579  */
580 
paint()581 void Animated_object::paint(
582 ) {
583 	animator->want_animation(); // Be sure animation is on.
584 	Game_object::paint();
585 }
586 
587 /*
588  *  Create at given position.
589  */
590 
Animated_ireg_object(int shapenum,int framenum,unsigned int tilex,unsigned int tiley,unsigned int lft)591 Animated_ireg_object::Animated_ireg_object(
592     int shapenum,
593     int framenum,
594     unsigned int tilex, unsigned int tiley,
595     unsigned int lft
596 ) : Ireg_game_object(shapenum, framenum, tilex, tiley, lft) {
597 	animator = Animator::create(this);
598 }
599 
600 /*
601  *  When we delete, better remove from queue.
602  */
603 
~Animated_ireg_object()604 Animated_ireg_object::~Animated_ireg_object(
605 ) {
606 	delete animator;
607 }
608 
609 /*
610  *  Render.
611  */
612 
paint()613 void Animated_ireg_object::paint(
614 ) {
615 	animator->want_animation(); // Be sure animation is on.
616 	Ireg_game_object::paint();
617 }
618 
619 /*
620  *  Write out.
621  */
622 
write_ireg(ODataSource * out)623 void Animated_ireg_object::write_ireg(ODataSource *out) {
624 	int oldframe = get_framenum();
625 	set_frame(animator->get_framenum());
626 	Ireg_game_object::write_ireg(out);
627 	set_frame(oldframe);
628 }
629 
630 /*
631  *  Create at given position.
632  */
633 
Animated_ifix_object(int shapenum,int framenum,unsigned int tilex,unsigned int tiley,unsigned int lft)634 Animated_ifix_object::Animated_ifix_object(
635     int shapenum,
636     int framenum,
637     unsigned int tilex, unsigned int tiley,
638     unsigned int lft
639 ) : Ifix_game_object(shapenum, framenum, tilex, tiley, lft) {
640 	animator = Animator::create(this);
641 }
642 
643 /*
644  *  When we delete, better remove from queue.
645  */
646 
~Animated_ifix_object()647 Animated_ifix_object::~Animated_ifix_object(
648 ) {
649 	delete animator;
650 }
651 
652 /*
653  *  Render.
654  */
655 
paint()656 void Animated_ifix_object::paint(
657 ) {
658 	animator->want_animation(); // Be sure animation is on.
659 	Ifix_game_object::paint();
660 }
661 
662 /*
663  *  Write out an IFIX object.
664  */
665 
write_ifix(ODataSource * ifix,bool v2)666 void Animated_ifix_object::write_ifix(ODataSource *ifix,  bool v2)
667 
668 {
669 	int oldframe = get_framenum();
670 	set_frame(animator->get_framenum());
671 	Ifix_game_object::write_ifix(ifix, v2);
672 	set_frame(oldframe);
673 }
674 
675 
676