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