#include <cstring>
#include "nuvieDefs.h"
#include "Game.h"

#include "Actor.h"
#include "Map.h"
#include "Party.h"
#include "Script.h"
#include "AnimManager.h"
#include "MapWindow.h"
#include "TileManager.h"
#include "GameClock.h"
#include "EffectManager.h"
#include "UseCode.h"
#include "MsgScroll.h"
#include "ActorManager.h"
#include "SoundManager.h"
#include "U6objects.h"
#include "Effect.h"
#include "Player.h"

#include <cassert>

#define MESG_ANIM_HIT_WORLD  ANIM_CB_HIT_WORLD
#define MESG_ANIM_HIT        ANIM_CB_HIT
#define MESG_ANIM_DONE       ANIM_CB_DONE
#define MESG_EFFECT_COMPLETE EFFECT_CB_COMPLETE
//#define MESG_INPUT_READY     EVENT_CB_INPUT_READY
#define MESG_INPUT_READY     MSGSCROLL_CB_TEXT_READY

#define TRANSPARENT_COLOR 0xFF /* transparent pixel color */

#define EXP_EFFECT_TILE_NUM 382

QuakeEffect *QuakeEffect::current_quake = NULL;
FadeEffect *FadeEffect::current_fade = NULL;


/* Add self to effect list (for future deletion).
 */
Effect::Effect() : defunct(false)
{
	retain_count = 0;
    game = Game::get_game();
    effect_manager = game->get_effect_manager();
    effect_manager->add_effect(this);
}


Effect::~Effect()
{
    // FIXME: should we remove self from Callbacks' default targets?
}


/* Start managing new animation. (AnimMgr will do that actually, but we point to
 * it and can stop it when necessary)
 */
void Effect::add_anim(NuvieAnim *anim)
{
    anim->set_target(this); // add self as callback target for anim
    game->get_map_window()->get_anim_manager()->new_anim(anim);
}


/* Fire from a cannon in direction: 0=north, 1=east, 2=south, 3=west,
 *                                 -1=use cannon frame
 */
CannonballEffect::CannonballEffect(Obj *src_obj, sint8 direction)
                                   : target_loc()
{
    usecode = game->get_usecode();
    obj = src_obj;
    MapCoord obj_loc(obj->x, obj->y, obj->z);
    target_loc = obj_loc;

    if(direction == -1)
        direction = obj->frame_n;

    uint8 target_dist = 5; // distance that cannonball will fly
    if(direction == NUVIE_DIR_N)
        target_loc.y -= target_dist;
    else if(direction == NUVIE_DIR_E)
        target_loc.x += target_dist;
    else if(direction == NUVIE_DIR_S)
        target_loc.y += target_dist;
    else if(direction == NUVIE_DIR_W)
        target_loc.x -= target_dist;

    start_anim();
}


/* Pause world & start animation. */
void CannonballEffect::start_anim()
{
    MapCoord obj_loc(obj->x, obj->y, obj->z);

    game->pause_world();
    game->pause_anims();
    game->pause_user();

    anim = new TossAnim(game->get_tile_manager()->get_tile(399),
                        obj_loc, target_loc, CANNON_SPEED, TOSS_TO_BLOCKING|TOSS_TO_ACTOR|TOSS_TO_OBJECT);
    add_anim(anim);
}


/* Handle messages from animation. Hit actors & walls. */
uint16 CannonballEffect::callback(uint16 msg, CallBack *caller, void *msg_data)
{
    bool stop_effect = false;
    Actor *hit_actor = NULL;

    switch(msg)
    {
        case MESG_ANIM_HIT_WORLD:
        {
            MapCoord *hit_loc = static_cast<MapCoord *>(msg_data);
            Tile *obj_tile = game->get_obj_manager()->get_obj_tile(hit_loc->x, hit_loc->y, hit_loc->z);
            Tile *tile = game->get_game_map()->get_tile(hit_loc->x,hit_loc->y,
                                                        hit_loc->z);

            if((tile->flags2 & TILEFLAG_MISSILE_BOUNDARY)
               || (obj_tile && (obj_tile->flags2 & TILEFLAG_MISSILE_BOUNDARY)))
            {
                //new ExplosiveEffect(hit_loc->x, hit_loc->y, 2);
            	new ExpEffect(EXP_EFFECT_TILE_NUM, MapCoord(hit_loc->x, hit_loc->y, hit_loc->z));
                stop_effect = true;
            }
            break;
        }
        case MESG_ANIM_HIT:
        {
            MapEntity *hit_ent = static_cast<MapEntity *>(msg_data);
            if(hit_ent->entity_type == ENT_ACTOR)
            {
                //hit_ent->actor->hit(32);
            	hit_actor = hit_ent->actor;
                stop_effect = true;
            }
            if(hit_ent->entity_type == ENT_OBJ)
            {
                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);
                // FIX: U6 specific
                // FIX: hit any part of ship, and reduce qty of center
                if(hit_ent->obj->obj_n == 412)
                {
                    uint8 f = hit_ent->obj->frame_n;
                    if(f == 9 || f == 15 || f == 11 || f == 13) // directions
                    {
                        if(hit_ent->obj->qty < 20) hit_ent->obj->qty = 0;
                        else                      hit_ent->obj->qty -= 20;
                        if(hit_ent->obj->qty == 0)
                            game->get_scroll()->display_string("Ship broke!\n");
                        stop_effect = true;
                    }
                }
            }
            break;
        }
        case MESG_ANIM_DONE:
            //new ExplosiveEffect(target_loc.x, target_loc.y, 3);
        	new ExpEffect(EXP_EFFECT_TILE_NUM, MapCoord(target_loc.x, target_loc.y, target_loc.z));
            stop_effect = true;
            break;
    }

    if(stop_effect)
    {
        if(hit_actor)
        {
        	anim->pause(); //pause to avoid recursive problems when animations are called from actor_hit() lua script.
        	Game::get_game()->get_script()->call_actor_hit(hit_actor, 32, true);
        }
        if(msg != MESG_ANIM_DONE) // this msg means anim stopped itself
            anim->stop();
        game->unpause_all();
        usecode->message_obj(obj, MESG_EFFECT_COMPLETE, this);
        delete_self();
    }
    return(0);
}

#define EXP_EFFECT_SPEED 3


ExpEffect::ExpEffect(uint16 tileNum, MapCoord location)
{
	start_loc = location;
    finished_tiles = 0;
    exp_tile_num = tileNum;
    usecode = NULL;
    obj = NULL;

    start_anim();
}


/* Pause world & start animation. */
void ExpEffect::start_anim()
{
    game->pause_world();
    game->pause_anims();
    game->pause_user();

    targets.resize(16);

    targets[0] = MapCoord(start_loc.x+2,start_loc.y-1,start_loc.z);
    targets[1] = MapCoord(start_loc.x+1,start_loc.y+2,start_loc.z);
    targets[2] = MapCoord(start_loc.x,start_loc.y-2,start_loc.z);

    targets[3] = MapCoord(start_loc.x+1,start_loc.y-1,start_loc.z);
    targets[4] = MapCoord(start_loc.x-1,start_loc.y+2,start_loc.z);
    targets[5] = MapCoord(start_loc.x-1,start_loc.y-1,start_loc.z);

    targets[6] = MapCoord(start_loc.x-2,start_loc.y,start_loc.z);
    targets[7] = MapCoord(start_loc.x-1,start_loc.y+1,start_loc.z);
    targets[8] = MapCoord(start_loc.x,start_loc.y+2,start_loc.z);

    targets[9] = MapCoord(start_loc.x-1,start_loc.y-2,start_loc.z);
    targets[10] = MapCoord(start_loc.x-2,start_loc.y-1,start_loc.z);
    targets[11] = MapCoord(start_loc.x-2,start_loc.y+1,start_loc.z);

    targets[12] = MapCoord(start_loc.x+2,start_loc.y+1,start_loc.z);
    targets[13] = MapCoord(start_loc.x+2,start_loc.y,start_loc.z);
    targets[14] = MapCoord(start_loc.x+1,start_loc.y+1,start_loc.z);
    targets[15] = MapCoord(start_loc.x+1,start_loc.y-2,start_loc.z);


    anim = new ProjectileAnim(exp_tile_num, &start_loc, targets, EXP_EFFECT_SPEED, true);
    add_anim(anim);

}

ProjectileEffect::ProjectileEffect(uint16 tileNum, MapCoord start, MapCoord target, uint8 speed, bool trailFlag, uint16 initialTileRotation, uint16 rotationAmount, uint8 src_y_offset)
{
	vector<MapCoord> t;
	t.push_back(target);

	init(tileNum, start, t, speed, trailFlag, initialTileRotation, rotationAmount, src_y_offset);
}

ProjectileEffect::ProjectileEffect(uint16 tileNum, MapCoord start, vector<MapCoord> t, uint8 speed, bool trailFlag, uint16 initialTileRotation)
{
	init(tileNum, start, t, speed, trailFlag, initialTileRotation, 0, 0);
}

void ProjectileEffect::init(uint16 tileNum, MapCoord start, vector<MapCoord> t, uint8 speed, bool trailFlag, uint16 initialTileRotation, uint16 rotationAmount, uint8 src_y_offset)
{
    finished_tiles = 0;

	tile_num = tileNum;
	start_loc = start;
	anim_speed = speed;
	trail = trailFlag;
	initial_tile_rotation = initialTileRotation;
	rotation_amount = rotationAmount;

	src_tile_y_offset = src_y_offset;
    targets = t;

	start_anim();
}

/* Pause world & start animation. */
void ProjectileEffect::start_anim()
{
    game->pause_world();
    //game->pause_anims();
    game->pause_user();

    add_anim(new ProjectileAnim(tile_num, &start_loc, targets, anim_speed, trail, initial_tile_rotation, rotation_amount, src_tile_y_offset));

}
/* Handle messages from animation. Hit actors & walls. */
uint16 ProjectileEffect::callback(uint16 msg, CallBack *caller, void *msg_data)
{
    bool stop_effect = false;
    switch(msg)
    {
        case MESG_ANIM_HIT_WORLD:
        {
            MapCoord *hit_loc = static_cast<MapCoord *>(msg_data);
            Tile *tile = game->get_game_map()->get_tile(hit_loc->x,hit_loc->y,
                                                        hit_loc->z);
            if(tile->flags1 & TILEFLAG_WALL)
            {
                //new ExplosiveEffect(hit_loc->x, hit_loc->y, 2);
                stop_effect = true;
            }
            break;
        }
        case MESG_ANIM_HIT:
        {
            MapEntity *hit_ent = static_cast<MapEntity *>(msg_data);
            hit_entities.push_back(*hit_ent);
            break;
        }
        case MESG_ANIM_DONE:
            //new ExplosiveEffect(target_loc.x, target_loc.y, 3);
            stop_effect = true;
            break;
    }

    if(stop_effect)
    {
    	//finished_tiles++;

        if(msg != MESG_ANIM_DONE) // this msg means anim stopped itself
            ((NuvieAnim *)caller)->stop();
        //if(finished_tiles == 16)
       // {
			game->unpause_world();
			game->unpause_user();
			game->unpause_anims();
			//usecode->message_obj(obj, MESG_EFFECT_COMPLETE, this);
			delete_self();
       // }
    }
    return(0);
}


/*** TimedEffect ***/
void TimedEffect::start_timer(uint32 delay)
{
    if(!timer)
        timer = new TimedCallback(this, NULL, delay, true);
}


void TimedEffect::stop_timer()
{
    if(timer)
    {
        timer->clear_target();
        timer = NULL;
    }
}


/*** QuakeEffect ***/
/* Shake the visible play area around for `duration' milliseconds. Magnitude
 * determines the speed of movement. An actor may be selected to keep the
 * MapWindow centered on after the Quake.
 */
QuakeEffect::QuakeEffect(uint8 magnitude, uint32 duration, Actor *keep_on)
{
    // single use only, so MapWindow doesn't keep moving away from center
    // ...and do nothing if magnitude isn't usable
    if(current_quake || magnitude == 0)
    {
        delete_self();
        return;
    }
    current_quake = this; // cleared in timer function

    map_window = game->get_map_window();
    stop_time = game->get_clock()->get_ticks() + duration;
    strength = magnitude;

    // get random direction (always move left-right more than up-down)
    init_directions();

    map_window->get_pos(&orig.x, &orig.y);
    map_window->get_level(&orig.z);
    orig_actor = keep_on;
    map_window->set_freeze_blacking_location(true);

    start_timer(strength * 5);
}


QuakeEffect::~QuakeEffect()
{
}


/* On TIMED: Move map.
 */
uint16 QuakeEffect::callback(uint16 msg, CallBack *caller, void *msg_data)
{
  //  uint8 twice_strength = strength * 2;
    if(msg != MESG_TIMED)
        return(0);
    if(game->get_clock()->get_ticks() >= stop_time)
    {
        stop_quake();
        return(0);
    }
    recenter_map();
    map_window->shiftMapRelative(sx, sy);

    // move in opposite direction on next call
    if(sx == -(4*strength) || sx == (4*strength))
        sx = (sx == -(4*strength)) ? (2*strength) : -(2*strength);
    else if(sx == -(2*strength) || sx == (2*strength))
        sx = 0;

    if(sy == -(2*strength) || sy == (2*strength))
        sy = 0;

    if(sx == 0 && sy == 0)
        init_directions();
    return(0);
}


/* Finish effect. Move map back to initial position.
 */
void QuakeEffect::stop_quake()
{
    current_quake = NULL;
    map_window->set_freeze_blacking_location(false);
    recenter_map();
    delete_self();
}


/* Set sx,sy to a random direction. (always move left-right more than up-down)
 */
void QuakeEffect::init_directions()
{
    uint8 dir = NUVIE_RAND() % 8;
    sx = 0; sy = 0;

    switch(dir)
    {
     case NUVIE_DIR_N : sy = -(strength*2); break;
     case NUVIE_DIR_NE : sx = (strength*4); sy = -(strength*2); break;
     case NUVIE_DIR_E : sx = (strength*4); break;
     case NUVIE_DIR_SE : sx = (strength*4); sy = (strength*2); break;
     case NUVIE_DIR_S : sy = (strength*2); break;
     case NUVIE_DIR_SW : sx = -(strength*4); sy = (strength*2); break;
     case NUVIE_DIR_W : sx = -(strength*4); break;
     case NUVIE_DIR_NW : sx = -(strength*4); sy = -(strength*2); break;
    }
}


/* Center map on original actor or move to original location.
 */
void QuakeEffect::recenter_map()
{
    if(orig_actor)
        map_window->centerMapOnActor(orig_actor);
    else
        map_window->moveMap(orig.x, orig.y, orig.z);
}


/*** HitEffect ***/
/* Hit target actor. FIXME: implement duration and hitting a location
 */
HitEffect::HitEffect(Actor *target, uint32 duration)
{
    game->pause_user();
    add_anim(new HitAnim(target));
    Game::get_game()->get_sound_manager()->playSfx(NUVIE_SFX_HIT); //FIXME use NUVIE_SFX_SAMPLE defines here.
}

HitEffect::HitEffect(MapCoord location)
{
    game->pause_user();
    add_anim(new HitAnim(&location));
    Game::get_game()->get_sound_manager()->playSfx(NUVIE_SFX_HIT); //FIXME use NUVIE_SFX_SAMPLE defines here.
}

/* On ANIM_DONE: end
 */
uint16 HitEffect::callback(uint16 msg, CallBack *caller, void *msg_data)
{
    if(msg == MESG_ANIM_DONE)
    {
        game->unpause_user();
        delete_self();
    }
    return(0);
}

TextEffect::TextEffect(std::string text) // default somewhat centered on player for cheat messages
{
	MapWindow *map_window = game->get_map_window();
	if(!map_window || map_window->Status() != WIDGET_VISIBLE) // scripted sequence like intro and intro menu
		return;
	MapCoord loc = game->get_player()->get_actor()->get_location();
	loc.x = (loc.x- map_window->get_cur_x() -2)*16;
	loc.y = (loc.y- map_window->get_cur_y() -1)*16;

	add_anim(new TextAnim(text, loc, 1500));
}

/*** TextEffect ***/
/* Print Text to MapWindow for duration
 */
TextEffect::TextEffect(std::string text, MapCoord location)
{
    add_anim(new TextAnim(text, location, 1500));
}

/* On ANIM_DONE: end
 */
uint16 TextEffect::callback(uint16 msg, CallBack *caller, void *msg_data)
{
    if(msg == MESG_ANIM_DONE)
    {
        delete_self();
    }
    return(0);
}

/*** ExplosiveEffect ***/
ExplosiveEffect::ExplosiveEffect(uint16 x, uint16 y, uint32 size, uint16 dmg)
                                 : start_at()
{
    start_at.x = x; start_at.y = y;
    radius = size;
    hit_damage = dmg;

    start_anim();
}


/* Pause world & start animation.
 */
void ExplosiveEffect::start_anim()
{
    game->pause_world();
    game->pause_user();
    add_anim(new ExplosiveAnim(&start_at, radius));
}


/* Handle messages from animation. Hit actors & objects.
 */
uint16 ExplosiveEffect::callback(uint16 msg, CallBack *caller, void *msg_data)
{
    bool stop_effect = false;
    switch(msg)
    {
        case MESG_ANIM_HIT:
        {
            MapEntity *hit_ent = static_cast<MapEntity *>(msg_data);
            if(hit_ent->entity_type == ENT_ACTOR)
            {
                if(hit_damage != 0) // hit actor if effect causes damage
                    hit_ent->actor->hit(hit_damage);
            }
            else if(hit_ent->entity_type == ENT_OBJ)
            {
                DEBUG(0,LEVEL_DEBUGGING,"Explosion hit object %d (%x,%x)\n", hit_ent->obj->obj_n, hit_ent->obj->x, hit_ent->obj->y);
                stop_effect = hit_object(hit_ent->obj);
            }
            break;
        }
        case MESG_ANIM_DONE:
            stop_effect = true;
            break;
    }

    if(stop_effect)
    {
        if(msg != MESG_ANIM_DONE)
            anim->stop();
        game->unpause_world();
        game->unpause_user();
        delete_self();
    }
    return(0);
}


/* UseCodeExplosiveEffect: before deleting send message to source object
 */
void UseCodeExplosiveEffect::delete_self()
{
    if(obj)
        game->get_usecode()->message_obj(obj, MESG_EFFECT_COMPLETE, this);
    Effect::delete_self();
}


/* The explosion hit an object.
 * Returns true if the effect should end, false to continue.
 */
bool UseCodeExplosiveEffect::hit_object(Obj *hit_obj)
{
    // ignite & destroy powder kegs (U6)
    if(hit_obj->obj_n == 223 && hit_obj != original_obj)
    {
        // FIXME: this doesn't belong here (U6/obj specific)
        uint16 x = hit_obj->x, y = hit_obj->y;
        game->get_obj_manager()->remove_obj_from_map(hit_obj);
        delete_obj(hit_obj);
        if(obj) // pass our source obj on to next effect as original_obj
            new UseCodeExplosiveEffect(NULL, x, y, 2, hit_damage, obj);
        else // pass original_obj on to next effect
            new UseCodeExplosiveEffect(NULL, x, y, 2, hit_damage, original_obj);
    }
    return(false);
}


/*** ThrowObjectEffect ***/
ThrowObjectEffect::ThrowObjectEffect()
{
    obj_manager = game->get_obj_manager();

    anim = NULL;
    throw_obj = NULL;
    throw_tile = 0;
    throw_speed = 0;
    degrees = 0;
    stop_flags = 0;
}


void ThrowObjectEffect::start_anim()
{
    game->pause_anims();
    game->pause_world();
    game->pause_user();

    assert(throw_tile || throw_obj); // make sure it was properly initialized
    assert(throw_speed != 0);

    if(throw_obj)
        anim = new TossAnim(throw_obj, degrees, start_at, stop_at, throw_speed, stop_flags);
    else
        anim = new TossAnim(throw_tile, start_at, stop_at, throw_speed, stop_flags);
    add_anim(anim);
}


/* Object has stopped. */
void ThrowObjectEffect::hit_target()
{
    if(anim)
        anim->stop();
    game->unpause_all();
    delete_self();
}


/* The animation will travel from original object location to drop location if
 * NULL actor is specified.
 */
DropEffect::DropEffect(Obj *obj, uint16 qty, Actor *actor, MapCoord *drop_loc)
{
    drop_from_actor = actor;
    start_at = drop_from_actor ? drop_from_actor->get_location() : MapCoord(obj->x, obj->y, obj->z);
    stop_at = *drop_loc;
    degrees = 90;

    get_obj(obj, qty); // remove from actor, set throw_obj

    if(start_at != stop_at)
    {
        throw_speed = 192; // animation speed
        start_anim();
    }
    else
        hit_target(); // done already? why bother calling DropEffect? :p
}


/* Take `qty' objects of a stack if necessary, and remove from the actor's
 * inventory. Set `throw_obj'.
 */
void DropEffect::get_obj(Obj *obj, uint16 qty)
{
    throw_obj = obj_manager->get_obj_from_stack(obj, qty);
    if(drop_from_actor)
        drop_from_actor->inventory_remove_obj(throw_obj);
}


/* On ANIM_HIT_WORLD: end at hit location
 * On ANIM_DONE: end
 */
uint16 DropEffect::callback(uint16 msg, CallBack *caller, void *msg_data)
{
    // if throw_obj is NULL, object already hit target
    if(!throw_obj || (msg != MESG_ANIM_DONE && msg != MESG_ANIM_HIT_WORLD))
        return(0);

    if(msg == MESG_ANIM_HIT_WORLD && stop_at == *(MapCoord *)msg_data
       && anim)
        anim->stop();

    hit_target();
    return(0);
}


/* Add object to map. (call before completing effect) */
void DropEffect::hit_target()
{
    throw_obj->x = stop_at.x;
    throw_obj->y = stop_at.y;
    throw_obj->z = stop_at.z;

    //FIXME drop logic should probably be in lua script.
    if(drop_from_actor && obj_manager->is_breakable(throw_obj) && start_at.distance(stop_at) > 1)
    {
    	nuvie_game_t game_type = game->get_game_type();
    	if(game_type == NUVIE_GAME_U6 && throw_obj->obj_n == OBJ_U6_DRAGON_EGG)
    	{
    		throw_obj->frame_n = 1; //brake egg.
    		obj_manager->add_obj(throw_obj, OBJ_ADD_TOP);
    	}
    	else if(game_type == NUVIE_GAME_U6 && throw_obj->obj_n == OBJ_U6_MIRROR)
    	{
    		throw_obj->frame_n = 2; //break mirror.
    		obj_manager->add_obj(throw_obj, OBJ_ADD_TOP);
    	}
    	else
    	{	// remove items from container if there is one
    		if(game->get_usecode()->is_container(throw_obj))
    		{
    			U6Link *link = throw_obj->container->start();
    			for(;link != NULL; link = throw_obj->container->start())
    			{
    				Obj *obj = (Obj *)link->data;
    				obj_manager->moveto_map(obj, stop_at);
    			}
    		}
    		obj_manager->unlink_from_engine(throw_obj);
    		delete_obj(throw_obj);
    	}

        Game::get_game()->get_scroll()->display_string("\nIt broke!\n");
        Game::get_game()->get_sound_manager()->playSfx(NUVIE_SFX_BROKEN_GLASS);
    }
    else
    {
        Obj *dest_obj = obj_manager->get_obj(stop_at.x, stop_at.y, stop_at.z);
        if(obj_manager->can_store_obj(dest_obj, throw_obj))
            obj_manager->moveto_container(throw_obj, dest_obj);
        else
            obj_manager->add_obj(throw_obj, OBJ_ADD_TOP);
    }
    throw_obj = NULL; // set as dropped

    // not appropriate to do "Event::endAction(true)" from here to display
    // prompt, as we MUST unpause_user() in ThrowObjectEffect::hit_target, and
    // that would be redundant and may not unpause everything if wait mode was
    // already cancelled... so just prompt
    game->get_scroll()->display_string("\n");
    game->get_scroll()->display_prompt();
 
    game->get_map_window()->updateBlacking();
    ThrowObjectEffect::hit_target(); // calls delete_self()
}


/*** MissileEffect ***/
MissileEffect::MissileEffect(uint16 tile_num, uint16 obj_n, const MapCoord &source,
                             const MapCoord &target, uint8 dmg,
                             uint8 intercept, uint16 speed)
{
    actor_manager = game->get_actor_manager();
    hit_actor = 0;
    hit_obj = 0;

    init(tile_num, obj_n, source, target, dmg, intercept, speed);
}

/* Start effect. If target is unset then the actor is the target. */
void MissileEffect::init(uint16 tile_num, uint16 obj_n,
                         const MapCoord &source, const MapCoord &target,
                         uint32 dmg, uint8 intercept, uint32 speed)
{
    assert(tile_num || obj_n); // at least obj_n must be set
                               // (although it might work if throw_obj is already set)
    assert(speed != 0);
    assert(intercept != 0); // must hit target

    if(obj_n != 0)
        throw_obj = new_obj(obj_n, 0, 0,0,0);
    if(tile_num != 0)
        throw_tile = game->get_tile_manager()->get_tile(tile_num);
    else if(throw_obj != 0)
        throw_tile = obj_manager->get_obj_tile(throw_obj->obj_n,0);
    throw_speed = speed;
    hit_damage = dmg;

    start_at = source;
    stop_at = target;
    stop_flags = intercept;
    assert(stop_at != start_at); // Hmm, can't attack self with boomerang then
//    if (stop_at != start_at) {
//      start_at.x=WRAPPED_COORD(start_at.x+1,start_at.z);
//      start_at.y=WRAPPED_COORD(start_at.y-1,start_at.z);
//    }

    // set tile rotation here based on obj_num
    if(throw_obj != 0)
    {
        if(throw_obj->obj_n == OBJ_U6_SPEAR)
            degrees = 315;
        if(throw_obj->obj_n == OBJ_U6_THROWING_AXE)
            degrees = 0;
        if(throw_obj->obj_n == OBJ_U6_DAGGER)
            degrees = 315;
        if(throw_obj->obj_n == OBJ_U6_ARROW)
            degrees = 270;
        if(throw_obj->obj_n == OBJ_U6_BOLT)
            degrees = 270;
    }
    start_anim();
}

/* On HIT: hit Actor or Obj and end
 * On HIT_WORLD: end at hit location, hit Actor or Obj, else place obj
 * On DONE: end
 */
uint16 MissileEffect::callback(uint16 msg, CallBack *caller, void *msg_data)
{
    if(msg != MESG_ANIM_DONE && msg != MESG_ANIM_HIT_WORLD && msg != MESG_ANIM_HIT)
        return 0;

    if(msg == MESG_ANIM_DONE)
    {
        // will always hit anything at the target
        // FIXME: only hit breakable objects like doors
//        hit_obj = obj_manager->get_obj(stop_at.x,stop_at.y,stop_at.z);
        hit_actor = actor_manager->get_actor(stop_at.x,stop_at.y,stop_at.z);
        hit_target();
    }    
    else if(msg == MESG_ANIM_HIT && ((MapEntity *)msg_data)->entity_type == ENT_ACTOR)
    {
        if(hit_damage != 0)
            hit_actor = ((MapEntity*)msg_data)->actor;
        hit_target();
    }
    else if(msg == MESG_ANIM_HIT && ((MapEntity *)msg_data)->entity_type == ENT_OBJ)
    {
        // FIXME: only hit breakable objects like doors
/*        if(hit_damage != 0)
            hit_obj = ((MapEntity*)msg_data)->obj;
        hit_target();*/
    }
    // MESG_ANIM_HIT_WORLD
    hit_blocking();
    return 0;
}

/* Hit target or add object to map. (call before completing effect) */
void MissileEffect::hit_target()
{
    if(hit_actor)
    {
        hit_actor->hit(hit_damage, ACTOR_FORCE_HIT);
        delete_obj(throw_obj); throw_obj = 0; // don't drop
    }
    else if(hit_obj)
    {
        if(hit_obj->qty < hit_damage)
            hit_obj->qty = 0;
        else hit_obj->qty -= hit_damage;
        delete_obj(throw_obj); throw_obj = 0; // don't drop
    }
    if(throw_obj != 0)
    {
        throw_obj->x = stop_at.x; throw_obj->y = stop_at.y;
        throw_obj->z = stop_at.z;
        throw_obj->status |= OBJ_STATUS_OK_TO_TAKE | OBJ_STATUS_TEMPORARY;
        if(obj_manager->is_stackable(throw_obj))
            throw_obj->qty = 1; // stackable objects must have a quantity
        obj_manager->add_obj(throw_obj, OBJ_ADD_TOP);
        throw_obj = 0;
    }

    ThrowObjectEffect::hit_target(); // calls delete_self()
}

void MissileEffect::hit_blocking()
{
    delete_obj(throw_obj);
    ThrowObjectEffect::hit_target();
}


/*** SleepEffect ***/
/* The TimedAdvance is started after the fade-out completes. */
SleepEffect::SleepEffect(std::string until)
                         : timer(NULL),
                           stop_hour(0),
                           stop_minute(0),
                           stop_time("")
{
    stop_time = until;
    game->pause_user();
    effect_manager->watch_effect(this, new FadeEffect(FADE_PIXELATED, FADE_OUT));
}


SleepEffect::SleepEffect(uint8 to_hour)
                       : timer(NULL),
                         stop_hour(to_hour),
                         stop_minute(0),
                         stop_time("")
{
    game->pause_user();
    effect_manager->watch_effect(this, new FadeEffect(FADE_PIXELATED, FADE_OUT));
}


SleepEffect::~SleepEffect()
{
    //if(timer) // make sure it doesn't try to call us again
    //    timer->clear_target();
}


/* As with TimedEffect, make sure the timer doesn't try to use callback again.
 */
void SleepEffect::delete_self()
{
    //timer->clear_target(); // this will also stop/delete the TimedAdvance
    //timer = NULL;
    Effect::delete_self();
}


/* Resume normal play when requested time has been reached.
 */
//FIXME: need to handle TimedAdvance() errors and fade-in
uint16 SleepEffect::callback(uint16 msg, CallBack *caller, void *data)
{
    uint8 hour = Game::get_game()->get_clock()->get_hour();
    uint8 minute = Game::get_game()->get_clock()->get_minute();

    // waited for FadeEffect
    if(msg == MESG_EFFECT_COMPLETE)
    {
        if(timer == NULL) // starting
        {
            if(stop_time != "") // advance to start time
            {
                timer = new TimedAdvance(stop_time, 360); // 6 hours per second FIXME: it isnt going anywhere near that fast
                timer->set_target(this);
                timer->get_time_from_string(stop_hour, stop_minute, stop_time);
                // stop_hour & stop_minute are checked each hour
            }
            else // advance a number of hours
            {
                uint16 advance_h = (hour == stop_hour) ? 24
                                 : (hour < stop_hour) ? (stop_hour-hour)
                                 : (24-(hour-stop_hour));
                timer = new TimedAdvance(advance_h, 360);
                timer->set_target(this);
                stop_minute = minute;
            }
        }
        else // stopping
        {
        	Party *party = game->get_party();
            for(int s=0; s<party->get_party_size(); s++)
            {
            	Actor *actor = party->get_actor(s);

            	//heal actors.
            	uint8 hp_diff = actor->get_maxhp() - actor->get_hp();
            	if(hp_diff > 0)
            	{
            		if(hp_diff == 1)
            			hp_diff = 2;
            		actor->set_hp(actor->get_hp() + NUVIE_RAND()%(hp_diff/2) + hp_diff/2);
            	}
            }
            game->unpause_user();
            delete_self();
        }
        return(0);
    }
    // assume msg == MESG_TIMED; will stop after effect completes
    if(hour == stop_hour && minute >= stop_minute)
        effect_manager->watch_effect(this, new FadeEffect(FADE_PIXELATED, FADE_IN));

    return(0);
}


/*** FadeEffect ***/
FadeEffect::FadeEffect(FadeType fade, FadeDirection dir, uint32 color, uint32 speed)
{
    speed = speed ? speed : game->get_map_window()->get_win_area()*2116; // was 256000
    init(fade, dir, color, NULL, 0, 0, speed);
}

/* Takes an image to fade from/to. */
FadeEffect::FadeEffect(FadeType fade, FadeDirection dir, SDL_Surface *capture, uint32 speed)
{
    speed = speed ? speed : game->get_map_window()->get_win_area()*1620; // was 196000
    init(fade, dir, 0, capture, 0, 0, speed); // color=black
}

/* Localizes effect to specific coordinates. The size of the effect is determined
 * by the size of the image. */
FadeEffect::FadeEffect(FadeType fade, FadeDirection dir, SDL_Surface *capture, uint16 x, uint16 y, uint32 speed)
{
    speed = speed ? speed : 1024;
    init(fade, dir, 0, capture, x, y, speed); // color=black
}


void FadeEffect::init(FadeType fade, FadeDirection dir, uint32 color, SDL_Surface *capture, uint16 x, uint16 y, uint32 speed)
{
    if(current_fade)
    {
        delete_self();
        return;
    }
    current_fade = this; // cleared in dtor

    screen = game->get_screen();
    map_window = game->get_map_window();
    viewport = new SDL_Rect(map_window->GetRect());

    fade_type = fade;
    fade_dir = dir;
    fade_speed = speed; // pixels-per-second (to check, not draw)

    evtime = prev_evtime = 0;
    fade_x = x; fade_y = y;
    fade_from = NULL;
    fade_iterations = 0;
    if(capture)
    {
        fade_from = SDL_CreateRGBSurface(SDL_SWSURFACE, capture->w, capture->h,
                                         capture->format->BitsPerPixel, 0, 0, 0, 0);
        for(uint32 p = 0, pixels = (capture->w*capture->h); p < pixels; p++)
            ((uint8*)fade_from->pixels)[p] = ((uint8*)capture->pixels)[p];
    }

    if(fade_type == FADE_PIXELATED || fade_type == FADE_PIXELATED_ONTOP)
    {
        pixelated_color = color;
        init_pixelated_fade();
    }
    else
        init_circle_fade();
}


FadeEffect::~FadeEffect()
{
  //moved to delete_self
}

void FadeEffect::delete_self()
{
    if(current_fade == this) // these weren't init. if FadeEffect didn't start
    {
        delete viewport;
        if(fade_dir == FADE_IN) // overlay should be empty now, so just delete it
            map_window->set_overlay(NULL);
        if(fade_from)
            SDL_FreeSurface(fade_from);

        current_fade = NULL;
    }

	TimedEffect::delete_self();
}

/* Start effect. */
void FadeEffect::init_pixelated_fade()
{
    int fillret = -1; // check error
    overlay = map_window->get_overlay();
    if(overlay != NULL)
    {
        pixel_count = fade_from ? (fade_from->w) * (fade_from->h)
                                : (overlay->w-fade_x) * (overlay->h-fade_y);
        // clear overlay to fill color or transparent
        if(fade_dir == FADE_OUT)
        {
            if(fade_from) // fade from captured surface to transparent
            {
                // put surface on transparent background (not checked)
                fillret = SDL_FillRect(overlay, NULL, uint32(TRANSPARENT_COLOR));
                SDL_Rect overlay_rect = { (Sint16)fade_x, (Sint16)fade_y, 0, 0 };
                fillret = SDL_BlitSurface(fade_from, NULL,
                                          overlay, &overlay_rect);
            }
            else // fade from transparent to color
                fillret = SDL_FillRect(overlay, NULL, uint32(TRANSPARENT_COLOR));
        }
        else
        {
            if(fade_from) // fade from transparent to captured surface
                fillret = SDL_FillRect(overlay, NULL, uint32(TRANSPARENT_COLOR));
            else // fade from color to transparent
                fillret = SDL_FillRect(overlay, NULL, uint32(pixelated_color));
        }
    }
    if(fillret == -1)
    {
        DEBUG(0,LEVEL_DEBUGGING,"FadeEffect: error creating overlay surface\n");
        delete_self();
        return;
    }

    // if FADE_PIXELATED_ONTOP is set, place the effect layer above the map border
    map_window->set_overlay_level((fade_type == FADE_PIXELATED)
                                  ? MAP_OVERLAY_DEFAULT : MAP_OVERLAY_ONTOP);
    colored_total = 0;
    start_timer(1); // fire timer continuously
}


/* Start effect.
 */
void FadeEffect::init_circle_fade()
{
    delete_self(); // FIXME
}


/* Called by the timer as frequently as possible. Do the appropriate
 * fade method and stop when the effect is complete.
 */
uint16 FadeEffect::callback(uint16 msg, CallBack *caller, void *data)
{
    bool fade_complete = false;

    // warning: msg is assumed to be CB_TIMED and data is set
    evtime = *(uint32 *)(data);

    // do effect
    if(fade_type == FADE_PIXELATED || fade_type == FADE_PIXELATED_ONTOP)
        fade_complete = (fade_dir == FADE_OUT) ? pixelated_fade_out() : pixelated_fade_in();
    else /* CIRCLE */
        fade_complete = (fade_dir == FADE_OUT) ? circle_fade_out() : circle_fade_in();

    // done
    if(fade_complete == true)
    {
        delete_self();
        return(1);
    }
    return(0);
}


/* Scan the overlay, starting at pixel rnum, for a transparent pixel if fading
 * out, and a colored pixel if fading in.
 * Returns true if a free pixel was found and set as rnum.
 */
// FIXME: this probably doesn't work because it only handles 8bpp
inline bool FadeEffect::find_free_pixel(uint32 &rnum, uint32 pixel_count)
{
    uint8 scan_color = (fade_dir == FADE_OUT) ? TRANSPARENT_COLOR
                                              : pixelated_color;
    uint8 *pixels = (uint8 *)(overlay->pixels);

    for(uint32 p = rnum; p < pixel_count; p++) // check all pixels after rnum
        if(pixels[p] == scan_color)
        {
            rnum = p;
            return(true);
        }
    for(uint32 q = 0; q < rnum; q++) // check all pixels before rnum
        if(pixels[q] == scan_color)
        {
            rnum = q;
            return(true);
        }
    return(false);
}


/* Returns the next pixel to check/colorize. */
#if 0
#warning this crashes if x,y is near boundary
#warning make sure center_thresh doesnt go over boundary
inline uint32 FadeEffect::get_random_pixel(uint16 center_thresh)
{
    if(center_x == -1 || center_y == -1)
        return(NUVIE_RAND()%pixel_count);

    uint16 x = center_x, y = center_y;
    if(center_thresh == 0)
        center_thresh = overlay->w / 2;
    x += (NUVIE_RAND()%(center_thresh * 2)) - center_thresh,
    y += (NUVIE_RAND()%(center_thresh * 2)) - center_thresh;
    return((y * overlay->w) + x);
}
#endif

/* Randomly add pixels of the appropriate color to the overlay. If the color
 * is -1, it will be taken from the "fade_from" surface.
 * Returns true when the overlay is completely colored.
 */
bool FadeEffect::pixelated_fade_core(uint32 pixels_to_check, sint16 fade_to)
{
    uint8 *pixels = (uint8 *)(overlay->pixels);
    uint8 *from_pixels = fade_from ? (uint8 *)(fade_from->pixels) : NULL;
    uint32 p = 0; // scan counter
    uint32 rnum = 0; // pixel index
    uint32 colored = 0; // number of pixels that get colored
    uint16 fade_width = fade_from ? fade_from->w : overlay->w-fade_x;
    uint16 fade_height = fade_from ? fade_from->h : overlay->h-fade_y;
    uint8 color = fade_to;

    if(fade_to == -1 && fade_from == NULL)
    {
      return false;
    }

    while(p < pixels_to_check)
    {
        uint16 x = uint16(float(NUVIE_RAND())*(fade_width-1)/NUVIE_RAND_MAX) + fade_x,
               y = uint16(float(NUVIE_RAND())*(fade_height-1)/NUVIE_RAND_MAX) + fade_y;
        if(x >= overlay->w) x = overlay->w-1; // prevent overflow if fade_from is too big
        if(y >= overlay->h) y = overlay->h-1;
        rnum = y*overlay->w + x;
//ERIC        rnum = y*overlay->pitch + x;
				
        if(fade_to == -1) // get color from "fade_from"
        {
            x -= fade_x; y -= fade_y;
            color = from_pixels[y*fade_from->w + x];
        }
        if(pixels[rnum] != color)
        {
            pixels[rnum] = color;
            ++colored, ++colored_total; // another pixel was set
        }
        ++p;
    }
    // all but two lines colored
    if(colored_total >= (pixel_count - fade_width*2) || fade_iterations > FADE_EFFECT_MAX_ITERATIONS) // fill the rest
    {
        if(fade_to >= 0)
            SDL_FillRect(overlay, NULL, (uint32)fade_to);
        else // Note: assert(fade_from) if(fade_to < 0)
        {
            SDL_Rect fade_from_rect = { 0, 0, (Uint16)fade_from->w, (Uint16)fade_from->h };
            SDL_Rect overlay_rect = { (Sint16)fade_x, (Sint16)fade_y, (Uint16)fade_from->w, (Uint16)fade_from->h };
            SDL_BlitSurface(fade_from, &fade_from_rect, overlay, &overlay_rect);
        }
        return(true);
    }
    else return(false);
}


/* Color some of the mapwindow.
 * Returns true when all pixels have been filled, and nothing is visible.
 */
bool FadeEffect::pixelated_fade_out()
{
    if(fade_from)
        return(pixelated_fade_core(pixels_to_check(), TRANSPARENT_COLOR));
    return(pixelated_fade_core(pixels_to_check(), pixelated_color));
}


/* Clear some of the mapwindow.
 * Returns true when all colored pixels have been removed, and the MapWindow
 * is visible.
 */
bool FadeEffect::pixelated_fade_in()
{
    if(fade_from)
        return(pixelated_fade_core(pixels_to_check(), -1));
    return(pixelated_fade_core(pixels_to_check(), TRANSPARENT_COLOR));
}


/* Returns the number of pixels that should be checked/colored (based on speed)
 * since the previous call.
 */
uint32 FadeEffect::pixels_to_check()
{
    uint32 time_passed = (prev_evtime == 0) ? 0 : evtime - prev_evtime;
    uint32 fraction = 1000 / (time_passed > 0 ? time_passed : 1); // % of second passed, in milliseconds
    uint32 pixels_per_fraction = fade_speed / (fraction > 0 ? fraction : 1);
    prev_evtime = evtime;
    fade_iterations++;
    return(pixels_per_fraction);
}


/* Reduce the MapWindow's ambient light level, according to the set speed.
 * Returns true when nothing is visible.
 */
bool FadeEffect::circle_fade_out()
{
// FIXME
    return(false);
}


/* Add to the MapWindow's ambient light level, according to the set speed.
 * Returns true when the light level has returned to normal.
 */
bool FadeEffect::circle_fade_in()
{
// FIXME
    return(false);
}


/* Pause game and do FadeEffect.
 */
GameFadeInEffect::GameFadeInEffect(uint32 color)
                              : FadeEffect(FADE_PIXELATED_ONTOP, FADE_IN, color)
{
    game->pause_user();
}


GameFadeInEffect::~GameFadeInEffect()
{
}


/* Identical to FadeEffect, but unpause game when finished.
 */
uint16 GameFadeInEffect::callback(uint16 msg, CallBack *caller, void *data)
{
    // done
    if(FadeEffect::callback(msg, caller, data) != 0)
        game->unpause_user();
    return(0);
}


FadeObjectEffect::FadeObjectEffect(Obj *obj, FadeDirection dir)
{
    obj_manager = game->get_obj_manager();
    fade_obj = obj;
    fade_dir = dir;

    SDL_Surface *capture = game->get_map_window()->get_sdl_surface();
    if(fade_dir == FADE_IN) // fading IN to object, so fade OUT from capture
    {
        effect_manager->watch_effect(this, /* call me */
                                     new FadeEffect(FADE_PIXELATED, FADE_OUT, capture));
        obj_manager->add_obj(fade_obj, OBJ_ADD_TOP);
        game->get_map_window()->updateBlacking(); // object is likely a moongate
    }
    else if(fade_dir == FADE_OUT)
    {
        effect_manager->watch_effect(this, /* call me */
                                     new FadeEffect(FADE_PIXELATED, FADE_OUT, capture, 0, 0, game->get_map_window()->get_win_area()*1058)); //was 128000
//        obj_manager->remove_obj(fade_obj);
        game->get_map_window()->updateBlacking();
    }
    SDL_FreeSurface(capture);

    game->pause_user();
}

FadeObjectEffect::~FadeObjectEffect()
{
    game->unpause_user();
}

/* Assume FadeEffect is complete. */
uint16 FadeObjectEffect::callback(uint16 msg, CallBack *caller, void *data)
{
    delete_self();
    return(0);
}


/* These types of local/vanish effects are slightly longer than a normal Fade.
 * FIXME: FadeEffect should take local effect area, or change speed to time.
 */
VanishEffect::VanishEffect(bool pause_user)
                         : input_blocked(pause_user)
{
    SDL_Surface *capture = game->get_map_window()->get_sdl_surface();
//    effect_manager->watch_effect(this, /* call me */
//                                 new FadeEffect(FADE_PIXELATED, FADE_OUT, capture, 0, 0, 128000));
    effect_manager->watch_effect(this, /* call me */
                                 new FadeEffect(FADE_PIXELATED, FADE_OUT, capture));
    SDL_FreeSurface(capture);

    if(input_blocked == VANISH_WAIT)
        game->pause_user();
    game->pause_anims();
}

VanishEffect::~VanishEffect()
{
    game->unpause_anims();
    if(input_blocked == VANISH_WAIT)
        game->unpause_user();
}

/* Assume FadeEffect is complete. */
uint16 VanishEffect::callback(uint16 msg, CallBack *caller, void *data)
{
    delete_self();
    return(0);
}


/* TileFadeEffect */
TileFadeEffect::TileFadeEffect(MapCoord loc, Tile *from, Tile *to, FadeType type, uint16 speed)
{
	anim = NULL;
	to_tile = NULL;
	anim_tile = NULL;
	actor = NULL;
	color_from = color_to = 0;
	inc_reverse = false;
	spd = 0;
	add_anim(new TileFadeAnim(&loc, from, to, speed));
	num_anim_running = 1;
}

//Fade out actor.
TileFadeEffect::TileFadeEffect(Actor *a, uint16 speed)
{
	inc_reverse = false;
	anim = NULL;
	to_tile = NULL;
	anim_tile = NULL;
	actor = a;
	color_from = color_to = 0;
	spd = speed;
	num_anim_running = 0;
	add_actor_anim();
	actor->hide();
}

TileFadeEffect::~TileFadeEffect()
{

}

void TileFadeEffect::add_actor_anim()
{
	MapCoord loc = actor->get_location();
	Tile *from = actor->get_tile();
	add_tile_anim(loc, from);

	std::list<Obj *> *surrounding_objs = actor->get_surrounding_obj_list();

	if(surrounding_objs)
	{
		std::list<Obj *>::iterator obj_iter;
		for(obj_iter = surrounding_objs->begin(); obj_iter != surrounding_objs->end(); obj_iter++)
		{
			add_obj_anim(*obj_iter);
		}
	}
}

void TileFadeEffect::add_obj_anim(Obj *obj)
{
	ObjManager *obj_manager = Game::get_game()->get_obj_manager();
	MapCoord loc(obj->x, obj->y, obj->z);
	add_tile_anim(loc, obj_manager->get_obj_tile(obj->obj_n, obj->frame_n));
}

void TileFadeEffect::add_fade_anim(MapCoord loc, Tile *tile)
{
	add_anim(new TileFadeAnim(&loc, tile, NULL, spd));
	num_anim_running++;
}

void TileFadeEffect::add_tile_anim(MapCoord loc, Tile *tile)
{
	TileManager *tile_manager = Game::get_game()->get_tile_manager();
	uint16 tile_num = tile->tile_num;

	add_fade_anim(loc, tile);

	if(tile->dbl_width)
	{
		tile_num--;
		loc.x -= 1;
		add_fade_anim(loc, tile_manager->get_tile(tile_num));
		loc.x += 1;
	}

	if(tile->dbl_height)
	{
		tile_num--;
		loc.y -= 1;
		add_fade_anim(loc, tile_manager->get_tile(tile_num));
		loc.y += 1;
	}

	if(tile->dbl_width && tile->dbl_height)
	{
		tile_num--;
		loc.x -= 1;
		loc.y -= 1;
		add_fade_anim(loc, tile_manager->get_tile(tile_num));
		loc.x += 1;
		loc.y += 1;
	}
}

uint16 TileFadeEffect::callback(uint16 msg, CallBack *caller, void *data)
{
	if(msg == MESG_ANIM_DONE)
	{
		num_anim_running--;
	}

	if(num_anim_running == 0)
	{
		if(inc_reverse)
		{
			inc_reverse = false;
			add_actor_anim();
			return 0;
		}

		if(actor)
			actor->show();

		delete_self();
	}

	return 0;
}


TileBlackFadeEffect::TileBlackFadeEffect(Actor *a, uint8 fade_color, uint16 speed)
{
	init(fade_color, speed);
	actor = a;
	actor->hide();
	add_actor_anim();
}

TileBlackFadeEffect::TileBlackFadeEffect(Obj *o, uint8 fade_color, uint16 speed)
{
	init(fade_color, speed);
	obj = o;
	obj->set_invisible(true);
	add_obj_anim(obj);
}

void TileBlackFadeEffect::init(uint8 fade_color, uint16 speed)
{
	fade_speed = speed;
	color = fade_color;
	actor = NULL;
	obj = NULL;
	reverse = false;

	num_anim_running = 0;
}
TileBlackFadeEffect::~TileBlackFadeEffect()
{

}

void TileBlackFadeEffect::add_actor_anim()
{
	MapCoord loc = actor->get_location();
	Tile *from = actor->get_tile();
	add_tile_anim(loc, from);

	std::list<Obj *> *surrounding_objs = actor->get_surrounding_obj_list();

	if(surrounding_objs)
	{
		std::list<Obj *>::iterator obj_iter;
		for(obj_iter = surrounding_objs->begin(); obj_iter != surrounding_objs->end(); obj_iter++)
		{
			add_obj_anim(*obj_iter);
		}
	}
}

void TileBlackFadeEffect::add_obj_anim(Obj *o)
{
	MapCoord loc(o);
	Tile *from = Game::get_game()->get_obj_manager()->get_obj_tile(o->obj_n, o->frame_n);
	add_tile_anim(loc, from);
}

void TileBlackFadeEffect::add_tile_anim(MapCoord loc, Tile *tile)
{
	TileManager *tile_manager = Game::get_game()->get_tile_manager();
	uint16 tile_num = tile->tile_num;

	add_anim(new TileFadeAnim(&loc, tile, 0, color, reverse, fade_speed));
	num_anim_running++;

	if(tile->dbl_width)
	{
		tile_num--;
		loc.x -= 1;
		add_anim(new TileFadeAnim(&loc, tile_manager->get_tile(tile_num), 0, color, reverse, fade_speed));
		num_anim_running++;
		loc.x += 1;
	}

	if(tile->dbl_height)
	{
		tile_num--;
		loc.y -= 1;
		add_anim(new TileFadeAnim(&loc, tile_manager->get_tile(tile_num), 0, color, reverse, fade_speed));
		num_anim_running++;
		loc.y += 1;
	}

	if(tile->dbl_width && tile->dbl_height)
	{
		tile_num--;
		loc.x -= 1;
		loc.y -= 1;
		add_anim(new TileFadeAnim(&loc, tile_manager->get_tile(tile_num), 0, color, reverse, fade_speed));
		num_anim_running++;
		loc.x += 1;
		loc.y += 1;
	}
}

uint16 TileBlackFadeEffect::callback(uint16 msg, CallBack *caller, void *data)
{
	if(msg == MESG_ANIM_DONE)
	{
		num_anim_running--;
	}

	if(num_anim_running == 0)
	{
		if(reverse == false)
		{
			reverse = true;
			if(actor)
				add_actor_anim();
			else
				add_obj_anim(obj);

			return 0;
		}

		if(actor)
			actor->show();
		else
			obj->set_invisible(false);

		delete_self();
	}

	return 0;
}

XorEffect::XorEffect(uint32 eff_ms)
                                       : map_window(game->get_map_window()),
                                         length(eff_ms)
{
    game->pause_user();
    game->pause_anims();

    init_effect();
}

void XorEffect::init_effect()
{
    capture = map_window->get_sdl_surface();
    map_window->set_overlay_level(MAP_OVERLAY_DEFAULT);
    map_window->set_overlay(capture);

    xor_capture(0xd); // changes black to pink
    start_timer(length);
}

/* Timer finished. Cleanup. */
uint16 XorEffect::callback(uint16 msg, CallBack *caller, void *data)
{
    if(msg == MESG_TIMED)
    {
        stop_timer();
        game->unpause_anims();
        game->unpause_user();
        map_window->set_overlay(NULL);
        delete_self();
    }
    return 0;
}

/* Do binary-xor on each pixel of the mapwindow image.*/
void XorEffect::xor_capture(uint8 mod)
{
    uint8 *pixels = (uint8 *)(capture->pixels);
    for(int p = 0; p < (capture->w*capture->h); p++)
        pixels[p] ^= mod;
}


U6WhitePotionEffect::U6WhitePotionEffect(uint32 eff_ms, uint32 delay_ms, Obj *callback_obj)
                                       : map_window(game->get_map_window()),
                                         state(0), start_length(1000),
                                         eff1_length(eff_ms), eff2_length(800),
                                         xray_length(delay_ms), capture(0),
                                         potion(callback_obj)
{
    game->pause_user();
    game->pause_anims();

    init_effect();
}

void U6WhitePotionEffect::init_effect()
{
    // FIXME: play sound, and change state to 1 when sound is complete
    capture = map_window->get_sdl_surface();
    map_window->set_overlay_level(MAP_OVERLAY_DEFAULT);
    map_window->set_overlay(capture);
    start_timer(start_length);
}

/* The state is changed from current to next when this is called. */
uint16 U6WhitePotionEffect::callback(uint16 msg, CallBack *caller, void *data)
{
    if(msg == MESG_TIMED)
    {
        stop_timer();

        if(state == 0) // start/sound effect
        { // FIXME: make start_length a timeout, force sound to stop
            xor_capture(0xd); // changes black to pink
            start_timer(eff1_length);
            state = 1;
        }
        else if(state == 1) // xor-effect
        {
            map_window->set_overlay(NULL);
            start_timer(eff2_length);
            state = 2;
        }
        else if(state == 2) // character outline
        {
            game->unpause_anims();
            map_window->set_x_ray_view(X_RAY_ON);
            map_window->updateBlacking();
            start_timer(xray_length);
            state = 3;
        }
        else if(state == 3) // x-ray
        {
            map_window->set_x_ray_view(X_RAY_OFF);
            map_window->updateBlacking();
            game->unpause_user();
            if(potion)
                game->get_usecode()->message_obj(potion, MESG_EFFECT_COMPLETE, this);
            state = 4; // finished
            delete_self();
        }
    }
    return 0;
}

/* Do binary-xor on each pixel of the mapwindow image.*/
void U6WhitePotionEffect::xor_capture(uint8 mod)
{
    uint8 *pixels = (uint8 *)(capture->pixels);
    for(int p = 0; p < (capture->w*capture->h); p++)
        pixels[p] ^= mod;
}


XRayEffect::XRayEffect(uint32 eff_ms)
{
	xray_length = eff_ms;
	init_effect();
}

void XRayEffect::init_effect()
{
	Game::get_game()->get_map_window()->set_x_ray_view(X_RAY_ON);
	start_timer(xray_length);
}

uint16 XRayEffect::callback(uint16 msg, CallBack *caller, void *data)
{
    if(msg == MESG_TIMED)
    {
        stop_timer();
        Game::get_game()->get_map_window()->set_x_ray_view(X_RAY_OFF);
        delete_self();
    }

    return 0;
}

PauseEffect::PauseEffect()
{
    game->pause_world();
    // FIXME: need a way to detect any keyboard/mouse input
    game->get_scroll()->set_input_mode(true, "\n", true);
    game->get_scroll()->request_input(this, 0);
}

/* The effect ends when this is called. (if input is correct) */
uint16 PauseEffect::callback(uint16 msg, CallBack *caller, void *data)
{
    if(msg == MESG_INPUT_READY)
    {
        game->unpause_world();
        delete_self();
    }
    return 0;
}

WizardEyeEffect::WizardEyeEffect(MapCoord location, uint16 duration)
{
	game->get_map_window()->wizard_eye_start(location, duration, this);
}

uint16 WizardEyeEffect::callback(uint16 msg, CallBack *caller, void *data)
{
    if(msg == MESG_EFFECT_COMPLETE)
    {
        delete_self();
    }

    return 0;
}

TextInputEffect::TextInputEffect(const char *allowed_chars, bool can_escape)
{
    game->pause_world();
    // FIXME: need a way to detect any keyboard/mouse input
	game->get_gui()->unblock();
    game->get_scroll()->set_input_mode(true, allowed_chars, can_escape);
    game->get_scroll()->request_input(this, 0);
}

/* The effect ends when this is called. (if input is correct) */
uint16 TextInputEffect::callback(uint16 msg, CallBack *caller, void *data)
{
    if(msg == MESG_INPUT_READY)
    {
    	input = *(std::string*)data;
        game->unpause_world();
        delete_self();
    }
    return 0;
}


PeerEffect::PeerEffect(uint16 x, uint16 y, uint8 z, Obj *callback_obj)
                     : map_window(game->get_map_window()), overlay(0),
                       gem(callback_obj), area(x, y, z), tile_trans(0),
                       map_pitch(0)
{
    uint8 lvl=0;
    map_window->get_level(&lvl);
    map_pitch = (lvl==0) ? 1024 : 256;

    init_effect();
}

void PeerEffect::init_effect()
{
    overlay = map_window->get_sdl_surface();
    map_window->set_overlay_level(MAP_OVERLAY_DEFAULT);
    map_window->set_overlay(overlay);
    assert(overlay->w%PEER_TILEW == 0); // overlay must be a multiple of tile size
    SDL_FillRect(overlay, NULL, 0);
    
    peer();
}

void PeerEffect::delete_self()
{
    map_window->set_overlay(NULL);
    if(gem)
        game->get_usecode()->message_obj(gem, MESG_EFFECT_COMPLETE, this);
    else // FIXME: I don't want prompt display here, so it's also in UseCode,
         // but it has to be here if no object was set. (until we have another
         // way to tell caller effect is complete, and return to player)
    {
        game->get_scroll()->display_string("\n");
        game->get_scroll()->display_prompt();
    }
    Effect::delete_self();
}

void PeerEffect::peer()
{
    uint16 w = overlay->w, h = overlay->h;
    // effect is limited to 48x48 area
    if(overlay->w > 48*PEER_TILEW) w = 48*PEER_TILEW;
    if(overlay->h > 48*PEER_TILEW) h = 48*PEER_TILEW;

    MapCoord player_loc = game->get_player()->get_actor()->get_location();
    uint16 cx = player_loc.x-area.x; // rough center of area
    uint16 cy = player_loc.y-area.y;
    area.x%=map_pitch; // we have to wrap here because we use a map buffer
    area.y%=map_pitch;
    uint8 *mapbuffer = new uint8[48*48]; // array of tile types/colors
    memset(mapbuffer, 0x00, sizeof(uint8)*48*48); // fill with black
    fill_buffer(mapbuffer, cx, cy);

    for(int x = 0; x < w; x += PEER_TILEW)
        for(int y = 0; y < h; y += PEER_TILEW)
        {
            uint16 wx = area.x+x/PEER_TILEW, wy = area.y+y/PEER_TILEW;
            uint8 tile_type = mapbuffer[(wy-area.y)*48 + (wx-area.x)];
            blit_tile(x,y, tile_type);
            if(tile_type != 0x00)
            {
                Actor *actor = game->get_actor_manager()->get_actor(wx,wy,area.z);
                if(actor)
                    blit_actor(actor);
            }
        }

    delete [] mapbuffer;
}

void PeerEffect::fill_buffer(uint8 *mapbuffer, uint16 x, uint16 y)
{
    uint16 wx = area.x+x, wy = area.y+y;
    uint8 *tile = &mapbuffer[y*48 + x];

    if(*tile != 0x00)
        return; // already filled

    wx %= map_pitch; // we have to wrap here because we use a map buffer
    wy %= map_pitch;
    *tile = get_tilemap_type(wx, wy, area.z);

    // stop at unpassable tiles
    // FIXME: stop at Nothing/black tiles
    if(*tile != peer_tilemap[2]
       || game->get_game_map()->get_tile(wx, wy, area.z, true)->passable)
    {
        if(y > 0)
        {
            if(x > 0) fill_buffer(mapbuffer,x-1,y-1); // +-+-+
            if(y > 0) fill_buffer(mapbuffer,x,  y-1); // |\|/|
            if(x+1 < 48) fill_buffer(mapbuffer,x+1,y-1);
        }

        if(x > 0)     fill_buffer(mapbuffer,x-1,y);   // +-+-+
        if(x+1 < 48)  fill_buffer(mapbuffer,x+1,y);

        if(y+1 < 48)
        {
            if(x > 0) fill_buffer(mapbuffer,x-1,y+1); // |/|\|
                      fill_buffer(mapbuffer,x,  y+1); // +-+-+
            if(x+1 < 48) fill_buffer(mapbuffer,x+1,y+1);
        }
    }
}

inline void PeerEffect::blit_tile(uint16 x, uint16 y, uint8 c)
{
    uint8 *pixels = (uint8*)overlay->pixels;
    for(int j=0;j<PEER_TILEW && j<overlay->h;j++)
        for(int i=0;i<PEER_TILEW && i<overlay->w;i++)
        {
            if(peer_tile[i*PEER_TILEW+j] != tile_trans)
                pixels[overlay->w*(y+j)+(x+i)] = c;
        }
}

inline void PeerEffect::blit_actor(Actor *actor)
{
    tile_trans = 1;
    blit_tile((actor->get_location().x-area.x)*PEER_TILEW,
              (actor->get_location().y-area.y)*PEER_TILEW, 0x0F);
    tile_trans = 0;
    if(game->get_player()->get_actor() == actor)
        blit_tile((actor->get_location().x-area.x)*PEER_TILEW,
                  (actor->get_location().y-area.y)*PEER_TILEW, 0x0F);
}

inline uint8 PeerEffect::get_tilemap_type(uint16 wx, uint16 wy, uint8 wz)
{
    Map *map = game->get_game_map();
    // ignore objects (bridges and docks), and show coasts as land
    if(map->is_water(wx, wy, wz, true) && !map->get_tile(wx, wy, wz, true)->passable)
        return peer_tilemap[1];
    if(!map->is_passable(wx, wy, wz))
        return peer_tilemap[2];
    if(map->is_damaging(wx, wy, wz))
        return peer_tilemap[3];
    return peer_tilemap[0]; // ground/passable
}

WingStrikeEffect::WingStrikeEffect(Actor *target_actor)
{
	actor = target_actor;

	add_anim(new WingAnim(actor->get_location()));
}

uint16 WingStrikeEffect::callback(uint16 msg, CallBack *caller, void *data)
{
	switch(msg)
	{
		case MESG_ANIM_HIT :
			DEBUG(0,LEVEL_DEBUGGING,"hit target!\n");
			Game::get_game()->get_script()->call_actor_hit(actor, (NUVIE_RAND()%20)+1);
			break;
		case MESG_ANIM_DONE :
			delete_self();
			break;
	}
	return 0;
}

HailStormEffect::HailStormEffect(MapCoord target)
{
	add_anim(new HailstormAnim(target));
}

uint16 HailStormEffect::callback(uint16 msg, CallBack *caller, void *data)
{
	switch(msg)
	{
		case MESG_ANIM_HIT :
			DEBUG(0,LEVEL_DEBUGGING,"hit target!\n");
			Game::get_game()->get_script()->call_actor_hit((Actor *)data, 1);
			break;
		case MESG_ANIM_DONE :
			delete_self();
			break;
	}
	return 0;
}

/*** AsyncEffect ***/
AsyncEffect::AsyncEffect(Effect *e)
{
	effect_complete = false;
	effect = e;
	effect->retain();
    effect_manager->watch_effect(this, effect);
}

AsyncEffect::~AsyncEffect()
{
	effect->release();
}

/* The effect is marked as defunct after run finishes and will be removed from the system.*/
void AsyncEffect::run(bool process_gui_input)
{
	if(!process_gui_input)
		Game::get_game()->pause_user();
	for(;effect_complete == false;)
	{
		//spin world
		Game::get_game()->update_once(process_gui_input);
		if(!effect_complete)
			Game::get_game()->update_once_display();
	}
	if(!process_gui_input)
		Game::get_game()->unpause_user();
	delete_self();
}

uint16 AsyncEffect::callback(uint16 msg, CallBack *caller, void *data)
{

    // effect complete
    if(msg == MESG_EFFECT_COMPLETE)
    {
    	effect_complete = true;
    }

    return 0;
}
