-- constants
GAME_KTA = 0
GAME_FFA = 1
GAME_PRD = 2
GAME_CTF = 3
GAME_TAG = 4
GAME_DOM = 5

WEAP_HAS_AMMO = 1000
WEAP_CAN_KILL = 200
WEAP_CURRENT = 100
WEAP_SAFE_DIST = 200

FL_APPLY_GRAV = 0
FL_JUMP_RELEASED = 1
FL_LINE_OUT = 2
FL_LINE_HIT = 3
FL_LINE_PULL = 4
FL_CAN_JUMP = 5
FL_GOT_ENEMY = 6
FL_ANIM_FORWARD = 7
FL_CAN_CHANGE = 8
FL_JUMP_OK = 9
FL_ROPE_OK = 10
FL_SHOW_CAM = 11
FL_PREDATOR = 12
FL_CROSSHAIR = 13

AI_FLAG_JUMP = 1
AI_FLAG_ROPE = 2

AI_WYPT_SAFE = 1

gamekeys = { jump = 1; fire = 2; change = 4; left = 8; right = 16; up = 32; down = 64; };


-- behaviour
aiReflex = 0.2;
aiTargetLead = 1.0;
aiPredictGravity = 1.0;

aiChaseTime = 550;
aiWaypointTime = 45;

-- custom variables
enemy = nil; -- current enemy
enemyVelX, enemyVelY = 0, 0; -- enemy velocity
enemyChase = 0;

targetPoint = -1; -- for waypoint navigation
targetPointChase = 0;
routeFlags = 0;

direction = 1; -- for random wandering


-- configuration data
angleToRope = 29;


function onDamage(other, amount)

	if (other ~= self) and
	   (other ~= enemy) and
	   ((teamPlay == 0) or (getWormTeam(other) ~= getWormTeam(self))) then
		enemy = other;
		enemyVelX, enemyVelY = 0, 0;
	end

end

function findEnemy()
	
	local i;

	local sx,sy = getWormPos(self);
	local st = getWormTeam(self);

	local bestEnemy = nil;
	local bestDist;

	for i = 0, numWorms - 1, 1 do
		local ex, ey = getWormPos(i);
		local eh = getWormHealth(i);
		local et = getWormTeam(i);
		local dx = ex - sx;
		local dy = ey - sy;

		local ed = math.sqrt(dx*dx + dy*dy);

		if (i ~= self) and
		   (eh > 0) and
		   ((teamPlay == 0) or (et ~= st))
		then

			if (bestEnemy == nil) or (ed < bestDist) then
				bestEnemy = i;
				bestDist = ed;
			end

		end
	end

	return bestEnemy;

end

function getBestWeap(reload)

	local best = curWeap;
	local bestScore = -999999;

	local enemyHealth = 0;

	local dist;
	local hackedDist;

	if (enemy ~= nil) then

		local sx, sy = getWormPos(self);
		local ex, ey = getWormPos(enemy);
		local dx = ex - sx;
		local dy = ey - sy;
		dist = math.sqrt(dx*dx + dy*dy);
		hackedDist = math.sqrt(dx*dx/9 + dy*dy);

		enemyHealth = getWormHealth(enemy);

		if (dist == 0) then

			dist = 1;

		end

	else

		dist = 1;
		hackedDist = 1;

	end

	for i = 1, 5, 1 do

		local weapon = weapons[i];

		local safeDist = 0;

		local timeToHit;

		if (weaponData[weapon].initSpeed == 0) then
			timeToHit = 99999;
		else
			timeToHit = dist / weaponData[weapon].initSpeed;
		end

		local goodDist;
		if (weaponData[weapon].avgExplosionTime > 0) then
			goodDist = (
			            ((timeToHit / weaponData[weapon].avgExplosionTime) > 0.8) or
			            (
			             weaponData[weapon].explodeOnWorm and
			             (dist > weaponData[weapon].explosionDist)
			            )
				   ) and
			           ((timeToHit / weaponData[weapon].avgExplosionTime) < 1.1);
		else
			goodDist = (dist > weaponData[weapon].explosionDist);
		end		

		local score = 0;

		if (hackedDist < weaponData[weapon].maxRange) then

			if (i == curWeap) then

				score = score + WEAP_CURRENT;

			end

			if not reload then

				if (goodDist) then

					score = score + WEAP_SAFE_DIST;

				end

				if (ammo[i] > 0) then

					score = score + WEAP_HAS_AMMO;

				end

				if (weaponData[weapon].avgDamage >= enemyHealth) then

					score = score + WEAP_CAN_KILL;

				end

				if (fireDelay[i] > 0) then

					score = score - fireDelay[i];

				end

				score = score - dist / weaponData[weapon].initSpeed;

				score = score - weaponData[weapon].fireDelay;

			else

				if (weaponData[weapon].loadTime == -1) then

					score = -999999;

				else

					if (ammo[i] == 0) then

						score = score + WEAP_HAS_AMMO;

					end

					if (loadDelay[i] > 0) then

						score = score - loadDelay[i];

					end
					
				end

			end

			if (score > bestScore) then

				bestScore = score;
				best = i;

			end

		end

	end

	return best;
end


function getPointToAim()

	local sx, sy = getWormPos(self);
	local svx, svy = getWormVel(self);
	local ex, ey = getWormPos(enemy);
	local evx, evy = getWormVel(enemy);

	enemyVelX = (1 - aiReflex) * enemyVelX + aiReflex * evx;
	enemyVelY = (1 - aiReflex) * enemyVelY + aiReflex * evy;

	evx = enemyVelX;
	evy = enemyVelY;

	local curAim = getWormAim(self);

	local dx = ex - sx;
	local dy = ey - sy;

	local weapon = weapons[curWeap];

	local tx, ty = aimToVec(curAim);

	local vx = weaponData[weapon].initSpeed * tx;
	local vy = weaponData[weapon].initSpeed * ty;

	local affected = weaponData[weapon].affectedByWormMotion;

	if (affected ~= 0) then

		vx = vx + svx * affected;
		vy = vy + svy * affected;

	end

	local dist = math.sqrt(vx*vx + vy*vy);

	if (dist == 0) then
		dist = 1;
	end

	dist = math.sqrt(dx*dx + dy*dy) / dist;

	local gravity = weaponData[weapon].gravity * dist * dist * aiTargetLead * aiPredictGravity / 2;

	dx, dy = trace(ex, ey,  ex + evx * dist, ey + evy * dist * aiTargetLead - gravity, 2);

	return dx, dy;
end




function aiGetMove()

	local keys = { jump = 0; fire = 0; change = 0; left = 0; right = 0; up = 0; down = 0; };

	if (enemy == nil) then

		local temp = math.random();
		local fire = 0;

		enemyChase = 0;

		if (temp > 0.98) then
			direction = 1 - direction;
		end

		if (direction == 1) then
			keys.left = 1;
			if (getWormDir(self) == 1) then
				keys.right = 1;
			end
		else
			keys.right = 1;
			if (getWormDir(self) == 0) then
				keys.left = 1;
			end
		end

		enemy = findEnemy();
		enemyChase = time + aiChaseTime;

	else

		local sx, sy = getWormPos(self);
		local svx, svy = getWormVel(self);
		local ex, ey = getWormPos(enemy);
		local evx, evy = getWormVel(enemy);

		local curAim = getWormAim(self);
		local curDir = getWormDir(self);

		local dx, dy = getPointToAim();

		local cx, cy = trace(sx, sy, dx, dy, 1);

		local angle = aim(sx, sy, dx, dy);

		local enemyHealth = getWormHealth(enemy);

		local enemyVisible = false;

		local hasAmmo = 0;

		local i;

		for i = 1, 5, 1 do

			if (ammo[i] > 0) then

				hasAmmo = hasAmmo + 1;

			end

		end

		if (cx == dx) and (cy == dy) and
		   (enemyHealth ~= nil) and (enemyHealth > 0) and
		   (hasAmmo > 2) then
			enemyVisible = true;
		end

		local enemyDist = math.sqrt((dx - sx)*(dx - sx) + (dy - sy)*(dy - sy));

		local hackedDist = math.sqrt((dx - sx)*(dx - sx)/9 + (dy - sy)*(dy - sy));

		local bestWeap = getBestWeap(not enemyVisible);

		local chLeft = curWeap - bestWeap;

		if (chLeft < 0) then

			chLeft = chLeft + 5;

		end

		local chRight = bestWeap - curWeap;

		if (chRight < 0) then

			chRight = chRight + 5;

		end

		if (enemyHealth == nil) then
			-- no such player
			enemy = nil;
		end

		if ((bestWeap == curWeap) or (getWormFlag(self, FL_CAN_CHANGE) == 0)) then

			local ropeOut = getWormFlag(self, FL_LINE_OUT);

			if (enemyVisible) then

				enemyChase = time + aiChaseTime;

--				print("Visible!");

				targetPoint = -1;

				if (curAim < angle) then
					keys.up = 1;
				elseif (curAim > angle) then
					keys.down = 1;
				end

				if ((dx + 10 < sx) or ((dx < sx) and (curDir ~= 1))) then
					keys.left = 1;
					if (curDir == 1) then
						keys.right = 1;
					end
				elseif ((dx - 10 > sx) or ((dx > sx) and (curDir ~= 0))) then
					keys.right = 1;
					if (curDir == 0) then
						keys.left = 1;
					end
				end


				if (angle >= angleToRope) and (curAim >= angleToRope) and
				   (ropeOut == 0) then
				   	keys.jump = 1;
					keys.change = 1;
				end

				if (ropeOut == 1) and
				   (keys.change == 0) and
				   (sy < ey - 5) then
					keys.jump = 1;
				end

				if (weaponData[weapons[curWeap]].initSpeed > 0) then
					timeToHit = enemyDist / weaponData[weapons[curWeap]].initSpeed;
				else
					timeToHit = 9999;
				end

				if (angle == curAim) and
				   (hackedDist < weaponData[weapons[curWeap]].maxRange) and
				   (
				      (weaponData[weapons[curWeap]].avgExplosionTime == 0) or
				      (
				         (timeToHit / weaponData[weapons[curWeap]].avgExplosionTime < 1.1) and
				         (
				          (timeToHit / weaponData[weapons[curWeap]].avgExplosionTime > 0.3) or
				          (weaponData[weapons[curWeap]].explodeOnWorm)
				         )
				      )
				   ) then
					keys.fire = 1;
				end
				if (keys.change == 0) and
				   (sy > ey + 5) and
				   (getWormFlag(self, FL_LINE_OUT) == 0) and
				   (getWormFlag(self, FL_CAN_JUMP) == 1) then
					keys.jump = 1;
				end

			elseif (targetPoint ~= -1) then

				local tx, ty = getWaypointPos(targetPoint);

				local srx, sry = getRopePos(self);

				local cx, cy = trace(sx, sy, tx, ty, 1);

				if (cx == tx) and (cy == ty) then
					targetPointChase = time + aiWaypointTime;
				end

				if (getFlag(routeFlags, AI_FLAG_ROPE) == 1) and (ty < sy) and
				   ((sry < ty) or (getWormFlag(self, FL_LINE_HIT) == 0) or (getWormFlag(self, FL_LINE_OUT) == 0)) then

					angle = 32;

					if (getWormFlag(self, FL_ROPE_OK)) then
--						print("Need rope here");
						if (curAim == angle) and (ropeOut == 0) and
						   (math.abs(tx - sx) < 10) and (math.abs(svx) < 2) then
							keys.jump = 1;
							keys.change = 1;
						elseif (ropeOut == 1) then
							keys.jump = 1;
						end
					end

				else

					if (sry > ty) and (getWormFlag(self, FL_LINE_HIT) == 1) and (getWormFlag(self, FL_LINE_OUT) == 1) then
--						print(time .. " Rope went bad. " .. sry .. " > " .. ty);
					end

					if ((ropeOut == 1) or (getFlag(routeFlags, AI_FLAG_JUMP) == 1)) and
					   (getWormFlag(self, FL_CAN_JUMP) == 1) then

--						print("Need jump here");
						keys.jump = 1;

					end;
				end

				if (curAim < angle) then
					keys.up = 1;
				elseif (curAim > angle) then
					keys.down = 1;
				end

				local cx, cy = tx - sx, ty - sy;

				local canFly = false;

				if (jumpNotOnGround) then

					canFly = true;

				end

				if canFly then

					local cxr;

					if (cx == 0) then
						cxr = 1;
					else
						cxr = math.abs(svx / cx);
					end

					local cvy = 0;

					if (cxr == 0) then
						cvy = svy;
					else
						cvy = cy * cxr;
					end

					if (svy > cvy) and (getWormFlag(self, FL_CAN_JUMP) == 1) then
--						print("Flying as " .. svy .. " > " .. cvy .. " moving by " .. cx .. ", " .. cy);
						keys.jump = 1;
					end

				end

				if (tx + 5 < sx) then
					keys.left = 1;
					if (curDir == 1) then
						keys.right = 1;
					end
				elseif (tx - 5 > sx) then
					keys.right = 1;
					if (curDir == 0) then
						keys.left = 1;
					end
				end
				
				local ax, ay = sx - tx, sy - ty;

				if (math.sqrt(ax*ax + ay*ay) <= 10) then

--					print(self .. " reached " .. targetPoint .. " at " .. tx .. ", " .. ty);

					targetPoint = -1;

				end;

				if (targetPointChase < time) then

--					print(self .. " got lost");

					targetPoint = -1; -- find another route as we got lost

				end

			else
				local sp = findWaypoint(sx, sy);
				local ep = findWaypoint(dx, dy);

				targetPoint, routeFlags = findPath(sp, ep);

				if (targetPoint == -1) then
					if ((dx + 10 < sx) or ((dx < sx) and (curDir ~= 1))) then
						keys.left = 1;
						if (curDir == 1) then
							keys.right = 1;
						end
					elseif ((dx - 10 > sx) or ((dx > sx) and (curDir ~= 0))) then
						keys.right = 1;
						if (curDir == 0) then
							keys.left = 1;
						end
					end
					if (keys.change == 0) and
					   (sy > ey + 5) and
					   (getWormFlag(self, FL_LINE_OUT) == 0) and
					   (getWormFlag(self, FL_CAN_JUMP) == 1) then
						keys.jump = 1;
					end
--					print("No route to enemy from " .. sp .. " to " .. ep);
				else
					targetPointChase = time + aiWaypointTime;
--					print("Going to " .. targetPoint);
				end
			end
		else

--			print("Changing weaps");

			if (ropeOut == 1) then
				keys.jump = 1;
			else

				keys.change = 1;

				if (chLeft < chRight) then

					keys.left = 1;
					keys.right = 0;

				else

					keys.left = 0;
					keys.right = 1;

				end

			end

		end
		if (enemyHealth == nil) or (enemyHealth <= 0) or (enemyChase < time) then
			enemy = nil;
			targetPoint = -1;
--			print("Giving up");
		end

	end

	local value = 0;

	if (keys.jump ~= 0) then value = value + gamekeys.jump; end;
	if (keys.fire ~= 0) then value = value + gamekeys.fire; end;
	if (keys.change ~= 0) then value = value + gamekeys.change; end;
	if (keys.left ~= 0) then value = value + gamekeys.left; end;
	if (keys.right ~= 0) then value = value + gamekeys.right; end;
	if (keys.up ~= 0) then value = value + gamekeys.up; end;
	if (keys.down ~= 0) then value = value + gamekeys.down; end;

	return value;
end
