void BehaviorStandard::checkMoveStateStance() {

	// If the enemy is capable of fleeing and is at a safe distance, have it hold its position instead of moving
	if (hero_dist >= e->stats.threat_range/2 && e->stats.chance_flee > 0 && e->stats.waypoints.empty()) return;

	if ((target_dist > e->stats.melee_range && percentChance(e->stats.chance_pursue)) || fleeing) {

		if (e->move()) {
			e->stats.cur_state = ENEMY_MOVE;
		}
		else {
			collided = true;
			if (fleeing || move_to_safe_dist) {
				fleeing = false;
				move_to_safe_dist = false;
			}
			else {
				unsigned char prev_direction = e->stats.direction;

				// hit an obstacle, try the next best angle
				e->stats.direction = e->faceNextBest(pursue_pos.x, pursue_pos.y);
				if (e->move()) {
					e->stats.cur_state = ENEMY_MOVE;
				}
				else
					e->stats.direction = prev_direction;
			}
		}
	}
}
void BehaviorStandard::checkMoveStateStance() {

	// If the enemy is capable of fleeing and is at a safe distance, have it hold its position instead of moving
	if (hero_dist >= e->stats.threat_range/2 && e->stats.chance_flee > 0) return;

	if ((hero_dist > e->stats.melee_range && percentChance(e->stats.chance_pursue)) || fleeing) {

		if (e->move()) {
			e->newState(ENEMY_MOVE);
		}
		else {

			int prev_direction = e->stats.direction;

			// hit an obstacle, try the next best angle
			e->stats.direction = e->faceNextBest(pursue_pos.x, pursue_pos.y);
			if (e->move()) {
				e->newState(ENEMY_MOVE);
			}
			else e->stats.direction = prev_direction;
		}
	}
}
/**
 * Perform miscellaneous state-based actions.
 * 1) Set animations and sound effects
 * 2) Return to the default state (Stance) when actions are complete
 */
void BehaviorStandard::updateState() {

	// stunned enemies can't act
	if (e->stats.effects.stun) return;

	int power_id;
	int power_state;

	// continue current animations
	e->activeAnimation->advanceFrame();

	switch (e->stats.cur_state) {

		case ENEMY_STANCE:

			e->setAnimation("stance");
			break;

		case ENEMY_MOVE:

			e->setAnimation("run");
			break;

		case ENEMY_POWER:

			power_id = e->stats.power_index[e->stats.activated_powerslot];
			power_state = powers->powers[power_id].new_state;

			// animation based on power type
			if (power_state == POWSTATE_INSTANT) e->instant_power = true;
			else if (power_state == POWSTATE_ATTACK) e->setAnimation(powers->powers[power_id].attack_anim);

			// sound effect based on power type
			if (e->activeAnimation->isFirstFrame()) {
				if (powers->powers[power_id].attack_anim == "swing" || powers->powers[power_id].attack_anim == "shoot") e->play_sfx_phys = true;
				else if (powers->powers[power_id].attack_anim == "cast") e->play_sfx_ment = true;
			}

			if (e->activeAnimation->isLastFrame() || (power_state == POWSTATE_ATTACK && e->activeAnimation->getName() != powers->powers[power_id].attack_anim))
				e->newState(ENEMY_STANCE);
			break;

		case ENEMY_SPAWN:

			e->setAnimation("spawn");
			//the second check is needed in case the entity does not have a spawn animation
			if (e->activeAnimation->isLastFrame() || e->activeAnimation->getName() != "spawn") {
				e->newState(ENEMY_STANCE);
			}
			break;

		case ENEMY_BLOCK:

			e->setAnimation("block");
			break;

		case ENEMY_HIT:

			e->setAnimation("hit");
			if (e->activeAnimation->isFirstFrame()) {
				e->stats.effects.triggered_hit = true;
			}
			if (e->activeAnimation->isLastFrame() || e->activeAnimation->getName() != "hit")
				e->newState(ENEMY_STANCE);
			break;

		case ENEMY_DEAD:
			if (e->stats.effects.triggered_death) break;

			e->setAnimation("die");
			if (e->activeAnimation->isFirstFrame()) {
				e->play_sfx_die = true;
				e->stats.corpse_ticks = CORPSE_TIMEOUT;
				e->stats.effects.clearEffects();
			}
			if (e->activeAnimation->isSecondLastFrame()) {
				if (percentChance(e->stats.power_chance[ON_DEATH]))
					powers->activate(e->stats.power_index[ON_DEATH], &e->stats, e->stats.pos);
			}
			if (e->activeAnimation->isLastFrame() || e->activeAnimation->getName() != "die") {
				// puts renderable under object layer
				e->stats.corpse = true;

				//allow free movement over the corpse
				mapr->collider.unblock(e->stats.pos.x, e->stats.pos.y);

				// remove corpses that land on blocked tiles, such as water or pits
				if (!mapr->collider.is_valid_position(e->stats.pos.x, e->stats.pos.y, MOVEMENT_NORMAL, false)) {
					e->stats.corpse_ticks = 0;
				}

				// prevent "jumping" when rendering
				alignFPoint(&e->stats.pos);
			}

			break;

		case ENEMY_CRITDEAD:

			e->setAnimation("critdie");
			if (e->activeAnimation->isFirstFrame()) {
				e->play_sfx_critdie = true;
				e->stats.corpse_ticks = CORPSE_TIMEOUT;
				e->stats.effects.clearEffects();
			}
			if (e->activeAnimation->isSecondLastFrame()) {
				if (percentChance(e->stats.power_chance[ON_DEATH]))
					powers->activate(e->stats.power_index[ON_DEATH], &e->stats, e->stats.pos);
			}
			if (e->activeAnimation->isLastFrame() || e->activeAnimation->getName() != "critdie") {
				// puts renderable under object layer
				e->stats.corpse = true;

				//allow free movement over the corpse
				mapr->collider.unblock(e->stats.pos.x, e->stats.pos.y);

				// prevent "jumping" when rendering
				alignFPoint(&e->stats.pos);
			}

			break;

		default:
			break;
	}
}
/**
 * Check state changes related to movement
 */
void BehaviorStandard::checkMove() {

	// dying enemies can't move
	if (e->stats.cur_state == ENEMY_DEAD || e->stats.cur_state == ENEMY_CRITDEAD) return;

	// stunned enemies can't act
	if (e->stats.effects.stun) return;

	// handle not being in combat and (not patrolling waypoints or waiting at waypoint)
	if (!e->stats.hero_ally && !e->stats.in_combat && (e->stats.waypoints.empty() || e->stats.waypoint_pause_ticks > 0)) {

		if (e->stats.cur_state == ENEMY_MOVE) {
			e->newState(ENEMY_STANCE);
		}

		// currently enemies only move while in combat or patrolling
		return;
	}

	// clear current space to allow correct movement
	mapr->collider.unblock(e->stats.pos.x, e->stats.pos.y);

	// update direction
	if (e->stats.facing) {
		if (++e->stats.turn_ticks > e->stats.turn_delay) {

			// if blocked, face in pathfinder direction instead
			if (!mapr->collider.line_of_movement(e->stats.pos.x, e->stats.pos.y, pursue_pos.x, pursue_pos.y, e->stats.movement_type)) {

				// if a path is returned, target first waypoint

				bool recalculate_path = false;

				//if theres no path, it needs to be calculated
				if(path.empty())
					recalculate_path = true;

				//if the target moved more than 1 tile away, recalculate
				if(calcDist(map_to_collision(prev_target), map_to_collision(pursue_pos)) > 1.f)
					recalculate_path = true;

				//if a collision ocurred then recalculate
				if(collided)
					recalculate_path = true;

				//add a 5% chance to recalculate on every frame. This prevents reclaulating lots of entities in the same frame
				chance_calc_path += 5;

				if(percentChance(chance_calc_path))
					recalculate_path = true;

				//dont recalculate if we were blocked and no path was found last time
				//this makes sure that pathfinding calculation is not spammed when the target is unreachable and the entity is as close as its going to get
				if(!path_found && collided && !percentChance(chance_calc_path))
					recalculate_path = false;
				else//reset the collision flag only if we dont want the cooldown in place
					collided = false;

				prev_target = pursue_pos;

				// target first waypoint
				if(recalculate_path) {
					chance_calc_path = -100;
					path.clear();
					path_found = mapr->collider.compute_path(e->stats.pos, pursue_pos, path, e->stats.movement_type);
				}

				if(!path.empty()) {
					pursue_pos = path.back();

					//if distance to node is lower than a tile size, the node is going to be passed and can be removed
					if(calcDist(e->stats.pos, pursue_pos) <= 1.f)
						path.pop_back();
				}
			}
			else {
				path.clear();
			}

			if(fleeing)
				e->stats.direction = calcDirection(pursue_pos, e->stats.pos);
			else
				e->stats.direction = calcDirection(e->stats.pos, pursue_pos);
			e->stats.turn_ticks = 0;
		}
	}

	// try to start moving
	if (e->stats.cur_state == ENEMY_STANCE) {
		checkMoveStateStance();
	}

	// already moving
	else if (e->stats.cur_state == ENEMY_MOVE) {
		checkMoveStateMove();
	}

	// if patrolling waypoints and has reached a waypoint, cycle to the next one
	if (!e->stats.waypoints.empty()) {
		FPoint waypoint = e->stats.waypoints.front();
		FPoint pos = e->stats.pos;
		// if the patroller is close to the waypoint
		if (fabs(waypoint.x - pos.x) <= 0.5f && fabs(waypoint.y - pos.y) <= 0.5f) {
			e->stats.waypoints.pop();
			// pick a new random point if we're wandering
			if (e->stats.wander) {
				waypoint = getWanderPoint();
			}
			e->stats.waypoints.push(waypoint);
			e->stats.waypoint_pause_ticks = e->stats.waypoint_pause;
		}
	}

	// re-block current space to allow correct movement
	mapr->collider.block(e->stats.pos.x, e->stats.pos.y, e->stats.hero_ally);

}
/**
 * Begin using a power if idle, based on behavior % chances.
 * Activate a ready power, if the attack animation has followed through
 */
void BehaviorStandard::checkPower() {

	// stunned enemies can't act
	if (e->stats.effects.stun || fleeing) return;

	// currently all enemy power use happens during combat
	if (!e->stats.in_combat) return;

	// if the enemy is on global cooldown it cannot act
	if (e->stats.cooldown_ticks > 0) return;

	// Note there are two stages to activating a power.
	// First is the enemy choosing to use a power based on behavioral chance
	// Second is the power actually firing off once the related animation reaches the active frame.
	// (these are separate so that interruptions can take place)

	// Begin Power Animation:
	// standard enemies can begin a power-use animation if they're standing around or moving voluntarily.
	if (los && (e->stats.cur_state == ENEMY_STANCE || e->stats.cur_state == ENEMY_MOVE)) {

		// check half dead power use
		if (!e->stats.on_half_dead_casted && e->stats.hp <= e->stats.get(STAT_HP_MAX)/2) {
			if (percentChance(e->stats.power_chance[ON_HALF_DEAD])) {
				e->newState(ENEMY_POWER);
				e->stats.activated_powerslot = ON_HALF_DEAD;
				return;
			}
		}

		// check ranged power use
		if (target_dist > e->stats.melee_range) {

			if (percentChance(e->stats.power_chance[RANGED_PHYS]) && e->stats.power_ticks[RANGED_PHYS] == 0) {
				bool can_use = true;
				if(powers->powers[e->stats.power_index[RANGED_PHYS]].type == POWTYPE_SPAWN)
					if(e->stats.summonLimitReached(e->stats.power_index[RANGED_PHYS]))
						can_use = false;

				if(can_use) {
					e->newState(ENEMY_POWER);
					e->stats.activated_powerslot = RANGED_PHYS;
					return;
				}
			}
			if (percentChance(e->stats.power_chance[RANGED_MENT]) && e->stats.power_ticks[RANGED_MENT] == 0) {
				bool can_use = true;
				if(powers->powers[e->stats.power_index[RANGED_MENT]].type == POWTYPE_SPAWN)
					if(e->stats.summonLimitReached(e->stats.power_index[RANGED_MENT]))
						can_use = false;

				if(can_use) {
					e->newState(ENEMY_POWER);
					e->stats.activated_powerslot = RANGED_MENT;
					return;
				}
			}

		}
		else { // check melee power use

			if (percentChance(e->stats.power_chance[MELEE_PHYS]) && e->stats.power_ticks[MELEE_PHYS] == 0) {
				bool can_use = true;
				if(powers->powers[e->stats.power_index[MELEE_PHYS]].type == POWTYPE_SPAWN)
					if(e->stats.summonLimitReached(e->stats.power_index[MELEE_PHYS]))
						can_use = false;

				if(can_use) {
					e->newState(ENEMY_POWER);
					e->stats.activated_powerslot = MELEE_PHYS;
					return;
				}
			}
			if (percentChance(e->stats.power_chance[MELEE_MENT]) && e->stats.power_ticks[MELEE_MENT] == 0) {
				bool can_use = true;
				if(powers->powers[e->stats.power_index[MELEE_MENT]].type == POWTYPE_SPAWN)
					if(e->stats.summonLimitReached(e->stats.power_index[MELEE_MENT]))
						can_use = false;

				if(can_use) {
					e->newState(ENEMY_POWER);
					e->stats.activated_powerslot = MELEE_MENT;
					return;
				}
			}
		}
	}

	// Activate Power:
	// enemy has started the animation to use a power. Activate the power on the Active animation frame
	if (e->stats.cur_state == ENEMY_POWER) {

		// if we're at the active frame of a power animation,
		// activate the power and set the local and global cooldowns
		if (e->activeAnimation->isActiveFrame() || e->instant_power) {
			e->instant_power = false;

			int power_slot =  e->stats.activated_powerslot;
			int power_id = e->stats.power_index[e->stats.activated_powerslot];

			powers->activate(power_id, &e->stats, pursue_pos);
			e->stats.power_ticks[power_slot] = powers->powers[power_id].cooldown;
			e->stats.cooldown_ticks = e->stats.cooldown;

			if (e->stats.activated_powerslot == ON_HALF_DEAD) {
				e->stats.on_half_dead_casted = true;
			}
		}
	}

}
/**
 * Locate the player and set various targeting info
 */
void BehaviorStandard::findTarget() {
	float stealth_threat_range = (e->stats.threat_range * (100 - static_cast<float>(e->stats.hero_stealth))) / 100;

	// stunned enemies can't act
	if (e->stats.effects.stun) return;

	// check distance and line of sight between enemy and hero
	if (pc->stats.alive)
		hero_dist = calcDist(e->stats.pos, pc->stats.pos);
	else
		hero_dist = 0;


	// aggressive enemies are always in combat
	if (!e->stats.in_combat && e->stats.combat_style == COMBAT_AGGRESSIVE) {
		e->stats.in_combat = true;
		powers->activate(e->stats.power_index[BEACON], &e->stats, e->stats.pos); //emit beacon
	}

	// check entering combat (because the player hit the enemy)
	if (e->stats.join_combat) {
		if (hero_dist <= (stealth_threat_range *2)) {
			e->stats.join_combat = false;
		}
		else {
			e->stats.in_combat = true;
			powers->activate(e->stats.power_index[BEACON], &e->stats, e->stats.pos); //emit beacon
		}
	}

	// check entering combat (because the player got too close)
	if (!e->stats.in_combat && los && hero_dist < stealth_threat_range && e->stats.combat_style != COMBAT_PASSIVE) {
		e->stats.in_combat = true;
		powers->activate(e->stats.power_index[BEACON], &e->stats, e->stats.pos); //emit beacon
	}

	// check exiting combat (player died or got too far away)
	if (e->stats.in_combat && hero_dist > (e->stats.threat_range *2) && !e->stats.join_combat && e->stats.combat_style != COMBAT_AGGRESSIVE) {
		e->stats.in_combat = false;
	}

	// check exiting combat (player or enemy died)
	if ((!e->stats.alive || !pc->stats.alive) && e->stats.combat_style != COMBAT_AGGRESSIVE) {
		e->stats.in_combat = false;
	}

	// by default, the enemy pursues the hero directly
	pursue_pos.x = pc->stats.pos.x;
	pursue_pos.y = pc->stats.pos.y;
	target_dist = hero_dist;


	//if there are player allies closer than the hero, target an ally instead
	if(e->stats.in_combat) {
		for (unsigned int i=0; i < enemies->enemies.size(); i++) {
			if(!enemies->enemies[i]->stats.corpse && enemies->enemies[i]->stats.hero_ally) {
				//now work out the distance to the minion and compare it to the distance to the current targer (we want to target the closest ally)
				float ally_dist = calcDist(e->stats.pos, enemies->enemies[i]->stats.pos);
				if (ally_dist < target_dist) {
					pursue_pos.x = enemies->enemies[i]->stats.pos.x;
					pursue_pos.y = enemies->enemies[i]->stats.pos.y;
					target_dist = ally_dist;
				}
			}
		}
	}

	// if we just started wandering, set the first waypoint
	if (e->stats.wander && e->stats.waypoints.empty()) {
		FPoint waypoint = getWanderPoint();
		e->stats.waypoints.push(waypoint);
		e->stats.waypoint_pause_ticks = e->stats.waypoint_pause;
	}

	// if we're not in combat, pursue the next waypoint
	if (!(e->stats.in_combat || e->stats.waypoints.empty())) {
		FPoint waypoint = e->stats.waypoints.front();
		pursue_pos.x = waypoint.x;
		pursue_pos.y = waypoint.y;
	}

	// check line-of-sight
	if (target_dist < e->stats.threat_range && pc->stats.alive)
		los = mapr->collider.line_of_sight(e->stats.pos.x, e->stats.pos.y, pc->stats.pos.x, pc->stats.pos.y);
	else
		los = false;

	if(e->stats.effects.fear) fleeing = true;

	// If we have a successful chance_flee roll, try to move to a safe distance
	if (e->stats.cur_state == ENEMY_STANCE && !move_to_safe_dist && hero_dist < e->stats.threat_range/2 && hero_dist >= e->stats.melee_range && percentChance(e->stats.chance_flee))
		move_to_safe_dist = true;

	if (move_to_safe_dist) fleeing = true;
}
/**
 * Perform miscellaneous state-based actions.
 * 1) Set animations and sound effects
 * 2) Return to the default state (Stance) when actions are complete
 */
void BehaviorStandard::updateState() {

	// stunned enemies can't act
	if (e->stats.effects.stun) return;

	int power_id;
	int power_state;

	// continue current animations
	e->activeAnimation->advanceFrame();

	switch (e->stats.cur_state) {

		case ENEMY_STANCE:

			e->setAnimation("stance");
			break;

		case ENEMY_MOVE:

			e->setAnimation("run");
			break;

		case ENEMY_POWER:

			power_id = e->stats.power_index[e->stats.activated_powerslot];
			power_state = e->powers->powers[power_id].new_state;

			// animation based on power type
			if (power_state == POWSTATE_SWING) e->setAnimation("melee");
			else if (power_state == POWSTATE_SHOOT) e->setAnimation("ranged");
			else if (power_state == POWSTATE_CAST) e->setAnimation("ment");
			else if (power_state == POWSTATE_INSTANT) e->instant_power = true;

			// sound effect based on power type
			if (e->activeAnimation->isFirstFrame()) {
				if (power_state == POWSTATE_SWING) e->sfx_phys = true;
				else if (power_state == POWSTATE_SHOOT) e->sfx_phys = true;
				else if (power_state == POWSTATE_CAST) e->sfx_ment = true;
			}

			if (e->activeAnimation->isLastFrame()) e->newState(ENEMY_STANCE);
			break;

		case ENEMY_SPAWN:

			e->setAnimation("spawn");
			//the second check is needed in case the entity does not have a spawn animation
			if (e->activeAnimation->isLastFrame() || e->activeAnimation->getName() != "spawn") {
				e->newState(ENEMY_STANCE);
				e->CheckSummonSustained();
			}
			break;

		case ENEMY_BLOCK:

			e->setAnimation("block");
			break;

		case ENEMY_HIT:

			e->setAnimation("hit");
			if (e->activeAnimation->isFirstFrame()) {
				e->stats.effects.triggered_hit = true;
			}
			if (e->activeAnimation->isLastFrame()) e->newState(ENEMY_STANCE);
			break;

		case ENEMY_DEAD:
			if (e->stats.effects.triggered_death) break;

			e->setAnimation("die");
			if (e->activeAnimation->isFirstFrame()) {
				e->sfx_die = true;
				e->stats.corpse_ticks = CORPSE_TIMEOUT;
				e->stats.effects.clearEffects();
			}
			if (e->activeAnimation->isSecondLastFrame()) {
				if (percentChance(e->stats.power_chance[ON_DEATH]))
					e->powers->activate(e->stats.power_index[ON_DEATH], &e->stats, e->stats.pos);
			}
			if (e->activeAnimation->isLastFrame()) {
				e->stats.corpse = true; // puts renderable under object layer
				//allow free movement over the corpse
				e->map->collider.unblock(e->stats.pos.x, e->stats.pos.y);
			}

			break;

		case ENEMY_CRITDEAD:

			e->setAnimation("critdie");
			if (e->activeAnimation->isFirstFrame()) {
				e->sfx_critdie = true;
				e->stats.corpse_ticks = CORPSE_TIMEOUT;
				e->stats.effects.clearEffects();
			}
			if (e->activeAnimation->isSecondLastFrame()) {
				if (percentChance(e->stats.power_chance[ON_DEATH]))
					e->powers->activate(e->stats.power_index[ON_DEATH], &e->stats, e->stats.pos);
			}
			if (e->activeAnimation->isLastFrame()) e->stats.corpse = true; // puts renderable under object layer

			break;

		default:
			break;
	}
}
/**
 * Locate the player and set various targeting info
 */
void BehaviorStandard::findTarget() {
	int stealth_threat_range = (e->stats.threat_range * (100 - e->stats.hero_stealth)) / 100;

	// stunned enemies can't act
	if (e->stats.effects.stun) return;

	// check distance and line of sight between enemy and hero
	if (e->stats.hero_alive)
		hero_dist = calcDist(e->stats.pos, e->stats.hero_pos);
	else
		hero_dist = 0;


	// check entering combat (because the player hit the enemy)
	if (e->stats.join_combat) {
		if (hero_dist <= (stealth_threat_range *2)) {
			e->stats.join_combat = false;
		}
		else {
			e->stats.in_combat = true;
			e->powers->activate(e->stats.power_index[BEACON], &e->stats, e->stats.pos); //emit beacon
		}
	}

	// check entering combat (because the player got too close)
	if (!e->stats.in_combat && los && hero_dist < stealth_threat_range && !e->stats.passive_attacker) {

		if (e->stats.in_combat) e->stats.join_combat = true;
		e->stats.in_combat = true;
		e->powers->activate(e->stats.power_index[BEACON], &e->stats, e->stats.pos); //emit beacon
	}

	// check exiting combat (player died or got too far away)
	if (e->stats.in_combat && hero_dist > (e->stats.threat_range *2) && !e->stats.join_combat) {
		e->stats.in_combat = false;
	}

	// check exiting combat (player or enemy died)
	if (!e->stats.alive || !e->stats.hero_alive) {
		e->stats.in_combat = false;
	}

	// if the creature is a wanderer, pick a random point within the wander area to travel to
	if (e->stats.wander && !e->stats.in_combat && e->stats.wander_area.w > 0 && e->stats.wander_area.h > 0) {
		if (e->stats.wander_ticks == 0) {
			pursue_pos.x = e->stats.wander_area.x + (rand() % (e->stats.wander_area.w));
			pursue_pos.y = e->stats.wander_area.y + (rand() % (e->stats.wander_area.h));
			e->stats.wander_ticks = (rand() % 150) + 150;
		}
	}
	else {
		// by default, the enemy pursues the hero directly
		pursue_pos.x = e->stats.hero_pos.x;
		pursue_pos.y = e->stats.hero_pos.y;
		target_dist = hero_dist;


		//if there are player allies closer than the hero, target an ally instead
		if(e->stats.in_combat) {
			for (unsigned int i=0; i < enemies->enemies.size(); i++) {
				if(!enemies->enemies[i]->stats.corpse && enemies->enemies[i]->stats.hero_ally) {
					//now work out the distance to the minion and compare it to the distance to the current targer (we want to target the closest ally)
					int ally_dist = calcDist(e->stats.pos, enemies->enemies[i]->stats.pos);
					if (ally_dist < target_dist) {
						pursue_pos.x = enemies->enemies[i]->stats.pos.x;
						pursue_pos.y = enemies->enemies[i]->stats.pos.y;
						target_dist = ally_dist;
					}
				}
			}
		}


		if (!(e->stats.in_combat || e->stats.waypoints.empty())) {
			Point waypoint = e->stats.waypoints.front();
			pursue_pos.x = waypoint.x;
			pursue_pos.y = waypoint.y;
		}
	}

	// check line-of-sight
	if (target_dist < e->stats.threat_range && e->stats.hero_alive)
		los = e->map->collider.line_of_sight(e->stats.pos.x, e->stats.pos.y, pursue_pos.x, pursue_pos.y);
	else
		los = false;

	if(e->stats.effects.fear) fleeing = true;

	// If we have a successful chance_flee roll, try to move to a safe distance
	if (e->stats.cur_state == ENEMY_STANCE && !move_to_safe_dist && hero_dist < e->stats.threat_range/2 && hero_dist >= e->stats.melee_range && percentChance(e->stats.chance_flee))
		move_to_safe_dist = true;

	if (move_to_safe_dist) fleeing = true;
}
void HazardManager::logic() {

	// remove all hazards with lifespan 0.  Most hazards still display their last frame.
	for (size_t i=h.size(); i>0; i--) {
		if (h[i-1]->lifespan == 0) {
			delete h[i-1];
			h.erase(h.begin()+(i-1));
		}
	}

	checkNewHazards();

	// handle single-frame transforms
	for (size_t i=h.size(); i>0; i--) {
		h[i-1]->logic();

		// remove all hazards that need to die immediately (e.g. exit the map)
		if (h[i-1]->remove_now) {
			delete h[i-1];
			h.erase(h.begin()+(i-1));
			continue;
		}


		// if a moving hazard hits a wall, check for an after-effect
		if (h[i-1]->hit_wall) {
			if (h[i-1]->script_trigger == SCRIPT_TRIGGER_WALL) {
				EventManager::executeScript(h[i-1]->script, h[i-1]->pos.x, h[i-1]->pos.y);
			}

			if (h[i-1]->wall_power > 0 && percentChance(h[i-1]->wall_power_chance)) {
				powers->activate(h[i-1]->wall_power, h[i-1]->src_stats, h[i-1]->pos);

				if (powers->powers[h[i-1]->wall_power].directional) {
					powers->hazards.back()->animationKind = h[i-1]->animationKind;
				}
			}

			// clear wall hit
			h[i-1]->hit_wall = false;
		}

	}

	bool hit;

	// handle collisions
	for (size_t i=0; i<h.size(); i++) {
		if (h[i]->isDangerousNow()) {

			// process hazards that can hurt enemies
			if (h[i]->source_type != SOURCE_TYPE_ENEMY) { //hero or neutral sources
				for (unsigned int eindex = 0; eindex < enemym->enemies.size(); eindex++) {

					// only check living enemies
					if (enemym->enemies[eindex]->stats.hp > 0 && h[i]->active && (enemym->enemies[eindex]->stats.hero_ally == h[i]->target_party)) {
						if (isWithinRadius(h[i]->pos, h[i]->radius, enemym->enemies[eindex]->stats.pos)) {
							if (!h[i]->hasEntity(enemym->enemies[eindex])) {
								h[i]->addEntity(enemym->enemies[eindex]);
								if (!h[i]->beacon) last_enemy = enemym->enemies[eindex];
								// hit!
								hit = enemym->enemies[eindex]->takeHit(*h[i]);
								hitEntity(i, hit);
							}
						}
					}

				}
			}

			// process hazards that can hurt the hero
			if (h[i]->source_type != SOURCE_TYPE_HERO && h[i]->source_type != SOURCE_TYPE_ALLY) { //enemy or neutral sources
				if (pc->stats.hp > 0 && h[i]->active) {
					if (isWithinRadius(h[i]->pos, h[i]->radius, pc->stats.pos)) {
						if (!h[i]->hasEntity(pc)) {
							h[i]->addEntity(pc);
							// hit!
							hit = pc->takeHit(*h[i]);
							hitEntity(i, hit);
						}
					}
				}

				//now process allies
				for (unsigned int eindex = 0; eindex < enemym->enemies.size(); eindex++) {
					// only check living allies
					if (enemym->enemies[eindex]->stats.hp > 0 && h[i]->active && enemym->enemies[eindex]->stats.hero_ally) {
						if (isWithinRadius(h[i]->pos, h[i]->radius, enemym->enemies[eindex]->stats.pos)) {
							if (!h[i]->hasEntity(enemym->enemies[eindex])) {
								h[i]->addEntity(enemym->enemies[eindex]);
								// hit!
								hit = enemym->enemies[eindex]->takeHit(*h[i]);
								hitEntity(i, hit);
							}
						}
					}
				}

			}

		}
	}
}
Beispiel #10
0
/**
 * Whenever a hazard collides with an entity, this function resolves the effect
 * Called by HazardManager
 *
 * Returns false on miss
 */
bool Entity::takeHit(Hazard &h) {

	//check if this enemy should be affected by this hazard based on the category
	if(!powers->powers[h.power_index].target_categories.empty() && !stats.hero) {
		//the power has a target category requirement, so if it doesnt match, dont continue
		bool match_found = false;
		for (unsigned int i=0; i<stats.categories.size(); i++) {
			if(std::find(powers->powers[h.power_index].target_categories.begin(), powers->powers[h.power_index].target_categories.end(), stats.categories[i]) != powers->powers[h.power_index].target_categories.end()) {
				match_found = true;
			}
		}
		if(!match_found)
			return false;
	}

	//if the target is already dead, they cannot be hit
	if ((stats.cur_state == ENEMY_DEAD || stats.cur_state == ENEMY_CRITDEAD) && !stats.hero)
		return false;

	if(stats.cur_state == AVATAR_DEAD && stats.hero)
		return false;

	//if the target is an enemy and they are not already in combat, activate a beacon to draw other enemies into battle
	if (!stats.in_combat && !stats.hero && !stats.hero_ally) {
		stats.join_combat = true;
		stats.in_combat = true;
		powers->activate(stats.power_index[BEACON], &stats, stats.pos); //emit beacon
	}

	// exit if it was a beacon (to prevent stats.targeted from being set)
	if (powers->powers[h.power_index].beacon) return false;

	// prepare the combat text
	CombatText *combat_text = comb;

	// if it's a miss, do nothing
	int accuracy = h.accuracy;
	if(powers->powers[h.power_index].mod_accuracy_mode == STAT_MODIFIER_MODE_MULTIPLY)
		accuracy = accuracy * powers->powers[h.power_index].mod_accuracy_value / 100;
	else if(powers->powers[h.power_index].mod_accuracy_mode == STAT_MODIFIER_MODE_ADD)
		accuracy += powers->powers[h.power_index].mod_accuracy_value;
	else if(powers->powers[h.power_index].mod_accuracy_mode == STAT_MODIFIER_MODE_ABSOLUTE)
		accuracy = powers->powers[h.power_index].mod_accuracy_value;

	int avoidance = 0;
	if(!powers->powers[h.power_index].trait_avoidance_ignore) {
		avoidance = stats.get(STAT_AVOIDANCE);
	}

	int true_avoidance = 100 - (accuracy + 25 - avoidance);
	//if we are using an absolute accuracy, offset the constant 25 added to the accuracy
	if(powers->powers[h.power_index].mod_accuracy_mode == STAT_MODIFIER_MODE_ABSOLUTE)
		true_avoidance += 25;
	clampFloor(true_avoidance, MIN_AVOIDANCE);
	clampCeil(true_avoidance, MAX_AVOIDANCE);

	if (h.missile && percentChance(stats.get(STAT_REFLECT))) {
		// reflect the missile 180 degrees
		h.setAngle(h.angle+M_PI);

		// change hazard source to match the reflector's type
		// maybe we should change the source stats pointer to the reflector's StatBlock
		if (h.source_type == SOURCE_TYPE_HERO || h.source_type == SOURCE_TYPE_ALLY)
			h.source_type = SOURCE_TYPE_ENEMY;
		else if (h.source_type == SOURCE_TYPE_ENEMY)
			h.source_type = stats.hero ? SOURCE_TYPE_HERO : SOURCE_TYPE_ALLY;

		// reset the hazard ticks
		h.lifespan = h.base_lifespan;

		if (activeAnimation->getName() == "block") {
			play_sfx_block = true;
		}

		return false;
	}

	if (percentChance(true_avoidance)) {
		combat_text->addMessage(msg->get("miss"), stats.pos, COMBAT_MESSAGE_MISS);
		return false;
	}

	// calculate base damage
	int dmg = randBetween(h.dmg_min, h.dmg_max);

	if(powers->powers[h.power_index].mod_damage_mode == STAT_MODIFIER_MODE_MULTIPLY)
		dmg = dmg * powers->powers[h.power_index].mod_damage_value_min / 100;
	else if(powers->powers[h.power_index].mod_damage_mode == STAT_MODIFIER_MODE_ADD)
		dmg += powers->powers[h.power_index].mod_damage_value_min;
	else if(powers->powers[h.power_index].mod_damage_mode == STAT_MODIFIER_MODE_ABSOLUTE)
		dmg = randBetween(powers->powers[h.power_index].mod_damage_value_min, powers->powers[h.power_index].mod_damage_value_max);

	// apply elemental resistance
	if (h.trait_elemental >= 0 && unsigned(h.trait_elemental) < stats.vulnerable.size()) {
		unsigned i = h.trait_elemental;
		int vulnerable = stats.vulnerable[i];
		clampFloor(vulnerable,MIN_RESIST);
		if (stats.vulnerable[i] < 100)
			clampCeil(vulnerable,MAX_RESIST);
		dmg = (dmg * vulnerable) / 100;
	}

	if (!h.trait_armor_penetration) { // armor penetration ignores all absorption
		// substract absorption from armor
		int absorption = randBetween(stats.get(STAT_ABS_MIN), stats.get(STAT_ABS_MAX));

		if (absorption > 0 && dmg > 0) {
			int abs = absorption;
			if ((abs*100)/dmg < MIN_BLOCK)
				absorption = (dmg * MIN_BLOCK) /100;
			if ((abs*100)/dmg > MAX_BLOCK)
				absorption = (dmg * MAX_BLOCK) /100;
			if ((abs*100)/dmg < MIN_ABSORB && !stats.effects.triggered_block)
				absorption = (dmg * MIN_ABSORB) /100;
			if ((abs*100)/dmg > MAX_ABSORB && !stats.effects.triggered_block)
				absorption = (dmg * MAX_ABSORB) /100;

			// Sometimes, the absorb limits cause absorbtion to drop to 1
			// This could be confusing to a player that has something with an absorb of 1 equipped
			// So we round absorption up in this case
			if (absorption == 0) absorption = 1;
		}

		dmg = dmg - absorption;
		if (dmg <= 0) {
			dmg = 0;
			if (h.trait_elemental < 0) {
				if (stats.effects.triggered_block && MAX_BLOCK < 100) dmg = 1;
				else if (!stats.effects.triggered_block && MAX_ABSORB < 100) dmg = 1;
			}
			else {
				if (MAX_RESIST < 100) dmg = 1;
			}
			if (activeAnimation->getName() == "block") {
				play_sfx_block = true;
				resetActiveAnimation();
			}
		}
	}

	// check for crits
	int true_crit_chance = h.crit_chance;

	if(powers->powers[h.power_index].mod_crit_mode == STAT_MODIFIER_MODE_MULTIPLY)
		true_crit_chance = true_crit_chance * powers->powers[h.power_index].mod_crit_value / 100;
	else if(powers->powers[h.power_index].mod_crit_mode == STAT_MODIFIER_MODE_ADD)
		true_crit_chance += powers->powers[h.power_index].mod_crit_value;
	else if(powers->powers[h.power_index].mod_crit_mode == STAT_MODIFIER_MODE_ABSOLUTE)
		true_crit_chance = powers->powers[h.power_index].mod_crit_value;

	if (stats.effects.stun || stats.effects.speed < 100)
		true_crit_chance += h.trait_crits_impaired;

	bool crit = percentChance(true_crit_chance);
	if (crit) {
		dmg = dmg + h.dmg_max;
		if(!stats.hero)
			mapr->shaky_cam_ticks = MAX_FRAMES_PER_SEC/2;
	}

	if(stats.hero)
		combat_text->addMessage(dmg, stats.pos, COMBAT_MESSAGE_TAKEDMG);
	else {
		if(crit)
			combat_text->addMessage(dmg, stats.pos, COMBAT_MESSAGE_CRIT);
		else
			combat_text->addMessage(dmg, stats.pos, COMBAT_MESSAGE_GIVEDMG);
	}

	// temporarily save the current HP for calculating HP/MP steal on final blow
	int prev_hp = stats.hp;

	// apply damage
	stats.takeDamage(dmg);

	// after effects
	if (dmg > 0) {

		// damage always breaks stun
		stats.effects.removeEffectType(EFFECT_STUN);

		if (stats.hp > 0) {
			powers->effect(&stats, h.src_stats, h.power_index,h.source_type);
		}

		if (!stats.effects.immunity) {
			if (h.hp_steal != 0) {
				int steal_amt = (std::min(dmg, prev_hp) * h.hp_steal) / 100;
				if (steal_amt == 0) steal_amt = 1;
				combat_text->addMessage(msg->get("+%d HP",steal_amt), h.src_stats->pos, COMBAT_MESSAGE_BUFF);
				h.src_stats->hp = std::min(h.src_stats->hp + steal_amt, h.src_stats->get(STAT_HP_MAX));
			}
			if (h.mp_steal != 0) {
				int steal_amt = (std::min(dmg, prev_hp) * h.mp_steal) / 100;
				if (steal_amt == 0) steal_amt = 1;
				combat_text->addMessage(msg->get("+%d MP",steal_amt), h.src_stats->pos, COMBAT_MESSAGE_BUFF);
				h.src_stats->mp = std::min(h.src_stats->mp + steal_amt, h.src_stats->get(STAT_MP_MAX));
			}
		}
	}

	// post effect power
	if (h.post_power > 0 && dmg > 0) {
		powers->activate(h.post_power, h.src_stats, stats.pos);
	}

	// loot
	if (dmg > 0 && !h.loot.empty()) {
		for (unsigned i=0; i<h.loot.size(); i++) {
			powers->loot.push_back(h.loot[i]);
			powers->loot.back().x = (int)stats.pos.x;
			powers->loot.back().y = (int)stats.pos.y;
		}
	}

	// interrupted to new state
	if (dmg > 0) {
		bool chance_poise = percentChance(stats.get(STAT_POISE));

		if(stats.hp <= 0) {
			stats.effects.triggered_death = true;
			if(stats.hero)
				stats.cur_state = AVATAR_DEAD;
			else {
				doRewards(h.source_type);
				if (crit)
					stats.cur_state = ENEMY_CRITDEAD;
				else
					stats.cur_state = ENEMY_DEAD;
				mapr->collider.unblock(stats.pos.x,stats.pos.y);
			}
		}
		// don't go through a hit animation if stunned
		else if (!stats.effects.stun && !chance_poise) {
			play_sfx_hit = true;

			if(!chance_poise && stats.cooldown_hit_ticks == 0) {
				if(stats.hero)
					stats.cur_state = AVATAR_HIT;
				else
					stats.cur_state = ENEMY_HIT;
				stats.cooldown_hit_ticks = stats.cooldown_hit;

				if (stats.untransform_on_hit)
					stats.transform_duration = 0;
			}
			// roll to see if the enemy's ON_HIT power is casted
			if (percentChance(stats.power_chance[ON_HIT])) {
				powers->activate(stats.power_index[ON_HIT], &stats, stats.pos);
			}
		}
		// just play the hit sound
		else
			play_sfx_hit = true;
	}

	return true;
}
/**
 * A particular event has been triggered.
 * Process all of this events components.
 *
 * @param The triggered event
 * @return Returns true if the event shall not be run again.
 */
bool EventManager::executeEvent(Event &ev) {
	// skip executing events that are on cooldown
	if (ev.cooldown_ticks > 0) return false;

	// need to know this for early returns
	Event_Component *ec_repeat = ev.getComponent(EC_REPEAT);
	if (ec_repeat) {
		ev.keep_after_trigger = ec_repeat->x == 0 ? false : true;
	}

	// delay event execution
	if (ev.delay > 0) {
		ev.delay_ticks = ev.delay;

		mapr->delayed_events.push_back(ev);
		mapr->delayed_events.back().delay_ticks = ev.delay;
		mapr->delayed_events.back().delay = 0;

		ev.cooldown_ticks = ev.cooldown + ev.delay;

		return !ev.keep_after_trigger;
	}

	// set cooldown
	ev.cooldown_ticks = ev.cooldown;

	// if chance_exec roll fails, don't execute the event
	// we respect the value of "repeat", even if the event doesn't execute
	Event_Component *ec_chance_exec = ev.getComponent(EC_CHANCE_EXEC);
	if (ec_chance_exec && !percentChance(ec_chance_exec->x)) {
		return !ev.keep_after_trigger;
	}

	Event_Component *ec;

	for (unsigned i = 0; i < ev.components.size(); ++i) {
		ec = &ev.components[i];

		if (ec->type == EC_SET_STATUS) {
			camp->setStatus(ec->s);
		}
		else if (ec->type == EC_UNSET_STATUS) {
			camp->unsetStatus(ec->s);
		}
		else if (ec->type == EC_INTERMAP) {
			if (ec->z == 1) {
				// this is intermap_random
				std::string map_list = ec->s;
				Event_Component random_ec = getRandomMapFromFile(map_list);

				ec->s = random_ec.s;
				ec->x = random_ec.x;
				ec->y = random_ec.y;
			}

			if (fileExists(mods->locate(ec->s))) {
				mapr->teleportation = true;
				mapr->teleport_mapname = ec->s;

				if (ec->x == -1 && ec->y == -1) {
					// the teleport destination will be set to the map's hero_pos once the map is loaded
					mapr->teleport_destination.x = -1;
					mapr->teleport_destination.y = -1;
				}
				else {
					mapr->teleport_destination.x = static_cast<float>(ec->x) + 0.5f;
					mapr->teleport_destination.y = static_cast<float>(ec->y) + 0.5f;
				}
			}
			else {
				ev.keep_after_trigger = false;
				pc->logMsg(msg->get("Unknown destination"), false);
			}
		}
		else if (ec->type == EC_INTRAMAP) {
			mapr->teleportation = true;
			mapr->teleport_mapname = "";
			mapr->teleport_destination.x = static_cast<float>(ec->x) + 0.5f;
			mapr->teleport_destination.y = static_cast<float>(ec->y) + 0.5f;
		}
		else if (ec->type == EC_MAPMOD) {
			if (ec->s == "collision") {
				if (ec->x >= 0 && ec->x < mapr->w && ec->y >= 0 && ec->y < mapr->h) {
					mapr->collider.colmap[ec->x][ec->y] = static_cast<unsigned short>(ec->z);
					mapr->map_change = true;
				}
				else
					logError("EventManager: Mapmod at position (%d, %d) is out of bounds 0-255.", ec->x, ec->y);
			}
			else {
				size_t index = static_cast<size_t>(distance(mapr->layernames.begin(), find(mapr->layernames.begin(), mapr->layernames.end(), ec->s)));
				if (!mapr->isValidTile(ec->z))
					logError("EventManager: Mapmod at position (%d, %d) contains invalid tile id (%d).", ec->x, ec->y, ec->z);
				else if (index >= mapr->layers.size())
					logError("EventManager: Mapmod at position (%d, %d) is on an invalid layer.", ec->x, ec->y);
				else if (ec->x >= 0 && ec->x < mapr->w && ec->y >= 0 && ec->y < mapr->h)
					mapr->layers[index][ec->x][ec->y] = static_cast<unsigned short>(ec->z);
				else
					logError("EventManager: Mapmod at position (%d, %d) is out of bounds 0-255.", ec->x, ec->y);
			}
		}
		else if (ec->type == EC_SOUNDFX) {
			FPoint pos(0,0);
			bool loop = false;

			if (ec->x != -1 && ec->y != -1) {
				if (ec->x != 0 && ec->y != 0) {
					pos.x = static_cast<float>(ec->x) + 0.5f;
					pos.y = static_cast<float>(ec->y) + 0.5f;
				}
			}
			else if (ev.location.x != 0 && ev.location.y != 0) {
				pos.x = static_cast<float>(ev.location.x) + 0.5f;
				pos.y = static_cast<float>(ev.location.y) + 0.5f;
			}

			if (ev.activate_type == EVENT_ON_LOAD || ec->z != 0)
				loop = true;

			SoundManager::SoundID sid = snd->load(ec->s, "MapRenderer background soundfx");

			snd->play(sid, GLOBAL_VIRTUAL_CHANNEL, pos, loop);
			mapr->sids.push_back(sid);
		}
		else if (ec->type == EC_LOOT) {
			Event_Component *ec_lootcount = ev.getComponent(EC_LOOT_COUNT);
			if (ec_lootcount) {
				mapr->loot_count.x = ec_lootcount->x;
				mapr->loot_count.y = ec_lootcount->y;
			}
			else {
				mapr->loot_count.x = 0;
				mapr->loot_count.y = 0;
			}

			ec->x = ev.hotspot.x;
			ec->y = ev.hotspot.y;
			mapr->loot.push_back(*ec);
		}
		else if (ec->type == EC_MSG) {
			pc->logMsg(ec->s, false);
		}
		else if (ec->type == EC_SHAKYCAM) {
			mapr->shaky_cam_ticks = ec->x;
		}
		else if (ec->type == EC_REMOVE_CURRENCY) {
			camp->removeCurrency(ec->x);
		}
		else if (ec->type == EC_REMOVE_ITEM) {
			camp->removeItem(ec->x);
		}
		else if (ec->type == EC_REWARD_XP) {
			camp->rewardXP(ec->x, true);
		}
		else if (ec->type == EC_REWARD_CURRENCY) {
			camp->rewardCurrency(ec->x);
		}
		else if (ec->type == EC_REWARD_ITEM) {
			ItemStack istack;
			istack.item = ec->x;
			istack.quantity = ec->y;
			camp->rewardItem(istack);
		}
		else if (ec->type == EC_RESTORE) {
			camp->restoreHPMP(ec->s);
		}
		else if (ec->type == EC_SPAWN) {
			Point spawn_pos;
			spawn_pos.x = ec->x;
			spawn_pos.y = ec->y;
			enemym->spawn(ec->s, spawn_pos);
		}
		else if (ec->type == EC_POWER) {
			Event_Component *ec_path = ev.getComponent(EC_POWER_PATH);
			FPoint target;

			if (ec_path) {
				// targets hero option
				if (ec_path->s == "hero") {
					target.x = pc->stats.pos.x;
					target.y = pc->stats.pos.y;
				}
				// targets fixed path option
				else {
					target.x = static_cast<float>(ec_path->a) + 0.5f;
					target.y = static_cast<float>(ec_path->b) + 0.5f;
				}
			}
			// no path specified, targets self location
			else {
				target.x = static_cast<float>(ev.location.x) + 0.5f;
				target.y = static_cast<float>(ev.location.y) + 0.5f;
			}

			// ec->x is power id
			// ec->y is statblock index
			mapr->activatePower(ec->x, ec->y, target);
		}
		else if (ec->type == EC_STASH) {
			mapr->stash = ec->x == 0 ? false : true;
			if (mapr->stash) {
				mapr->stash_pos.x = static_cast<float>(ev.location.x) + 0.5f;
				mapr->stash_pos.y = static_cast<float>(ev.location.y) + 0.5f;
			}
		}
		else if (ec->type == EC_NPC) {
			mapr->event_npc = ec->s;
		}
		else if (ec->type == EC_MUSIC) {
			mapr->music_filename = ec->s;
			mapr->loadMusic();
		}
		else if (ec->type == EC_CUTSCENE) {
			mapr->cutscene = true;
			mapr->cutscene_file = ec->s;
		}
		else if (ec->type == EC_REPEAT) {
			ev.keep_after_trigger = ec->x == 0 ? false : true;
		}
		else if (ec->type == EC_SAVE_GAME) {
			mapr->save_game = ec->x == 0 ? false : true;
		}
		else if (ec->type == EC_NPC_ID) {
			mapr->npc_id = ec->x;
		}
		else if (ec->type == EC_BOOK) {
			mapr->show_book = ec->s;
		}
		else if (ec->type == EC_SCRIPT) {
			if (ev.center.x != -1 && ev.center.y != -1)
				executeScript(ec->s, ev.center.x, ev.center.y);
			else
				executeScript(ec->s, pc->stats.pos.x, pc->stats.pos.y);
		}
	}
	return !ev.keep_after_trigger;
}