object	Sail, Rope, Flag, Vant;

// NK 05-03-21 moved max ships, two min_distance defines, all
// ammo/morale/mast/etc defines to BS.h

bool	bAbordageShipCanBe = false;
int		iAbordageShipEnemyCharacter;

bool	bNoDamage2Us = false;
bool	bCanEnterToLand = false;
aref	arIslandReload = &NullCharacter; // KK //MAXIMUS
string	sIslandLocator;
string	sIslandID;
int		iShipPriority = 31;

int		iNumShips = 0;
int		Ships[MAX_SHIPS_ON_SEA];
object	ShipStats[MAX_SHIPS_ON_SEA]; // NK for BI stats in PRS 05-04-15
object	NearShips[MAX_SHIPS_ON_SEA]; // NK for finding near ships; 05-04-22. 05-06-27 change to array.

#define SHIP_DETONATE_SMALL		"ShipDetonateSmall"

void CreateRiggingEnvironment()
{
	CreateEntity(&Sail, "Sail");
	LayerAddObject(sCurrentSeaExecute, &Sail, iShipPriority + 1);
	LayerAddObject(sCurrentSeaRealize, &Sail, iShipPriority + 1);
	LayerAddObject("sails_trace", &Sail, 10);
	LayerAddObject("sea_reflection", &Sail, 3);
	Sail.MinSpeed = 0.05;

	CreateEntity(&Rope, "Rope");
	LayerAddObject(sCurrentSeaExecute, &Rope, iShipPriority + 2);
	LayerAddObject(sCurrentSeaRealize, &Rope, iShipPriority + 2);

	CreateEntity(&Flag, "Flag");
	LayerAddObject(sCurrentSeaExecute, &Flag, iShipPriority + 3);
	LayerAddObject(sCurrentSeaRealize, &Flag, iShipPriority + 3);
	LayerAddObject("sea_reflection", &Flag, 3);

	CreateEntity(&Vant, "Vant");
	LayerAddObject(sCurrentSeaExecute, &Vant, iShipPriority + 4);
	LayerAddObject(sCurrentSeaRealize, &Vant, iShipPriority + 4);

	iNumShips = 0;
}

void DeleteRiggingEnvironment()
{
	DeleteClass(&Sail);
	DeleteClass(&Rope);
	DeleteClass(&Flag);
	DeleteClass(&Vant);
}

void DeleteShipEnvironment()
{
	DeleteRiggingEnvironment();

	DelEventHandler(SHIP_CHANGE_CHARGE, "Ship_ChangeChargeEvent");
	DelEventHandler(SHIP_HULL_HIT, "Ship_HullHitEvent");
	DelEventHandler(SHIP_CHECK_RELOAD_ENABLE, "Ship_CheckMainCharacter");
	DelEventHandler(SHIP_DETONATE_SMALL, "Ship_DetonateSmall");
	DelEventHandler(SHIP_BRANDER_DETONATE, "Ship_BranderDetonate");
	DelEventHandler(SHIP_FIRE_DAMAGE, "Ship_FireDamage");
	DelEventHandler(SHIP_TO_STRAND_NEXT, "Ship_OnStrandNext");
	DelEventHandler(SHIP_ACTIVATE_FIRE_PLACE, "Ship_ActivateFirePlace");
	DelEventHandler(SHIP_UPDATE_PARAMETERS, "Ship_UpdateParameters");
	DelEventHandler(WHR_LIGHTNING, "Ship_Lightning");
	DelEventHandler("eSwitchPerks", "Ship_UpdatePerks");
	DelEventHandler("TornadoDamage", "Ship_TornadoDamage");

	// scan characters for delete snd id's
}

void CreateShipEnvironment()
{
	bAbordageFortCanBe = false;
	bAbordageShipCanBe = false;
	bAbordageTownCanBe = false; // KK
	bCanEnterToLand = false;
	sIslandLocator = "";
	sIslandID = "";

	CreateRiggingEnvironment();

	SetEventHandler(SEA_GET_LAYERS, "SeaAI_GetLayers", 0);

	SetEventHandler(SHIP_NOT_ENOUGH_BALLS, "Ship_NotEnoughBalls", 0);
	SetEventHandler(SHIP_GET_CURRENT_BALLS_NUM, "Ship_GetCurrentBallsNum", 0);
	SetEventHandler(SHIP_SHIP2ISLAND_COLLISION, "Ship_Ship2IslandCollision", 0);
	SetEventHandler(SHIP_SHIP2SHIP_COLLISION, "Ship_Ship2ShipCollision", 0);
	SetEventHandler(SHIP_SAIL_DAMAGE, "Ship_SailDamage", 0);
	SetEventHandler(SHIP_FIRE_DAMAGE, "Ship_FireDamage", 0);
	SetEventHandler(SHIP_CHECK_SITUATION, "Ship_CheckSituation", 0);
	SetEventHandler(SHIP_TO_STRAND, "Ship_OnStrand", 0);
	SetEventHandler(SHIP_TO_STRAND_NEXT, "Ship_OnStrandNext", 0);
	SetEventHandler(SHIP_DEAD, "Ship_OnDead", 0);
	SetEventHandler(SHIP_UPDATE_PARAMETERS, "Ship_UpdateParameters", 0);
	SetEventHandler(SHIP_CREATE, "Ship_OnCreate", 0);
	SetEventHandler(SHIP_CHANGE_CHARGE, "Ship_ChangeChargeEvent", 0);
	SetEventHandler(SHIP_HULL_HIT, "Ship_HullHitEvent", 0);
	SetEventHandler(SHIP_CHECK_RELOAD_ENABLE, "Ship_CheckMainCharacter", 0);
	SetEventHandler(SHIP_FIRE_ACTION, "Ship_FireAction", 0);
	SetEventHandler(SHIP_GET_BORT_FIRE_DELTA, "Ship_GetBortFireDelta", 0);
	SetEventHandler(SHIP_DETONATE_SMALL, "Ship_DetonateSmall", 0);
	SetEventHandler(SHIP_ACTIVATE_FIRE_PLACE, "Ship_ActivateFirePlace", 0);
	SetEventHandler(WHR_LIGHTNING, "Ship_Lightning", 0);
	SetEventHandler("TornadoDamage", "Ship_TornadoDamage", 0);

	SetEventHandler(SHIP_BRANDER_DETONATE, "Ship_BranderDetonate", 0);

	SetEventHandler(SHIP_MAST_DAMAGE, "Ship_MastDamage", 0);

	SetEventHandler("eSwitchPerks", "Ship_UpdatePerks", 0);
}

float Ship_GetBortFireDelta()
{
	aref aCharacter = GetEventData();
	float x = GetEventData();
	float y = GetEventData();
	float z = GetEventData();
	float fDistance = GetDistance2D(x, z, stf(aCharacter.Ship.Pos.x), stf(aCharacter.Ship.Pos.z));

	float fAccuracy = 1.0 - stf(aCharacter.TmpSkill.Accuracy);

	float fRadius1 = fAccuracy * Bring2Range(0.0, 140.0, 0.0, 800.0, fDistance);	// 35 @ 200 for example
	// LDH 16Sep06 additional radius calc based on normalized random number
	float fRadius = abs(randnorm(100.0, fAccuracy*20.0) - 100.0);		

	// LDH 27Sep06 make chasers more accurate
	int Quad = GetCannonQuadrant(clampangle(atan2(stf(aCharacter.ship.pos.x) - x, stf(aCharacter.ship.pos.z) - z) - (stf(aCharacter.ship.ang.y) + PI)));
	float minRadius = 3.0;
	if (Quad == 0 || Quad == 2) minRadius = 0.0;

	fRadius = fclamp(minRadius, fRadius1, fRadius);

	return fRadius;
}

string Ship_GetGroupID(ref rCharacter)
{
	if(!CheckAttribute(rCharacter,"SeaAI.group.name")) return "none"; // NK bugfix 05-04-22
	return rCharacter.SeaAI.Group.Name;
}

void Ship_FireAction()
{
// KK -->
	int iCharacterIndex = GetEventData();				// we attack this character
	int iMainGroupCharacterIndex = GetEventData();		// commander character for rCharacter
	int iRelation = GetEventData();

	if (iCharacterIndex < 0 || iMainGroupCharacterIndex < 0) return;

	ref rCharacter = GetCharacter(iCharacterIndex);
	ref rMainGroupCharacter = GetCharacter(iMainGroupCharacterIndex);
// <-- KK

	ref rMainCharacter = GetMainCharacter();
	// if companion - decrease reputation
	if (IsCompanion(rCharacter))
	{
		ChangeCharacterReputation(GetMainCharacter(), -15); // NK

		if (CheckAttribute(rCharacter, "CompanionEnemyEnable") && sti(rCharacter.CompanionEnemyEnable))
		{
			SeaAI_SetCompanionEnemy(rCharacter);
			// disconnect companion from our group and set it enemy to us
		}

		return;
	}
	// NK surrendered ships 05-04-20
	if(CheckAttribute(rCharacter,"surrendered") && sti(rCharacter.surrendered.seatime) + 20 < GetSeaTime()) // 20 second grace period
	{
		ChangeCharacterReputation(rMainCharacter, -10); // NK
	}

	if (iRelation != RELATION_ENEMY)
	{
		// RM -->
		SetCharacterRelationBoth(GetMainCharacterIndex(), iMainGroupCharacterIndex, RELATION_ENEMY); // KK
		UpdateRelations();

		RefreshBattleInterface();
		//if (bIslandLoaded) { SetNationRelation2MainCharacter(sti(rMainGroupCharacter.nation), RELATION_ENEMY); }
// changed by MAXIMUS [abordage MOD] -->
		if(sti(rMainGroupCharacter.nation)<=6)
		{
			AttackRMRelation(GetMainCharacter(), sti(rMainGroupCharacter.nation));
		}
		else
		{
			if(sti(rMainGroupCharacter.nation)>6) SetCharacterRelationBoth(sti(GetMainCharacterIndex()),iMainGroupCharacterIndex,RELATION_ENEMY); // KK
		}
// changed by MAXIMUS [abordage MOD] <--
		//if(GetRMRelation(GetMainCharacter(), sti(rMainGroupCharacter.nation)) > REL_AFTERATTACK) ChangeCharacterReputation(GetMainCharacter(), -10); // handled in AttackRM
		// RM <--
	}

	UpdateRelations();

	RefreshBattleInterface();

	// if fort - return
	if (rCharacter.Ship.Type == SHIP_FORT_NAME) { return; } // PS

	string sGroupID = Ship_GetGroupID(rMainGroupCharacter);
	string sGroupType = Group_GetType(sGroupID);
	ref rGroup = Group_GetGroupByID(sGroupID);

	Group_SetTaskAttack(PLAYER_GROUP, sGroupID);
}

// Walker -->
float Ship_MastDamage()
{
	int iDamageType = GetEventData();
	int iMastNum = GetEventData();
	float x = GetEventData();
	float y = GetEventData();
	float z = GetEventData();
	float fDamage = GetEventData();
	aref rCharacter = GetEventData();
	rCharacter.seatime.lasthit = GetSeaTime(); // NK 04-09-16, to set time when last hit
	// NK -->
	float hp = GetCharacterShipHP(rCharacter);
	hp = sqrt(sqrt(pow(hp,2.5)))/82.0;
	hp = 1.0/hp;
	float collhp = sqrt(hp);
	
	if(!bSeaActive) return makefloat(0);//MAXIMUS

	switch (iDamageType)
	{
		case SHIP_MAST_TOUCH_ISLAND:
			fDamage = fDamage + MAST_ISLAND_SCL * collhp;
		break;
			
		case SHIP_MAST_TOUCH_SHIP:
		//aref rCollideCharacter = GetEventData();
			fDamage = fDamage + MAST_SHIP_SCL * collhp;
		break;

		case SHIP_MAST_TOUCH_BALL:
			//Log_SetStringToLog("Captain, we hit their mast!");
			int iBallType = sti(AIBalls.CurrentBallType);
			float fMult = 1.0;
			if(CheckAttribute(rCharacter,"Ship.LastBallCharacter"))
			{
				int bidx = sti(rCharacter.Ship.LastBallCharacter);
				if(bidx >= 0 && bidx < MAX_CHARACTERS)
				{
					ref rBallCharacter = GetCharacter(bidx);
					if(sti(rBallCharacter.index) != GetMainCharacterIndex() && CheckAttribute(rCharacter,"surrendered")) return fDamage; // NK because AI doesn't respect surrender. 05-04-23
					rBallCharacter.Ship.lasttgt = sti(rCharacter.index); // NK 04-09-22 to see who AI is attacking
					ref rCannon; if(USE_REAL_CANNONS) rCannon = GetCannonByType(CANNON_TYPE_LONG_LBS4); // KNB
					else rCannon = GetCannonByType(CANNON_TYPE_CANNON_LBS12);
					if(CheckAttribute(rBallCharacter,"Ship.Cannons.Type")) {rCannon = GetCannonByType(sti(rBallCharacter.Ship.Cannons.Type)); } // NK bugfix 04-06
					fMult = stf(rCannon.DamageMultiply);
					fMult *= MAST_MULT_SCL;
				}
			}
			switch (iBallType)
			{
				case GOOD_BALLS:
					fDamage = fDamage + MAST_BALL_SCL * hp * fMult;
				break;

				case GOOD_GRAPES:
					fDamage = fDamage + MAST_GRAPE_SCL * hp * fMult;
				break;

				case GOOD_KNIPPELS:
					fDamage = fDamage + MAST_CHAIN_SCL * hp * fMult;
				break;

				case GOOD_BOMBS:
					fDamage = fDamage + MAST_BOMB_SCL * hp * fMult;
				break;
			}
		break;
	}

	fDamage = Clampf(fDamage);
	//KB - Tuning ships - Now hits at masts have a chance of critical hit, higher from chasers, even higher from gchasers. No matter calibers, but yes range
	if ( iBalltype != GOOD_GRAPES ) {  //Grapes can't bring down a mast... never!!!
		ref tempref;
		aref temparef;
		float endmastchance = 0.0;
		float mastchance = MASTCRIT/100;
		tempref=GetCharacter(sti(AIBalls.CharacterIndex));
		if(CheckAttribute(temparef,"quad")) {
			if ( AIBalls.quad == "cannonf" ) {  //KB It's a chaser	
				mastchance = CMASTCRIT/100;
				makearef(temparef,tempref.ship);
				if (CheckAttribute(temparef,"tune")) {
					makearef(temparef,tempref.ship.tune);
					if(CheckAttribute(temparef,"gchasers")) {  
						if ( sti(tempref.ship.tune.gchasers.on) == 1 ) {
							mastchance = GCMASTCRIT/100;
						}
					}
					if(CheckAttribute(temparef,"ltopmasts")) {  
						if ( sti(rCharacter.ship.tune.ltopmasts.on) == 1 ) {
							mastchance = mastchance + TMSAILCRIT/100;
						}
					}
				}
			}
		}

//Checking crit mast fall
		float fShotDistance = Ship_GetDistance2D(tempref, rCharacter);
		if(fShotDistance <= 0) fShotDistance = 1.0; // NK for when dist < 0 05-04-15
		float exponent = (fShotDistance/100) -1;
		endmastchance = mastchance / pow(2,exponent); // decreasing  with range
		if ( frnd() < endmastchance ) { // KB - bring down a mast
Trace("KB - Tuning - Critical on mast");
			PlaySound("objects\shipcharge\hurrah.wav");
			fDamage = 1.0;
		}
	}
//KB - END Tuning ships


	// if mast fall - play sound
	if (fDamage >= 1.0)
	{
		Play3DSound("mast_fall", x, y, z); if(AUTO_SKILL_SYSTEM) { Ship_AddCharacterExpChar(rCharacter, "Cannons", makefloat(fDamage*100)); }
	}else { if(AUTO_SKILL_SYSTEM) { Ship_AddCharacterExpChar(rCharacter, "Cannons", makefloat(fDamage*50)); } }

	return fDamage;
}
// Walker <--

void Ship_SetSailState(int iCharacterIndex, float fSailState)
{
	fSailState = clampf(fSailState); // NK 05-04-20 bugfix
	if(iCharacterIndex >= MAX_CHARACTERS) return; // bugfix here too
	SendMessage(&AISea, "laf", AI_MESSAGE_SHIP_SET_SAIL_STATE, &Characters[iCharacterIndex], fSailState);

	/* // Boatswain sounds MOD - Amokachi
	ref	rCharacter = GetCharacter(iCharacterIndex);
	fX = 0; fY = 0; fZ = 0;
	if(CheckAttribute(rCharacter,"Ship.pos.x")) fX=stf(rCharacter.Ship.pos.x);
	if(CheckAttribute(rCharacter,"Ship.pos.y")) fY=stf(rCharacter.Ship.pos.y);
	if(CheckAttribute(rCharacter,"Ship.pos.z")) fZ=stf(rCharacter.Ship.pos.z);
	switch (fSailState)
	{
		case 0.0:
			Log_SetStringToLog("Sails lowered");
			if(ENABLE_EXTRA_SOUNDS == 1) {			// added by KAM after build 11
			Play3DSound("sails_lowered", fX, fY, fZ);
			}						// added by KAM after build 11
		break;
		case 0.5:
		Log_SetStringToLog("Battle sails");
			if(ENABLE_EXTRA_SOUNDS == 1) {			// added by KAM after build 11
			Play3DSound("sails_battle", fX, fY, fZ);
			}						// added by KAM after build 11
		break;
		case 1.0:
			Log_SetStringToLog("Sails raised");
			if(ENABLE_EXTRA_SOUNDS == 1) {			// added by KAM after build 11
			Play3DSound("sails_raised", fX, fY, fZ);
			}						// added by KAM after build 11
		break;
	}*/
}
// NK add set ship task to group task 05-04-20
int Ship_SetTaskGroupTask(int iTaskPriority, int iCharacterIndex)
{
	float x, y, z;
	string target;
	ref chr = GetCharacter(iCharacterIndex);
	ref echr;
	int closetgt, i;
	string gname = Ship_GetGroupID(&chr);
	ref rGroup = Group_FindOrCreateGroup(gname);
	float fAng;
	if(!CheckAttribute(rGroup,"task")) return -1;
	bool bdone = true;
	int task = AITASK_NONE;
	switch(sti(rGroup.task))
	{
		case AITASK_NONE: task = AITASK_NONE; Ship_SetTaskNone(iTaskPriority, iCharacterIndex); break;
		case AITASK_DRIFT: task = AITASK_DRIFT; Ship_SetTaskDrift(iTaskPriority, iCharacterIndex); break;
		case AITASK_ATTACK:
			task = AITASK_ATTACK;
			if(CheckAttribute(rGroup,"target"))
			{
				target = rGroup.target;
				closetgt = FindClosestShipOfGroup(iCharacterIndex, &x, gname);
			}
			else { closetgt = FindClosestShipofRel(iCharacterIndex, &x, RELATION_ENEMY); }
				
			if(closetgt != -1)
			{
				echr = GetCharacter(closetgt);
				Ship_SetTaskAttack(iTaskPriority, iCharacterIndex, closetgt);
				
			}
			else bdone = false; 
		break;
		case AITASK_MOVE: 
			task = AITASK_MOVE; 
			if (CheckAttribute(rGroup,"target.pos")) Ship_SetTaskMove(iTaskPriority, iCharacterIndex, stf(rGroup.target.pos.x), stf(rGroup.target.pos.z));	// TIH attribute bug fix Jul11'06
		break;
		case AITASK_RUNAWAY: 
			task = AITASK_RUNAWAY; 
			Ship_SetTaskRunaway(iTaskPriority, iCharacterIndex); 
		break;
		bdone = false;
	}
	if(!bdone)
	{
		task = AITASK_MOVE; 
		fAng = frnd() * PIm2;
		Ship_SetTaskMove(iTaskPriority, iCharacterIndex, 10000.0 * sin(fAng) , 10000.0 * cos(fAng));
	}
	return task;
}
// NK <--

void Ship_SetTaskNone(int iTaskPriority, int iCharacterIndex)
{
	ref rCharacter = GetCharacter(iCharacterIndex);
	rCharacter.SeaAI.Task = AITASK_NONE;
}

void Ship_SetTaskBrander(int iTaskPriority, int iCharacterIndex, int iCharacterIndexBranderTo)
{
	ref rCharacter = GetCharacter(iCharacterIndex);
	rCharacter.SeaAI.Task = AITASK_BRANDER;
	rCharacter.SeaAI.Task.Target = iCharacterIndexBranderTo;
	SendMessage(&AISea, "lllaa", AI_MESSAGE_SHIP_SET_TASK, AITASK_BRANDER, iTaskPriority, &Characters[iCharacterIndex], &Characters[iCharacterIndexBranderTo]);
}

void Ship_SetTaskMove(int iTaskPriority, int iCharacterIndex, float x, float z)
{
	ref rCharacter = GetCharacter(iCharacterIndex);
	rCharacter.SeaAI.Task = AITASK_MOVE;
	rCharacter.SeaAI.Task.Target = "";
	rCharacter.SeaAI.Task.Pos.x = x;
	rCharacter.SeaAI.Task.Pos.z = z;
	SendMessage(&AISea, "lllaff", AI_MESSAGE_SHIP_SET_TASK, AITASK_MOVE, iTaskPriority, &Characters[iCharacterIndex], x, z);
}

void Ship_SetTaskRunaway(int iTaskPriority, int iCharacterIndex)
{
	ref rCharacter = GetCharacter(iCharacterIndex);
	rCharacter.SeaAI.Task = AITASK_RUNAWAY;
	rCharacter.SeaAI.Task.Target = "";
	SendMessage(&AISea, "llla", AI_MESSAGE_SHIP_SET_TASK, AITASK_RUNAWAY, iTaskPriority, &Characters[iCharacterIndex]);
}

void Ship_SetTaskAttack(int iTaskPriority, int iCharacterIndex, int iCharacterIndexVictim)
{
	ref rCharacter = GetCharacter(iCharacterIndex);
	rCharacter.SeaAI.Task = AITASK_ATTACK;
	rCharacter.SeaAI.Task.Target = iCharacterIndexVictim;
	SendMessage(&AISea, "lllaa", AI_MESSAGE_SHIP_SET_TASK, AITASK_ATTACK, iTaskPriority, &Characters[iCharacterIndex], &Characters[iCharacterIndexVictim]);
}

void Ship_SetTaskSailTo(int iTaskPriority, int iCharacterIndex, int iCharacterIndexSailTo)
{
	ref rCharacter = GetCharacter(iCharacterIndex);
	rCharacter.SeaAI.Task = AITASK_MOVE;
	rCharacter.SeaAI.Task.Target = iCharacterIndexSailTo;
	SendMessage(&AISea, "lllaa", AI_MESSAGE_SHIP_SET_TASK, AITASK_MOVE, iTaskPriority, &Characters[iCharacterIndex], &Characters[iCharacterIndexSailTo]);
}

void Ship_SetTaskDrift(int iTaskPriority, int iCharacterIndex)
{
	ref rCharacter = GetCharacter(iCharacterIndex);
	rCharacter.SeaAI.Task = AITASK_DRIFT;
	rCharacter.SeaAI.Task.Target = "";
	SendMessage(&AISea, "llla", AI_MESSAGE_SHIP_SET_TASK, AITASK_DRIFT, iTaskPriority, &Characters[iCharacterIndex]);
}

void Ship_SetTaskDefendGroup(int iTaskPriority, int iCharacterIndex, string sDefendGroup)
{
	SendMessage(&AISea, "lllas", AI_MESSAGE_SHIP_SET_TASK, AITASK_DEFEND_GROUP, iTaskPriority, &Characters[iCharacterIndex], sDefendGroup);
}

void Ship_SetTaskDefend(int iTaskPriority, int iCharacterIndex, int iCharacterIndex2Defend)
{
	ref rCharacter = GetCharacter(iCharacterIndex);
	rCharacter.SeaAI.Task = AITASK_DEFEND;
	rCharacter.SeaAI.Task.Target = iCharacterIndex2Defend;
	SendMessage(&AISea, "lllaa", AI_MESSAGE_SHIP_SET_TASK, AITASK_DEFEND, iTaskPriority, &Characters[iCharacterIndex], &Characters[iCharacterIndex2Defend]);
}

// LDH -->
// This is the original Ship_DoFire()
void Ship_DoAimedFire()
{
	int bCameraOutside = SeaCameras_isCameraOutside();

	SendMessage(&AISea, "lal", AI_MESSAGE_CANNON_FIRE, &Characters[GetMainCharacterIndex()], bCameraOutside);

}

void Ship_DoFire()
{
	int bCameraOutside = SeaCameras_isCameraOutside();
// LDH change to allow auto aiming from deck.  Use requires controls to be reset in the options screen.
	if (AUTO_AIMED_FIRE) {
		bCameraOutside = true;
	}

	SendMessage(&AISea, "lal", AI_MESSAGE_CANNON_FIRE, &Characters[GetMainCharacterIndex()], bCameraOutside);
}
// LDH <--

void Ship_Add2Sea(int iCharacterIndex, bool bFromCoast, string sFantomType)
{
	if(iCharacterIndex<0) return;
	ref rCharacter = GetCharacter(iCharacterIndex);
	if(CharacterIsDead(rCharacter)) return;
	aref arCharacter; makearef(arCharacter, Characters[sti(rCharacter.index)]);
	DeleteAttribute(arCharacter,"skillsnatmult"); // NK PRS3 04-09-15 as lookup to speed up national skill mult
	DeleteAttribute(arCharacter,"ship.changedammo"); // NK 04-09-15 clears "we already changed ammo this round" attribute
	DeleteAttribute(arCharacter,"ship.lasttgt"); // NK 04-09-16 clears "we last fired on this char idx" attribute
	DeleteAttribute(arCharacter,"ship.attackpcharset"); // NK 04-09-16 clears "we're already set to attack pchar" attribute
	DeleteAttribute(arCharacter,"seatime"); // NK 04-09-16 deletes seatime tree
	DeleteAttribute(arCharacter,"surrendered"); // NK surrender 05-04-20
	DeleteAttribute(arCharacter,"oldnation");
	if(iCharacterIndex == GetMainCharacterIndex()) { rCharacter.seatime = 0; rCharacter.lastupdateseatime = 0; } // NK 04-09-16 clears timer attribute, -21 for updatetime
	//trace("starting sa2s for " + rCharacter.id + " and prev numships " + iNumShips+ ", did delete attr");

	int iShipType = GetCharacterShipType(rCharacter); // PS
	if (iShipType >= SHIP_TYPES_QUANTITY_WITH_FORT)
	{
		Trace("Character.id = " + rCharacter.id + ", have invalid ship type = " + iShipType + ", and try load to sea");
		//return; // NK 04-09-12 fix so you never crash on bad ship type. -->
		if(!LAi_IsDead(rCharacter))
		{
			if(sti(rCharacter.nation) == PIRATE) rCharacter.fantomtype = PIRATE;
			string ftype = "war"; if(CheckAttribute(rCharacter,"fantomtype")) ftype = rCharacter.fantomtype;
			rCharacter.ship.type = GetShipID(Force_GetShipType(GetCharacterShipClass(GetMainCharacter()) - 1, GetCharacterShipClass(GetMainCharacter()) +1, ftype, sti(rCharacter.nation)));
			iShipType = GetCharacterShipType(rCharacter);
			LAi_Create_Officer(0, rCharacter); // NK
			Fantom_SetRandomMoney(rCharacter, ftype);
			Fantom_SetCannons(rCharacter, ftype);
			Fantom_SetSails(rCharacter, ftype);
		}
		// NK <--
	}
	//trace("found ship type");

	if (iCharacterIndex >= FANTOM_CHARACTERS && !CheckAttribute(&rCharacter, "skipsetfantom")) // WM to not do this for fantoms already done.
	{
		SetBaseShipData(rCharacter);
		Ship_SetFantomData(rCharacter);

		Fantom_ClearCargo(rCharacter); // TIH clear out prior slot cargo
		Fantom_SetBalls(rCharacter, sFantomType);
		Fantom_SetGoods(rCharacter, sFantomType);
	}
	//trace("did setfantom stuff");

	rCharacter.TmpPerks.Turn = false;

	CharacterUpdateShipFromBaseShip(iCharacterIndex);
	//trace("updated ship from base");

//	rCharacter.Ship.Ang.y = 0.0;
	rCharacter.Ship.Ang.y = rand(90);
	if(CheckAttribute(rCharacter,"forceshipangle")) { rCharacter.Ship.Ang.y = rCharacter.forceshipangle; DeleteAttribute(rCharacter,"forceshipangle"); } // NK 05-04-15

	Ship_ClearImpulseStrength(rCharacter);
	//trace("cleared impulse");

	rCharacter.Ship.LastBallCharacter = -1;
	rCharacter.Ship.Speed.z = 0.0;
	rCharacter.Ship.Strand = false;
	rCharacter.Ship.Strand.SndID = SOUND_INVALID_ID;
	rCharacter.Ship.Stopped = false;
	rCharacter.Ship.POS.Mode = SHIP_SAIL;
	if (bFromCoast == false) { rCharacter.Ship.Speed.z = 1.0; }
	//trace("set stuff");
	Event("eSwitchPerks", "l", iCharacterIndex);							// Perks
	//trace("perks start");
	Event(SHIP_UPDATE_PARAMETERS, "lf", iCharacterIndex, stf(rCharacter.Ship.Speed.z));		// Parameters
	//trace("started update params");
	CreateEntity(&Characters[iCharacterIndex], "Ship");
	//trace("created entity");
	SendMessage(&AISea, "liaa", AI_MESSAGE_ADD_SHIP, &Characters[iCharacterIndex], &Characters[iCharacterIndex], &ShipsTypes[iShipType]);
	//trace("added ship");
	
	ReloadProgressUpdate();

	Ships[iNumShips] = iCharacterIndex;
	rCharacter.curshipnum = iNumShips; // NK 05-04-15 to keep track.
	iNumShips++;
	if(!CheckAttribute(rCharacter,"Ship.Crew.Morale")) { rCharacter.Ship.Crew.Morale = 45; }//MAXIMUS
	rCharacter.seatime.basecrewmorale = rCharacter.Ship.Crew.Morale; // NK 04-09-16 mod morale in combat; this sets the base
	rCharacter.seatime.basecrewquantity = GetCrewQuantity(rCharacter); // ditto but for quantity
	//trace("updated ships[], set basecrew");

	Ship_CannonsFiringCheck(&rCharacter, true); // TIH run this so that it can clear any leftover firingnums Nov13'06
}

void Ship_ChangeChargeEvent()
{
	string	sBallName;

	ref rCharacter = GetCharacter(GetEventData());

	SendMessage(&AISea, "la", AI_MESSAGE_CANNON_RELOAD, rCharacter);
	sBallName = GetEventData();
	rCharacter.Ship.Cannons.Charge = sBallName;
	Cannon_RecalculateParameters(sti(rCharacter.index));
	rCharacter.seatime.lastfired = GetSeaTime(); // NK 04-09-16, to set time when last fired/reloaded
}

void Ship_ChangeCharge(ref rCharacter, int iNewChargeType)
{
	if (CANNONPOWDER_MOD) {// TIH --> mod toggle 7-7-06
		// changed by MAXIMUS [gunpowder mod] -->
		ref rCannon; makeref(rCannon,Cannon[GetCaracterShipCannonsType(rCharacter)]);
		int used = 0;
		if(CheckAttribute(rCannon,"gunpowder")) used = sti(rCannon.gunpowder);
		int gunPowderQty = GetCargoGoods(rCharacter, GOOD_GUNPOWDER);

		if( gunPowderQty >= sti(GetCannonArcCurQty(rCharacter, 0))*used ||  
			gunPowderQty >= sti(GetCannonArcCurQty(rCharacter, 2))*used )// check front and rear cannons
		{
			// do nothing as powder is A O K (for at least one arc)
		}
		else
		{
			if(rCharacter.Ship.Cannons.Type == CANNON_TYPE_NONECANNON)
			{
				//int tmpLangFileID = LanguageOpenFile("interface_strings.txt");
				Log_SetStringToLog(TranslateString("","Captain, we're out of gunpowder!"));
				//LanguageCloseFile(tmpLangFileID);
				rCharacter.ship.changedammo = false;
				return;// stop this function and return early
			}
		}
	}// TIH <-- mod toggle
	// continue on at this point normally if it hasnt been stopped above

	if (iNewChargeType == -1) return; // KK

	SendMessage(&AISea, "la", AI_MESSAGE_CANNON_RELOAD, &rCharacter); // must be first line in this function
	rCharacter.seatime.lastfired = GetSeaTime(); // NK 04-09-16, to set time when last fired/reloaded

	ref rGood = GetGoodByType(iNewChargeType);
	rCharacter.Ship.Cannons.Charge = rGood.name;
	rCharacter.Ship.Cannons.Charge.Type = iNewChargeType;

	// Boatswain sounds MOD - Amokachi
	if(sti(rCharacter.Index) == GetMainCharacterIndex())
	{
		switch (iNewChargeType)
		{
			case GOOD_BALLS:
				if(ENABLE_EXTRA_SOUNDS == 1) {			// added by KAM after build 11
				PlayStereoSound("loading_cannonballs");
				}						// added by KAM after build 11
			break;
			case GOOD_BOMBS:
				if(ENABLE_EXTRA_SOUNDS == 1) {			// added by KAM after build 11
				PlayStereoSound("loading_bombs");
				}						// added by KAM after build 11
			break;
			case GOOD_GRAPES:
				if(ENABLE_EXTRA_SOUNDS == 1) {			// added by KAM after build 11
				PlayStereoSound("loading_grapeshot");
				}						// added by KAM after build 11
			break;
			case GOOD_KNIPPELS:
				if(ENABLE_EXTRA_SOUNDS == 1) {			// added by KAM after build 11
				PlayStereoSound("loading_knippels");
				}						// added by KAM after build 11
			break;
		}
	}
	else
	{
		float fX, fY, fZ;
		fX = 0; fY = 0; fZ = 0;
		if(CheckAttribute(rCharacter,"Ship.pos.x")) fX=stf(rCharacter.Ship.pos.x);
		if(CheckAttribute(rCharacter,"Ship.pos.y")) fY=stf(rCharacter.Ship.pos.y);
		if(CheckAttribute(rCharacter,"Ship.pos.z")) fZ=stf(rCharacter.Ship.pos.z);
		switch (iNewChargeType)
		{
			case GOOD_BALLS:
				if(ENABLE_EXTRA_SOUNDS == 1) {			// added by KAM after build 11
				Play3DSound("loading_cannonballs", fX, fY, fZ);
				}						// added by KAM after build 11
			break;
			case GOOD_BOMBS:
				if(ENABLE_EXTRA_SOUNDS == 1) {			// added by KAM after build 11
				Play3DSound("loading_bombs", fX, fY, fZ);
				}						// added by KAM after build 11
			break;
			case GOOD_GRAPES:
				if(ENABLE_EXTRA_SOUNDS == 1) {			// added by KAM after build 11
				Play3DSound("loading_grapeshot", fX, fY, fZ);
				}						// added by KAM after build 11
			break;
			case GOOD_KNIPPELS:
				if(ENABLE_EXTRA_SOUNDS == 1) {			// added by KAM after build 11
				Play3DSound("loading_knippels", fX, fY, fZ);
				}						// added by KAM after build 11
			break;
		}
	}

	Cannon_RecalculateParameters(sti(rCharacter.index));
	rCharacter.ship.changedammo = true; // NK 04-09-15
}

void Ship_OnCreate()
{
	ref rCharacter = GetCharacter(GetEventData());
	//Trace("Ship_OnCreate rCharacter = " + rCharacter.id);

	rCharacter.Ship.Speed.z = 0.0;
	rCharacter.Ship.Speed.y = 0.0;
	rCharacter.Ship.Speed.x = 0.0;
}

// NK ship morale
void Ship_CheckMorale(int chridx)
{
//if (chridx && DEBUGINFO) trace("Ship_Checkmorale start, chridx = " + chridx);
	ref rCharacter = GetCharacter(chridx);
//if (chridx && DEBUGINFO) trace("Ship_Checkmorale probe 1");
//if (chridx && DEBUGINFO) trace("Ship_Checkmorale rCharacter.index = " + rCharacter.index);
	if(rCharacter.ship.type == SHIP_FORT_NAME) return; // NK fort check 05-04-20
	if(CheckAttribute(rCharacter,"surrendered")) return; // NK surrender 05-04-20
	int timecheck = CHANGE_MORALE_EVERY / (1 + (chridx != GetMainCharacterIndex())*2); //if not mainchar, divide by 3, since checksit is run once per 3 seconds for other ships.
	int timesincelast = timecheck;
	if(CheckAttribute(rCharacter,"seatime.timesincelast"))
		{ timesincelast = sti(rCharacter.seatime.timesincelast); }

	if(timesincelast < timecheck)
		{ rCharacter.seatime.timesincelast = timesincelast+1; } //increment one and skip; this is to due this once every CHANGE_MORALE_EVERY seconds
	else
	{
	  if(!IsCompanion(&rCharacter) && !CheckAttribute(rCharacter,"live")) // added by MAXIMUS [abordage MOD]
	  {
		rCharacter.seatime.timesincelast = 0; //reset counter
		float enemydistance = DIST_NOSHIP;
		int targetidx = FindClosestShipofRel(sti(rCharacter.index), &enemydistance, RELATION_ENEMY); // finds nearest hostile ship
		int currenttime = GetSeaTime();
		int lasthit = -1; if(CheckAttribute(rCharacter,"seatime.lasthit")) lasthit = sti(rCharacter.seatime.lasthit); //last time we were hit
		int lastfired = -1; if(CheckAttribute(rCharacter,"seatime.lastfired")) lastfired = sti(rCharacter.seatime.lastfired); // last time we fired or reloaded
		if(!CheckAttribute(rCharacter,"Ship.Crew.Morale")) { rCharacter.Ship.Crew.Morale = 45; }//MAXIMUS
		float tempmorale = stf(rCharacter.ship.crew.morale); if(CheckAttribute(rCharacter,"seatime.tempmorale")) tempmorale = stf(rCharacter.seatime.tempmorale); //i.e. a float morale
		int CrewQuantity = sti(rCharacter.Ship.Crew.Quantity);
		//Logit("Checking morale for the " + rCharacter.ship.name + ", time is " + currenttime + ", current morale is " + fts(tempmorale,2) + ", lht " + lasthit + ", lft " + lastfired);
		if(CheckAttribute(rCharacter,"seatime.lastcrew")) // i.e. have crewmen been killed?
		{
			tempmorale = tempmorale - (stf(rCharacter.seatime.lastcrew) - makefloat(CrewQuantity)) / stf(rCharacter.seatime.basecrewquantity) / CREW_PCT_FOR_MORALE;
			DeleteAttribute(rCharacter,"seatime.lastcrew");
		}
		float combatlevel = COMBATLEVEL_NONE;
		if(enemydistance <= RANGE_TO_ENEMY) combatlevel = COMBATLEVEL_ENEMYHERE;
		if(lastfired != -1) { if((currenttime - lastfired) < TIME_SINCE_GUNS) combatlevel = COMBATLEVEL_GUNS; }
		if(lasthit != -1) { if((currenttime - lasthit) < TIME_SINCE_HIT) combatlevel = COMBATLEVEL_HIT; }
		if(combatlevel == COMBATLEVEL_NONE)
		{
			// return to baseline when not in combat
			if(CheckAttribute(rCharacter,"seatime.basecrewmorale"))
			{
				int basemorale = sti(rCharacter.seatime.basecrewmorale);
				if(makeint(tempmorale) != basemorale) 
				{
					if(tempmorale > basemorale) tempmorale -= MORALE_AMT_COMEBACK;
					else tempmorale += MORALE_AMT_COMEBACK;
				}
			}
			else { rCharacter.seatime.basecrewmorale = makeint(tempmorale); } // NK 04-09-16 bugfix if attr not exist.
		}
		else
		{
			//tempmorale -= combatlevel;
			if(tempmorale > 5.0 / combatlevel) { tempmorale -= retmin(COMBATLEVEL_HIT*CHANGE_MORALE_EVERY, combatlevel*2.5); } // now use floors and scalar. 05-04-20
			//Logit("Current combatlevel is " + combatlevel);
		}
		if(CheckAttribute(rCharacter,"seatime.enemydead"))
		{
			tempmorale += stf(rCharacter.seatime.enemydead);
			DeleteAttribute(rCharacter,"seatime.enemydead"); // 04-09-22 fix
		}
		if(tempmorale > MORALE_MAX) tempmorale = MORALE_MAX;
		if(tempmorale < MORALE_MIN) tempmorale = MORALE_MIN;
		//Logit("Morale for " + rCharacter.ship.name + " is " + makeint(tempmorale) + ", change " + (tempmorale - makefloat(sti(rCharacter.ship.crew.morale))));
		rCharacter.ship.crew.morale = makeint(tempmorale);
		rCharacter.seatime.tempmorale = tempmorale;
		//Logit("new morale is " + tempmorale);


		// start surrender checking 05-04-20
		// below also check nosurrender 05-07-06
		if(SURR_GLOBAL_SCL >0 && !IsCompanion(&rCharacter) && targetidx != -1 && lasthit != -1 && !CheckAttribute(&rCharacter, "nosurrender")) // for now comps can't either. Makes life simpler. Also, must be an enemy nearby, and must have been hit.
		{
			float ftmult = 1.0;
			float fdist = DIST_NOSHIP; fdist = FindClosestShipofRel(chridx, &fdist, RELATION_FRIEND);
			if(CheckAttribute(rCharacter,"fantomtype"))
			{
				switch(rCharacter.fantomtype)
				{
					case "trade": ftmult = 2.0; break;
					case "pirate": ftmult = 0.75; break;
				}
			}
			float mrlmod = Bring2Range(SURR_MRL_SCL_MAX, SURR_MRL_SCL_MIN, 0.1, 1.4, stf(rCharacter.TmpSkill.Leadership)+ AIShip_isPerksUse(rCharacter.TmpPerks.IronWill, 0.0, 0.4));
			ref pch = GetMainCharacter();
			//if(CheckAttribute(rCharacter,"lastballcharacter")) { if(sti(rCharacter.lastballcharacter) == GetMainCharacterIndex()) { if(CheckAttribute(pch,"surmult")) ftmult = 99999.9; } }
			ref rGroup = Group_FindOrCreateGroup(Ship_GetGroupID(rCharacter));
			float surmorale = SURR_MAX_MORALE * mrlmod * ftmult;
//if (chridx && DEBUGINFO) trace("Ship_Checkmorale probe 2");
			if(tempmorale > surmorale)
			{
				// put ship back in group and do group's task.
				if(CheckAttribute(rGroup,"task") && rGroup.task != AITASK_RUNAWAY && CheckAttribute(rCharacter,"SeaAI.task") && rCharacter.SeaAI.task != rGroup.task) { Ship_SetTaskGroupTask(SECONDARY_TASK, chridx); }
				return;
			}
// LDH fix for possible divide by zero crash -->
			float surch;
			if (surmorale == 0.01)
			{
				surch = 0.01;
			}
			else
			{
				surch = Bring2Range(0.75, 0.01, 0.01, surmorale, tempmorale);	// original code
			}
// LDH <--
			float HP = GetCurrentShipHP(&rCharacter);
			float HPr = HP / makefloat(GetCharacterShipHP(&rCharacter));
			if(surch > 0.25 || HPr < 20) Ship_SetTaskRunaway(SECONDARY_TASK, chridx);
			if(HPr > 20 && fdist <= enemydistance) return; // if friend closer than enemy, no surrender.
			if(HPr < 20) surch *= Bring2Range(6.0 * (1.0 + makefloat(fdist+200 < enemydistance)), 1.25 * (0.5 + makefloat(fdist+200 < enemydistance)/2.0), 0.1, sqrt(20), sqrt(HPr)); // should often surrender when low on HP, but not if a friend is nearer (to pick them up).
			//ref echr = GetCharacter(targetidx);
			float strengthrat = sqrt(FindPowerRatio(chridx));
			float SP = GetSailPercent(rCharacter);
			if(strengthrat > 1.0) // they're stronger
			{
				if(SP < 40) surch *= Bring2Range(5.0, 0.5, 0.1, sqrt(40), sqrt(SP));
				else surch *= 0.5;
			}
			surch *= SURR_GLOBAL_SCL; // LDH
			Trace("SURR: for char " + rCharacter.id + " of ship " + rCharacter.ship.name + " surrender chance is " + round(surch*100.0)+"%");
			if(frnd() < surch)
			{
				Ship_Surrender(chridx);
			}
		}
	  }
	}
	// NK <--
}

void Ship_Surrender(int chridx)
{
	Trace("SURR: Ship_Surrender begin idx: " + chridx);
	ref chr = GetCharacter(chridx);
	string chid = chr.id;

	// TIH --> code to determine if they were released from a boarding freely Aug31'06
	// set a boolean to use, and clear out the attribute so it doesn't carry anywhere later
	bool bReleasedFree = false;
	if (CheckAttribute(chr,"releasedfree") && sti(chr.releasedfree))	// LDH 13Oct06 added sti()
	{
		bReleasedFree = true;
		DeleteAttribute(chr,"releasedfree");
		Trace("SURR: " + chid + " was released free from ransack");
		// if they are being release, we need to reset their nation, so that its setup again properly
		//if ( CheckAttribute(chr,"oldnation") && CheckAttribute(chr,"surrendered") && sti(chr.surrendered) == 1 ) 
		// only reset the nation if they are already in the surrendered group
		if ( CheckAttribute(chr,"oldnation") && Ship_GetGroupID(&chr) == SURR_GROUP )
		{
			chr.nation = sti(chr.oldnation);
		}
	}
	else
	{
		Trace("SURR: " + chid + " is surrendering");
	}
	// TIH <--

	Trace("SURR: ship nation: " + sti(chr.nation) );

	int oldnat = sti(chr.nation);
	chr.oldnation = oldnat;
	chr.nation = NEUTRAL_NATION;
	chr.surrendered = true;
	chr.surrendered.seatime = GetSeaTime();

	//int oldrel = SeaAI_GetRelation(chridx, pchridx); // TIH moved below so we can properly check with group commander Aug31'06

	int i, j, tmpidx;
	object charsdone;
	aref tmpattr;
	string tmpstr;
	ref rGroup = Group_FindOrCreateGroup(SURR_GROUP);
	Group_SetType(SURR_GROUP, "trade");
	
	ref pchar = GetMainCharacter();
	int pchridx = GetMainCharacterIndex();
	ref schr;
	float tmped, origed;
	string ogroup = Ship_GetGroupID(&chr);
	
	// TIH --> Nov5'06
	// check to see if this ship is already in the surrendered group
	bool bAlreadySurrendered = false;
	if (ogroup == SURR_GROUP)
	{
		bAlreadySurrendered = true;
		Trace("SURR: Already Surrendered Before - setting bool to skip steps");
	}
	// if oldgroup is not set, lets set it
	if (!CheckAttribute(chr,"surrendered.oldgroup"))
	{
		chr.surrendered.oldgroup = ogroup;
	}
	Trace("SURR: ship oldgroup: " + chr.surrendered.oldgroup);
	// TIH <--

	ref rOGroup = Group_GetGroupByID(ogroup);
// KK -->
	//ref cmdr = Group_GetGroupCommanderR(rOGroup);
	int cmdridx = Group_GetGroupCommanderIndex(rOGroup); //sti(cmdr.index);
	if (cmdridx < 0) return;
	ref cmdr = GetCharacter(cmdridx);
// <-- KK
	bool cmdrchange = cmdridx == chridx;
	bool firstchar = Group_GetCharacterIndexR(rOGroup, 0) == chridx;
	int og_ships = Group_GetCharactersNumR(rOGroup);

	int oldrel = -1;
	if (sti(cmdr.index) > 0) oldrel = SeaAI_GetRelation(cmdridx, pchridx); // TIH moved here so we can check relation with group commander Sep3'06
	Trace("SURR: SeaAI_GetRelation cmdr relation: " + oldrel);

	// TIH --> dont do this if already had surrendered Nov5'06
	if ( !bAlreadySurrendered )
	{
		if( cmdrchange )
		{
			Trace("SURR: fleet commander change, numships in group: " + og_ships);
			if(og_ships>=2)
			{
				cmdridx = Group_GetCharacterIndexR(rOGroup, firstchar);
				cmdr = GetCharacter(cmdridx);
				Group_SetGroupCommander(ogroup, cmdr.id);
				DeleteAttribute(cmdr,"relation.UseOtherCharacter");
				Trace("SURR: commander changed to " + cmdr.id);
			}
			else
			{
				Group_DeleteGroup(ogroup); // KK
				Trace("SURR: no other ships to change commander to");
			}
		}
	}
	// TIH <--

	// TIH --> moved this from above to here, no need to update relations until after a possible commander change Nov5'06
	UpdateRelations();
	RefreshBattleInterface();
	// TIH <--

	//las AI_MESSAGE_SHIP_CHANGE_GROUP char groupid

	// if no enemy ships near, assume player made this ship surrender and set this nation hostile to player 05-09-14 -->
	// commented out, will do find under lastballcharacter
	//float dt;// not used anymore, but left here because its usage is commented below
	/*if(FindClosestShipofRel(chridx, &dt, RELATION_ENEMY) == -1) 
	{
		// RM -->
		SetCharacterRelationBoth(GetMainCharacterIndex(), sti(cmdr.index), RELATION_ENEMY);
		//UpdateRelations();

		//RefreshBattleInterface();
		//if (bIslandLoaded) { SetNationRelation2MainCharacter(sti(cmdr.nation), RELATION_ENEMY); }
		AttackRMRelation(GetMainCharacter(), oldnat);
		//if(GetRMRelation(GetMainCharacter(), sti(cmdr.nation)) > REL_AFTERATTACK) ChangeCharacterReputation(GetMainCharacter(), -10); // handled in AttackRM
		// RM <--
		oldrel = RELATION_ENEMY;
	}*/
	//<--
	
	// TIH --> don't do any of this if ship had surrendered already Nov5'06
	if ( !bAlreadySurrendered )
	{
		// do morale boost
		int killidx = -1;
		
		if(CheckAttribute(chr,"ship.lastballcharacter"))
		{
			killidx = sti(chr.Ship.LastBallCharacter);
			if(killidx == GetMainCharacterIndex()) // if player shoots this ship, make this nation and player hostile. 05-09-14
			{
				if (sti(cmdr.index) > 0) SetCharacterRelationBoth(GetMainCharacterIndex(), sti(cmdr.index), RELATION_ENEMY);// TIH added index check Sep3'06
				AttackRMRelation(GetMainCharacter(), oldnat);
				oldrel = RELATION_ENEMY;
			}
			tmpstr = killidx;
			charsdone.(tmpstr) = true;
			ref rKillerCharacter = GetCharacter(killidx);
			tmped = 0; if(CheckAttribute(rKillerCharacter,"seatime.enemydead")) tmped = stf(rKillerCharacter.seatime.enemydead);
			origed = makefloat(GetCharacterShipClass(rKillerCharacter)) / makefloat(GetCharacterShipClass(chr)) * CLASS_SCALAR_FOR_MORALE;
			tmped += origed;
			rKillerCharacter.seatime.enemydead = tmped;
		}
	
		Trace("SURR: done lbc boost, now cycle through ships and forts");
		for(i = 0; i < iNumShips; i++)
		{
			if(ships[i] == -1) continue;
			schr = GetCharacter(ships[i]);
			//tmpstr = Ship_GetGroupID(&schr);
// KK -->
			if (Ship_GetGroupID(&schr) == ogroup) {
				SetCharacterRelationBoth(GetMainCharacterIndex(), ships[i], GetNationRelation2MainCharacter(sti(schr.nation)));
			}
// <-- KK
			if(SeaAI_GetRelation(Ships[i], chridx) == RELATION_ENEMY && ships[i] != killidx)
			{
				origed = makefloat(GetCharacterShipClass(&schr)) / makefloat(GetCharacterShipClass(&chr)) * CLASS_SCALAR_FOR_MORALE;
				tmped = 0; if(CheckAttribute(schr,"seatime.enemydead")) tmped = stf(schr.seatime.enemydead);
				tmped += origed * CLASS_SCALAR_FOR_MORALE_ORIG_TO_OTHER + makefloat(GetCharacterShipClass(schr)) / makefloat(GetCharacterShipClass(chr)) * CLASS_SCALAR_FOR_MORALE_OTHER;
				schr.seatime.enemydead = tmped;
			}
			//SetCharacterRelationBoth(Ships[i], cmdridx, oldrels[i]);
			//SetCharacterRelationBoth(Ships[i], chridx, RELATION_NEUTRAL);
			tmpstr = Ships[i];
			charsdone.(tmpstr) = true;
			//trace("did " + schr.id);
		}
		if(CheckAttribute(pchar,"seaAI.update.forts.l0"))
		{
			makearef(tmpattr, pchar.seaAI.update.forts);
			for(i = 0; i < GetAttributesNum(tmpattr); i++)
			{
				tmpstr= "l" + i;
				tmpidx = sti(tmpattr.(tmpstr).idx);
				if(tmpidx == -1) continue; // 04-12-06
				tmpstr = tmpidx;
				charsdone.(tmpstr) = true;
				//SetCharacterRelationBoth(tmpidx, chridx, RELATION_NEUTRAL);
			}
		}
	}
	// TIH <--

	//int tmpLangFileID = LanguageOpenFile("interface_strings.txt"); // KK

	// TIH --> bandaide until this whole process is handled better Jul22'06
	if ( !bReleasedFree ) {
		if(oldrel == RELATION_ENEMY) {
			TraceAndLog(TranslateString("","Sir, the") + " " + chr.ship.name + " " + TranslateString("","has struck her colors!"));
			PlaySound("interface\notebook.wav");
			//PlaySound("objects\shipcharge\hurrah.wav"); 
			PlaySound("objects\abordage\abordage_wining.wav"); 
		} else {
			TraceAndLog(TranslateString("","Sir, the") + " " + chr.ship.name + " " + TranslateString("","has struck her colors to the enemy!"));
		}
	}
	// TIH <--

	Trace("SURR: setting up character group");

	// set char to new group - doesn't work
	// use this, it's the only way I found to ACTUALLY change relation.
	SeaAI_SetOfficer2ShipAfterAbordage(&chr, &chr);
	DeleteAttribute(chr,"relation.UseOtherCharacter");
	Group_ChangeCharacter(SURR_GROUP, chr.id);
	Group_SetGroupCommander(SURR_GROUP, chr.id);
	Group_DeleteAtEnd(SURR_GROUP);
	
	Trace("SURR: group change done");

	// NO NO NO. Needed for fleets. 05-05-02 if(og_ships<= 1) Group_DeleteGroup(ogroup);
	UpdateRelations();
	RefreshBattleInterface();
	

	Ship_SetTaskDrift(PRIMARY_TASK, chridx);
	Ship_SetTaskDrift(SECONDARY_TASK, chridx);
	
	// TIH --> don't do any of this if ship had surrendered already Nov5'06
	if ( !bAlreadySurrendered )
	{
		if(!cmdrchange)
		{
			Trace("SURR: no cmdrchange, so update relations for ships to neutral");
			for(i = 0; i < GetAttributesNum(charsdone); i++)
			{
				/*trace("for shipslot " + i);
				for(j = 0; j < Group_GetCharactersNumR(rGroup); j++)
				{
					tmpidx = Group_GetCharacterIndexR(rGroup, j);
					SetCharacterRelationBoth(Ships[i], tmpidx, RELATION_NEUTRAL);
					trace("set relation to neutral between ship " + i + " and " + Characters[tmpidx].id);
					if(!i) SetCharacterRelationBoth(pchridx, tmpidx, RELATION_NEUTRAL);
				}*/
				tmpidx = sti(GetAttributeName(GetAttributeN(charsdone,i)));
				if (tmpidx > 0) SetCharacterRelationBoth(tmpidx, chridx, RELATION_NEUTRAL);// TIH added index check Sep3'06
				
			}
			SetCharacterRelationBoth(pchridx, chridx, RELATION_NEUTRAL);
		}
	}
	// TIH <--
	
	/*aref aShips, aShip;
	makearef(aShips, pchar.SeaAI.Update.Ships);
	int iShipsNum = GetAttributesNum(aShips);
	for (i=0; i<iShipsNum; i++)
	{
		aShip = GetAttributeN(aShips, i);
		if(sti(aShip.idx) == chridx) { aShip.relation = RELATION_NEUTRAL; break; }
	}*/
	//SetCharacterRelationBoth(cmdridx, pchridx, oldrel);
	/*if(og_ships >1)
	{
		for(i = 0; i < GetAttributesNum(ogrouprels); i++)
		{
			group = Group_GetGroupByID(GetAttributeName(GetAttributeN(ogrouprels, i)));
			if(!CheckAttribute(&group,"id")) { continue; }
			tmpidx = -1;
			if(CheckAttribute(group,"maincharacter") && group.maincharacter != "") tmpidx = sti(group.MainCharacter);
			if(tmpidx != -1)
			{
				SetCharacterRelationBoth(cmdridx, tmpidx, sti(GetAttributeValue(GetAttributeN(ogrouprels, i))));
			}
		}
	}*/

	UpdateRelations();
	RefreshBattleInterface();
	// check section
	/*
	for(i = 0; i < iNumShips; i++)
	{
		trace("for shipslot " + i + ", " + Characters[Ships[i]].id +", sai relation is " + SeaAI_GetRelation(chridx, Ships[i]) + ", normal is " + GetRelation(chridx, Ships[i]));
	}
	*/
	//LanguageCloseFile(tmpLangFileID);
	Trace("SURR: Ship_Surrender complete");
}



// NK can qty 05-04-18
// sets char.ship.cannons.borts.(arc).curqty based on *.qty and current crew
void Ship_SetCannonQtyByCrew(ref rCharacter)
{
	//bool ismch = sti(rCharacter.index) == GetMainCharacterIndex();
	if(!GetCannonQuantity(&rCharacter)) return;
	aref arship; makearef(arship, rCharacter.ship);
	int crewQuan = GetMinCrewQuantity(&rCharacter);//MAXIMUS
	if(crewQuan==0) crewQuan = 1;
	float crewratio = makefloat(GetCrewQuantity(&rCharacter)) / makefloat(crewQuan * 5); //yes, not max. This means multipliers on min/max crew make a difference here.
	//if(ismch) logit("doing setcancrew, ratio " + crewratio);
	ref rship = GetShipByType(GetCharacterShipType(rCharacter));
	int fullqty = GetLocalShipAttrib(&arship, rship, "MaxCanQty");
	crewratio = crewratio * makefloat(fullqty) / makefloat(GetCannonQuantity(&rCharacter)); // so if can qty decreases, not as much crew needed.
	int curqty = 0;
	if( crewratio < MIN_CREW_FOR_ALL_GUNS)
	{
		float canqty = clampf(Bring2Range(0.01, 1.0, 0.01, MIN_CREW_FOR_ALL_GUNS, crewratio));
		//if(ismch) logit("can qty ratio " + canqty);

		curqty = sti(rCharacter.ship.Cannons.Borts.cannonf.qty);
		if(curqty > 0) curqty = iclamp(1, curqty, makeint(canqty * curqty));
		rCharacter.ship.Cannons.Borts.cannonf.curqty = curqty;

		curqty = sti(rCharacter.ship.Cannons.Borts.cannonb.qty);
		if(curqty > 0) curqty = iclamp(1, curqty, makeint(canqty * curqty));
		rCharacter.ship.Cannons.Borts.cannonb.curqty = curqty;

		curqty = sti(rCharacter.ship.Cannons.Borts.cannonl.qty);
		if(curqty > 0) curqty = iclamp(1, curqty, makeint(canqty * curqty));
		rCharacter.ship.Cannons.Borts.cannonl.curqty = curqty;

		curqty = sti(rCharacter.ship.Cannons.Borts.cannonr.qty);
		if(curqty > 0) curqty = iclamp(1, curqty, makeint(canqty * curqty));
		rCharacter.ship.Cannons.Borts.cannonr.curqty = curqty;
	}
	else
	{
		//if(ismch) logit("reset to max canqty");
		rCharacter.ship.Cannons.Borts.cannonf.curqty = rCharacter.ship.Cannons.Borts.cannonf.qty;
		rCharacter.ship.Cannons.Borts.cannonb.curqty = rCharacter.ship.Cannons.Borts.cannonb.qty;
		rCharacter.ship.Cannons.Borts.cannonl.curqty = rCharacter.ship.Cannons.Borts.cannonl.qty;
		rCharacter.ship.Cannons.Borts.cannonr.curqty = rCharacter.ship.Cannons.Borts.cannonr.qty;
	}
}
// NK <--

// TIH --> Nov13'06
// New common function for determining if cannons are firing, and need to be reset for cannon loss restriction control.
// The 'reset' variable will allow a forced purge of attributes and reset of curqty (for adding ships to sea).
// See the detailed explanation in AIBalls.c 'Ball_AddBall' function for more.
void Ship_CannonsFiringCheck(ref rCharacter, bool reset) {

	string 	quadStr = "";
	bool 	isFiring = false;
	for (int quad=0; quad < 4; quad++) {
		quadStr = GetQuadName(quad);
		if(CheckAttribute(rCharacter, "Ship.Cannons.Borts."+ quadStr +".deleteme"))
		{
			isFiring = true;
			// 'numfired' will always exist if 'deleteme' exists, no need to CheckAttribute on it.
			if ( sti(rCharacter.Ship.Cannons.Borts.(quadStr).numfired) >= sti(rCharacter.Ship.Cannons.Borts.(quadStr).deleteme) || reset) 
			{
				DeleteAttribute(rCharacter, "Ship.Cannons.Borts."+ quadStr +".deleteme");
				DeleteAttribute(rCharacter, "Ship.Cannons.Borts."+ quadStr +".numfired");
			}
		}
	}
	if(!isFiring || reset) { Ship_SetCannonQtyByCrew(&rCharacter); } // NK reset curqty of cannon in each arc based on crew qty.

}
// TIH <--

void Ship_CheckSituation()
{
	ref 	rShip;
	//NK 04-09-15 comment out unneeded vars.
		ref rMainCharacter = GetMainCharacter();
	aref	rCharacter = GetEventData();
	aref	rShipObject = GetEventData();

	//int RelNat = GetNationRelation2MainCharacter(sti(rCharacter.nation));

	// NK can qty 05-04-18 -->
	// this will delete the numfired attribute after the deleteme counter hits 0. deleteme is reset whenever a gun is fired.
	// so in order to not reset numfired early, we have to do this.
	// TIH --> Nov13'06
	// Unfortunately the intent was there, but the execution didn't work right. This has been rewritten to work now.
	// See the detailed explanation in AIBalls.c 'Ball_AddBall' function for more.
	Ship_CannonsFiringCheck(&rCharacter, false);
	/*
	int nfdelay;
	bool isfiring = false;
	if(CheckAttribute(rCharacter, "ship.Cannons.Borts.cannonf.deleteme"))
	{
		isfiring = true;
		nfdelay = sti(rCharacter.ship.Cannons.Borts.cannonf.deleteme);
		nfdelay--;
		rCharacter.ship.Cannons.Borts.cannonf.deleteme = nfdelay;
		if(nfdelay <= 0)
		{
			DeleteAttribute(rCharacter, "ship.Cannons.Borts.cannonf.deleteme");
			DeleteAttribute(rCharacter, "ship.Cannons.Borts.cannonf.numfired");
		}
	}
	if(CheckAttribute(rCharacter, "ship.Cannons.Borts.cannonb.deleteme"))
	{
		isfiring = true;
		nfdelay = sti(rCharacter.ship.Cannons.Borts.cannonb.deleteme);
		nfdelay--;
		rCharacter.ship.Cannons.Borts.cannonb.deleteme = nfdelay;
		if(nfdelay <= 0)
		{
			DeleteAttribute(rCharacter, "ship.Cannons.Borts.cannonb.deleteme");
			DeleteAttribute(rCharacter, "ship.Cannons.Borts.cannonb.numfired");
		}
	}
	if(CheckAttribute(rCharacter, "ship.Cannons.Borts.cannonl.deleteme"))
	{
		isfiring = true;
		nfdelay = sti(rCharacter.ship.Cannons.Borts.cannonl.deleteme);
		nfdelay--;
		rCharacter.ship.Cannons.Borts.cannonl.deleteme = nfdelay;
		if(nfdelay <= 0)
		{
			DeleteAttribute(rCharacter, "ship.Cannons.Borts.cannonl.deleteme");
			DeleteAttribute(rCharacter, "ship.Cannons.Borts.cannonl.numfired");
		}
	}
	if(CheckAttribute(rCharacter, "ship.Cannons.Borts.cannonr.deleteme"))
	{
		isfiring = true;
		nfdelay = sti(rCharacter.ship.Cannons.Borts.cannonr.deleteme);
		nfdelay--;
		rCharacter.ship.Cannons.Borts.cannonr.deleteme = nfdelay;
		if(nfdelay <= 0)
		{
			DeleteAttribute(rCharacter, "ship.Cannons.Borts.cannonr.deleteme");
			DeleteAttribute(rCharacter, "ship.Cannons.Borts.cannonr.numfired");
		}
	}
	if(!isfiring) Ship_SetCannonQtyByCrew(&rCharacter); // reset curqty of cannon in each arc based on crew qty.
	*/
	// TIH <--
	// NK <--

	if (sti(rCharacter.index) == GetMainCharacterIndex()) { return; }
	
	//aref	rSituation;
	//makearef(rSituation, rCharacter.SeaAI.Update.Situation);
	//trace("*****For Char " + rCharacter.id + " of ship " + rCharacter.ship.name + " update.sit = ");
	//DumpAttributes(rSituation);
	
	Ship_CheckMorale(sti(rCharacter.index)); // NK 04-09-16

	// NK have AI use light repair 04-09-16-->
	if(GetCharacterPerkUsing(rCharacter,"InstantRepair"))
	{	if(GetHullPercent(rCharacter)<50.0 || GetSailPercent(rCharacter)<50.0)
		{
			ActivateCharacterPerk(rCharacter,"InstantRepair");
			ActivateSpecRepair(rCharacter,1);
		}
	}
	else
	{
		if(GetCharacterPerkUsing(rCharacter,"LightRepair"))
		{	if(GetHullPercent(rCharacter)<10.0 || GetSailPercent(rCharacter)<10.0)
			{
				ActivateCharacterPerk(rCharacter,"LightRepair");
				ActivateSpecRepair(rCharacter,0);
			}
		}
	}
	// NK <--

	rShip = GetShipByType(GetCharacterShipType(rCharacter)); // PS
	// NK -->
	if(CheckAttribute(rCharacter,"surrendered")) return; // NK surrender 05-04-20
//	bool bHostile = NationsRelations2MainCharacter[sti(rCharacter.nation)] == RELATION_ENEMY;
//	added by KAM -->
	bool bHostile;
	if (sti(rCharacter.nation) >= 0 && sti(rCharacter.nation) < MAX_NATIONS)	// LDH fix for nation < 0
	{
		bHostile = NationsRelations2MainCharacter[sti(rCharacter.nation)] == RELATION_ENEMY;
	}
	else
	{
		bHostile = false;
	}
//	<-- added by KAM

	if(SeaAI_GetRelation(sti(rCharacter.index), GetMainCharacterIndex()) == RELATION_ENEMY) bHostile = true;
	// move these here 04-09-16 NK
	float HPPercent = stf(rCharacter.Ship.HP) / makefloat(GetCharacterShipHP(rCharacter));
	int MinCrewQuantity = GetMinCrewQuantity(rCharacter);
	int CrewQuantity = sti(rCharacter.Ship.Crew.Quantity);
	if(HPPercent < HULL_PCT_CHAIN  || CrewQuantity < MinCrewQuantity) // move this here
	{
		Ship_SetTaskRunaway(SECONDARY_TASK,sti(rCharacter.index));
	}
	// NK 05-04-24 set attack
	string sGroupID = Ship_GetGroupID(rCharacter);
	ref rGroup = Group_FindOrCreateGroup(sGroupID);
	string sGroupType = Group_GetTypeR(rGroup);
	ref cmdr = Group_GetGroupCommanderR(rGroup);
	if(CheckAttribute(cmdr,"index"))
	{
		if(cmdr.index == rCharacter.index && sti(rGroup.task) == AITASK_MOVE && sGroupType != "trade")
		{
			float tdist;
			int tmpt = FindClosestShipofRel(sti(rCharacter.index), &tdist, RELATION_ENEMY);
			if(tmpt != -1) Group_SetTaskAttack(sGroupID, Ship_GetGroupID(GetCharacter(tmpt)));
		}
	}
	// NK <--

	//put big checkattr if here so we skip this stuff if already done
	if(!CheckAttribute(rCharacter,"ship.attackpcharset"))
	{
		if (bHostile)
		{
			//string sGroupID = Ship_GetGroupID(rCharacter);
			//string sGroupType = Group_GetType(sGroupID);
			//int SailsPercent = rCharacter.Ship.SP;
			

			SetCharacterRelationBoth(sti(rCharacter.index), GetMainCharacterIndex(), RELATION_ENEMY);
			UpdateRelations();

			if(sGroupType != "trade") Group_SetTaskAttack(sGroupID, PLAYER_GROUP);
			//if(rCharacter.fantomtype == "trade") Ship_SetTaskRunaway(SECONDARY_TASK, sti(rCharacter.index)); // NK 05-04-24
			rCharacter.ship.attackpcharset = true; //04-09-16 so we do this once.
			//this means ignore other ships! So commented out. NK 04-09-15
			/*if((SailsPercent < 50) || (HPPercent > 20) && (HPPercent < 50))
			{
				Ship_SetTaskAttack(SECONDARY_TASK, sti(rCharacter.index), GetMainCharacterIndex());
			}*/
			//move flee section out
		}
		// NK 04-09-15 clean this stuff up.
		else
		{
			if(HasSubStr(rCharacter.id, "Coastal_Captain")) // boal fix 29.11.03, NK use findsubstr 04-09-16
			{
				SetCharacterRelationBoth(sti(rCharacter.index), GetMainCharacterIndex(), RELATION_ENEMY);
				UpdateRelations();
				rCharacter.ship.attackpcharset = true; // 04-09-16 so we do this once
				
			}
		}
	}
	// NK 04-09-15 redo check situation and ammo loadout
	bool setammo = !CheckAttribute(rCharacter,"ship.cannons.type");
	if(!setammo) setammo = sti(rCharacter.ship.cannons.type) == CANNON_TYPE_NONECANNON; // NK to avoid checking ammo on cannonless ships 04-09-18
	int targetidx = -1;
	bool foundtgt = false;
	// check some tasks
	if (CheckAttribute(rCharacter, "SeaAI.Task"))
	{
		switch (sti(rCharacter.SeaAI.Task))
		{
			case AITASK_BRANDER:
				ref rCharacter2Brander = GetCharacter(sti(rCharacter.SeaAI.Task.Target));
				ref rBaseShip = GetShipByType(GetCharacterShipType(rCharacter2Brander)); // PS
				float fBranderDistance = 30.0 + (7.0 - stf(rBaseShip.Class)) * 40.0;
				float fDistance = Ship_GetDistance2D(rCharacter, rCharacter2Brander);
				if (fBranderDistance > fDistance && fDistance >= 0) // NK for when dist < 0 05-04-15
				{
					// fire ship
					int iNumFirePlaces = 0;
					SendMessage(rShipObject, "le", MSG_SHIP_GET_NUM_FIRE_PLACES, &iNumFirePlaces);
					for (int i=0; i<iNumFirePlaces / 3; i++)
					{
						PostEvent(SHIP_ACTIVATE_FIRE_PLACE, rand(10000), "ialsf", rShipObject, rCharacter, i, "ship_onfire", 30.0);
					}
					rCharacter.fireplaces.brander = true; // NK 05-04-18
					PostEvent(SHIP_BRANDER_DETONATE, 10000, "l", sti(rCharacter.index));

					Ship_SetTaskNone(SECONDARY_TASK, sti(rCharacter.index));
				}
				//Trace("test1 rCharacter2Brander = " + rCharacter2Brander.index);
			break;

			// NK -->
			case AITASK_ATTACK:
				if(!setammo) // NK to avoid checking ammo on cannonless ships 04-09-18
				{
					int temptgt = sti(rCharacter.SeaAI.task.target); //do this since we're already finding target above. 04-09-16
					if(temptgt >= 0 && temptgt >= TOTAL_CHARACTERS)
					{
						if(!LAi_IsDead(GetCharacter(temptgt)))
						{
							targetidx = temptgt; //set target to what we're attacking. If not, find a new one below.
							foundtgt = true;
							if(CheckAttribute(Characters[targetidx],"surrendered"))
							{
								if(!IsCompanion(Characters[targetidx]))
								{
								targetidx = FindClosestShipofRel(sti(rCharacter.index), RELATION_ENEMY);
								if(targetidx != -1) Ship_SetTaskAttack(SECONDARY_TASK, sti(rCharacter.index), targetidx);
								else Ship_SetTaskRunaway(SECONDARY_TASK, sti(rCharacter.index));
								}
							}
						}
					}
				}
			break;
		}
	}
	if(!setammo)
	{
//================================================ new tgt if cur tgt surrendered 05-04-23 ================================================//
		if(CheckAttribute(rCharacter, "ship.lasttgt"))
		{
			if(CheckAttribute(Characters[sti(rCharacter.ship.lasttgt)],"surrendered")) DeleteAttribute(&rCharacter, "ship.lasttgt");
		}
		if(!foundtgt && !IsCompanion(rCharacter)) targetidx = FindOurTarget(sti(rCharacter.index));
//!IsCompanion(rCharacter) added by MAXIMUS [abordage MOD]
//=========================================================================================================================================//

		//called in morale section at top - targetidx = FindOurTarget(sti(rCharacter.index)); // finds ship we last fired on, or if not nearest hostile ship
		if(targetidx >= 0 && targetidx < TOTAL_CHARACTERS)
		{
			Ship_AmmoUpdate(sti(rCharacter.index), targetidx);
			setammo = true;
		}
	}
}


// NK
//finds ratio of friend power vs. foe power
float FindPowerRatio(int idx)
{
	int num = FindNearShips(idx);
	ref chr = GetCharacter(idx);
	int sidx = sti(chr.curshipnum); // change to array 05-06-27
	//if(num == 0) return 0.0;
	string tstr = "rel"+RELATION_ENEMY;
	if(!CheckAttribute(NearShips[sidx],tstr+".0")) return 0.0;
	float epower = FindPowerOfNearShips(idx, RELATION_ENEMY);
	float fpower = FindPowerOfNearShips(idx, RELATION_FRIEND);
	if(epower < 0.5) epower = 1.0;
	if(fpower < 0.5) fpower = 1.0;
	return epower / fpower;
}

float FindPowerOfNearShips(int idx, int rel)
{
	int num = FindNearShips(idx);
	ref chr = GetCharacter(idx);
	int sidx = sti(chr.curshipnum); // change to array 05-06-27
	string tstr = "rel"+rel;
	if(num <= 0 || !CheckAttribute(NearShips[sidx],tstr+".0")) return 0.0;
	float power = 0.0;
	string tstr2;
	for(int i = 0; i < sti(NearShips[sidx].(tstr).qty); i++)
	{
		tstr2 = i;
		chr = GetCharacter(sti(NearShips[sidx].(tstr).(tstr2).idx));
		power += sqrt(GetCurrentShipHP(&chr) * GetCannonCurQuantity(&chr)) / (1.0 + pow2(stf(NearShips[sidx].(tstr).(tstr2).dist)/100.0, 1.5));
	}
	if(rel == RELATION_FRIEND)
	{
		chr = GetCharacter(idx);
		tstr2 = 0;
		power += sqrt(GetCurrentShipHP(&chr) * GetCannonCurQuantity(&chr)) / (1.0 + pow2(stf(NearShips[sidx].all.(tstr2).dist)/100.0, 1.5)); // nearest ship
	}
	return power;
}
// added 05-04-20
//finds closest ship ('s captain) of specified group. returns -1 if none available
int FindClosestShipOfGroup(int idx, ref retdist, string groupid)
{
	int tgt = FindNearShips(&idx);
	ref chr = GetCharacter(idx);
	int sidx = sti(chr.curshipnum); // change to array 05-06-27
	if(tgt == 0) return -1;
	string tstr = groupid+".0";
	if(!CheckAttribute(NearShips[sidx],tstr)) return -1;
	retdist = stf(NearShips[sidx].(tstr).dist);
	return sti(NearShips[sidx].(tstr).idx);
}


// finds the index of the character whom we last fired at, or, if nothing there, the closest hostile ship.
int FindOurTarget(int idx)
{
	ref chr = GetCharacter(idx);
	if(CheckAttribute(chr,"ship.lasttgt")) //try to find who ship last attacked
	{
		int lhidx = sti(chr.ship.lasttgt);
		if(lhidx != -1 && lhidx < TOTAL_CHARACTERS)
		{
			if(!LAi_IsDead(GetCharacter(lhidx)))
			{
				//retdist = Ship_GetDistance2D(GetCharacter(lhidx), chr); // so we return a distance.
				return lhidx;
			}
			else
			{
				DeleteAttribute(chr,"ship.lasttgt"); //if dead, don't check again until next tgt
			}
		}
	}
	//otherwise find closest hostile ship
	float retdist = -1;
	return FindClosestShipofRel(idx, &retdist, RELATION_ENEMY);
}

// finds the index of the character whose ship of relation rel to us (char index idx) is nearest, and returns it. -1 if none available.
int FindClosestShipofRel(int idx, ref retdist, int rel)
{
	ref chr = GetCharacter(idx);
	if(!CheckAttribute(chr,"curshipnum")) return -1; // MAXIMUS 22.12.06
	int sidx = sti(chr.curshipnum); // change to array 05-06-27
	int tgt = FindNearShips(&idx);
	if(tgt == 0) return -1;
	string tstr = "rel"+rel+".0";
	if(!CheckAttribute(NearShips[sidx],tstr)) return -1;
	retdist = stf(NearShips[sidx].(tstr).dist);
	return sti(NearShips[sidx].(tstr).idx);
}

//fills the NearShips object for use in other functions. 05-04-22
int FindNearShips(int idx)
{
	ref chr = GetCharacter(idx);
	int sidx = sti(chr.curshipnum); // change to array 05-06-27
	aref retobj; makearef(retobj, NearShips[sidx]);
	int st = GetSeaTime();
	if(CheckAttribute(retobj,"lastchar"))
	{
		if(sti(retobj.lastchar) == idx)
		{
			int lt = sti(retobj.lasttime);
			if(lt > st - 2) return sti(retobj.all.qty);
		}
	}
	
	ref achr;
	//bool checkrel = rel != -1;
	int achridx;
	//int attacker = -1;
	//float distances[1];
	int place = 0;
	string tstr;
	object tmpclose;
	tmpclose.t = "";
	aref closeobj;
	makearef(closeobj, tmpclose.t);
	float tmp;
	//bool check = false;
	if(idx == -1 || idx > TOTAL_CHARACTERS) return 0; // 04-12-06
	for(int i = 0; i < iNumShips; i++)
	{
		achridx = Ships[i];
		if(achridx == idx || achridx == -1) continue; // 04-12-06
		achr = GetCharacter(achridx);
		//below unneeded, we can just check relation...
		/*if(sti(achr.seaAI.task) == AITASK_ATTACK)
		{
			if(sti(achr.seaAI.task.target) == idx)
			{
				check = true;
			}
		}
		if(IsCompanion(achr) && !IsCompanion(chr))
		{
			if(GetRelation(idx, achridx) == RELATION_ENEMY) check = true;
		}
		if(check)
		{
			tmp = Ship_GetDistance2D(achr, chr);
			if(distance > tmp) { distance = tmp; attacker = achridx; }
		}*/
		//new code:
		if(!CheckAttribute(achr,"nation")) continue; // 04-12-06 check for nation
		else { if(sti(achr.nation) == -1 || sti(achr.nation) >= MAX_NATIONS) { continue; } } // 04-12-06 check for nation bounds
		//relchk = SeaAI_GetRelation(idx, achridx) == rel || !checkrel;
		if(!LAi_IsDead(achr)) //04-12-06 stupid bug; forgot SeaAI_.
		{
			//if(place+1 < GetArraySize(&distances)) SetArraySize(&distances, place+1);
			tstr = place;
			closeobj.(tstr).idx = achridx;
			closeobj.(tstr).id = achr.id;
			closeobj.(tstr).dist = Ship_GetDistance2D(achr, chr);
			closeobj.(tstr).group = Ship_GetGroupID(&achr);
			closeobj.(tstr).rel = SeaAI_GetRelation(idx, achridx);
			if(CheckAttribute(achr,"surrendered")) closeobj.(tstr).rel = RELATION_NEUTRAL; // important fix for surrender func! 05-04-23
			place++;
			//if(distance > tmp && tmp >= 0) { distance = tmp; attacker = achridx; } // NK for when dist < 0 05-04-15
		}
	}
	// 04-09-22 check forts too
	ref pchar = GetMainCharacter();
	if(CheckAttribute(pchar,"seaAI.update.forts.l0"))
	{
		
		aref updforts; makearef(updforts, pchar.seaAI.update.forts);
		string tmpa;
		for(i = 0; i < GetAttributesNum(updforts); i++)
		{
			tmpa= "l" + i;
			achridx = sti(updforts.(tmpa).idx);
			if(achridx == idx || achridx == -1) continue; // 04-12-06
			achr = GetCharacter(achridx);
			if(!CheckAttribute(achr,"nation")) continue; // 04-12-06 check for nation
			else { if(sti(achr.nation) == -1 || sti(achr.nation) >= MAX_NATIONS) { continue; } } // 04-12-06 check for nation bounds
			//relchk = SeaAI_GetRelation(idx, achridx) == rel || !checkrel;
			if(!LAi_IsDead(achr)) //04-12-06 stupid bug; forgot SeaAI_.
			{
				//tmp = Ship_GetDistance2D(achr, chr);
				//if(distance > tmp && tmp >=0) { distance = tmp; attacker = achridx; } // NK for when dist < 0 05-04-15
				tstr = place;
				closeobj.(tstr).idx = achridx;
				closeobj.(tstr).id = achr.id;
				closeobj.(tstr).dist = Ship_GetDistance2D(achr, chr);
				closeobj.(tstr).group = Ship_GetGroupID(&achr);
				closeobj.(tstr).rel = SeaAI_GetRelation(idx, achridx);
				place++;
			}
		}
	}
	DeleteAttribute(retobj, "");
	retobj.all.qty = place;
	retobj.lastchar = idx;
	retobj.lasttime = st;
	if(!place) { return 0; }
	//trace("******for char " + chr.id + " of ship " + chr.ship.name + " we dump presort NearShips:");
	//DumpAttributes(closeobj);
	aref tmpar; makearef(tmpar, closeobj);
	fSortArray(&tmpar, "dist", 0, place-1);

	int r, relplace[3]; relplace[0] = 0; relplace[1] = 0; relplace[2] = 0;
	object groupplaces;
	string tstr2;
	aref curplace, aref arrplace;
	for(i = 0; i < place; i++)
	{
		tstr = i;
		makearef(arrplace, closeobj.(tstr));
		retobj.all.(tstr) = "";
		makearef(curplace, retobj.all.(tstr));
		CopyAttributes(curplace, arrplace);
		r = sti(arrplace.rel);
		tstr = "rel" + r + "." + relplace[r];
		relplace[r]++;
		retobj.(tstr) = "";
		makearef(curplace,retobj.(tstr));
		CopyAttributes(curplace, arrplace);
		tstr = arrplace.group;
		r = 0;
		if(CheckAttribute(retobj,"groups."+tstr+".qty")) r = sti(retobj.groups.(tstr).qty);
		retobj.groups.(tstr).qty = r+1;
		tstr += "."+r;
		retobj.groups.(tstr) = "";
		makearef(curplace, retobj.groups.(tstr));
		CopyAttributes(curplace, arrplace);
	}
	// done above. retobj.all.qty = place;
	retobj.rel0.qty = relplace[0];
	retobj.rel1.qty = relplace[1];
	retobj.rel2.qty = relplace[2];
	//tstr = "all.0";
	//retdist = stf(retobj.(tstr).dist);
	return place;
	/*retdist = distance;
	return attacker;*/
}
// NK 05-04-22
// use scheffnow's sort code, generalized and set for attribute tree use.
// needs tree of: sortarray.X.things, where X is 0+, and things are any attributes. But all entries must have sortarray.X.sortthing, which is the thing we sort by.
// this is the float version; should work OK with ints too.
void fSortArray(aref sortarray, string sortthing, int intLowerPos, int intUpperPos)
{
	//trace("with L/U " + intLowerPos + " / " + intUpperPos + " we are sorting.");
	//dumpattributes(sortarray);
	float fCenterValue;
	int intCenterPos;
	int i;
	aref MinArr;
	aref MaxArr;
	object temp;
	temp.t = "";
	aref TmpArr; makearef(TmpArr, temp.t);
	string tstr;
	if (intLowerPos < intUpperPos)
	{
		tstr = intLowerPos
		makearef(MinArr,sortarray.(tstr));
		fCenterValue = stf(MinArr.(sortthing));
		intCenterPos   = intLowerPos;
		for(i=intLowerPos+1;i<=intUpperPos;i++)
		{
			tstr = i;
			makearef(MinArr,sortarray.(tstr));
			if (stf(MinArr.(sortthing)) < fCenterValue)
			{
				intCenterPos++;
				tstr = intCenterPos;
				makearef(MinArr,sortarray.(tstr));
				tstr = i;
				makearef(MaxArr,sortarray.(tstr));
				//makearef(TmpArr,sortarray[lastpos-1]);
				DeleteAttribute(TmpArr,""); // just in case
				CopyAttributes(TmpArr,MinArr);
				CopyAttributes(MinArr,MaxArr);
				CopyAttributes(MaxArr,TmpArr);
			}
		}
		tstr = intLowerPos;
		makearef(MinArr,sortarray.(tstr));
		tstr = intCenterPos;
		makearef(MaxArr,sortarray.(tstr));
		//makearef(TmpArr,sortarray[lastpos-1]);
		DeleteAttribute(TmpArr,"");
		CopyAttributes(TmpArr,MinArr);
		CopyAttributes(MinArr,MaxArr);
		CopyAttributes(MaxArr,TmpArr);
		fSortArray(&sortarray, sortthing, intLowerPos,intCenterPos-1);
		fSortArray(&sortarray, sortthing, intCenterPos+1,intUpperPos);
	}
}


// finds min reload percentage
float Ship_FindMinChargeRatio(ref chr) 
{
	float min = 0.0;
	/*if(!CheckAttribute(chr,"ship.Cannons.Borts.cannonl.chargeratio"))
	{
		//trace("char " + chr.id + " not have chargeratio attribute!");
		return 0.0;
	}*/
	if(CheckAttribute(chr,"ship.Cannons.Borts.cannonl.chargeratio")) { if(min > stf(chr.ship.Cannons.Borts.cannonl.chargeratio)) min = stf(chr.ship.Cannons.Borts.cannonl.chargeratio); }
	if(CheckAttribute(chr,"ship.Cannons.Borts.cannonr.chargeratio")) { if(min > stf(chr.ship.Cannons.Borts.cannonr.chargeratio)) min = stf(chr.ship.Cannons.Borts.cannonr.chargeratio); }
	if(CheckAttribute(chr,"ship.Cannons.Borts.cannonf.chargeratio")) { if(min > stf(chr.ship.Cannons.Borts.cannonf.chargeratio)) min = stf(chr.ship.Cannons.Borts.cannonf.chargeratio); }
	if(CheckAttribute(chr,"ship.Cannons.Borts.cannonb.chargeratio")) { if(min > stf(chr.ship.Cannons.Borts.cannonb.chargeratio)) min = stf(chr.ship.Cannons.Borts.cannonb.chargeratio); }
	return min;
}

//will order change in charge if it's OK or if out of range, and will call find new ammo
void Ship_AmmoUpdate(int iUs, int iThem)
{ 
	ref rUs = GetCharacter(iUs); ref rThem = GetCharacter(iThem);
	ref rCannon = GetCannonByType(sti(rUs.Ship.Cannons.Type));
	float range = stf(rCannon.FireRange) * AIShip_isPerksUse(rUs.TmpPerks.LongRangeShoot, 1.0, 1.15);

	float distance = Ship_GetDistance2D(&rUs, &rThem);
	if(distance < 0) distance = 1000; // NK for when dist < 0 05-04-15
	bool change = false;
	if(!change) change = sti(rUs.Ship.Cannons.Charge.Type) >= GOODS_QUANTITY; //bugfix 04-09-18
	if(!change) { if(distance > range * OUTOFRANGE_SCL * stf(Goods[sti(rUs.Ship.Cannons.Charge.Type)].SpeedV0) && sti(rUs.Ship.Cannons.Charge.Type) != sti(ShipLookupTable.ammo.ship.Range.idx)) change = true; }
	if(!change && frnd() < 0.5) change = Ship_FindMinChargeRatio(rUs) <= MAX_TO_RELOAD && !CheckAttribute(rUs,"ship.changedammo");
	if(change)
	{
		int newammo = Ship_FindOtherBallType(iUs, iThem);
		if(newammo != -1 && newammo != sti(rUs.Ship.Cannons.Charge.Type))
		{
			//trace("changing ammo for " + rUs.id + " of " + rUs.ship.name + ", new charge: " + Goods[newammo].name + "; status fblr: " + rUs.ship.Cannons.Borts.cannonf.chargeratio + " " + rUs.ship.Cannons.Borts.cannonb.chargeratio + " " + rUs.ship.Cannons.Borts.cannonl.chargeratio + " " + rUs.ship.Cannons.Borts.cannonr.chargeratio);
			Ship_ChangeCharge(rUs, newammo);
			//Logit("Updating ammo for  " + rUs.name + " " + rUs.lastname + " (" + rUs.id + ") of the " + rUs.Ship.Name + " vs. " + rThem.name + " " + rThem.lastname + " (" + rThem.id + ") of the " + rThem.Ship.Name)
			//Logit("Will change ammo. New ammo " + Goods[newammo].name);
		}
	}
}

//finds new ammo for ship of Us firing on Them
int Ship_FindOtherBallType(int iUs, int iThem)
{
	ref rUs = GetCharacter(iUs);
	bool bHull, bRig, bCrew, bAll, bRange;
	string stype = "ship"; if(rUs.ship.type == SHIP_FORT_NAME) return GOOD_BOMBS; // forts use heated shot.
	int aHull, aRig, aCrew, aAll, aRange;
	aAll = sti(ShipLookupTable.ammo.(stype).All.idx);
	aRange = sti(ShipLookupTable.ammo.(stype).Range.idx);
	aHull = sti(ShipLookupTable.ammo.(stype).Hull.idx);
	aRig = sti(ShipLookupTable.ammo.(stype).Rig.idx);
	aCrew = sti(ShipLookupTable.ammo.(stype).Crew.idx);

	int iShipCannonsNum = GetCannonQuantity(rUs);
	bHull = GetCargoGoods(rUs,aHull) >= iShipCannonsNum;
	bRig = GetCargoGoods(rUs,aRig) >= iShipCannonsNum;
	bCrew = GetCargoGoods(rUs,aCrew) >= iShipCannonsNum;
	bAll = GetCargoGoods(rUs,aAll) >= iShipCannonsNum;
	bRange = GetCargoGoods(rUs,aRange) >= iShipCannonsNum;
	if(iThem == -1)
	{
		if(bRange) return aRange;
		if(bAll) return aAll;
		if(bHull) return aHull;
		if(bRig) return aRig;
		if(bCrew) return aCrew;
		return -1;
	}
	ref rThem = GetCharacter(iThem);
	ref rCannon = GetCannonByType(sti(rUs.Ship.Cannons.Type));
	//ref rShip = GetShipByType(GetCharacterShipType(rUs));
	float range = stf(rCannon.FireRange) * AIShip_isPerksUse(rUs.TmpPerks.LongRangeShoot, 1.0, 1.15);
	//float oSP = stf(rUs.Ship.SP) / 100.0;
	float oHP = 1.0; if(GetCharacterShipHP(rUs)) oHP = makefloat(GetCurrentShipHP(rUs)) / makefloat(GetCharacterShipHP(rUs));
	//float tSP = stf(rThem.Ship.SP) / 100.0;
	float tHP = 1.0; if(GetCharacterShipHP(rThem)) tHP = makefloat(GetCurrentShipHP(rThem)) / makefloat(GetCharacterShipHP(rThem));
	int oCrewQ = GetCrewQuantity(rUs);
	int oMinCQ = GetMinCrewQuantity(rUs);
	//int tCrewQ = GetCrewQuantity(rThem);
	//int tMinCQ = GetMinCrewQuantity(rThem);
	float distance = Ship_GetDistance2D(&rUs, &rThem);
	if(distance < 0) distance = 1000; // NK for when dist < 0 05-04-15
	
	float rHull, rRig, rCrew, rAll, rRange;
	rHull = 0; rRig = 0; rCrew = 0; rAll = 0; rRange = 0;

	//trace(".");
	//trace("for character " + rUs.id + " determining new ammo");

	if (bRange && distance >= range * MAXRANGE) //if > max range, don't bother checking anything else. We need max range ammo.
	{
		//trace("out of range. change to maxrange");
		return aRange;
	}

	if(tHP < TARGET_HULL_MIN && bHull)
	{
		rHull = stf(ShipLookupTable.ammo.(stype).Hull.range);
		if (distance < range * rAll && frnd() < HULLCH) //if chance succeeds, and their hull is an eggshell...we'll hammer it!
		{
			//trace("tgt hull");
			return aHull;
		}
	}

	if(bCrew)
	{
		rCrew = stf(ShipLookupTable.ammo.(stype).Crew.range);
		if (distance < range * rAll && frnd() < CREWCH_ALL) //if chance succeeds, and we can hammer their crew...let's!
		{
			//trace("tgt crew");
			return aCrew;
		}
	}

	if(bRig)
	{
		rRig = stf(ShipLookupTable.ammo.(stype).Rig.range);
		if (distance < range * rRig)
		{
			if(oHP < HULL_PCT_CHAIN || oCrewQ < oMinCQ) { return aRig; } //if we're trying to run, knock out their rigging!
			if(CheckAttribute(rUs,"ship.maxpseedZ") && CheckAttribute(rThem,"ship.maxpseedZ"))
			{
				if(stf(rUs.ship.MaxSpeedZ) + CHAIN_SPEEDDIFF < stf(rThem.ship.MaxSpeedZ) && frnd() < CHAIN_SPEEDDIFF_CHANCE) //if we're at a speed disadvantage, ditto
				{
					//trace("tgt sail");
					return aRig;
				}
			}
		}
	}

	if (bAll)
	{
		rAll = stf(ShipLookupTable.ammo.(stype).All.range);
		if (distance < range * rAll)
		{
			//trace("use best-allround");
			return aAll;
		}
	}
	//trace("no checks passed, will default");
	if(bRange) return aRange;
	if(bHull) return aHull;
	if(bRig) return aRig;
	if(bAll) return aAll;
	if(bCrew) return aCrew;
	// NK & Mith<--
	// updated 04-09-15
	return -1;
}
// NK <-- 04-09-15

// NK --> 04-09-16
//gets the current time in seconds (roughly!) since loaded sea.
int GetSeaTime()
{
	ref pchar = GetMainCharacter();
	if(!CheckAttribute(pchar,"seatime")) return 0;
	return sti(pchar.seatime);
}
// 04-09-21
int AddSeaTimeToCurrent()
{
	ref pchar = GetMainCharacter();
	int st = GetSeaTime();
	int minutes = 0;
	if(!CheckAttribute(pchar,"lastupdateseatime")) pchar.lastupdateseatime = 0;
	if(sti(pchar.lastupdateseatime) + 1 <= st/60)
	{
		int oldhour = GetHour();
		minutes = TIMESCALAR_SEA * (st/60 - sti(pchar.lastupdateseatime));
		AddTimeToCurrent(0, minutes);
		pchar.lastupdateseatime = (st/60);
		if(oldhour != GetHour() && WEATHER_UPDATE_AT_SEA)
		{
			sNewExecuteLayer = SEA_EXECUTE;
			sNewRealizeLayer = SEA_REALIZE;
			Whr_UpdateWeather(false);
		}
	}
	return minutes;
}
// NK <--

void Ship_ActivateFirePlace()
{
	if(LAi_IsBoardingProcess() || ownDeckStarted()) return; // NK bugfix for boarding
	aref	arShipObject = GetEventData();
	aref	arCharacter = GetEventData();
	if(GetCharacterShipType(arCharacter)==SHIP_BOAT || GetCharacterShipType(arCharacter)==SHIP_NOTUSED) return;//MAXIMUS
	int		iFirePlaceIndex = GetEventData();
	string	sSoundName = GetEventData();
	float	fFireTime = GetEventData();

	aref	arPos; makearef(arPos, arCharacter.Ship.Pos);

	int iSoundID = Play3DSoundComplex(sSoundName, stf(arPos.x), 0.0, stf(arPos.z), true, false);
	string tmpstr = iFirePlaceIndex; arCharacter.fireplaces.(tmpstr) = iSoundID; // NK 05-04-19
	SendMessage(arShipObject, "llsslf", MSG_SHIP_ACTIVATE_FIRE_PLACE, iFirePlaceIndex, "ship_smoke", "ship_fire", iSoundID, fFireTime);

	// ccc firedrill
	if(ENABLE_FIREDRILL == 1 && IsMainCharacter(arCharacter)) // so this runs only for the player // KK
	{
		// MAR18
		DeleteAttribute(arCharacter,"firedrill"); // deletes the markerattribute that starts firedrill
		if(FIREDAMAGE>1)	// MAR18
		{
			Log_SetStringToLog(TranslateString("","Fire! Execute firedrill procedure!")); // infomessage MAR18 // KK
			PlaySound("interface\notebook.wav"); // soundeffect MAR18
		}
	} // MAR18
	// ccc firedrill end
}

void Ship_BranderDetonate()
{
	ref		rCharacter = GetCharacter(GetEventData());

	ref		rBranderBaseShip = GetShipByID(rCharacter.Ship.Type); // PS
	float	fBranderDistance = 2.5 * (30.0 + (7.0 - stf(rBranderBaseShip.Class)) * 40.0);
	float	fMaxDamage = 1000.0 + (7.0 - stf(rBranderBaseShip.Class)) * 1000.0;

	aref	arShipObjects[64];
	int		iNumShips = 1;

	if (!FindClass(&arShipObjects[0], "ship")) { return; }// can't be
	while (FindClassNext(&arShipObjects[iNumShips])) { iNumShips++; }

	// enumerate ship and do damage
	for (int i=0; i<iNumShips; i++)
	{
		if(i==SHIP_BOAT || i==SHIP_NOTUSED) continue;//MAXIMUS
		int iCharacterIndex = -1;
		SendMessage(arShipObjects[i], "le", MSG_SHIP_GET_CHARACTER_INDEX, &iCharacterIndex);
		if (iCharacterIndex == sti(rCharacter.index)) { continue; }
		ref rOtherCharacter = GetCharacter(iCharacterIndex);
		ref	rOtherBaseShip = GetShipByID(rOtherCharacter.Ship.Type); // PS
		float fDistance = Ship_GetDistance2D(rCharacter, rOtherCharacter);
		if (fDistance > fBranderDistance || fDistance < 0) { continue; } // NK for when dist < 0 05-04-15
		float fDamageMultiply = Bring2Range(1.0, 0.0, 10.0, fBranderDistance, fDistance);
		Ship_ApplyHullHitpoints(rOtherCharacter, fMaxDamage * fDamageMultiply, KILL_BY_SPY, sti(rBranderBaseShip.index));

		// fire random fire places
		int iNumFirePlaces = 0;
		SendMessage(&arShipObjects[i], "le", MSG_SHIP_GET_NUM_FIRE_PLACES, &iNumFirePlaces);

		// add move impulse to ship
		float x = stf(rOtherCharacter.Ship.Pos.x) - stf(rCharacter.Ship.Pos.x);
		float z = stf(rOtherCharacter.Ship.Pos.z) - stf(rCharacter.Ship.Pos.z);
		float fLen = stf(rOtherBaseShip.Class) * fDamageMultiply * (1.0 / sqrt(x * x + z * z));
		SendMessage(&arShipObjects[i], "llffffff", MSG_SHIP_ADD_MOVE_IMPULSE, false, x * fLen, 0.0, z * fLen, 0.0, 0.0, 0.0);
	}

	Ship_Detonate(rCharacter, true, true);
}

float Ship_GetDistance2D(ref rCharacter1, ref rCharacter2)
{
	// NK temp bugfix
	/*if(!CheckAttribute(rCharacter1,"pos")) trace("Char " + rCharacter1 + " has no ship.pos!");
	if(!CheckAttribute(rCharacter2,"pos")) trace("Char " + rCharacter2 + " has no ship.pos!");*/
	if(!CheckAttribute(rCharacter1,"ship.pos") || !CheckAttribute(rCharacter2,"ship.pos")) return -1.0; // NK 05-04-15
	return GetDistance2D(stf(rCharacter1.Ship.Pos.x), stf(rCharacter1.Ship.Pos.z), stf(rCharacter2.Ship.Pos.x), stf(rCharacter2.Ship.Pos.z));
}
// NK Ship_FindOtherBallType moved up to be near what calls it. 04-09-15

// event: indicate that ball is not enough for fire for main character
void Ship_NotEnoughBalls()
{
	bNotEnoughBalls = GetEventData();
}

int Ship_GetCurrentBallsNum()
{
	aref aCharacter = GetEventData();
	return GetCargoGoods(aCharacter,sti(aCharacter.Ship.Cannons.Charge.Type));
}

void Ship_SailDamage()
{
	float x, y, z;
	int iCharacterIndex = GetEventData();
	x = GetEventData();
	y = GetEventData();
	z = GetEventData();
	Play3DSound("sail_damage", x, y, z);
	Characters[iCharacterIndex].seatime.lasthit = GetSeaTime(); // NK 04-09-16, to set time when last hit
	if(CheckAttribute(Characters[iCharacterIndex], ".ship.LastBallCharacter") && sti(Characters[iCharacterIndex].ship.LastBallCharacter) != -1) Characters[sti(Characters[iCharacterIndex].ship.LastBallCharacter)].Ship.lasttgt = iCharacterIndex; // NK 04-09-22 to see who AI is attacking
}

void Ship_Ship2IslandCollision()
{
	float	fPower, fSlide, x, y, z;
	int		iOurCharacterIndex, iEnemyCharacterIndex, iTouchPoint;

	iOurCharacterIndex = GetEventData();
	iEnemyCharacterIndex = GetEventData();
	fPower = abs(GetEventData());
	fSlide = abs(GetEventData());
	iTouchPoint = GetEventData();
	x = GetEventData();
	y = GetEventData();
	z = GetEventData();

	ref rOurCharacter = GetCharacter(iOurCharacterIndex);

	float fHP = (1.0 - fSlide) * fPower * 3.0;
	//Trace("Ship->Island touch: fpower = " + fPower + ", fHP = " + fHP + ", fSlide = " + fSlide);
	Ship_ApplyHullHitpoints(rOurCharacter, fHP, KILL_BY_ISLAND, -1);

	if (fPower > 1.0) { Play3DSound("coll_ship2rock", x, y, z); }
}

float Ship_GetAttackHP(int iCharacterIndex, float fDistance)
{
	float fAttackHP = 0.0;
	if (bSeaActive)
	{
		SendMessage(&AISea, "lafe", AI_MESSAGE_SHIP_GET_ATTACK_HP, &Characters[iCharacterIndex], fDistance, &fAttackHP);
	}
	return fAttackHP;
}

float Ship_GetHP(ref rCharacter) { return stf(rCharacter.Ship.HP); }

void Ship_Ship2ShipCollision()
{
	int		iOurCharacterIndex, iEnemyCharacterIndex, iTouchPoint;
	float	fPower, fSlide, x, y, z;

	iOurCharacterIndex = GetEventData();
	iEnemyCharacterIndex = GetEventData();
	fPower = abs(GetEventData());
	fSlide = abs(GetEventData());
	iTouchPoint = GetEventData();
	x = GetEventData();
	y = GetEventData();
	z = GetEventData();

	ref rOurCharacter = GetCharacter(iOurCharacterIndex);

	float fHP = (1.0 - fSlide) * fPower * 20.0;
	//Trace("Ship->Ship touch: idx = " + iOurCharacterIndex + ", fpower = " + fPower + ", fHP = " + fHP + ", fSlide = " + fSlide);
	Ship_ApplyHullHitpoints(rOurCharacter, fHP, KILL_BY_TOUCH, iEnemyCharacterIndex);
	if (fPower>1.0) { Play3DSound("coll_ship2ship", x, y, z); }
}

void Ship_ApplyCrewHitpoints(ref rOurCharacter, float fCrewHP)
{
	if(!CheckAttribute(rOurCharacter,"seatime.basecrewquantity")) rOurCharacter.ship.crew.quantity; // NK need to check 05-04-09
	rOurCharacter.seatime.lasthit = GetSeaTime(); // NK 04-09-16, to set time when last hit
	if(!CheckAttribute(rOurCharacter,"seatime.lastcrew")) rOurCharacter.seatime.lastcrew = rOurCharacter.ship.crew.quantity; // ditto, to set what crew was before loss
	//ref rBaseShip = GetShipByType(GetCharacterShipType(rOurCharacter));
	float fMultiply; if(USE_REAL_CANNONS) fMultiply = Bring2Range(1.0, 0.5, 0.0, 1.0, stf(rOurCharacter.TmpSkill.Defence)); // KNB was from 1.0x to 0.2x damage
	else fMultiply = Bring2Range(1.0, 0.2, 0.0, 1.0, stf(rOurCharacter.TmpSkill.Defence));
//NK -->
	//aref arship; makearef(arship, rOurCharacter.ship); // PRS3
	fMultiply *= (0.25 * (stf(rOurCharacter.Ship.Crew.Quantity) / makefloat(GetMinCrewQuantity(rOurCharacter)))); // PRS3
// NK <--
	float fNewCrewQuantity = stf(rOurCharacter.Ship.Crew.Quantity) - fCrewHP * fMultiply;
	float f5Percent = makefloat(GetMinCrewQuantity(rOurCharacter)) * 0.05; // PRS3
	if (fNewCrewQuantity < f5Percent) { fNewCrewQuantity = f5Percent; }
	if (fNewCrewQuantity < 1.0) { fNewCrewQuantity = 1.0; }
	rOurCharacter.Ship.Crew.Quantity = fNewCrewQuantity;
}

void Ship_ApplyHullHitpoints(ref rOurCharacter, float fHP, int iKillStatus, int iKillerCharacterIndex)
{
	//int tmpLangFileID = LanguageOpenFile("interface_strings.txt");
	float fCurHP;
	float fMinus = 0.0;
	float fPlus = 0.0;

	if (bSeaReloadStarted) { return; }
	if (fHP <= 0.0) { return; }

	if (bNoDamage2Us)
	{
		ref rMainCharacter = GetMainCharacter();
		if (sti(rOurCharacter.index) == GetMainCharacterIndex()) { return; }
		if (FindFellowTravellers(rMainCharacter, rOurCharacter) == FELLOWTRAVEL_COMPANION) { return; }
	}

	if (LAi_IsImmortal(rOurCharacter)) { return; }
	rOurCharacter.seatime.lasthit = GetSeaTime(); // NK 04-09-16, to set time when last hit
	// KNB -- reduced perk effect on hull damage
	if(USE_REAL_CANNONS)
	{	
		if (iKillerCharacterIndex >= 0)
		{
			if (sti(Characters[iKillerCharacterIndex].TmpPerks.HullDamageUp)) fPlus = 0.1; //0.15
			if (sti(Characters[iKillerCharacterIndex].TmpPerks.CannonProfessional)) fPlus = 0.2; //0.3
		}

		if (sti(rOurCharacter.TmpPerks.BasicBattleState)) 	fMinus = 0.08; //0.15
		if (sti(rOurCharacter.TmpPerks.AdvancedBattleState))	  fMinus = 0.15; //0.25
		if (sti(rOurCharacter.TmpPerks.ShipDefenseProfessional))	fMinus = 0.25; //0.40
	}
	// KNB <--
	else
	{
		if (iKillerCharacterIndex >= 0)
		{
			if (sti(Characters[iKillerCharacterIndex].TmpPerks.HullDamageUp)) fPlus = 0.15;
			if (sti(Characters[iKillerCharacterIndex].TmpPerks.CannonProfessional)) fPlus = 0.3;
		}

		if (sti(rOurCharacter.TmpPerks.BasicBattleState))			fMinus = 0.15;
		if (sti(rOurCharacter.TmpPerks.AdvancedBattleState))		fMinus = 0.25;
		if (sti(rOurCharacter.TmpPerks.ShipDefenseProfessional))	fMinus = 0.40;
	}

	if (LAi_IsDead(rOurCharacter)) { return; }
	// NK can qty 05-04-18 -->
	fCurHP = stf(rOurCharacter.Ship.HP) //- fHP * (1.0 + fPlus - fMinus);
	//Trace("idx = " + rOurCharacter.index + ", fCurHP = " + fCurHP);
	fHP = fHP * (1.0 + fPlus - fMinus);

	if(iKillStatus == KILL_BY_BALL) // cannons are only destroyed by incoming projectiles, not ramming
	{
		float chance = CAN_DESTROY_CHANCE / 0.008 * fHP / GetCharacterShipHP(&rOurCharacter) * pow2(makefloat(8-GetCharacterShipClass(&rOurCharacter)),1.5) / 5;
		float fOurAngle = stf(rOurCharacter.Ship.Ang.y);
		float fBallAngle;
		if(CheckAttribute(AIBalls,"Dir")) fBallAngle = clampangle(stf(AIBalls.Dir)+PI); // inverse of ball angle because ball is incoming
		else fBallAngle = clampangle(fOurAngle+(1.0-rand(1)*2.0) * PId2); // default to broadside on
		int quad = quadrantG(fOurAngle, fBallAngle);
		bool israke = quad == 0 || quad == 2;
		chance *= 1.0 + makefloat(israke) * 0.25; // extra 25% chance if raking

//KB - Tuning Ships - effect of Bronze cannons
//if ( sti(rOurCharacter.index) == 0 ) {Trace("KB - chance before " + chance);}
		aref temparef;
		int testtune=0;
		makearef(temparef,rOurCharacter.ship);
		if (CheckAttribute(temparef,"tune")) {
			testtune = 1;
			makearef(temparef,rOurCharacter.ship.tune);
			if(CheckAttribute(temparef,"bcannons")) { 
				if ( sti(rOurCharacter.ship.tune.bcannons.on) == 1 ) {
Trace("KB Tuning - Critical because bcannons");					
					chance = chance * makefloat(BCCRITICALCANNON);
				}

			}
		}		
//if ( sti(rOurCharacter.index) == 0 ) {Trace("KB - chance after " + chance);}
//KB - END Tuning Ships - effect of Bronze cannons

		string qdstr = GetQuadName(quad);
		int fix;

		bool dest = frnd() < chance;
		if(CheckAttribute(rOurCharacter,"cannonhit.critchance")) { if(!dest) { dest = frnd() < CAN_DESTROY_ON_CRIT; } } // higher chance for crits. // TIH attribute renamed to critchance, and IS.h setting typo fixed Nov13'06
			
		if(dest)
		{
			aref arcan; makearef(arcan, rOurCharacter.ship.cannons.borts.(qdstr));
			int curnum = sti(arcan.qty);
			if(!curnum)
			{
				if(frnd() < CAN_DESTROY_OTHERARC || israke)
				{
					quad += 2; if(quad >= 4) quad -= 4; // get opposite arc
					qdstr = GetQuadName(quad);
					makearef(arcan, rOurCharacter.ship.cannons.borts.(qdstr));
					curnum = sti(arcan.qty);
				}
				if(!curnum && israke) // if raking, can damage any arc if not first, rather than just first or opposite
				{
					for(quad = 0; quad < 4; quad++)
					{
						qdstr = GetQuadName(quad);
						makearef(arcan, rOurCharacter.ship.cannons.borts.(qdstr));
						curnum = sti(arcan.qty);
						if(curnum) break;
					}
				}
			}
			if(curnum)
			{
				arcan.qty = curnum - 1;
				fix = 0;
				if(frnd() < Bring2Range(CAN_FIXABLE_MIN, CAN_FIXABLE_MAX, 0.0, 1.2, stf(rOurCharacter.TmpSkill.Repair) + (fMinus/2.0)))
				{
					if(CheckAttribute(arcan,"fix")) fix = sti(arcan.fix);
					fix++;
					arcan.fix = fix;
				}
				Ship_SetCannonQtyByCrew(&rOurCharacter);
				trace("Canloss: char " + rOurCharacter.id + " of ship " + rOurCharacter.ship.name + " has lost a gun from arc " + quad+ " (" + GetQuadText(quad) + "). Fix: " + fix);
				// FX and logging section
				float x,y,z;
				if(CheckAttribute(rOurCharacter,"cannonhit"))
				{
					x = stf(rOurCharacter.cannonhit.x);
					y = stf(rOurCharacter.cannonhit.y);
					z = stf(rOurCharacter.cannonhit.z);
				}
				else
				{
					x = stf(rOurCharacter.ship.pos.x);
					y = stf(rOurCharacter.ship.pos.y);
					z = stf(rOurCharacter.ship.pos.z);
				}
				if(sti(rOurCharacter.index) == GetMainCharacterIndex())
				{
					PlaySound("interface\notebook.wav"); // like CCC's firedrill
					PlaySound("objects\shipcharge\ship_explosion.wav");
					logit(TranslateString("","We lost a cannon! From the") + " " + TranslateString("",GetQuadText(quad)) + " " + XI_ConvertString("arc") + "!" + stringret(fix," " + TranslateString("","We think we can fix it, captain."), ""));
				}
				else
				{
					if(iKillerCharacterIndex == GetMainCharacterIndex())
					{
						logit(TranslateString("","We knocked out one of") + " " + rOurCharacter.ship.name + TranslateString("","'s") + " " + XI_ConvertString(GetQuadText(quad)) + " " + TranslateString("","guns!"));
						PlaySound("objects\shipcharge\hurrah.wav");
					}
					Play3DSound("objects\shipcharge\ship_explosion.wav", x, y, z);
					
				}
				if(AUTO_SKILL_SYSTEM)
				{
					Ship_AddCharacterExpChar(GetCharacter(iKillerCharacterIndex), "Cannons", 1500);
					Ship_AddCharacterExpChar(rOurCharacter, "Defence", 500);
					Ship_AddCharacterExpChar(rOurCharacter, "Cannons", 250);
				}
				Ship_Serious_Boom(x, y, z);
			}
		}
		// multiplier
		fHP *= CANNON_DAMAGE_SCALAR;
	}
	fCurHP -= fHP;
	// NK <--
	if (fCurHP <= 0.0)
	{
		fCurHP = 0.0;
		//Trace("Player dead = " + rOurCharacter.index + ", id = " + rOurCharacter.id);
		ShipDead(sti(rOurCharacter.index), iKillStatus, iKillerCharacterIndex);
	}

	//LanguageCloseFile(tmpLangFileID);
	rOurCharacter.Ship.HP = fCurHP;
}

void Ship_AddCharacterExp(ref rCharacter, int iExpQuantity)
{
	// NK -->
	bool t = IsCompanion(rCharacter);
	bool ns;
	if(!t)
	{
		ns = CheckAttribute(rCharacter,"donotshowXP");
		if(!ns) { rCharacter.donotshowXP = true; }
	}
	// NK <--
	AddCharacterExp(rCharacter, iExpQuantity);
	if(!t){ if(!ns) { DeleteAttribute(rCharacter,"donotshowXP"); } } // NK

	if (sti(rCharacter.index) == GetMainCharacterIndex())
	{
		fSeaExp = fSeaExp + iExpQuantity;
	}
}

void ShipDead(int iDeadCharacterIndex, int iKillStatus, int iKillerCharacterIndex)
{
	//int tmpLangFileID = LanguageOpenFile("interface_strings.txt");
	ref rDead, rKillerCharacter, rMainCharacter, rBaseShip, rKillerBaseShip;

	// LDH added trace for problems here 29AUG06
	trace("ShipDead: iDeadCharacterIndex = " + iDeadCharacterIndex + ", iKillStatus = " + iKillStatus + ", iKillerCharacterIndex = " + iKillerCharacterIndex);

	rDead = GetCharacter(iDeadCharacterIndex);
	rBaseShip = GetShipByID(rDead.Ship.Type); // PS
	aref arship; makearef(arship, rDead.ship); // PRS3
	aref arkship;

	if (iKillerCharacterIndex != -1)
	{
		rKillerCharacter = GetCharacter(iKillerCharacterIndex);
		rKillerBaseShip = GetShipByID(rKillerCharacter.Ship.Type); // PS
		makearef(arkship, rKillerCharacter.ship); // PRS3
	}

	rMainCharacter = GetMainCharacter();
	int iQuantity = GetCompanionQuantity(rMainCharacter);

	float fX = stf(rDead.Ship.Pos.x);
	float fY = stf(rDead.Ship.Pos.y);
	float fZ = stf(rDead.Ship.Pos.z);

	rDead.Ship.HP = 0;
	rDead.Killer.Status = iKillStatus;
	rDead.Killer.Index = iKillerCharacterIndex;

	LAi_SetCurHP(rDead, 0.0);
	//LAi_KillCharacter(rDead);

	bool bDeadCompanion = IsCompanion(rDead);

	int iExp = 0;
	if (iKillerCharacterIndex != -1) { iExp = 2 * MakeInt(makefloat(GetCharacterShipHP(rDead)) * makefloat(GetCharacterShipClass(rKillerCharacter)) / makefloat(GetCharacterShipClass(rDead))); } // PRS3
	// NK 04-09-16 -->
	if(iExp>0)
	// mod 04-09-22 to apply to other ships too
	{
		ref schr;
		float tmped = 0; if(CheckAttribute(rKillerCharacter,"seatime.enemydead")) tmped = stf(rKillerCharacter.seatime.enemydead);
		float origed = makefloat(GetCharacterShipClass(rKillerCharacter)) / makefloat(GetCharacterShipClass(rDead)) * CLASS_SCALAR_FOR_MORALE;
		tmped += origed;
		rKillerCharacter.seatime.enemydead = tmped;
		for(int s = 0; s < iNumShips; s++)
		{
			if(Ships[s] == iDeadCharacterIndex) continue;
			schr = GetCharacter(Ships[s]);
			if(SeaAI_GetRelation(iDeadCharacterIndex, Ships[s]) == RELATION_ENEMY)
			{
				tmped = 0; if(CheckAttribute(schr,"seatime.enemydead")) tmped = stf(schr.seatime.enemydead);
				tmped += origed * CLASS_SCALAR_FOR_MORALE_ORIG_TO_OTHER + makefloat(GetCharacterShipClass(schr)) / makefloat(GetCharacterShipClass(rDead)) * CLASS_SCALAR_FOR_MORALE_OTHER;
				schr.seatime.enemydead = tmped;
			}
		}
	}
	// NK <--

	bool bCompanion = false;
	if (iKillerCharacterIndex != -1) { bCompanion = IsCompanion(rKillerCharacter); }

	if (bCompanion && bDeadCompanion) { iExp = 0; }

	bool bRealKill = false;
	bool bHandofGod = false;
	switch (iKillStatus)
	{
		case KILL_BY_TOUCH:
			if (iKillerCharacterIndex != -1) { bRealKill = true; }
		break;
		case KILL_BY_BALL:
			bRealKill = true;
		break;
		case KILL_BY_ABORDAGE:
			bRealKill = true;
		break;
	}

	// Ship sunk log
	if (iDeadCharacterIndex != GetMainCharacterIndex())
	{
		rBaseShip = GetShipByID(rDead.Ship.Type);
		string sSunkString;
		string sSunkShipType = XI_ConvertString(rBaseShip.SName); // PS
		// TIH --> to prevent errors when ship is killed by rocks
		string sKillShipType = ""; // PS
		string sKillShipName = "";
		if ( bRealKill ) {
			if (iKillerCharacterIndex != -1) { // KK
				Group_UpdateGroup(rDead); // KK

				rKillerBaseShip = GetShipByID(rKillerCharacter.Ship.Type);
				sKillShipType = XI_ConvertString(rKillerBaseShip.SName); // PS
				sKillShipName = "" + rKillerCharacter.Ship.Name;
				if(HasSubStr(sKillShipName, "fort_")) sKillShipName = GetTownName(rKillerCharacter.town); // NK 04-06 // KK
			} // KK
		} else {
			sKillShipType = "";
			sKillShipName = "" + TranslateString("","Hand of God");
			bHandofGod = true;
			
		}
		// TIH <--

// corrected by MAXIMUS -->
		string sExp;
		if(iQuantity==0) sExp = "0";
		else sExp = iExp / iQuantity;
// corrected by MAXIMUS <--

		if (bCompanion && !bDeadCompanion && bRealKill)
		{ sSunkString = sSunkShipType + " '" + rDead.Ship.Name + "' " + TranslateString("","has been sunk by") + " " + rKillerCharacter.Name + " " + rKillerCharacter.LastName + TranslateString("",". Gained") + " " + iExp + " " + TranslateString("","Total XP") + "."; }
		else
		{ 
			if ( bHandofGod && rand(100)>95 )// TIH easteregg
			{ sSunkString = sSunkShipType + " '" + rDead.Ship.Name + "' " + TranslateString("","has been sunk by the") + " " + TranslateString("","Hand of God") + TranslateString("", ". God gained") + " " + (1000+rand(2000)) + " " + TranslateString("","Total XP") + "."; }
			else
			{ sSunkString = sSunkShipType + " '" + rDead.Ship.Name + "' " + TranslateString("","has been sunk by the") + " " + sKillShipType + " '" + sKillShipName + "'"; }
		}
		Log_SetStringToLog(sSunkString);
		if(!CheckAttribute(rDead,"ShipMoney")) rDead.ShipMoney = 0; // NK 04-11
		if(iKillStatus == KILL_BY_ABORDAGE) { AddMoneyToCharacter(rKillerCharacter, sti(rDead.ShipMoney)); Log_SetStringToLog(XI_ConvertString("Gained") + " " + rDead.ShipMoney + " " + XI_ConvertString("gold!")); } // NK
		if(SeaAI_GetRelation(iDeadCharacterIndex, GetMainCharacterIndex()) == RELATION_ENEMY) PlaySound("objects\abordage\abordage_wining.wav"); // NK cheers
		if(bHandofGod && CheckAttribute(&characters[iDeadCharacterIndex],"id") && characters[iDeadCharacterIndex].id=="Quest trader")
		{
			characters[GetMainCharacterIndex()].quest.generate_convoy_quest.storm = true;
		}
	}

	if (bRealKill && iKillerCharacterIndex != -1)
	{
	  int iRelation = SeaAI_GetRelation(iDeadCharacterIndex, iKillerCharacterIndex);
	  int iNation = sti(rDead.nation);
		switch (iRelation)
		{
			case RELATION_FRIEND:
			break;
			case RELATION_NEUTRAL:
			break;
			case RELATION_ENEMY:
				if (bCompanion)
				{
					for (int i=0; i<COMPANION_MAX; i++)
					{
						int iIndex = GetCompanionIndex(rMainCharacter, i);
						if (iIndex >= 0)
						{
							if(AUTO_SKILL_SYSTEM)
							{
								int curAdd = makeint(makeint(iExp / iQuantity)/2);
								AddPartyExpChar(&Characters[i], "Sailing", curAdd);
								AddPartyExpChar(&Characters[i], "Cannons", curAdd);
							}
							else { AddPartyExp(&Characters[i], iExp / iQuantity); }
						}
					}
					// RM -->
					float points = 1.0;
					if(CheckAttribute(rDead,"Points")) points = stf(rDead.Points);
					if(CheckAttribute(rDead,"FantomType")) Log_SetStringToLog("Type: " + stringRet(CheckAttribute(rDead,"FantomType"),rDead.FantomType + " ","")+"ship. Points: " + points);
					int nat = sti(rDead.nation); 
					// LDH -->
					if(nat == NEUTRAL_NATION)
					{
						if (CheckAttribute(rDead, "oldnation"))
						{
							nat = sti(rDead.oldnation); // NK surrender 05-04-20
						} 
// LDH removed 16Sep06
//						else 
//						{
//							nat = PIRATE;	// LDH - fix for missing nation, default to Pirate
//						}
					}
					// LDH <--
					UpdateRMRelation(GetMainCharacter(), nat, points);
					// RM <--
				}
				else
				{
					if(AUTO_SKILL_SYSTEM)
					{
						AddPartyExpChar(rKillerCharacter, "Sailing", makeint(iExp/2));
						AddPartyExpChar(rKillerCharacter, "Cannons", makeint(iExp/2));
					}
					else { AddCharacterExp(rKillerCharacter, iExp); }
				}
			break;
		}
	}

	if (rand(10) >= 6 && GetCharacterShipClass(&rDead) <= 6) // PRS3
	{
		bool bDetonate = false;
		switch (iKillStatus)
		{
			case KILL_BY_BALL:	bDetonate = true; break;
			case KILL_BY_SPY:	bDetonate = true; break;
		}
		bool bdosmall = false; if(CheckAttribute(rDead,"explosion")) { bdosmall = true; DeleteAttribute(&rDead,"explosion"); } // NK for death from fire explosion 05-04-19
		if (bDetonate) { Ship_Detonate(rDead, true, bdosmall); } // NK ditto
	}

	// set attributes for sinking effect
	rDead.Ship.Sink = "";
	aref aSink; makearef(aSink, rDead.Ship.Sink);
	aSink.Speed.y = 0.35;	// speed of sink y
	aSink.Speed.x = 0.017 * (frnd() * 2.0 - 1.0);	// speed sink angle rotate around x
	aSink.Speed.z = 0.017 * (frnd() * 2.0 - 1.0);	// speed sink angle rotate around z

	if (iDeadCharacterIndex == GetMainCharacterIndex())
	{
		SeaCameras_SetDieCamera();
		FadeOutMusic(2000);
		PlayStereoOGG("music_ship_dead");
		PostEvent(GAME_OVER_EVENT, 15000);
	}
	if(!bHandofGod && GetCharacterIndex(rKillerCharacter.id)!=GetMainCharacterIndex() && CheckAttribute(&characters[iDeadCharacterIndex],"id") && characters[iDeadCharacterIndex].id=="Quest trader")
	{
		string Add = "d_";
		if(Rand(10)<Rand(10)) Add = "d_sw_";
		characters[GetMainCharacterIndex()].quest.generate_convoy_quest.attack = XI_ConvertString(Add+GetNationDescByType(sti(rKillerCharacter.nation)));
	}

	// Boatswain sounds MOD - Amokachi
	if(ENABLE_EXTRA_SOUNDS == 1) {			// added by KAM after build 11
		Play3DSound("ship_sinking", fX, fY, fZ);
	}						// added by KAM after build 11


	Play3DSound("ship_sink", fX, fY, fZ);

	// Message to AI
		SendMessage(AISea, "la", AI_MESSAGE_CHARACTER_DEAD, rDead);

	// Event
		Event(SHIP_DEAD, "l", iDeadCharacterIndex);
	//LanguageCloseFile(tmpLangFileID);
}

// START MOD Code by Stone-D : 22/07/2003
void ShipTaken(int iDeadCharacterIndex, int iKillStatus, int iKillerCharacterIndex)
{
	//int tmpLangFileID = LanguageOpenFile("interface_strings.txt");
	ref rDead, rKillerCharacter, rMainCharacter, rBaseShip, rKillerBaseShip;

	rDead = GetCharacter(iDeadCharacterIndex);
	rDead.Killer.Index = iKillerCharacterIndex;
	rBaseShip = GetShipByID(rDead.Ship.Type); // PS
	rMainCharacter = GetMainCharacter();

	int iExp = 0;
	int iQuantity = GetCompanionQuantity(rMainCharacter);
	bool bCompanion = false;
	bool bDeadCompanion = IsCompanion(rDead);

	if (iKillerCharacterIndex != -1)
	{
		rKillerCharacter = GetCharacter(iKillerCharacterIndex);
		rKillerBaseShip = GetShipByID(rKillerCharacter.Ship.Type); // PS
		iExp = 2 * MakeInt(makefloat(GetCharacterShipHP(rDead)) * makefloat(GetCharacterShipClass(rKillerCharacter)) / makefloat(GetCharacterShipClass(rDead))); // PRS3
		iExp = makeint(iExp/4);
		bCompanion = IsCompanion(rKillerCharacter);
		if (CheckAttribute(rDead,"ShipMoney")) AddMoneyToCharacter(rKillerCharacter, sti(rDead.ShipMoney)); // NK
	}

	if (bCompanion && bDeadCompanion) { iExp = 0; }

	if (iDeadCharacterIndex != GetMainCharacterIndex())
	{
		string sSunkShipType = XI_ConvertString(rBaseShip.SName); // PS
		string sKillShipType = XI_ConvertString(rKillerBaseShip.SName); // PS
		string sExp = iExp / iQuantity;
		if(CheckAttribute(&characters[iDeadCharacterIndex],"id") && characters[iDeadCharacterIndex].id=="Quest trader")
		{
			characters[GetMainCharacterIndex()].quest.generate_convoy_quest.board = true;
		}

		if(bCompanion && !bDeadCompanion)
		{
			// RM -->
			float points = 1.0;
			if (CheckAttribute(rDead,"Points")) points = stf(rDead.Points);
			if (CheckAttribute(rDead,"FantomType")) 
				Log_SetStringToLog("Type: " + stringRet(CheckAttribute(rDead,"FantomType"),rDead.FantomType + " ","")+"ship" + ". Points: " + points);
			else
				Log_SetStringToLog("Type: " + "ship" + ". Points: " + points);
			int nat = sti(rDead.nation); if(nat == NEUTRAL_NATION) nat = sti(rDead.oldnation); // NK surrender 05-04-20
			UpdateRMRelation(GetMainCharacter(), nat, points);
			// RM <--
		}
		if(bCompanion && !bDeadCompanion)
//		{ Log_SetStringToLog(sSunkShipType + " '" + rDead.Ship.Name + "' " + "has been captured! Gained " + iExp + " Total XP"); }
		{ 
			Log_SetStringToLog(TranslateString("","ShipCaptured") + " " + sSunkShipType + " '" + rDead.Ship.Name + "' " + TranslateString("","has been captured!")); 
			if (CheckAttribute(rDead,"ShipMoney")) { Log_SetStringToLog("Gained " + rDead.ShipMoney + " gold!"); }
		} // NK
	}
// Decided to disable the extra XP, didn't realise at the time that boarding XP was also awarded.
//	if (iKillerCharacterIndex != -1) { AddCharacterExp(rKillerCharacter, iExp); }
	//LanguageCloseFile(tmpLangFileID);
}
// END MOD Code by Stone-D : 22/07/2003

void Ship_SailHitEvent()
{
}

void Ship_HullHitEvent()
{
	float	fHP;

	int		iBallType = sti(AIBalls.CurrentBallType);

	aref	rShipObject = GetEventData();

	int		iBallCharacterIndex = GetEventData();
	int		iOurCharacterIndex = GetEventData();

	ref		rBallCharacter = GetCharacter(iBallCharacterIndex);
	ref		rOurCharacter = GetCharacter(iOurCharacterIndex);

	rOurCharacter.Ship.LastBallCharacter = iBallCharacterIndex;
	rBallCharacter.Ship.lasttgt = iOurCharacterIndex; // NK 04-09-16 to see who AI is attacking

	int		iCompanion = GetCompanionNumber(rBallCharacter, iOurCharacterIndex);

	float	x = GetEventData();
	float	y = GetEventData();
	float	z = GetEventData();

	int		iFirePlaceIndex = GetEventData();
	float	fFirePlaceDistance = GetEventData();

	bool	bDead = LAi_IsDead(rOurCharacter);

	bool	bSeriousBoom = false;
	bool	bInflame = false;

	// NK 05-04-18 to handle relation change for REAL_CANNONS
	if(iBallCharacterIndex == GetMainCharacterIndex() && USE_REAL_CANNONS && EXTRA_HIT_CHECK && rOurCharacter.ship.type != SHIP_FORT_NAME)
	{
		int currel = SeaAI_GetRelation(iOurCharacterIndex, iBallCharacterIndex) ;
		if(currel != RELATION_ENEMY && !CheckAttribute(rOurCharacter,"surrendered")) // NK surrender 05-04-20
		{
			aref aShips; makearef(aShips, rBallCharacter.SeaAI.Update.Ships);
			int iShipsNum = GetAttributesNum(aShips);
			int s;
			bool enemy = false;
			for (s=0; s<iShipsNum; s++)
			{
				aref aShip = GetAttributeN(aShips, s);
				if (sti(aShip.relation) == RELATION_ENEMY) { enemy = true; break; }
			}
			int cmdridx = Group_GetGroupCommanderIndex(GetGroupIDFromCharacter(&rOurCharacter)); // KK
			if(!enemy) event(SHIP_FIRE_ACTION, "lll", iOurCharacterIndex, cmdridx, currel); // if there's no enemy, then you have no excuse. // KK
			else
			{
				if(!CheckAttribute(rOurCharacter,"numhits")) rOurCharacter.numhits = 1;
				else
				{
					int curhits = sti(rOurCharacter.numhits); // max of cqty / 2 hits.
					if(curhits > GetCannonMaxQuantity(&rBallCharacter) / 2) event(SHIP_FIRE_ACTION, "lll", iOurCharacterIndex, cmdridx, currel); // KK
					else { curhits++; rOurCharacter.numhits = curhits; }
				}
			}
		}
	}
	if(iBallCharacterIndex != GetMainCharacterIndex() && CheckAttribute(rOurCharacter,"surrendered")) return; // NK because AI doesn't respect surrender. 05-04-23
	// NK <--

	// Cannon damage multiply
	ref rCannon = GetCannonByType(sti(rBallCharacter.Ship.Cannons.Type));
	float maxqty, fCannonDamageMultiply;
	// THIS IS NOT NEEDED ANY MORE. But the comment is left and the code commented. NK 04-09-15 to simulate fewer crew manning fewer guns. Hence reload rate will now go back to constant.
	// KNB 05-02-04 changed this a bit: there is no penalty for >60% crew and the minimum damage is 10%
	// now use cannon qty rather than crew mult. 05-04-18
	if(USE_REAL_CANNONS)
	{
		fCannonDamageMultiply = stf(rCannon.DamageMultiply);

		// KNB 04-01-17 distance/aspect based damage multipliers -->
		float fShotDistance = Ship_GetDistance2D(rBallCharacter, rOurCharacter);
		if(fShotDistance < 0) fShotDistance = 500; // NK for when dist < 0 05-04-15
		float fShotSpeed = rCannon.SpeedV0 * stf(Goods[iBallType].SpeedV0);
		float fBallAngle = 0;
		float fOurAngle = 0;
		if (rOurCharacter.Ship.Type != SHIP_FORT_NAME && rBallCharacter.Ship.Type != SHIP_FORT_NAME)
		{
			fCannonDamageMultiply *= 2 * pow(1-1/(2.25*fShotSpeed),fShotDistance); // 2x at point blank, decreasing exponentially with range

			if (iBallType != GOOD_GRAPES)
			{
				if(CheckAttribute(rOurCharacter,"ship.ang.y")) // NK for checking atttributes first before we need CheckAttribute(rBallCharacter,"ship.ang.y") &&  to
				{ 
					// NK now get angle from AIBalls, this requires reversing of the B2R.
					fBallAngle = clampangle(stf(AIBalls.Dir)+PI); fOurAngle = stf(rOurCharacter.Ship.Ang.y);
					fCannonDamageMultiply *= Bring2Range(0.50, 2.0, 0.0, 1.0, abs(GetDotProduct(fBallAngle,fOurAngle))); //50%-200% depending on angle
				}
			}  
		}
	}
	// <-- KNB
	else
	{
		//maxqty = GetMaxCrewQuantity(rBallCharacter);
		fCannonDamageMultiply = stf(rCannon.DamageMultiply);
		//if( maxqty!= 0) { fCannonDamageMultiply *= (makefloat(GetCrewQuantity(rBallCharacter)) / maxqty); }
		//else { trace(rBallCharacter.id + " of " + rBallCharacter.ship.name + " has 0 maxcrewqty!");}
	}

	if (sti(rBallCharacter.TmpPerks.CriticalShoot) && rand(19)==10) { bSeriousBoom = true; }		// +5%
	if (sti(rBallCharacter.TmpPerks.CannonProfessional) && rand(9)==4) { bSeriousBoom = true; }		// +10%

	ref rBall = GetGoodByType(iBallType);
	switch (iBallType)
	{
// TIH --> this section adjusted to be a bit more understandable in setting, and reasonable in graphics Nov13'06
// Removal of "CreateBlastM()" function due to that function causing a CTD in battles Nov12'06
	// NK add USE_PARTICLES switch // TIH changed to own switch SHIPHIT_PARTICLES 7-7-06
	// Ball_impact and splinters by MM
	// By MM, Flames and Critical hits only present on bombs now. // changed to add a rare chance (otherwise what's the POINT of the PERKS?)
		case GOOD_BALLS:
			if (ALLOW_CRIT_FROM_BALLS && rand(1)) { bSeriousBoom = false; }// pref toggle and 50% chance a possible critical hit is allowed
			if (ALLOW_FIRE_FROM_BALLS && rand(32) == 1) { bInflame = true; }// pref toggle and 3% chance ship catches on fire from hit
			Play3DSound("ball2bort", x, y, z);
			switch(SHIPHIT_PARTICLES)
			{
				case 0: CreateParticleSystem("blast", x, y, z, 0.0, 0.0, 0.0, 0); break; // one orange puff // stock behavior
				case 1:
						CreateParticleSystem("ball_impact", x, y, z, 0.0, 0.0, 0.0, 0); // two small gray puffs
				break;
				case 2: 
						CreateParticleSystem("ball_impact", x, y, z, 0.0, 0.0, 0.0, 0);	// two small gray puffs
						CreateParticleSystem("splinters2", x, y, z, 0.0, 0.0, 0.0, 0);	// six medium splinters
						CreateParticleSystem("splinters3", x, y, z, 0.0, 0.0, 0.0, 0);	// five large splinters
						CreateParticleSystem("flyers", x, y, z, 0.0, 0.0, 0.0, 0);		// Petros adds one crew flying overboard
						CreateParticleSystem("flyers2", x, y, z, 0.0, 0.0, 0.0, 0);
				break;
				case 3:
						CreateBlast(x,y,z); // excessive plume of planks and barrels, causing big water splashes
						CreateParticleSystem("ball_impact", x, y, z, 0.0, 0.0, 0.0, 0);	// two small gray puffs
						CreateParticleSystem("splinters2", x, y, z, 0.0, 0.0, 0.0, 0);	// six medium splinters
						CreateParticleSystem("splinters3", x, y, z, 0.0, 0.0, 0.0, 0);	// five large splinters
						CreateParticleSystem("flyers", x, y, z, 0.0, 0.0, 0.0, 0);
						CreateParticleSystem("flyers2", x, y, z, 0.0, 0.0, 0.0, 0);
				break;
			}
		break;
		case GOOD_GRAPES:
			bSeriousBoom = false;
			bInflame = false;
			Play3DSound("grapes2bort", x, y, z);
			switch(SHIPHIT_PARTICLES)
			{
				case 0: CreateParticleSystem("blast", x, y, z, 0.0, 0.0, 0.0, 0); break; // one orange puff // stock behavior
				case 1:
					CreateParticleSystem("ball_impact", x, y, z, 0.0, 0.0, 0.0, 0);
				break;
				case 2:
					CreateParticleSystem("ball_impact", x, y, z, 0.0, 0.0, 0.0, 0);
					CreateParticleSystem("splinters", x, y, z, 0.0, 0.0, 0.0, 0);
				break;
				case 3:
					CreateBlast(x,y,z);
					CreateParticleSystem("ball_impact", x, y, z, 0.0, 0.0, 0.0, 0);
				break;
			}
		break;
		case GOOD_KNIPPELS:
			bSeriousBoom = false;
			bInflame = false;
			Play3DSound("knippel2bort", x, y, z);
			switch(SHIPHIT_PARTICLES)
			{
				case 0: CreateParticleSystem("blast", x, y, z, 0.0, 0.0, 0.0, 0); break; // one orange puff // stock behavior
				case 1:
					CreateParticleSystem("ball_impact", x, y, z, 0.0, 0.0, 0.0, 0);
				break;
				case 2:
					CreateParticleSystem("ball_impact", x, y, z, 0.0, 0.0, 0.0, 0);
					CreateParticleSystem("splinters", x, y, z, 0.0, 0.0, 0.0, 0);
					CreateParticleSystem("splinters2", x, y, z, 0.0, 0.0, 0.0, 0);
					CreateParticleSystem("flyers", x, y, z, 0.0, 0.0, 0.0, 0);
					CreateParticleSystem("flyers2", x, y, z, 0.0, 0.0, 0.0, 0);
				break;
				case 3:
					CreateBlast(x,y,z);
					CreateParticleSystem("ball_impact", x, y, z, 0.0, 0.0, 0.0, 0);
					CreateParticleSystem("flyers", x, y, z, 0.0, 0.0, 0.0, 0);
					CreateParticleSystem("flyers2", x, y, z, 0.0, 0.0, 0.0, 0);
				break;
			}
		break;
		case GOOD_BOMBS:
			if (rand(20) == 10) { bSeriousBoom = true; }
			if (rand(2) == 1) { bInflame = true; }
			Play3DSound("bomb2bort", x, y, z);
			switch(SHIPHIT_PARTICLES)
			{
				case 0: CreateParticleSystem("blast", x, y, z, 0.0, 0.0, 0.0, 0); break; // one orange puff // stock behavior
				case 1:
					CreateParticleSystem("blast", x, y, z, 0.0, 0.0, 0.0, 0);
				break;
				case 2:
					CreateBlast(x,y,z);
					CreateParticleSystem("blast", x, y, z, 0.0, 0.0, 0.0, 0);
					CreateParticleSystem("flyers", x, y, z, 0.0, 0.0, 0.0, 0);
					CreateParticleSystem("flyers2", x, y, z, 0.0, 0.0, 0.0, 0);
				break;
				case 3:
					CreateBlast(x,y,z);
					CreateParticleSystem("blast", x, y, z, 0.0, 0.0, 0.0, 0);
					CreateParticleSystem("blast_inv", x, y, z, 0.0, 0.0, 0.0, 0);
					CreateParticleSystem("splinters2", x, y, z, 0.0, 0.0, 0.0, 0);
					CreateParticleSystem("splinters3", x, y, z, 0.0, 0.0, 0.0, 0);
					CreateParticleSystem("flyers", x, y, z, 0.0, 0.0, 0.0, 0);
					CreateParticleSystem("flyers2", x, y, z, 0.0, 0.0, 0.0, 0);
				break;
			}
		break;
// TIH <-- end adjustment
	}

	if (sti(rOurCharacter.TmpPerks.ShipDefenseProfessional) && frnd() < PROSHIPDEF_NOCRITCH) { bSeriousBoom = false; }				// no seriouse boom, NK 05-04-19 add setting

	if (!bDead)
	{
		float fCrewDamage; if(USE_REAL_CANNONS) fCrewDamage = stf(rBall.DamageCrew) * fCannonDamageMultiply * AIShip_isPerksUse(rBallCharacter.TmpPerks.CrewDamageUp, 1.0, 1.10); // 1.15  KNB;
		else fCrewDamage = stf(rBall.DamageCrew) * fCannonDamageMultiply * AIShip_isPerksUse(rBallCharacter.TmpPerks.CrewDamageUp, 1.0, 1.15);
// KK -->
		if (ENABLE_FLAGS == 1) {
			ref PChar = GetMainCharacter();
			int iNation = sti(rOurCharacter.nation);
			if ((GetMainCharacterIndex() == iBallCharacterIndex) && (GetRMRelation(PChar, iNation) > REL_WAR)) {
				SetCharacterRelationBoth(iOurCharacterIndex, GetMainCharacterIndex(), RELATION_ENEMY);
				SetRMRelation(PChar, iNation, REL_WAR); 
				for (int i = 0; i < 4; i++) {
					if (GetCompanionIndex(PChar, i) >= 0 && GetRemovable(GetCharacter(GetCompanionIndex(PChar, i)))) {
						SetCharacterRelationBoth(iOurCharacterIndex, GetCompanionIndex(PChar, i), RELATION_ENEMY);
						SetRMRelation(GetCharacter(GetCompanionIndex(PChar, i)), iNation, REL_WAR);
					}
				}
			}
		}

		if (LAi_IsCrewImmortal(rOurCharacter)) fCrewDamage = 0.0; // KK

		rOurCharacter.cannonhit = true; rOurCharacter.cannonhit.x = x; rOurCharacter.cannonhit.y = y; rOurCharacter.cannonhit.z = z; // NK 05-04-20 so cannon losing has pos for FX

		if (bSeriousBoom)
		{
			//fCrewDamage = fCrewDamage * 2.0; // stock
			//fHP = fCannonDamageMultiply * stf(rBall.DamageHull) * (4.0 + frnd() * 2.0); // stock
			//fCrewDamage = fCrewDamage * 5.0; // excessive
			//fHP = fCannonDamageMultiply * stf(rBall.DamageHull) * (80.0 + frnd() * 40.0); // excessive
			fHP = fCannonDamageMultiply * stf(rBall.DamageHull) * (2.0 + frnd());
			rOurCharacter.cannonhit.critchance = true; // NK 05-04-20 higher chance for loss of cannon on crit // TIH attribute renamed to critchance Nov13'06
			Ship_ApplyHullHitpoints(rOurCharacter, fHP, KILL_BY_BALL, iBallCharacterIndex);
			Ship_ApplyCrewHitpoints(rOurCharacter, fCrewDamage  * (2.0 + frnd()) );
			//Ship_ApplyCrewHitpoints(rOurCharacter, fCrewDamage * 1.5); // stock
			if (iCompanion == - 1) { if(AUTO_SKILL_SYSTEM) { Ship_AddCharacterExpChar(rBallCharacter, "Cannons", 150); }else{ Ship_AddCharacterExp(rBallCharacter, 150); } }

			if (iBallCharacterIndex == GetMainCharacterIndex())
			{
				Log_SetStringToLog(LanguageConvertString(iSeaSectionLang, "Ship_critical"));
			}
			if(AUTO_SKILL_SYSTEM) { Ship_AddCharacterExpChar(GetCharacter(iBallCharacterIndex), "Cannons", 1000); }
		}
		else
		{
			fHP = fCannonDamageMultiply * stf(rBall.DamageHull);
			Ship_ApplyHullHitpoints(rOurCharacter, fHP, KILL_BY_BALL, iBallCharacterIndex);
			Ship_ApplyCrewHitpoints(rOurCharacter, fCrewDamage); // moved from below, so not applied twice during crits
			if (iCompanion == - 1) { if(AUTO_SKILL_SYSTEM) { Ship_AddCharacterExpChar(rBallCharacter, "Cannons", 20); }else{ Ship_AddCharacterExp(rBallCharacter, 20); } }
			if(AUTO_SKILL_SYSTEM) { Ship_AddCharacterExpChar(GetCharacter(iBallCharacterIndex), "Cannons", 250); }
		}
		DeleteAttribute(&rOurCharacter, "cannonhit"); // NK 05-04-20 cannon lost FX

		//Ship_ApplyCrewHitpoints(rOurCharacter, fCrewDamage); // moved up, so not applied twice during crits
	}

	if (bInflame == true && fFirePlaceDistance < 4.0 && iFirePlaceIndex >= 0)
	{
		int iRandStartTime = rand(1000);
		float fTotalFireTime = Ship_GetTotalFireTime(rOurCharacter);

		PostEvent(SHIP_ACTIVATE_FIRE_PLACE, iRandStartTime, "ialsf", rShipObject, rOurCharacter, iFirePlaceIndex, "ship_onfire", fTotalFireTime);
		PostEvent(SHIP_FIRE_DAMAGE, iRandStartTime, "lllf", iOurCharacterIndex, iBallCharacterIndex, iFirePlaceIndex, fTotalFireTime);
	}

	if (bSeriousBoom == true) //{ Ship_Serious_Boom(x, y, z); } // NK modify coords some 05-04-18 -->
	{
		if(SHIPHIT_PARTICLES==3) { Ship_Detonate(rOurCharacter, false, false); }//{ Ship_Serious_Boom(x-1+frnd()*2, y-1+frnd()*2, z-1+frnd()*2); }
		else { Ship_Serious_Boom(x, y, z); }
	}
}

float Ship_GetTotalFireTime(ref rCharacter)
{
	int iRepairSkill = MakeInt(stf(rCharacter.TmpSkill.Repair) * 10.0);
// KK -->
	float TotalFireTime = MakeFloat(5 + 3 * (10 - iRepairSkill));
	if (ENABLE_FIREDRILL == 1) TotalFireTime = TotalFireTime * FIRETIME;	// ccc firedrill
	return TotalFireTime;
// <-- KK
}

void Ship_FireDamage()
{
	//int tmpLangFileID = LanguageOpenFile("interface_strings.txt");
	int iOurCharacterIndex = GetEventData();
	int iBallCharacterIndex = GetEventData();

	int iFirePlaceIndex = GetEventData();
	float fTotalFireTime = GetEventData();

	ref rOurCharacter = GetCharacter(iOurCharacterIndex);
	ref rBallCharacter = GetCharacter(iBallCharacterIndex);

	int iSClass = GetCharacterShipClass(rOurCharacter);

	float fHP = (8 - iSClass) * (0.5 + frnd() * 0.5); // KK 

	int iTime = 1000 + rand(500);

	if (ENABLE_FIREDRILL == 1) fHP = fHP * FIREDAMAGE; // ccc firedrill MAR18 // KK

	// if abordage or interface launched
	if (!bAbordageStarted && !sti(InterfaceStates.Launched))
	{
		// NK add super-crit 05-04-19
		float hpratio = GetHullPercent(&rOurCharacter)/100.0;
		if(hpratio <= FIREKILLCH_HP_MIN) // add fire can kill via super-crit
		{
			if(frnd() < FIREKILLCH_BASE * Bring2Range(FIREKILLCH_MAXCH, FIREKILLCH_MINCH, 0.0, pow2(FIREKILLCH_HP_MIN, FIREKILLCH_HP_RAT_PWR), pow2(hpratio, FIREKILLCH_HP_RAT_PWR)))
			{
				fHP = 1000000;
				rOurCharacter.explosion = true;
				logit(TranslateString("","Fire has reached the") + " " + rOurCharacter.ship.name + TranslateString("","'s powder magazine!"));
				PlaySound("interface\notebook.wav"); // sound so you notice
				if (CANNONPOWDER_MOD) {// TIH --> mod toggle 7-7-06
					// added by MAXIMUS [gunpowder mod] -->
					int newQty;
					if(sti(GetCargoGoods(rOurCharacter, GOOD_GUNPOWDER))>0) newQty = sti(GetCargoGoods(rOurCharacter, GOOD_GUNPOWDER)) / 100 * rand(50);
					else newQty = 0;
					SetCharacterGoods(rOurCharacter, GOOD_GUNPOWDER, newQty);
					// added by MAXIMUS [gunpowder mod] <--
				}// TIH <-- mod toggle
			}
		}
		// NK <--
		Ship_ApplyHullHitpoints(rOurCharacter, fHP, KILL_BY_BALL, iBallCharacterIndex);

		fTotalFireTime = fTotalFireTime - MakeFloat(iTime) / 1000.0;
	}
	bool needstopall = false;
	float fX, fY, fZ;
	if(!LAi_IsBoardingProcess() && !ownDeckStarted())
	{
	// ccc firedrill
		//get ship coords for graphic effects
		fX = 0; fY = 0; fZ = 0;
		fX=stf(rOurCharacter.Ship.pos.x);
		fY=stf(rOurCharacter.Ship.pos.y);
		fZ=stf(rOurCharacter.Ship.pos.z);
		// NK add AI firedrill 05-04-19
		bool dofiredrill = CheckAttribute(rOurCharacter,"firedrill");
		bool ismc = true;
		if(iOurCharacterIndex != GetMainCharacterIndex())
		{
			ismc = false;
			float stopch = stf(rOurCharacter.TmpPerks.BasicBattleState) * AI_FIREDRILL_CHANCE * bring2Range(0.1, 2.0, 0.0, 1.0, stf(rOurCharacter.TmpSkill.Leadership)); // yes, leadership. Persuading men to brave the fire.
			//trace("Firedrill: char " + rOurCharacter.id + " of " + rOurCharacter.ship.name + " has a fire. Chance to stop is " + stopch);
			if(frnd() < stopch) { dofiredrill = true; }
		}
		// NK add AI <--
		if (ENABLE_FIREDRILL == 0) dofiredrill = true; // KK
		if ((dofiredrill == true) && (fTotalFireTime > 0.0)) 	//checks marker if "C" was hit & fire burning MAR18 // KK
		// NK <--
		{
// KK -->
			int casualties = 0;
			if (ENABLE_FIREDRILL == 1) {
				casualties = makeint(sti(rOurCharacter.Ship.Crew.Quantity)* rand(3)/100 );	// a few firefighters may die
				RemoveCharacterCrew(rOurCharacter,casualties);
			}
// <-- KK
			
			fTotalFireTime = 0.0;	// for code below
			// NK stop particles:
			string tmpstr;
			
			int id;
			aref arShipObject = GetCharacter(iOurCharacterIndex);
			int maxplaces; SendMessage(arShipObject, "le", MSG_SHIP_GET_NUM_FIRE_PLACES, &maxplaces);
			for(int p = 0; p < maxplaces; p++)
			{
				tmpstr = p;
				if(CheckAttribute(rOurCharacter,"fireplaces."+tmpstr)) // should always be true
				{
					 id = sti(rOurCharacter.fireplaces.(tmpstr));
					StopSound(id, 500);
					DeleteAttribute(rOurCharacter, "fireplaces."+tmpstr);
					//trace("stopped sound, deleting fire");
					//SendMessage(arShipObject, "llsslf", MSG_SHIP_ACTIVATE_FIRE_PLACE, iFirePlaceIndex, "ship_smoke", "ship_fire", id, 0.5); // should disable fire, I hope. Seems not to work as of 05-04-22
					//trace("deleted fire")
				}
				//else needstopall = true;
			}
			tmpstr = iFirePlaceIndex;
			//if(needstopall)
			//{
				DeleteParticles();	// stops firegraphics, albeit ALL :(		
				InitParticles();	// reinits firegraphics
			//}
			// NK do them one by one <--
			// no need for this line - NK 05-04-19 PostEvent(SHIP_FIRE_DAMAGE, iTime, "lllf", iOurCharacterIndex, iBallCharacterIndex, iFirePlaceIndex, 0.0);
			// Stopps application of firedamagecode for THIS particular fireplace

			// Soundeffects, nice graphics for firefighting and messages
			if(ismc) // NK for AI
			{
				PlaySound("objects\shipcharge\ship_bow.wav");
				PlaySound("ambient\shipyard\axe.wav");
			}
			else
			{
				Play3DSound("clear_for_action", fX, fY, fZ);
				Play3DSound("objects\abordage\abordage_loosing.wav", fX, fY, fZ);
				Play3DSound("objects\shipcharge\ship_bow.wav", fX, fY, fZ);
				Play3DSound("ambient\shipyard\axe.wav", fX, fY, fZ);
				//Ship_SetSailState(iOurCharacterIndex, 0.0);
				//if(GUN_DOUSING==1) Ship_ChangeCharge(&rOurCharacter, sti(rOurCharacter.Ship.Cannons.Charge.Type));
				//trace("fire doused!");
			}
					
			CreateParticleSystemX("ball_splash",fX-2, fY+5, fZ, 3.0, 7.0, 0.0, 0);
			CreateParticleSystemX("ball_splash",fX+2, fY+6, fZ, 0.0, 7.0, 3.0, 0);
			CreateParticleSystemX("ball_splash",fX, fY+7, fZ+2, -2.0, 8.0, 0.0, 0);
			CreateParticleSystemX("ball_splash",fX, fY+5, fZ-2, 0.0, 9.0, -3.0, 0);
			CreateParticleSystemX("gunfire",fX-3, fY+3, fZ-1, 6.0, 4.0, 0.0, 0);
			CreateParticleSystemX("gunfire",fX+3, fY+4, fZ+1, 0.0, 4.0, 7.0, 0);
			CreateParticleSystemX("gunfire",fX-1, fY+5, fZ+3, -6.0, 5.0, 0.0, 0);
			CreateParticleSystemX("gunfire",fX+1, fY+3, fZ-3, 0.0, 5.0, -7.0, 0); 
			
			if(ismc) Log_SetStringToLog(TranslateString("", "One fire doused!") + " " + casualties + " " + TranslateString("", "men burnt")) + "..."; // KK
		}
	// ccc firedrill end
	}


	if (ENABLE_FIREDRILL == 1 && fTotalFireTime > 0.0) // KK
	{
		PostEvent(SHIP_FIRE_DAMAGE, iTime, "lllf", iOurCharacterIndex, iBallCharacterIndex, iFirePlaceIndex, fTotalFireTime);
		if(!bAbordageStarted)
		{
			if(sti(rOurCharacter.Index) == GetMainCharacterIndex()&&FIREDAMAGE>1) {Log_SetStringToLog(TranslateString("","Fire still burning! Execute firedrill procedure immediately!"));}	//ccc firedrill MAR18 // KK
			if(needstopall) CreateParticleSystem("ball_impact", fX, fY, fZ, 0.0, 0.0, 0.0, 0); // ccc some smoke if fireparticle was deleted above, NK do only if needed
		}
	}
	//LanguageCloseFile(tmpLangFileID);
}

void Ship_Serious_Boom(float x, float y, float z)
{
	CreateBlast(x,y,z);
	CreateParticleSystem("blast_inv", x, y, z, 0.0, 0.0, 0.0, 0);
	Play3DSound("ship_explosion", x, y, z);
}

void Ship_SetFantomData(ref rFantom)
{
	ref		rCannon, rBaseShip;

	rBaseShip = GetShipByID(rFantom.Ship.Type); // PS
	aref arship; makearef(arship, rFantom.ship); // PRS3

	int iMinCrew = sti(GetLocalShipAttrib(arship, rBaseShip, "MinCrew"));
	int iMaxCrew = sti(GetLocalShipAttrib(arship, rBaseShip, "MaxCrew"));

	int iBaseHP = sti(GetLocalShipAttrib(arship, rBaseShip, "HP"));
	int iBaseSP = sti(GetLocalShipAttrib(arship, rBaseShip, "SP"));

	int iCapacity = sti(GetLocalShipAttrib(arship, rBaseShip, "Capacity"));

// START MOD Code by Stone-D : 30/07/2003
//	rFantom.Ship.Crew.Quantity = iMinCrew + rand(iMaxCrew - iMinCrew - 1);
	rFantom.Ship.Crew.Quantity = iMaxCrew - makeint(rand(makeint(iMaxCrew/5)));
	if(!CheckAttribute(rFantom.skill,"Leadership")) rFantom.skill.Leadership = 1; // MAXIMUS 28.07.2006
	rFantom.Ship.Crew.morale = makeint((rand(sti(rFantom.skill.Leadership))+1)*10);
	rFantom.Ship.Crew.morale = makeint(rand(sti(rFantom.Ship.Crew.morale))+rand(50)+25); // Gotcha ya bast!
	if (CheckAttribute(rFantom, "Ship.Masts")) { DeleteAttribute(rFantom, "Ship.Masts"); }
	if (CheckAttribute(rFantom, "Ship.Blots")) { DeleteAttribute(rFantom, "Ship.Blots"); }
	if (CheckAttribute(rFantom, "Ship.Sails")) { DeleteAttribute(rFantom, "Ship.Sails"); }

// Next part unnecessary, but entertaining.
// The following code only takes effect if rFantom does not have FantomType of trade, war or pirate, see CheckAttribute() below
	if (rand(3) > 1)	// 50% of the time
	{
		rFantom.Ship.HP = iBaseHP - rand(iBaseHP / 2);		// remove up to half the ship's HP
		rFantom.Ship.SP = iBaseSP - rand( (iBaseSP*((sti(rFantom.Ship.HP)/iBaseHP)*2)) ); // this does nothing: sti("50")/100 = 0
//		Whack_Ship(rFantom); // Stone-D : Not working 27/07/2003
	}
	if (sti(GetLocalShipAttrib(arship, rBaseShip, "HP")) == iBaseHP && rand(3) > 1)		// if HP hasn't been changed, then 50% of the time // KK
	{
		rFantom.Ship.SP = iBaseSP - rand(iBaseSP / 2);		// remove up to half of the ship's SP
	}
//	MakeSailDmg(sti(rFantom.index), iBaseSP - rFantom.Ship.SP); // Stone-D : Not working // LDH, moved to BattleInterface
// END MOD Code by Stone-D : 30/07/2003
	//NK -->
	//MSM switch based on fantomtype.
	float hulldmg = 1.0;
	float saildmg = 1.0;
	if(CheckAttribute(rFantom,"FantomType"))
	{
		switch(rFantom.FantomType)
		{
			case "trade":
				rFantom.Ship.Crew.morale = makeint(25.0 * (1.0 + makefloat(rand(GetCharacterSkill(rFantom,SKILL_LEADERSHIP)/5)+1)/2.0));
				rFantom.Ship.Crew.Quantity = makeint(iMinCrew + Rand(iMinCrew) + Rand(iMinCrew));
				hulldmg = 0.5;
				saildmg = 0.3;
			break;

			case "war":
				rFantom.Ship.Crew.morale = makeint(40.0 * (1.0 + makefloat(rand(GetCharacterSkill(rFantom,SKILL_LEADERSHIP)/5)+1)/2.0));
				rFantom.Ship.Crew.Quantity = makeint(iMinCrew*2 + (Rand(iMinCrew) + Rand(iMinCrew)*1.5));
				hulldmg = 0.25;
				saildmg = 0.25;
			break;

			case "pirate":
				rFantom.Ship.Crew.morale = makeint(50.0 * (1.0 + makefloat(rand(GetCharacterSkill(rFantom,SKILL_LEADERSHIP)/2)+1)/5.0));
				rFantom.Ship.Crew.Quantity = makeint(iMinCrew*2 + (Rand(iMinCrew*2) + Rand(iMinCrew)));
				hulldmg = 0.5;
				saildmg = 0.2;
			break;
		}
		hulldmg *= (1.5 - 0.2 * makefloat(GetDifficulty()));
		saildmg *= (1.5 - 0.2 * makefloat(GetDifficulty()));
		rFantom.Ship.HP = iBaseHP - makeint(makefloat(iBaseHP) * frand(hulldmg));
		rFantom.Ship.SP = iBaseSP - makeint(makefloat(iBaseSP) * frand(saildmg));
	}
	if (sti(rFantom.Ship.Crew.morale)>MORALE_MAX) rFantom.Ship.Crew.morale=MORALE_MAX;
	if (sti(rFantom.Ship.Crew.morale)<MORALE_MIN) rFantom.Ship.Crew.morale=MORALE_MIN;
	//Log_SetStringToLog("Type " + rFantom.FantomType);
	// NK <--

	// LDH make sure sail damage gets applied properly -->
	float tempDamage = iBaseSP - stf(rFantom.Ship.SP);
	rFantom.Ship.SP = iBaseSP;			// damage will be applied later in BattleInterface
	rFantom.NeedSailDmg = tempDamage;
	// LDH <--
}

void CharacterUpdateShipFromBaseShip(int iCharacterIndex)
{
	ref rCharacter, rCannon, rBaseShip;

	rCharacter = GetCharacter(iCharacterIndex);
	rBaseShip = GetShipByID(rCharacter.Ship.Type); // PS
	aref arship; makearef(arship, rCharacter.ship); // PRS3

	int iMinCrew = sti(GetLocalShipAttrib(arship, rBaseShip, "MinCrew")); // PRS3
	int iMaxCrew = sti(GetLocalShipAttrib(arship, rBaseShip, "MaxCrew")); // PRS3

	if (!CheckAttribute(rCharacter,"Ship.Crew.Quantity")) { rCharacter.Ship.Crew.Quantity = iMinCrew + rand(iMaxCrew - iMinCrew - 1); }
	if (!CheckAttribute(rCharacter,"Ship.HP")) { rCharacter.Ship.HP = GetCharacterShipHP(rCharacter); } // PRS3
	if (!CheckAttribute(rCharacter,"Ship.SP")) { rCharacter.Ship.SP = rBaseShip.SP; }

	if (!CheckAttribute(rCharacter,"Ship.Cannons.Charge.Type"))
	{
		if(iCharacterIndex == GetMainCharacterIndex()) { rCharacter.Ship.Cannons.Charge.Type = DEFAULT_CHARGE; } // NK 04-09-16 so player can set default charge
		else { rCharacter.Ship.Cannons.Charge.Type = GOOD_BOMBS; }
	}

	//Trace("Init cindex = " + iCharacterIndex + ", ship HP = " + rCharacter.Ship.HP + ", id = " + rCharacter.id + ", idx = " + rCharacter.index);
	if (!CheckAttribute(rCharacter,"Ship.Cannons.Type"))
	{
		if(USE_REAL_CANNONS) { rCharacter.Ship.Cannons.Type = CANNON_TYPE_LONG_LBS9; }; // KNB
		else { rCharacter.Ship.Cannons.Type = CANNON_TYPE_CULVERINE_LBS12; };
	}

	rCharacter.Ship.Cannons.Borts = "";
	// NK cannon qty 05-04-18 -->
	//if(USE_REAL_CANNONS)
	//{
		if(!CheckAttribute(rCharacter,"ship.Cannons.Borts.cannonf.qty")) rCharacter.ship.Cannons.Borts.cannonf.qty = rBaseShip.Cannons.Borts.cannonf.qty;
		if(!CheckAttribute(rCharacter,"ship.Cannons.Borts.cannonb.qty")) rCharacter.ship.Cannons.Borts.cannonb.qty = rBaseShip.Cannons.Borts.cannonb.qty;
		if(!CheckAttribute(rCharacter,"ship.Cannons.Borts.cannonl.qty")) rCharacter.ship.Cannons.Borts.cannonl.qty = rBaseShip.Cannons.Borts.cannonl.qty;
		if(!CheckAttribute(rCharacter,"ship.Cannons.Borts.cannonr.qty")) rCharacter.ship.Cannons.Borts.cannonr.qty = rBaseShip.Cannons.Borts.cannonr.qty;
		//crew based:
		Ship_SetCannonQtyByCrew(&rCharacter);
	//}
	// NK <--
}

void Ship_CheckMainCharacter()
{
	aref	arUpdate;
	ref		rIsland;
	int		i, iRelation;
	float	x, z, fBestDistance, fDistance;
	float	fMinEnemyDistance = 1000000.0;
	bool	bAbordageStartNow = false;

	PostEvent(SHIP_CHECK_RELOAD_ENABLE, 1000);

	ref rCharacter = GetMainCharacter();

	// if abordage launched, return
	if (bAbordageStarted) { return; }

	// if interface launched, return
	if (sti(InterfaceStates.Launched)) { return; }

	// if main character is dead - skip
	if (LAi_IsDead(rCharacter)) { return; };

	iStormLockSeconds = iStormLockSeconds - 1;
	if (iStormLockSeconds < 0) { iStormLockSeconds = 0; }
	// NK 04-09-16 implement rough timer function
	int temptime = 1; if(CheckAttribute(rCharacter,"seatime")) { temptime += sti(rCharacter.seatime); } rCharacter.seatime = temptime;
	//locTmpTime = temptime; // also update loc tmp time.
	AddSeaTimeToCurrent();

	Ship_CheckMorale(GetMainCharacterIndex()); // NK for morale check 04-09-16 <--

	bAbordageFortCanBe = false;
	bAbordageShipCanBe = false;
	bAbordageTownCanBe = false;
	bCanEnterToLand = false;
	bDisableSailTo = false;
	sIslandLocator = "";
	sIslandID = "";

	makearef(arUpdate, rCharacter.SeaAI.Update);

	if (!CheckAttribute(arUpdate, "Ships")) { return; }
	if (!CheckAttribute(arUpdate, "Forts")) { return; }

	x = stf(rCharacter.Ship.Pos.x);
	z = stf(rCharacter.Ship.Pos.z);

	float fOurHP = Ship_GetHP(rCharacter);
	float fOurFencing = pow2(stf(rCharacter.TmpSkill.Fencing),FENCING_BOARD_PWR); // NK scale by fencingpwr 05-04-20
	float fOurGrappling = stf(rCharacter.TmpSkill.Grappling);
	float fOurCrewFencing = (0.1 + fOurFencing * stf(rCharacter.Ship.Crew.Quantity) * (1.0 - ((1.0 - fclamp(0.1, 10.0, stf(rCharacter.ship.crew.morale)/(MORALE_MAX-MORALE_MIN) * 2))/2.0))); // NK 05-04-20 add morale

	float fOurBoxSizeX = stf(rCharacter.Ship.BoxSize.x) / 2.0;

	bool bGrapplingProfessional = sti(rCharacter.TmpPerks.GrapplingProfessional);

	// max abordage parameters for our player
		float fGrapplingPerk = AIShip_isPerksUse(rCharacter.TmpPerks.LongRangeGrappling, 1.0, 1.15);
		fGrapplingPerk = AIShip_isPerksUse(rCharacter.TmpPerks.GrapplingProfessional, fGrapplingPerk, 1.25);
		float fOurMaxAbordageDistance, fOurMaxAbordageAngle, fOurMaxAbordageSpeed; // KNB -->
		if(USE_REAL_SAILING)
		{
			fOurMaxAbordageDistance = fGrapplingPerk * Bring2Range(8.0, 25.0, 0.0, 1.0, fOurGrappling); // was 15.0, 40.0
			fOurMaxAbordageAngle = Bring2Range(0.80-makefloat(bGrapplingProfessional)*0.1, 0.60-makefloat(bGrapplingProfessional)*0.1, 0.0, 1.0, fOurGrappling);// was 0.70, 0.45. NK add Grapp Prof, because it's not used lower.
			fOurMaxAbordageSpeed = Bring2Range(2.0, 4.0, 0.0, 1.0, fOurGrappling); // was 3.0, 5.0
		}
		// KNB <--
		else
		{
			fOurMaxAbordageDistance = fGrapplingPerk * Bring2Range(15.0, 40.0, 0.0, 1.0, fOurGrappling);
			fOurMaxAbordageAngle = Bring2Range(0.70, 0.45, 0.0, 1.0, fOurGrappling);
			fOurMaxAbordageSpeed = Bring2Range(3.0, 5.0, 0.0, 1.0, fOurGrappling);
		}

	string sPlayerLocation = rCharacter.location;
	string sPlayerLocator = rCharacter.location.locator;

	// find island
	int iIslandIndex = FindIsland(sPlayerLocation);
	if (iIslandIndex >= 0)
	{
		rIsland = GetIslandByIndex(iIslandIndex);
	}

	aref aShips, aForts;
	makearef(aShips, arUpdate.Ships);
	makearef(aForts, arUpdate.Forts);

	// check forts for abordage
	int iFortsNum = GetAttributesNum(aForts);
	fBestDistance = 10000000.0;
	for (i=0; i<iFortsNum; i++)
	{
		aref aFort = GetAttributeN(aForts, i);
		iRelation = sti(aFort.relation);
		if (iRelation == RELATION_FRIEND) { continue; }
		ref rFortCharacter = GetCharacter(sti(aFort.idx));
		if (LAi_IsDead(rFortCharacter)) { continue; }
		aref aRLoc = FindIslandReloadLocator(sPlayerLocation, rFortCharacter.location.locator);
		float fRadius = stf(aRLoc.Radius);
		fDistance = stf(aFort.Distance);
		int iFortMode = sti(rFortCharacter.Fort.Mode);

		switch (iFortMode)
		{
			case FORT_NORMAL:
				if (iRelation == RELATION_ENEMY)
				{
					if (fMinEnemyDistance > fDistance) { fMinEnemyDistance = fDistance; }
				}
			break;
			case FORT_DEAD:
				if (fDistance > fRadius) { continue; }
				if (fBestDistance > fDistance)
				{
					fBestDistance = fDistance;

					bCanEnterToLand = true;

					sIslandID = rIsland.id;
					makearef(arIslandReload, rIsland.reload);
					sIslandLocator = aRLoc.GoAfterBoard.location;
				}
			break;
			case FORT_ABORDAGE:
				if (iRelation == RELATION_ENEMY)
				{
					if (fMinEnemyDistance > fDistance) { fMinEnemyDistance = fDistance; }
				}
				if (fDistance > fRadius) { continue; }
				if (!Character_IsAbordageEnable(rFortCharacter)) { continue; }

				if (fBestDistance > fDistance)
				{
					fBestDistance = fDistance;

					bAbordageFortCanBe = true;
					sAbordageLocator = rFortCharacter.location.locator;
					iAbordageFortEnemyCharacter = sti(rFortCharacter.index);
				}
			break;
		}
	}

// check ships for abordage
	int iShipsNum = GetAttributesNum(aShips);
	fBestDistance = 10000000.0;
	aref aShip; // KK
	ref rShipCharacter; // KK
	for (i=0; i<iShipsNum; i++)
	{
		aShip = GetAttributeN(aShips, i);

		rShipCharacter = GetCharacter(sti(aShip.idx));

		if (LAi_IsDead(rShipCharacter)) { continue; } // TIH moved up here for program speed Aug24'06
		if (!Character_IsAbordageEnable(rShipCharacter)) { continue; } // TIH moved up here for program speed Aug24'06
		if (IsCompanion(rShipCharacter)) { continue; } // TIH don't even do this stuff with companion ships Aug24'06

		fDistance = stf(aShip.Distance);		// distance between player ship and this ship
		iRelation = sti(aShip.relation);

		// NK surrender 05-04-20 -->
		if (iRelation == RELATION_ENEMY && fMinEnemyDistance > fDistance) { fMinEnemyDistance = fDistance; }
		if (iRelation != RELATION_ENEMY && !CheckAttribute(rShipCharacter,"surrendered")) { continue; }
		//if (fMinEnemyDistance > fDistance) { fMinEnemyDistance = fDistance; }
		// NK <--

		float fAng = stf(aShip.d_ay);						// it's dot.ay between ships
		float fRelativeSpeed = stf(aShip.RelativeSpeed);	// it's relative speed about player ship
		float fEnemyBoxSizeX = stf(rShipCharacter.Ship.BoxSize.x) / 2.0 * (1.0 + makefloat(CheckAttribute(rShipCharacter,"surrendered"))*BOARD_SURR_DISTANCE_MULT); //so you can board surrednered ships from afar. NK 05-04-23 //KB - corrected
		bool bAngleTest = abs(fAng) > fOurMaxAbordageAngle && fRelativeSpeed < fOurMaxAbordageSpeed;
		if(!USE_REAL_SAILING) bAngleTest = bAngleTest || bGrapplingProfessional; // KNB
		//if (abs(fAng) > fOurMaxAbordageAngle && fRelativeSpeed < fOurMaxAbordageSpeed)
		float fAbordageDistance = fDistance - fEnemyBoxSizeX - fOurBoxSizeX;
		// NK check for surrender 05-04-21 (reorganize this so dist checked first, angle second)
		if (fAbordageDistance < fOurMaxAbordageDistance && fBestDistance > fDistance)
		{
			
			if(CheckAttribute(rShipCharacter,"surrendered"))
			{
				/*string sGroupID = rShipCharacter.surrendered.oldgroup;
				bool alive = true;
				int gidx = Group_FindGroup(sGroupID);
				if(gidx == -1) alive = false;
				if(alive) alive = Group_isDead(sGroupID);
				if(alive)
				{
					ref gcmdr = Group_GetGroupCommander(sGroupID);
					if(SeaAI_GetRelation(sti(gcmdr.index), GetMainCharacterIndex()) != RELATION_ENEMY) { continue; }
				}
				else { */
					// TIH --> use a better function for relation checking to determine if you can board a surrendered ship Aug24'06
					//Trace("BDCHK: surrendered ship boarding check: " + rShipCharacter.id + " oldnation: " + rShipCharacter.oldnation + " curnation: " + rShipCharacter.nation );

					// attempt 7: (couldn't seem to board ANYONE who surrendered)
					//int tmpNation = sti(rShipCharacter.nation);
					//rShipCharacter.nation = rShipCharacter.oldnation;
					//int tmpRelation = SeaAI_GetRelation(sti(rShipCharacter.index), GetMainCharacterIndex());
					//rShipCharacter.nation = tmpNation;
					//if (tmpRelation != RELATION_ENEMY) continue;

					// LDH unable to board surrendered ship test
					// Relations 0=friend, 1=neutral, 2=enemy
					// Flags 0=England, 1=France, 5=Portugal
					// 5, 1, 0, 
					//logit("Surrendered ship: flag = " + GetCurrentFlag() + ", oldnation = " + rShipCharacter.oldnation + ", recognized = " + bRecognized);
					// 0 with English (Portugese!!) flag, 1 with personal flag
					//logit("NationRelation = " + GetNationRelation(GetCurrentFlag(),sti(rShipCharacter.oldnation)));
					// 2
					//logit("ActualRelation = " + GetActualNationRelation2MainCharacter(sti(rShipCharacter.oldnation)));

					// attempt 6: (seems to work except for one damned french ship - but he is set to never surrender now, so who cares?)

					int tmpRelation = "";
					if (ENABLE_FLAGS == 1) {
						if ( GetCurrentFlag() != PERSONAL_NATION && !bRecognized ) 
								{ tmpRelation = GetNationRelation(GetCurrentFlag(),sti(rShipCharacter.oldnation)); 		
								/*Trace("BDCHK: GetNationRelation: " + tmpRelation + "  (" + RELATION_ENEMY + ")" );*/}
						else 	{ tmpRelation = GetActualNationRelation2MainCharacter(sti(rShipCharacter.oldnation)); 	
								/*Trace("BDCHK: GetActualNationRelation2MainCharacter: " + tmpRelation + "  (" + RELATION_ENEMY + ")" );*/}
					} 
					else 		{ tmpRelation = GetNationRelation2MainCharacter(sti(rShipCharacter.oldnation)); 		
								/*Trace("BDCHK: GetNationRelation2MainCharacter: " + tmpRelation + "  (" + RELATION_ENEMY + ")" );*/}
					if (tmpRelation != RELATION_ENEMY) continue;

					// attempt 5:
					//int tmpnation = sti(rShipCharacter.nation);
					//rShipCharacter.nation = rShipCharacter.oldnation;
					//if(GetRelation(rCharacter.index,rShipCharacter.index) != RELATION_ENEMY) { rShipCharacter.nation = tmpnation; continue; } 
					//rShipCharacter.nation = tmpnation;
					// attempt 4:
					//if(SeaAI_GetRelation(sti(rShipCharacter.index), GetMainCharacterIndex()) != RELATION_ENEMY) { continue; }
					// attempt 3:
					//if(GetRMRelationType(GetRMRelation(rCharacter, sti(rShipCharacter.oldnation))) != RELATION_ENEMY) { continue; }
					// attempt 2:
					//if(GetActualNationRelation2MainCharacter(sti(rShipCharacter.oldnation)) != RELATION_ENEMY) { continue; }
					// attempt 1:
					//if(GetNationRelation2MainCharacter(sti(rShipCharacter.oldnation)) != RELATION_ENEMY) { continue; }
					// TIH <--
				//}
				bAbordageShipCanBe = true;
				iAbordageShipEnemyCharacter = sti(rShipCharacter.index);
				fBestDistance = fDistance;
				continue;
			}
			if (bAngleTest)
			{
				// NK surrender 05-04-20 <--
				// abordage enable
				fBestDistance = fDistance;
				bAbordageShipCanBe = true;
				iAbordageShipEnemyCharacter = sti(rShipCharacter.index);

				// maybe other character want abordage us?
				float fEnemyFencing = pow2(stf(rShipCharacter.TmpSkill.Fencing),FENCING_BOARD_PWR); // NK scale by fencingpwr 05-04-20
				float fEnemyCrewFencing = (0.1 + fEnemyFencing * stf(rShipCharacter.Ship.Crew.Quantity) * (1.0 - ((1.0 - fclamp(0.1, 10.0, stf(rShipCharacter.ship.crew.morale)/(MORALE_MAX-MORALE_MIN) * 2))/2.0))); // NK add morale 05-04-20
				float fRatio = fEnemyCrewFencing / fOurCrewFencing;
				if (sti(rShipCharacter.nation) == PIRATE) { fRatio = fRatio * 1.4; }
				if (fRatio > 1.2)
				{
					bAbordageStartNow = true;
					break;
				}
			}
			continue;
		}
	}

// check reload to locations possibility
	if (iIslandIndex >= 0 && fMinEnemyDistance > MIN_ENEMY_DISTANCE_TO_DISABLE_ENTER_2_LOCATION)
	{
		if (Island_isReloadEnableGlobal(sPlayerLocation))
		{
			aref arReload;
			makearef(arReload, rIsland.Reload);

			int iNumLocators = GetAttributesNum(arReload);
			for (int j=0; j<iNumLocators; j++)
			{
				aref arLocator;
				arLocator = GetAttributeN(arReload, j);
				string sLocatorName = GetAttributeName(arLocator);
				if (Island_isReloadFort(sPlayerLocation, arLocator.name)) { continue; }
				if (!Island_isReloadEnableLocal(sPlayerLocation, arLocator.name)) { continue; }
				float x1 = stf(rIsland.reload.(sLocatorName).x);
				float z1 = stf(rIsland.reload.(sLocatorName).z);
				float r = stf(rIsland.reload.(sLocatorName).radius);
				if (sqrt(sqr(x1 - x) + sqr(z1 - z)) < r)
				{
					bCanEnterToLand = true;
					sIslandID = rIsland.id;
					makearef(arIslandReload, rIsland.reload);
					sIslandLocator = rIsland.reload.(sLocatorName).name;
// KK -->
					string town = GetTownIDFromLocID(rIsland.reload.(sLocatorName).go);
					iFortsNum = GetTownNumForts(town);
					if (CAPTURE_COLONIES == 1 && iFortsNum == 0) {
						int iNation = sti(rCharacter.nation);
						int tNation = GetTownNation(town);
						if (GetNationRelation(iNation, tNation) == RELATION_ENEMY) {
							bAbordageTownCanBe = true;
							for (i = 0; i < iShipsNum; i++) {
								aShip = GetAttributeN(aShips, i);

								rShipCharacter = GetCharacter(sti(aShip.idx));
						
								if (LAi_IsDead(rShipCharacter)) continue;
								if (!Character_IsAbordageEnable(rShipCharacter)) continue;
								if (IsCompanion(rShipCharacter)) continue;
								if (sti(rShipCharacter.nation) == tNation) {
									bAbordageTownCanBe = false;
									break;
								}
							}
							if (bAbordageTownCanBe) {
								iAbordageTownEnemyCharacter = GetTownGovernorIndex(town);
								if (iAbordageTownEnemyCharacter < 0) iAbordageTownEnemyCharacter = GetCharacterIndex("Captured governor");
								characters[iAbordageTownEnemyCharacter].town = town;
							}
						}
					}
// <-- KK
					break;
				}
			}
		}
	}

// disable any abordage if storm
	if (Whr_IsStorm())
	{
		bAbordageStartNow = false;
		bAbordageFortCanBe = false;
		bAbordageShipCanBe = false;
		bAbordageTownCanBe = false; // KK
	}

// Enter 2 map enable/disable
// Peoples on ship mode update
	int iPrevShipPOSMode = sti(rCharacter.Ship.POS.Mode);
	rCharacter.Ship.POS.Mode = SHIP_SAIL;

	bDisableMapEnter = false;
	if (fMinEnemyDistance < MIN_ENEMY_DISTANCE_TO_DISABLE_MAP_ENTER)
	{
		bCanEnterToLand = false;
		bDisableMapEnter = true;

		rCharacter.Ship.POS.Mode = SHIP_WAR;
	}

	if (iStormLockSeconds)
	{
		bDisableMapEnter = true;
		bCanEnterToLand = false;
		bDisableSailTo = true;

		rCharacter.Ship.POS.Mode = SHIP_STORM;
	}

	// new music
	// NK PGMus handling 05-04-18 -->
	// make general 05-04-29
	if(CheckAttribute(rCharacter,"musfix"))
	{
		/*SetMusicNoPause(rCharacter.pgmus.cur);
		oldMusicName = rCharacter.pgmus.old;
		DeleteAttribute(&rCharacter,"pgmus")*/
		SetSchemeForSea();
		iPrevShipPOSMode = SHIP_SAIL;
		DeleteAttribute(rCharacter,"musfix");
	}
	// NK <--
	if (sti(rCharacter.Ship.POS.Mode) != iPrevShipPOSMode)
	{
		switch (sti(rCharacter.Ship.POS.Mode))
		{
			case SHIP_SAIL:
				if (!Whr_IsStorm()) { Sound_OnSeaAlarm(false); }
				if (iPrevShipPOSMode == SHIP_WAR) { PlayStereoOGG("music_ship_victory"); }
				if (iPrevShipPOSMode == SHIP_STORM)
				{
					FadeOutMusic(3000);
					PlayStereoOGG("music_End_storm");
				}
			break;
			case SHIP_WAR:
				Sound_OnSeaAlarm(true);
			break;
		}
	}

// if quest disable enter 2 map
	if (bQuestDisableMapEnter) { bDisableMapEnter = true; }

// Start abordage automatically
	if (bAbordageStartNow) { Sea_AbordageLoad(SHIP_ABORDAGE, false); }
}

void Ship_OnStrandNext()
{
	ref	rCharacter = GetCharacter(GetEventData());
	if (LAi_IsDead(rCharacter)) { return; }

	bool bOnStrand = sti(rCharacter.Ship.Strand);
	if (!bOnStrand)
	{
		int iSoundID = sti(rCharacter.Ship.Strand.SndID);
		if (iSoundID != 0) { StopSound(sti(rCharacter.Ship.Strand.SndID), 3000); }
		rCharacter.Ship.Strand.SndID = SOUND_INVALID_ID;
		return;
	}

	if (!sti(rCharacter.TmpPerks.SandbankManeuver))
	{
		float fCurrentSpeedZ = stf(rCharacter.Ship.Speed.z);

		float fHP = 4.0 * fCurrentSpeedZ / 7.0;
		Ship_ApplyHullHitpoints(rCharacter, fHP, KILL_BY_ISLAND, -1);
	}
	PostEvent(SHIP_TO_STRAND_NEXT, 100 + rand(100), "l", sti(rCharacter.index));
}

void Ship_OnStrand()
{
	ref rCharacter = GetCharacter(GetEventData());
	bool bNewStrand = GetEventData();

	bool bPrevStrand = sti(rCharacter.Ship.Strand);

	rCharacter.Ship.Strand = bNewStrand;

	float x = stf(rCharacter.Ship.Pos.x);
	float y = stf(rCharacter.Ship.Pos.y);
	float z = stf(rCharacter.Ship.Pos.z);

	if (bPrevStrand == 0 && bNewStrand == 1)
	{
		PostEvent(SHIP_TO_STRAND_NEXT, 100, "l", sti(rCharacter.index));
		rCharacter.Ship.Strand.SndID = Play3DSoundComplex("ship_aground", x, y, z, true, false);
	}
}

// Small boom
void Ship_DetonateSmall()
{
	ref		rCharacter = GetCharacter(GetEventData());
	// NK allow this to not repeat forever 05-04-18
	if(CheckAttribute(rCharacter,"maxsmallboomtimes"))
	{
		int bt = sti(rCharacter.maxsmallboomtimes);
		if(bt) { bt--; rCharacter.maxsmallboomtimes = bt; }
		else { DeleteAttribute(rCharacter,"maxsmallboomtimes"); return; }
	}
	// NK <--
	aref	arCharShip; makearef(arCharShip, rCharacter.Ship);

	float x = stf(arCharShip.Pos.x);
	float y = stf(arCharShip.Pos.y);
	float z = stf(arCharShip.Pos.z);

	if (y > -3.0)
	{
		int iSClass = GetCharacterShipClass(rCharacter);
		PostEvent(SHIP_DETONATE_SMALL, 200 + iSClass * 300 + rand(900), "l", sti(rCharacter.index));
	}

	if (bAbordageStarted) { return; }
	if (sti(InterfaceStates.Launched)) { return; }

	float bx = arCharShip.BoxSize.x;
	float by = arCharShip.BoxSize.y;
	float bz = arCharShip.BoxSize.z;

	float fCos = cos(stf(arCharShip.Ang.y));
	float fSin = sin(stf(arCharShip.Ang.y));

	for (int i=0; i < 1 + rand(1); i++)
	{
		float x1, y1, z1;
		x1 = (frnd() - 0.5) * bx;
		z1 = (frnd() - 0.5) * bz;

		RotateAroundY(&x1, &z1, fCos, fSin);

		y1 = 1.0 + y + frnd() * 3.0;
		Ship_Serious_Boom(x1 + x, y1, z1 + z);
		CreateParticleSystem("blast", x1 + x, y1, z1 + z, 0.0, 0.0, 0.0, 0);
	}
}

void Ship_Turn180(ref rCharacter)
{
	rCharacter.TmpPerks.Turn = true;
	rCharacter.TmpPerks.Turn.Angle = PI;
}

// Big Boom on ship
void Ship_Detonate(ref rCharacter, bool bMakeSmallBoom, bool bMakeDead)
{
	if (!CheckAttribute(rCharacter, "Ship.Pos.x"))
	{
		Trace("Ship_Detonate: Can't find Ship.Pos in character with id: " + rCharacter.id);
		return;
	}
	//trace("for char " + rCharacter.id + " of ship " + rCharacter.ship.name + " do Ship_Detonate");

	aref	arCharShip; makearef(arCharShip, rCharacter.Ship);

	float x = stf(arCharShip.Pos.x);
	float y = stf(arCharShip.Pos.y);
	float z = stf(arCharShip.Pos.z);

	int iMinBoom = MakeInt(Bring2Range(6.0, 2.0, 1.0, 7.0, stf(GetCharacterShipClass(rCharacter))));

	float bx = arCharShip.BoxSize.x;
	float by = arCharShip.BoxSize.y;
	float bz = arCharShip.BoxSize.z;

	float fCos = cos(stf(arCharShip.Ang.y));
	float fSin = sin(stf(arCharShip.Ang.y));

	for (int i=0; i<iMinBoom + rand(2); i++)
	{
		float x1, y1, z1;
		x1 = (frnd() - 0.5) * bx;
		z1 = (frnd() - 0.5) * bz;

		RotateAroundY(&x1, &z1, fCos, fSin);

		y1 = 1.0 + y + frnd() * 3.0;
		Ship_Serious_Boom(x1 + x, y1, z1 + z);
		CreateParticleSystem("blast", x1 + x, y1, z1 + z, 0.0, 0.0, 0.0, 0);
	}
	if (bMakeDead) { ShipDead(sti(rCharacter.Index), KILL_BY_SPY, -1); }

	if (bMakeSmallBoom) { PostEvent(SHIP_DETONATE_SMALL, 600 + rand(1000), "l", sti(rCharacter.index)); }
}

float AIShip_isPerksUse(string sPerk, float fOff, float fOn)
{
	if (sti(sPerk)) { return fOn; }
	return fOff;
}

// EVENT, calculate dynamic parameters for ship, one per second
void Ship_UpdateParameters()
{
	//int tmpLangFileID = LanguageOpenFile("interface_strings.txt");
	int		iCharacterIndex = GetEventData();
	ref		rCharacter = GetCharacter(iCharacterIndex);
	float	fSailState = GetEventData();

// NK, Amokachi -->
	/*float fX, fY, fZ;
	fX = 0; fY = 0; fZ = 0;
	if(CheckAttribute(rCharacter,"Ship.pos.x")) fX=stf(rCharacter.Ship.pos.x);
	if(CheckAttribute(rCharacter,"Ship.pos.y")) fY=stf(rCharacter.Ship.pos.y);
	if(CheckAttribute(rCharacter,"Ship.pos.z")) fZ=stf(rCharacter.Ship.pos.z);
	//Log_SetStringToLog("Character " + rCharacter.name + " " + rCharacter.lastname + " has pos: (" + fX + ", " + fY + ", " + fZ + "), angle: (" + sti(rCharacter.Ship.ang.x) + ", " + sti(rCharacter.Ship.ang.y) + ", " + sti(rCharacter.Ship.ang.z) + ").");
	ref tempchar = GetMainCharacter();*/
	if(IsMainCharacter(rCharacter))
	{
		if(!CheckAttribute(rCharacter,"LastSailState")) rCharacter.LastSailState = makeint(fSailState * 2);
		if(makeint(fSailState * 2) != sti(rCharacter.LastSailState))
		{
			switch (makeint(fSailState * 2))
			{
				case 0:
					Log_SetStringToLog(TranslateString("","Strike all sail!"));
					//Play3DSound("sails_lowered", fX, fY, fZ);
				break;

				case 1:
					Log_SetStringToLog(TranslateString("","Battle sails!"));
					//Play3DSound("sails_battle", fX, fY, fZ);
				break;

				case 2:
					Log_SetStringToLog(TranslateString("","Make all sail!"));
					//Play3DSound("full_sails", fX, fY, fZ);
				break;
			}
			if(makeInt(rCharacter.LastSailState) > fSailState * 2)
			{
				if(ENABLE_EXTRA_SOUNDS == 1)	// added by KAM after build 11
				{
				PlayStereoSound("sails_down");
				}
			}
			else
			{
				if(ENABLE_EXTRA_SOUNDS == 1)	// added by KAM after build 11
				{
					PlayStereoSound("sails_up");
				}
				// DeleteAttribute(rCharacter,"firedrill"); // ccc Firedrill
			}
			rCharacter.LastSailState = makeint(fSailState * 2);
		}
	}
// NK, Amokachi <--


	bool	bStrand = sti(rCharacter.Ship.Strand);

	bool	bDead = LAi_IsDead(rCharacter);

	aref	arTmpPerks; makearef(arTmpPerks, rCharacter.TmpPerks);

	// update temporary skill storage
	Ship_UpdateTmpSkills(rCharacter);

	Cannon_RecalculateParameters(sti(rCharacter.index));

	// some of ship parameters
	aref	arCharShip; makearef(arCharShip, rCharacter.Ship);
	ref		rShip = GetShipByType(GetCharacterShipType(rCharacter)); // PS
// NK -->
	if(IsMainCharacter(rCharacter))
	{
		bool bSinking = false;
		if(!CheckAttribute(rCharacter,"SinkState")) rCharacter.SinkState = bSinking;
		if(Ship_GetHP(rCharacter) < GetCharacterShipHP(rCharacter) * 0.1) bSinking = true; // PRS3
		if(bSinking != makeint(rCharacter.SinkState))
		{
			if(bSinking)
			{
					Log_SetStringToLog(TranslateString("","We're sinking!"));
					if(ENABLE_EXTRA_SOUNDS == 1) {			// added by KAM after build 11
					PlayStereoSound("ship_sinking");
					}						// added by KAM after build 11
			}
			rCharacter.SinkState = bSinking;
		}
	}
// NK <--

	float	fMaxSpeedZ = stf(GetLocalShipAttrib(arCharShip, &rShip, "SpeedRate")); // PRS3
	float	fMaxSpeedY = stf( GetLocalShipAttrib(arCharShip, &rShip, "TurnRate")) / 444.444;	// cool magic number :)) //PRS3
	float	fCurrentSpeedZ = stf(arCharShip.Speed.z);
	if(!CheckAttribute(arCharShip,"Cargo.load")) RecalculateCargoLoad(rCharacter); // NK 04-16 bugfix
	float	fLoad = Clampf(stf(arCharShip.Cargo.Load) / stf(GetLocalShipAttrib(arCharShip, &rShip, "Capacity"))); // PRS3

// Calc min crew ratio
	float fCrewMin = makefloat(GetMinCrewQuantity(&rCharacter)); // PRS3 - stf(rShip.MinCrew);
	float fCrewCur = makefloat(GetCrewQuantity(&rCharacter));//stf(rCharacter.Ship.Crew.Quantity);//MAXIMUS
	float fCrewMin25Percent = fCrewMin* 0.25; // NK no need to check attr _twice_...
	arCharShip.Crew.MinRatio = Bring2Range(0.0, 1.0, fCrewMin25Percent, fCrewMin, fCrewCur);

// wind power
	float	fWindPower = Whr_GetWindSpeed() / WIND_NORMAL_POWER;
	float	fWindDotShip = GetDotProduct(Whr_GetWindAngle(), stf(arCharShip.Ang.y));		// Wind.ay | Ship.ay

// calculate MaxSpeedZ
	float fSpeedPerk = AIShip_isPerksUse(arTmpPerks.ShipSpeedUp, 1.0, 1.15);
	fSpeedPerk = AIShip_isPerksUse(arTmpPerks.SailingProfessional, fSpeedPerk, 1.20);

	float	fK = Clampf(1.0 - stf(GetLocalShipAttrib(arCharShip, rShip, "SpeedDependWeight")) * fLoad);
//NK -->
	if(bArcadeMode) {
		arCharShip.MaxSpeedZ = (fMaxSpeedZ * fWindPower * fK) * fSpeedPerk;
	} else {
// KK -->
		float	fClosestPoint = DEFAULT_CLOSESTPOINT;
		if (CheckShipAttribute(arCharShip, rShip, "ClosestPoint")) fClosestPoint = stf(GetLocalShipAttrib(arCharShip, rShip, "ClosestPoint")); //Closest point of sail

//KB - Tuning ships - Tack
//KB if ( sti(rCharacter.index) == 0 ) {Trace("KB - Tack before " + fClosestPoint);}
		aref temparef;
		int testtune=0;
		makearef(temparef,rCharacter.ship);
		if (CheckAttribute(temparef,"tune")) {
			testtune = 1;
			makearef(temparef,rCharacter.ship.tune);
			if(CheckAttribute(temparef,"tack")) { 
				if ( sti(rCharacter.ship.tune.tack) > 0 ) { 
					fClosestPoint = fClosestPoint + stf(rCharacter.ship.tune.tackmod);
					if ( fClosestPoint < 0.05 ) { fClosestPoint = 0.05; }
					if ( fClosestPoint > 1 ) { fClosestPoint = 1; }
				}
			}
		}
//KB if ( sti(rCharacter.index) == 0 ) {Trace("KB - Tack after " + fClosestPoint);}
//KB - END Tuning ships - Tack

		float	fBestPoint = DEFAULT_BESTPOINT;
		if (CheckShipAttribute(arCharShip, rShip, "BestPoint")) fBestPoint = stf(GetLocalShipAttrib(arCharShip, rShip, "BestPoint")); //Best point of sail

		string rigtype = "Fri";
		if (CheckShipAttribute(arCharShip, rShip, "RigType")) rigtype = GetLocalShipAttrib(arCharShip, rShip, "RigType");
// <-- KK

//move some code to their own funcs at bottom 04-09-19


//get angles
		float	fWindAngle = Whr_GetWindAngle();
		float	fShipAngle = stf(arCharShip.Ang.y);

		float fOffWind = RS_CalcOffwind(fWindAngle, fShipAngle);

// LDH --> sail luffing sounds
		if (SAIL_LUFFING_SOUNDS) {
			if(iCharacterIndex == GetMainCharacterIndex()) {
				if(fOffWind < fClosestPoint + RS_CP_OFFSET) {
					if (fSailState > 0.1) {		// don't play if sails are furled
						for (int i=0 ; i<SAIL_LUFFING_SOUNDS; i++ )
						{
							PostEvent("luffing_event",Rand(999));
						}
					}
				}
			}
		}
// LDH <--

//	Log_SetStringToLog("Angle1: " + fOffWind*100);

		arCharShip.MaxSpeedZ = fMaxSpeedZ * RS_CalcSpeedScalar(fClosestPoint, fBestPoint, rigtype, fOffWind, fWindPower) * fK * fSpeedPerk; // 04-09-19 now a function
		arCharShip.speedmult = fMaxSpeedZ * fK * fSpeedPerk * GetSailPercent(rCharacter) / 100.0; // NK 04-09-21
//	Log_SetStringToLog("Trgt: " + arCharShip.MaxSpeedZ);
	
	}
// NK <--
	// ORZEL SECTION :), DELETE /*
	/*if (bStrand) fK = fK * stf(rCharacter.TmpSkill.);
	if (fWindDotShip < -0.98) arCharShip.MaxSpeedZ = 0.0;*/
	// ORZEL SECTION :), DELETE */

// calculate MaxSpeedY
	float	fTRFromWeight = Clampf(1.0 - stf(GetLocalShipAttrib(arCharShip, rShip, "TurnDependWeight")) * fLoad);
// NK -->
	float	fTRFromSpeed = (1.0 - stf(GetLocalShipAttrib(arCharShip, rShip, "TurnDependSpeed"))) + stf(GetLocalShipAttrib(arCharShip, rShip, "TurnDependSpeed")) * (Clampf((abs(fCurrentSpeedZ) / fMaxSpeedZ))); // fixed 05-04-10 NK
// NK <--
	float	fTRFromSkill, fTRFromPeople;
	if(USE_REAL_SAILING)
	{
		// KNB -->
		fTRFromSkill = Bring2Range(0.4, 1.0, 0.0, 2.0, 2.0 * stf(rCharacter.TmpSkill.Sailing));  // was min 0.2
		fTRFromPeople = Bring2Range(0.3, 1.0, 0.0, 1.0, stf(arCharShip.Crew.MinRatio));  // was min 0.2
		// <--KNB
	}
	else
	{
		fTRFromSkill = Bring2Range(0.2, 1.0, 0.0, 2.0, 2.0 * stf(rCharacter.TmpSkill.Sailing));
		fTRFromPeople = Bring2Range(0.2, 1.0, 0.0, 1.0, stf(arCharShip.Crew.MinRatio));
	}

	float	fTRFromSailDamage = 1.0;	// LDH - note that this is not changed from 1.0

// Turn rate depend from sail State
	float	fTRFromSailState = 1.0;
	switch (MakeInt(fSailState * 2.0))
	{
		case 0:
			if(USE_REAL_SAILING) fTRFromSailState = 0.001; // NK 05-04-10
			else fTRFromSailState = 0.3;
		break;
		case 1: fTRFromSailState = 1.0; break;
		case 2: fTRFromSailState = 0.6; break;
	}

	float	fTRResult; if(USE_REAL_SAILING) fTRResult = Bring2Range(0.10, 0.85, 0.00001, 1.0, fTRFromWeight * fTRFromSpeed * fTRFromSkill * fTRFromPeople * fTRFromSailDamage * fTRFromSailState); // KNB  reduced min turn speed from 20% to 10%
	else fTRResult = Bring2Range(0.2, 0.85, 0.00001, 1.0, fTRFromWeight * fTRFromSpeed * fTRFromSkill * fTRFromPeople * fTRFromSailDamage * fTRFromSailState);

	float fTurnPerk = AIShip_isPerksUse(arTmpPerks.ShipTurnRateUp, 1.0, 1.15);
	fTurnPerk = AIShip_isPerksUse(arTmpPerks.SailingProfessional, fTurnPerk, 1.20);

	// fast turn 180
	if (sti(arTmpPerks.Turn) == -1)
	{
		CharacterPerkOff(rCharacter, "Turn180");
		arTmpPerks.Turn = false;
	}
	float fFastTurn180 = AIShip_isPerksUse(arTmpPerks.Turn, 1.0, 8.0);

	fTRResult = fTRResult * fMaxSpeedY * fTurnPerk * fFastTurn180 * floatRet(bArcadeMode, 1.0, LDH_TURN_RATE_FACTOR);	// LDH 04Oct06

// LDH --> change for turn rate, added sail damage 08Sep06
	float fSailPercent = GetSailPercent(rCharacter);	// current percentage of sails left, 0.0 to 100.0
	if (SAIL_DAMAGE_TURNRATE)
	{
		// for atmin=7.5 and atmax=85.0 - Below 7.5% ship cannot turn.  Above 85% ship turns normally.
		//At 10% sails ship turns 10% normal. At 20% turns 37%. At 50% turns 76%.  At 70% turns 90%.  At 80% turns 97%.

		// Below 7.5% ship cannot turn.
		// At 10% sails ship turns 10% normal. At 20% turns 35%. At 50% turns 71%.  At 70% turns 82%.  At 80% turns 88%.
		float atmin = 7.5;		// percent of sail below which ship can no longer turn
		float atmax = 100.0;	// percent of sail above which ship turning is not affected
		if (fSailPercent <= atmin) 
		{
			fTRResult = 0.0;
		}
		else
		{
			if (fSailPercent < atmax)
			{
				atmin /= 10.0; atmax /= 10.0; fSailPercent /= 10.0;		// numbers need to be in range 0 to 10
				float athalf = (atmax+atmin)/2.0;
//				athalf = -1.0;		// uncomment to test using logN function throughout the range
				if (fSailPercent > athalf)	// function is mostly linear in this range, avoid time consuming calc
				{
					fTRResult = fTRResult * fclamp(0.0, 1.0, (0.73 + (fSailPercent-athalf)*0.27/(atmax-athalf)));
				}
				else
				{
	  				fSailPercent = fclamp(atmin, atmax, fSailPercent);
	  				fTRResult = fTRResult * fclamp(0.0, 1.0, logN(fSailPercent+(1.0-atmin)) / logN(1.0-atmin+atmax));
				}
			}
		}
	}
// LDH <--

	arCharShip.MaxSpeedY =	fTRResult;

// calculate immersion
	arCharShip.Immersion = (stf(GetLocalShipAttrib(arCharShip, rShip, "SubSeaDependWeight")) * fLoad);

// do damage if ship hull < 10%, sinking
	float fBaseSailHP = stf(rShip.SP);
	float fBaseShipHP = makefloat(GetCharacterShipHP(rCharacter)); // PRS3
	float fCurHP = stf(arCharShip.HP);
	if (bSeaActive && (fCurHP / fBaseShipHP < 0.1))
	{
		float fLightRepair = AIShip_isPerksUse(arTmpPerks.LightRepair, 1.0, 0.0);
		if (CheckAttribute(rCharacter, "SinkTenPercent") && !sti(rCharacter.SinkTenPercent)) { fLightRepair = 0.0; }
		float fSinkDamage = fLightRepair * (fBaseShipHP / 100.0) * 0.08;
		if (CheckAttribute(rCharacter, "Ship.LastBallCharacter"))
		{
			Ship_ApplyHullHitpoints(rCharacter, fSinkDamage, KILL_BY_BALL, sti(rCharacter.Ship.LastBallCharacter));
		}
		else
		{
			Ship_ApplyHullHitpoints(rCharacter, fSinkDamage, KILL_BY_TOUCH, -1);
		}
		if (iCharacterIndex == GetMainCharacterIndex()) { AddPerkToActiveList("sink"); }
	}
	else
	{
		if (iCharacterIndex == GetMainCharacterIndex()) { DelPerkFromActiveList("sink"); }
	}

	float fStormProfessional = AIShip_isPerksUse(arTmpPerks.StormProfessional, 1.0, 0.6);

// NK -->

// do damage if storm or tornado
//placed here by MAXIMUS -->
	float fExp;
	float fWindAttack;
	float fDamageMultiply;
	float fDamageHP;
//placed here by MAXIMUS <--
	if (bStorm && bSeaActive)
	{
		fWindAttack = 1.0 - abs(fWindDotShip);

		fDamageMultiply = fStormProfessional * Bring2Range(0.25, 1.0, 0.0, 1.0, fWindAttack);

		// hull damage
// NK -->
		fDamageHP = (sqrt(fBaseShipHP) / 15);
// NK <--
		Ship_ApplyHullHitpoints(rCharacter, fDamageMultiply * fDamageHP, KILL_BY_TOUCH, -1);

		// sails damage
		switch (MakeInt(fSailState * 2.0))
		{
			case 0: MakeSailDmg(iCharacterIndex, fDamageMultiply * (fBaseSailHP / 100.0) * 0.15); break;
			case 1: MakeSailDmg(iCharacterIndex, fDamageMultiply * (fBaseSailHP / 100.0) * 0.55); break;
			case 2: MakeSailDmg(iCharacterIndex, fDamageMultiply * (fBaseSailHP / 100.0) * 1.1); break;
		}

		// experience
// NK -- first part of next line. The skill bit is from Stone-D; it makes exp from storms based on 0-sailing skill.
		fExp = (rand((GetSummonSkillFromName(rCharacter,SKILL_SAILING)-1))+1) * (5.0 - fWindAttack) * fDamageHP;
		if (!bDead) { if(AUTO_SKILL_SYSTEM) { Ship_AddCharacterExpChar(rCharacter, "Sailing", MakeInt(fExp)); }else{ Ship_AddCharacterExp(rCharacter, MakeInt(fExp)); } }

		// rotate impulse
// NK -->
		/*float fRotate = stf(arCharShip.Impulse.Rotate.y) + (frnd() * 0.08 - 0.04);
		if (fRotate > 0.07) { fRotate = 0.07; }
		if (fRotate < -0.07) { fRotate = -0.07; }*/
		float fRotate = stf(arCharShip.Impulse.Rotate.y);
		if(frnd() < 0.5)
		{
			if(CheckAttribute(arCharShip,"stormimp"))
			{
				arCharShip.Impulse.Rotate.y = arCharShip.stormimp;
				DeleteAttribute(arCharShip,"stormimp");
			}
		}
		else
		{
			if(!CheckAttribute(arCharShip,"stormimp"))
			{
				switch(rand(9))
				{
					case 0: arCharShip.stormimp = fRotate; fRotate -= 0.02; break;
					case 1: arCharShip.stormimp = fRotate; fRotate -= 0.02; break;
				}
				arCharShip.Impulse.Rotate.y = fRotate;
			}
		}
// NK <--
	}

	if (bTornado && bSeaActive)
	{
//placed here by MAXIMUS -->
		fWindAttack = 1.0 - abs(fWindDotShip);

		fDamageMultiply = fStormProfessional * Bring2Range(0.25, 1.0, 0.0, 1.0, fWindAttack);

		// hull damage
// NK -->
		fDamageHP = (sqrt(fBaseShipHP) / 10);
// NK <--
//placed here by MAXIMUS <--
		float fTornadoDistance = GetDistance2D(stf(Tornado.x), stf(Tornado.z), stf(arCharShip.Pos.x), stf(arCharShip.Pos.z));
		float fTornadoDamageMultiply = fStormProfessional * Bring2Range(1.0, 0.0, 0.0, 100.0, fTornadoDistance);

		// hull damage
		Ship_ApplyHullHitpoints(rCharacter, fTornadoDamageMultiply * (fBaseShipHP / 100.0) * 8.5, KILL_BY_TOUCH, -1);

		// sails damage
		switch (MakeInt(fSailState * 2.0))
		{
			case 0: MakeSailDmg(iCharacterIndex, fTornadoDamageMultiply * (fBaseSailHP / 100.0) * 1.5); break;
			case 1: MakeSailDmg(iCharacterIndex, fTornadoDamageMultiply * (fBaseSailHP / 100.0) * 4.5); break;
			case 2: MakeSailDmg(iCharacterIndex, fTornadoDamageMultiply * (fBaseSailHP / 100.0) * 8.5); break;
		}
//placed here by MAXIMUS -->
		fExp = (rand((GetSummonSkillFromName(rCharacter,SKILL_SAILING)-1))+1) * (5.0 - fWindAttack) * fDamageHP;
		if (!bDead) { if(AUTO_SKILL_SYSTEM) { Ship_AddCharacterExpChar(rCharacter, "Sailing", MakeInt(fExp)); }else{ Ship_AddCharacterExp(rCharacter, MakeInt(fExp)); } }
//placed here by MAXIMUS <--
	}

// Expirience log section
	if (iCharacterIndex == GetMainCharacterIndex() && bSeaActive)
	{
		fSeaExpTimer = fSeaExpTimer + 1.0;
		if (fSeaExpTimer >= 15.0 && bStorm)
		{
			// NK already logged - if (!bDead) { Ship_PrintExp(MakeInt(fSeaExp)); }

			fSeaExpTimer = 0.0;
			fSeaExp = 0.0;
		}
	}

// Apply arcade mode
// NK switch to using defines -->
	if (bArcadeMode)
	{
		arCharShip.MaxSpeedZ = ARCADE_MULT_SPEED * stf(arCharShip.MaxSpeedZ);
		arCharShip.MaxSpeedY =	ARCADE_MULT_TURN* stf(arCharShip.MaxSpeedY);
	}
// NK <--

	// Recalculate Cargo Load if need
	if (CheckAttribute(rCharacter, "Ship.Cargo.RecalculateCargoLoad") && sti(rCharacter.Ship.Cargo.RecalculateCargoLoad))
	{
		RecalculateCargoLoad(rCharacter);
		rCharacter.Ship.Cargo.RecalculateCargoLoad = 0;
	}
	//LanguageCloseFile(tmpLangFileID);
}

void Ship_PrintExp(int iExp)
{
	if (iExp <= 0) { return; }

	object oRes;
	string sExpQuantity = iExp;
	string sExpString = LanguageConvertString(iSeaSectionLang, "sea_exp_add");

	Event(PARSE_STRING, "asls", &oRes, sExpString, 1, sExpQuantity);

	Log_SetStringToLog(oRes.Str);
}

void Ship_TornadoDamage()
{
	float fTime = GetEventData();

	Tornado.x = GetEventData();
	Tornado.z = GetEventData();
}

void Ship_UpdateTmpSkills(ref rCharacter)
{
	rCharacter.TmpSkill = "";

	aref aTmpSkill; makearef(aTmpSkill, rCharacter.TmpSkill);

	// NK -->
	float mult = 1.0;
	bool bcomp = true;
	string tmp; // 04-09-15
	if(!IsCompanion(rCharacter))
	{
		bcomp = false;
		if(CheckAttribute(rCharacter, "randofficers"))
		{
			mult = stf(rCharacter.randofficers);
			//unneeded, already clamped. 04-09-15
			/*if(mult < 1.0) mult = 1.0;
			if(mult > 4.0) mult = 4.0;*/
		}
		else
		{
			int sclass = GetCharacterShipClass(rCharacter);
			sclass = 7-sclass;
			mult += (fRand(GetOfficersQuantity(GetMainCharacter()))/3.0 + sclass/6.0 + fRand(sclass/6.0)) * (0.5 + makefloat(GetDifficulty()-1.0)/3.0);
			if(mult < 1.0) mult = 1.0;
			if(mult > 4.0) mult = 4.0;
			rCharacter.randofficers = mult;
			//Log_SetStringToLog("Fantom " + rCharacter.id + ", (" + rCharacter.name + " " + rCharacter.lastname + ") has mult " + mult);
		}
		if(!CheckAttribute(rCharacter,"skillnatmult")) // lookup to speed up national skill mults. 04-09-15
		{
			for(int i = 0; i < 10; i++)
			{ tmp = GetSkillName(i); rCharacter.skillnatmult.(tmp) = GetSkillMultByNation(tmp, sti(rCharacter.nation)); }
			/*rCharacter.skillnatmult.(SKILL_COMMERCE) = GetSkillMultByNation(SKILL_COMMERCE, sti(rCharacter.nation));
			rCharacter.skillnatmult.(SKILL_LEADERSHIP) = GetSkillMultByNation(SKILL_LEADERSHIP, sti(rCharacter.nation));
			rCharacter.skillnatmult.(SKILL_SNEAK) = GetSkillMultByNation(SKILL_SNEAK, sti(rCharacter.nation));
			rCharacter.skillnatmult.(SKILL_DEFENCE) = GetSkillMultByNation(SKILL_DEFENCE, sti(rCharacter.nation));
			rCharacter.skillnatmult.(SKILL_GRAPPLING) = GetSkillMultByNation(SKILL_GRAPPLING, sti(rCharacter.nation));
			rCharacter.skillnatmult.(SKILL_SAILING) = GetSkillMultByNation(SKILL_SAILING, sti(rCharacter.nation));
			rCharacter.skillnatmult.(SKILL_REPAIR) = GetSkillMultByNation(SKILL_REPAIR, sti(rCharacter.nation));
			rCharacter.skillnatmult.(SKILL_FENCING) = GetSkillMultByNation(SKILL_FENCING, sti(rCharacter.nation));
			rCharacter.skillnatmult.(SKILL_ACCURACY) = GetSkillMultByNation(SKILL_ACCURACY, sti(rCharacter.nation));
			rCharacter.skillnatmult.(SKILL_CANNONS) = GetSkillMultByNation(SKILL_CANNONS, sti(rCharacter.nation));*/
		}
	}
	// calc skills for speed optimization
	// NK uncommented commerce and leadership 04-09-10, you never know when they'd be needed, esp. leadership.
	// NK PRS3 04-09-10 added mult by shipstatsmult for nation
	// 04-09-15 switched to attribute.
	if(bcomp)
	{
		aTmpSkill.Commerce = Clampf(mult * MakeFloat(GetSummonSkillFromName(rCharacter, SKILL_COMMERCE)) / SKILL_MAX);
		aTmpSkill.Leadership = Clampf(mult * MakeFloat(GetSummonSkillFromName(rCharacter, SKILL_LEADERSHIP)) / SKILL_MAX);
		aTmpSkill.Sneak = Clampf(mult * MakeFloat(GetSummonSkillFromName(rCharacter, SKILL_SNEAK)) / SKILL_MAX);
		aTmpSkill.Defence = Clampf(mult * MakeFloat(GetSummonSkillFromName(rCharacter, SKILL_DEFENCE)) / SKILL_MAX);
		aTmpSkill.Grappling = Clampf(mult * MakeFloat(GetSummonSkillFromName(rCharacter, SKILL_GRAPPLING)) / SKILL_MAX);
		aTmpSkill.Sailing = Clampf(mult * MakeFloat(GetSummonSkillFromName(rCharacter, SKILL_SAILING)) / SKILL_MAX);
		aTmpSkill.Repair = Clampf(mult * MakeFloat(GetSummonSkillFromName(rCharacter, SKILL_REPAIR)) / SKILL_MAX);
		aTmpSkill.Fencing = Clampf(mult * MakeFloat(GetSummonSkillFromName(rCharacter, SKILL_FENCING)) / SKILL_MAX);
		aTmpSkill.Accuracy = Clampf(mult * MakeFloat(GetSummonSkillFromName(rCharacter, SKILL_ACCURACY))  / SKILL_MAX);
		aTmpSkill.Cannons = Clampf(mult * MakeFloat(GetSummonSkillFromName(rCharacter, SKILL_CANNONS))  / SKILL_MAX);
	}
	else
	{
		tmp = SKILL_COMMERCE;
		aTmpSkill.Commerce = Clampf(mult * MakeFloat(GetSummonSkillFromName(rCharacter, SKILL_COMMERCE)) * stf(rCharacter.skillnatmult.(tmp)) / SKILL_MAX);
		tmp = SKILL_LEADERSHIP;
		aTmpSkill.Leadership = Clampf(mult * MakeFloat(GetSummonSkillFromName(rCharacter, SKILL_LEADERSHIP)) * stf(rCharacter.skillnatmult.(tmp)) / SKILL_MAX);
		tmp = SKILL_SNEAK;
		aTmpSkill.Sneak = Clampf(mult * MakeFloat(GetSummonSkillFromName(rCharacter, SKILL_SNEAK)) * stf(rCharacter.skillnatmult.(tmp)) / SKILL_MAX);
		tmp = SKILL_DEFENCE;
		aTmpSkill.Defence = Clampf(mult * MakeFloat(GetSummonSkillFromName(rCharacter, SKILL_DEFENCE)) * stf(rCharacter.skillnatmult.(tmp)) / SKILL_MAX);
		tmp = SKILL_GRAPPLING;
		aTmpSkill.Grappling = Clampf(mult * MakeFloat(GetSummonSkillFromName(rCharacter, SKILL_GRAPPLING)) * stf(rCharacter.skillnatmult.(tmp)) / SKILL_MAX);
		tmp = SKILL_SAILING;
		aTmpSkill.Sailing = Clampf(mult * MakeFloat(GetSummonSkillFromName(rCharacter, SKILL_SAILING)) * stf(rCharacter.skillnatmult.(tmp)) / SKILL_MAX);
		tmp = SKILL_REPAIR;
		aTmpSkill.Repair = Clampf(mult * MakeFloat(GetSummonSkillFromName(rCharacter, SKILL_REPAIR)) * stf(rCharacter.skillnatmult.(tmp)) / SKILL_MAX);
		tmp = SKILL_FENCING;
		aTmpSkill.Fencing = Clampf(mult * MakeFloat(GetSummonSkillFromName(rCharacter, SKILL_FENCING)) * stf(rCharacter.skillnatmult.(tmp)) / SKILL_MAX);
		tmp = SKILL_ACCURACY;
		aTmpSkill.Accuracy = Clampf(mult * MakeFloat(GetSummonSkillFromName(rCharacter, SKILL_ACCURACY)) * stf(rCharacter.skillnatmult.(tmp)) / SKILL_MAX);
		tmp = SKILL_CANNONS;
		aTmpSkill.Cannons = Clampf(mult * MakeFloat(GetSummonSkillFromName(rCharacter, SKILL_CANNONS)) * stf(rCharacter.skillnatmult.(tmp)) / SKILL_MAX);
	}
	// NK <--

	if (SeaCameras.Camera == "SeaDeckCamera" && sti(rCharacter.index) == GetMainCharacterIndex())
	{
		switch (GetTargetPlatform())
		{
			case "xbox":
				aTmpSkill.Accuracy = 1.0;
			break;
			case "pc":
				if(bArcadeMode) // 1.03
				{
					aTmpSkill.Accuracy = Clampf(stf(aTmpSkill.Accuracy) + 0.4);
				}
			break;
		}
	}
}

void Ship_UpdatePerks()
{
	int iCharacterIndex = GetEventData();
	ref rCharacter = GetCharacter(iCharacterIndex);

	//Trace("Ship_UpdatePerks: rCharacter.id = " + rCharacter.id);

	rCharacter.TmpPerks = "";
	aref aTmpPerks; makearef(aTmpPerks, rCharacter.TmpPerks);

	// calc perks for speed optimization
	if (!CheckAttribute(rCharacter, "TmpPerks.Turn")) {	rCharacter.TmpPerks.Turn = 0; }

	aTmpPerks.FastReload				= CheckOfficersPerk(rCharacter, "FastReload");
	aTmpPerks.ImmediateReload			= CheckOfficersPerk(rCharacter, "ImmediateReload");
	aTmpPerks.HullDamageUp			 	= CheckOfficersPerk(rCharacter, "HullDamageUp");
	aTmpPerks.CrewDamageUp				= CheckOfficersPerk(rCharacter, "CrewDamageUp");
	aTmpPerks.CriticalShoot				= CheckOfficersPerk(rCharacter, "CriticalShoot");
	aTmpPerks.LongRangeShoot			= CheckOfficersPerk(rCharacter, "LongRangeShoot");
	aTmpPerks.CannonProfessional		= CheckOfficersPerk(rCharacter, "CannonProfessional");
	aTmpPerks.LongRangeGrappling		= CheckOfficersPerk(rCharacter, "LongRangeGrappling");
	aTmpPerks.MusketsShoot				= CheckOfficersPerk(rCharacter, "MusketsShoot");				// !!!!
	aTmpPerks.GrapplingProfessional		= CheckOfficersPerk(rCharacter, "GrapplingProfessional");
	aTmpPerks.BasicBattleState			= CheckOfficersPerk(rCharacter, "BasicBattleState");
	aTmpPerks.AdvancedBattleState		= CheckOfficersPerk(rCharacter, "AdvancedBattleState");
	aTmpPerks.ShipDefenseProfessional	= CheckOfficersPerk(rCharacter, "ShipDefenseProfessional");
	aTmpPerks.ShipSpeedUp				= CheckOfficersPerk(rCharacter, "ShipSpeedUp");
	aTmpPerks.ShipTurnRateUp			= CheckOfficersPerk(rCharacter, "ShipTurnRateUp");
	aTmpPerks.StormProfessional			= CheckOfficersPerk(rCharacter, "StormProfessional");			// !!!!
	aTmpPerks.SandbankManeuver			= CheckOfficersPerk(rCharacter, "SandbankManeuver");
	aTmpPerks.SailingProfessional		= CheckOfficersPerk(rCharacter, "SailingProfessional");
	aTmpPerks.IronWill				= CheckOfficersPerk(rCharacter, "IronWill"); // NK 05-04-20

	aTmpPerks.LightRepair				= CheckOfficersPerk(rCharacter, "LightRepair");

	// update main character if rCharacter is companion
	if (iCharacterIndex != GetMainCharacterIndex() && IsCompanion(rCharacter))
	{
		Event("eSwitchPerks", "l", GetMainCharacterIndex());
	}
}

void Ship_Lightning()
{
	if (!bSeaActive) { return; }

	float x = GetEventData();
	float y = GetEventData();
	float z = GetEventData();

	for (int i=0; i<iNumShips; i++)
	{
		if(i==SHIP_BOAT || i==SHIP_NOTUSED) continue;//MAXIMUS
		ref rCharacter = GetCharacter(Ships[i]);
		aref arShipChar; makearef(arShipChar, rCharacter.Ship);

		float sx = stf(arShipChar.Pos.x);
		float sy = stf(arShipChar.Pos.y);
		float sz = stf(arShipChar.Pos.z);
		float bx = stf(arShipChar.BoxSize.x);
		float by = stf(arShipChar.BoxSize.y);
		float bz = stf(arShipChar.BoxSize.z);
		float fDist = GetDistance2D(x, z, sx, sz);
		float fMinLightningDist = 20.0 + bz / 1.5;
		if (fDist < fMinLightningDist)
		{
			int iMinBoom = MakeInt(Bring2Range(5.0, 2.0, 1.0, 7.0, stf(GetCharacterShipClass(rCharacter))));
			float fCos = cos(stf(arShipChar.Ang.y));
			float fSin = sin(stf(arShipChar.Ang.y));
			for (int k=0; k<iMinBoom + rand(2); k++)
			{
				float dx = 0.6 * bx * (frnd() - 0.5);
				float dz = 0.7 * bz * (frnd() - 0.5);
				RotateAroundY(&dx, &dz, fCos, fSin);
				Ship_Serious_Boom(sx + dx, sy + 0.1 * by * frnd(), sz + dz);
			}

			Ship_ApplyHullHitpoints(rCharacter, iMinBoom * 40.0, KILL_BY_TOUCH, -1);
			Ship_ApplyCrewHitpoints(rCharacter, MakeFloat(rand(3)));

			int iNumFirePlaces = 0;
			SendMessage(rCharacter, "le", MSG_SHIP_GET_NUM_FIRE_PLACES, &iNumFirePlaces);

			float fFirePlaces = Bring2Range(1.0, 0.1, 0.0, fMinLightningDist, fDist);

			float fCurr = 0.0;
			float fTest = (1.0 - fFirePlaces) * iNumFirePlaces;
			for (int j=0; j<iNumFirePlaces; j++)
			{
				fCurr = fCurr + 1.0;
				if (fCurr < fTest) { continue; }

				fCurr = 0.0;
				int iRandStartTime = rand(10000);
				float fTotalFireTime = Ship_GetTotalFireTime(rCharacter);

				PostEvent(SHIP_ACTIVATE_FIRE_PLACE, iRandStartTime, "ialsf", rCharacter, rCharacter, j, "ship_onfire", fTotalFireTime);
				PostEvent(SHIP_FIRE_DAMAGE, iRandStartTime, "lllf", Ships[i], -1, j, fTotalFireTime);
			}
		}
	}
}

// Create a new ship for character
void Ship_CreateForCharacter(ref rCharacter, int iShipType, string sShipName)
{
	DeleteAttribute(rCharacter, "Ship");

	rCharacter.Ship.Type = GetShipID(iShipType); // PS
	rCharacter.Ship.Name = sShipName;
	
	if (CANNONPOWDER_MOD) {// TIH --> mod toggle 7-7-06
		//rCharacter.Ship.Gunpowder = GetCargoGoods(rCharacter, GOOD_GUNPOWDER); // added by MAXIMUS [gunpowder mod]
		// added by MAXIMUS [gunpowder mod] -->
		int fireGunpowder = 0;
		ref rCannon; makeref(rCannon,Cannon[GetCaracterShipCannonsType(rCharacter)]);
		if(CheckAttribute(rCannon,"gunpowder")) fireGunpowder = sti(rCannon.gunpowder)*sti(GetCannonCurQuantity(rCharacter));
		if(fireGunpowder<GetCargoGoods(rCharacter, GOOD_GUNPOWDER))
		{
			rCharacter.Ship.Cannons.Charge.Type = GOOD_BALLS; // KNB
		}
		// added by MAXIMUS [gunpowder mod] <--
	}// TIH <-- mod toggle

	rCharacter.Ship.Cannons.Borts = "";
//	rCharacter.Ship.Cannons.Charge.Type = GOOD_BALLS; // KNB
	rCharacter.Ship.Ang.y = 0.0;
	rCharacter.Ship.Pos.x = 0.0;
	rCharacter.Ship.Pos.z = 0.0;
	rCharacter.Ship.Speed.z = 0.0;

	SetBaseShipData(rCharacter);
}

void Ship_ClearImpulseStrength(ref rCharacter)
{
	aref arImpulse; makearef(arImpulse, rCharacter.Ship.Impulse);
	arImpulse.Rotate.x = 0.0;
	arImpulse.Rotate.y = 0.0;
	arImpulse.Rotate.z = 0.0;
	arImpulse.Speed.x = 0.0;
	arImpulse.Speed.y = 0.0;
	arImpulse.Speed.z = 0.0;
}

void Ship_PrepareShipForLocation(ref rCharacter)
{
	Ship_ClearImpulseStrength(rCharacter);
}

// START MOD Code by Stone-D : 01/08/2003
void Whack_Ship(ref rCharacter) // Stone-D : Not working. Only eye candy, put on hold.
{
/*
	aref arShipChar;
	int iRandStartTime = 0;
	int iNumFirePlaces = 0;
	float fFirePlaces = 0.0;
	float fTotalFireTime = 0.0;
	float fCurr = 0.0;
	float fTest = 0.0;


	makearef(arShipChar, rCharacter.Ship);

	SendMessage(rCharacter, "le", MSG_SHIP_GET_NUM_FIRE_PLACES, &iNumFirePlaces);

//	fFirePlaces = Bring2Range(1.0, 0.1, 0.0, 0.1, 1.0);
	fFirePlaces = rand(iNumFirePlaces);
	fTest = (1.0 - fFirePlaces) * iNumFirePlaces;

	for (int j=0; j<iNumFirePlaces; j++)
	{
		fCurr = fCurr + 1.0;
		if (fCurr < fTest) { continue; }

		fCurr = 0.0;
//		int iRandStartTime = rand(10);
//		fTotalFireTime = rand(30);

		PostEvent(SHIP_ACTIVATE_FIRE_PLACE, 1, "ialsf", arShipChar, rCharacter, j, "ship_onfire", rand(30));
//		PostEvent(SHIP_FIRE_DAMAGE, 0, "lllf", iOurCharacterIndex, iBallCharacterIndex, iFirePlaceIndex, rand(30));
//		PostEvent(SHIP_FIRE_DAMAGE, 0, "lllf", Ships[i], -1, j, fTotalFireTime);
	}
*/
}
// END MOD Code by Stone-D : 01/07/2003

// NK realistic sailing -->
float RS_GetRigTypeDivisor(string sDet, float fOffWind)
{
	float fDivisor = 1.0;
	switch(sDet)
	{
		case "Fsc": return(RS_GetRigTypeDivisor("Gat", fOffWind) + RS_GetRigTypeDivisor("Slo", fOffWind))/2; break; // NK new div for JMV's fast sch.
		case "shn": return RS_GetRigTypeDivisor("bri", fOffWind); break; //return(RS_GetRigTypeDivisor("Gat", fOffWind) + RS_GetRigTypeDivisor("Slo", fOffWind))/2; break; // NK new div for AM shnyava
		case "btn": return(RS_GetRigTypeDivisor("bri", fOffWind) + RS_GetRigTypeDivisor("Slo", fOffWind))/2; break; // NK new div for AM brigantine
		case "Xeb":
			if(fOffWind > 0.01) { fDivisor = sqrt(sqrt(sqrt(sqrt((fOffWind * 0.9) ^ 3)))) * 0.9785; }
			else { fDivisor = 0.25; }
			break;

		case "Tar":
			if(fOffWind > 0.01) { fDivisor = sqrt(sqrt(sqrt(sqrt((fOffWind * 0.9) ^ 3)))) * 0.9785; }
			else { fDivisor = 0.25; }
			break;

		case "Sch":
			if(fOffWind > 0.01) { fDivisor = sqrt(sqrt(sqrt(sqrt((fOffWind * 0.9) ^ 3)))) * 0.9785; }
			else { fDivisor = 0.25; }
			break;

		case "Lug":
			if(fOffWind > 0.01) { fDivisor = 1.2433*fOffWind ^ 3 - 2.7092*fOffWind ^ 2 + 2.0879*fOffWind + 0.3779; }
			else { fDivisor = 0.38; }
			break;

		case "Gal":
			if(fOffWind > 0.01) { fDivisor = 0.7467 * fOffWind ^ 3 - 1.9657*fOffWind ^ 2 + 1.839*fOffWind + 0.3803; }
			else { fDivisor = 0.38; }
			break;

		case "Gat":
			if(fOffWind > 0.01) { fDivisor = 1.2433*fOffWind ^ 3 - 2.7092*fOffWind ^ 2 + 2.0879*fOffWind + 0.3779; }
			else { fDivisor = 0.38; }
			break;

		case "Cor":
			if(fOffWind > 0.01) { fDivisor = 1.2433*fOffWind ^ 3 - 2.7092*fOffWind ^ 2 + 2.0879*fOffWind + 0.3779; }
			else { fDivisor = 0.39; }
			break;

		case "Car":
			if(fOffWind > 0.01) { fDivisor = 1.2433*fOffWind ^ 3 - 2.7092*fOffWind ^ 2 + 2.0879*fOffWind + 0.3779; }
			else { fDivisor = 0.39; }
			break;

		case "Bar":
			if(fOffWind > 0.01) { fDivisor = 1.2433*fOffWind ^ 3 - 2.7092*fOffWind ^ 2 + 2.0879*fOffWind + 0.3779; }
			else { fDivisor = 0.39; }
			break;

		case "Yac":
			if(fOffWind > 0.01) { fDivisor = 1.2433*fOffWind ^ 3 - 2.7092*fOffWind ^ 2 + 2.0879*fOffWind + 0.3779; }
			else { fDivisor = 0.39; }
			break;

		//default:  // scheffnow - bfd
			if(fOffWind > 0.01) { fDivisor = 0.7467 * fOffWind ^ 3 - 1.9657*fOffWind ^ 2 + 1.839*fOffWind + 0.3803; }
			else { fDivisor = 0.38; }
	}
	return fDivisor;
}

// NK 04-09-19 move some RS funcs here instead of as part of UpdateParams.
#define RS_SCALAR		1.0 //scales all speeds
#define RS_BACKWIND	-0.25 //speed backwinded
#define RS_CP_OFFSET	0.02 // fudge on either side of Closest Point

float RS_CalcOffwind(float fWindAngle, float fShipAngle)
{
	float fOffWind = 0; // = AngleOffAngle(fWindAngle, fShipAngle); // use generic function 05-04-18 NOT DONE YET!
	fWindAngle = ClampAngle(fWindAngle);
	fShipAngle = ClampAngle(fShipAngle);
	//find difference Mith
	
	if(fShipAngle >= fWindAngle && fShipAngle - fWindAngle <= PI) fOffWind = abs(fShipAngle - fWindAngle);
	else
	{
		if(fShipAngle < fWindAngle && fWindAngle - fShipAngle <= PI) fOffWind = abs(fShipAngle - fWindAngle);
		else
		{
			if(fShipAngle >= fWindAngle && fShipAngle - fWindAngle > PI) fOffWind = PIm2 - abs(fShipAngle - fWindAngle);
			else
			{
				if(fShipAngle < fWindAngle && fWindAngle - fShipAngle > PI) fOffWind = PIm2 - abs(fShipAngle - fWindAngle);	
			}
		}
	}
	fOffWind = 1 - (fOffWind / PI);
	return fOffWind;
}

float RS_CalcSpeedScalar(float fClosestPoint, float fBestPoint, string rigtype, float fOffWind, float fWindPower)
{
	float	fRBWMax = sqrt(sqrt(sqrt(fClosestPoint))) +0.02; //Max speedscalar running before the wind, as ratio of max speed. Constant.
	float	fBeam = 1 / (20 * sqrt(fClosestPoint ^ 3)) +0.3; //Max speedscalar on a beam reach. Derived from fClosestPoint for now. Constant.
	float	fTackSpeed = 1 / (sqrt(fClosestPoint) * 4); //minimum speed on a tack, i.e. speed at fOffWind = closestpoint - RS_CP_OFFSET. Constant.

	//begin speedscalar calcs
	float fTempSpeedZ = 1.0; // PRS3 and below
	//reorganized we only do one b2r calc.
	if(fOffWind > fBestPoint) fTempSpeedZ = Bring2Range(1.0, fRBWMax, fBestPoint, 1.0, fOffWind); //scale speedscalar between best point of sail and running before the wind		
	else
	{
			if(fOffWind > 0.5) fTempSpeedZ = Bring2Range(fBeam, 1.0, 0.5, fBestPoint, fOffWind); //scale speedscalar between beam reach and best point of sail
			else
			{
				if(fOffWind > fClosestPoint + RS_CP_OFFSET) fTempSpeedZ = Bring2Range(fTackSpeed, fBeam, fClosestPoint + RS_CP_OFFSET, 0.5, fOffWind); //fixed by Hook
				//was if(fOffWind > fClosestPoint + RS_CP_OFFSET) fTempSpeedZ = Bring2Range(1.0, fBeam, fClosestPoint + RS_CP_OFFSET, 0.5, fOffWind);  //scale speedscalar between closest point of sail and beam reach.
				else
				{
					if(fOffWind > fClosestPoint - RS_CP_OFFSET) fTempSpeedZ = Bring2Range(0.0, fTackSpeed, fClosestPoint - RS_CP_OFFSET, fClosestPoint + RS_CP_OFFSET, fOffWind); //ramp quickly from 0 to min speed with wind
					else //if(fOffWind <= fClosestPoint - RS_CP_OFFSET). Because that's the only thing it _can_ be.
					{
						if (fClosestPoint > 0.2) fTempSpeedZ = Bring2Range(RS_BACKWIND, 0.0, 0.0, fClosestPoint - RS_CP_OFFSET, fOffWind); //allow backwards travel
						else fTempSpeedZ = 0.0; //if not square rigged, can't be pushed back
					}
				}
			}
	}
	//end speedscalar calcs

	//begin calcs for fudge to offset EXE's speed scaling.
	float	fDivisor = RS_GetRigTypeDivisor(rigtype, fOffWind); // PRS3 yipes big typo 04-09-12
	//end fudge calcs

	//calc wind modifier based on ship weight--big ships can ride out storms and carry more canvas in them
	float fWindMod = fWindPower;
	/*if(fWindPower >= 1.0)
	{
		fWindMod -= (1 - fWindPower / sqrt(sqrt(arCharShip.Weight)) * 10);
		if(fWindMod < 0.25) fWindMod = 0.25;
	}*/

	//fudge deceleration -- seems to be not needed, but I'm not sure. So right now the implementation is commented out.
	//float fPreDecel = fTempScalar * fTempSpeedZ * fWindMod / fDivisor;
//	Log_SetStringToLog("PreD: " + arCharShip.MaxSpeedZ);
//	Log_SetStringToLog("Brk1: " + stf(rShip.InertiaBrakingZ));
//	rShip.InertiaBrakingZ = 0.75
//	Log_SetStringToLog("Brk2: " + stf(rShip.InertiaBrakingZ));
//	Log_SetStringToLog("AccBot: " + stf(rShip.InertiaAccelerationZ));
//	Log_SetStringToLog("SpdRt: " + fMaxSpeedZ);
	/*if(fPreDecel < arCharShip.MaxSpeedZ - stf(rShip.InertiaBrakingZ) && arCharShip.MaxSpeedZ - stf(rShip.InertiaBrakingZ) > 0 && arCharShip.MaxSpeedZ > 1.5) arCharShip.MaxSpeedZ = arCharShip.MaxSpeedZ - stf(rShip.InertiaBrakingZ);
	else*/
	return (RS_SCALAR* fTempSpeedZ * fWindMod / fDivisor);
	
}

//returns time in seconds for arship (char.ship) to travel dist units at given angle.
int GetTimeToSailDist(aref arship, float dist, float angle)
{
	ref rShip = GetShipByID(arship.type);
// KK -->
	float	fClosestPoint = DEFAULT_CLOSESTPOINT;
	if (CheckShipAttribute(arship, rShip, "ClosestPoint")) fClosestPoint = stf(GetLocalShipAttrib(arship, rShip, "ClosestPoint")); //Closest point of sail

	float	fBestPoint = DEFAULT_BESTPOINT;
	if (CheckShipAttribute(arship, rShip, "BestPoint")) fBestPoint = stf(GetLocalShipAttrib(arship, rShip, "BestPoint")); //Best point of sail

	string rigtype = "Fri";
	if (CheckShipAttribute(arship, rShip, "RigType")) rigtype = GetLocalShipAttrib(arship, rShip, "RigType");
// <-- KK
	
	//get angles
	float	fWindAngle = Whr_GetWindAngle();
	float fWindPower = Whr_GetWindSpeed() / WIND_NORMAL_POWER;
	float fOffWind = RS_CalcOffWind(angle,fWindAngle);
	float speed = stf(GetLocalShipAttrib(arship, rShip, "speedrate")); if(CheckAttribute(arship,"speedmult")) speed = stf(arship.speedmult); // get sail state, load, speedperk, and mult by speedrate
	if(bArcadeMode) return makeint(dist/(fWindPower * speed * (0.5+fOffWind/2.0)));
	if(fOffWind < fClosestPoint) { fOffWind = fClosestPoint; speed *= 0.5; } //assume tacking takes twice as long.

	speed *= RS_CalcSpeedScalar(fClosestPoint, fBestPoint, rigtype, fOffWind, fWindPower); // apply RS scaling
	//we know that at 1 knot we cover 0.5 units per second
	return makeint(dist/(speed/2.0));
}
// NK <--

// LDH -- making luffing sounds more random
#event_handler("luffing_event", "PlayLuffSound");
void PlayLuffSound()
{
	switch (Rand(2))
	{
	case 0:
		PlaySound("AMBIENT\SEA\sails1.wav");
		break;
	case 1:
		PlaySound("AMBIENT\SEA\sails2.wav");
		break;
	case 2:
 		PlaySound("AMBIENT\SEA\sails3.wav");
		break;
	}
}

// KK -->
void RemoveShipFromSea(int idx)
{
	if (idx < 0) return;
	ref shipchar = GetCharacter(idx);
	// Message to AI
	SendMessage(AISea, "la", AI_MESSAGE_CHARACTER_DEAD, shipchar);
	// Event
	Event(SHIP_DEAD, "l", idx);
}

// skasowaæ
void UsunFlagi()
{
	DeleteClass(&Flag);
}
// <-- KK
