<commit_before>/*
 * AiAgentImplementation.cpp
 *
 *  Created on: 13/07/2010
 *      Author: victor
 */

#include "AiAgent.h"

#include "events/AiThinkEvent.h"
#include "events/AiMoveEvent.h"
#include "events/AiWaitEvent.h"
#include "events/AiAwarenessEvent.h"
#include "events/RespawnCreatureTask.h"
#include "events/DespawnCreatureOnPlayerDissappear.h"
#include "server/zone/managers/combat/CombatManager.h"
#include "server/zone/managers/creature/CreatureManager.h"
#include "server/zone/managers/templates/TemplateManager.h"
#include "server/zone/objects/player/PlayerCreature.h"
#include "server/zone/objects/tangible/weapon/WeaponObject.h"
#include "server/zone/managers/templates/TemplateManager.h"
#include "server/zone/managers/creature/CreatureTemplate.h"
#include "server/zone/managers/creature/CreatureTemplateManager.h"
#include "server/zone/managers/combat/CombatManager.h"
#include "server/zone/managers/collision/PathFinderManager.h"
#include "server/zone/packets/scene/UpdateTransformMessage.h"
#include "server/zone/packets/scene/LightUpdateTransformMessage.h"
#include "server/zone/packets/scene/LightUpdateTransformWithParentMessage.h"
#include "server/zone/packets/scene/UpdateTransformWithParentMessage.h"
#include "server/zone/packets/object/StringList.h"
#include "server/zone/packets/object/NpcConversationMessage.h"
#include "server/zone/packets/object/StartNpcConversation.h"
#include "server/zone/packets/object/StopNpcConversation.h"
#include "server/zone/objects/creature/events/DespawnCreatureTask.h"
#include "server/zone/Zone.h"
#include "server/zone/ZoneServer.h"
#include "PatrolPoint.h"
#include "AiObserver.h"
#include "CreatureSetDefenderTask.h"
#include "server/zone/templates/appearance/PortalLayout.h"
#include "server/zone/templates/appearance/FloorMesh.h"


void AiAgentImplementation::loadTemplateData(SharedObjectTemplate* templateData) {
	CreatureObjectImplementation::loadTemplateData(templateData);
	objectTemplate = templateData;
}

void AiAgentImplementation::loadTemplateData(CreatureTemplate* templateData) {
	npcTemplate = templateData;

	setPvpStatusBitmask(npcTemplate->getPvpBitmask());

	optionsBitmask = npcTemplate->getOptionsBitmask();
	//npcTemplate->getCreatureBitmask(); -- TODO: need to add a bitmask for AI (pack, herd, etc)
	level = npcTemplate->getLevel();

	if (weapons.size() == 0) {
		Vector<String> wepgroups = npcTemplate->getWeapons();
		for (int i = 0; i < wepgroups.size(); ++i) {
			Vector<String> weptemps = CreatureTemplateManager::instance()->getWeapons(wepgroups.get(i));

			if (weptemps.size() > 0) {
				uint32 crc = weptemps.get(System::random(weptemps.size() - 1)).hashCode();
				ManagedReference<WeaponObject*> weao = dynamic_cast<WeaponObject*>(server->getZoneServer()->createObject(crc, 0));
				weao->setMinDamage((weao->getMinDamage() / 2) + npcTemplate->getDamageMin());
				weao->setMaxDamage((weao->getMaxDamage() / 2) + npcTemplate->getDamageMax());
				weapons.add(weao);
			}
		}

		// add the default weapon
		weapons.add(getWeapon());
	}

	// set the damage of the default weapon
	getWeapon()->setMinDamage(npcTemplate->getDamageMin());
	getWeapon()->setMaxDamage(npcTemplate->getDamageMax());

	int ham;
	baseHAM.removeAll();
	for (int i = 0; i < 9; ++i) {
		if (i % 3 == 0) {
			ham = System::random(npcTemplate->getBaseHAMmax() - npcTemplate->getBaseHAM()) + npcTemplate->getBaseHAM();
			baseHAM.add(ham);
		} else
			baseHAM.add(ham/100);
	}

	hamList.removeAll();
	for (int i = 0; i < 9; ++i) {
		hamList.add(baseHAM.get(i));
	}

	maxHamList.removeAll();
	for (int i = 0; i < 9; ++i) {
		maxHamList.add(baseHAM.get(i));
	}

	objectName = npcTemplate->getObjectName();
}

void AiAgentImplementation::initializeTransientMembers() {
	CreatureObjectImplementation::initializeTransientMembers();

	npcTemplate = NULL;

	SharedObjectTemplate* templ = TemplateManager::instance()->getTemplate(serverObjectCRC);
	objectTemplate = templ;
}

void AiAgentImplementation::doRecovery() {
	if (isDead() || getZone() == NULL)
		return;

	activateHAMRegeneration();
	activateStateRecovery();
	activatePostureRecovery();

	if (damageOverTimeList.hasDot() && damageOverTimeList.isNextTickPast()) {
		damageOverTimeList.activateDots(_this);
	}

	CreatureObject* target = damageMap.getHighestThreatCreature();

	if (target != NULL && !defenderList.contains(target) && !target->isDead())
		addDefender(target);

	if (target == NULL && defenderList.size() > 0) {
		for (int i = 0; i < defenderList.size(); ++i) {
			SceneObject* tarObj = defenderList.get(i);

			if (tarObj->isCreatureObject()) {
				CreatureObject* targetCreature = (CreatureObject*)tarObj;

				if (!targetCreature->isDead()) {
					target = targetCreature;

					break;
				}
			}
		}
	}

	if (!isInCombat() || defenderList.size() <= 0 || target == NULL) {
		tryRetreat();
		activateRecovery();
		return;
	}

	if (target != NULL && (!target->isInRange(_this, 128) || !target->isAttackableBy(_this))) {
		//Locker clocker(target, _this);
		CombatManager::instance()->forcePeace(_this); //calling this on target will cause a deadlock, needs to be called in a new task
		activateRecovery();
		return;
	}

	if (commandQueue.size() > 5) {
		activateRecovery();
		return;
	}

	if (target != followObject)
		setDefender(target);

	selectWeapon();

	if (System::random(2) == 0 && npcTemplate != NULL && commandQueue.size() < 3) {
		// do special attack
		CreatureAttackMap* attackMap = npcTemplate->getAttacks();
		int attackNum = attackMap->getRandomAttackNumber();

		if (attackNum < 0 || attackNum >= attackMap->size()) {
			enqueueCommand(String("defaultattack").hashCode(), 0, target->getObjectID(), "");
		} else {
			String args = attackMap->getArguments(attackNum);

			if (!validateStateAttack(target, args)) {
				// do default attack
				enqueueCommand(String("defaultattack").hashCode(), 0, target->getObjectID(), "");
			} else {
				// queue special attack
				unsigned int actionCRC = attackMap->getCommand(attackNum).hashCode();
				enqueueCommand(actionCRC, 0, target->getObjectID(), args);

				if (System::random(4) == 0) {
					// queue second special attack (rudimentary combo)
					int secondAttackNum = attackMap->getRandomAttackNumber();
					args = attackMap->getArguments(secondAttackNum);

					if (validateStateAttack(target, args) && secondAttackNum != attackNum) {
						actionCRC = attackMap->getCommand(attackNum).hashCode();
						enqueueCommand(actionCRC, 0, target->getObjectID(), args);
					}
				}
			}
		}
	} else
		enqueueCommand(String("defaultattack").hashCode(), 0, target->getObjectID(), "");

	activateRecovery();
}

int AiAgentImplementation::notifyAttack(Observable* observable) {
	// TODO: add reaction attacks
	return 0;
}

int AiAgentImplementation::notifyCallForHelp(Observable* observable, ManagedObject* arg1) {
	// TODO: add aggroing
	return 0;
}

void AiAgentImplementation::selectWeapon() {
	float dist = 5.f;

	if (followObject != NULL)
		dist = getDistanceTo(followObject.get());

	float diff = 1024.f;
	WeaponObject* finalWeap = getWeapon();

	for (int i = 0; i < weapons.size(); ++i) {
		WeaponObject* weap = weapons.get(i);
		float range = abs(weap->getIdealRange() - dist);
		if (range < diff) {
			diff = range;
			finalWeap = weap;
		}
	}

	setWeapon(finalWeap, true);
}

bool AiAgentImplementation::validateStateAttack(CreatureObject* target, String& args) {
	StringTokenizer tokenizer(args);
	tokenizer.setDelimeter(";");

	while (tokenizer.hasMoreTokens()) {
		String singleArg;
		tokenizer.getStringToken(singleArg);

		if (singleArg.indexOf("Chance") != -1) {
			String stateName = singleArg.subString(0, args.indexOf("Chance"));
			uint64 state = CreatureState::getState(stateName);
			if (target->hasState(state) || (stateName == "postureDown" && target->isProne()) || (stateName == "knockdown" && target->isKnockedDown()) || (stateName == "postureUp" && target->isStanding())) {
				return false;

			}
		}
	}

	return true;
}

void AiAgentImplementation::setDespawnOnNoPlayerInRange(bool val) {
	if (despawnOnNoPlayerInRange == val)
		return;

	despawnOnNoPlayerInRange = val;

	if (val && numberOfPlayersInRange <= 0) {
		if (despawnEvent == NULL) {
			despawnEvent = new DespawnCreatureOnPlayerDissappear(_this);
		}

		if (!despawnEvent->isScheduled())
			despawnEvent->schedule(30000);
	}
}

bool AiAgentImplementation::tryRetreat() {
	try {
		if (homeLocation.getPositionX() == 0 && homeLocation.getPositionY() == 0 && homeLocation.getPositionZ() == 0)
			return false;

		if (lastSuccessfulCombatAction.miliDifference() <= 30000) {
			return false;
		} else if (homeLocation.isInRange(_this, 100))
			return false;

		setOblivious();

		showFlyText("npc_reaction/flytext", "afraid", 0xFF, 0, 0);

		homeLocation.setReached(false);

		damageMap.removeAll();

		patrolPoints.removeAll();
		patrolPoints.add(homeLocation);

		CombatManager::instance()->forcePeace(_this);

		activateMovementEvent();
	} catch (Exception& e) {
		error(e.getMessage());
		e.printStackTrace();
	}

	return true;
}

void AiAgentImplementation::setDefender(SceneObject* defender) {
	CreatureObjectImplementation::setDefender(defender);

	Reference<CreatureSetDefenderTask*> task;
	ManagedReference<AiObserver*> observer = NULL;

	if (aiObserverMap.size() == 0) {
		observer = new AiObserver(_this);
		ObjectManager::instance()->persistObject(observer, 1, "aiobservers");
		aiObserverMap.put(observer);
	} else {
		observer = aiObserverMap.get(0);

		task = new CreatureSetDefenderTask(_this, defender, observer, true);

		/*for (int i = 0; i < defenderList.size(); ++i)
			defenderList.get(i)->dropObserver(ObserverEventType::SPECIALATTACK, observer);*/
	}

	/*if (observer != NULL)
			defender->registerObserver(ObserverEventType::SPECIALATTACK, observer);*/


	if (task == NULL)
		task = new CreatureSetDefenderTask(_this, defender, observer, false);

	Core::getTaskManager()->executeTask(task);

	setFollowObject(defender);

	activateRecovery();
}

void AiAgentImplementation::addDefender(SceneObject* defender) {
	if (defenderList.size() == 0)
		showFlyText("npc_reaction/flytext", "threaten", 0xFF, 0, 0);

	CreatureObjectImplementation::addDefender(defender);

	activateRecovery();
}

void AiAgentImplementation::removeDefender(SceneObject* defender) {
	CreatureObjectImplementation::removeDefender(defender);

	if (defender != NULL) {
		if (defender->isCreatureObject())
			damageMap.dropDamage((CreatureObject*)defender);

		if (aiObserverMap.size() > 0)
			defender->dropObserver(ObserverEventType::SPECIALATTACK, aiObserverMap.get(0));
	}

	if (followObject == defender) {
		CreatureObject* target = damageMap.getHighestThreatCreature();

		if (target == NULL && defenderList.size() > 0) {
			SceneObject* tarObj = defenderList.get(0);
			if (tarObj->isCreatureObject())
				target = (CreatureObject*)tarObj;
		}

		if (target == NULL) {
			setOblivious();
		} else  {
			setDefender(target);
		}
	}

	activateRecovery();
}

/**
 * Cleares the combat state
 * @pre { this object is locked }
 * @post { this object is locked, this object is not in a combat state }
 * @param clearDefenders if true the defender vector willl be emptied
 */
void AiAgentImplementation::clearCombatState(bool clearDefenders) {
	CreatureObjectImplementation::clearCombatState(clearDefenders);

	damageMap.removeAll();

	setOblivious();
}

void AiAgentImplementation::notifyInsert(QuadTreeEntry* entry) {
	SceneObject* scno = (SceneObject*) entry;

	if (scno == _this)
		return;

	if (scno->isPlayerCreature())
		++numberOfPlayersInRange;
}

void AiAgentImplementation::clearDespawnEvent() {
	despawnEvent = NULL;
}

void AiAgentImplementation::respawn(Zone* zone, int level) {
	if (getZone() != NULL)
		return;

	setLevel(level);

	initializePosition(homeLocation.getPositionX(), homeLocation.getPositionZ(), homeLocation.getPositionY());

	SceneObject* cell = homeLocation.getCell();

	Locker zoneLocker(zone);

	if (cell != NULL)
		cell->addObject(_this, -1);

	insertToZone(zone);
}

void AiAgentImplementation::notifyDespawn(Zone* zone) {
	if (objectTemplate == NULL || npcTemplate == NULL)
		return;

	loadTemplateData(objectTemplate);
	loadTemplateData(npcTemplate);
	stateBitmask = 0;
	posture = CreaturePosture::UPRIGHT;
	shockWounds = 0;
	damageMap.removeAll();

	if (respawnTimer == 0) {
		zone->getCreatureManager()->addToReservePool(_this);
		return;
	}

	Reference<Task*> task = new RespawnCreatureTask(_this, zone, level);
	task->schedule(respawnTimer * 1000);
}

void AiAgentImplementation::scheduleDespawn() {
	if (getPendingTask("despawn") != NULL)
		return;

	Reference<DespawnCreatureTask*> despawn = new DespawnCreatureTask(_this);
	//despawn->schedule(300000); /// 5 minutes
	despawn->schedule(10000); /// 45 seconds
	addPendingTask("despawn", despawn);
}

void AiAgentImplementation::notifyDissapear(QuadTreeEntry* entry) {
	SceneObject* scno = (SceneObject*) entry;

	if (scno == _this)
		return;

	if (scno->isPlayerCreature()) {
		if ((--numberOfPlayersInRange <= 0)  && (despawnEvent == NULL)) {
			despawnEvent = new DespawnCreatureOnPlayerDissappear(_this);
			despawnEvent->schedule(30000);
		}
	}
}

void AiAgentImplementation::activateAwarenessEvent(CreatureObject *target) {
	if (awarenessEvent == NULL) {
		awarenessEvent = new AiAwarenessEvent(_this, target);

		awarenessEvent->schedule(1000);
	}

	if (!awarenessEvent->isScheduled()) {
		awarenessEvent->setTarget(target);
		awarenessEvent->schedule(1000);
	}
}

void AiAgentImplementation::activateRecovery() {
	if (thinkEvent == NULL) {
		thinkEvent = new AiThinkEvent(_this);

		thinkEvent->schedule(2000);
	}

	if (!thinkEvent->isScheduled())
		thinkEvent->schedule(2000);
}

void AiAgentImplementation::activatePostureRecovery() {
	if (isProne() || isKnockedDown() || isKneeling())
		executeObjectControllerAction(0xA8A25C79); // stand
}

void AiAgentImplementation::updateCurrentPosition(PatrolPoint* pos) {
	PatrolPoint* nextPosition = pos;

	setPosition(nextPosition->getPositionX(), nextPosition->getPositionZ(),
			nextPosition->getPositionY());

	SceneObject* cell = nextPosition->getCell();

	/*StringBuffer reachedPosition;
	reachedPosition << "(" << positionX << ", " << positionY << ")";
	info("reached " + reachedPosition.toString(), true);*/

	if (cell != NULL)
		updateZoneWithParent(cell, false, false);
	else
		updateZone(false, false);
}


void AiAgentImplementation::checkNewAngle() {
	if (followObject == NULL)
		return;

	faceObject(followObject);

	if (!nextStepPosition.isReached()) {
		broadcastNextPositionUpdate(&nextStepPosition);
	} else {
		++movementCounter;

		if (parent != NULL && parent->isCellObject())
			updateZoneWithParent(parent, true, true);
		else
			updateZone(true, true);
	}
}

bool AiAgentImplementation::findNextPosition(float maxDistance, WorldCoordinates& nextPosition) {
	Vector3 thisWorldPos = getWorldPosition();

	float newSpeed = runSpeed;
	if (followObject == NULL && !isFleeing() && !isRetreating()) // TODO: think about implementing a more generic "walk, don't run" criterion
		newSpeed = walkSpeed;

	float updateTicks = float(UPDATEMOVEMENTINTERVAL) / 1000.f;
	currentSpeed = newSpeed;

	newSpeed *= updateTicks;

	float newSpeedSquared = newSpeed * newSpeed;
	float newPositionX, newPositionZ, newPositionY;
	PathFinderManager* pathFinder = PathFinderManager::instance();

	bool found = false;
	float dist = 0;
	float dx, dy;
	ManagedReference<SceneObject*> cellObject;


	while (!found && patrolPoints.size() != 0) {
		PatrolPoint* targetPosition = &patrolPoints.get(0);

		Vector<WorldCoordinates>* path = pathFinder->findPath(_this, targetPosition->getCoordinates());

		if (path == NULL) {
			patrolPoints.remove(0);

			continue;
		}

		WorldCoordinates* oldCoord = NULL;
		float pathDistance = 0;

		bool remove = true;

		for (int i = 1; i < path->size() && !found; ++i) { // i = 0 is our position
			WorldCoordinates* coord = &path->get(i);

			Vector3 nextWorldPos = coord->getWorldPosition();

			if (oldCoord == NULL) {
				pathDistance += nextWorldPos.squaredDistanceTo(thisWorldPos);
				oldCoord = &path->get(0);
			} else {
				pathDistance += oldCoord->getWorldPosition().squaredDistanceTo(nextWorldPos);
			}

			if (i == path->size() - 1 || pathDistance >= newSpeedSquared || coord->getCell() != parent) { //last waypoint
				cellObject = coord->getCell();

				//TODO: calculate height
				Vector3 noHeightWorldPos(thisWorldPos.getX(), thisWorldPos.getY(), 0);
				Vector3 noHeightNextWorldPos(nextWorldPos.getX(), nextWorldPos.getY(), 0);
				dist = noHeightNextWorldPos.squaredDistanceTo(noHeightWorldPos);

				nextPosition = *coord;
				found = true;

				if (dist <= maxDistance * maxDistance && cellObject == parent) {
					if (i == path->size() - 1) {
						patrolPoints.remove(0);
						remove = false;
					}

					found = false;
				} else {
					//lets convert source and target coordinates to model or world space

					Vector3 oldCoordinates = oldCoord->getPoint();

					if (coord->getCell() != NULL) { //target coord in cell
						if (oldCoord->getCell() == NULL)  // convert old coord to model space
							oldCoordinates = PathFinderManager::transformToModelSpace(oldCoord->getPoint(), coord->getCell()->getParent());

					} else { // target coord in world
						oldCoordinates = oldCoord->getWorldPosition();
					}

					if (pathDistance > newSpeedSquared) {
						Vector3 oldWorldCoord = oldCoord->getWorldPosition();

						dist = oldWorldCoord.squaredDistanceTo(nextWorldPos);

						float distanceToTravel = dist - (pathDistance - newSpeedSquared);

						if (distanceToTravel <= 0) {
							newPositionX = nextPosition.getX();
							newPositionY = nextPosition.getY();
						} else {
							float rest = Math::sqrt(distanceToTravel);

							dist = Math::sqrt(dist);

							if (dist != 0 && !isnan(dist)) {
								dx = nextPosition.getX() - oldCoordinates.getX();
								dy = nextPosition.getY() - oldCoordinates.getY();

								newPositionX = oldCoordinates.getX() + (rest * (dx / dist));// (newSpeed * (dx / dist));
								newPositionY = oldCoordinates.getY() + (rest * (dy / dist)); //(newSpeed * (dy / dist));
							} else {
								newPositionX = nextPosition.getX();
								newPositionY = nextPosition.getY();
							}
						}

						if (nextPosition.getCell() == NULL) {
							newPositionZ = getZone()->getHeight(newPositionX, newPositionY);
							//newPositionZ = nextPosition.getZ();
						} else {
							newPositionZ = nextPosition.getZ();
						}
					} else {
						//info("setting nextPosition point", true);
						newPositionX = nextPosition.getX();
						newPositionY = nextPosition.getY();
						newPositionZ = nextPosition.getZ();
					}
				}
			}

			oldCoord = coord;
		}

		if (!found && remove) {
			patrolPoints.remove(0);
		}

		delete path;
	}

	nextPosition.setX(newPositionX);
	nextPosition.setY(newPositionY);
	nextPosition.setZ(newPositionZ);
	nextPosition.setCell(cellObject);

	return found;
}

void AiAgentImplementation::doMovement() {
	//info("doMovement", true);
	if (isDead() || getZone() == NULL)
		return;

	if (currentSpeed != 0) {
		updateCurrentPosition(&nextStepPosition);
		nextStepPosition.setReached(true);
	}

	float maxDistance = 5;

	if (followObject != NULL) {
		// drop everything and go after the target, this will also chase the target without having to reach them to change direction
		//patrolPoints.removeAll();

		switch (followState) {
		case AiAgent::OBLIVIOUS:
			break;
		case AiAgent::WATCHING:
			setNextPosition(getPositionX(), getPositionZ(), getPositionY(), getParent());
			setDirection(atan2(followObject->getPositionX() - getPositionX(), followObject->getPositionX() - getPositionX()));
			checkNewAngle();
			updateCurrentPosition(&patrolPoints.get(0));
			break;
		case AiAgent::STALKING:
			setNextPosition(followObject->getPositionX(), followObject->getPositionZ(), followObject->getPositionY(), followObject->getParent());
			maxDistance = 25;
			break;
		case AiAgent::FOLLOWING: {

			//we might get out of the floor
			/*float newX = followObject->getPositionX() + (-3 + System::random(6));
			float newY = followObject->getPositionY() + (-3 + System::random(6));*/
<commit_msg>[fixed] QT updating error<commit_after>/*
 * AiAgentImplementation.cpp
 *
 *  Created on: 13/07/2010
 *      Author: victor
 */

#include "AiAgent.h"

#include "events/AiThinkEvent.h"
#include "events/AiMoveEvent.h"
#include "events/AiWaitEvent.h"
#include "events/AiAwarenessEvent.h"
#include "events/RespawnCreatureTask.h"
#include "events/DespawnCreatureOnPlayerDissappear.h"
#include "server/zone/managers/combat/CombatManager.h"
#include "server/zone/managers/creature/CreatureManager.h"
#include "server/zone/managers/templates/TemplateManager.h"
#include "server/zone/objects/player/PlayerCreature.h"
#include "server/zone/objects/tangible/weapon/WeaponObject.h"
#include "server/zone/managers/templates/TemplateManager.h"
#include "server/zone/managers/creature/CreatureTemplate.h"
#include "server/zone/managers/creature/CreatureTemplateManager.h"
#include "server/zone/managers/combat/CombatManager.h"
#include "server/zone/managers/collision/PathFinderManager.h"
#include "server/zone/packets/scene/UpdateTransformMessage.h"
#include "server/zone/packets/scene/LightUpdateTransformMessage.h"
#include "server/zone/packets/scene/LightUpdateTransformWithParentMessage.h"
#include "server/zone/packets/scene/UpdateTransformWithParentMessage.h"
#include "server/zone/packets/object/StringList.h"
#include "server/zone/packets/object/NpcConversationMessage.h"
#include "server/zone/packets/object/StartNpcConversation.h"
#include "server/zone/packets/object/StopNpcConversation.h"
#include "server/zone/objects/creature/events/DespawnCreatureTask.h"
#include "server/zone/Zone.h"
#include "server/zone/ZoneServer.h"
#include "PatrolPoint.h"
#include "AiObserver.h"
#include "CreatureSetDefenderTask.h"
#include "server/zone/templates/appearance/PortalLayout.h"
#include "server/zone/templates/appearance/FloorMesh.h"


void AiAgentImplementation::loadTemplateData(SharedObjectTemplate* templateData) {
	CreatureObjectImplementation::loadTemplateData(templateData);
	objectTemplate = templateData;
}

void AiAgentImplementation::loadTemplateData(CreatureTemplate* templateData) {
	npcTemplate = templateData;

	setPvpStatusBitmask(npcTemplate->getPvpBitmask());

	optionsBitmask = npcTemplate->getOptionsBitmask();
	//npcTemplate->getCreatureBitmask(); -- TODO: need to add a bitmask for AI (pack, herd, etc)
	level = npcTemplate->getLevel();

	if (weapons.size() == 0) {
		Vector<String> wepgroups = npcTemplate->getWeapons();
		for (int i = 0; i < wepgroups.size(); ++i) {
			Vector<String> weptemps = CreatureTemplateManager::instance()->getWeapons(wepgroups.get(i));

			if (weptemps.size() > 0) {
				uint32 crc = weptemps.get(System::random(weptemps.size() - 1)).hashCode();
				ManagedReference<WeaponObject*> weao = dynamic_cast<WeaponObject*>(server->getZoneServer()->createObject(crc, 0));
				weao->setMinDamage((weao->getMinDamage() / 2) + npcTemplate->getDamageMin());
				weao->setMaxDamage((weao->getMaxDamage() / 2) + npcTemplate->getDamageMax());
				weapons.add(weao);
			}
		}

		// add the default weapon
		weapons.add(getWeapon());
	}

	// set the damage of the default weapon
	getWeapon()->setMinDamage(npcTemplate->getDamageMin());
	getWeapon()->setMaxDamage(npcTemplate->getDamageMax());

	int ham;
	baseHAM.removeAll();
	for (int i = 0; i < 9; ++i) {
		if (i % 3 == 0) {
			ham = System::random(npcTemplate->getBaseHAMmax() - npcTemplate->getBaseHAM()) + npcTemplate->getBaseHAM();
			baseHAM.add(ham);
		} else
			baseHAM.add(ham/100);
	}

	hamList.removeAll();
	for (int i = 0; i < 9; ++i) {
		hamList.add(baseHAM.get(i));
	}

	maxHamList.removeAll();
	for (int i = 0; i < 9; ++i) {
		maxHamList.add(baseHAM.get(i));
	}

	objectName = npcTemplate->getObjectName();
}

void AiAgentImplementation::initializeTransientMembers() {
	CreatureObjectImplementation::initializeTransientMembers();

	npcTemplate = NULL;

	SharedObjectTemplate* templ = TemplateManager::instance()->getTemplate(serverObjectCRC);
	objectTemplate = templ;
}

void AiAgentImplementation::doRecovery() {
	if (isDead() || getZone() == NULL)
		return;

	activateHAMRegeneration();
	activateStateRecovery();
	activatePostureRecovery();

	if (damageOverTimeList.hasDot() && damageOverTimeList.isNextTickPast()) {
		damageOverTimeList.activateDots(_this);
	}

	CreatureObject* target = damageMap.getHighestThreatCreature();

	if (target != NULL && !defenderList.contains(target) && !target->isDead())
		addDefender(target);

	if (target == NULL && defenderList.size() > 0) {
		for (int i = 0; i < defenderList.size(); ++i) {
			SceneObject* tarObj = defenderList.get(i);

			if (tarObj->isCreatureObject()) {
				CreatureObject* targetCreature = (CreatureObject*)tarObj;

				if (!targetCreature->isDead()) {
					target = targetCreature;

					break;
				}
			}
		}
	}

	if (!isInCombat() || defenderList.size() <= 0 || target == NULL) {
		tryRetreat();
		activateRecovery();
		return;
	}

	if (target != NULL && (!target->isInRange(_this, 128) || !target->isAttackableBy(_this))) {
		//Locker clocker(target, _this);
		CombatManager::instance()->forcePeace(_this); //calling this on target will cause a deadlock, needs to be called in a new task
		activateRecovery();
		return;
	}

	if (commandQueue.size() > 5) {
		activateRecovery();
		return;
	}

	if (target != followObject)
		setDefender(target);

	selectWeapon();

	if (System::random(2) == 0 && npcTemplate != NULL && commandQueue.size() < 3) {
		// do special attack
		CreatureAttackMap* attackMap = npcTemplate->getAttacks();
		int attackNum = attackMap->getRandomAttackNumber();

		if (attackNum < 0 || attackNum >= attackMap->size()) {
			enqueueCommand(String("defaultattack").hashCode(), 0, target->getObjectID(), "");
		} else {
			String args = attackMap->getArguments(attackNum);

			if (!validateStateAttack(target, args)) {
				// do default attack
				enqueueCommand(String("defaultattack").hashCode(), 0, target->getObjectID(), "");
			} else {
				// queue special attack
				unsigned int actionCRC = attackMap->getCommand(attackNum).hashCode();
				enqueueCommand(actionCRC, 0, target->getObjectID(), args);

				if (System::random(4) == 0) {
					// queue second special attack (rudimentary combo)
					int secondAttackNum = attackMap->getRandomAttackNumber();
					args = attackMap->getArguments(secondAttackNum);

					if (validateStateAttack(target, args) && secondAttackNum != attackNum) {
						actionCRC = attackMap->getCommand(attackNum).hashCode();
						enqueueCommand(actionCRC, 0, target->getObjectID(), args);
					}
				}
			}
		}
	} else
		enqueueCommand(String("defaultattack").hashCode(), 0, target->getObjectID(), "");

	activateRecovery();
}

int AiAgentImplementation::notifyAttack(Observable* observable) {
	// TODO: add reaction attacks
	return 0;
}

int AiAgentImplementation::notifyCallForHelp(Observable* observable, ManagedObject* arg1) {
	// TODO: add aggroing
	return 0;
}

void AiAgentImplementation::selectWeapon() {
	float dist = 5.f;

	if (followObject != NULL)
		dist = getDistanceTo(followObject.get());

	float diff = 1024.f;
	WeaponObject* finalWeap = getWeapon();

	for (int i = 0; i < weapons.size(); ++i) {
		WeaponObject* weap = weapons.get(i);
		float range = abs(weap->getIdealRange() - dist);
		if (range < diff) {
			diff = range;
			finalWeap = weap;
		}
	}

	setWeapon(finalWeap, true);
}

bool AiAgentImplementation::validateStateAttack(CreatureObject* target, String& args) {
	StringTokenizer tokenizer(args);
	tokenizer.setDelimeter(";");

	while (tokenizer.hasMoreTokens()) {
		String singleArg;
		tokenizer.getStringToken(singleArg);

		if (singleArg.indexOf("Chance") != -1) {
			String stateName = singleArg.subString(0, args.indexOf("Chance"));
			uint64 state = CreatureState::getState(stateName);
			if (target->hasState(state) || (stateName == "postureDown" && target->isProne()) || (stateName == "knockdown" && target->isKnockedDown()) || (stateName == "postureUp" && target->isStanding())) {
				return false;

			}
		}
	}

	return true;
}

void AiAgentImplementation::setDespawnOnNoPlayerInRange(bool val) {
	if (despawnOnNoPlayerInRange == val)
		return;

	despawnOnNoPlayerInRange = val;

	if (val && numberOfPlayersInRange <= 0) {
		if (despawnEvent == NULL) {
			despawnEvent = new DespawnCreatureOnPlayerDissappear(_this);
		}

		if (!despawnEvent->isScheduled())
			despawnEvent->schedule(30000);
	}
}

bool AiAgentImplementation::tryRetreat() {
	try {
		if (homeLocation.getPositionX() == 0 && homeLocation.getPositionY() == 0 && homeLocation.getPositionZ() == 0)
			return false;

		if (lastSuccessfulCombatAction.miliDifference() <= 30000) {
			return false;
		} else if (homeLocation.isInRange(_this, 100))
			return false;

		setOblivious();

		showFlyText("npc_reaction/flytext", "afraid", 0xFF, 0, 0);

		homeLocation.setReached(false);

		damageMap.removeAll();

		patrolPoints.removeAll();
		patrolPoints.add(homeLocation);

		CombatManager::instance()->forcePeace(_this);

		activateMovementEvent();
	} catch (Exception& e) {
		error(e.getMessage());
		e.printStackTrace();
	}

	return true;
}

void AiAgentImplementation::setDefender(SceneObject* defender) {
	CreatureObjectImplementation::setDefender(defender);

	Reference<CreatureSetDefenderTask*> task;
	ManagedReference<AiObserver*> observer = NULL;

	if (aiObserverMap.size() == 0) {
		observer = new AiObserver(_this);
		ObjectManager::instance()->persistObject(observer, 1, "aiobservers");
		aiObserverMap.put(observer);
	} else {
		observer = aiObserverMap.get(0);

		task = new CreatureSetDefenderTask(_this, defender, observer, true);

		/*for (int i = 0; i < defenderList.size(); ++i)
			defenderList.get(i)->dropObserver(ObserverEventType::SPECIALATTACK, observer);*/
	}

	/*if (observer != NULL)
			defender->registerObserver(ObserverEventType::SPECIALATTACK, observer);*/


	if (task == NULL)
		task = new CreatureSetDefenderTask(_this, defender, observer, false);

	Core::getTaskManager()->executeTask(task);

	setFollowObject(defender);

	activateRecovery();
}

void AiAgentImplementation::addDefender(SceneObject* defender) {
	if (defenderList.size() == 0)
		showFlyText("npc_reaction/flytext", "threaten", 0xFF, 0, 0);

	CreatureObjectImplementation::addDefender(defender);

	activateRecovery();
}

void AiAgentImplementation::removeDefender(SceneObject* defender) {
	CreatureObjectImplementation::removeDefender(defender);

	if (defender != NULL) {
		if (defender->isCreatureObject())
			damageMap.dropDamage((CreatureObject*)defender);

		if (aiObserverMap.size() > 0)
			defender->dropObserver(ObserverEventType::SPECIALATTACK, aiObserverMap.get(0));
	}

	if (followObject == defender) {
		CreatureObject* target = damageMap.getHighestThreatCreature();

		if (target == NULL && defenderList.size() > 0) {
			SceneObject* tarObj = defenderList.get(0);
			if (tarObj->isCreatureObject())
				target = (CreatureObject*)tarObj;
		}

		if (target == NULL) {
			setOblivious();
		} else  {
			setDefender(target);
		}
	}

	activateRecovery();
}

/**
 * Cleares the combat state
 * @pre { this object is locked }
 * @post { this object is locked, this object is not in a combat state }
 * @param clearDefenders if true the defender vector willl be emptied
 */
void AiAgentImplementation::clearCombatState(bool clearDefenders) {
	CreatureObjectImplementation::clearCombatState(clearDefenders);

	damageMap.removeAll();

	setOblivious();
}

void AiAgentImplementation::notifyInsert(QuadTreeEntry* entry) {
	SceneObject* scno = (SceneObject*) entry;

	if (scno == _this)
		return;

	if (scno->isPlayerCreature())
		++numberOfPlayersInRange;
}

void AiAgentImplementation::clearDespawnEvent() {
	despawnEvent = NULL;
}

void AiAgentImplementation::respawn(Zone* zone, int level) {
	if (getZone() != NULL)
		return;

	setLevel(level);

	initializePosition(homeLocation.getPositionX(), homeLocation.getPositionZ(), homeLocation.getPositionY());

	SceneObject* cell = homeLocation.getCell();

	Locker zoneLocker(zone);

	if (cell != NULL)
		cell->addObject(_this, -1);

	insertToZone(zone);
}

void AiAgentImplementation::notifyDespawn(Zone* zone) {
	if (objectTemplate == NULL || npcTemplate == NULL)
		return;

	loadTemplateData(objectTemplate);
	loadTemplateData(npcTemplate);
	stateBitmask = 0;
	posture = CreaturePosture::UPRIGHT;
	shockWounds = 0;
	damageMap.removeAll();

	if (respawnTimer == 0) {
		zone->getCreatureManager()->addToReservePool(_this);
		return;
	}

	Reference<Task*> task = new RespawnCreatureTask(_this, zone, level);
	task->schedule(respawnTimer * 1000);
}

void AiAgentImplementation::scheduleDespawn() {
	if (getPendingTask("despawn") != NULL)
		return;

	Reference<DespawnCreatureTask*> despawn = new DespawnCreatureTask(_this);
	//despawn->schedule(300000); /// 5 minutes
	despawn->schedule(10000); /// 45 seconds
	addPendingTask("despawn", despawn);
}

void AiAgentImplementation::notifyDissapear(QuadTreeEntry* entry) {
	SceneObject* scno = (SceneObject*) entry;

	if (scno == _this)
		return;

	if (scno->isPlayerCreature()) {
		if ((--numberOfPlayersInRange <= 0)  && (despawnEvent == NULL)) {
			despawnEvent = new DespawnCreatureOnPlayerDissappear(_this);
			despawnEvent->schedule(30000);
		}
	}
}

void AiAgentImplementation::activateAwarenessEvent(CreatureObject *target) {
	if (awarenessEvent == NULL) {
		awarenessEvent = new AiAwarenessEvent(_this, target);

		awarenessEvent->schedule(1000);
	}

	if (!awarenessEvent->isScheduled()) {
		awarenessEvent->setTarget(target);
		awarenessEvent->schedule(1000);
	}
}

void AiAgentImplementation::activateRecovery() {
	if (thinkEvent == NULL) {
		thinkEvent = new AiThinkEvent(_this);

		thinkEvent->schedule(2000);
	}

	if (!thinkEvent->isScheduled())
		thinkEvent->schedule(2000);
}

void AiAgentImplementation::activatePostureRecovery() {
	if (isProne() || isKnockedDown() || isKneeling())
		executeObjectControllerAction(0xA8A25C79); // stand
}

void AiAgentImplementation::updateCurrentPosition(PatrolPoint* pos) {
	PatrolPoint* nextPosition = pos;

	setPosition(nextPosition->getPositionX(), nextPosition->getPositionZ(),
			nextPosition->getPositionY());

	SceneObject* cell = nextPosition->getCell();

	/*StringBuffer reachedPosition;
	reachedPosition << "(" << positionX << ", " << positionY << ")";
	info("reached " + reachedPosition.toString(), true);*/

	if (cell != NULL)
		updateZoneWithParent(cell, false, false);
	else
		updateZone(false, false);
}


void AiAgentImplementation::checkNewAngle() {
	if (followObject == NULL)
		return;

	faceObject(followObject);

	if (!nextStepPosition.isReached()) {
		broadcastNextPositionUpdate(&nextStepPosition);
	} else {
		++movementCounter;

		if (parent != NULL && parent->isCellObject())
			updateZoneWithParent(parent, true, true);
		else
			updateZone(true, true);
	}
}

bool AiAgentImplementation::findNextPosition(float maxDistance, WorldCoordinates& nextPosition) {
	Vector3 thisWorldPos = getWorldPosition();

	float newSpeed = runSpeed;
	if (followObject == NULL && !isFleeing() && !isRetreating()) // TODO: think about implementing a more generic "walk, don't run" criterion
		newSpeed = walkSpeed;

	float updateTicks = float(UPDATEMOVEMENTINTERVAL) / 1000.f;
	currentSpeed = newSpeed;

	newSpeed *= updateTicks;

	float newSpeedSquared = newSpeed * newSpeed;
	float newPositionX, newPositionZ, newPositionY;
	PathFinderManager* pathFinder = PathFinderManager::instance();

	bool found = false;
	float dist = 0;
	float dx, dy;
	ManagedReference<SceneObject*> cellObject;


	while (!found && patrolPoints.size() != 0) {
		PatrolPoint* targetPosition = &patrolPoints.get(0);

		Vector<WorldCoordinates>* path = pathFinder->findPath(_this, targetPosition->getCoordinates());

		if (path == NULL) {
			patrolPoints.remove(0);

			continue;
		}

		WorldCoordinates* oldCoord = NULL;
		float pathDistance = 0;

		bool remove = true;

		for (int i = 1; i < path->size() && !found; ++i) { // i = 0 is our position
			WorldCoordinates* coord = &path->get(i);

			Vector3 nextWorldPos = coord->getWorldPosition();

			if (oldCoord == NULL) {
				pathDistance += nextWorldPos.squaredDistanceTo(thisWorldPos);
				oldCoord = &path->get(0);
			} else {
				pathDistance += oldCoord->getWorldPosition().squaredDistanceTo(nextWorldPos);
			}

			if (i == path->size() - 1 || pathDistance >= newSpeedSquared || coord->getCell() != parent) { //last waypoint
				cellObject = coord->getCell();

				//TODO: calculate height
				Vector3 noHeightWorldPos(thisWorldPos.getX(), thisWorldPos.getY(), 0);
				Vector3 noHeightNextWorldPos(nextWorldPos.getX(), nextWorldPos.getY(), 0);
				dist = noHeightNextWorldPos.squaredDistanceTo(noHeightWorldPos);

				nextPosition = *coord;
				found = true;

				if (dist <= maxDistance * maxDistance && cellObject == parent) {
					if (i == path->size() - 1) {
						patrolPoints.remove(0);
						remove = false;
					}

					found = false;
				} else {
					//lets convert source and target coordinates to model or world space

					Vector3 oldCoordinates = oldCoord->getPoint();

					if (coord->getCell() != NULL) { //target coord in cell
						if (oldCoord->getCell() == NULL)  // convert old coord to model space
							oldCoordinates = PathFinderManager::transformToModelSpace(oldCoord->getPoint(), coord->getCell()->getParent());

					} else { // target coord in world
						oldCoordinates = oldCoord->getWorldPosition();
					}

					if (pathDistance > newSpeedSquared) {
						Vector3 oldWorldCoord = oldCoord->getWorldPosition();

						dist = oldWorldCoord.squaredDistanceTo(nextWorldPos);

						float distanceToTravel = dist - (pathDistance - newSpeedSquared);

						if (distanceToTravel <= 0) {
							newPositionX = nextPosition.getX();
							newPositionY = nextPosition.getY();
						} else {
							float rest = Math::sqrt(distanceToTravel);

							dist = Math::sqrt(dist);

							if (dist != 0 && !isnan(dist)) {
								dx = nextPosition.getX() - oldCoordinates.getX();
								dy = nextPosition.getY() - oldCoordinates.getY();

								newPositionX = oldCoordinates.getX() + (rest * (dx / dist));// (newSpeed * (dx / dist));
								newPositionY = oldCoordinates.getY() + (rest * (dy / dist)); //(newSpeed * (dy / dist));
							} else {
								newPositionX = nextPosition.getX();
								newPositionY = nextPosition.getY();
							}
						}

						if (nextPosition.getCell() == NULL) {
							newPositionZ = getZone()->getHeight(newPositionX, newPositionY);
							//newPositionZ = nextPosition.getZ();
						} else {
							newPositionZ = nextPosition.getZ();
						}
					} else {
						//info("setting nextPosition point", true);
						newPositionX = nextPosition.getX();
						newPositionY = nextPosition.getY();
						newPositionZ = nextPosition.getZ();
					}
				}
			}

			oldCoord = coord;
		}

		if (!found && remove) {
			patrolPoints.remove(0);
		}

		delete path;
	}

	nextPosition.setX(newPositionX);
	nextPosition.setY(newPositionY);
	nextPosition.setZ(newPositionZ);
	nextPosition.setCell(cellObject);

	return found;
}

void AiAgentImplementation::doMovement() {
	//info("doMovement", true);
	if (isDead() || getZone() == NULL || !isInQuadTree()) {
		setFollowObject(NULL);
		return;
	}


	if (currentSpeed != 0) {
		updateCurrentPosition(&nextStepPosition);
		nextStepPosition.setReached(true);
	}

	float maxDistance = 5;

	if (followObject != NULL) {
		// drop everything and go after the target, this will also chase the target without having to reach them to change direction
		//patrolPoints.removeAll();

		switch (followState) {
		case AiAgent::OBLIVIOUS:
			break;
		case AiAgent::WATCHING:
			setNextPosition(getPositionX(), getPositionZ(), getPositionY(), getParent());
			setDirection(atan2(followObject->getPositionX() - getPositionX(), followObject->getPositionX() - getPositionX()));
			checkNewAngle();
			updateCurrentPosition(&patrolPoints.get(0));
			break;
		case AiAgent::STALKING:
			setNextPosition(followObject->getPositionX(), followObject->getPositionZ(), followObject->getPositionY(), followObject->getParent());
			maxDistance = 25;
			break;
		case AiAgent::FOLLOWING: {

			//we might get out of the floor
			/*float newX = followObject->getPositionX() + (-3 + System::random(6));
			float newY = followObject->getPositionY() + (-3 + System::random(6));*/

{'fraction_non_alphanumeric': 0.14368556701030927, 'fraction_numerical': 0.00877279936558287, 'mean_word_length': 4.813715602939058, 'pattern_counts': {'":': 0, '<': 77, '<?xml version=': 0, '>': 251, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1919243', 'n_tokens_mistral': 14870, 'n_tokens_neox': 12985, 'n_words': 2519}