1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "ultima/nuvie/core/nuvie_defs.h"
24 #include "ultima/nuvie/core/game.h"
25 #include "ultima/nuvie/actors/actor.h"
26 #include "ultima/nuvie/core/map.h"
27 #include "ultima/nuvie/core/party.h"
28 #include "ultima/nuvie/script/script.h"
29 #include "ultima/nuvie/core/anim_manager.h"
30 #include "ultima/nuvie/gui/widgets/map_window.h"
31 #include "ultima/nuvie/core/tile_manager.h"
32 #include "ultima/nuvie/core/game_clock.h"
33 #include "ultima/nuvie/core/effect_manager.h"
34 #include "ultima/nuvie/usecode/usecode.h"
35 #include "ultima/nuvie/gui/widgets/msg_scroll.h"
36 #include "ultima/nuvie/actors/actor_manager.h"
37 #include "ultima/nuvie/sound/sound_manager.h"
38 #include "ultima/nuvie/core/u6_objects.h"
39 #include "ultima/nuvie/core/effect.h"
40 #include "ultima/nuvie/core/player.h"
41 
42 namespace Ultima {
43 namespace Nuvie {
44 
45 #define MESG_ANIM_HIT_WORLD  ANIM_CB_HIT_WORLD
46 #define MESG_ANIM_HIT        ANIM_CB_HIT
47 #define MESG_ANIM_DONE       ANIM_CB_DONE
48 #define MESG_EFFECT_COMPLETE EFFECT_CB_COMPLETE
49 //#define MESG_INPUT_READY     EVENT_CB_INPUT_READY
50 #define MESG_INPUT_READY     MSGSCROLL_CB_TEXT_READY
51 
52 #define TRANSPARENT_COLOR 0xFF /* transparent pixel color */
53 
54 #define EXP_EFFECT_TILE_NUM 382
55 
56 QuakeEffect *QuakeEffect::current_quake = NULL;
57 FadeEffect *FadeEffect::current_fade = NULL;
58 
59 
60 /* Add self to effect list (for future deletion).
61  */
Effect()62 Effect::Effect() : defunct(false) {
63 	retain_count = 0;
64 	game = Game::get_game();
65 	effect_manager = game->get_effect_manager();
66 	effect_manager->add_effect(this);
67 }
68 
69 
~Effect()70 Effect::~Effect() {
71 	// FIXME: should we remove self from Callbacks' default targets?
72 }
73 
74 
75 /* Start managing new animation. (AnimMgr will do that actually, but we point to
76  * it and can stop it when necessary)
77  */
add_anim(NuvieAnim * anim)78 void Effect::add_anim(NuvieAnim *anim) {
79 	anim->set_target(this); // add self as callback target for anim
80 	game->get_map_window()->get_anim_manager()->new_anim(anim);
81 }
82 
83 
84 /* Fire from a cannon in direction: 0=north, 1=east, 2=south, 3=west,
85  *                                 -1=use cannon frame
86  */
CannonballEffect(Obj * src_obj,sint8 direction)87 CannonballEffect::CannonballEffect(Obj *src_obj, sint8 direction)
88 	: target_loc() {
89 	usecode = game->get_usecode();
90 	obj = src_obj;
91 	MapCoord obj_loc(obj->x, obj->y, obj->z);
92 	target_loc = obj_loc;
93 
94 	if (direction == -1)
95 		direction = obj->frame_n;
96 
97 	uint8 target_dist = 5; // distance that cannonball will fly
98 	if (direction == NUVIE_DIR_N)
99 		target_loc.y -= target_dist;
100 	else if (direction == NUVIE_DIR_E)
101 		target_loc.x += target_dist;
102 	else if (direction == NUVIE_DIR_S)
103 		target_loc.y += target_dist;
104 	else if (direction == NUVIE_DIR_W)
105 		target_loc.x -= target_dist;
106 
107 	start_anim();
108 }
109 
110 
111 /* Pause world & start animation. */
start_anim()112 void CannonballEffect::start_anim() {
113 	MapCoord obj_loc(obj->x, obj->y, obj->z);
114 
115 	game->pause_world();
116 	game->pause_anims();
117 	game->pause_user();
118 
119 	anim = new TossAnim(game->get_tile_manager()->get_tile(399),
120 	                    obj_loc, target_loc, CANNON_SPEED, TOSS_TO_BLOCKING | TOSS_TO_ACTOR | TOSS_TO_OBJECT);
121 	add_anim(anim);
122 }
123 
124 
125 /* Handle messages from animation. Hit actors & walls. */
callback(uint16 msg,CallBack * caller,void * msg_data)126 uint16 CannonballEffect::callback(uint16 msg, CallBack *caller, void *msg_data) {
127 	bool stop_effect = false;
128 	Actor *hit_actor = NULL;
129 
130 	switch (msg) {
131 	case MESG_ANIM_HIT_WORLD: {
132 		MapCoord *hit_loc = static_cast<MapCoord *>(msg_data);
133 		Tile *obj_tile = game->get_obj_manager()->get_obj_tile(hit_loc->x, hit_loc->y, hit_loc->z);
134 		Tile *tile = game->get_game_map()->get_tile(hit_loc->x, hit_loc->y,
135 		             hit_loc->z);
136 
137 		if ((tile->flags2 & TILEFLAG_MISSILE_BOUNDARY)
138 		        || (obj_tile && (obj_tile->flags2 & TILEFLAG_MISSILE_BOUNDARY))) {
139 			//new ExplosiveEffect(hit_loc->x, hit_loc->y, 2);
140 			new ExpEffect(EXP_EFFECT_TILE_NUM, MapCoord(hit_loc->x, hit_loc->y, hit_loc->z));
141 			stop_effect = true;
142 		}
143 		break;
144 	}
145 	case MESG_ANIM_HIT: {
146 		MapEntity *hit_ent = static_cast<MapEntity *>(msg_data);
147 		if (hit_ent->entity_type == ENT_ACTOR) {
148 			//hit_ent->actor->hit(32);
149 			hit_actor = hit_ent->actor;
150 			stop_effect = true;
151 		}
152 		if (hit_ent->entity_type == ENT_OBJ) {
153 			DEBUG(0, LEVEL_DEBUGGING, "hit object %d at %x,%x,%x\n", hit_ent->obj->obj_n, hit_ent->obj->x, hit_ent->obj->y, hit_ent->obj->z);
154 			// FIX: U6 specific
155 			// FIX: hit any part of ship, and reduce qty of center
156 			if (hit_ent->obj->obj_n == 412) {
157 				uint8 f = hit_ent->obj->frame_n;
158 				if (f == 9 || f == 15 || f == 11 || f == 13) { // directions
159 					if (hit_ent->obj->qty < 20) hit_ent->obj->qty = 0;
160 					else                      hit_ent->obj->qty -= 20;
161 					if (hit_ent->obj->qty == 0)
162 						game->get_scroll()->display_string("Ship broke!\n");
163 					stop_effect = true;
164 				}
165 			}
166 		}
167 		break;
168 	}
169 	case MESG_ANIM_DONE:
170 		//new ExplosiveEffect(target_loc.x, target_loc.y, 3);
171 		new ExpEffect(EXP_EFFECT_TILE_NUM, MapCoord(target_loc.x, target_loc.y, target_loc.z));
172 		stop_effect = true;
173 		break;
174 	}
175 
176 	if (stop_effect) {
177 		if (hit_actor) {
178 			anim->pause(); //pause to avoid recursive problems when animations are called from actor_hit() lua script.
179 			Game::get_game()->get_script()->call_actor_hit(hit_actor, 32, true);
180 		}
181 		if (msg != MESG_ANIM_DONE) // this msg means anim stopped itself
182 			anim->stop();
183 		game->unpause_all();
184 		usecode->message_obj(obj, MESG_EFFECT_COMPLETE, this);
185 		delete_self();
186 	}
187 	return (0);
188 }
189 
190 #define EXP_EFFECT_SPEED 3
191 
192 
ExpEffect(uint16 tileNum,MapCoord location)193 ExpEffect::ExpEffect(uint16 tileNum, MapCoord location) {
194 	start_loc = location;
195 	finished_tiles = 0;
196 	exp_tile_num = tileNum;
197 	usecode = NULL;
198 	obj = NULL;
199 
200 	start_anim();
201 }
202 
203 
204 /* Pause world & start animation. */
start_anim()205 void ExpEffect::start_anim() {
206 	game->pause_world();
207 	game->pause_anims();
208 	game->pause_user();
209 
210 	targets.resize(16);
211 
212 	targets[0] = MapCoord(start_loc.x + 2, start_loc.y - 1, start_loc.z);
213 	targets[1] = MapCoord(start_loc.x + 1, start_loc.y + 2, start_loc.z);
214 	targets[2] = MapCoord(start_loc.x, start_loc.y - 2, start_loc.z);
215 
216 	targets[3] = MapCoord(start_loc.x + 1, start_loc.y - 1, start_loc.z);
217 	targets[4] = MapCoord(start_loc.x - 1, start_loc.y + 2, start_loc.z);
218 	targets[5] = MapCoord(start_loc.x - 1, start_loc.y - 1, start_loc.z);
219 
220 	targets[6] = MapCoord(start_loc.x - 2, start_loc.y, start_loc.z);
221 	targets[7] = MapCoord(start_loc.x - 1, start_loc.y + 1, start_loc.z);
222 	targets[8] = MapCoord(start_loc.x, start_loc.y + 2, start_loc.z);
223 
224 	targets[9] = MapCoord(start_loc.x - 1, start_loc.y - 2, start_loc.z);
225 	targets[10] = MapCoord(start_loc.x - 2, start_loc.y - 1, start_loc.z);
226 	targets[11] = MapCoord(start_loc.x - 2, start_loc.y + 1, start_loc.z);
227 
228 	targets[12] = MapCoord(start_loc.x + 2, start_loc.y + 1, start_loc.z);
229 	targets[13] = MapCoord(start_loc.x + 2, start_loc.y, start_loc.z);
230 	targets[14] = MapCoord(start_loc.x + 1, start_loc.y + 1, start_loc.z);
231 	targets[15] = MapCoord(start_loc.x + 1, start_loc.y - 2, start_loc.z);
232 
233 
234 	anim = new ProjectileAnim(exp_tile_num, &start_loc, targets, EXP_EFFECT_SPEED, true);
235 	add_anim(anim);
236 
237 }
238 
ProjectileEffect(uint16 tileNum,MapCoord start,MapCoord target,uint8 speed,bool trailFlag,uint16 initialTileRotation,uint16 rotationAmount,uint8 src_y_offset)239 ProjectileEffect::ProjectileEffect(uint16 tileNum, MapCoord start, MapCoord target, uint8 speed, bool trailFlag, uint16 initialTileRotation, uint16 rotationAmount, uint8 src_y_offset) {
240 	vector<MapCoord> t;
241 	t.push_back(target);
242 
243 	init(tileNum, start, t, speed, trailFlag, initialTileRotation, rotationAmount, src_y_offset);
244 }
245 
ProjectileEffect(uint16 tileNum,MapCoord start,vector<MapCoord> t,uint8 speed,bool trailFlag,uint16 initialTileRotation)246 ProjectileEffect::ProjectileEffect(uint16 tileNum, MapCoord start, vector<MapCoord> t, uint8 speed, bool trailFlag, uint16 initialTileRotation) {
247 	init(tileNum, start, t, speed, trailFlag, initialTileRotation, 0, 0);
248 }
249 
init(uint16 tileNum,MapCoord start,vector<MapCoord> t,uint8 speed,bool trailFlag,uint16 initialTileRotation,uint16 rotationAmount,uint8 src_y_offset)250 void ProjectileEffect::init(uint16 tileNum, MapCoord start, vector<MapCoord> t, uint8 speed, bool trailFlag, uint16 initialTileRotation, uint16 rotationAmount, uint8 src_y_offset) {
251 	finished_tiles = 0;
252 
253 	tile_num = tileNum;
254 	start_loc = start;
255 	anim_speed = speed;
256 	trail = trailFlag;
257 	initial_tile_rotation = initialTileRotation;
258 	rotation_amount = rotationAmount;
259 
260 	src_tile_y_offset = src_y_offset;
261 	targets = t;
262 
263 	start_anim();
264 }
265 
266 /* Pause world & start animation. */
start_anim()267 void ProjectileEffect::start_anim() {
268 	game->pause_world();
269 	//game->pause_anims();
270 	game->pause_user();
271 
272 	add_anim(new ProjectileAnim(tile_num, &start_loc, targets, anim_speed, trail, initial_tile_rotation, rotation_amount, src_tile_y_offset));
273 
274 }
275 /* Handle messages from animation. Hit actors & walls. */
callback(uint16 msg,CallBack * caller,void * msg_data)276 uint16 ProjectileEffect::callback(uint16 msg, CallBack *caller, void *msg_data) {
277 	bool stop_effect = false;
278 	switch (msg) {
279 	case MESG_ANIM_HIT_WORLD: {
280 		MapCoord *hit_loc = static_cast<MapCoord *>(msg_data);
281 		Tile *tile = game->get_game_map()->get_tile(hit_loc->x, hit_loc->y,
282 		             hit_loc->z);
283 		if (tile->flags1 & TILEFLAG_WALL) {
284 			//new ExplosiveEffect(hit_loc->x, hit_loc->y, 2);
285 			stop_effect = true;
286 		}
287 		break;
288 	}
289 	case MESG_ANIM_HIT: {
290 		MapEntity *hit_ent = static_cast<MapEntity *>(msg_data);
291 		hit_entities.push_back(*hit_ent);
292 		break;
293 	}
294 	case MESG_ANIM_DONE:
295 		//new ExplosiveEffect(target_loc.x, target_loc.y, 3);
296 		stop_effect = true;
297 		break;
298 	}
299 
300 	if (stop_effect) {
301 		//finished_tiles++;
302 
303 		if (msg != MESG_ANIM_DONE) // this msg means anim stopped itself
304 			((NuvieAnim *)caller)->stop();
305 		//if(finished_tiles == 16)
306 		// {
307 		game->unpause_world();
308 		game->unpause_user();
309 		game->unpause_anims();
310 		//usecode->message_obj(obj, MESG_EFFECT_COMPLETE, this);
311 		delete_self();
312 		// }
313 	}
314 	return (0);
315 }
316 
317 
318 /*** TimedEffect ***/
start_timer(uint32 delay)319 void TimedEffect::start_timer(uint32 delay) {
320 	if (!timer)
321 		timer = new TimedCallback(this, NULL, delay, true);
322 }
323 
324 
stop_timer()325 void TimedEffect::stop_timer() {
326 	if (timer) {
327 		timer->clear_target();
328 		timer = NULL;
329 	}
330 }
331 
332 
333 /*** QuakeEffect ***/
334 /* Shake the visible play area around for `duration' milliseconds. Magnitude
335  * determines the speed of movement. An actor may be selected to keep the
336  * MapWindow centered on after the Quake.
337  */
QuakeEffect(uint8 magnitude,uint32 duration,Actor * keep_on)338 QuakeEffect::QuakeEffect(uint8 magnitude, uint32 duration, Actor *keep_on) {
339 	// single use only, so MapWindow doesn't keep moving away from center
340 	// ...and do nothing if magnitude isn't usable
341 	if (current_quake || magnitude == 0) {
342 		delete_self();
343 		return;
344 	}
345 	current_quake = this; // cleared in timer function
346 
347 	map_window = game->get_map_window();
348 	stop_time = game->get_clock()->get_ticks() + duration;
349 	strength = magnitude;
350 
351 	// get random direction (always move left-right more than up-down)
352 	init_directions();
353 
354 	map_window->get_pos(&orig.x, &orig.y);
355 	map_window->get_level(&orig.z);
356 	orig_actor = keep_on;
357 	map_window->set_freeze_blacking_location(true);
358 
359 	start_timer(strength * 5);
360 }
361 
362 
~QuakeEffect()363 QuakeEffect::~QuakeEffect() {
364 }
365 
366 
367 /* On TIMED: Move map.
368  */
callback(uint16 msg,CallBack * caller,void * msg_data)369 uint16 QuakeEffect::callback(uint16 msg, CallBack *caller, void *msg_data) {
370 	//  uint8 twice_strength = strength * 2;
371 	if (msg != MESG_TIMED)
372 		return (0);
373 	if (game->get_clock()->get_ticks() >= stop_time) {
374 		stop_quake();
375 		return (0);
376 	}
377 	recenter_map();
378 	map_window->shiftMapRelative(sx, sy);
379 
380 	// move in opposite direction on next call
381 	if (sx == -(4 * strength) || sx == (4 * strength))
382 		sx = (sx == -(4 * strength)) ? (2 * strength) : -(2 * strength);
383 	else if (sx == -(2 * strength) || sx == (2 * strength))
384 		sx = 0;
385 
386 	if (sy == -(2 * strength) || sy == (2 * strength))
387 		sy = 0;
388 
389 	if (sx == 0 && sy == 0)
390 		init_directions();
391 	return (0);
392 }
393 
394 
395 /* Finish effect. Move map back to initial position.
396  */
stop_quake()397 void QuakeEffect::stop_quake() {
398 	current_quake = NULL;
399 	map_window->set_freeze_blacking_location(false);
400 	recenter_map();
401 	delete_self();
402 }
403 
404 
405 /* Set sx,sy to a random direction. (always move left-right more than up-down)
406  */
init_directions()407 void QuakeEffect::init_directions() {
408 	uint8 dir = NUVIE_RAND() % 8;
409 	sx = 0;
410 	sy = 0;
411 
412 	switch (dir) {
413 	case NUVIE_DIR_N :
414 		sy = -(strength * 2);
415 		break;
416 	case NUVIE_DIR_NE :
417 		sx = (strength * 4);
418 		sy = -(strength * 2);
419 		break;
420 	case NUVIE_DIR_E :
421 		sx = (strength * 4);
422 		break;
423 	case NUVIE_DIR_SE :
424 		sx = (strength * 4);
425 		sy = (strength * 2);
426 		break;
427 	case NUVIE_DIR_S :
428 		sy = (strength * 2);
429 		break;
430 	case NUVIE_DIR_SW :
431 		sx = -(strength * 4);
432 		sy = (strength * 2);
433 		break;
434 	case NUVIE_DIR_W :
435 		sx = -(strength * 4);
436 		break;
437 	case NUVIE_DIR_NW :
438 		sx = -(strength * 4);
439 		sy = -(strength * 2);
440 		break;
441 	}
442 }
443 
444 
445 /* Center map on original actor or move to original location.
446  */
recenter_map()447 void QuakeEffect::recenter_map() {
448 	if (orig_actor)
449 		map_window->centerMapOnActor(orig_actor);
450 	else
451 		map_window->moveMap(orig.x, orig.y, orig.z);
452 }
453 
454 
455 /*** HitEffect ***/
456 /* Hit target actor. FIXME: implement duration and hitting a location
457  */
HitEffect(Actor * target,uint32 duration)458 HitEffect::HitEffect(Actor *target, uint32 duration) {
459 	game->pause_user();
460 	add_anim(new HitAnim(target));
461 	Game::get_game()->get_sound_manager()->playSfx(NUVIE_SFX_HIT); //FIXME use NUVIE_SFX_SAMPLE defines here.
462 }
463 
HitEffect(MapCoord location)464 HitEffect::HitEffect(MapCoord location) {
465 	game->pause_user();
466 	add_anim(new HitAnim(&location));
467 	Game::get_game()->get_sound_manager()->playSfx(NUVIE_SFX_HIT); //FIXME use NUVIE_SFX_SAMPLE defines here.
468 }
469 
470 /* On ANIM_DONE: end
471  */
callback(uint16 msg,CallBack * caller,void * msg_data)472 uint16 HitEffect::callback(uint16 msg, CallBack *caller, void *msg_data) {
473 	if (msg == MESG_ANIM_DONE) {
474 		game->unpause_user();
475 		delete_self();
476 	}
477 	return (0);
478 }
479 
TextEffect(Std::string text)480 TextEffect::TextEffect(Std::string text) { // default somewhat centered on player for cheat messages
481 	MapWindow *map_window = game->get_map_window();
482 	if (!map_window || map_window->Status() != WIDGET_VISIBLE) // scripted sequence like intro and intro menu
483 		return;
484 	MapCoord loc = game->get_player()->get_actor()->get_location();
485 	loc.x = (loc.x - map_window->get_cur_x() - 2) * 16;
486 	loc.y = (loc.y - map_window->get_cur_y() - 1) * 16;
487 
488 	add_anim(new TextAnim(text, loc, 1500));
489 }
490 
491 /*** TextEffect ***/
492 /* Print Text to MapWindow for duration
493  */
TextEffect(Std::string text,MapCoord location)494 TextEffect::TextEffect(Std::string text, MapCoord location) {
495 	add_anim(new TextAnim(text, location, 1500));
496 }
497 
498 /* On ANIM_DONE: end
499  */
callback(uint16 msg,CallBack * caller,void * msg_data)500 uint16 TextEffect::callback(uint16 msg, CallBack *caller, void *msg_data) {
501 	if (msg == MESG_ANIM_DONE) {
502 		delete_self();
503 	}
504 	return (0);
505 }
506 
507 /*** ExplosiveEffect ***/
ExplosiveEffect(uint16 x,uint16 y,uint32 size,uint16 dmg)508 ExplosiveEffect::ExplosiveEffect(uint16 x, uint16 y, uint32 size, uint16 dmg)
509 	: start_at() {
510 	start_at.x = x;
511 	start_at.y = y;
512 	radius = size;
513 	hit_damage = dmg;
514 
515 	start_anim();
516 }
517 
518 
519 /* Pause world & start animation.
520  */
start_anim()521 void ExplosiveEffect::start_anim() {
522 	game->pause_world();
523 	game->pause_user();
524 	add_anim(new ExplosiveAnim(&start_at, radius));
525 }
526 
527 
528 /* Handle messages from animation. Hit actors & objects.
529  */
callback(uint16 msg,CallBack * caller,void * msg_data)530 uint16 ExplosiveEffect::callback(uint16 msg, CallBack *caller, void *msg_data) {
531 	bool stop_effect = false;
532 	switch (msg) {
533 	case MESG_ANIM_HIT: {
534 		MapEntity *hit_ent = static_cast<MapEntity *>(msg_data);
535 		if (hit_ent->entity_type == ENT_ACTOR) {
536 			if (hit_damage != 0) // hit actor if effect causes damage
537 				hit_ent->actor->hit(hit_damage);
538 		} else if (hit_ent->entity_type == ENT_OBJ) {
539 			DEBUG(0, LEVEL_DEBUGGING, "Explosion hit object %d (%x,%x)\n", hit_ent->obj->obj_n, hit_ent->obj->x, hit_ent->obj->y);
540 			stop_effect = hit_object(hit_ent->obj);
541 		}
542 		break;
543 	}
544 	case MESG_ANIM_DONE:
545 		stop_effect = true;
546 		break;
547 	}
548 
549 	if (stop_effect) {
550 		if (msg != MESG_ANIM_DONE)
551 			anim->stop();
552 		game->unpause_world();
553 		game->unpause_user();
554 		delete_self();
555 	}
556 	return (0);
557 }
558 
559 
560 /* UseCodeExplosiveEffect: before deleting send message to source object
561  */
delete_self()562 void UseCodeExplosiveEffect::delete_self() {
563 	if (obj)
564 		game->get_usecode()->message_obj(obj, MESG_EFFECT_COMPLETE, this);
565 	Effect::delete_self();
566 }
567 
568 
569 /* The explosion hit an object.
570  * Returns true if the effect should end, false to continue.
571  */
hit_object(Obj * hit_obj)572 bool UseCodeExplosiveEffect::hit_object(Obj *hit_obj) {
573 	// ignite & destroy powder kegs (U6)
574 	if (hit_obj->obj_n == 223 && hit_obj != original_obj) {
575 		// FIXME: this doesn't belong here (U6/obj specific)
576 		uint16 x = hit_obj->x, y = hit_obj->y;
577 		game->get_obj_manager()->remove_obj_from_map(hit_obj);
578 		delete_obj(hit_obj);
579 		if (obj) // pass our source obj on to next effect as original_obj
580 			new UseCodeExplosiveEffect(NULL, x, y, 2, hit_damage, obj);
581 		else // pass original_obj on to next effect
582 			new UseCodeExplosiveEffect(NULL, x, y, 2, hit_damage, original_obj);
583 	}
584 	return (false);
585 }
586 
587 
588 /*** ThrowObjectEffect ***/
ThrowObjectEffect()589 ThrowObjectEffect::ThrowObjectEffect() {
590 	obj_manager = game->get_obj_manager();
591 
592 	anim = NULL;
593 	throw_obj = NULL;
594 	throw_tile = 0;
595 	throw_speed = 0;
596 	degrees = 0;
597 	stop_flags = 0;
598 }
599 
600 
start_anim()601 void ThrowObjectEffect::start_anim() {
602 	game->pause_anims();
603 	game->pause_world();
604 	game->pause_user();
605 
606 	assert(throw_tile || throw_obj); // make sure it was properly initialized
607 	assert(throw_speed != 0);
608 
609 	if (throw_obj)
610 		anim = new TossAnim(throw_obj, degrees, start_at, stop_at, throw_speed, stop_flags);
611 	else
612 		anim = new TossAnim(throw_tile, start_at, stop_at, throw_speed, stop_flags);
613 	add_anim(anim);
614 }
615 
616 
617 /* Object has stopped. */
hit_target()618 void ThrowObjectEffect::hit_target() {
619 	if (anim)
620 		anim->stop();
621 	game->unpause_all();
622 	delete_self();
623 }
624 
625 
626 /* The animation will travel from original object location to drop location if
627  * NULL actor is specified.
628  */
DropEffect(Obj * obj,uint16 qty,Actor * actor,MapCoord * drop_loc)629 DropEffect::DropEffect(Obj *obj, uint16 qty, Actor *actor, MapCoord *drop_loc) {
630 	drop_from_actor = actor;
631 	start_at = drop_from_actor ? drop_from_actor->get_location() : MapCoord(obj->x, obj->y, obj->z);
632 	stop_at = *drop_loc;
633 	degrees = 90;
634 
635 	get_obj(obj, qty); // remove from actor, set throw_obj
636 
637 	if (start_at != stop_at) {
638 		throw_speed = 192; // animation speed
639 		start_anim();
640 	} else
641 		hit_target(); // done already? why bother calling DropEffect? :p
642 }
643 
644 
645 /* Take `qty' objects of a stack if necessary, and remove from the actor's
646  * inventory. Set `throw_obj'.
647  */
get_obj(Obj * obj,uint16 qty)648 void DropEffect::get_obj(Obj *obj, uint16 qty) {
649 	throw_obj = obj_manager->get_obj_from_stack(obj, qty);
650 	if (drop_from_actor)
651 		drop_from_actor->inventory_remove_obj(throw_obj);
652 }
653 
654 
655 /* On ANIM_HIT_WORLD: end at hit location
656  * On ANIM_DONE: end
657  */
callback(uint16 msg,CallBack * caller,void * msg_data)658 uint16 DropEffect::callback(uint16 msg, CallBack *caller, void *msg_data) {
659 	// if throw_obj is NULL, object already hit target
660 	if (!throw_obj || (msg != MESG_ANIM_DONE && msg != MESG_ANIM_HIT_WORLD))
661 		return (0);
662 
663 	if (msg == MESG_ANIM_HIT_WORLD && stop_at == *(MapCoord *)msg_data
664 	        && anim)
665 		anim->stop();
666 
667 	hit_target();
668 	return (0);
669 }
670 
671 
672 /* Add object to map. (call before completing effect) */
hit_target()673 void DropEffect::hit_target() {
674 	throw_obj->x = stop_at.x;
675 	throw_obj->y = stop_at.y;
676 	throw_obj->z = stop_at.z;
677 
678 	//FIXME drop logic should probably be in lua script.
679 	if (drop_from_actor && obj_manager->is_breakable(throw_obj) && start_at.distance(stop_at) > 1) {
680 		nuvie_game_t game_type = game->get_game_type();
681 		if (game_type == NUVIE_GAME_U6 && throw_obj->obj_n == OBJ_U6_DRAGON_EGG) {
682 			throw_obj->frame_n = 1; //brake egg.
683 			obj_manager->add_obj(throw_obj, OBJ_ADD_TOP);
684 		} else if (game_type == NUVIE_GAME_U6 && throw_obj->obj_n == OBJ_U6_MIRROR) {
685 			throw_obj->frame_n = 2; //break mirror.
686 			obj_manager->add_obj(throw_obj, OBJ_ADD_TOP);
687 		} else {
688 			// remove items from container if there is one
689 			if (game->get_usecode()->is_container(throw_obj)) {
690 				U6Link *link = throw_obj->container->start();
691 				for (; link != NULL; link = throw_obj->container->start()) {
692 					Obj *obj = (Obj *)link->data;
693 					obj_manager->moveto_map(obj, stop_at);
694 				}
695 			}
696 			obj_manager->unlink_from_engine(throw_obj);
697 			delete_obj(throw_obj);
698 		}
699 
700 		Game::get_game()->get_scroll()->display_string("\nIt broke!\n");
701 		Game::get_game()->get_sound_manager()->playSfx(NUVIE_SFX_BROKEN_GLASS);
702 	} else {
703 		Obj *dest_obj = obj_manager->get_obj(stop_at.x, stop_at.y, stop_at.z);
704 		if (obj_manager->can_store_obj(dest_obj, throw_obj))
705 			obj_manager->moveto_container(throw_obj, dest_obj);
706 		else
707 			obj_manager->add_obj(throw_obj, OBJ_ADD_TOP);
708 	}
709 	throw_obj = NULL; // set as dropped
710 
711 	// not appropriate to do "Events::endAction(true)" from here to display
712 	// prompt, as we MUST unpause_user() in ThrowObjectEffect::hit_target, and
713 	// that would be redundant and may not unpause everything if wait mode was
714 	// already cancelled... so just prompt
715 	game->get_scroll()->display_string("\n");
716 	game->get_scroll()->display_prompt();
717 
718 	game->get_map_window()->updateBlacking();
719 	ThrowObjectEffect::hit_target(); // calls delete_self()
720 }
721 
722 
723 /*** MissileEffect ***/
MissileEffect(uint16 tile_num,uint16 obj_n,const MapCoord & source,const MapCoord & target,uint8 dmg,uint8 intercept,uint16 speed)724 MissileEffect::MissileEffect(uint16 tile_num, uint16 obj_n, const MapCoord &source,
725 							 const MapCoord &target, uint8 dmg,
726 							 uint8 intercept, uint16 speed) {
727 	actor_manager = game->get_actor_manager();
728 	hit_actor = 0;
729 	hit_obj = 0;
730 
731 	init(tile_num, obj_n, source, target, dmg, intercept, speed);
732 }
733 
734 /* Start effect. If target is unset then the actor is the target. */
init(uint16 tile_num,uint16 obj_n,const MapCoord & source,const MapCoord & target,uint32 dmg,uint8 intercept,uint32 speed)735 void MissileEffect::init(uint16 tile_num, uint16 obj_n,
736 						 const MapCoord &source, const MapCoord &target,
737 						 uint32 dmg, uint8 intercept, uint32 speed) {
738 	assert(tile_num || obj_n); // at least obj_n must be set
739 	// (although it might work if throw_obj is already set)
740 	assert(speed != 0);
741 	assert(intercept != 0); // must hit target
742 
743 	if (obj_n != 0)
744 		throw_obj = new_obj(obj_n, 0, 0, 0, 0);
745 	if (tile_num != 0)
746 		throw_tile = game->get_tile_manager()->get_tile(tile_num);
747 	else if (throw_obj != 0)
748 		throw_tile = obj_manager->get_obj_tile(throw_obj->obj_n, 0);
749 	throw_speed = speed;
750 	hit_damage = dmg;
751 
752 	start_at = source;
753 	stop_at = target;
754 	stop_flags = intercept;
755 	assert(stop_at != start_at); // Hmm, can't attack self with boomerang then
756 //    if (stop_at != start_at) {
757 //      start_at.x=WRAPPED_COORD(start_at.x+1,start_at.z);
758 //      start_at.y=WRAPPED_COORD(start_at.y-1,start_at.z);
759 //    }
760 
761 	// set tile rotation here based on obj_num
762 	if (throw_obj != 0) {
763 		if (throw_obj->obj_n == OBJ_U6_SPEAR)
764 			degrees = 315;
765 		if (throw_obj->obj_n == OBJ_U6_THROWING_AXE)
766 			degrees = 0;
767 		if (throw_obj->obj_n == OBJ_U6_DAGGER)
768 			degrees = 315;
769 		if (throw_obj->obj_n == OBJ_U6_ARROW)
770 			degrees = 270;
771 		if (throw_obj->obj_n == OBJ_U6_BOLT)
772 			degrees = 270;
773 	}
774 	start_anim();
775 }
776 
777 /* On HIT: hit Actor or Obj and end
778  * On HIT_WORLD: end at hit location, hit Actor or Obj, else place obj
779  * On DONE: end
780  */
callback(uint16 msg,CallBack * caller,void * msg_data)781 uint16 MissileEffect::callback(uint16 msg, CallBack *caller, void *msg_data) {
782 	if (msg != MESG_ANIM_DONE && msg != MESG_ANIM_HIT_WORLD && msg != MESG_ANIM_HIT)
783 		return 0;
784 
785 	if (msg == MESG_ANIM_DONE) {
786 		// will always hit anything at the target
787 		// FIXME: only hit breakable objects like doors
788 //        hit_obj = obj_manager->get_obj(stop_at.x,stop_at.y,stop_at.z);
789 		hit_actor = actor_manager->get_actor(stop_at.x, stop_at.y, stop_at.z);
790 		hit_target();
791 	} else if (msg == MESG_ANIM_HIT && ((MapEntity *)msg_data)->entity_type == ENT_ACTOR) {
792 		if (hit_damage != 0)
793 			hit_actor = ((MapEntity *)msg_data)->actor;
794 		hit_target();
795 	} else if (msg == MESG_ANIM_HIT && ((MapEntity *)msg_data)->entity_type == ENT_OBJ) {
796 		// FIXME: only hit breakable objects like doors
797 		/*        if(hit_damage != 0)
798 		            hit_obj = ((MapEntity*)msg_data)->obj;
799 		        hit_target();*/
800 	}
801 	// MESG_ANIM_HIT_WORLD
802 	hit_blocking();
803 	return 0;
804 }
805 
806 /* Hit target or add object to map. (call before completing effect) */
hit_target()807 void MissileEffect::hit_target() {
808 	if (hit_actor) {
809 		hit_actor->hit(hit_damage, ACTOR_FORCE_HIT);
810 		delete_obj(throw_obj);
811 		throw_obj = 0; // don't drop
812 	} else if (hit_obj) {
813 		if (hit_obj->qty < hit_damage)
814 			hit_obj->qty = 0;
815 		else hit_obj->qty -= hit_damage;
816 		delete_obj(throw_obj);
817 		throw_obj = 0; // don't drop
818 	}
819 	if (throw_obj != 0) {
820 		throw_obj->x = stop_at.x;
821 		throw_obj->y = stop_at.y;
822 		throw_obj->z = stop_at.z;
823 		throw_obj->status |= OBJ_STATUS_OK_TO_TAKE | OBJ_STATUS_TEMPORARY;
824 		if (obj_manager->is_stackable(throw_obj))
825 			throw_obj->qty = 1; // stackable objects must have a quantity
826 		obj_manager->add_obj(throw_obj, OBJ_ADD_TOP);
827 		throw_obj = 0;
828 	}
829 
830 	ThrowObjectEffect::hit_target(); // calls delete_self()
831 }
832 
hit_blocking()833 void MissileEffect::hit_blocking() {
834 	delete_obj(throw_obj);
835 	ThrowObjectEffect::hit_target();
836 }
837 
838 
839 /*** SleepEffect ***/
840 /* The TimedAdvance is started after the fade-out completes. */
SleepEffect(Std::string until)841 SleepEffect::SleepEffect(Std::string until)
842 	: timer(NULL),
843 	  stop_hour(0),
844 	  stop_minute(0),
845 	  stop_time("") {
846 	stop_time = until;
847 	game->pause_user();
848 	effect_manager->watch_effect(this, new FadeEffect(FADE_PIXELATED, FADE_OUT));
849 }
850 
851 
SleepEffect(uint8 to_hour)852 SleepEffect::SleepEffect(uint8 to_hour)
853 	: timer(NULL),
854 	  stop_hour(to_hour),
855 	  stop_minute(0),
856 	  stop_time("") {
857 	game->pause_user();
858 	effect_manager->watch_effect(this, new FadeEffect(FADE_PIXELATED, FADE_OUT));
859 }
860 
861 
~SleepEffect()862 SleepEffect::~SleepEffect() {
863 	//if(timer) // make sure it doesn't try to call us again
864 	//    timer->clear_target();
865 }
866 
867 
868 /* As with TimedEffect, make sure the timer doesn't try to use callback again.
869  */
delete_self()870 void SleepEffect::delete_self() {
871 	//timer->clear_target(); // this will also stop/delete the TimedAdvance
872 	//timer = NULL;
873 	Effect::delete_self();
874 }
875 
876 
877 /* Resume normal play when requested time has been reached.
878  */
879 //FIXME: need to handle TimedAdvance() errors and fade-in
callback(uint16 msg,CallBack * caller,void * data)880 uint16 SleepEffect::callback(uint16 msg, CallBack *caller, void *data) {
881 	uint8 hour = Game::get_game()->get_clock()->get_hour();
882 	uint8 minute = Game::get_game()->get_clock()->get_minute();
883 
884 	// waited for FadeEffect
885 	if (msg == MESG_EFFECT_COMPLETE) {
886 		if (timer == NULL) { // starting
887 			if (stop_time != "") { // advance to start time
888 				timer = new TimedAdvance(stop_time, 360); // 6 hours per second FIXME: it isnt going anywhere near that fast
889 				timer->set_target(this);
890 				timer->get_time_from_string(stop_hour, stop_minute, stop_time);
891 				// stop_hour & stop_minute are checked each hour
892 			} else { // advance a number of hours
893 				uint16 advance_h = (hour == stop_hour) ? 24
894 				                   : (hour < stop_hour) ? (stop_hour - hour)
895 				                   : (24 - (hour - stop_hour));
896 				timer = new TimedAdvance(advance_h, 360);
897 				timer->set_target(this);
898 				stop_minute = minute;
899 			}
900 		} else { // stopping
901 			Party *party = game->get_party();
902 			for (int s = 0; s < party->get_party_size(); s++) {
903 				Actor *actor = party->get_actor(s);
904 
905 				//heal actors.
906 				uint8 hp_diff = actor->get_maxhp() - actor->get_hp();
907 				if (hp_diff > 0) {
908 					if (hp_diff == 1)
909 						hp_diff = 2;
910 					actor->set_hp(actor->get_hp() + NUVIE_RAND() % (hp_diff / 2) + hp_diff / 2);
911 				}
912 			}
913 			game->unpause_user();
914 			delete_self();
915 		}
916 		return (0);
917 	}
918 	// assume msg == MESG_TIMED; will stop after effect completes
919 	if (hour == stop_hour && minute >= stop_minute)
920 		effect_manager->watch_effect(this, new FadeEffect(FADE_PIXELATED, FADE_IN));
921 
922 	return (0);
923 }
924 
925 
926 /*** FadeEffect ***/
FadeEffect(FadeType fade,FadeDirection dir,uint32 color,uint32 speed)927 FadeEffect::FadeEffect(FadeType fade, FadeDirection dir, uint32 color, uint32 speed) {
928 	speed = speed ? speed : game->get_map_window()->get_win_area() * 2116; // was 256000
929 	init(fade, dir, color, NULL, 0, 0, speed);
930 }
931 
932 /* Takes an image to fade from/to. */
FadeEffect(FadeType fade,FadeDirection dir,Graphics::ManagedSurface * capture,uint32 speed)933 FadeEffect::FadeEffect(FadeType fade, FadeDirection dir, Graphics::ManagedSurface *capture, uint32 speed) {
934 	speed = speed ? speed : game->get_map_window()->get_win_area() * 1620; // was 196000
935 	init(fade, dir, 0, capture, 0, 0, speed); // color=black
936 }
937 
938 /* Localizes effect to specific coordinates. The size of the effect is determined
939  * by the size of the image. */
FadeEffect(FadeType fade,FadeDirection dir,Graphics::ManagedSurface * capture,uint16 x,uint16 y,uint32 speed)940 FadeEffect::FadeEffect(FadeType fade, FadeDirection dir, Graphics::ManagedSurface *capture, uint16 x, uint16 y, uint32 speed) {
941 	speed = speed ? speed : 1024;
942 	init(fade, dir, 0, capture, x, y, speed); // color=black
943 }
944 
945 
init(FadeType fade,FadeDirection dir,uint32 color,Graphics::ManagedSurface * capture,uint16 x,uint16 y,uint32 speed)946 void FadeEffect::init(FadeType fade, FadeDirection dir, uint32 color, Graphics::ManagedSurface *capture, uint16 x, uint16 y, uint32 speed) {
947 	if (current_fade) {
948 		delete_self();
949 		return;
950 	}
951 	current_fade = this; // cleared in dtor
952 
953 	screen = game->get_screen();
954 	map_window = game->get_map_window();
955 	viewport = new Common::Rect(map_window->GetRect());
956 
957 	fade_type = fade;
958 	fade_dir = dir;
959 	fade_speed = speed; // pixels-per-second (to check, not draw)
960 
961 	evtime = prev_evtime = 0;
962 	fade_x = x;
963 	fade_y = y;
964 	fade_from = NULL;
965 	fade_iterations = 0;
966 	if (capture) {
967 		fade_from = new Graphics::ManagedSurface(capture->w, capture->h, capture->format);
968 		fade_from->blitFrom(*capture);
969 	}
970 
971 	if (fade_type == FADE_PIXELATED || fade_type == FADE_PIXELATED_ONTOP) {
972 		pixelated_color = color;
973 		init_pixelated_fade();
974 	} else
975 		init_circle_fade();
976 }
977 
978 
~FadeEffect()979 FadeEffect::~FadeEffect() {
980 	//moved to delete_self
981 }
982 
delete_self()983 void FadeEffect::delete_self() {
984 	if (current_fade == this) { // these weren't init. if FadeEffect didn't start
985 		delete viewport;
986 		if (fade_dir == FADE_IN) // overlay should be empty now, so just delete it
987 			map_window->set_overlay(NULL);
988 		if (fade_from)
989 			SDL_FreeSurface(fade_from);
990 
991 		current_fade = NULL;
992 	}
993 
994 	TimedEffect::delete_self();
995 }
996 
997 /* Start effect. */
init_pixelated_fade()998 void FadeEffect::init_pixelated_fade() {
999 	int fillret = -1; // check error
1000 	overlay = map_window->get_overlay();
1001 	if (overlay != NULL) {
1002 		pixel_count = fade_from ? (fade_from->w) * (fade_from->h)
1003 		              : (overlay->w - fade_x) * (overlay->h - fade_y);
1004 		// clear overlay to fill color or transparent
1005 		if (fade_dir == FADE_OUT) {
1006 			if (fade_from) { // fade from captured surface to transparent
1007 				// put surface on transparent background (not checked)
1008 				fillret = SDL_FillRect(overlay, NULL, uint32(TRANSPARENT_COLOR));
1009 				Common::Rect overlay_rect(fade_x, fade_y, fade_x, fade_y);
1010 				fillret = SDL_BlitSurface(fade_from, NULL, overlay, &overlay_rect);
1011 			} else // fade from transparent to color
1012 				fillret = SDL_FillRect(overlay, NULL, uint32(TRANSPARENT_COLOR));
1013 		} else {
1014 			if (fade_from) // fade from transparent to captured surface
1015 				fillret = SDL_FillRect(overlay, NULL, uint32(TRANSPARENT_COLOR));
1016 			else // fade from color to transparent
1017 				fillret = SDL_FillRect(overlay, NULL, uint32(pixelated_color));
1018 		}
1019 	}
1020 	if (fillret == -1) {
1021 		DEBUG(0, LEVEL_DEBUGGING, "FadeEffect: error creating overlay surface\n");
1022 		delete_self();
1023 		return;
1024 	}
1025 
1026 	// if FADE_PIXELATED_ONTOP is set, place the effect layer above the map border
1027 	map_window->set_overlay_level((fade_type == FADE_PIXELATED)
1028 	                              ? MAP_OVERLAY_DEFAULT : MAP_OVERLAY_ONTOP);
1029 	colored_total = 0;
1030 	start_timer(1); // fire timer continuously
1031 }
1032 
1033 
1034 /* Start effect.
1035  */
init_circle_fade()1036 void FadeEffect::init_circle_fade() {
1037 	delete_self(); // FIXME
1038 }
1039 
1040 
1041 /* Called by the timer as frequently as possible. Do the appropriate
1042  * fade method and stop when the effect is complete.
1043  */
callback(uint16 msg,CallBack * caller,void * data)1044 uint16 FadeEffect::callback(uint16 msg, CallBack *caller, void *data) {
1045 	bool fade_complete = false;
1046 
1047 	// warning: msg is assumed to be CB_TIMED and data is set
1048 	evtime = *(uint32 *)(data);
1049 
1050 	// do effect
1051 	if (fade_type == FADE_PIXELATED || fade_type == FADE_PIXELATED_ONTOP)
1052 		fade_complete = (fade_dir == FADE_OUT) ? pixelated_fade_out() : pixelated_fade_in();
1053 	else /* CIRCLE */
1054 		fade_complete = (fade_dir == FADE_OUT) ? circle_fade_out() : circle_fade_in();
1055 
1056 	// done
1057 	if (fade_complete == true) {
1058 		delete_self();
1059 		return (1);
1060 	}
1061 	return (0);
1062 }
1063 
1064 
1065 /* Scan the overlay, starting at pixel rnum, for a transparent pixel if fading
1066  * out, and a colored pixel if fading in.
1067  * Returns true if a free pixel was found and set as rnum.
1068  */
1069 // FIXME: this probably doesn't work because it only handles 8bpp
find_free_pixel(uint32 & rnum,uint32 pixelCount)1070 inline bool FadeEffect::find_free_pixel(uint32 &rnum, uint32 pixelCount) {
1071 	uint8 scan_color = (fade_dir == FADE_OUT) ? TRANSPARENT_COLOR
1072 	                   : pixelated_color;
1073 	const uint8 *pixels = (const uint8 *)(overlay->getPixels());
1074 
1075 	for (uint32 p = rnum; p < pixelCount; p++) // check all pixels after rnum
1076 		if (pixels[p] == scan_color) {
1077 			rnum = p;
1078 			return (true);
1079 		}
1080 	for (uint32 q = 0; q < rnum; q++) // check all pixels before rnum
1081 		if (pixels[q] == scan_color) {
1082 			rnum = q;
1083 			return (true);
1084 		}
1085 	return (false);
1086 }
1087 
1088 
1089 /* Returns the next pixel to check/colorize. */
1090 #if 0
1091 #warning this crashes if x,y is near boundary
1092 #warning make sure center_thresh doesnt go over boundary
1093 inline uint32 FadeEffect::get_random_pixel(uint16 center_thresh) {
1094 	if (center_x == -1 || center_y == -1)
1095 		return (NUVIE_RAND() % pixel_count);
1096 
1097 	uint16 x = center_x, y = center_y;
1098 	if (center_thresh == 0)
1099 		center_thresh = overlay->w / 2;
1100 	x += (NUVIE_RAND() % (center_thresh * 2)) - center_thresh,
1101 	     y += (NUVIE_RAND() % (center_thresh * 2)) - center_thresh;
1102 	return ((y * overlay->w) + x);
1103 }
1104 #endif
1105 
1106 /* Randomly add pixels of the appropriate color to the overlay. If the color
1107  * is -1, it will be taken from the "fade_from" surface.
1108  * Returns true when the overlay is completely colored.
1109  */
pixelated_fade_core(uint32 pixels_to_check,sint16 fade_to)1110 bool FadeEffect::pixelated_fade_core(uint32 pixels_to_check, sint16 fade_to) {
1111 	Graphics::Surface s = overlay->getSubArea(Common::Rect(0, 0, overlay->w, overlay->h));
1112 	uint8 *pixels = (uint8 *)s.getPixels();
1113 	const uint8 *from_pixels = fade_from ? (const uint8 *)(fade_from->getPixels()) : NULL;
1114 	uint32 p = 0; // scan counter
1115 	uint32 rnum = 0; // pixel index
1116 	uint32 colored = 0; // number of pixels that get colored
1117 	uint16 fade_width = fade_from ? fade_from->w : overlay->w - fade_x;
1118 	uint16 fade_height = fade_from ? fade_from->h : overlay->h - fade_y;
1119 	uint8 color = fade_to;
1120 
1121 	if (fade_to == -1 && fade_from == NULL) {
1122 		return false;
1123 	}
1124 
1125 	while (p < pixels_to_check) {
1126 		uint16 x = uint16(float(NUVIE_RAND()) * (fade_width - 1) / NUVIE_RAND_MAX) + fade_x,
1127 		       y = uint16(float(NUVIE_RAND()) * (fade_height - 1) / NUVIE_RAND_MAX) + fade_y;
1128 		if (x >= overlay->w) x = overlay->w - 1; // prevent overflow if fade_from is too big
1129 		if (y >= overlay->h) y = overlay->h - 1;
1130 		rnum = y * overlay->w + x;
1131 //ERIC        rnum = y*overlay->pitch + x;
1132 
1133 		if (fade_to == -1) { // get color from "fade_from"
1134 			x -= fade_x;
1135 			y -= fade_y;
1136 			color = from_pixels[y * fade_from->w + x];
1137 		}
1138 		if (pixels[rnum] != color) {
1139 			pixels[rnum] = color;
1140 			++colored;
1141 			++colored_total; // another pixel was set
1142 		}
1143 		++p;
1144 	}
1145 	// all but two lines colored
1146 	if (colored_total >= (pixel_count - fade_width * 2) || fade_iterations > FADE_EFFECT_MAX_ITERATIONS) { // fill the rest
1147 		if (fade_to >= 0)
1148 			SDL_FillRect(overlay, NULL, (uint32)fade_to);
1149 		else { // Note: assert(fade_from) if(fade_to < 0)
1150 			Common::Rect fade_from_rect(fade_from->w, (int16)fade_from->h);
1151 			Common::Rect overlay_rect(fade_x, fade_y, fade_x + fade_from->w, fade_y + fade_from->h);
1152 			SDL_BlitSurface(fade_from, &fade_from_rect, overlay, &overlay_rect);
1153 		}
1154 		return (true);
1155 	} else return (false);
1156 }
1157 
1158 
1159 /* Color some of the mapwindow.
1160  * Returns true when all pixels have been filled, and nothing is visible.
1161  */
pixelated_fade_out()1162 bool FadeEffect::pixelated_fade_out() {
1163 	if (fade_from)
1164 		return (pixelated_fade_core(pixels_to_check(), TRANSPARENT_COLOR));
1165 	return (pixelated_fade_core(pixels_to_check(), pixelated_color));
1166 }
1167 
1168 
1169 /* Clear some of the mapwindow.
1170  * Returns true when all colored pixels have been removed, and the MapWindow
1171  * is visible.
1172  */
pixelated_fade_in()1173 bool FadeEffect::pixelated_fade_in() {
1174 	if (fade_from)
1175 		return (pixelated_fade_core(pixels_to_check(), -1));
1176 	return (pixelated_fade_core(pixels_to_check(), TRANSPARENT_COLOR));
1177 }
1178 
1179 
1180 /* Returns the number of pixels that should be checked/colored (based on speed)
1181  * since the previous call.
1182  */
pixels_to_check()1183 uint32 FadeEffect::pixels_to_check() {
1184 	uint32 time_passed = (prev_evtime == 0) ? 0 : evtime - prev_evtime;
1185 	uint32 fraction = 1000 / (time_passed > 0 ? time_passed : 1); // % of second passed, in milliseconds
1186 	uint32 pixels_per_fraction = fade_speed / (fraction > 0 ? fraction : 1);
1187 	prev_evtime = evtime;
1188 	fade_iterations++;
1189 	return (pixels_per_fraction);
1190 }
1191 
1192 
1193 /* Reduce the MapWindow's ambient light level, according to the set speed.
1194  * Returns true when nothing is visible.
1195  */
circle_fade_out()1196 bool FadeEffect::circle_fade_out() {
1197 // FIXME
1198 	return (false);
1199 }
1200 
1201 
1202 /* Add to the MapWindow's ambient light level, according to the set speed.
1203  * Returns true when the light level has returned to normal.
1204  */
circle_fade_in()1205 bool FadeEffect::circle_fade_in() {
1206 // FIXME
1207 	return (false);
1208 }
1209 
1210 
1211 /* Pause game and do FadeEffect.
1212  */
GameFadeInEffect(uint32 color)1213 GameFadeInEffect::GameFadeInEffect(uint32 color)
1214 	: FadeEffect(FADE_PIXELATED_ONTOP, FADE_IN, color) {
1215 	game->pause_user();
1216 }
1217 
1218 
~GameFadeInEffect()1219 GameFadeInEffect::~GameFadeInEffect() {
1220 }
1221 
1222 
1223 /* Identical to FadeEffect, but unpause game when finished.
1224  */
callback(uint16 msg,CallBack * caller,void * data)1225 uint16 GameFadeInEffect::callback(uint16 msg, CallBack *caller, void *data) {
1226 	// done
1227 	if (FadeEffect::callback(msg, caller, data) != 0)
1228 		game->unpause_user();
1229 	return (0);
1230 }
1231 
1232 
FadeObjectEffect(Obj * obj,FadeDirection dir)1233 FadeObjectEffect::FadeObjectEffect(Obj *obj, FadeDirection dir) {
1234 	obj_manager = game->get_obj_manager();
1235 	fade_obj = obj;
1236 	fade_dir = dir;
1237 
1238 	Graphics::ManagedSurface *capture = game->get_map_window()->get_sdl_surface();
1239 	if (fade_dir == FADE_IN) { // fading IN to object, so fade OUT from capture
1240 		effect_manager->watch_effect(this, /* call me */
1241 		                             new FadeEffect(FADE_PIXELATED, FADE_OUT, capture));
1242 		obj_manager->add_obj(fade_obj, OBJ_ADD_TOP);
1243 		game->get_map_window()->updateBlacking(); // object is likely a moongate
1244 	} else if (fade_dir == FADE_OUT) {
1245 		effect_manager->watch_effect(this, /* call me */
1246 		                             new FadeEffect(FADE_PIXELATED, FADE_OUT, capture, 0, 0, game->get_map_window()->get_win_area() * 1058)); //was 128000
1247 //        obj_manager->remove_obj(fade_obj);
1248 		game->get_map_window()->updateBlacking();
1249 	}
1250 	SDL_FreeSurface(capture);
1251 
1252 	game->pause_user();
1253 }
1254 
~FadeObjectEffect()1255 FadeObjectEffect::~FadeObjectEffect() {
1256 	game->unpause_user();
1257 }
1258 
1259 /* Assume FadeEffect is complete. */
callback(uint16 msg,CallBack * caller,void * data)1260 uint16 FadeObjectEffect::callback(uint16 msg, CallBack *caller, void *data) {
1261 	delete_self();
1262 	return (0);
1263 }
1264 
1265 
1266 /* These types of local/vanish effects are slightly longer than a normal Fade.
1267  * FIXME: FadeEffect should take local effect area, or change speed to time.
1268  */
VanishEffect(bool pause_user)1269 VanishEffect::VanishEffect(bool pause_user)
1270 	: input_blocked(pause_user) {
1271 	Graphics::ManagedSurface *capture = game->get_map_window()->get_sdl_surface();
1272 //    effect_manager->watch_effect(this, /* call me */
1273 //                                 new FadeEffect(FADE_PIXELATED, FADE_OUT, capture, 0, 0, 128000));
1274 	effect_manager->watch_effect(this, /* call me */
1275 	                             new FadeEffect(FADE_PIXELATED, FADE_OUT, capture));
1276 	SDL_FreeSurface(capture);
1277 
1278 	if (input_blocked == VANISH_WAIT)
1279 		game->pause_user();
1280 	game->pause_anims();
1281 }
1282 
~VanishEffect()1283 VanishEffect::~VanishEffect() {
1284 	game->unpause_anims();
1285 	if (input_blocked == VANISH_WAIT)
1286 		game->unpause_user();
1287 }
1288 
1289 /* Assume FadeEffect is complete. */
callback(uint16 msg,CallBack * caller,void * data)1290 uint16 VanishEffect::callback(uint16 msg, CallBack *caller, void *data) {
1291 	delete_self();
1292 	return (0);
1293 }
1294 
1295 
1296 /* TileFadeEffect */
TileFadeEffect(MapCoord loc,Tile * from,Tile * to,FadeType type,uint16 speed)1297 TileFadeEffect::TileFadeEffect(MapCoord loc, Tile *from, Tile *to, FadeType type, uint16 speed) {
1298 	anim = NULL;
1299 	to_tile = NULL;
1300 	anim_tile = NULL;
1301 	actor = NULL;
1302 	color_from = color_to = 0;
1303 	inc_reverse = false;
1304 	spd = 0;
1305 	add_anim(new TileFadeAnim(&loc, from, to, speed));
1306 	num_anim_running = 1;
1307 }
1308 
1309 //Fade out actor.
TileFadeEffect(Actor * a,uint16 speed)1310 TileFadeEffect::TileFadeEffect(Actor *a, uint16 speed) {
1311 	inc_reverse = false;
1312 	anim = NULL;
1313 	to_tile = NULL;
1314 	anim_tile = NULL;
1315 	actor = a;
1316 	color_from = color_to = 0;
1317 	spd = speed;
1318 	num_anim_running = 0;
1319 	add_actor_anim();
1320 	actor->hide();
1321 }
1322 
~TileFadeEffect()1323 TileFadeEffect::~TileFadeEffect() {
1324 
1325 }
1326 
add_actor_anim()1327 void TileFadeEffect::add_actor_anim() {
1328 	MapCoord loc = actor->get_location();
1329 	Tile *from = actor->get_tile();
1330 	add_tile_anim(loc, from);
1331 
1332 	Std::list<Obj *> *surrounding_objs = actor->get_surrounding_obj_list();
1333 
1334 	if (surrounding_objs) {
1335 		Std::list<Obj *>::iterator obj_iter;
1336 		for (obj_iter = surrounding_objs->begin(); obj_iter != surrounding_objs->end(); obj_iter++) {
1337 			add_obj_anim(*obj_iter);
1338 		}
1339 	}
1340 }
1341 
add_obj_anim(Obj * obj)1342 void TileFadeEffect::add_obj_anim(Obj *obj) {
1343 	ObjManager *obj_manager = Game::get_game()->get_obj_manager();
1344 	MapCoord loc(obj->x, obj->y, obj->z);
1345 	add_tile_anim(loc, obj_manager->get_obj_tile(obj->obj_n, obj->frame_n));
1346 }
1347 
add_fade_anim(MapCoord loc,Tile * tile)1348 void TileFadeEffect::add_fade_anim(MapCoord loc, Tile *tile) {
1349 	add_anim(new TileFadeAnim(&loc, tile, NULL, spd));
1350 	num_anim_running++;
1351 }
1352 
add_tile_anim(MapCoord loc,Tile * tile)1353 void TileFadeEffect::add_tile_anim(MapCoord loc, Tile *tile) {
1354 	TileManager *tile_manager = Game::get_game()->get_tile_manager();
1355 	uint16 tile_num = tile->tile_num;
1356 
1357 	add_fade_anim(loc, tile);
1358 
1359 	if (tile->dbl_width) {
1360 		tile_num--;
1361 		loc.x -= 1;
1362 		add_fade_anim(loc, tile_manager->get_tile(tile_num));
1363 		loc.x += 1;
1364 	}
1365 
1366 	if (tile->dbl_height) {
1367 		tile_num--;
1368 		loc.y -= 1;
1369 		add_fade_anim(loc, tile_manager->get_tile(tile_num));
1370 		loc.y += 1;
1371 	}
1372 
1373 	if (tile->dbl_width && tile->dbl_height) {
1374 		tile_num--;
1375 		loc.x -= 1;
1376 		loc.y -= 1;
1377 		add_fade_anim(loc, tile_manager->get_tile(tile_num));
1378 		loc.x += 1;
1379 		loc.y += 1;
1380 	}
1381 }
1382 
callback(uint16 msg,CallBack * caller,void * data)1383 uint16 TileFadeEffect::callback(uint16 msg, CallBack *caller, void *data) {
1384 	if (msg == MESG_ANIM_DONE) {
1385 		num_anim_running--;
1386 	}
1387 
1388 	if (num_anim_running == 0) {
1389 		if (inc_reverse) {
1390 			inc_reverse = false;
1391 			add_actor_anim();
1392 			return 0;
1393 		}
1394 
1395 		if (actor)
1396 			actor->show();
1397 
1398 		delete_self();
1399 	}
1400 
1401 	return 0;
1402 }
1403 
1404 
TileBlackFadeEffect(Actor * a,uint8 fade_color,uint16 speed)1405 TileBlackFadeEffect::TileBlackFadeEffect(Actor *a, uint8 fade_color, uint16 speed) {
1406 	init(fade_color, speed);
1407 	actor = a;
1408 	actor->hide();
1409 	add_actor_anim();
1410 }
1411 
TileBlackFadeEffect(Obj * o,uint8 fade_color,uint16 speed)1412 TileBlackFadeEffect::TileBlackFadeEffect(Obj *o, uint8 fade_color, uint16 speed) {
1413 	init(fade_color, speed);
1414 	obj = o;
1415 	obj->set_invisible(true);
1416 	add_obj_anim(obj);
1417 }
1418 
init(uint8 fade_color,uint16 speed)1419 void TileBlackFadeEffect::init(uint8 fade_color, uint16 speed) {
1420 	fade_speed = speed;
1421 	color = fade_color;
1422 	actor = NULL;
1423 	obj = NULL;
1424 	reverse = false;
1425 
1426 	num_anim_running = 0;
1427 }
~TileBlackFadeEffect()1428 TileBlackFadeEffect::~TileBlackFadeEffect() {
1429 
1430 }
1431 
add_actor_anim()1432 void TileBlackFadeEffect::add_actor_anim() {
1433 	MapCoord loc = actor->get_location();
1434 	Tile *from = actor->get_tile();
1435 	add_tile_anim(loc, from);
1436 
1437 	Std::list<Obj *> *surrounding_objs = actor->get_surrounding_obj_list();
1438 
1439 	if (surrounding_objs) {
1440 		Std::list<Obj *>::iterator obj_iter;
1441 		for (obj_iter = surrounding_objs->begin(); obj_iter != surrounding_objs->end(); obj_iter++) {
1442 			add_obj_anim(*obj_iter);
1443 		}
1444 	}
1445 }
1446 
add_obj_anim(Obj * o)1447 void TileBlackFadeEffect::add_obj_anim(Obj *o) {
1448 	MapCoord loc(o);
1449 	Tile *from = Game::get_game()->get_obj_manager()->get_obj_tile(o->obj_n, o->frame_n);
1450 	add_tile_anim(loc, from);
1451 }
1452 
add_tile_anim(MapCoord loc,Tile * tile)1453 void TileBlackFadeEffect::add_tile_anim(MapCoord loc, Tile *tile) {
1454 	TileManager *tile_manager = Game::get_game()->get_tile_manager();
1455 	uint16 tile_num = tile->tile_num;
1456 
1457 	add_anim(new TileFadeAnim(&loc, tile, 0, color, reverse, fade_speed));
1458 	num_anim_running++;
1459 
1460 	if (tile->dbl_width) {
1461 		tile_num--;
1462 		loc.x -= 1;
1463 		add_anim(new TileFadeAnim(&loc, tile_manager->get_tile(tile_num), 0, color, reverse, fade_speed));
1464 		num_anim_running++;
1465 		loc.x += 1;
1466 	}
1467 
1468 	if (tile->dbl_height) {
1469 		tile_num--;
1470 		loc.y -= 1;
1471 		add_anim(new TileFadeAnim(&loc, tile_manager->get_tile(tile_num), 0, color, reverse, fade_speed));
1472 		num_anim_running++;
1473 		loc.y += 1;
1474 	}
1475 
1476 	if (tile->dbl_width && tile->dbl_height) {
1477 		tile_num--;
1478 		loc.x -= 1;
1479 		loc.y -= 1;
1480 		add_anim(new TileFadeAnim(&loc, tile_manager->get_tile(tile_num), 0, color, reverse, fade_speed));
1481 		num_anim_running++;
1482 		loc.x += 1;
1483 		loc.y += 1;
1484 	}
1485 }
1486 
callback(uint16 msg,CallBack * caller,void * data)1487 uint16 TileBlackFadeEffect::callback(uint16 msg, CallBack *caller, void *data) {
1488 	if (msg == MESG_ANIM_DONE) {
1489 		num_anim_running--;
1490 	}
1491 
1492 	if (num_anim_running == 0) {
1493 		if (reverse == false) {
1494 			reverse = true;
1495 			if (actor)
1496 				add_actor_anim();
1497 			else
1498 				add_obj_anim(obj);
1499 
1500 			return 0;
1501 		}
1502 
1503 		if (actor)
1504 			actor->show();
1505 		else
1506 			obj->set_invisible(false);
1507 
1508 		delete_self();
1509 	}
1510 
1511 	return 0;
1512 }
1513 
XorEffect(uint32 eff_ms)1514 XorEffect::XorEffect(uint32 eff_ms)
1515 	: map_window(game->get_map_window()),
1516 	  length(eff_ms) {
1517 	game->pause_user();
1518 	game->pause_anims();
1519 
1520 	init_effect();
1521 }
1522 
init_effect()1523 void XorEffect::init_effect() {
1524 	capture = map_window->get_sdl_surface();
1525 	map_window->set_overlay_level(MAP_OVERLAY_DEFAULT);
1526 	map_window->set_overlay(capture);
1527 
1528 	xor_capture(0xd); // changes black to pink
1529 	start_timer(length);
1530 }
1531 
1532 /* Timer finished. Cleanup. */
callback(uint16 msg,CallBack * caller,void * data)1533 uint16 XorEffect::callback(uint16 msg, CallBack *caller, void *data) {
1534 	if (msg == MESG_TIMED) {
1535 		stop_timer();
1536 		game->unpause_anims();
1537 		game->unpause_user();
1538 		map_window->set_overlay(NULL);
1539 		delete_self();
1540 	}
1541 	return 0;
1542 }
1543 
1544 /* Do binary-xor on each pixel of the mapwindow image.*/
xor_capture(uint8 mod)1545 void XorEffect::xor_capture(uint8 mod) {
1546 	Graphics::Surface s = capture->getSubArea(Common::Rect(0, 0, capture->w, capture->h));
1547 
1548 	uint8 *pixels = (uint8 *)s.getPixels();
1549 	for (int p = 0; p < (capture->w * capture->h); p++)
1550 		pixels[p] ^= mod;
1551 }
1552 
1553 
U6WhitePotionEffect(uint32 eff_ms,uint32 delay_ms,Obj * callback_obj)1554 U6WhitePotionEffect::U6WhitePotionEffect(uint32 eff_ms, uint32 delay_ms, Obj *callback_obj)
1555 	: map_window(game->get_map_window()),
1556 	  state(0), start_length(1000),
1557 	  eff1_length(eff_ms), eff2_length(800),
1558 	  xray_length(delay_ms), capture(0),
1559 	  potion(callback_obj) {
1560 	game->pause_user();
1561 	game->pause_anims();
1562 
1563 	init_effect();
1564 }
1565 
init_effect()1566 void U6WhitePotionEffect::init_effect() {
1567 	// FIXME: play sound, and change state to 1 when sound is complete
1568 	capture = map_window->get_sdl_surface();
1569 	map_window->set_overlay_level(MAP_OVERLAY_DEFAULT);
1570 	map_window->set_overlay(capture);
1571 	start_timer(start_length);
1572 }
1573 
1574 /* The state is changed from current to next when this is called. */
callback(uint16 msg,CallBack * caller,void * data)1575 uint16 U6WhitePotionEffect::callback(uint16 msg, CallBack *caller, void *data) {
1576 	if (msg == MESG_TIMED) {
1577 		stop_timer();
1578 
1579 		if (state == 0) { // start/sound effect
1580 			// FIXME: make start_length a timeout, force sound to stop
1581 			xor_capture(0xd); // changes black to pink
1582 			start_timer(eff1_length);
1583 			state = 1;
1584 		} else if (state == 1) { // xor-effect
1585 			map_window->set_overlay(NULL);
1586 			start_timer(eff2_length);
1587 			state = 2;
1588 		} else if (state == 2) { // character outline
1589 			game->unpause_anims();
1590 			map_window->set_x_ray_view(X_RAY_ON);
1591 			map_window->updateBlacking();
1592 			start_timer(xray_length);
1593 			state = 3;
1594 		} else if (state == 3) { // x-ray
1595 			map_window->set_x_ray_view(X_RAY_OFF);
1596 			map_window->updateBlacking();
1597 			game->unpause_user();
1598 			if (potion)
1599 				game->get_usecode()->message_obj(potion, MESG_EFFECT_COMPLETE, this);
1600 			state = 4; // finished
1601 			delete_self();
1602 		}
1603 	}
1604 	return 0;
1605 }
1606 
1607 /* Do binary-xor on each pixel of the mapwindow image.*/
xor_capture(uint8 mod)1608 void U6WhitePotionEffect::xor_capture(uint8 mod) {
1609 	Graphics::Surface s = capture->getSubArea(Common::Rect(0, 0, capture->w, capture->h));
1610 	uint8 *pixels = (uint8 *)s.getPixels();
1611 	for (int p = 0; p < (capture->w * capture->h); p++)
1612 		pixels[p] ^= mod;
1613 }
1614 
1615 
XRayEffect(uint32 eff_ms)1616 XRayEffect::XRayEffect(uint32 eff_ms) {
1617 	xray_length = eff_ms;
1618 	init_effect();
1619 }
1620 
init_effect()1621 void XRayEffect::init_effect() {
1622 	Game::get_game()->get_map_window()->set_x_ray_view(X_RAY_ON);
1623 	start_timer(xray_length);
1624 }
1625 
callback(uint16 msg,CallBack * caller,void * data)1626 uint16 XRayEffect::callback(uint16 msg, CallBack *caller, void *data) {
1627 	if (msg == MESG_TIMED) {
1628 		stop_timer();
1629 		Game::get_game()->get_map_window()->set_x_ray_view(X_RAY_OFF);
1630 		delete_self();
1631 	}
1632 
1633 	return 0;
1634 }
1635 
PauseEffect()1636 PauseEffect::PauseEffect() {
1637 	game->pause_world();
1638 	// FIXME: need a way to detect any keyboard/mouse input
1639 	game->get_scroll()->set_input_mode(true, "\n", true);
1640 	game->get_scroll()->request_input(this, 0);
1641 }
1642 
1643 /* The effect ends when this is called. (if input is correct) */
callback(uint16 msg,CallBack * caller,void * data)1644 uint16 PauseEffect::callback(uint16 msg, CallBack *caller, void *data) {
1645 	if (msg == MESG_INPUT_READY) {
1646 		game->unpause_world();
1647 		delete_self();
1648 	}
1649 	return 0;
1650 }
1651 
WizardEyeEffect(MapCoord location,uint16 duration)1652 WizardEyeEffect::WizardEyeEffect(MapCoord location, uint16 duration) {
1653 	game->get_map_window()->wizard_eye_start(location, duration, this);
1654 }
1655 
callback(uint16 msg,CallBack * caller,void * data)1656 uint16 WizardEyeEffect::callback(uint16 msg, CallBack *caller, void *data) {
1657 	if (msg == MESG_EFFECT_COMPLETE) {
1658 		delete_self();
1659 	}
1660 
1661 	return 0;
1662 }
1663 
TextInputEffect(const char * allowed_chars,bool can_escape)1664 TextInputEffect::TextInputEffect(const char *allowed_chars, bool can_escape) {
1665 	game->pause_world();
1666 	// FIXME: need a way to detect any keyboard/mouse input
1667 	game->get_gui()->unblock();
1668 	game->get_scroll()->set_input_mode(true, allowed_chars, can_escape);
1669 	game->get_scroll()->request_input(this, 0);
1670 }
1671 
1672 /* The effect ends when this is called. (if input is correct) */
callback(uint16 msg,CallBack * caller,void * data)1673 uint16 TextInputEffect::callback(uint16 msg, CallBack *caller, void *data) {
1674 	if (msg == MESG_INPUT_READY) {
1675 		input = *(Std::string *)data;
1676 		game->unpause_world();
1677 		delete_self();
1678 	}
1679 	return 0;
1680 }
1681 
1682 
PeerEffect(uint16 x,uint16 y,uint8 z,Obj * callback_obj)1683 PeerEffect::PeerEffect(uint16 x, uint16 y, uint8 z, Obj *callback_obj)
1684 	: map_window(game->get_map_window()), overlay(0),
1685 	  gem(callback_obj), area(x, y, z), tile_trans(0),
1686 	  map_pitch(0) {
1687 	uint8 lvl = 0;
1688 	map_window->get_level(&lvl);
1689 	map_pitch = (lvl == 0) ? 1024 : 256;
1690 
1691 	init_effect();
1692 }
1693 
init_effect()1694 void PeerEffect::init_effect() {
1695 	overlay = map_window->get_sdl_surface();
1696 	map_window->set_overlay_level(MAP_OVERLAY_DEFAULT);
1697 	map_window->set_overlay(overlay);
1698 	assert(overlay->w % PEER_TILEW == 0); // overlay must be a multiple of tile size
1699 	SDL_FillRect(overlay, NULL, 0);
1700 
1701 	peer();
1702 }
1703 
delete_self()1704 void PeerEffect::delete_self() {
1705 	map_window->set_overlay(NULL);
1706 	if (gem)
1707 		game->get_usecode()->message_obj(gem, MESG_EFFECT_COMPLETE, this);
1708 	else // FIXME: I don't want prompt display here, so it's also in UseCode,
1709 		// but it has to be here if no object was set. (until we have another
1710 		// way to tell caller effect is complete, and return to player)
1711 	{
1712 		game->get_scroll()->display_string("\n");
1713 		game->get_scroll()->display_prompt();
1714 	}
1715 	Effect::delete_self();
1716 }
1717 
peer()1718 void PeerEffect::peer() {
1719 	uint16 w = overlay->w, h = overlay->h;
1720 	// effect is limited to 48x48 area
1721 	if (overlay->w > 48 * PEER_TILEW) w = 48 * PEER_TILEW;
1722 	if (overlay->h > 48 * PEER_TILEW) h = 48 * PEER_TILEW;
1723 
1724 	MapCoord player_loc = game->get_player()->get_actor()->get_location();
1725 	uint16 cx = player_loc.x - area.x; // rough center of area
1726 	uint16 cy = player_loc.y - area.y;
1727 	area.x %= map_pitch; // we have to wrap here because we use a map buffer
1728 	area.y %= map_pitch;
1729 	uint8 *mapbuffer = new uint8[48 * 48]; // array of tile types/colors
1730 	memset(mapbuffer, 0x00, sizeof(uint8) * 48 * 48); // fill with black
1731 	fill_buffer(mapbuffer, cx, cy);
1732 
1733 	for (int x = 0; x < w; x += PEER_TILEW)
1734 		for (int y = 0; y < h; y += PEER_TILEW) {
1735 			uint16 wx = area.x + x / PEER_TILEW, wy = area.y + y / PEER_TILEW;
1736 			uint8 tile_type = mapbuffer[(wy - area.y) * 48 + (wx - area.x)];
1737 			blit_tile(x, y, tile_type);
1738 			if (tile_type != 0x00) {
1739 				Actor *actor = game->get_actor_manager()->get_actor(wx, wy, area.z);
1740 				if (actor)
1741 					blit_actor(actor);
1742 			}
1743 		}
1744 
1745 	delete [] mapbuffer;
1746 }
1747 
fill_buffer(uint8 * mapbuffer,uint16 x,uint16 y)1748 void PeerEffect::fill_buffer(uint8 *mapbuffer, uint16 x, uint16 y) {
1749 	uint16 wx = area.x + x, wy = area.y + y;
1750 	uint8 *tile = &mapbuffer[y * 48 + x];
1751 
1752 	if (*tile != 0x00)
1753 		return; // already filled
1754 
1755 	wx %= map_pitch; // we have to wrap here because we use a map buffer
1756 	wy %= map_pitch;
1757 	*tile = get_tilemap_type(wx, wy, area.z);
1758 
1759 	// stop at unpassable tiles
1760 	// FIXME: stop at Nothing/black tiles
1761 	if (*tile != peer_tilemap[2]
1762 	        || game->get_game_map()->get_tile(wx, wy, area.z, true)->passable) {
1763 		if (y > 0) {
1764 			if (x > 0) fill_buffer(mapbuffer, x - 1, y - 1); // +-+-+
1765 			if (y > 0) fill_buffer(mapbuffer, x,  y - 1); // |\|/|
1766 			if (x + 1 < 48) fill_buffer(mapbuffer, x + 1, y - 1);
1767 		}
1768 
1769 		if (x > 0)     fill_buffer(mapbuffer, x - 1, y); // +-+-+
1770 		if (x + 1 < 48)  fill_buffer(mapbuffer, x + 1, y);
1771 
1772 		if (y + 1 < 48) {
1773 			if (x > 0) fill_buffer(mapbuffer, x - 1, y + 1); // |/|\|
1774 			fill_buffer(mapbuffer, x,  y + 1); // +-+-+
1775 			if (x + 1 < 48) fill_buffer(mapbuffer, x + 1, y + 1);
1776 		}
1777 	}
1778 }
1779 
blit_tile(uint16 x,uint16 y,uint8 c)1780 inline void PeerEffect::blit_tile(uint16 x, uint16 y, uint8 c) {
1781 	Graphics::Surface s = overlay->getSubArea(Common::Rect(0, 0, overlay->w, overlay->h));
1782 	uint8 *pixels = (uint8 *)s.getPixels();
1783 	for (int j = 0; j < PEER_TILEW && j < overlay->h; j++)
1784 		for (int i = 0; i < PEER_TILEW && i < overlay->w; i++) {
1785 			if (peer_tile[i * PEER_TILEW + j] != tile_trans)
1786 				pixels[overlay->w * (y + j) + (x + i)] = c;
1787 		}
1788 }
1789 
blit_actor(Actor * actor)1790 inline void PeerEffect::blit_actor(Actor *actor) {
1791 	tile_trans = 1;
1792 	blit_tile((actor->get_location().x - area.x)*PEER_TILEW,
1793 	          (actor->get_location().y - area.y)*PEER_TILEW, 0x0F);
1794 	tile_trans = 0;
1795 	if (game->get_player()->get_actor() == actor)
1796 		blit_tile((actor->get_location().x - area.x)*PEER_TILEW,
1797 		          (actor->get_location().y - area.y)*PEER_TILEW, 0x0F);
1798 }
1799 
get_tilemap_type(uint16 wx,uint16 wy,uint8 wz)1800 inline uint8 PeerEffect::get_tilemap_type(uint16 wx, uint16 wy, uint8 wz) {
1801 	Map *map = game->get_game_map();
1802 	// ignore objects (bridges and docks), and show coasts as land
1803 	if (map->is_water(wx, wy, wz, true) && !map->get_tile(wx, wy, wz, true)->passable)
1804 		return peer_tilemap[1];
1805 	if (!map->is_passable(wx, wy, wz))
1806 		return peer_tilemap[2];
1807 	if (map->is_damaging(wx, wy, wz))
1808 		return peer_tilemap[3];
1809 	return peer_tilemap[0]; // ground/passable
1810 }
1811 
WingStrikeEffect(Actor * target_actor)1812 WingStrikeEffect::WingStrikeEffect(Actor *target_actor) {
1813 	actor = target_actor;
1814 
1815 	add_anim(new WingAnim(actor->get_location()));
1816 }
1817 
callback(uint16 msg,CallBack * caller,void * data)1818 uint16 WingStrikeEffect::callback(uint16 msg, CallBack *caller, void *data) {
1819 	switch (msg) {
1820 	case MESG_ANIM_HIT :
1821 		DEBUG(0, LEVEL_DEBUGGING, "hit target!\n");
1822 		Game::get_game()->get_script()->call_actor_hit(actor, (NUVIE_RAND() % 20) + 1);
1823 		break;
1824 	case MESG_ANIM_DONE :
1825 		delete_self();
1826 		break;
1827 	}
1828 	return 0;
1829 }
1830 
HailStormEffect(MapCoord target)1831 HailStormEffect::HailStormEffect(MapCoord target) {
1832 	add_anim(new HailstormAnim(target));
1833 }
1834 
callback(uint16 msg,CallBack * caller,void * data)1835 uint16 HailStormEffect::callback(uint16 msg, CallBack *caller, void *data) {
1836 	switch (msg) {
1837 	case MESG_ANIM_HIT :
1838 		DEBUG(0, LEVEL_DEBUGGING, "hit target!\n");
1839 		Game::get_game()->get_script()->call_actor_hit((Actor *)data, 1);
1840 		break;
1841 	case MESG_ANIM_DONE :
1842 		delete_self();
1843 		break;
1844 	}
1845 	return 0;
1846 }
1847 
1848 /*** AsyncEffect ***/
AsyncEffect(Effect * e)1849 AsyncEffect::AsyncEffect(Effect *e) {
1850 	effect_complete = false;
1851 	effect = e;
1852 	effect->retain();
1853 	effect_manager->watch_effect(this, effect);
1854 }
1855 
~AsyncEffect()1856 AsyncEffect::~AsyncEffect() {
1857 	effect->release();
1858 }
1859 
1860 /* The effect is marked as defunct after run finishes and will be removed from the system.*/
run(bool process_gui_input)1861 void AsyncEffect::run(bool process_gui_input) {
1862 	if (!process_gui_input)
1863 		Game::get_game()->pause_user();
1864 	for (; effect_complete == false;) {
1865 		//spin world
1866 		Game::get_game()->update_once(process_gui_input);
1867 		if (!effect_complete)
1868 			Game::get_game()->update_once_display();
1869 	}
1870 	if (!process_gui_input)
1871 		Game::get_game()->unpause_user();
1872 	delete_self();
1873 }
1874 
callback(uint16 msg,CallBack * caller,void * data)1875 uint16 AsyncEffect::callback(uint16 msg, CallBack *caller, void *data) {
1876 
1877 	// effect complete
1878 	if (msg == MESG_EFFECT_COMPLETE) {
1879 		effect_complete = true;
1880 	}
1881 
1882 	return 0;
1883 }
1884 
1885 } // End of namespace Nuvie
1886 } // End of namespace Ultima
1887