object	Sail, Rope, Flag, Vant, Pennant, sFlag; //, Penn;

#define MIN_ENEMY_DISTANCE_TO_DISABLE_MAP_ENTER				850.0
#define MIN_ENEMY_DISTANCE_TO_DISABLE_MAP_ENTER_FORT		1250.0
#define MIN_ENEMY_DISTANCE_TO_DISABLE_ENTER_2_LOCATION		1000.0
#define LIFEBOAT_RESCUE_TIME 1000.0
//#20180223-03 Mitigate crew damage when low
#define CANNON_CREW_MITIGATE_PERCENT 0.1
#define CANNON_CREW_MITIGATE_DAMAGE 3.0
#define CANNON_CREW_MITIGATE_FRAND 1.0
#define CANNON_CREW_LOW_DIFFICULTY 9
//#2019011-04
#define MAX_RELATIONS 5
#define SHIP_DETONATE_SMALL "ShipDetonateSmall"
//#20180124-01 Multiboard
#define MULTI_BOARD_CHECK_DIST 90.0
//#20190612-02
#define RA_CANNON_RATIO 0.55
#define RA_AMMO_DET 3.0
#define RA_POWDER_DET 3.0

#define AISHIPDEBUG 0

bool	bAbordageShipCanBe = false;
int		iAbordageShipEnemyCharacter;
//bool	bNoDamage2Us = false;
bool	bCanEnterToLand = false;
aref	arIslandReload;
string	sIslandLocator;
string	sIslandID;
string	sSeaSoundPostfix;
int		iShipPriorityExecute = 2;
int		iShipPriorityRealize = 31;
bool	bUpdatePosMode = false;
int		iVicSoundTime = 0;
string	sSeaStartMusicName;
bool	isEnemyAround = false;
int		iNumShips;
//#20180527-02
int		Ships[MAX_SHIPS_IN_LOCATION];
object	NearShips[MAX_SHIPS_IN_LOCATION];

#event_handler("Ship_StartLoad", "Ship_StartLoad");
#event_handler("Ship_EndLoad", "Ship_EndLoad");
//#20210810-01
#event_handler(SHIP_TASK_CHANGE, "Shp_TaskChange");

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

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

	Flag.ratioLimit = 1;
	Flag.sizeRatio = 3.5;
	Flag.isDefault = 1;
	CreateEntity(&Flag, "Flag");
	LayerAddObject(sCurrentSeaExecute, &Flag, iShipPriorityExecute + 3);
	LayerAddObject(sCurrentSeaRealize, &Flag, iShipPriorityRealize + 3);
	LayerAddObject("sea_reflection2", &Flag, 3);

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

	//Boyer mod for new version #20170201
	Pennant.ratioExceeds = 1;
	Pennant.sizeRatio = 3.5;
	Pennant.iniSection = "PENNANT";
	CreateEntity(&Pennant, "Flag");
	LayerAddObject(sCurrentSeaExecute, &Pennant, iShipPriorityExecute + 5);
	LayerAddObject(sCurrentSeaRealize, &Pennant, iShipPriorityRealize + 5);
	LayerAddObject("sea_reflection2", &Pennant, 3);

	sFlag.lblGroup = "sflag";
	sFlag.iniSection = "PENNANT";
	CreateEntity(&sFlag, "Flag");
	LayerAddObject(sCurrentSeaExecute, &sFlag, iShipPriorityExecute + 6);
	LayerAddObject(sCurrentSeaRealize, &sFlag, iShipPriorityRealize + 6);
	LayerAddObject("sea_reflection2", &sFlag, 3);

	/*
	Penn.lblGroup = "penn";
	Penn.iniSection = "PENNANT";
	CreateEntity(&Penn, "Flag");
	LayerAddObject(sCurrentSeaExecute, &Penn, iShipPriorityExecute + 7);
	LayerAddObject(sCurrentSeaRealize, &Penn, iShipPriorityRealize + 7);
	LayerAddObject("sea_reflection2", &Penn, 3);
    */
}

void DeleteRiggingEnvironment()
{
	DeleteClass(&Sail);
	DeleteClass(&Rope);
	DeleteClass(&Flag);
	//Boyer mod for new version #20170201
	DeleteClass(&sFlag);
	DeleteClass(&Pennant);
	//DeleteClass(&Penn);
	DeleteClass(&Vant);
}

void DeleteShipEnvironment()
{
	DeleteRiggingEnvironment();

	DelEventHandler(SHIP_CHANGE_CHARGE, "Ship_ChangeChargeEvent");
	DelEventHandler(SHIP_DROP_GOOD, "DropGoodsToSea");
	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_BORT_RELOADED, "Ship_OnBortReloaded");
	DelEventHandler(SHIP_ACTIVATE_FIRE_PLACE, "Ship_ActivateFirePlace");
	DelEventHandler(SHIP_UPDATE_PARAMETERS, "Ship_UpdateParameters");
	DelEventHandler(SHIP_CHECK_SITUATION, "Ship_CheckSituation");
	DelEventHandler(WHR_LIGHTNING, "Ship_Lightning");
	DelEventHandler("eSwitchPerks", "Ship_UpdatePerks");
	DelEventHandler("TornadoDamage", "Ship_TornadoDamage");
	DelEventHandler(SHIP_GET_RUNAWAY_POINT, "Ship_GetRunAwayPoint");
	DelEventHandler("frame", "Ship_VicSoundTime");
	DelEventHandler("Ship_SailsMoveSound", "Ship_SailsMoveSound");
	DelEventHandler("Ship_BortReloadEvent", "Ship_BortReloadEvent");
	//#20180222-03 Primary task complete post event
    DelEventHandler(SHIP_TASK_COMPLETE, "Shp_TaskComplete");

	for (int i=0; i<iNumShips; i++)
	{
		if (Ships[i] != -1)
		{
			// delete particles from ship/etc
			SendMessage(&Characters[Ships[i]], "l", MSG_SHIP_SAFE_DELETE);

			// delete sounds from ship
			//#20190402-03
			//if (CheckAttribute(&Characters[Ships[i]], "Ship.Sounds.WaterID"))	ReleaseSound(sti(Characters[Ships[i]].Ship.Sounds.WaterID));
			//if (CheckAttribute(&Characters[Ships[i]], "Ship.Sounds.SailsID"))	ReleaseSound(sti(Characters[Ships[i]].Ship.Sounds.SailsID));
			//if (CheckAttribute(&Characters[Ships[i]], "Ship.Sounds.TurnID"))	ReleaseSound(sti(Characters[Ships[i]].Ship.Sounds.TurnID));
		}
	}
	// scan characters for delete and id's
	DeleteEntitiesByType("ship");
}

void ClearAllFire()
{
	for (int i=0; i<iNumShips; i++)
	{
		// delete particles from ship/etc
		if (Ships[i] != -1) SendMessage(&Characters[Ships[i]], "l", MSG_SHIP_SAFE_DELETE);
	}
}

void Sea_ClearCheckFlag()
{
	if (bSeaActive)
	{
		int i;

		for (i=0; i<iNumShips; i++)
		{
			//Boyer fix #20170318-39
			if(sti(Ships[i]) >= 0 && sti(Ships[i]) < TOTAL_CHARACTERS) {
                if (!CheckAttribute(&Characters[Ships[i]], "DontCheckFlag"))
                {
                    DeleteAttribute(&Characters[Ships[i]], "CheckFlagYet");
                    DeleteAttribute(&Characters[Ships[i]], "CheckFlagDate");
                }
			}
		}
		for (i=0; i<MAX_COLONIES; i++)
		{
			if (CheckAttribute(&colonies[i], "commanderIdx"))
			{
				if (!CheckAttribute(&Characters[sti(colonies[i].commanderIdx)], "DontCheckFlag"))
				{
					DeleteAttribute(&Characters[sti(colonies[i].commanderIdx)], "CheckFlagYet");
					DeleteAttribute(&Characters[sti(colonies[i].commanderIdx)], "CheckFlagDate");
				}
			}
		}
	}
}

void CreateShipEnvironment()
{
	isEnemyAround = false;
	bUpdatePosMode = false;
	iVicSoundTime = 0;

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

	aref arCurWeather = GetCurrentWeather();
	sSeaSoundPostfix = arCurWeather.Sounds.Sea.Postfix;

	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_DROP_GOOD, "DropGoodsToSea", 0);
	SetEventHandler(SHIP_CREATE, "Ship_OnCreate", 0);
	SetEventHandler(SHIP_BORT_RELOADED, "Ship_OnBortReloaded", 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(SHIP_CREATELOADSHIP, "Ship_LoadShip", 0);
	SetEventHandler(SHIP_LOAD_SHIPACTIVATEFIREPLACE, "Ship_LoadShipActivateFirePlace", 0);
	SetEventHandler(SHIP_GET_RUNAWAY_POINT, "Ship_GetRunAwayPoint", 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);
	SetEventHandler("frame", "Ship_VicSoundTime", 0);
	SetEventHandler("Ship_SailsMoveSound", "Ship_SailsMoveSound", 0);
	SetEventHandler("Ship_BortReloadEvent", "Ship_BortReloadEvent", 0);
	//#20180222-03 Primary task complete post event
	SetEventHandler(SHIP_TASK_COMPLETE, "Shp_TaskComplete", 0);
}

//#20180222-03 Primary task complete post event
void Shp_TaskComplete()
{
    aref chr = GetEventData();
    int nPriority = GetEventData();
    int taskType = GetEventData();
    int isShipDead = GetEventData();

    //Trace("Task completed " + chr.id + ", " + taskType + ", " + isShipDead);
}
//#20210810-01
void Shp_TaskChange()
{
    aref chr = GetEventData();
    int nPriority = GetEventData();
    int taskType = GetEventData();
    aref echr = GetEventData();
    int oldTask = GetEventData();

    if(CheckAttribute(chr, "SeaAI.Task")) {
        chr.SeaAI.Task = taskType;
        if(CheckAttribute(echr, "index"))
            chr.SeaAI.Task.Target = echr.index;
        else
            chr.SeaAI.Task.Target = -1;
    }
}

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.3 - stf(aCharacter.TmpSkill.Accuracy);

	if (iArcadeSails == 1)// && aCharacter.id == characters[nMainCharacterIndex].id)
	{
		fAccuracy  = fAccuracy - 0.2;
		if (fAccuracy < 0.1)
		{
			fAccuracy = 0.1;
		}
	}
	/*
	if (aCharacter.id != characters[nMainCharacterIndex].id)
	{
		fAccuracy  = fAccuracy - 0.2;
		if (fAccuracy < 0.1)
		{
			fAccuracy = 0.1;
		}
	} */
	float fRadius = fAccuracy * Bring2Range(0.0, 120.0, 0.0, 1000.0, fDistance);
	return fRadius;
}

string Ship_GetGroupID(ref rCharacter)
{
	if (CheckAttribute(rCharacter, "SeaAI.Group"))// fix bug
	{
		return rCharacter.SeaAI.Group.Name;
	}
	else
	{
		return "";
	}
}

void Ship_NationAgressivePatent(ref rCharacter)
{
	ref rMainCharacter = GetMainCharacter();

	if (isMainCharacterPatented())
	{
		if (sti(rCharacter.nation) != PIRATE && GetNationRelation(sti(Items[sti(rMainCharacter.EquipedPatentId)].Nation), sti(rCharacter.nation)) != RELATION_ENEMY)
		{
			TakeItemFromCharacter(rMainCharacter, "patent_" + NationShortName(sti(Items[sti(rMainCharacter.EquipedPatentId)].Nation)));
			ChangeCharacterHunterScore(rMainCharacter, NationShortName(sti(Items[sti(rMainCharacter.EquipedPatentId)].Nation)) + "hunter", 40);
			Items[sti(rMainCharacter.EquipedPatentId)].TitulCur = 1;
			Items[sti(rMainCharacter.EquipedPatentId)].TitulCurNext = 0;
			RemoveCharacterEquip(rMainCharacter, "patent");
			rCharacter.CheckNationBounty = true;
		}
	}
	else
	{
		if (sti(rCharacter.nation) != PIRATE && !CheckAttribute(rCharacter, "CheckNationBounty"))
		{
			if (GetNationRelation(GetBaseHeroNation(), sti(rCharacter.nation)) != RELATION_ENEMY)
			{
				rCharacter.CheckNationBounty = true;
				ChangeCharacterHunterScore(rMainCharacter, NationShortName(sti(rCharacter.nation)) + "hunter", 1 + rand(2));
			}
		}
	}
}
//#20200307-02
void SetCompanionsEnemy(ref rCharacter)
{
    if(!CheckAttribute(rCharacter, "index")) return;

    int i, iCompanionIndex;
    int nIndex =  sti(rCharacter.index);
    for (i=1; i<COMPANION_MAX; i++)
    {
        iCompanionIndex = GetCompanionIndex(&Characters[nMainCharacterIndex],i);
        if (iCompanionIndex != -1)
        {
            SetCharacterRelationBoth(iCompanionIndex, nIndex, RELATION_ENEMY);
        }
    }
}

void Ship_NationAgressive(ref rMainGroupCharacter, ref rCharacter)
{
	ref rMainCharacter = GetMainCharacter();

	if (CheckAttribute(rCharacter, "Coastal_Captain")) return;

	//Make sure ship doesn't surrender again
	if (CheckAttribute(rCharacter, "surrendered")) {
		rCharacter.cannotsurrender = true;
	}
	//Boyer #20170318-42
	bool bCheckChar = false;
	if (rCharacter.id == "MQPirate")
        bCheckChar = true;
    if(!bCheckChar && CheckAttribute(rCharacter, "PGGAi.IsPGG") && CheckAttribute(rCharacter, "AlwaysEnemy")){
        if(sti(rCharacter.AlwaysEnemy) == 1)
            bCheckChar = true;
    }
    int nCharNation = sti(rCharacter.nation);
    //Boyer change where hostile PGG Hero treated same as Pirate
	if (bCheckChar && nCharNation != PIRATE)
	{
		rCharacter.nation = PIRATE;
        //Boyer #20170318-42
		//Ship_FlagRefresh(characterFromId("MQPirate"));
		if(CheckAttribute(rCharacter, "PGGAi.IsPGG")) {
            rCharacter.needFlagRefresh = true; //Flag refresh does not work, so need to do in update with ship object
		}
        else {
            Ship_FlagRefresh(characterFromId(rCharacter.id));

            //Ship_SetTaskRunaway(SECONDARY_TASK, sti(rCharacter.index), nMainCharacterIndex);
            //Boyer change #20170318-42
            if(rCharacter.id == "MQPirate")
            {
                Pchar.quest.DestroyPirate_PirateIsOut.win_condition.l1 = "ExitFromSea";
                Pchar.quest.DestroyPirate_PirateIsOut.win_condition = "DestroyPirate_PirateIsOut";
            }
        }
	}
	else
	{
		if (CheckAttribute(rCharacter, "ShipEnemyDisable") || CheckAttribute(rCharacter, "AlwaysFriend"))  return;
		if( sti(rMainCharacter.nation) == nCharNation && sti(rMainCharacter.nation) != PIRATE)
		{
            SetCharacterRelationBoth(sti(rCharacter.index), GetMainCharacterIndex(), RELATION_ENEMY);
            rMainCharacter.nation = PIRATE;
			DoQuestCheckDelay(NationShortName(PIRATE) + "_flag_rise", 0.1);
			// Ship_FlagRefresh(PChar);
			//SetNationToOfficers(PIRATE);
			ChangeCharacterReputation(rMainCharacter, -10);
		}
		Ship_NationAgressivePatent(rCharacter);
		// clear group temporary task status
		for (int i=0; i<MAX_SHIP_GROUPS; i++) { AIGroups[i].TempTask = false; }
		SetNationRelation2MainCharacter(sti(rMainGroupCharacter.nation), RELATION_ENEMY);
		SetCharacterRelationBoth(sti(rMainGroupCharacter.index), GetMainCharacterIndex(), RELATION_ENEMY);
		//#20200307-02
        SetCompanionsEnemy(rMainGroupCharacter);
		if (sti(rMainGroupCharacter.nation) != PIRATE)
		{
			ChangeCharacterHunterScore(rMainCharacter, NationShortName(sti(rMainGroupCharacter.nation)) + "hunter", 5);
		}
	}
	//#20200307-02
    SetCompanionsEnemy(rCharacter);
}

void Ship_FireAction()
{
	aref rCharacter = GetEventData();				// we attack this character
	aref rMainGroupCharacter = GetEventData();		// commander character for rCharacter
	int iRelation = GetEventData();

	if (LAi_IsDead(rCharacter)) return; // fix

	// if companion - decrease reputation
	if (IsCompanion(rCharacter))
	{
		if (CheckAttribute(rCharacter, "CompanionEnemyEnable") && sti(rCharacter.CompanionEnemyEnable))
		{
			SeaAI_SetCompanionEnemy(rCharacter);
			// disconnect companion from our group and set it enemy to us
		}
		return;
	}
	else
	{
		if (CheckAttribute(rCharacter, "ShipEnemyDisable") )  return;
		DeleteAttribute(rCharacter, "AlwaysFriend");
		//#20181031-02
        //#20171114-02 Tortuga pirate friendly
        //DeleteAttribute(rCharacter, "pirateFriend");
        if(sti(rCharacter.nation) != PIRATE)
            DeleteAttribute(pchar, "pirateFriend");
	}
	// surrendered ships
	if(CheckAttribute(rCharacter,"surrendered") && sti(rCharacter.surrendered.seatime) + 20 < GetSeaTime()) // 20 second grace period
	{
		//if (AISHIPDEBUG) trace("Fire on surrendered ships, -10 reputation");
		ChangeCharacterReputation(pchar, -10);
	}
	if (iRelation != RELATION_ENEMY)
	{
		Ship_NationAgressive(rMainGroupCharacter, rCharacter);

		// making whole group as enemy to us
		if (CheckAttribute(rMainGroupCharacter, "SeaAI.Group.Name"))
		{
		    //#20191013-01
		    if(rMainGroupCharacter.SeaAI.Group.Name != SURR_GROUP)
                Group_SetEnemyToCharacter(rMainGroupCharacter.SeaAI.Group.Name, nMainCharacterIndex);
		}
		if (CheckAttribute(&rCharacter, "relation.UseOtherCharacter"))
		{
			if (sti(rCharacter.relation.UseOtherCharacter) >= 0)
			{
				SetCharacterRelationBoth(nMainCharacterIndex, sti(rCharacter.relation.UseOtherCharacter), RELATION_ENEMY);
			}
		}
		//#20181101-01
		NationUpdate();
	} //#20190716-01
	//else
	//{
	//	Ship_NationAgressivePatent(rCharacter);
	//}
	DoQuestCheckDelay("NationUpdate", 0.3);

	if (CheckAttribute(rCharacter, "ShipTaskLock")) return;
	int i;
	string sGroupID, sGroupType;
	ref rGroup;

	// clear group temporary task status
	for (i=0; i<MAX_SHIP_GROUPS; i++) { AIGroups[i].TempTask = false; }

	// looking for enemy war/trade ships around
	/*
	for (i=0; i<iNumShips; i++)
	{
        //#20180707-02 Fix
	    if(Ships[i] < 0) continue;
		ref rShipCharacter = GetCharacter(Ships[i]);

		if (SeaAI_GetRelation(GetMainCharacterIndex(), Ships[i]) != RELATION_ENEMY) { continue; }

		sGroupID = Ship_GetGroupID(rShipCharacter);
		sGroupType = Group_GetType(sGroupID);
		rGroup = Group_GetGroupByID(sGroupID);

		//float fDistance = Ship_GetDistance2D(GetMainCharacter(), rShipCharacter);

		//if (!sti(rGroup.TempTask) && fDistance < 2000.0)	// if distance < 2 km
		//{
		//	switch (sGroupType)
		//	{
		//		case "trade":
		//			Group_SetTaskRunaway(sGroupID, PLAYER_GROUP);
		//		break;
		//		case "war":
		//			Group_SetTaskAttack(sGroupID, PLAYER_GROUP);
		//		break;
		//		case "pirate":
		//			Group_SetTaskAttack(sGroupID, PLAYER_GROUP);
		//		break;
		//	}
		//}
	}
	*/
	// if fort - return
	int iShipType = sti(rCharacter.Ship.Type);
	iShipType = sti(RealShips[iShipType].basetype);
	if (ShipsTypes[iShipType].name == "Fort")
	{
		return;
	}
    //#20190612-01
    if (CheckAttribute(rCharacter, "Surrendered") || CheckAttribute(rCharacter, "speak.ransom.taked")) {
        DeleteAttribute(rCharacter, "Surrendered");
        DeleteAttribute(rCharacter, "speak.ransom.taked");
        Ship_SetTaskRunaway(SECONDARY_TASK,sti(rCharacter.index), GetMainCharacterIndex());
        rCharacter.cannotsurrender = true;
    }
    else {
        sGroupID = Ship_GetGroupID(rMainGroupCharacter);
        //#20191013-01
        if(sGroupID != SURR_GROUP)
            Group_SetTaskAttack(PLAYER_GROUP, sGroupID);
    }
}

//#20171230-01 Mast damage mod
#define MAX_CAL_MAST_DMG 50

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(); // JA: added for surrender code

	switch (iDamageType)
	{
		case SHIP_MAST_TOUCH_ISLAND:
			fDamage = fDamage + 0.1;
		break;
		case SHIP_MAST_TOUCH_SHIP:
			//aref rCollideCharacter = GetEventData();
			fDamage = fDamage + 0.1;
		break;
		case SHIP_MAST_TOUCH_BALL:
		    //#20171230-01 Mast damage mod
            //int iBallCharacterIndex = GetEventData();  //Passed in message, but not needed as AIBalls has needed info
            //ref rBallCharacter = GetCharacter(iBallCharacterIndex);
            ref rCannon = GetCannonByType(sti(AIBalls.CurrentBallCannonType));
            int nCaliber = sti(rCannon.caliber);
            int	iBallType = sti(AIBalls.CurrentBallType);
            int iShipType = sti(rCharacter.ship.type);
            ref rBaseShip = GetRealShip(iShipType);
            int nClass = sti(rBaseShip.Class);
            float nDirect = 0.35; //Glancing
            int nKni = nCaliber;
            if(iBallType == GOOD_KNIPPELS)
                nKni += 5;
            if(rand(65) < nKni)
                nDirect = 1.1; //Direct
            float fCbrMDamage = 0.5 * retMin(makefloat(nCaliber) / MAX_CAL_MAST_DMG, 1.0);
            float fClsMDamage = 0.005 * (nClass / SHIP_CLASS_6);
            float tempDamage = 0.0;
            float baseDamage = 0.0;
			switch (iBallType)
			{
				case GOOD_BALLS:
					baseDamage = pow(nClass, 2.0) * 0.005; //0.025;
					CreateBlast(x,y,z); // excessive plume of planks and barrels, causing big water splashes
					CreateParticleSystemXPS("blast", x, y, z, 0.0, 0.0, 0.0, 0);	// two small gray puffs
					CreateParticleSystemXPS("blast_inv", x, y, z, 0.0, 0.0, 0.0, 0);	// six medium splinters
				break;
				case GOOD_GRAPES:
					baseDamage = 0.0;
					fCbrMDamage = 0.0;
					fClsMDamage = 0.0;
					CreateBlast(x,y,z); // excessive plume of planks and barrels, causing big water splashes
					CreateParticleSystemXPS("blast_inv", x, y, z, 0.0, 0.0, 0.0, 0);	// six medium splinters
				break;
				case GOOD_KNIPPELS:
					baseDamage = pow(nClass, 2.0) * 0.0035; //0.015;
					CreateBlast(x,y,z); // excessive plume of planks and barrels, causing big water splashes
					CreateParticleSystemXPS("blast_inv", x, y, z, 0.0, 0.0, 0.0, 0);	// six medium splinters
				break;
				case GOOD_BOMBS:
					baseDamage = pow(nClass, 2.0) * 0.0015; //0.005;
					CreateBlast(x,y,z); // excessive plume of planks and barrels, causing big water splashes
					CreateParticleSystemXPS("blast", x, y, z, 0.0, 0.0, 0.0, 0);	// two small gray puffs
					CreateParticleSystemXPS("blast_inv_explode", x, y, z, 0.0, 0.0, 0.0, 0);	// six medium splinters
				break;
			}
			tempDamage = baseDamage * fCbrMDamage + fClsMDamage;
			tempDamage = tempDamage * nDirect;
			fDamage = fDamage + tempDamage;
			//#20190113-06
            int iBallCharacterIndex = GetEventData();
            ref rBallCharacter = GetCharacter(iBallCharacterIndex);
            if(GetNationRelation(sti(rBallCharacter.Nation), sti(rCharacter.nation)) != RELATION_FRIEND) {
                //#20190117-01
                if(DeleteAttribute(rCharacter, "SeaAI.fortSanctuary")) {
                    //DeleteAttribute(rCharacter, "SeaAI.fortSanctuary")
                    rCharacter.SeaAI.hitInSanctuary = true;
                }
                else {
                    //#20190623-01
                    if(CheckAttribute(rCharacter, "FishingShips")) {
                        string sEGrp = Ship_GetGroupID(rBallCharacter);
                        FishingGroupRunaway(rCharacter.FishingShips, sEGrp);
                    }
                }
            }
		break;
	}
	//float fAccuracy = stf(rCharacter.TmpSkill.Accuracy);
	//fDamage = fDamage * (1.0 + (0.3 * fAccuracy));

	fDamage = Clampf(fDamage);
    //trace("Base " + (100 * baseDamage));
    //trace("fCbrMDamage " + (100 * fCbrMDamage));
    //trace("fClsMDamage " + (100 * fClsMDamage));
    //trace("Dmg " + (100 * fDamage));
	// if mast fall - play sound
	if (fDamage >= 1.0)
	{
		Play3DSound("mast_fall", x, y, z);
		rCharacter.ship.sp = CalculateShipSP(rCharacter);
		rCharacter.Tmp.SpeedRecall = 0;
	}
	return fDamage;
	//procMastFall
}
//#20190623-01
void FishingGroupRunaway(string CID, string EGroup)
{
    int achridx;
    ref achr;
    for(int i = 0; i < iNumShips; i++)
	{
		achridx = Ships[i];
		if(achridx < 0 || achridx >= TOTAL_CHARACTERS) continue;
		achr = GetCharacter(achridx);
		if(!CheckAttribute(achr, "FishingShips")) continue;
		if(achr.FishingShips != CID) continue;
        string sOurGrp = Ship_GetGroupID(achr);
        Group_SetTaskRunaway(sOurGrp, EGroup);
	}
}

float Ship_GetSailState(ref rCharacter)
{
	float fSailState = 1.0;
	SendMessage(rCharacter, "le", MSG_SHIP_GET_SAIL_STATE, &fSailState);
	return fSailState;
}

void Ship_SetExplosion(ref rCharacter, ref	rShipObject)
{
	int i = 0;
	// fire ship
	//int iNumFirePlaces = rand(2);
	//SendMessage(rShipObject, "le", MSG_SHIP_GET_NUM_FIRE_PLACES, &iNumFirePlaces);
	//for (i=0; i<=iNumFirePlaces; i++)
	//{
		//PostEvent(SHIP_ACTIVATE_FIRE_PLACE, rand(300), "ialsfl", rShipObject, rCharacter, i, "ship_onfire", 3, -1);
	//}
	//PostEvent(SHIP_ACTIVATE_FIRE_PLACE, 100, "ialsfl", rShipObject, rCharacter, i, "ship_onfire", 3, -1);
	PostEvent(SHIP_BRANDER_DETONATE, 800, "l", sti(rCharacter.index));
	PlaySound("Sea Battles\Vzriv_fort_001.wav");
	Ship_SetTaskNone(SECONDARY_TASK, sti(rCharacter.index));
}

void Ship_SetSailState(int iCharacterIndex, float fSailState)
{
	SendMessage(&AISea, "laf", AI_MESSAGE_SHIP_SET_SAIL_STATE, &Characters[iCharacterIndex], fSailState);
}

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

void Ship_SetTaskAbordage(int iTaskPriority, int iCharacterIndex, int iCharacterIndexAbordageTo)
{
	ref rCharacter = GetCharacter(iCharacterIndex);
	rCharacter.SeaAI.Task = AITASK_ABORDAGE;
	rCharacter.SeaAI.Task.Target = iCharacterIndexAbordageTo;
	SendMessage(&AISea, "lllaa", AI_MESSAGE_SHIP_SET_TASK, AITASK_ABORDAGE, iTaskPriority, &Characters[iCharacterIndex], &Characters[iCharacterIndexAbordageTo]);
}
//#20190313-01
void Ship_SetTaskAbordageMulti(int iTaskPriority, int iCharacterIndex, int iCharacterIndexAbordageTo)
{
	ref rCharacter = GetCharacter(iCharacterIndex);
	rCharacter.SeaAI.Task = AITASK_ABORDAGE_MULTI;
	rCharacter.SeaAI.Task.Target = iCharacterIndexAbordageTo;
	SendMessage(&AISea, "lllaa", AI_MESSAGE_SHIP_SET_TASK, AITASK_ABORDAGE_MULTI, iTaskPriority, &Characters[iCharacterIndex], &Characters[iCharacterIndexAbordageTo]);
}

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, int iFromIndex)
{
	ref rCharacter = GetCharacter(iCharacterIndex);
	rCharacter.SeaAI.Task = AITASK_RUNAWAY;
	rCharacter.SeaAI.Task.Target = iFromIndex;
	SendMessage(&AISea, "llla", AI_MESSAGE_SHIP_SET_TASK, AITASK_RUNAWAY, iTaskPriority, &Characters[iCharacterIndex]);
	Ship_SetSailState(iCharacterIndex, 1.0);
}

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]);
}
//#20210731-03
void Ship_SetTaskDriftWatch(int iTaskPriority, int iCharacterIndex)
{
	ref rCharacter = GetCharacter(iCharacterIndex);
	rCharacter.SeaAI.Task = AITASK_DRIFT_WATCH;
	//rCharacter.SeaAI.Task.Target = "";
	SendMessage(&AISea, "llla", AI_MESSAGE_SHIP_SET_TASK, AITASK_DRIFT_WATCH, 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]);
}
//#20211224-01
void Ship_HoldFire(int iCharacterIndex)
{
    ref rCharacter = GetCharacter(iCharacterIndex);
    rCharacter.SeaAI.HoldFire = true;
    SendMessage(&AISea, "lal", AI_MESSAGE_HOLD_FIRE, rCharacter, 1);
}
//#20230304-01
void Ship_SetIgnoreBoarding(int iCharacterIndex, bool val)
{
    ref rCharacter = GetCharacter(iCharacterIndex);
    rCharacter.SeaAI.IgnoreBoarding = val;
    SendMessage(&AISea, "lal", AI_MESSAGE_IGNOREBOARDING, rCharacter, val);
}

void Ship_SetIgnoreTacking(int iCharacterIndex, bool val)
{
    ref rCharacter = GetCharacter(iCharacterIndex);
    rCharacter.SeaAI.IgnoreTacking = val;
    SendMessage(&AISea, "lal", AI_MESSAGE_IGNORETACKING, rCharacter, val);
}

void Ship_ResumeFire(int iCharacterIndex)
{
    ref rCharacter = GetCharacter(iCharacterIndex);
    rCharacter.SeaAI.HoldFire = false;
    SendMessage(&AISea, "lal", AI_MESSAGE_HOLD_FIRE, rCharacter, 0);
}

void Ship_DoFire()
{
	int bCameraOutside = SeaCameras_isCameraOutside();
	SendMessage(&AISea, "lal", AI_MESSAGE_CANNON_FIRE, &Characters[nMainCharacterIndex], bCameraOutside);
}

void Ship_StartLoad()
{
	aref rCharacter = GetEventData();
	if(rCharacter.id != "0" && sti(rCharacter.ship.type) != SHIP_NOTUSED)
	{
		ref refBaseShip;
		int nShipType = GetCharacterShipType(rCharacter);
		refBaseShip = GetRealShip(nShipType);
		SetTextureForShip(refBaseShip, rCharacter);
	}
}

void Ship_EndLoad()
{
	SetTexturePath(0, "");
}

void Ship_SetTrackSettings(ref rCharacter)
{
	ref rShip = GetRealShip(GetCharacterShipType(rCharacter));

	rCharacter.Ship.Track.Enable = rShip.Track.Enable;
	if (!sti(rShip.Track.Enable)) return;

	rCharacter.Ship.Track.TrackDistance = 1.0;
	rCharacter.Ship.Track.WaveHeight2 = 0.9;
	rCharacter.Ship.Track.WaveHeight1 = 0.2;

	rCharacter.Ship.Track1.Texture = "ships\trailShip.tga.tx";
	rCharacter.Ship.Track1.TrackWidthSteps = 12.0;

	rCharacter.Ship.Track2.Texture = "ships\trailKeel.tga.tx";
	rCharacter.Ship.Track2.TrackWidthSteps = 4.0;

	rCharacter.Ship.Track1.ZStart = rShip.Track1.ZStart;
	rCharacter.Ship.Track1.LifeTime = rShip.Track1.LifeTime;
	rCharacter.Ship.Track1.Width = rShip.Track1.Width;
	rCharacter.Ship.Track1.Speed = rShip.Track1.Speed;

	rCharacter.Ship.Track2.ZStart = rShip.Track2.ZStart;
	rCharacter.Ship.Track2.LifeTime = rShip.Track2.LifeTime;
	rCharacter.Ship.Track2.Width = rShip.Track2.Width;
    //Boyer tracing for debug
    //if(!CheckAttribute(rCharacter, "Ship.Track2.speed")) Trace("No speed rChar.Ship " + rShip.Name);
    if(!CheckAttribute(rShip, "Track2.speed")) Trace("No speed rship " + rShip.Name);
        rCharacter.Ship.Track2.Speed = rShip.Track2.Speed;
}

void Ship_SetLightsOff(ref rCharacter, float fTime, bool bLights, bool bFlares, bool bNow)
{
	SendMessage(rCharacter, "lflll", MSG_SHIP_SETLIGHTSOFF, fTime, bLights, bFlares, bNow);
	rCharacter.Ship.Lights = 0; //fix
}

void Ship_SetLightsAndFlares(ref rCharacter)
{
	if(sti(sea.lights) == 1)
	{
		rCharacter.Ship.Flares = 1;
		rCharacter.Ship.Lights = 1;
	}
	else
	{
		rCharacter.Ship.Flares = 0;
		rCharacter.Ship.Lights = 0;
	}
}
//#20190902-02
bool Ship_Add2Sea(int iCharacterIndex, bool bFromCoast, string sFantomType)
{
    if((iNumShips+1) > MAX_SHIPS_IN_LOCATION) {
        trace("Ship_Add2Sea too many fail, iCharacterIndex:" + iCharacterIndex + ", sFantomType:" + sFantomType + ", iNumShips:" + iNumShips);
        return false;
    }
    ref		rCharacter = GetCharacter(iCharacterIndex);
	trace("Ship_Add2Sea, iCharacterIndex:" + iCharacterIndex + " Group = " + rCharacter.SeaAI.Group.Name + " Nation = " + rCharacter.nation + ", sFantomType:" + sFantomType + ", iNumShips:" + iNumShips);

	DeleteAttribute(rCharacter, "ship.cannons.chance_for_explode");

	//#2019012-01
	rCharacter.persFlgMount = 0;
	aref	arCharacter; makearef(arCharacter, Characters[sti(rCharacter.index)]);

	int iShipType = sti(rCharacter.Ship.Type);

	if(iShipType == SHIP_NOTUSED)
	{
		return false;
	}
	int iRealShip = sti(RealShips[iShipType].basetype);

	if(!CheckAttribute(&RealShips[iShipType], "name"))
	{
		Trace("Ship_Add2Sea() : SHIP " + iShipType + ", has no name!	NPCid = "+ rCharacter.id);
		if (MOD_BETTATESTMODE == "On") Log_Info("Ship_Add2Sea ERROR : SHIP " + iShipType + ", has no name!	NPCid = "+ rCharacter.id);
		return false;
	}
	//#20190902-01
	if (iRealShip > SHIP_TYPES_MAXVALID)
	{
		Trace("Character.id = " + rCharacter.id + ", has invalid ship type = " + iShipType + ", and try load to sea");
		return false;
	}
	if (!CheckAttribute(rCharacter, "AlwaysEnemy") && !CheckAttribute(rCharacter, "AlwaysFriend"))
    {
		DelCharacterRelation(iCharacterIndex);
	}
	DeleteAttribute(rCharacter, "Ship_SetTaskAbordage");
	DeleteAttribute(rCharacter, "Killer.status"); // SUPER FIX
	DeleteAttribute(rCharacter, "CheckFlagYet");
	DeleteAttribute(rCharacter, "CheckNationBounty");
	DeleteAttribute(rCharacter, "SeaAI.fortSanctuary");
	if (CheckAttribute(rCharacter, "DontCheckFlag"))
	{
		rCharacter.CheckFlagYet = true;
	}
	rCharacter.TmpSkillRecall = "0";
	rCharacter.Tmp.SpeedRecall = 0;
	rCharacter.Tmp.fShipSpeed  = 1;
	rCharacter.Tmp.fWatchFort  = rand(15);
	rCharacter.Tmp.fWatchFort.Qty = 15;

	if (iCharacterIndex == GetMainCharacterIndex()) { // NK 04-09-16 clears timer attribute, -21 for updatetime
		rCharacter.seatime = 0;
		rCharacter.lastupdateseatime = 0;
	}
	if (iCharacterIndex >= FANTOM_CHARACTERS)
	{
		SetBaseShipData(rCharacter);
		Ship_SetFantomData(rCharacter);

		Fantom_SetBalls(rCharacter, sFantomType);
		Fantom_SetGoods(rCharacter, sFantomType);
		SaveCurrentNpcQuestDateParam(rCharacter, "Add2SeaTime");
	}
	AcceptWindCatcherPerk(rCharacter);
	//rCharacter.TmpPerks.Turn = false;

	CharacterUpdateShipFromBaseShip(iCharacterIndex);

	rCharacter.Ship.Ang.y = 0.0;

	Ship_ClearImpulseStrength(rCharacter);
	Ship_SetLightsAndFlares(rCharacter);
	Ship_SetTrackSettings(rCharacter);

	aref baseship;
	ref refBaseShip;

	makearef(baseship,rCharacter.ship);
	int nShipType = GetCharacterShipType(rCharacter);
	refBaseShip = GetRealShip(nShipType);

	rCharacter.Tmp.BortsReloaded.Event = false;
	rCharacter.Tmp.BortsReloaded.cannonl = false;
	rCharacter.Tmp.BortsReloaded.cannonr = false;
	rCharacter.BOAL_ReadyCharge = "1";

	rCharacter.Features.GeraldSails = true;
	rCharacter.Ship.LastBallCharacter = -1;
	rCharacter.Ship.WindDotShip = 1.0;
	rCharacter.Ship.Speed.z = 0.0;
	rCharacter.Ship.Speed.y = 0.0;
	rCharacter.Ship.Speed.x = 0.0;

	rCharacter.Ship.BoxSize.x = 1.0;
	rCharacter.Ship.BoxSize.y = 1.0;
	rCharacter.Ship.BoxSize.z = 2.0;

	rCharacter.Ship.Strand = false;
	rCharacter.Ship.Strand.SndID = SOUND_INVALID_ID;
	rCharacter.Ship.Stopped = false;
	rCharacter.Ship.POS.Mode = SHIP_SAIL;
	rCharacter.Ship.SeaAI.Init.AbordageDistance = 30.0;
	ref	rCannon = GetCannonByType(sti(rCharacter.Ship.Cannons.Type));
	float range = 550;
	if (CheckAttribute(rCannon, "FireRange")) range = stf(rCannon.FireRange);
	rCharacter.Ship.SeaAI.Init.AttackDistance = range  / (1.4 + frandSmall(2.0));
	rCharacter.Ship.SeaAI.Init.AttackDistance.qtyTryChangeCannon = 0;
	//#20180126-01
	rCharacter.Ship.SeaAI.Init.FollowDistance = makefloat(180 + rand(120));
	DeleteAttribute(rCharacter, "Ship.SeaAI.Init.AttackRangeSet");
    //#20180208-01 Add flexibility to AI ship
    rCharacter.Ship.SeaAI.Init.WindC = 2.0;
    rCharacter.Ship.SeaAI.Init.FortC = 2.75;
    rCharacter.Ship.SeaAI.Init.DefEnemyC = 5.0;
    rCharacter.Ship.SeaAI.Init.MinEnemyC = 3.0;
    rCharacter.Ship.SeaAI.Init.MaxEnemyC = 10.0;
    rCharacter.Ship.SeaAI.Init.IslandDist = 500.0;
    rCharacter.Ship.SeaAI.Init.IslandC = 1.75;
    rCharacter.Ship.SeaAI.Init.POSRatio = 0.3;
    rCharacter.Ship.SeaAI.Init.LowPredTime = 20.0;
    rCharacter.Ship.SeaAI.Init.HighPredTime = 50.0;
    rCharacter.Ship.SeaAI.Init.LowSpeed = 0.1;
    rCharacter.Ship.SeaAI.Init.HighSpeed = 50.0;
    rCharacter.Ship.SeaAI.Init.MinBrakeDist = 15.0;
    rCharacter.Ship.SeaAI.Init.FortEnemyDist = 750.0;
    //#20190313-01
    rCharacter.Ship.SeaAI.Init.MultiBoardDist = 100.0;

    rCharacter.Ship.SeaAI.Init.MaxTackAccumDistance = 200.0;
    rCharacter.Ship.SeaAI.Init.TackOffDistance = 50.0;
    rCharacter.Ship.SeaAI.Init.TackCheckAngle = (PIm2 / 13.0);
    rCharacter.Ship.SeaAI.Init.TackTurnAngle = (PIm2 / 12.0);

    rCharacter.Ship.SeaAI.Init.AttackRotate = 1.65; //1.2;
    rCharacter.Ship.SeaAI.Init.AttackRand = 50.0;

	rCharacter.Ship.Sounds = "";

	if(sti(rCharacter.Ship.Cannons.Type) == CANNON_TYPE_NONECANNON)
	{
		rCharacter.Ship.Cannons.Type = CANNON_TYPE_CANNON_LBS4;
	}
	if (bFromCoast == false) { rCharacter.Ship.Speed.z = 1.0; }

	// create ship
	Event("eSwitchPerks", "l", iCharacterIndex);
	Event(SHIP_UPDATE_PARAMETERS, "lf", iCharacterIndex, stf(rCharacter.Ship.Speed.z));
	CreateEntity(&Characters[iCharacterIndex], "Ship");

	aref arChar, arShip;
	makearef(arChar, characters[iCharacterIndex]);
	makearef(arShip, RealShips[iShipType]);

	Ship_CreateStaticSounds(rCharacter);

	//SendMessage(&AISea, "liaa", AI_MESSAGE_ADD_SHIP, &Characters[iCharacterIndex], &Characters[iCharacterIndex], &RealShips[iShipType]);
	int nAddShp = SendMessage(&AISea, "liaa", AI_MESSAGE_ADD_SHIP, &Characters[iCharacterIndex], arChar, arShip);
	//#20190902-01
	if (!nAddShp)
	{
		Trace("Character.id = " + rCharacter.id + ", failed to add ship type = " + iShipType + ", and try load to sea");
		rCharacter.curshipnum = -1;
		return false;
	}
	//#20211224-01
	//Bring2Range(1.0, 0.65, 100.0, 1000.0, maxfiredist); //Will be range from <=100= 1.0 to >=1000 max=.65
	//SendMessage(&AISea, "laf", AI_MESSAGE_RESTRICT_FIREDIST, rCharacter, 0.75); //Const 0.75 of maxfiredist
	//For scene reload
	rCharacter.SeaAI.RangeLimit.useConstant = false;
	//rCharacter.SeaAI.RangeLimit.constRange = 0.75;
	rCharacter.SeaAI.RangeLimit.minPercent = 1.0;
	rCharacter.SeaAI.RangeLimit.maxPercent = 0.65;
	rCharacter.SeaAI.RangeLimit.minRange = 100.0;
	rCharacter.SeaAI.RangeLimit.maxRange = 1000.0;
	SendMessage(&AISea, "laffff", AI_MESSAGE_RESTRICT_FIREDIST, rCharacter, 1.0, 0.65, 100.0, 1000.0);
	/* if (!CheckAttribute(rCharacter, "Ship.BoxSize.x"))
	{
		trace("No Ship.BoxSize id=" + rCharacter.id);
		rCharacter.Ship.BoxSize.x = 1.0;
		rCharacter.Ship.BoxSize.y = 1.0;
		rCharacter.Ship.BoxSize.z = 2.0;
	} */
	//#20230304-01
	rCharacter.SeaAI.HoldFire = false;
	rCharacter.SeaAI.IgnoreBoarding = false;
	rCharacter.SeaAI.IgnoreTacking = false;
	ReloadProgressUpdate();

	Ships[iNumShips] = iCharacterIndex;
	rCharacter.curshipnum = iNumShips;

	//trace("Adding ship num: " + iNumShips);
	//JA fix for lookout spotting quest ships
	CheckForQuestShip(arChar);
	iNumShips++;
	return true;
}

void Ship_RecreateStaticSounds()
{
	for (int i=0; i<iNumShips; i++) {
		//Boyer fix for log error: #20170318-39
		//RUNTIME ERROR - file: sea_ai\AIShip.c; line: 851
		//invalid index -1 [size:1000]
		//RUNTIME ERROR - file: sea_ai\AIShip.c; line: 851
		//function 'Ship_RecreateStaticSounds' stack error
		if (makeint(Ships[i]) > -1) {
			Ship_CreateStaticSounds(&Characters[Ships[i]]);
		}
	}
	//#20190117-03
	if (CheckAttribute(&WeatherParams, "Rain.Sound") && sti(WeatherParams.Rain.Sound))
        Whr_SetRainSound(true, sti(Weathers[iCurWeatherNum].Night));
}

void Ship_CreateStaticSounds(ref rCharacter)
{
	if (!CheckAttribute(rCharacter, "Ship.Pos.x")) return; // fix
	if (GetCharacterShipType(rCharacter) == SHIP_NOTUSED) return;

	ref refBaseShip = GetRealShip(GetCharacterShipType(rCharacter));
	// create water sound
	float fX = stf(rCharacter.Ship.Pos.x);
	float fY = stf(rCharacter.Ship.Pos.y);
	float fZ = stf(rCharacter.Ship.Pos.z);
	rCharacter.Ship.Sounds.WaterID.x = 0.0;
	rCharacter.Ship.Sounds.WaterID.y = 0.0;
	rCharacter.Ship.Sounds.WaterID.z = 0.12;
	rCharacter.Ship.Sounds.WaterID = Play3DSoundComplex("ship_water_" + refBaseShip.Soundtype, fX, fY, fZ, true, false);
	if (sti(rCharacter.Ship.Sounds.WaterID) > 0) { Sound_SetVolume(sti(rCharacter.Ship.Sounds.WaterID), 0.0); }
	rCharacter.Ship.Sounds.WaterSpeed = 30.0;

	// create sails sound
	rCharacter.Ship.Sounds.SailsID.x = 0.0;
	rCharacter.Ship.Sounds.SailsID.y = 0.5;
	rCharacter.Ship.Sounds.SailsID.z = 0.0;
	rCharacter.Ship.Sounds.SailsID = Play3DSoundComplex("sails_up_" + refBaseShip.Soundtype, fX, fY, fZ, true, false);
	if (sti(rCharacter.Ship.Sounds.SailsID) > 0) { Sound_SetVolume(sti(rCharacter.Ship.Sounds.SailsID), 0.0); }
	rCharacter.Ship.Sounds.SailsMove = 0.0;

	// create turn sound
	rCharacter.Ship.Sounds.TurnID.x = 0.0;
	rCharacter.Ship.Sounds.TurnID.y = 0.0;
	rCharacter.Ship.Sounds.TurnID.z = 0.0;
	rCharacter.Ship.Sounds.TurnID = Play3DSoundComplex("ship_turn_" + refBaseShip.Soundtype, fX, fY, fZ, true, false);
	if (sti(rCharacter.Ship.Sounds.TurnID) > 0) { Sound_SetVolume(sti(rCharacter.Ship.Sounds.TurnID), 0.0); }
	rCharacter.Ship.Sounds.TurnSpeed = 0.05;
}

void Ship_PlaySound3DComplex(ref rCharacter, string sSoundID, float fVolume, float fX, float fY, float fZ)
{
	if (!CheckAttribute(rCharacter, "Ship.Pos.x")) return; // fix
	float x = stf(rCharacter.Ship.Pos.x);
	float y = stf(rCharacter.Ship.Pos.y);
	float z = stf(rCharacter.Ship.Pos.z);

	float aY = stf(rCharacter.Ship.Ang.y);

	if (!CheckAttribute(rCharacter, "Ship.BoxSize.x")) return; // fix

	float x1 = fX * stf(rCharacter.Ship.BoxSize.x) / 2.0;
	float z1 = fZ * stf(rCharacter.Ship.BoxSize.z) / 2.0;
	RotateAroundY(&x1, &z1, cos(aY), sin(aY));

	int iSoundID = Play3DSoundRet(sSoundID, x + x1, y + fY * stf(rCharacter.Ship.BoxSize.y) / 2.0, z + z1);
	if (iSoundID > 0) { Sound_SetVolume(iSoundID, fVolume); }
}

void Ship_PlaySound3D(ref rCharacter, string sSoundID, float fVolume)
{
	float x = stf(rCharacter.Ship.Pos.x);
	float y = stf(rCharacter.Ship.Pos.y);
	float z = stf(rCharacter.Ship.Pos.z);

	int iSoundID = Play3DSoundRet(sSoundID, x, y, z);
	if (iSoundID > 0) { Sound_SetVolume(iSoundID, fVolume); }
}

void Ship_OnBortReloaded()
{
	aref aCharacter = GetEventData();
	string sBort = GetEventData();

	if (!bSeaLoaded || bAbordageStarted) { return; }  // fix
	if (sti(aCharacter.index) != nMainCharacterIndex) { return; } // fix
	/*{
		if (sBort == "cannonl" || sBort == "cannonr") { Ship_PlaySound3D(aCharacter, "bortreloaded_" + sBort, 1.0);	}
		//"bortreloaded_all"
	}*/
	if (!sti(aCharacter.Tmp.BortsReloaded.Event))
	{
		aCharacter.Tmp.BortsReloaded.Event = true;
		PostEvent("Ship_BortReloadEvent", 300, "a", aCharacter);
	}
	if (sBort == "cannonl") { aCharacter.Tmp.BortsReloaded.cannonl = true; }
	else { if (sBort == "cannonr") { aCharacter.Tmp.BortsReloaded.cannonr = true; } }
}

void Ship_BortReloadEvent()
{
	aref aCharacter = GetEventData();

	if (sti(aCharacter.Tmp.BortsReloaded.Event))
	{
		bool bLeft = sti(aCharacter.Tmp.BortsReloaded.cannonl) == true;
		bool bRight = sti(aCharacter.Tmp.BortsReloaded.cannonr) == true;

		if (bLeft && bRight)
		{
		    //Boyer change #20170327-01  Sound to use aliases changes
			PlaySound("bortreloaded_all");
		}
		else
		{
		    //Boyer change #20170327-01  Sound to use aliases changes
			if (bLeft)  { PlaySound("bortreloaded_cannonl"); }
			else { if (bRight) { PlaySound("bortreloaded_cannonr"); } }
		}
	}
	Ship_ClearBortsReloadedEvent(aCharacter);
}

void Ship_ClearBortsReloadedEvent(aref aCharacter)
{
	aCharacter.Tmp.BortsReloaded.Event = false;
	aCharacter.Tmp.BortsReloaded.cannonl = false;
	aCharacter.Tmp.BortsReloaded.cannonr = false;
}

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));

	//fix if (bSeaLoaded) { Ship_PlaySound3D(rCharacter, "reloadstart_" + sBallName, 1.0); }

	Ship_ClearBortsReloadedEvent(rCharacter);
}

void Ship_ChangeCharge(ref rCharacter, int iNewChargeType, bool fromInterface)
{
    ref rGood = GetGoodByType(iNewChargeType);
    if(CheckAttribute(rCharacter,"Ship.Cannons.Charge.Type") && rCharacter.Ship.Cannons.Charge.Type == iNewChargeType)
    {
        if(fromInterface)
        {
            string sGName = "";
            if (rGood)
                sGName = xiStr(rGood.name);
            Log_Info(sGName + xiStr("already_loaded"));
            //PlaySound("knock");
        }
        return;
    }
	SendMessage(&AISea, "la", AI_MESSAGE_CANNON_RELOAD, rCharacter); // must be first line in this function

	if (rGood) {
		rCharacter.Ship.Cannons.Charge = rGood.name;
		rCharacter.Ship.Cannons.Charge.Type = iNewChargeType;
	}
	Cannon_RecalculateParameters(sti(rCharacter.index));
	//#20180126-01
    ChangeAttackDistance(rCharacter, iNewChargeType);

	//rCharacter.BOAL_ReadyCharge = "1";
	//fix Ship_PlaySound3D(rCharacter, "reloadstart_" + rGood.name, 1.0);

	Ship_ClearBortsReloadedEvent(rCharacter);
}

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

	rCharacter.Ship.Speed.z = 0.0;
	rCharacter.Ship.Speed.y = 0.0;
	rCharacter.Ship.Speed.x = 0.0;
}
//#20190710-02
void fortDecision(ref rSitu, ref rChar, int iCharIx, int iFortCmd)
{
    if (sti(rChar.Tmp.fWatchFort) >= sti(rChar.Tmp.fWatchFort.Qty)) {
        float fFortPwrCompare = 2.5;
        float fDistConcern = 1200.0;
        if(CheckAttribute(rChar,"EncType"))
        {
            switch(rChar.EncType)
            {
                case "trade":
                    fDistConcern = 1750.0;
                break;
                case "war":
                    fFortPwrCompare = 0.8;
                break;
                 //#20191021-01
                case "pirate":
                    fFortPwrCompare = 1.5; //was 1.2
                break;
            }
        }
        rChar.Tmp.fWatchFort = 0;
        if (stf(rSitu.MinFortDistance) < fDistConcern)
        {
            //#20190709-02
            float mypower, frtpower;
            SendMessage(&AISea, "lale", AI_MESSAGE_GROUP_GET_POWERREL, rChar, RELATION_FRIEND, &mypower);
            frtpower = stf(rSitu.NearFortPower);
            frtpower += 0.0000001; //Div by zero
            float pwrDiff = mypower / frtpower;
            if(pwrDiff < fFortPwrCompare)
            {
                Ship_SetTaskRunaway(SECONDARY_TASK, iCharIx, iFortCmd);
                rChar.Tmp.fWatchFort.Qty = 200;
            }
        }
    }
    else
    {
        rChar.Tmp.fWatchFort = sti(rChar.Tmp.fWatchFort) + 1;
    }
}

void Ship_CheckSituation()
{
    //#20181101-01
    int oldRel, newRel;
	ref		rShip;
	aref	rSituation;

	aref	rCharacter = GetEventData();
	aref	rShipObject = GetEventData();

	//#20180322-01 Ships in Harbor traffic
	if(!bSeaActive) return;

	if (LAi_IsDead(rCharacter) || sti(rCharacter.ship.type) == SHIP_NOTUSED) {
            trace("check situ ret " + rCharacter.ship.name);
    return; }

	int iCharIdx = sti(rCharacter.index);
	if (iCharIdx == nMainCharacterIndex) { return; }
	//#20190311-02
    bool bWaitRepel = CheckAttribute(rCharacter, "waitRepel");
    if(bWaitRepel) {
        float fWaitRepel = stf(rCharacter.waitRepel);
        if(fWaitRepel > WAIT_REPEL_TIME) {
            DeleteAttribute(rCharacter, "waitRepel");
            bWaitRepel = false;
        }
        else {
            rCharacter.waitRepel = fWaitRepel + (fHighPrecisionDeltaTime * 10.0);
        }
    }
    //#20190703-01
    makearef(rSituation, rCharacter.SeaAI.Update.Situation);
    if (CheckCanSurrender(rCharacter) && Ship_CheckMorale(iCharIdx,false)) {
		if(Ship_Check_Surrender(iCharIdx, 1.0)) {
			Ship_Surrender(iCharIdx);
		}
	}
	//#20180126-02
	int aiTask = AITASK_NONE;
	if (CheckAttribute(rCharacter, "SeaAI.Task"))
    {
         aiTask = sti(rCharacter.SeaAI.Task);
    }
	else
    {
		rCharacter.SeaAI.Task = AITASK_NONE;
	}
	rShip = GetRealShip(sti(rCharacter.Ship.Type));

    //Boyer add #20170318-42 -- Flag refresh does not work for NPC Heroes, so need to use actual ship object
    //#20190117-01
    if(DeleteAttribute(rCharacter, "needFlagRefresh"))
    {
        //DeleteAttribute(rCharacter, "needFlagRefresh")
        Ship_FlagRefreshByShip(rCharacter, rShipObject);
    }
    //End Boyer add
	float	fMinEnemyDistance = stf(rSituation.MinEnemyDistance);
	float	fMinFriendDistance = stf(rSituation.MinFriendDistance);
	float	fMinGroupDistance = stf(rSituation.MinGroupDistance);
	//#20190701-01
	int nMinEnemyIdx = sti(rSituation.MinEnemyIndex);
	int nMinFriendIdx = sti(rSituation.MinFriendIndex);
	int nMinGroupIdx = sti(rSituation.MinGroupIndex);
	string sGroupID = Ship_GetGroupID(rCharacter);
	int cmdridx = Group_GetGroupCommanderIndex(sGroupID);
	bool bIamCmdr = false;
	if(iCharIdx == cmdridx) bIamCmdr = true;
	bool bIsCompanion = IsCompanion(rCharacter);
    float fMyPwr = -1.0;
    float fOtherPwr = -1.0;
	float	fDistance;
	int nLastBallChar = sti(rCharacter.Ship.LastBallCharacter);
	// fix AI fMinEnemyDistance
	if (nLastBallChar != -1 && LAi_IsDead(GetCharacter(nLastBallChar)))
	{
		rCharacter.Ship.LastBallCharacter = -1;
	}
	//#20190113-06
	bool bFortSanctuary = CheckAttribute(rCharacter, "SeaAI.fortSanctuary");
	//#20180925-01
    int nTaskTarg = 0;
	if (CheckAttribute(rCharacter, "SeaAI.Task.Target"))
	{
	    nTaskTarg = sti(rCharacter.SeaAI.Task.Target);

		if(aiTask == AITASK_RUNAWAY)
		{
			if(CheckForSpeak(iCharIdx) == true)
			{
				Sea_Speak(rCharacter, iCharIdx, -1); //Offer money to get away
			}
			//#20190612-03 will never reach
			/*
			if(aiTask == AITASK_ATTACK ||
				aiTask == AITASK_DEFEND ||
				aiTask == AITASK_DEFEND_GROUP)
			{
				if(CheckForSpeak(iCharIdx) == true)
				{
					if(sti(pchar.ship.hp) * 2 < sti(rCharacter.ship.hp))
					{
						if(!CheckAttribute(rCharacter, "last_speak"))
						{
							Sea_Speak(rCharacter, iCharIdx, 1); // ask for money to leave player alone
						}
						else
						{
							rCharacter.last_speak = sti(rCharacter.last_speak) - 1;
							if(sti(rCharacter.last_speak) == 0)
							{
								DeleteAttribute(rCharacter, "last_speak");
							}
						}
					}
				}
			}
			*/
		}
		else { //#20190701-01
            bool bFindNewTrg = false;
            if (rCharacter.SeaAI.Task.Target != "") {
                if(nTaskTarg > -1) {
                    ref chrTrg = GetCharacter(nTaskTarg);
                    if(LAi_IsDead(chrTrg))
                        bFindNewTrg = true;
                    else {
                        //#20190720-01
                        if(CheckAttribute(chrTrg, "surrendered") || CheckAttribute(chrTrg, "speak.ransom.taked")) {
                            bFindNewTrg = true;
                        }
                        else {
                            if(CheckAttribute(rCharacter, "speak.ransom.taked")) {
                               Ship_SetTaskRunaway(SECONDARY_TASK,iCharIdx, GetMainCharacterIndex());
                            }
                        }
                    }
                }
                else {
                    bFindNewTrg = true;
                }
            }
		    if (bFindNewTrg)
            {
                //#20180126-02 Task target dead update
                //#20190313-01
                if(aiTask == AITASK_NONE || aiTask == AITASK_ATTACK || aiTask == AITASK_BRANDER || aiTask == AITASK_DEFEND || aiTask == AITASK_ABORDAGE || aiTask == AITASK_ABORDAGE_MULTI || aiTask == AITASK_FOLLOW) {
                    int bChkFrdfRef = -1;
                    int bChkGrpRef = -1;
                    if(nMinFriendIdx > -1 && CheckAttribute(&characters[nMinFriendIdx], "SeaAI.Task.Target"))
                        bChkFrdfRef = sti(characters[nMinFriendIdx].SeaAI.Task.Target);
                    if(nMinGroupIdx > -1 && CheckAttribute(&characters[nMinGroupIdx], "SeaAI.Task.Target"))
                        bChkGrpRef = sti(characters[nMinGroupIdx].SeaAI.Task.Target);
                    if(fMinGroupDistance < fMinEnemyDistance && bChkGrpRef != iCharIdx) {
                        Ship_SetTaskDefend(SECONDARY_TASK,iCharIdx,nMinGroupIdx);
                        aiTask = AITASK_DEFEND;
                    }
                    else {
                        if(fMinFriendDistance < fMinEnemyDistance && bChkFrdfRef != iCharIdx) {
                            Ship_SetTaskDefend(SECONDARY_TASK,iCharIdx,nMinFriendIdx);
                            aiTask = AITASK_DEFEND;
                        }
                        if(nMinEnemyIdx != -1) {
                            Ship_SetTaskAttack(SECONDARY_TASK,iCharIdx,nMinEnemyIdx);
                            aiTask = AITASK_ATTACK;
                        }
                        else {
                            if(!bIamCmdr) {
                                Ship_SetTaskDefend(SECONDARY_TASK,iCharIdx,cmdridx);
                                aiTask = AITASK_DEFEND;
                            }
                        }
                    }
                }
                else {
                    if(aiTask != AITASK_DRIFT && aiTask != AITASK_DRIFT_WATCH && !CheckAttribute(rCharacter, "surrendered")) {
                        if(nMinEnemyIdx != -1)
                            Ship_SetTaskRunaway(SECONDARY_TASK,iCharIdx, nMinEnemyIdx);
                        else
                            Ship_SetTaskDefend(SECONDARY_TASK,iCharIdx, nMinFriendIdx);
                        aiTask = AITASK_RUNAWAY;
                    }
                }
            }
            //if(aiTask == AITASK_ABORDAGE || aiTask == AITASK_ATTACK || aiTask == AITASK_DEFEND || aiTask == AITASK_FOLLOW) {

            //    switch(aiTask) {
            //        case AITASK_ABORDAGE:
            //        break;
            //    }
                //string sOtherGrp = Ship_GetGroupID(rCharacter);
                //SendMessage(&AISea, "lssee", AI_MESSAGE_GROUP_GET_POWERFLEEME, sGroupID, Ship_GetGroupID(&characters[sti(rCharacter.SeaAI.Task.Target)]), &fMyPwr, &fOtherPwr);
            //}
		}
		nTaskTarg = sti(rCharacter.SeaAI.Task.Target);
		/*if (rCharacter.SeaAI.Task == AITASK_ABORDAGE && rCharacter.SeaAI.Task.Target == "")
		{
			Ship_SetTaskRunaway(SECONDARY_TASK, sti(rCharacter.index), nMainCharacterIndex);
		} */



		//#20181031-02
		// check ships for abordage
        if (Ship_AutoAbordage(rCharacter, fMinEnemyDistance)) { return; }
	}
	// check for enough quantity of balls
	int iShipCannonsNum  = GetCannonsNum(rCharacter);//sti(rShip.CannonsQuantity);
	int iCurrentBallType = sti(rCharacter.Ship.Cannons.Charge.Type);
	int iNewBallType = iCurrentBallType;
    oldRel = GetRelation(iCharIdx, GetMainCharacterIndex());
    //#20190612-02
    bool bStartRunaway = false;
	if(CheckAttribute(rCharacter, "Ship.Cannons.borts.cannonr.ChargeRatio"))
	{
		if(CheckAttribute(rCharacter, "Ship.Cannons.chargeoverride")) {
			if(sti(rCharacter.Ship.Cannons.chargeoverride) != iCurrentBallType) {
				Ship_ChangeCharge(rCharacter, sti(rCharacter.Ship.Cannons.chargeoverride), false);
			}
			if (GetCargoGoods(rCharacter,iNewBallType) < iShipCannonsNum) {
				//check last warning time
				int LastWarning = GetNpcQuestPastMinutesParam(rCharacter,"Cannons.chargeoverride");
				if (LastWarning == 0 || LastWarning > 10) {
					ref rGood = GetGoodByType(iNewBallType);
					SaveCurrentNpcQuestDateParam(rCharacter,"Cannons.chargeoverride");
					if(bIsCompanion)
                        Log_SetStringToLog(rCharacter.ship.name + xiStr("MSG_AIShip_29") + rGood.name + xiStr("MSG_AIShip_30"));
                    else //#20190612-02
                        bStartRunaway = true;
				}
			}
		}
		else
		{
		    //#20181031-02
		    bool bBalls, bKnippels, bBombs, bGrapes;
            bBalls = true; bKnippels = true; bBombs = true; bGrapes = true;

            float fRightChargeRatio = stf(rCharacter.Ship.Cannons.borts.cannonr.ChargeRatio);
            float fLeftChargeRatio = stf(rCharacter.Ship.Cannons.borts.cannonl.ChargeRatio);

            if (!CheckAttribute(rCharacter, "Sea_AI.cannon.charge"))
            {
                if (fRightChargeRatio != 1.0 && fLeftChargeRatio != 1.0)
                {
                    //#20181031-02
                    if (GetCargoGoods(rCharacter,GOOD_BALLS)	< iShipCannonsNum) { bBalls	= false; }
                    if (GetCargoGoods(rCharacter,GOOD_BOMBS)	< iShipCannonsNum) { bBombs	= false; }
                    if (GetCargoGoods(rCharacter,GOOD_KNIPPELS) < iShipCannonsNum) { bKnippels = false; }
                    if (GetCargoGoods(rCharacter,GOOD_GRAPES)	< iShipCannonsNum) { bGrapes	= false; }
                    iNewBallType = Ship_FindOtherBallType(rCharacter, fMinEnemyDistance, bBalls, bBombs, bGrapes, bKnippels);
                }
                rCharacter.Sea_AI.cannon.charge = iNewBallType;
            }
            else
            {
                if(!CheckAttribute(rCharacter, "Sea_AI.cannon.charge.lasttime"))
                {
                    rCharacter.Sea_AI.cannon.charge.lasttime = 0;
                }
                if (fRightChargeRatio == 1.0 && fLeftChargeRatio == 1.0)
                {
                    rCharacter.Sea_AI.cannon.charge.lasttime = sti(rCharacter.Sea_AI.cannon.charge.lasttime) + 1;
                }
                if(sti(rCharacter.Sea_AI.cannon.charge.lasttime) > 15 || CheckAttribute(rCharacter,"Ship.Cannons.chargeoverridecancel"))
                {
                    //#20181031-02
                    if (GetCargoGoods(rCharacter,GOOD_BALLS)	< iShipCannonsNum) { bBalls	= false; }
                    if (GetCargoGoods(rCharacter,GOOD_BOMBS)	< iShipCannonsNum) { bBombs	= false; }
                    if (GetCargoGoods(rCharacter,GOOD_KNIPPELS) < iShipCannonsNum) { bKnippels = false; }
                    if (GetCargoGoods(rCharacter,GOOD_GRAPES)	< iShipCannonsNum) { bGrapes	= false; }
                    DeleteAttribute(rCharacter,"Ship.Cannons.chargeoverridecancel");
                    iNewBallType = Ship_FindOtherBallType(rCharacter, fMinEnemyDistance, bBalls, bBombs, bGrapes, bKnippels);
                    rCharacter.Sea_AI.cannon.charge = iNewBallType;
                    rCharacter.Sea_AI.cannon.charge.lasttime = 0;
                    rCharacter.Ship.SeaAI.Init.AttackDistance.qtyTryChangeCannon = sti(rCharacter.Ship.SeaAI.Init.AttackDistance.qtyTryChangeCannon) + 1;
                }
            }
            //#20190612-02
            if (iNewBallType >= 0)
            {
                if(iNewBallType != iCurrentBallType)
                    Ship_ChangeCharge(rCharacter, iNewBallType, false);
            }
            else {
                bStartRunaway = true;
            }
        }
	}
	// check some tasks
	if (CheckAttribute(rCharacter, "SeaAI.Task") && CheckAttribute(rCharacter, "SeaAI.Task.Target"))
	{
		switch (aiTask)
		{
			case AITASK_BRANDER:
				ref rCharacter2Brander = GetCharacter(nTaskTarg);
				float fBranderDistance = 45.0 + (7.0 - stf(rShip.Class)) * 15.0;
				//#20180524-01 Remove unneeded sqrt
				fBranderDistance *= fBranderDistance;
				fDistance = Ship_GetDistance2DRel(rCharacter, rCharacter2Brander);
				if (fBranderDistance > fDistance)
				{
					// fire ship
					Ship_SetExplosion(rCharacter, rShipObject);
					Log_Info("" + XI_ConvertString(rShip.BaseName) + " '" + rCharacter.Ship.Name + "' " + GetShipSexWord(rShip.BaseName, xiStr("MSG_AIShip_1"), xiStr("MSG_AIShip_2")) + xiStr("MSG_AIShip_3"));
					return;
				}
				//Trace("test1 rCharacter2Brander = " + rCharacter2Brander.index);
			break;
			case AITASK_ATTACK:
			    if (CheckAttribute(rCharacter, "Sea_AI.cannon.charge.lasttime") && sti(rCharacter.Sea_AI.cannon.charge.lasttime) == 1)
				{
					//if (sti(rCharacter.Ship.SeaAI.Init.AttackDistance.qtyTryChangeCannon) == 2 || sti(rCharacter.Ship.SeaAI.Init.AttackDistance.qtyTryChangeCannon) == 5)
					//{
					    //#20180126-01
					//	rCharacter.Ship.SeaAI.Init.AttackDistance = 80.0;
					//}
					//else
					//{
                        //#20180126-01
						//if(!CheckAttribute(rCharacter, "Ship.SeaAI.Init.AttackRangeSet")) {
                             ChangeAttackDistance(rCharacter, sti(rCharacter.Sea_AI.cannon.charge));
						//}
					//}
					Ship_SetTaskAttack(SECONDARY_TASK, iCharIdx, nTaskTarg);
				}
			break;
		}
	}
	//#20190113-06
    if(bFortSanctuary) {
        //#20190117-01
        if(!DeleteAttribute(rCharacter, "SeaAI.hitInSanctuary")) {
            //Not hit...continue to wait
            Ship_SetTaskDriftWatch(SECONDARY_TASK, iCharIdx);
            return;
        }
        //else {
        //    DeleteAttribute(rCharacter, "SeaAI.hitInSanctuary");
        //}
        //Else, continue and reconsider options
    }
    int iCharNation = sti(rCharacter.nation);
    //#20190716-02
    bool bUseAI = true;
    if (bIsCompanion) {
        bUseAI = false;
    }
    if (bUseAI && !CheckAttribute(rCharacter, "Surrendered") && !CheckAttribute(rCharacter, "speak.tax")  && !CheckAttribute(rCharacter, "speak.ransom.taked"))
	{
	    bool chkAmmo = false;
	    if(aiTask == AITASK_ATTACK || aiTask == AITASK_DEFEND)
            chkAmmo = true;
		if (chkAmmo && CheckAttribute(rCharacter, "SeaAI.Task.Target"))
		{
		    //trace("rCharacter.SeaAI.Task.Target:" + rCharacter.SeaAI.Task.Target);
		    //#20190612-02
            float fCannonRat = 0.0;
            float fPowderRat = 1.0;
            float fAmmoRat = 1.0;
            float fIShipCn = makefloat(iShipCannonsNum);
            if(sti(rShip.CannonsQuantity) > 0) {
                fCannonRat = fIShipCn / stf(rShip.CannonsQuantity);
            }
            if(iShipCannonsNum > 0) {
                fPowderRat = makefloat(GetCargoGoods(rCharacter, GOOD_POWDER)) / fIShipCn;
                if(iNewBallType >= 0)
                    fAmmoRat = makefloat(GetCargoGoods(rCharacter, iNewBallType)) / fIShipCn;
            }
            if(bStartRunaway || iNewBallType < 0 || fAmmoRat < RA_AMMO_DET || fCannonRat < RA_CANNON_RATIO || fPowderRat < RA_POWDER_DET) {
                //if (iNewBallType < 0 || iShipCannonsNum < (sti(rShip.CannonsQuantity) / 10))
                //{
                if(nTaskTarg == 0) {
                    if(nLastBallChar >= 0)
                        nTaskTarg = nLastBallChar;
                    else
                        nTaskTarg = nMinEnemyIdx;
                    if(nTaskTarg < 0)
                        nTaskTarg = 0;
                }
                Ship_SetTaskRunaway(SECONDARY_TASK,iCharIdx, nTaskTarg);
                //#20191012-03
                if(!CheckAttribute(rCharacter, "SinkTenPercent"))
                    return;
            }
			if (nTaskTarg > 0 && CheckForSurrender(rCharacter, GetCharacter(nTaskTarg),1))
			{
				Ship_SetTaskRunaway(SECONDARY_TASK,iCharIdx, nTaskTarg);
				//#20191012-03
                if(!CheckAttribute(rCharacter, "SinkTenPercent"))
                    return;
			}
		}
		if (GetNationRelation2MainCharacter(iCharNation) == RELATION_ENEMY)
		{
		    if (CheckAttribute(rCharacter, "AlwaysFriend"))
			{
			    //trace("Char always friend? " + rCharacter.name);
				SetCharacterRelationBoth(iCharIdx, GetMainCharacterIndex(), RELATION_FRIEND);
			}
			else
			{
				if (CheckAttribute(rCharacter, "WatchFort") && bIsFortAtIsland && GetRelation(iCharIdx, iFortCommander) == RELATION_ENEMY && !CheckAttribute(rCharacter, "ShipTaskLock"))
				{
					fortDecision(rSituation, rCharacter, iCharIdx, iFortCommander);
				}
				if (!CheckAttribute(rCharacter, "SinkTenPercent"))
				{
					int	SailsPercent	= sti(rCharacter.Ship.SP);
					float HPPercent		= GetHullPercent(rCharacter);
					int	CrewQuantity	= sti(rCharacter.Ship.Crew.Quantity);
					int	MinCrewQuantity = GetMinCrewQuantity(rCharacter);
					int	iCharactersNum1, iCharactersNum2;
                    //#20181031-02
					if (GetRelation(iCharIdx, GetMainCharacterIndex()) != RELATION_ENEMY)
					{
					    SetCharacterRelationBoth(iCharIdx, GetMainCharacterIndex(), RELATION_ENEMY);
						Group_SetTaskAttack(sGroupID, PLAYER_GROUP);
						NationUpdate();
						DoQuestCheckDelay("NationUpdate", 0.9);
					}
					//#20191022-02
					float fSurrTest = 0.0;
					if (nLastBallChar != -1 &&
                        GetRelation(iCharIdx, nLastBallChar) == RELATION_ENEMY &&
                        !CheckAttribute(rCharacter, "Ship_SetTaskAbordage") &&
                        !CheckAttribute(rCharacter, "ShipTaskLock"))
					{
						if (CheckAttribute(rCharacter,"seatime.tempmorale") && CheckAttribute(rCharacter,"seatime.surmorale"))
						{
						    //#20191022-02
						    if(CheckAttribute(rCharacter, "seatime.surmargin"))
                                fSurrTest = stf(rCharacter.seatime.surmorale) + stf(rCharacter.seatime.surmargin);
							if (stf(rCharacter.seatime.tempmorale) < fSurrTest)
							{
								Ship_SetTaskRunaway(SECONDARY_TASK, iCharIdx, nLastBallChar);
							}
							else //#20181101-02
                            {
                                if(aiTask != AITASK_RUNAWAY && aiTask != AITASK_BRANDER) {
                                    Ship_SetTaskAttack(SECONDARY_TASK, iCharIdx, nLastBallChar);
                                }
                            }
						}
						else
						{
							if (!CheckAttribute(rCharacter, "SeaAI.Task") || sti(rCharacter.SeaAI.Task) == AITASK_NONE)
							{
								if (CheckAttribute(rCharacter, "AnalizeShips"))
								{
									if (stf(rCharacter.ship.hp) < (stf(characters[nLastBallChar].ship.hp) / 2.0))
									{
										Ship_SetTaskRunaway(SECONDARY_TASK, iCharIdx, nLastBallChar);
									}
									else
									{
									    Ship_SetTaskAttack(SECONDARY_TASK, iCharIdx, nLastBallChar);
									}
								}
								else
								{
								    Ship_SetTaskAttack(SECONDARY_TASK, iCharIdx, nLastBallChar);
								}
							}
							else
							{
								if ((SailsPercent > 50.0) && (HPPercent > 30.0) && sti(rCharacter.SeaAI.Task) != AITASK_RUNAWAY)
								{
									if (CheckAttribute(rCharacter, "AnalizeShips"))
									{
										iCharactersNum1 =  Group_GetLiveCharactersNum(rCharacter.SeaAI.Group.Name);
                                        //#20191208-01
										//if(!CheckAttribute(characters[nLastBallChar],"SeaAI.Group.Name")) {
										//	trace("ship: " + nLastBallChar + " has no SeaAI node");
										//}
                                        if(nLastBallChar == iFortCommander)
                                            iCharactersNum2 = 2;
										else
                                            iCharactersNum2 =  Group_GetLiveCharactersNum(characters[nLastBallChar].SeaAI.Group.Name);
										if (stf(rCharacter.ship.hp) < (stf(characters[nLastBallChar].ship.hp) / 2.0))
										{
										    //Boyer fix for divide by zero #20170318-41
										    if (iCharactersNum2 == 0)
                                                iCharactersNum2 = 1;
											if ((iCharactersNum1 / iCharactersNum2) >= 2.2)
											{
												Ship_SetTaskAttack(SECONDARY_TASK, iCharIdx, nLastBallChar);
											}
											else
											{
												Ship_SetTaskRunaway(SECONDARY_TASK, iCharIdx, nLastBallChar);
											}
										}
										else
										{
										    //Boyer fix for divide by zero #20170318-41
										    if (iCharactersNum1 == 0)
                                                iCharactersNum1 = 1;
											if ((makefloat(iCharactersNum2) / makefloat(iCharactersNum1)) >= 2.0 && sti(rShip.Class) > sti(RealShips[sti(characters[nLastBallChar].ship.type)].Class))
											{
												Ship_SetTaskRunaway(SECONDARY_TASK, iCharIdx, nLastBallChar);
											}
											else
											{
											    Ship_SetTaskAttack(SECONDARY_TASK, iCharIdx, nLastBallChar);
											}
										}
									}
									else
									{
									    Ship_SetTaskAttack(SECONDARY_TASK, iCharIdx, nLastBallChar);
									}
								}
								else
								{
									if (HPPercent < 15.0)
									{
									    //#20191208-01
                                        if (nLastBallChar != iFortCommander && rand(20) == 1)
                                        {
                                            Ship_SetTaskBrander(SECONDARY_TASK, iCharIdx, nLastBallChar);
                                        }
                                        else //#20181101-02
                                        {
                                            Ship_SetTaskRunaway(SECONDARY_TASK, iCharIdx, nLastBallChar);
                                        }
									}
									else
									{
										if ((HPPercent < 30.0) || (CrewQuantity <= (2*MinCrewQuantity)))
										{
                                            Ship_SetTaskRunaway(SECONDARY_TASK, iCharIdx, nLastBallChar);
										}
										else
										{
											if (CheckAttribute(rCharacter, "AnalizeShips") && stf(rCharacter.ship.hp) > (stf(characters[nLastBallChar].ship.hp) / 2))
											{
												if (sti(rCharacter.Tmp.fWatchFort.Qty) == 200)
												{
													if (sti(rCharacter.Tmp.fWatchFort) >= 199)
													{
														if (nLastBallChar == iFortCommander)
														{
															Ship_SetTaskAttack(SECONDARY_TASK, iCharIdx, GetMainCharacterIndex());
														}
														else
														{
															Ship_SetTaskAttack(SECONDARY_TASK, iCharIdx, nLastBallChar);
														}
													}
													else //#20181101-02
                                                    {
                                                        Ship_SetTaskRunaway(SECONDARY_TASK, iCharIdx, iFortCommander);
                                                    }
												}
												else
												{
													if (nLastBallChar == iFortCommander)
													{
														Ship_SetTaskAttack(SECONDARY_TASK, iCharIdx, GetMainCharacterIndex());
													}
													else
													{
														iCharactersNum1 =  Group_GetLiveCharactersNum(rCharacter.SeaAI.Group.Name);
														//#20191208-01
														if(nLastBallChar == iFortCommander)
                                                            iCharactersNum2 = 2;
                                                        else
                                                            iCharactersNum2 =  Group_GetLiveCharactersNum(characters[nLastBallChar].SeaAI.Group.Name);
														//Boyer fix for divide by zero #20170318-41
                                                        if (iCharactersNum1 == 0)
                                                            iCharactersNum1 = 1;
                                                        if ((makefloat(iCharactersNum2) / makefloat(iCharactersNum1)) >= 2.0 && sti(rShip.Class) > sti(RealShips[sti(characters[nLastBallChar].ship.type)].Class))
														{
															Ship_SetTaskRunaway(SECONDARY_TASK, iCharIdx, nLastBallChar);
														}
														else
														{
															Ship_SetTaskAttack(SECONDARY_TASK, iCharIdx, nLastBallChar);
														}
													}
												}
											}
											else //#20181101-02
                                            {
                                                Ship_SetTaskRunaway(SECONDARY_TASK, iCharIdx, nLastBallChar);
                                            }
										}
									}
								}
							}
						}
					}
					else
					{
					    if (!CheckAttribute(rCharacter, "ShipTaskLock"))
						{
							if (CheckAttribute(rCharacter,"seatime.tempmorale") && CheckAttribute(rCharacter,"seatime.surmorale"))
							{
								//#20191022-02
								if(CheckAttribute(rCharacter, "seatime.surmargin"))
                                    fSurrTest = stf(rCharacter.seatime.surmorale) + stf(rCharacter.seatime.surmargin);
                                if (stf(rCharacter.seatime.tempmorale) < fSurrTest)
								{
									Ship_SetTaskRunaway(SECONDARY_TASK, iCharIdx, GetMainCharacterIndex());
								}
								else //#20181101-02
                                {
                                    if(aiTask != AITASK_RUNAWAY && aiTask != AITASK_BRANDER) {
                                        Ship_SetTaskAttack(SECONDARY_TASK, iCharIdx, GetMainCharacterIndex());
                                    }
                                }
							}
							else
							{
								if (!CheckAttribute(rCharacter, "SeaAI.Task.Target") || nTaskTarg < 1)
								{
									if (CheckAttribute(rCharacter, "SeaAI.Task") && sti(rCharacter.SeaAI.Task) != AITASK_RUNAWAY)
									{
										iCharactersNum1 =  Group_GetLiveCharactersNum(rCharacter.SeaAI.Group.Name);
										iCharactersNum2 =  Group_GetLiveCharactersNum(characters[GetMainCharacterIndex()].SeaAI.Group.Name);
										if (CheckAttribute(rCharacter, "AnalizeShips") && stf(rCharacter.ship.hp) < (stf(pchar.ship.hp) / 2))
										{
										    //Boyer fix for divide by zero #20170318-41
										    if (iCharactersNum2 == 0)
                                                iCharactersNum2 = 1;
											if ((makefloat(iCharactersNum1) / makefloat(iCharactersNum2)) >= 2.2)
											{
											    Ship_SetTaskAttack(SECONDARY_TASK, iCharIdx, GetMainCharacterIndex()));
											}
											else
											{
											    Ship_SetTaskRunaway(SECONDARY_TASK, iCharIdx, GetMainCharacterIndex());
											}
										}
										else
										{
											//Boyer fix for divide by zero #20170318-41
											if(iCharactersNum1 == 0)
                                                iCharactersNum1 = 1;
											if ((makefloat(iCharactersNum2) / makefloat(iCharactersNum1)) >= 2.0 && sti(rShip.Class) > sti(RealShips[sti(pchar.ship.type)].Class))
											{
												Ship_SetTaskRunaway(SECONDARY_TASK, iCharIdx, GetMainCharacterIndex());
											}
											else
											{
												Ship_SetTaskAttack(SECONDARY_TASK, iCharIdx, GetMainCharacterIndex());
											}
										}
									}
									else
									{
										if (CheckAttribute(rCharacter, "AnalizeShips") && (HPPercent > 60.0) && (SailsPercent > 70.0) && stf(rCharacter.ship.hp) > (stf(pchar.ship.hp) / 2.0))
										{
										    Ship_SetTaskAttack(SECONDARY_TASK, iCharIdx, GetMainCharacterIndex());
										}
										else //#20181101-02
                                        {
                                            Ship_SetTaskRunaway(SECONDARY_TASK, iCharIdx, GetMainCharacterIndex());
                                        }
									}
								}
							}
						}
					}
				}
				else // Is SinkTenPercent att
				{
				    if (CheckAttribute(rCharacter, "PearlTartane") && sti(rCharacter.PearlTartane))
					{
					    //#20180524-01 Remove unneeded sqrt
						if (Ship_GetDistance2DRel(GetMainCharacter(), rCharacter) < 256.0) // 16^2 Rel, instead of 16)
						{
							PlaySound("interface\important_item.wav");
							int SmallPearlQty = rand(250) + 100;
							int BigPearlQty = rand(150) + 50;
							TakeNItems(pchar, "jewelry12", SmallPearlQty);
							TakeNItems(pchar, "jewelry11", BigPearlQty);
							Log_SetStringToLog(xiStr("MSG_AIShip_14") + rCharacter.ship.name + xiStr("MSG_AIShip_15") + SmallPearlQty + xiStr("MSG_AIShip_16") + BigPearlQty + xiStr("MSG_AIShip_17"));
							rCharacter.PearlTartane = false;
							pchar.questTemp.Sharp.PearlAreal.SmallPearlQty = sti(pchar.questTemp.Sharp.PearlAreal.SmallPearlQty) + SmallPearlQty;
							pchar.questTemp.Sharp.PearlAreal.BigPearlQty = sti(pchar.questTemp.Sharp.PearlAreal.BigPearlQty) + BigPearlQty;
							pchar.questTemp.Sharp = "SeaPearl_success";
						}
					}
					else
					{
                        if (oldRel != RELATION_ENEMY)
                        {
                            SetCharacterRelationBoth(iCharIdx, GetMainCharacterIndex(), RELATION_ENEMY);
                            NationUpdate();
                        }
					    //#20181030-01
                        if(CheckAttribute(rCharacter, "FishingShips")) {
                            if(fMinEnemyDistance < 1850.0 && aiTask != AITASK_RUNAWAY)
                                Ship_SetTaskRunaway(SECONDARY_TASK, iCharIdx, GetMainCharacterIndex());
                        }
                        else
                            Ship_SetTaskAttack(SECONDARY_TASK, iCharIdx, GetMainCharacterIndex());
					}
				}
			}
		}
		else // Not Main Char enemy yet
		{
		    //#20171114-02 Tortuga pirate friendly
            bool pirateFriend = false;
            ref isld = GetIslandByID(sIslandID);
            if(CheckAttribute(isld, "pirateFriendly") && CheckAttribute(pchar, "pirateFriend")) {
                if(GetBaseHeroNation() == PIRATE || pchar.nation == PIRATE);
                    pirateFriend = true;
            }
            //#20190612-04
            if(!pirateFriend) {
                if(CheckAttribute(isld, "pirateTolerant") &&  CheckAttribute(pchar, "pirateFriend") && GetBaseHeroNation() == PIRATE)
                    pirateFriend = true;
            }
            if (CheckAttribute(rCharacter, "AlwaysEnemy"))
			{
			    SetCharacterRelationBoth(iCharIdx, GetMainCharacterIndex(), RELATION_ENEMY);
                Ship_SetTaskAttack(SECONDARY_TASK, iCharIdx, GetMainCharacterIndex());
			}
			else
			{
			    if (CheckAttribute(rCharacter, "AlwaysFriend"))
				{
					SetCharacterRelationBoth(iCharIdx, GetMainCharacterIndex(), RELATION_FRIEND);
				}
				else
				{
				    bool natCheck = false;
				    if (iCharNation != PIRATE && iCharNation != NEUTRAL_NATION)
                        natCheck = true;
					if (natCheck || bBettaTestMode)
					{
					    //#20171114-02 Tortuga pirate friendly
                        //if(pchar.nation == PIRATE)
                        if(!pirateFriend)
                            SetCharacterRelationBoth(iCharIdx, GetMainCharacterIndex(), GetNationRelation2MainCharacter(iCharNation));
					    if (!CheckAttribute(rCharacter, "CheckFlagYet"))
						{
                            if (!CheckAttribute(rCharacter, "CheckFlagDate") || GetNpcQuestPastDayParam(rCharacter, "CheckFlagDate") > 1)
                            {
                                //#20180524-01 Remove unneeded sqrt
                                float minMapEnter = MIN_ENEMY_DISTANCE_TO_DISABLE_MAP_ENTER * MIN_ENEMY_DISTANCE_TO_DISABLE_MAP_ENTER;
								if (Ship_GetDistance2DRel(GetMainCharacter(), rCharacter) < minMapEnter)
								{
									rCharacter.CheckFlagYet = true;
									//#20181031-02
                                    //#20171114-02 Tortuga pirate friendly
									Ship_CheckFlagEnemy(rCharacter, pirateFriend);
									SaveCurrentNpcQuestDateParam(rCharacter, "CheckFlagDate");
								}
							}
						}
						if (rCharacter.id == "MQPirate")
						{
						    //#20180524-01 Remove unneeded sqrt, was 150, now 150^2
							if (Ship_GetDistance2DRel(GetMainCharacter(), rCharacter) < 22500.0 && !CheckAttribute(pchar, "GenQuest.DestroyPirate.WasFirstDeside"))
							{
								pchar.GenQuest.DestroyPirate.WasFirstDeside = true;
								bool fight, shipHealth, shipClass, shipCrew, bFort;
								fight = false;
								bFort = false;
								shipHealth = false;
								shipClass = false;
								shipCrew = false;
								aref arReloadLoc, arFort;
								makearef(arReloadLoc, Islands[FindIsland(pchar.location)].reload);
								string  sLabel;
								int iQty = GetAttributesNum(arReloadLoc);
								//Log_TestInfo("Sea.MaxSeaHeight " + Sea.MaxSeaHeight);
								if (bIsFortAtIsland)
								{
								    //#20180524-01 Remove unneeded sqrt, was 1000, now 1000^2
									if (GetDistance2DRel(stf(rCharacter.Ship.Pos.x), stf(rCharacter.Ship.Pos.z), fFort_x, fFort_z) < 1000000.0)
									{
										bFort = true;
									}
								}
								if ((GetCurrentShipHP(pchar) / GetCharacterShipHP(pchar)) < 0.5 || (sti(pchar.ship.sp) / GetCharacterShipSP(pchar) < 0.4))
								{
									shipHealth = true;
								}
								if ((sti(rShip.Class) - sti(RealShips[sti(pchar.ship.type)].Class)) <= SHIP_CLASS_1)
								{
									shipClass = true;
								}
								int nPCharCrew = GetCrewQuantity(pchar);
								int nRCharCrew = GetCrewQuantity(rCharacter);
								if ((nPCharCrew / GetMaxCrewQuantity(pchar)) < 0.4 || GetCrewExp(pchar, "Soldiers") < 20.0 || GetCharacterCrewMorale(pchar) < 20)
								{
									if ((nPCharCrew / nRCharCrew) < 1.34) shipCrew = true;
								}
								iQty = GetCompanionQuantity(pchar);
								//Boyer fix for expanded companions
								switch(iQty)
								{
                                case 0: break;
                                case 1:
									if (shipHealth || shipClass || shipCrew) fight = true;
                                    break;
								case 2:
									if (shipClass && shipCrew || shipHealth) fight = true;
                                    break;
								//default  >= 3
									if (shipClass && shipCrew && shipHealth) fight = true;
									break;
								}
								if (fight && !bFort)
								{
                                    if (iCharNation != PIRATE)
									{
										rCharacter.nation = PIRATE;
										Ship_NationAgressivePatent(rCharacter);
										Ship_FlagRefresh(characterFromId("MQPirate"));
									}
									Ship_SetTaskAbordage(SECONDARY_TASK, iCharIdx, nMainCharacterIndex);
								}
								pchar.GenQuest.DestroyPirate.shipState = shipCrew + shipClass + shipHealth;
								pchar.GenQuest.DestroyPirate.fortPlace = bFort;
							}
							//#20180524-01 Remove unneeded sqrt, was 30, now 30^2
							if (Ship_GetDistance2DRel(GetMainCharacter(), rCharacter) < 900.0 && CheckAttribute(pchar, "GenQuest.DestroyPirate.WasFirstDeside") && !CheckAttribute(pchar, "GenQuest.DestroyPirate.FastAbordage"))
							{
								pchar.GenQuest.DestroyPirate.FastAbordage = true;
								if ((nPCharCrew / nRCharCrew) < 1.6)
								{
                                    if (iCharNation != PIRATE)
									{
										rCharacter.nation = PIRATE;
										Ship_NationAgressivePatent(rCharacter);
										Ship_FlagRefresh(characterFromId("MQPirate"));
									}
									Ship_SetTaskAbordage(SECONDARY_TASK, iCharIdx, nMainCharacterIndex);
								}
								else
								{
									Ship_SetTaskRunaway(SECONDARY_TASK, iCharIdx, nMainCharacterIndex);
								}
								return;
							}
						}
					}
					else
					{
					    //#20171114-02 Tortuga pirate friendly
                        if(!pirateFriend && iCharNation != NEUTRAL_NATION))
                        SetCharacterRelationBoth(iCharIdx, GetMainCharacterIndex(), RELATION_ENEMY);
					}
				}
			}
			//#20181031-02
			//#20171114-02 Tortuga pirate friendly
			newRel = GetRelation(iCharIdx, GetMainCharacterIndex());
			if(oldRel != newRel) {
                if(newRel == RELATION_ENEMY)
                    Group_SetTaskAttack(sGroupID, PLAYER_GROUP);
                //#20181101-01
                NationUpdate();
			}
			//UpdateRelations();
			//#20181030-01  //#20190709-02
            if(CheckAttribute(rCharacter, "FishingShips")) {
                if(fMinEnemyDistance < 1850.0 && aiTask != AITASK_RUNAWAY)
                    Ship_SetTaskRunaway(SECONDARY_TASK, iCharIdx, rSituation.MinEnemyIndex);
            }
            else {
                if (CheckAttribute(rCharacter, "WatchFort") && bIsFortAtIsland && GetRelation(iCharIdx, iFortCommander) == RELATION_ENEMY && !CheckAttribute(rCharacter, "ShipTaskLock"))
                {
                    fortDecision(rSituation, rCharacter, iCharIdx, iFortCommander);
                }
            }
		}
	}
}

//#20171114-02 Tortuga pirate friendly
void Ship_CheckFlagEnemy(ref rCharacter, bool skipPirate)
{
    //Boyer check for Tortuga
	ref	mChar = GetMainCharacter();
	int	iCompan, iClass, nTest;
	float	fSneak;
	int	i, cn;
	ref	chref;
	bool	bViewFlag = false;
	int	iNationToChange = sti(mChar.nation);
    int nCharNation = sti(rCharacter.nation);
	if (nCharNation == PIRATE) return;
	if (nCharNation == NEUTRAL_NATION) return;
    if (skipPirate) return;

	iCompan = GetCompanionQuantity(mChar);
	iClass  = SHIP_CLASS_7;
	for (i=0; i<COMPANION_MAX; i++)
	{
		cn = GetCompanionIndex(GetMainCharacter(), i);
		if (cn>=0)
		{
			chref = GetCharacter(cn);
			nTest = GetCharacterShipClass(chref);
            if (nTest < iClass) iClass = nTest;
		}
	}
	if (GetNationRelation(GetBaseHeroNation(), nCharNation) == RELATION_ENEMY && GetDaysContinueNationLicence(nCharNation) == -1)
	{
		iNationToChange = GetBaseHeroNation();
		bViewFlag		= true;
	}
	if (!bViewFlag)
	{
		if (ChangeCharacterHunterScore(mChar, NationShortName(nCharNation) + "hunter", 0) > (8 + rand( (iClass-1)*(COMPANION_MAX-iCompan) ) ))
		{
            bViewFlag		= true;
            iNationToChange = PIRATE;
		}
	}
	if (bViewFlag)
	{
		fSneak  = stf(mChar.TmpSkill.Sneak); // 0.01..1.0
		int rep = sti(abs(REPUTATION_NEUTRAL - sti(mChar.reputation)) * 0.75);
		if ((rand(100) + rand(20) + rand(rep)) > (fSneak * 10 * iClass * (COMPANION_MAX-iCompan)))
		{
			/*mChar.nation = iNationToChange;
			Ship_FlagRefresh(PChar);
			SetNationToOfficers(iNationToChange);
			SetNationRelation2MainCharacter(sti(rCharacter.nation), RELATION_ENEMY);
			*/
			Log_Info(xiStr("MSG_AIShip_4")+ NationNamePeople(nCharNation) + xiStr("MSG_AIShip_5"));
			SetCharacterRelationBoth(sti(rCharacter.index), GetMainCharacterIndex(), RELATION_ENEMY);
			DoQuestCheckDelay(NationShortName(iNationToChange) + "_flag_rise", 0.1);
			AddCharacterExpToSkill(mChar, SKILL_SNEAK, 100);
		}
		else
		{
			AddCharacterExpToSkill(mChar, SKILL_SNEAK, (iCompan * 100 / iClass));
			ChangeCrewExp(mChar, "Sailors", 0.5);
		}
	}
}

void Ship_ActivateFirePlace()
{
	aref	arShipObject = GetEventData();
	aref	arCharacter = GetEventData();
	int		iFirePlaceIndex = GetEventData();
	string	sSoundName = GetEventData();
	float	fFireTime = GetEventData();
	int		iBallCharacterIndex = GetEventData();

	//aref	arPos; makearef(arPos, arCharacter.Ship.Pos);
	//Log_TestInfo("iFirePlaceIndex " + iFirePlaceIndex);
	if (!bAbordageStarted) // fix
	{
		SendMessage(arShipObject, "llsssfl", MSG_SHIP_ACTIVATE_FIRE_PLACE, iFirePlaceIndex, "ship_smoke", "ship_fire", sSoundName, fFireTime, iBallCharacterIndex);
	}
}

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

	ref		rBranderBaseShip = GetRealShip(sti(rCharacter.Ship.Type));
	float	fBranderDistance = 1.9 * (45.0 + (7.0 - stf(rBranderBaseShip.Class)) * 15.0); //fix
	fBranderDistance *= fBranderDistance;
	//float	fMaxDamage = 1000.0 + (7.0 - stf(rBranderBaseShip.Class)) * 700.0;  //fix
	float	fMaxDamage = 1000.0 + MakeFloat(GetCargoGoods(rCharacter, GOOD_POWDER)) * 4;

	aref	arShipObjects[64]; //MAX_SHIPS_IN_LOCATION*2
	int		iBranderNumShips = 1;
	if (!FindClass(&arShipObjects[0], "ship")) { return; }// can't be
	while (iBranderNumShips < 64 && FindClassNext(&arShipObjects[iBranderNumShips])) { iBranderNumShips++; }
	// enumerate ship and do damage
	iBranderNumShips = i_min(63, iBranderNumShips);
	for (int i=0; i<iBranderNumShips; i++)
	{
		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 = GetRealShip(sti(rOtherCharacter.Ship.Type));
		float fDistance = Ship_GetDistance2DRel(rCharacter, rOtherCharacter);
		if (fDistance > fBranderDistance) { continue; }
		float fDamageMultiply = Bring2Range(1.0, 0.0, 10.0, sqrt(fBranderDistance), sqrt(fDistance));
		Ship_ApplyHullHitpoints(rOtherCharacter, fMaxDamage * fDamageMultiply, KILL_BY_SPY, sti(rCharacter.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)
{
	if (CheckAttribute(rCharacter1, "Ship.Pos.x") && CheckAttribute(rCharacter2, "Ship.Pos.x")) // fix
	{
		return GetDistance2D(stf(rCharacter1.Ship.Pos.x), stf(rCharacter1.Ship.Pos.z), stf(rCharacter2.Ship.Pos.x), stf(rCharacter2.Ship.Pos.z));
	}
	else
	{
		return 1000.0;
	}
}

//#20180524-01 Remove unneeded sqrt
float Ship_GetDistance2DRel(ref rCharacter1, ref rCharacter2)
{
	if (CheckAttribute(rCharacter1, "Ship.Pos.x") && CheckAttribute(rCharacter2, "Ship.Pos.x")) // fix
	{
		return GetDistance2DRel(stf(rCharacter1.Ship.Pos.x), stf(rCharacter1.Ship.Pos.z), stf(rCharacter2.Ship.Pos.x), stf(rCharacter2.Ship.Pos.z));
	}
	else
	{
		return 1000000.0;
	}
}

//#20180126-01
void ChangeAttackDistance(ref rCharacter, int ballType)
{
    ref	rCannon = GetCannonByType(sti(rCharacter.Ship.Cannons.Type));
    float range = stf(rCannon.FireRange);
    if(CheckAttribute(rCharacter, "Ship.Cannons.borts.cannonr.MaxFireDistance")) {
        range = retMin(range, stf(rCharacter.Ship.Cannons.borts.cannonr.MaxFireDistance));
    }
    if(CheckAttribute(rCharacter, "Ship.Cannons.borts.cannonl.MaxFireDistance")) {
        range = retMin(range, stf(rCharacter.Ship.Cannons.borts.cannonl.MaxFireDistance));
    }
    float maxRange = stf(rCannon.FireRange) / 2.0;
	if (CheckAttribute(rCharacter, "SeaAI.Task.Target") && rCharacter.SeaAI.task.target != "")
	{
		DeleteAttribute(rCharacter, "Ship.SeaAI.Init.AttackRangeSet");
		//#20200220-01
		if(sti(rCharacter.SeaAI.Task) == AITASK_ATTACK) {
            int enemy = sti(rCharacter.SeaAI.Task.Target);
            float fRangeModifier = 0.5; //1.0;

            switch(ballType) {
                //case GOOD_KNIPPELS:
                //    fRangeModifier = 0.8;
                //break;
                //case GOOD_BOMBS:
                //    fRangeModifier = 0.6;
                //break;
                case GOOD_GRAPES:
                    fRangeModifier = 0.8;
                break;
            }
            float fDistance = Ship_GetDistance2D(rCharacter, &characters[enemy]);
            range = range * fRangeModifier / 1.1;
            rCharacter.Ship.SeaAI.Init.AttackRangeSet = true;
            if (range < fDistance)
                rCharacter.Ship.SeaAI.Init.AttackDistance = range;
            else {
                if(maxRange > fDistance) {
                    maxRange = (maxrange + range + fDistance) / 3.0;
                    rCharacter.Ship.SeaAI.Init.AttackDistance = maxRange;
                }
                else {
                    fDistance = (maxrange + range) / 2.0;
                    rCharacter.Ship.SeaAI.Init.AttackDistance = fDistance;
                }
            }
		}
	}
}

int Ship_FindOtherBallType(ref rCharacter, float fMinEnemyDistance, bool bBalls, bool bBombs, bool bGrapes, bool bKnippels)
{
	ref	rCannon = GetCannonByType(sti(rCharacter.Ship.Cannons.Type));
    float range = stf(rCannon.FireRange);
	if (checkAttribute(rCharacter, "SeaAI.task.target") && rCharacter.SeaAI.task.target != "") //fix
	{
		int enemy = sti(rCharacter.SeaAI.task.target);
		ref rBaseShip = GetRealShip(GetCharacterShipType(rCharacter));
		int iType = -1;

		/*if (sti(rCharacter.SeaAI.Task) == AITASK_RUNAWAY)
		{
			Ship_SetSailState(sti(rCharacter.index), 1);
			if (sti(characters[enemy].ship.sp) > 20 && ShipSailState(enemy) < 2)
			{
				if(fMinEnemyDistance <= (range * 0.9) && bKnippels)
				{
					iType = GOOD_KNIPPELS;
				}
				if(fMinEnemyDistance <= (range * 0.4) && bGrapes)
				{
					iType = GOOD_GRAPES;
				}
			}
		}*/
		if ( bBalls) //(fMinEnemyDistance <= range) &&
		{
			iType = GOOD_BALLS;
		}
		if ( (fMinEnemyDistance <= (range * 0.8)) && bKnippels)
		{
			if (sti(characters[enemy].ship.sp) > 30 && ShipSailState(enemy) >= 0.5)
			{
				iType = GOOD_KNIPPELS;
			}
		}
		if ( (fMinEnemyDistance <= (range * 0.5)) && bBombs)
		{
			iType = GOOD_BOMBS;
		}
		if ((fMinEnemyDistance <= (range * 0.2)) && bGrapes)
		{
			if (sti(characters[enemy].ship.crew.quantity) > sti(rBaseShip.MinCrew))
			{
				iType = GOOD_GRAPES;
			}
		}
		//#20180126-01
        ChangeAttackDistance(rCharacter, iType);
		return iType;
	}
	if (bBalls) return GOOD_BALLS;
	if (bBombs) return GOOD_BOMBS;
	if (bKnippels) return GOOD_KNIPPELS;
	if (bGrapes) return GOOD_GRAPES;

	return -1;
}

// event: indicate that ball is not enough for fire for main character
void Ship_NotEnoughBalls()
{
	bNotEnoughBalls = GetEventData();
	if (!bNotEnoughBalls)
	{
		if (GetCargoGoods(pchar, GOOD_POWDER) < 1)
		{
			bNotEnoughBalls = true;
		}
	}
}

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();

	string sSound = "ball2sail";
	int	iBallType = sti(AIBalls.CurrentBallType);
	switch (iBallType)
	{
		//case GOOD_BALLS:	sSound = "ball2sail";	break;
		case GOOD_GRAPES:	sSound = "grapes2sail";	break;
		case GOOD_KNIPPELS:	sSound = "knippel2sail";	break;
		//case GOOD_BOMBS:	sSound = "ball2sail"; break;
	}
	Play3DSound(sSound, x, y, z);
	if (rand(100) < 15) { Play3DSound("fly_ball_misc", x, y, z); }

	//JA: surrender related code copied from build mod
	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);
	if (!sti(rOurCharacter.TmpPerks.SandbankManeuver) && !CheckAttribute(rOurCharacter, "AlwaysSandbankManeuver"))
	{
		float fHP = (1.0 - fSlide) * fPower * 3.0;
		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 * 10.0;
	//Trace("Ship->Ship touch: idx = " + iOurCharacterIndex + ", fpower = " + fPower + ", fHP = " + fHP + ", fSlide = " + fSlide);
	Ship_ApplyHullHitpoints(rOurCharacter, fHP, KILL_BY_TOUCH, iEnemyCharacterIndex);
	if (iEnemyCharacterIndex == GetMainCharacterIndex() && GetHullPercent(rOurCharacter) <= 14  && !LAi_IsDead(rOurCharacter))
	{
		if (!IsCompanion(rOurCharacter) && GetRelation(sti(rOurCharacter.index), GetMainCharacterIndex()) != RELATION_ENEMY)
		{
			Ship_NationAgressive(rOurCharacter, rOurCharacter);
			NationUpdate();
			DoQuestCheckDelay("NationUpdate", 0.9);
		}
	}
	if (fPower>1.0) { Play3DSound("coll_ship2ship", x, y, z); }
}

//#20180223-03 Mitigate crew damage
void Ship_ApplyCrewHitpoints(ref rOurCharacter, float fCrewHP, bool mitigateDmg)
{
	if (LAi_IsImmortal(rOurCharacter))
	{
		return;
	}
	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

	if (MOD_SKILL_ENEMY_RATE == 1 && sti(rOurCharacter.index) == GetMainCharacterIndex())
	{
		fCrewHP = fCrewHP / MOD_Complexity_1_DMG;
	}
	ref rBaseShip = GetRealShip(GetCharacterShipType(rOurCharacter));
	float fMultiply = 1.0 - (0.75 * stf(rOurCharacter.TmpSkill.Defence));

	if(CheckOfficersPerk(rOurCharacter, "Doctor2"))
	{
		fMultiply = fMultiply * 0.8;
	}
	else
	{
		if(CheckOfficersPerk(rOurCharacter, "Doctor1"))
		{
			fMultiply = fMultiply * 0.9;
		}
	}
	float fDamage = fCrewHP * fMultiply;
	//#20180223-03 Mitigate crew damage
	float fNewCrewQuantity = stf(rOurCharacter.Ship.Crew.Quantity); // - fDamage;
	float fOptCrew = stf(rBaseShip.OptCrew);
    float fMinCrew = stf(rBaseShip.MinCrew);
    float fMinCrewMitigate = fOptCrew * CANNON_CREW_MITIGATE_PERCENT;

	if(mitigateDmg && fNewCrewQuantity < fMinCrewMitigate) {
        float fPerc = 0.1;
        if(fOptCrew > 0.0)
            fPerc = fNewCrewQuantity / fOptCrew;
        if(rand(CANNON_CREW_LOW_DIFFICULTY) < 1) {
            float fDmgK = frandSmall(CANNON_CREW_MITIGATE_FRAND) * fPerc;
            fDamage = fDamage * CANNON_CREW_MITIGATE_DAMAGE * fDmgK;
        }
        else {
            fDamage = 0.0;
        }
	}
	fNewCrewQuantity -= fDamage;
	//#20180223-03 End Mitigate crew damage
	float f5Percent = fMinCrew * 0.25; //  fix
	//  check skill -->
	if (fNewCrewQuantity >= f5Percent && IsCompanion(rOurCharacter))
	{
		AddCharacterExpToSkill(rOurCharacter, "Defence", makefloat(fCrewHP / 2 + 0.5));
		ChangeCrewExp(rOurCharacter, "Soldiers",  (fCrewHP / fOptCrew));
	}
	if (fNewCrewQuantity < f5Percent) { fNewCrewQuantity = f5Percent; }
	if (fNewCrewQuantity < 0.0) { fNewCrewQuantity = 0.0; }

    float fOurCrew = stf(rOurCharacter.Ship.Crew.Quantity);

	if (sti(rOurCharacter.index) == GetMainCharacterIndex() && fMinCrew > fNewCrewQuantity && fMinCrew <= fOurCrew)
	{
		PlaySound("Interface\_Damage1.wav");
	}
	//  fix
	if (fOurCrew > fNewCrewQuantity)
	{
		rOurCharacter.Ship.Crew.Quantity = fNewCrewQuantity;
	}
	if (fOurCrew < 0.0) { rOurCharacter.Ship.Crew.Quantity = 0.0; }
	//  fix
}

void Ship_ApplyHullHitpoints(ref rOurCharacter, float fHP, int iKillStatus, int iKillerCharacterIndex)
{
    if (LAi_IsImmortal(rOurCharacter)) { return; }

	float fCurHP;
	float fMinus = 0.0;
	float fPlus = 0.0;
	//float fAccuracy = 0.0;

	if (bSeaReloadStarted) { return; }
	if (MOD_SKILL_ENEMY_RATE == 1 && sti(rOurCharacter.index) == GetMainCharacterIndex())
	{
		fHP = fHP / MOD_Complexity_1_DMG;
	}
	if (fHP <= 0.0) { return; }
	if (LAi_IsDead(rOurCharacter) || !CheckAttribute(rOurCharacter, "Ship.HP")) { return; }	// fix

	rOurCharacter.seatime.lasthit = GetSeaTime(); // JA: added for surrender code
	if(!CheckAttribute(rOurCharacter,"seatime.lastHP")) rOurCharacter.seatime.lastHP = rOurCharacter.Ship.HP; // to set what HP was lost in last hit

	/*if (iKillerCharacterIndex >= 0)
	{
		if (!CheckAttribute(&Characters[iKillerCharacterIndex], "TmpPerks"))
		{
			Trace("Error iKillerCharacterIndex = " + iKillerCharacterIndex);
		}

		if(sti(characters[iKillerCharacterIndex].TmpPerks.Gunner) == 1) { fHP = fHP * 0.95; }


	} */
	if (iKillerCharacterIndex >= 0)
	{
		if (sti(Characters[iKillerCharacterIndex].TmpPerks.HullDamageUp)) fPlus = 0.15;
		if (sti(Characters[iKillerCharacterIndex].TmpPerks.CannonProfessional)) fPlus = 0.3;
		//fAccuracy = stf(characters[iKillerCharacterIndex].TmpSkill.Accuracy);
	}
	if (sti(rOurCharacter.TmpPerks.BasicBattleState))			fMinus = 0.15;
	if (sti(rOurCharacter.TmpPerks.AdvancedBattleState))		fMinus = 0.25;
	if (sti(rOurCharacter.TmpPerks.ShipDefenseProfessional))	fMinus = 0.35;

	/*if (bNoDamage2Us)
	{
		if (sti(rOurCharacter.index) == nMainCharacterIndex) { return; }
		if (FindFellowTravellers(pchar, rOurCharacter) == FELLOWTRAVEL_COMPANION) { return; }
	}*/

	//float fMultiply = (1.0 + (0.3 * fAccuracy));

	/*if (iSeaDamage == 1 && sti(rOurCharacter.index) == nMainCharacterIndex)
	{
		fMultiply = fMultiply * 0.5;
	} */
	//fCurHP = stf(rOurCharacter.Ship.HP) - fHP * fMultiply * (1.0 + fPlus - fMinus);
	fCurHP = stf(rOurCharacter.Ship.HP) - fHP * (1.0 + fPlus - fMinus);
	if (fCurHP <= 0.0)
	{
		fCurHP = 0.0;
		ShipDead(sti(rOurCharacter.index), iKillStatus, iKillerCharacterIndex);
	}
	if(sti(rOurCharacter.ship.type) != SHIP_NOTUSED && fCurHP > sti(RealShips[sti(rOurCharacter.ship.type)].HP))
	{
		fCurHP = RealShips[sti(rOurCharacter.ship.type)].HP;
	}
	rOurCharacter.Ship.HP = fCurHP;
}

void Ship_AddCharacterExp(ref rCharacter, int iExpQuantity)
{
	if(IsCompanion(rCharacter) == true || rCharacter.id == pchar.id)
	{
		AddPartyExp(pchar, iExpQuantity);
	}
	if (sti(rCharacter.index) == nMainCharacterIndex)
	{
		fSeaExp = fSeaExp + iExpQuantity;
	}
}

void ShipDead(int iDeadCharacterIndex, int iKillStatus, int iKillerCharacterIndex)
{
	ref rDead, rKillerCharacter, rBaseShip, rKillerBaseShip;

	rDead = GetCharacter(iDeadCharacterIndex);
	if (!CheckAttribute(rDead, "Ship.Type")) // fix
	{
		if (MOD_BETTATESTMODE == "On") Log_Info("Error: ShipDead iDeadCharacterIndex = "+iDeadCharacterIndex);
		return;
	}
	bool bDeadCompanion = IsCompanion(rDead);
    rBaseShip = GetRealShip(sti(rDead.Ship.Type));
    //#20180925-01
    float fBaseClass = stf(rBaseShip.Class);
	if (iKillerCharacterIndex != -1)
	{
		rKillerCharacter = GetCharacter(iKillerCharacterIndex);
		rKillerBaseShip = GetRealShip(sti(rKillerCharacter.Ship.Type));
		//  check skill -->
		if (!bDeadCompanion && iKillStatus != KILL_BY_SPY)
		{
			switch (iKillStatus)
			{
				case KILL_BY_TOUCH:
                    AddCharacterExpToSkill(rKillerCharacter, "Sailing", stf(rKillerBaseShip.Class) / fBaseClass * 90);
                    ChangeCrewExp(rKillerCharacter, "Sailors", 1);
                    // statistic info 17.12.2003 -->
                    Statistic_AddValue(rKillerCharacter, "KillShip_" + rBaseShip.Class, 1);
                    //Boyer add #20170318-42
                    rDead.KilledEnv = "sea";
                    // statistic info 17.12.2003 <--
				break;
				case KILL_BY_BALL:
					AddCharacterExpToSkill(rKillerCharacter, "Accuracy", stf(rKillerBaseShip.Class) / fBaseClass * 35);
					AddCharacterExpToSkill(rKillerCharacter, "Sailing", stf(rKillerBaseShip.Class) / fBaseClass * 65);
					AddCharacterExpToSkill(rKillerCharacter, "Cannons", stf(rKillerBaseShip.Class) / fBaseClass * 40);
					ChangeCrewExp(rKillerCharacter, "Sailors", 1);
					ChangeCrewExp(rKillerCharacter, "Cannoners", 2);
					// statistic info 17.12.2003 -->
                    Statistic_AddValue(rKillerCharacter, "KillShip_" + rBaseShip.Class, 1);
                    // statistic info 17.12.2003 <--
                    //Boyer add #20170318-42
                    rDead.KilledEnv = "sea";
					if (rand(4) == 2 && GetRelation(sti(rDead.Index), sti(Pchar.Index))== RELATION_ENEMY)
                    {
                        if(!CheckAttribute(rDead, "DontRansackCaptain") || rDead.DontRansackCaptain == false) AISeaGoods_AddGood(rDead, "enemy_boat", "lo_boat", LIFEBOAT_RESCUE_TIME, 1); //homo 25/06/07
                    }
                    //homo
				break;
				case KILL_BY_ABORDAGE:
					AddCharacterExpToSkill(rKillerCharacter, "Grappling", stf(rKillerBaseShip.Class) / fBaseClass * 110);
					AddCharacterExpToSkill(rKillerCharacter, "Sailing", stf(rKillerBaseShip.Class) / fBaseClass * 70);
					AddCharacterExpToSkill(rKillerCharacter, SKILL_FORTUNE, stf(rKillerBaseShip.Class) / fBaseClass * 10);
					AddCharacterExpToSkill(rKillerCharacter, "Defence", stf(rKillerBaseShip.Class) / fBaseClass * 60);
					ChangeCrewExp(rKillerCharacter, "Sailors", 0.5);
					// statistic info 17.12.2003 -->
                    Statistic_AddValue(rKillerCharacter, "KillAbordShip_" + rBaseShip.Class, 1);
                    // statistic info 17.12.2003 <--
				break;
			}
			if (iKillStatus != KILL_BY_SELF)
			{
				AddCharacterExpToSkill(rKillerCharacter, SKILL_FORTUNE, stf(rKillerBaseShip.Class) / fBaseClass * 20);
				AddCharacterExpToSkill(rKillerCharacter, "Leadership", stf(rKillerBaseShip.Class) / fBaseClass * 25);
				Statistic_AddValue(rKillerCharacter, NationShortName(sti(rDead.nation))+"_KillShip", 1);
				if (rand(8) < 3 && sti(rDead.nation) != PIRATE)
				{
					ChangeCharacterHunterScore(rKillerCharacter, NationShortName(sti(rDead.nation)) + "hunter", 1+rand(1));
				}
				//JA -> Add morale to killer and everyone else (original code by NK)
				AddKillMorale(rKillerCharacter,rDead);
			}
		}
	}
	//int iQuantity = GetCompanionQuantity(pchar);

	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);

	if (CheckAttribute(rDead, "curshipnum")) {
		Ships[sti(rDead.curshipnum)] = -1;
		DeleteAttribute(rDead, "curshipnum");
	}
	int iExp = 0;
	if (iKillerCharacterIndex != -1)
	{
		iExp = 2 * MakeInt(stf(rBaseShip.HP) * stf(rKillerBaseShip.Class) / fBaseClass);
	}
	bool bCompanion = false;
	if (iKillerCharacterIndex != -1)
	{
		bCompanion = IsCompanion(rKillerCharacter);
	}
	if (bCompanion && bDeadCompanion)
	{
		iExp = 0;
		if (iKillerCharacterIndex == GetMainCharacterIndex())
		{
			ChangeCharacterReputation(pchar, -15);
		}
	}
	bool bRealKill = 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 != nMainCharacterIndex)
	{
		string sSunkString;
		string sSunkShipType = XI_ConvertString(rBaseShip.BaseName);
		string sKillShipType = "";
		string sKillShipName = "";
		if (iKillerCharacterIndex != -1)
		{
			sKillShipType = XI_ConvertString(rKillerBaseShip.BaseName);
			sKillShipName = "" + rKillerCharacter.Ship.Name;
		}
		if (bCompanion && !bDeadCompanion && bRealKill)
		{
            sSunkString = sSunkShipType + " '" + rDead.Ship.Name + "' " + GetShipSexWord(rBaseShip.BaseName, xiStr("MSG_AIShip_6"), xiStr("MSG_AIShip_7")) + GetFullName(rKillerCharacter);
		}
		else
		{
            if (sKillShipName == "")
            {
                sSunkString = sSunkShipType + " '" + rDead.Ship.Name + "' " + GetShipSexWord(rBaseShip.BaseName, xiStr("MSG_AIShip_8"), xiStr("MSG_AIShip_9"));
            }
            else
            {
                sSunkString = sSunkShipType + " '" + rDead.Ship.Name + "' " + GetShipSexWord(rBaseShip.BaseName, xiStr("MSG_AIShip_6"), xiStr("MSG_AIShip_7")) + sKillShipType + " '" + sKillShipName + "'";
			}
		}
		Log_SetStringToLog(sSunkString);
	}
	if (rand(10) >= 6)
	{
		bool bDetonate = false;
		switch (iKillStatus)
		{
			case KILL_BY_BALL:
				bDetonate = true;
			break;
			/*case KILL_BY_SPY:
				bDetonate = true;
			break;  */
		}
		if (bDetonate)
		{
			Ship_Detonate(rDead, true, false);
		}
	}
	//Boyer add #20170318-43...remove sunk ship flags to reduce graphic anomalies...delete, not needed #20180712-03
    //SendMessage(rDead, "l", MSG_SHIP_FLAG_REMOVE);

	// set attributes for sinking effect Naurshima's Sinking Mod
	rDead.Ship.Sink = "";
	aref aSink; makearef(aSink, rDead.Ship.Sink);
	aSink.Speed.y = 0.15 + (frnd() * 0.05 ); // speed of sink y (0.15)
	aSink.Speed.x = 0.019 * (frnd() * 2.0 - 1.0); // speed sink angle rotate around x (0.019)
	aSink.Speed.z = 0.019 * (frnd() * 2.0 - 1.0); // speed sink angle rotate around z (0.019)

	if (iDeadCharacterIndex == nMainCharacterIndex)
	{
		SeaCameras_SetDieCamera();
		FadeOutMusic(2);
		//PlayStereoOGG("music_ship_dead");
		if (iKillerCharacterIndex != -1 && rKillerCharacter.id == "GhostCapt")
		{
			DoQuestCheckDelay("Survive_In_Sea", 9.0);
		}
		else {
            //Boyer add feature to wash ashore if ship sinks, rather than end game
            //See Treasure.c and quests_reaction.c for implementation
            //If in this section, main character/pchar is the sunk/killed
            //#20170318-32
            int nChance = 40; //Base chance for survival is 40%
            nChance += (GetSummonSkillFromName(pchar, SKILL_FORTUNE) / 20); //'Luck' improves chance

            if(CheckCharacterPerk(pchar, "ShipEscape")) //Lifeboat skill improves chance
                nChance += 20;
            if(CheckCharacterItem(pchar, "jewelry8_2")) //Caul locket
                nChance += 10;
            if(CheckCharacterItem(pchar, "jewelry7_3"))
                nChance = 100; //Always with Caul
            if(rand(99) < nChance){
                DoQuestCheckDelay("SurviveSinkingWashAshore", 9.0); //quests_reaction.c case label
            }
            //Original line after GhostShip/Dutchman Quest check...game ends
            else
            {
                InterfaceStates.Buttons.Save.enable = false;
                PostEvent(GAME_OVER_EVENT, 15000);
            }
        }
	}
	else
	{
	    bool isRescue = false;
		//if (bDeadCompanion && CheckOfficersPerk(rDead, "ShipEscape") && GetRemovable(rDead))
		//Boyer change for caul and caul locket
		if (bDeadCompanion && GetRemovable(rDead))
		{
		    if(CheckOfficersPerk(rDead, "ShipEscape") || CheckCharacterItem(rDead, "jewelry7_3")) //Caul
                isRescue = true;
            if(CheckCharacterItem(rDead, "jewelry8_2")) { //Caul locket 75%
               if(rand(99) < 75)
                    isRescue = true;
            }
		    if(isRescue) {
				//homo 22/06/07
				AISeaGoods_AddGood(rDead, "boat", "lo_boat", LIFEBOAT_RESCUE_TIME, 1);
				//#20180222-04 10 days adrift, then handle
				SetCompanionAdriftList(rDead);
		    }
		}
	}
	Play3DSound("ship_sink", fX, fY, fZ);
	// Message to AI
	SendMessage(&AISea, "la", AI_MESSAGE_CHARACTER_DEAD, rDead);
	// Event
	Event(SHIP_DEAD, "l", iDeadCharacterIndex);
}

void ShipTaken(int iDeadCharacterIndex, int iKillStatus, int iKillerCharacterIndex)
{
	ref rDead, rKillerCharacter, rMainCharacter, rBaseShip, rKillerBaseShip;

	rDead = GetCharacter(iDeadCharacterIndex);
	rDead.Killer.Index = iKillerCharacterIndex;
	rBaseShip = GetRealShip(sti(rDead.Ship.Type));
	rMainCharacter = GetMainCharacter();
	//#20180925-01
    float fBaseClass = stf(rBaseShip.Class);

	bool bCompanion = false;
	bool bDeadCompanion = IsCompanion(rDead);

	LAi_SetCurHP(rDead, 0.0);
	rDead.Killer.Status = iKillStatus;
	rDead.Killer.Index = iKillerCharacterIndex;
	if (iKillerCharacterIndex != -1 && !bDeadCompanion)
	{
		rKillerCharacter = GetCharacter(iKillerCharacterIndex);
		rKillerBaseShip = GetRealShip(sti(rKillerCharacter.Ship.Type));
		bCompanion = IsCompanion(rKillerCharacter);
		//  check skill -->
		AddCharacterExpToSkill(rKillerCharacter, SKILL_FORTUNE, stf(rKillerBaseShip.Class) / fBaseClass * 20);
		AddCharacterExpToSkill(rKillerCharacter, "Leadership", stf(rKillerBaseShip.Class) / fBaseClass * 25);
		AddCharacterExpToSkill(rKillerCharacter, "Grappling", stf(rKillerBaseShip.Class) / fBaseClass * 120);
		AddCharacterExpToSkill(rKillerCharacter, "Sailing", stf(rKillerBaseShip.Class) / fBaseClass * 70);
		AddCharacterExpToSkill(rKillerCharacter, "Commerce", stf(rKillerBaseShip.Class) / fBaseClass * 30);
		AddCharacterExpToSkill(rKillerCharacter, "Repair", stf(rKillerBaseShip.Class) / fBaseClass * 60);
		AddCharacterExpToSkill(rKillerCharacter, "Defence", stf(rKillerBaseShip.Class) / fBaseClass * 60);
		// statistic info 17.12.2003 -->
		Statistic_AddValue(rKillerCharacter, "AbordShip_" + rBaseShip.Class, 1);
		Statistic_AddValue(rKillerCharacter, NationShortName(sti(rDead.nation))+"_AbordShip", 1);
		// statistic info 17.12.2003 <--

		ChangeCrewExp(rKillerCharacter, "Sailors", 0.5);
	}
	if (iDeadCharacterIndex != GetMainCharacterIndex())
	{
		string sSunkShipType = XI_ConvertString(rBaseShip.BaseName);
		string sKillShipType = XI_ConvertString(rKillerBaseShip.BaseName);

		if(bCompanion && !bDeadCompanion)
		{
            Log_SetStringToLog(sSunkShipType + " '" + rDead.Ship.Name + "' " + xiStr("MSG_AIShip_10"));
		}
	}
	if (rand(8) < 3 && !bDeadCompanion && sti(rDead.nation) != PIRATE)
	{
		ChangeCharacterHunterScore(rKillerCharacter, NationShortName(sti(rDead.nation)) + "hunter", 1+rand(1));
	}
}

void ShipTakenFree(int iDeadCharacterIndex, int iKillStatus, int iKillerCharacterIndex)
{
	ref rDead, rKillerCharacter, rMainCharacter, rBaseShip, rKillerBaseShip;

	rDead = GetCharacter(iDeadCharacterIndex);
	rDead.Killer.Index = iKillerCharacterIndex;
	rBaseShip = GetRealShip(sti(rDead.Ship.Type));
	rMainCharacter = GetMainCharacter();
    //#20180925-01
    float fBaseClass = stf(rBaseShip.Class);
	LAi_SetCurHP(rDead, 0.0);
	rDead.Killer.Status = iKillStatus;
	rDead.Killer.Index = iKillerCharacterIndex;
	if (iKillerCharacterIndex != -1)
	{
		rKillerCharacter = GetCharacter(iKillerCharacterIndex);
		rKillerBaseShip = GetRealShip(sti(rKillerCharacter.Ship.Type));

		AddCharacterExpToSkill(rKillerCharacter, "Leadership", stf(rKillerBaseShip.Class) / fBaseClass * 50);
		AddCharacterExpToSkill(rKillerCharacter, "Grappling", stf(rKillerBaseShip.Class) / fBaseClass * 120);
		AddCharacterExpToSkill(rKillerCharacter, "Sailing", stf(rKillerBaseShip.Class) / fBaseClass * 80);
		AddCharacterExpToSkill(rKillerCharacter, SKILL_FORTUNE, stf(rKillerBaseShip.Class) / fBaseClass * 70);
		AddCharacterExpToSkill(rKillerCharacter, "Repair", stf(rKillerBaseShip.Class) / fBaseClass * 10);
		AddCharacterExpToSkill(rKillerCharacter, "Defence", stf(rKillerBaseShip.Class) / fBaseClass * 10);

		Statistic_AddValue(rKillerCharacter, "AbordShipFree_" + rBaseShip.Class, 1);
		Statistic_AddValue(rKillerCharacter, NationShortName(sti(rDead.nation))+"_AbordShipFree", 1);
		Statistic_AddValue(rKillerCharacter, "AbordShipFree", 1);

		ChangeCrewExp(rKillerCharacter, "Sailors", 0.5);
		//#20220908-02 Now handled in transfer_main, depends on LoM and other variables
		//ChangeCharacterReputation(rKillerCharacter, 2);
		//OfficersReaction("good");
	}
	if (iDeadCharacterIndex != GetMainCharacterIndex())
	{
		string sSunkShipType = XI_ConvertString(rBaseShip.BaseName);
		string sKillShipType = XI_ConvertString(rKillerBaseShip.BaseName);

		Log_SetStringToLog(sSunkShipType + " '" + rDead.Ship.Name + "' " + xiStr("MSG_AIShip_11"));
	}
	if (rand(20) < 3 && sti(rDead.nation) != PIRATE)
	{
		ChangeCharacterHunterScore(rKillerCharacter, NationShortName(sti(rDead.nation)) + "hunter", 1+rand(1));
	}
}

void Ship_PlayVictory(string sSound, int iSoundTime)
{
    //#20180920-05
    if(CheckAttribute(pchar, "vicID")) {
        iVicSoundTime += iSoundTime;
        return;
    }
	FadeOutMusic(3); // 3000
	pchar.vicID = PlayStereoOgg(sSound);

	iVicSoundTime = iSoundTime;
}

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

	if (iVicSoundTime > 0)
	{
		iVicSoundTime = iVicSoundTime - iRealDeltaTime;
		if (iVicSoundTime <= 0)
		{
			bUpdatePosMode = true;
		}
	}
}

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; // JA from build mod

	float	x = GetEventData();
	float	y = GetEventData();
	float	z = GetEventData();
	int		iFirePlaceIndex = GetEventData();
	float	fFirePlaceDistance = GetEventData();
	//#20200616-01
	int rakeType = GetEventData(); // -1 = none, 0 = indirect, 1 = bow, 2 = stern
    float ballDir = GetEventData();
    float shipDir = GetEventData(); //This ship dir
    float ballDist = GetEventData(); //-1.0 for non-ship

	bool	bDead = LAi_IsDead(rOurCharacter) && CheckAttribute(rBallCharacter, "Ship.Cannons.Type");  // fix
	bool	bSeriousBoom = false;
	bool	bInflame = false;
	bool	isOurCompanion	= IsCompanion(rOurCharacter);
	float	fTmpCannonDamage = 0.0;

	if (bDead) return;
	// Cannon damage multiply
	ref rCannon = GetCannonByType(sti(rBallCharacter.Ship.Cannons.Type));
	float fCannonDamageMultiply = stf(rCannon.DamageMultiply);

	if(ballDist < 0.0) //If not available, use AIBalls last fire dist...slightly inaccurate
        ballDist = stf(AIBalls.CurrentBallDistance);
	float fDistanceDamageMultiply = Bring2Range(1.2, 0.25, 0.0, stf(AIBalls.CurrentMaxBallDistance), ballDist);

	int CritChance = 19; // +5%
	int CritChancePro = 9; // +10%
	switch(rakeType) {
        case 1: //Bow
            CritChance -= 3; //6%
            CritChancePro -= 1; //11%
        break;
        case 2: //Stern
            CritChance -= 8; //7.7%
            CritChancePro -= 3; //14.3%
        break;
	}
	if (sti(rBallCharacter.TmpPerks.CriticalShoot) && rand(CritChance)==10) { bSeriousBoom = true; }
	if (sti(rBallCharacter.TmpPerks.CannonProfessional) && rand(CritChancePro)==4) { bSeriousBoom = true; }

	ref rBall = GetGoodByType(iBallType);
	switch (iBallType)
	{
		case GOOD_BALLS:
			if (rand(85) == 17) { bSeriousBoom = true; }  // 35
			if (rand(30) == 15) { bInflame = true; }
			Play3DSound("ball2bort", x, y, z);
		break;
		case GOOD_GRAPES:
			bSeriousBoom = false;
			Play3DSound("grapes2bort", x, y, z);
            if (rand(100) < 30) {Play3DSound("episode_boom", x, y, z);}
		break;
		case GOOD_KNIPPELS:
			bSeriousBoom = false;
			Play3DSound("ball2bort", x, y, z);
		break;
		case GOOD_BOMBS:
			if (rand(50) == 10) { bSeriousBoom = true; }  // 20
			if (rand(5) == 1) { bInflame = true; }
			Play3DSound("bomb2bort", x, y, z);
		break;
	}
	float fBallDmgHull = stf(rBall.DamageHull);
	fTmpCannonDamage = fCannonDamageMultiply * fBallDmgHull;
	if (rand(2) == 1) CreateParticleSystemXPS("blast", x, y, z, 0.0, 0.0, 0.0, 0);// fix

	if (sti(rOurCharacter.TmpPerks.ShipDefenseProfessional) && rand(1000) < 700) { bSeriousBoom = false; }	// no serious boom

	float fCrewDamage = stf(rBall.DamageCrew) * fCannonDamageMultiply * AIShip_isPerksUse(rBallCharacter.TmpPerks.CrewDamageUp, 1.0, 1.15);

	if (bSeriousBoom)
	{
		fCrewDamage *= 7.0;
		fHP = fDistanceDamageMultiply * fCannonDamageMultiply * fBallDmgHull * (8.0 + frnd() * 4.0); // 4.0
		Ship_ApplyHullHitpoints(rOurCharacter, fHP, KILL_BY_BALL, iBallCharacterIndex);

		if (iBallCharacterIndex == nMainCharacterIndex)
		{
			Log_SetStringToLog(LanguageConvertString(iSeaSectionLang, "Ship_critical"));
		}
		/*int iFlow = rand(100);  // to_do
		if(iFlow < 10)
		{
			if(sti(rOurCharacter.index) == nMainCharacterIndex)
			{
				Log_SetStringToLog(LanguageConvertString(iSeaSectionLang, "Ship_flow"));
			}
			rOurCharacter.ship.flow = iFlow;
		}
		*/
		//  check skill -->
		if (!isOurCompanion && IsCompanion(rBallCharacter))
		{
            AddCharacterExpToSkill(rBallCharacter, "Accuracy", 15);
            AddCharacterExpToSkill(rBallCharacter, SKILL_FORTUNE, 15);
            AddCharacterExpToSkill(rBallCharacter, "Leadership", 2);
            AddCharacterExpToSkill(rBallCharacter, "Sailing", 15);
            AddCharacterExpToSkill(rBallCharacter, "Cannons", 15);
            ChangeCrewExp(rBallCharacter, "Sailors", 0.05);
			ChangeCrewExp(rBallCharacter, "Cannoners", 0.5);
		}
	}
	else
	{
	    if(rakeType > -1)
            fCrewDamage *= 2.0;
		fHP = fDistanceDamageMultiply * fCannonDamageMultiply * fBallDmgHull;
		//#20180223-03 Mitigate crew damage
		Ship_ApplyHullHitpoints(rOurCharacter, fHP, KILL_BY_BALL, iBallCharacterIndex);
		//  check skill -->
		if (!isOurCompanion && IsCompanion(rBallCharacter))
		{
            AddCharacterExpToSkill(rBallCharacter, "Accuracy", 2.5);
            AddCharacterExpToSkill(rBallCharacter, "Sailing", 1);
            AddCharacterExpToSkill(rBallCharacter, "Cannons", 1);
            ChangeCrewExp(rBallCharacter, "Cannoners", 0.01);
		}
	}
	Ship_ApplyCrewHitpoints(rOurCharacter, fCrewDamage, true);

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

		PostEvent(SHIP_ACTIVATE_FIRE_PLACE, iRandStartTime, "ialsfl", rShipObject, rOurCharacter, iFirePlaceIndex, "ship_onfire", fTotalFireTime, iBallCharacterIndex);
		PostEvent(SHIP_FIRE_DAMAGE, iRandStartTime, "lllf", iOurCharacterIndex, iBallCharacterIndex, iFirePlaceIndex, fTotalFireTime);
	}
	if (bSeriousBoom == true)
	{
		if (rOurCharacter.id == pchar.id || rand(2) == 1)
		{
			Ship_Serious_Boom(x, y, z);
		}
		fTmpCannonDamage = fTmpCannonDamage * 6.0;
		if ((GetCargoGoods(rOurCharacter, GOOD_POWDER) / 20.0) > (GetCargoMaxSpace(rOurCharacter) * 0.25) && rand(1) == 1)
		{
			Ship_SetExplosion(rOurCharacter, rShipObject);
			Log_Info(xiStr("MSG_AIShip_12") + rOurCharacter.Ship.Name + xiStr("MSG_AIShip_13"));
		}
	}
	SendMessage(&AISea, "laffff", AI_MESSAGE_CANNONS_BOOM_CHECK, rOurCharacter, fTmpCannonDamage, x, y, z);  // fix
	//Log_Info(rOurCharacter.id);
	//Log_Info(" LastBallCharacter = "+rOurCharacter.Ship.LastBallCharacter);
	if (iBallCharacterIndex == GetMainCharacterIndex() && GetNationRelation2MainCharacter(sti(rOurCharacter.nation)) != RELATION_ENEMY)
	{
		if (!isOurCompanion && !CheckAttribute(rOurCharacter, "Coastal_Captain"))
		{
            Ship_NationAgressive(rOurCharacter, rOurCharacter);
            NationUpdate();
            DoQuestCheckDelay("NationUpdate", 0.7);
		}
	}
	//#20190113-06
    if(GetNationRelation(sti(rBallCharacter.Nation), sti(rOurCharacter.nation)) != RELATION_FRIEND) {
        //#20190113-06
        if(DeleteAttribute(rOurCharacter, "SeaAI.fortSanctuary")) {
            //DeleteAttribute(rOurCharacter, "SeaAI.fortSanctuary");
            rOurCharacter.SeaAI.hitInSanctuary = true;
        }
        else {
            //#20190623-01
            if(CheckAttribute(rOurCharacter, "FishingShips")) {
                string sEGrp = Ship_GetGroupID(rBallCharacter);
                FishingGroupRunaway(rOurCharacter.FishingShips, sEGrp);
            }
        }
    }
}

float Ship_GetTotalFireTime(ref rCharacter)
{
	int iRepairSkill = MakeInt(stf(rCharacter.TmpSkill.Repair) * 10.0);
	return MakeFloat(5 + 3 * (10 - iRepairSkill));
}

void Ship_FireDamage()
{
	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 = (SHIP_CLASS_8 - iSClass) * (0.5 + frnd() * 0.5);
	int iTime = 1000 + rand(500);

	// if abordage or interface launched
	if (!bAbordageStarted && !sti(InterfaceStates.Launched))
	{
		Ship_ApplyHullHitpoints(rOurCharacter, fHP, KILL_BY_BALL, iBallCharacterIndex);
		fTotalFireTime = fTotalFireTime - MakeFloat(iTime) / 250.0;
	}
	if (fTotalFireTime > 0.0)
	{
		PostEvent(SHIP_FIRE_DAMAGE, iTime, "lllf", iOurCharacterIndex, iBallCharacterIndex, iFirePlaceIndex, fTotalFireTime);
	}
}

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

void Ship_SetFantomData(ref rFantom)
{
	//if(AISHIPDEBUG) trace("Ship_SetFantomData, id:" + rFantom.id);

	ref		rBaseShip;

	rBaseShip = GetRealShip(sti(rFantom.Ship.Type));

	int iOptCrew = sti(rBaseShip.OptCrew);
	int iBaseHP = sti(rBaseShip.HP);
	int iCapacity = sti(rBaseShip.Capacity);

	rFantom.ship.Crew.Quantity = makeint(iOptCrew * 1.2) - rand(makeint(iOptCrew / 3));
	rFantom.Ship.HP = iBaseHP;
	if (rand(3) == 2)
	{
		rFantom.Ship.HP = iBaseHP - rand(makeint(iBaseHP / 8));
	}
	if(!CheckAttribute(rFantom, "skill.Leadership")) rFantom.skill.Leadership = 1; //JA: Copied from build mod
	rFantom.Ship.Crew.morale = makeint(25 + rand(40) + (rand(sti(rFantom.skill.Leadership))+1)*10);
	if(CheckAttribute(rFantom,"EncType"))
	{
		switch(rFantom.EncType)
		{
			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 = iOptCrew - rand(makeint(iOptCrew / 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(iOptCrew * 1.2) - rand(makeint(iOptCrew / 3));
			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(iOptCrew * 1.2) - rand(makeint(iOptCrew / 4));
			break;
		}
	}
	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;

	DeleteAttribute(rFantom,"seatime");
	DeleteAttribute(rFantom,"speak");

	if (GetCrewQuantity(rFantom) > GetMaxCrewQuantity(rFantom))
	{
		SetCrewQuantity(rFantom, GetMaxCrewQuantity(rFantom));
	}
	//if(AISHIPDEBUG) trace("Ship_SetFantomData, rFantom.Ship.Crew.morale:" + rFantom.Ship.Crew.morale);
	//if(AISHIPDEBUG) trace("Ship_SetFantomData, rFantom.ship.Crew.Quantity:" + rFantom.ship.Crew.Quantity);

	//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");
}

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

	rCharacter = GetCharacter(iCharacterIndex);
	rBaseShip = GetRealShip(sti(rCharacter.Ship.Type));

	if (!CheckAttribute(rCharacter,"Ship.Crew.Quantity"))
	{
		rCharacter.ship.Crew.Quantity = rBaseShip.MaxCrew;
	}
	if (!CheckAttribute(rCharacter,"Ship.HP"))
	{
		rCharacter.Ship.HP = rBaseShip.HP;
	}
	if (!CheckAttribute(rCharacter,"Ship.SP"))
	{
		rCharacter.Ship.SP = rBaseShip.SP;
	}
	if (!CheckAttribute(rCharacter,"Ship.Cannons.Charge.Type"))
	{
		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"))
	{
		rCharacter.Ship.Cannons.Type = CANNON_TYPE_CULVERINE_LBS4;
	}
	rCharacter.Ship.Cannons.Borts = "";
}

float Ship_CalcGrapplingPerk(ref rCharacter)
{
	aref	arTmpPerks;
	makearef(arTmpPerks, rCharacter.TmpPerks);

	float fGrapplingPerk = AIShip_isPerksUse(rCharacter.TmpPerks.LongRangeGrappling, 1.0, 1.15);
	return AIShip_isPerksUse(rCharacter.TmpPerks.GrapplingProfessional, fGrapplingPerk, 1.25);

	return fGrapplingPerk;
}

float Ship_CalcMaxAbordageDistance(float fGrappling, float fGrapplingPerk)
{
	return fGrapplingPerk * Bring2Range(15.0, 40.0, 0.0, 1.0, fGrappling);
}

float Ship_CalcMaxAbordageAngle(float fGrappling)
{
	return Bring2Range(0.70, 0.45, 0.0, 1.0, fGrappling);
}

float Ship_CalcMaxAbordageSpeed(float fGrappling)
{
	return Bring2Range(3.0, 5.0, 0.0, 1.0, fGrappling);
}

void Ship_CheckEnemiesAroundMainCharacter()
{
}

void Ship_CheckMainCharacter()
{
    aref	arUpdate;
	ref		rIsland;
	int		i, iRelation;
	float	x, z, fBestDistance, fDistance;
	float	fMinEnemyDistance = 1000000.0;
	float	fMinFortAbordDistance = 1000000.0;
	float	fortDistance = 1000000.0;
	bool	bAbordageStartNow = false;
	int		iAbordageStartNowCharacter = -1;
	//#20171114-02 Tortuga pirate friendly
	bool pirateFriend = false;

	PostEvent(SHIP_CHECK_RELOAD_ENABLE, 1000);

	ref rCharacter = pchar;

	// 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; }
	//#20190311-02
    bool bWaitRepel = CheckAttribute(rCharacter, "waitRepel");
    if(bWaitRepel) {
        float fWaitRepel = stf(rCharacter.waitRepel);
        if(fWaitRepel > WAIT_REPEL_TIME) {
            DeleteAttribute(rCharacter, "waitRepel");
            bWaitRepel = false;
        }
        else {
            rCharacter.waitRepel = fWaitRepel + (fHighPrecisionDeltaTime * 10.0);
        }
    }
	iStormLockSeconds = iStormLockSeconds - 1;
	if (iStormLockSeconds < 0) { iStormLockSeconds = 0; }

	//increment time counter
	int temptime = 1;
	if(CheckAttribute(rCharacter,"seatime"))
	{
		temptime += sti(rCharacter.seatime);
	}
	rCharacter.seatime = temptime;

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

	makearef(arUpdate, rCharacter.SeaAI.Update);

	if (!CheckAttribute(arUpdate, "Ships")) { return; }
	if (!CheckAttribute(arUpdate, "Forts")) { return; }
	locTmpTime = locTmpTime + 0.5;
	if (locTmpTime > 9)
	{
		/*if (bDisableMapEnter)
		{
			if (locTmpTime > 29)
			{
				CalcLocalTime(6);
				BattleInterface.textinfo.Date.text = XI_convertString("Date:")+GetQuestBookData();
			}
		}
		else
		{ */
			CalcLocalTime(2);
            BattleInterface.textinfo.Date.text = XI_convertString("Date:")+GetQuestBookData();
		//}
	}
	x = stf(rCharacter.Ship.Pos.x);
	z = stf(rCharacter.Ship.Pos.z);

	float fOurHP = Ship_GetHP(rCharacter);
	float fOurFencing = stf(rCharacter.TmpSkill.Fencing);
	float fOurGrappling = stf(rCharacter.TmpSkill.Grappling);
	float fOurCrewFencing = (0.1 + fOurFencing * stf(rCharacter.Ship.Crew.Quantity));

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

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

	// max abordage parameters for our player
	float fOurGrapplingPerk = Ship_CalcGrapplingPerk(rCharacter);
	//#20190311-02
    bool bRepelBoard = sti(rCharacter.TmpPerks.RepelBoarding);

	float fOurMaxAbordageDistance = Ship_CalcMaxAbordageDistance(fOurGrappling, fOurGrapplingPerk);
	float fOurMaxAbordageAngle = Ship_CalcMaxAbordageAngle(fOurGrappling);
	float fOurMaxAbordageSpeed = Ship_CalcMaxAbordageSpeed(fOurGrappling);

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

	// find island
	int iIslandIndex = FindIsland(sPlayerLocation);
	if (iIslandIndex >= 0)
	{
		rIsland = GetIslandByIndex(iIslandIndex);
		//#20171114-02 Tortuga pirate friendly
		sIslandID = rIsland.id;
		if(CheckAttribute(rIsland, "pirateFriendly") && CheckAttribute(pchar, "pirateFriend")) {
            if(GetBaseHeroNation() == PIRATE || pchar.nation == PIRATE);
                pirateFriend = true;
        }
        //#20190612-04
        if(!pirateFriend) {
            if(CheckAttribute(rIsland, "pirateTolerant") && GetBaseHeroNation() == PIRATE)
                pirateFriend = true;
        }
	}
	else {
        sIslandID = "";
	}
	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);

		ref rFortCharacter = GetCharacter(sti(aFort.idx));
		if (LAi_IsDead(rFortCharacter)) { continue; }
		int iFortMode = sti(rFortCharacter.Fort.Mode);
		aref aRLoc = FindIslandReloadLocator(sPlayerLocation, rFortCharacter.location.locator);
		if (!CheckAttribute(aRLoc, "Radius")) { continue; }  // fix
		float fRadius = stf(aRLoc.Radius);
		fDistance = stf(aFort.Distance);

		switch (iFortMode)
		{
			case FORT_NORMAL:
			    if (iRelation == RELATION_ENEMY)
				{
					if (fMinEnemyDistance > fDistance) { fortDistance = fDistance; }
				}
				else
				{
					if (!CheckAttribute(rFortCharacter, "CheckFlagYet"))
					{
                        if (!CheckAttribute(rFortCharacter, "CheckFlagDate") || GetNpcQuestPastDayParam(rFortCharacter, "CheckFlagDate") > 1)
                        {
							if (fDistance < (MIN_ENEMY_DISTANCE_TO_DISABLE_MAP_ENTER_FORT * 1.5))
							{
								rFortCharacter.CheckFlagYet = true;
								//#20171114-02 Tortuga pirate friendly
								Ship_CheckFlagEnemy(rFortCharacter, pirateFriend);
								SaveCurrentNpcQuestDateParam(rFortCharacter, "CheckFlagDate");
							}
						}
					}
				}
			break;
			case FORT_DEAD:
                if (iRelation == RELATION_FRIEND) { continue; }
				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_FRIEND) { continue; }
				if (iRelation == RELATION_ENEMY)
				{
					//if (fMinEnemyDistance > fDistance) { fMinEnemyDistance = fDistance; }
					if (fMinFortAbordDistance > fDistance) { fMinFortAbordDistance = fDistance; } //fix
				}
				if (fDistance > fRadius) { continue; }
				if (!Character_IsAbordageEnable(rFortCharacter) || bDisableMapEnter)  { continue; } // bDisableMapEnter
				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;

	bool bABActive = CheckAttribute(rCharacter, "abordage_active");
	bool bABCount = CheckAttribute(rCharacter,"abordage_active_count");
	for (i=0; i<iShipsNum; i++)
	{
		aref aShip = GetAttributeN(aShips, i);
		ref rShipCharacter = GetCharacter(sti(aShip.idx));
		fDistance = stf(aShip.Distance);		// distance between player ship and this ship
		iRelation = sti(aShip.relation);

		if (LAi_IsDead(rShipCharacter))
		{
			continue;
		}
		if (iRelation == RELATION_FRIEND) continue;
		bool bSurrenderedEnemy = CheckAttribute(rShipCharacter, "surrendered") && CheckAttribute(rShipCharacter, "oldrelation") && (rShipCharacter.oldrelation.1 == RELATION_ENEMY);

		if (iRelation != RELATION_ENEMY && !bSurrenderedEnemy)
		{
			continue;
		}
		if (fMinEnemyDistance > fDistance)
		{
			fMinEnemyDistance = fDistance;
		}
		if (!Character_IsAbordageEnable(rShipCharacter))
		{
			continue;
		}
		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 surrendered ships from afar. NK 05-04-23 //KB - corrected

		bool bOurAngleTest = abs(fAng) > fOurMaxAbordageAngle && fRelativeSpeed < fOurMaxAbordageSpeed;
		float fAbordageDistance = fDistance - fEnemyBoxSizeX - fOurBoxSizeX;

		// test our ship with enemy
		if (bGrapplingProfessional || bOurAngleTest)
		{
			if (fAbordageDistance < fOurMaxAbordageDistance && fBestDistance > fDistance)
			{
			    //trace("Setting bAbordageShipCanBe true");
				// abordage enable
				fBestDistance = fDistance;
				bAbordageShipCanBe = true;
				iAbordageShipEnemyCharacter = sti(rShipCharacter.index);
			}
		}
		//#20190311-02
        if(bWaitRepel) continue;
		// test enemy ship with our
		float fEnemyGrappling = stf(rShipCharacter.TmpSkill.Grappling);

		// max abordage parameters for our player
		float fEnemyGrapplingPerk = Ship_CalcGrapplingPerk(rShipCharacter);

		float fEnemyMaxAbordageDistance = Ship_CalcMaxAbordageDistance(fEnemyGrappling, fEnemyGrapplingPerk);
		float fEnemyMaxAbordageAngle = Ship_CalcMaxAbordageAngle(fEnemyGrappling);
		float fEnemyMaxAbordageSpeed = Ship_CalcMaxAbordageSpeed(fEnemyGrappling);

		bool bEnemyAngleTest = abs(fAng) > fEnemyMaxAbordageAngle && fRelativeSpeed < fEnemyMaxAbordageSpeed;
		if (bEnemyAngleTest && !CheckAttribute(rShipCharacter, "surrendered"))
		{
			if (fAbordageDistance < fEnemyMaxAbordageDistance && !bABActive && !bABCount)
			{
				// maybe other character want abordage us?
				float fEnemyFencing = stf(rCharacter.TmpSkill.Fencing);
				float fEnemyCrewFencing = (0.1 + fEnemyFencing * stf(rShipCharacter.Ship.Crew.Quantity));
				float fRatio = fEnemyCrewFencing / fOurCrewFencing;
				if (sti(rShipCharacter.nation) == PIRATE)
				{
					fRatio = fRatio * 1.6;
				}
				if (fRatio > 1.2)
				{
				    //#20190311-02
                    if(bRepelBoard && ((stf(rCharacter.Ship.Crew.Quantity) * 3.0) >= stf(rShipCharacter.Ship.Crew.Quantity)))
                    {
                        float fMyLuck = FRAND(GetCharacterSkillToOld(rCharacter, SKILL_FORTUNE));
                        if(FRAND(100.0) < (PERK_BASE_REPEL_BRD + fMyLuck))
                        {
                            Log_SetStringToLog(xiStr("MSG_LAi_boarding_8") + rCharacter.Ship.Name);
                            rCharacter.waitRepel = 0.0;
                            break;
                        }
                    }
					iAbordageStartNowCharacter = sti(rShipCharacter.index);
					bAbordageStartNow = true;
					break;
				}
			}
		}
	}
	// check reload to locations possibility
	if (iIslandIndex >= 0 && fMinEnemyDistance > MIN_ENEMY_DISTANCE_TO_DISABLE_ENTER_2_LOCATION)
	{
		if (Island_isReloadEnableGlobal(sPlayerLocation))
		{
			//aref arReload;
			makearef(arIslandReload, rIsland.Reload);

			int iNumLocators = GetAttributesNum(arIslandReload);
			for (int j=0; j<iNumLocators; j++)
			{
				aref arLocator;
				arLocator = GetAttributeN(arIslandReload, j);
				string sLocatorName = GetAttributeName(arLocator);
				if (Island_isReloadFort(sPlayerLocation, arLocator.name)) { continue; }
				if (!Island_isReloadEnableLocal(sPlayerLocation, arLocator.name)) { continue; }
				if(!CheckAttribute(rIsland, "reload." + sLocatorName + ".x") || !CheckAttribute(rIsland, "reload." + sLocatorName + ".z"))
				{
					continue;
				}
				float x1 = stf(rIsland.reload.(sLocatorName).x);
				float z1 = stf(rIsland.reload.(sLocatorName).z);
				/*if (checkAttribute(rIsland, "reload."+sLocatorName+".wdmisland"))
				{
					wdmCurrentIsland = rIsland.reload.(sLocatorName).wdmisland;
				}*/
				float r = stf(rIsland.reload.(sLocatorName).radius);
				//#20180524-01 Remove unneeded sqrt
				r *= r;
				//if (sqrt(sqr(x1 - x) + sqr(z1 - z)) < r)
				if ((sqr(x1 - x) + sqr(z1 - z)) < r)
				{
					bCanEnterToLand = true;
					sIslandID = rIsland.id;
					//makearef(arIslandReload, rIsland.reload);
					sIslandLocator = rIsland.reload.(sLocatorName).name;
					break;
				}
			}
		}
	}
	// disable any abordage if storm
	if (Whr_IsStorm())
	{
		bAbordageStartNow = false;
		bAbordageFortCanBe = false;
		bAbordageShipCanBe = false;
	}
	// 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 (fortDistance < MIN_ENEMY_DISTANCE_TO_DISABLE_MAP_ENTER_FORT)
	{
		bCanEnterToLand = false;
		bDisableMapEnter = true;
		//#20190205-02
		rCharacter.Ship.POS.Mode = SHIP_WAR;
	}
	if (fMinFortAbordDistance < MIN_ENEMY_DISTANCE_TO_DISABLE_ENTER_2_LOCATION)
	{
		bCanEnterToLand = false;
	}
	if (iStormLockSeconds)
	{
		bDisableMapEnter = true;
		bCanEnterToLand = false;
		bDisableSailTo = true;

		rCharacter.Ship.POS.Mode = SHIP_STORM;
	}
	// new music
	bool bMakeCurrentUpdate = bUpdatePosMode;
	if( iVicSoundTime<=0 && sti(rCharacter.Ship.POS.Mode) != iPrevShipPOSMode ) {
		bMakeCurrentUpdate = true;
		//Boyer change #20170328-01
		iVicSoundTime = 0;
	}
	if( bMakeCurrentUpdate )
	{
	    //#20170530-01
        seaAlarmed = false;
		switch (sti(rCharacter.Ship.POS.Mode))
		{
			case SHIP_SAIL:
				if (!Whr_IsStorm())
				{
					seaAlarmed = false;
					if (iPrevShipPOSMode == SHIP_WAR)
					{
					    //#20180823-03
					    //SetRevolvingMusic(false);
                        Ship_PlayVictory("music_ship_victory", 10000);
					} // fix
					else {
                        //#20180920-05
                        if(CheckAttribute(pchar, "vicID")) {
                            if (sti(pchar.vicID) != SOUND_INVALID_ID) StopSound(sti(pchar.vicID), SOUNDS_FADE_TIME);
                            DeleteAttribute(pchar, "vicID");
                        }
                        //FadeOutMusic(3);
                        //#20170530-01 Music revolves, sea uses nation-specific is false/0
                        SetRevolvingMusic(false);
					}
				}
				else
				{
					FadeOutMusic(3); //fix
					PlayStereoOGG("music_End_storm");
				}
			break;
			case SHIP_WAR:
			    //#20180920-05
				FadeOutMusic(500); // fix
				seaAlarmed = true;
				SetMusic("music_sea_battle");
			break;
		}
		bUpdatePosMode = false;
	}
	// if quest disable enter 2 map
	if (bQuestDisableMapEnter) { bDisableMapEnter = true; }

	// Start abordage automatically
	if (bAbordageStartNow)
	{
		ref rTargetCharacter = GetCharacter(iAbordageStartNowCharacter);
		if(GetRelation(sti(rCharacter.index), iAbordageStartNowCharacter) == RELATION_ENEMY)
		{
			if(LAi_IsDead(rCharacter) == false && LAi_IsDead(rTargetCharacter) == false)
			{
				//JA: make sure other ships don't board, then surrender!
				if (!CheckForSurrender(rCharacter, rTargetCharacter, 1)) {
					iAbordageShipEnemyCharacter = iAbordageStartNowCharacter;
					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 != SOUND_INVALID_ID) { StopSound(sti(rCharacter.Ship.Strand.SndID), SOUNDS_FADE_TIME); }
		rCharacter.Ship.Strand.SndID = SOUND_INVALID_ID;
		return;
	}
	if (!sti(rCharacter.TmpPerks.SandbankManeuver) && !CheckAttribute(rCharacter, "AlwaysSandbankManeuver"))
	{
		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());
	aref	arCharShip; makearef(arCharShip, rCharacter.Ship);

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

	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));
	}
	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);
		CreateParticleSystemXPS("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;
	rCharacter.Tmp.SpeedRecall = 0;
}

// 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;
	}
	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));

	bool isBoomAlready = false;
	int iMax = iMinBoom + rand(2); //fix
	for (int i=0; i<iMax; 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;
		if(isBoomAlready == false)
		{
			Ship_Serious_Boom(x1 + x, y1, z1 + z);
		}
		isBoomAlready = true;
		CreateParticleSystem("blast_inv", 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		iCharacterIndex = GetEventData();
	ref		rCharacter = GetCharacter(iCharacterIndex);
	float	fSailState = GetEventData();

	bool	bDead = LAi_IsDead(rCharacter);
	//Log_Testinfo("Ship_UpdateParameters " + rCharacter.id);

	if(sti(rCharacter.ship.type) == SHIP_NOTUSED || bDead)	// super fix
	{
		return;
	}
	//bool	bStrand = sti(rCharacter.Ship.Strand);
	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 = GetRealShip(sti(arCharShip.Type));

	float fShipSpeed, fShipTurnRate;

	if (!CheckAttribute(rCharacter, "Tmp.SpeedRecall") || sti(rCharacter.Tmp.SpeedRecall) <= 0)
	{
		fShipSpeed	= FindShipSpeed(rCharacter);
		fShipTurnRate = FindShipTurnRate(rCharacter);

		rCharacter.Tmp.SpeedRecall	= 8 + rand(5);
		rCharacter.Tmp.fShipSpeed	= fShipSpeed;
		rCharacter.Tmp.fShipTurnRate = fShipTurnRate;
	}
	else
	{
		fShipSpeed	= stf(rCharacter.Tmp.fShipSpeed);
		fShipTurnRate = stf(rCharacter.Tmp.fShipTurnRate);

		rCharacter.Tmp.SpeedRecall	= sti(rCharacter.Tmp.SpeedRecall) - 1;
	}
	// wind power
	float	fWindPower = Whr_GetWindSpeed() / WIND_NORMAL_POWER;
	float	fWindDotShip = GetDotProduct(Whr_GetWindAngle(), stf(arCharShip.Ang.y)); // Wind.ay | Ship.ay
	fShipSpeed = (fShipSpeed * fWindPower);
	arCharShip.MaxSpeedZ = fShipSpeed;
	//Log_info("1 = " + arCharShip.MaxSpeedZ);
	arCharShip.MaxSpeedZ = Sea_ApplyMaxSpeedZ(arCharShip, fWindDotShip);
	//Log_info("2 = " + arCharShip.MaxSpeedZ);

	float fMaxSpeedY = stf(rShip.TurnRate) / Sea_TurnRateMagicNumber();

	float fTRFromSailState = 1.0;
	switch (MakeInt(fSailState * 2.0))
	{
		case 0: fTRFromSailState = 0.08; break;
		case 1: fTRFromSailState = 1.0; break;
		case 2: fTRFromSailState = 0.68; break;
	}
	float	fTRFromSpeed = 1.0;
	float fCurrentSpeedZ = stf(rCharacter.Ship.Speed.z);
	if (iCharacterIndex == GetMainCharacterIndex())
	{
		if (MOD_SKILL_ENEMY_RATE > 2)
		{
            if (iArcadeSails == 1)
			{
                fTRFromSpeed = 1.0 - 0.5 * (1.0 - Clampf(fCurrentSpeedZ / fShipSpeed));
			}
			else
			{
				fTRFromSpeed = 1.0 - 0.86 * (1.0 - Clampf(fCurrentSpeedZ / fShipSpeed));
			}
		}
	}
	else
	{
		if (iArcadeSails == 1)
		{
			fTRFromSpeed = 1.0 - (0.51 - MOD_SKILL_ENEMY_RATE*0.01) * (1.0 - Clampf(fCurrentSpeedZ / fShipSpeed));
		}
		else
		{
			fTRFromSpeed = 1.0 - (0.87 - MOD_SKILL_ENEMY_RATE*0.01) * (1.0 - Clampf(fCurrentSpeedZ / fShipSpeed));
		}
	}
	float fTRResult = fMaxSpeedY * fShipTurnRate * fTRFromSailState * fTRFromSpeed;
	// VANO :: update fTRResult using rotate with wind direction
	//float fRotateSide = stf(arCharShip.speed.y);
	/*float fPrevWindDotShip = stf(arCharShip.WindDotShip);
	bool bShipTurnToWind = fWindDotShip >= fPrevWindDotShip;
	float fWindTurnMultiply = 1.0;
	if (bShipTurnToWind)
		{ fWindTurnMultiply = Bring2Range(1.5, 1.0, 0.0, 1.0, abs(fWindDotShip)); }
	else
		{ fWindTurnMultiply = Bring2Range(0.5, 1.0, 0.0, 1.0, abs(fWindDotShip)); }

	arCharShip.WindDotShip = fWindDotShip;
	//Trace("fWindTurnMultiply = " + fWindTurnMultiply);

	fTRResult = fWindTurnMultiply * Bring2Range(0.01, 1.0, 0.00001, 1.0, fTRResult);
	*/
	fTRResult = Bring2Range(0.07, 0.95, 0.00001, 1.0, fTRResult);
	arCharShip.MaxSpeedY =	fTRResult;

	// Apply arcade mode
	if (iArcadeSails == 1)
	{
		//arCharShip.MaxSpeedZ = 6.0 * stf(arCharShip.MaxSpeedZ);
		arCharShip.MaxSpeedZ = (2.5 * stf(arCharShip.MaxSpeedZ));
		arCharShip.MaxSpeedY = (2.0 * stf(arCharShip.MaxSpeedY));
	}
	else
	{
		//arCharShip.MaxSpeedY = 0.75 * stf(arCharShip.MaxSpeedY);
		arCharShip.MaxSpeedZ = (1.0 * stf(arCharShip.MaxSpeedZ));
	}
	//Log_Info("MaxSpeedY = "  + arCharShip.MaxSpeedY);
	// calculate immersion
	float	fLoad = Clampf(GetCargoLoad(rCharacter) / stf(rShip.Capacity));
	arCharShip.Immersion = (stf(rShip.SubSeaDependWeight) * fLoad);

	// do damage if ship hull < 10%, sinking
	float fBaseSailHP = stf(rShip.SP);
	float fBaseShipHP = stf(rShip.HP);
	float fCurHP = stf(arCharShip.HP);
	if(fBaseShipHP < fCurHP)
	{
		fBaseShipHP = fCurHP;
	}
	if (bSeaActive && (fCurHP / fBaseShipHP < 0.09999))
	{
		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(!CheckAttribute(rCharacter, "ship.flow")) // to_do
		{
			rCharacter.ship.flow = 0;
		}
		rCharacter.ship.flow = sti(rCharacter.ship.flow) + 1;
		*/
		if (iCharacterIndex == nMainCharacterIndex)
		{
			if(!IsPerkIntoList("sink"))
       	    {
				if (rand(1) == 0)
				{
					PlaySound("Interface\_Abandon1.wav");
				}
				else
				{
					PlaySound("Interface\_Abandon2.wav");
				}
			}
			AddPerkToActiveList("sink");
		}
	}
	else
	{
		if (iCharacterIndex == nMainCharacterIndex) { DelPerkFromActiveList("sink"); }
	}
    if(!CheckAttribute(arTmpPerks, "StormProfessional"))
        arTmpPerks.StormProfessional = 0;
	float fStormProfessional = AIShip_isPerksUse(arTmpPerks.StormProfessional, 1.0, 0.7);

	// fix defence ship in storm 11.05.05 -->
	if (CheckAttribute(rCharacter, "DontHitInStorm"))
	{
		fStormProfessional = 0;
	}
	// fix defence ship in storm 11.05.05 <--
	// do damage if storm or tornado
	if (bStorm && bSeaActive)
	{
		float fWindAttack = 1.0 - abs(fWindDotShip);

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

		// hull damage
		float fDamageHP = (fBaseShipHP / 100.0) * 1.1;
		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
		float fExp = (1.0 - fWindAttack) * fDamageHP / (8 - GetCharacterShipClass(rCharacter));
		fExp = fExp / 10;
		if (!bDead)
		{
            Ship_AddCharacterExp(rCharacter, MakeInt(fExp));
            if (IsCompanion(rCharacter))
            {
                AddCharacterExpToSkill(rCharacter, "Sailing", fExp);
                ChangeCrewExp(rCharacter, "Sailors", 0.5);
            }
		}
		// rotate impulse
		float fRotate = stf(rCharacter.Ship.Tornado.Rotate.y) + (frnd() * 0.08 - 0.04);  //fix
		if (fRotate > 0.01)
		{
			fRotate = 0.01;
		}
		if (fRotate < -0.01)
		{
			fRotate = -0.01;
		}
		rCharacter.Ship.Tornado.Rotate.y = fRotate;  //fix
		arCharShip.Impulse.Rotate.y = fRotate;
	}
	if (bTornado && bSeaActive)
	{
		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;
		}
	}
	// Experience log section
	//if (iCharacterIndex == nMainCharacterIndex && bSeaActive)
	//{
	//	fSeaExpTimer = fSeaExpTimer + 1.0;
	//	if (fSeaExpTimer >= 15.0 && bStorm)
	//	{
	//		if (!bDead)
	//		{
				//Ship_PrintExp(MakeInt(fSeaExp));
	//		}

	//		fSeaExpTimer = 0.0;
	//		fSeaExp = 0.0;
	//	}
	//}
	// Recalculate Cargo Load if need
	if (CheckAttribute(rCharacter, "Ship.Cargo.RecalculateCargoLoad") && sti(rCharacter.Ship.Cargo.RecalculateCargoLoad))
	{
		if (rand(5) == 1)
		{
			RecalculateCargoLoad(rCharacter);
			rCharacter.Ship.Cargo.RecalculateCargoLoad = 0;
		}
	}
	if (bSeaLoaded && CheckAttribute(rCharacter, "SeaAI.Task.Target"))
	{
		if(sti(rCharacter.SeaAI.Task.Target) > 0)
		{
			ref rTargetCharacter = GetCharacter(sti(rCharacter.SeaAI.Task.Target));
			bool bSkipTarget = false;
			if(!CheckAttribute(rTargetCharacter, "location"))
			{
				bSkipTarget = true;
			}
			if(rTargetCharacter.location == "none")
			{
				bSkipTarget = true;
			}
			if(rTargetCharacter.location != rCharacter.location)
			{
				bSkipTarget = true;
			}
			if (!bSkipTarget)
			{
				if (!CheckAttribute(rTargetCharacter,"TmpSkill.Leadership")) Ship_UpdateTmpSkills(rTargetCharacter);
                //#20190720-01
                bool bDecideBoard = true;
                if(CheckAttribute(rCharacter, "speak.ransom.taked") || CheckAttribute(rCharacter, "surrendered")) {
                    bDecideBoard = false;
                }
                if(bDecideBoard) {
                    float fDistance = Ship_GetDistance2D(rCharacter, rTargetCharacter);
                    float fEnemyLeadership = stf(rTargetCharacter.TmpSkill.Leadership);
                    float fEnemyFencing = stf(rTargetCharacter.TmpSkill.Fencing);
                    float fEnemyCrewFencing = (0.1 + fEnemyLeadership + fEnemyFencing * stf(rCharacter.Ship.Crew.Quantity));
                    float fOurLeadership = stf(rCharacter.TmpSkill.Leadership);
                    float fOurFencing = stf(rCharacter.TmpSkill.Fencing);
                    float fOurCrewFencing = (0.1 + fOurLeadership + fOurFencing * stf(rTargetCharacter.Ship.Crew.Quantity));
                    float fRatio = fEnemyCrewFencing / fOurCrewFencing;
                    if (sti(rCharacter.nation) == PIRATE)
                    {
                        fRatio = fRatio * 1.6;
                    }
                    if (sti(rCharacter.rank) > sti(rTargetCharacter.rank))
                    {
                        fRatio = fRatio * 1.2;
                    }
                    if (fRatio > 1.2 && CheckAttribute(rCharacter, "Tmp.fShipSpeed") && CheckAttribute(rTargetCharacter, "Tmp.fShipSpeed") && stf(rCharacter.Tmp.fShipSpeed) > (stf(rTargetCharacter.Tmp.fShipSpeed) / 1.5))
                    {
                        //ref rTargetCharacter = GetCharacter(sti(rCharacter.SeaAI.Task.Target));
                        if(GetRelation(sti(rCharacter.index), sti(rTargetCharacter.index)) == RELATION_ENEMY)
                        {
                            Ship_SetTaskAbordage(SECONDARY_TASK, sti(rCharacter.index), sti(rTargetCharacter.index)); // fix PRIMARY_TASK
                            rCharacter.Ship_SetTaskAbordage = true;
                        }
                    }
                    else
                    {
                        DeleteAttribute(rCharacter, "Ship_SetTaskAbordage");
                    }
                }
                else {
                    DeleteAttribute(rCharacter, "Ship_SetTaskAbordage");
                }
				if (sti(rCharacter.SeaAI.Task) == AITASK_RUNAWAY)
				{
					/*Ship_SetSailState(sti(rCharacter.index), 1);

					Ship_SetLightsOff(rCharacter, 15.0, true, true, false);

					ref rCannon = GetCannonByType(sti(rTargetCharacter.Ship.Cannons.Type));
					float TargetRange = stf(rCannon.FireRange);
					int isDropGoods = 0;

					if(fDistance <= TargetRange)
					{
						isDropGoods = isDropGoods + 1;
					}

					float fOurSpeed = FindShipSpeed(rCharacter);
					float fEnemySpeed = FindShipSpeed(rTargetCharacter);

					if(fOurSpeed <= fEnemySpeed)
					{
						isDropGoods = isDropGoods + 1;
					}

					if(fOurLeadership < rand(12))
					{
						isDropGoods = isDropGoods + 1;
					}

					float fOurMorale = GetCharacterCrewMorale(rCharacter);
					if(fOurMorale < rand(99))
					{
						isDropGoods = isDropGoods + 1;
					}

					if(isDropGoods > 2)
					*/
					Ship_SetSailState(sti(rCharacter.index), 1.0);
					if (sti(rCharacter.Ship.Lights) == 1)
					{
						Ship_SetLightsOff(rCharacter, 15.0, true, true, false);
					}
					if (sti(rCharacter.Ship.SP) > 50 && rand(100) > 90)
					{
						PostEvent(SHIP_DROP_GOOD, 1000, "a", rCharacter);
					}
				}
				else
				{
				    //#20190313-01
					if (sti(rCharacter.SeaAI.Task) == AITASK_ABORDAGE || sti(rCharacter.SeaAI.Task) == AITASK_ABORDAGE_MULTI)
					{
						if (GetRelation(sti(rCharacter.index), sti(rTargetCharacter.index)) == RELATION_ENEMY)
						{
							if(LAi_IsDead(rCharacter) == false && LAi_IsDead(rTargetCharacter) == false)
							{
								if (sti(rTargetCharacter.index) == nMainCharacterIndex && !CheckAttribute(pchar, "abordage_active") && !CheckAttribute(pchar,"abordage_active_count"))
								{
								    //#20190311-02
                                    bool bWaitRepel = CheckAttribute(rTargetCharacter, "waitRepel");
                                    if(bWaitRepel) {
                                        float fWaitRepel = stf(rTargetCharacter.waitRepel);
                                        if(fWaitRepel > WAIT_REPEL_TIME) {
                                            DeleteAttribute(rTargetCharacter, "waitRepel");
                                            bWaitRepel = false;
                                        }
                                        else {
                                            rTargetCharacter.waitRepel = fWaitRepel + (fHighPrecisionDeltaTime * 10.0);
                                        }
                                    }
								    bool bRepelBoard = sti(rTargetCharacter.TmpPerks.RepelBoarding);
									float fCharacterGrappling = stf(rCharacter.TmpSkill.Grappling);
									float fCharacterGrapplingPerk  = Ship_CalcGrapplingPerk(rCharacter);
									float fCharacterDistance = Ship_CalcMaxAbordageDistance(fCharacterGrappling, fCharacterGrapplingPerk);
									float fCharacterAngle = Ship_CalcMaxAbordageAngle(fCharacterGrappling);
									float fCharacterSpeed = Ship_CalcMaxAbordageSpeed(fCharacterGrappling);

									float fAng = 0.0;
									float fRelativeSpeed = stf(arCharShip.Speed.z);

									aref	arUpdate;
									makearef(arUpdate, rCharacter.SeaAI.Update);
									aref aShips;
									makearef(aShips, arUpdate.Ships);
									int iShipsNum = GetAttributesNum(aShips);
									for (int i=0; i<iShipsNum; i++)
									{
										aref aShip = GetAttributeN(aShips, i);
										ref rShipCharacter = GetCharacter(sti(aShip.idx));
										if(rShipCharacter.id == rTargetCharacter.id)
										{
											fRelativeSpeed = stf(aShip.RelativeSpeed);
											fAng = stf(aShip.d_ay);
											break;
										}
									}
									float fEnemyBoxSizeX = stf(rTargetCharacter.Ship.BoxSize.x) / 2.0;
									float fCharacterBoxSizeX = stf(rCharacter.Ship.BoxSize.x) / 2.0;
									float fAbordageDistance = fDistance - fEnemyBoxSizeX - fCharacterBoxSizeX;

									if (fAbordageDistance < fCharacterDistance && fAng < fCharacterAngle && fRelativeSpeed < fCharacterSpeed)
									{
										/*if (sti(rTargetCharacter.index) != nMainCharacterIndex)
										{
											CalculateAbordage(rCharacter);
										}
										else  */
										//if (sti(rTargetCharacter.index) == nMainCharacterIndex)
										//{
										if (!bWaitRepel && fRatio > 1.2)
										{
										    //#20190311-02
										    bool bWillBoard = true;
                                            if(bRepelBoard && ((stf(rTargetCharacter.Ship.Crew.Quantity) * 3.0) >= stf(rCharacter.Ship.Crew.Quantity)))
                                            {
                                                float fMyLuck = FRAND(GetCharacterSkillToOld(rTargetCharacter, SKILL_FORTUNE));
                                                if(FRAND(100.0) < (PERK_BASE_REPEL_BRD + fMyLuck))
                                                {
                                                    Log_SetStringToLog(xiStr("MSG_LAi_boarding_8") + rTargetCharacter.Ship.Name);
                                                    rTargetCharacter.waitRepel = 0.0;
                                                    bWillBoard = false;
                                                }
                                            }
											if(bWillBoard) {
                                                iAbordageShipEnemyCharacter = sti(rCharacter.index);
                                                Sea_AbordageLoad(SHIP_ABORDAGE, false);
											}
										}
										else
										{
											if (stf(rCharacter.ship.hp) > (stf(rTargetCharacter.ship.hp) / 2) || GetCrewQuantity(rCharacter) > GetCrewQuantity(rTargetCharacter)) //fix
											{
												Ship_SetTaskAttack(SECONDARY_TASK, sti(rCharacter.index), sti(rTargetCharacter.index));	//fix
											}
											else
											{
												Ship_SetTaskRunaway(SECONDARY_TASK,sti(rCharacter.index), sti(rCharacter.SeaAI.Task.Target));  //fix
											}
										}
										//}
									}
								} // if
							}
						}
					}
				} // else
			}
		}
	}
	//  to_do <--
	// start random sounds :: SAILS
	if (rand(40) <= fSailState * 5.0) { Ship_PlaySound3DComplex(rCharacter, "sails_ambient", fSailState * 0.5, 0.0, 0.0, 0.5 + frnd() * 0.0); }

	// start random sounds :: SHIPS
	if (rand(2) == 1) { Ship_PlaySound3DComplex(rCharacter, "squeak_sea"+sSeaSoundPostfix, 0.9, frnd() * 1.2 - 0.6, 0.0, frnd() * 1.6 - 0.8); }
}

void Ship_SailsMoveSound()
{
	aref arCharacter = GetEventData();
	bool bMove = GetEventData();

	//#20171230-02 Misc fixes
    if(!CheckAttribute(arCharacter, "Ship.Sounds.SailsID"))
        return;
	// update sounds :: SAILS - moving
	if (CheckAttribute(arCharacter, "Ship.Sounds") && sti(arCharacter.Ship.Sounds.SailsID) > 0)
		{ Sound_SetVolume(sti(arCharacter.Ship.Sounds.SailsID), 0.75 * bMove); }
}

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)
{
	if (!CheckAttribute(rCharacter, "TmpSkillRecall") || sti(rCharacter.TmpSkillRecall) <= 0)
	{
		rCharacter.TmpSkill = "";

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

		// calc skills for speed optimization
		//aTmpSkill.Commerce = MakeFloat(GetSummonSkillFromName(rCharacter, SKILL_COMMERCE)) / SKILL_MAX;
		aTmpSkill.Leadership = MakeFloat(GetSummonSkillFromName(rCharacter, SKILL_LEADERSHIP)) / SKILL_MAX;
		aTmpSkill.Sneak	= MakeFloat(GetSummonSkillFromName(rCharacter, SKILL_SNEAK)) / SKILL_MAX;
		aTmpSkill.Defence	= MakeFloat(GetSummonSkillFromName(rCharacter, SKILL_DEFENCE)) / SKILL_MAX;
		aTmpSkill.Grappling = MakeFloat(GetSummonSkillFromName(rCharacter, SKILL_GRAPPLING)) / SKILL_MAX;
		aTmpSkill.Sailing	= MakeFloat(GetSummonSkillFromName(rCharacter, SKILL_SAILING)) / SKILL_MAX;
		aTmpSkill.Repair	= MakeFloat(GetSummonSkillFromName(rCharacter, SKILL_REPAIR)) / SKILL_MAX;
		aTmpSkill.Fencing	= MakeFloat(GetSummonSkillFromName(rCharacter, SKILL_FENCING)) / SKILL_MAX;
		aTmpSkill.Accuracy  = MakeFloat(GetSummonSkillFromName(rCharacter, SKILL_ACCURACY)) / SKILL_MAX;
		aTmpSkill.Cannons	= MakeFloat(GetSummonSkillFromName(rCharacter, SKILL_CANNONS)) / SKILL_MAX;

		if (sti(rCharacter.index) == GetMainCharacterIndex())
		{
            if (SeaCameras.Camera == "SeaDeckCamera")
			{
				aTmpSkill.Accuracy = Clampf(stf(aTmpSkill.Accuracy) + 0.15);
			}
			//rCharacter.SystemInfo.GlobalMinEnemyDistance = stf(rCharacter.SystemInfo.LastMainMinEnemyDistance); // fix
			if (rand(10) == 3)//fix
			{
				DoQuestCheckDelay("NationUpdate", 1.0);
			}
		}
		rCharacter.TmpSkillRecall = 25 + rand(49);
		if (GetNpcQuestPastTimeParam(rCharacter, "Add2SeaTime") > 55)
		{
			if (CheckAttribute(rCharacter, "SeaAI.Task") && rCharacter.SeaAI.Task != AITASK_RUNAWAY)
			{
				Ship_SetTaskRunaway(SECONDARY_TASK, sti(rCharacter.index), sti(pchar.index));
			}
		}
	}
	else
	{
		rCharacter.TmpSkillRecall = sti(rCharacter.TmpSkillRecall) - 1;

		if (sti(rCharacter.index) == GetMainCharacterIndex())
		{
            rCharacter.TmpSkill.Accuracy  = MakeFloat(GetSummonSkillFromName(rCharacter, SKILL_ACCURACY)) / SKILL_MAX;
            if (SeaCameras.Camera == "SeaDeckCamera")
            {
                rCharacter.TmpSkill.Accuracy = Clampf(stf(rCharacter.TmpSkill.Accuracy) + 0.15);
            }
		}
	}
}

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

	//Trace("Ship_UpdatePerks: rCharacter.id = " + rCharacter.id);
	if (sti(rCharacter.ship.type) == SHIP_NOTUSED) return; // fix
	rCharacter.TmpPerks = "";
	aref aTmpPerks; makearef(aTmpPerks, rCharacter.TmpPerks);

	// calc perks for speed optimization
	//if (!CheckAttribute(rCharacter, "TmpPerks.Turn")) {	rCharacter.TmpPerks.Turn = 0; }
		// to_do
	aTmpPerks.StormProfessional			= CheckOfficersPerk(rCharacter, "StormProfessional");
	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.GrapplingProfessional		= CheckOfficersPerk(rCharacter, "GrapplingProfessional");
	//#20190311-02
	aTmpPerks.RepelBoarding     		= CheckOfficersPerk(rCharacter, "RepelBoarding");
	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.SandbankManeuver			= CheckOfficersPerk(rCharacter, "SandbankManeuver");
	aTmpPerks.SailingProfessional		= CheckOfficersPerk(rCharacter, "SailingProfessional");
	aTmpPerks.LightRepair				= CheckOfficersPerk(rCharacter, "LightRepair");
	aTmpPerks.IronWill					= CheckOfficersPerk(rCharacter, "IronWill");
	// update main character if rCharacter is companion
	if (iCharacterIndex != nMainCharacterIndex && IsCompanion(rCharacter))
	{
		Event("eSwitchPerks", "l", nMainCharacterIndex);
	}
}

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

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

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

		//Boyer change #20170318-38
		if(!CheckAttribute(arShipChar, "Pos"))
            continue;
		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));
			int iMax = iMinBoom + rand(2);
			for (int k=0; k<iMax; 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);
			//#20180223-03 Mitigate crew damage
			Ship_ApplyCrewHitpoints(rCharacter, MakeFloat(rand(3)), true);

			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, "ialsfl", rCharacter, rCharacter, j, "ship_onfire", fTotalFireTime, -1);
				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 = iShipType;
	rCharacter.Ship.Name = sShipName;

	rCharacter.Ship.Cannons.Borts = "";
	rCharacter.Ship.Cannons.Charge.Type = GOOD_BOMBS;
	rCharacter.Ship.Ang.y = 0.0;
	rCharacter.Ship.Pos.x = 0.0;
	rCharacter.Ship.Pos.y = 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;

	rCharacter.Ship.Tornado.Rotate.y = 0.0;  // fix
}

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

void Ship_LoadShip()
{
	int iCharacterIndex = GetEventData();

	ref rCharacter = GetCharacter(iCharacterIndex);

	Dev_Trace("Ship_LoadShip = " + iCharacterIndex + " id = " + rCharacter.id + " ship.type = " + rCharacter.ship.type + " Group = " + rCharacter.SeaAI.Group.Name + " Nation = " + rCharacter.nation + " IsDead = " + LAi_IsDead(rCharacter));
    //#20191119-02
	if (sti(rCharacter.ship.type) != SHIP_NOTUSED) {
        if(!LAi_IsDead(rCharacter))
        {
            Ship_CreateStaticSounds(rCharacter);
            Event("eSwitchPerks", "l", iCharacterIndex);
            Event(SHIP_UPDATE_PARAMETERS, "lf", iCharacterIndex, stf(rCharacter.Ship.Speed.z));
        }
	}
	else {
        SendMessage(&AISea, "la", AI_MESSAGE_CHARACTER_REMOVE, rCharacter);
	}
}

void Ship_LoadShipActivateFirePlace()
{
	int iOurCharacterIndex = GetEventData();
	int iBallCharacterIndex = GetEventData();
	int iFirePlaceIndex = GetEventData();
	float fTotalFireTime = GetEventData();

	//Dev_Trace("iBallCharacterIndex = " + iBallCharacterIndex);
	//int iStartTime = 500;

	//PostEvent(SHIP_FIRE_DAMAGE, iStartTime, "lllf", iOurCharacterIndex, iBallCharacterIndex, iFirePlaceIndex, fTotalFireTime);
	PostEvent(SHIP_FIRE_DAMAGE, 500, "lllf", iOurCharacterIndex, iBallCharacterIndex, iFirePlaceIndex, fTotalFireTime);
}

void SetTextureForShip(aref refBaseShip, aref rCharacter)
{
	if(!CheckAttribute(refBaseShip, "name"))
	{
		trace("SetTextureForShip(): ship " + refBaseShip.basetype + " have no name! NPCid = "+ rCharacter.id);
		if (MOD_BETTATESTMODE == "On") Log_Info("SetTextureForShip(): ship " + refBaseShip.basetype + " have no name! NPCid = "+ rCharacter.id);
		return;
	}
	string sPath = "ships\" + refBaseShip.name + "\";

	if(CheckAttribute(refBaseShip, "ship.Upgrades.hull"))
	{
		sPath = sPath + "hull" + refBaseShip.ship.upgrades.hull + "\";
	}
    //trace("ship tex path = " + sPath);
    //Boyer add #20170423-01
    rCharacter.Ship.setTexturePath = sPath;
	//SetTexturePath(0, sPath);
}

void DropGoodsToSea()
{
	aref rCharacter = GetEventData();

	ref rGood;
	string sGood;

	for (int i=GOOD_MEDICAMENT; i<GOOD_SILVER; i++)
	{
		sGood = Goods[i].name;

		if(CheckAttribute(rCharacter, "Ship.Cargo.Goods."+sGood))
		{
			int	iQuantity = sti(rCharacter.Ship.Cargo.Goods.(sGood));

			RemoveCharacterGoods(rCharacter, i, iQuantity);
			iQuantity = iQuantity / sti(Goods[i].Units);

			if (iQuantity <= 1)
			{
				continue;
			}
			int iSwimQuantity = rand(iQuantity/4) + rand(iQuantity/4) + rand(iQuantity/4) + rand(iQuantity/4);

			if(iSwimQuantity < 1)
			{
				iSwimQuantity = 1;
			}
			rCharacter.Ship.Cargo.Goods.(sGood) = 0;
            rCharacter.Ship.Cargo.RecalculateCargoLoad = true; //  fix
			//RecalculateCargoLoad(rCharacter);

			if (CheckAttribute(&Goods[i], "Swim"))
			{
				float fTime = stf(Goods[i].Swim.Time);
				string sModel = Goods[i].Swim.Model;
				//trace ("sModel is " + sModel);
				fTime = fTime + (frnd() - 0.5) * fTime / 2.0;
				AISeaGoods_AddGood(rCharacter, sGood, sModel, fTime * 60.0, iSwimQuantity);
			}
			return;
		}
	}
}

int Ship_GetRunAwayPoint()
{
	aref arChar = GetEventData();
	float fX = GetEventData();
	float fZ = GetEventData();

	float fRadius = 10000.0;
	float fWindAngle = Whr_GetWindAngle();

	arChar.SeaAI.WindAngle = fWindAngle;
	arChar.SeaAI.RunAwayPnt.x = stf(arChar.Ship.Pos.x) + fRadius * sin(fWindAngle);
	arChar.SeaAI.RunAwayPnt.z = stf(arChar.Ship.Pos.z) + fRadius * cos(fWindAngle);

	return 0;
}

string GetShipSexWord(string _type, string _man, string _woman)
{
	if (_type == "Tartane" || _type == "Tartane2" || _type == "Schooner" || _type == "Caravel" || _type == "PequenaCaravela" || _type == "Caravel20s" || _type == "Caravel30m")
	{
		return _woman;
	}
	else
	{
		return _man;
	}
}

void Ship_ResearchCannons(ref aCharacter)
{
	if (bSeaLoaded)
	{
		SendMessage(&AISea, "la", AI_MESSAGE_RESEARCH_CANNONS, aCharacter);
		RefreshBattleInterface();
		Ship_ChangeCharge(aCharacter, sti(aCharacter.Ship.Cannons.Charge.Type), false);
	}
}

void Ship_FlagRefresh(ref rCharacter)
{
    bool swap = false;
    //#2019012-01
    rCharacter.persFlgMount = 0;
    if (bSeaLoaded)
	{
		if (CheckAttribute(rCharacter,"surrendered")) {
            swap = true;
			int iOldNat = sti(rCharacter.nation);
			rCharacter.nation = NEUTRAL_NATION;
		}
		SendMessage(rCharacter, "l", MSG_SHIP_FLAG_REFRESH);

		if (swap) {
			rCharacter.nation = iOldNat;
		}
	}
}

//Boyer add #20170318-42
void Ship_FlagRefreshByShip(ref rCharacter, ref rShipObject)
{
    bool swap = false;
    //#2019012-01
    rCharacter.persFlgMount = 0;
    if (bSeaLoaded)
	{
		if (CheckAttribute(rCharacter,"surrendered")) {
            swap = true;
			int iOldNat = sti(rCharacter.nation);
			rCharacter.nation = NEUTRAL_NATION;
		}
		SendMessage(rShipObject, "l", MSG_SHIP_FLAG_REFRESH);

		if (swap) {
			rCharacter.nation = iOldNat;
		}
	}
}
//End Boyer add

//JA
void CheckForQuestShip(ref captain)
{
	string sLocation;
	if (CheckAttribute(captain,"quest.cribcity")) {
		//check in range 2000^2
		if (GetDistance2DRel(stf(pchar.Ship.Pos.x), stf(pchar.Ship.Pos.z), stf(captain.Ship.Pos.x), stf(captain.Ship.Pos.z)) > 4000000.0) { return; }

		//check we haven't reported this in the last 12 hours.
		if (CheckAttribute(captain, "lastspotted")) {
			//check lastspotted
			int ihoursSinceSpotted = GetPastTime("hour", sti(captain.lastspotted.Year), sti(captain.lastspotted.Month), sti(captain.lastspotted.Day), stf(captain.lastspotted.Time), GetDataYear(), GetDataMonth(), GetDataDay(), GetTime());
			if (ihoursSinceSpotted < 12) { return; }
		}
		rCharacter = &characters[GetCharacterIndex("QuestCitiz_" + captain.quest.cribCity)];
		//use a generic quest text tag. (bad for translating, good for mod compatibility)
		string sQuestTextTag = "ShipTuning";

		string sQuestTitle = captain.quest.cribCity +  "SCQ_" + rCharacter.quest.SeekCap;
		if (pchar.location == "") {
			sLocation = xiStr("MSG_sea_16");
		}
		else
		{
			int iIsland = FindIsland(pchar.location);
			sLocation = xiStr("MSG_sea_17") + XI_ConvertString(Islands[iIsland].id);
		}
		if(CheckAttribute(captain,"ship.name")) {
			Log_SetStringToLog(xiStr("MSG_sea_7") + captain.ship.name + "!");

			String sQuestMsg = xiStr("MSG_sea_8") + captain.name + " " + captain.lastname + xiStr("MSG_sea_9") + captain.ship.name + sLocation + "!";

			AddQuestRecordEx(sQuestTitle, sQuestTextTag, "t1");
			AddQuestUserData(sQuestTitle, "sText", sQuestMsg);

			captain.lastspotted.Year = GetDataYear();
			captain.lastspotted.Month = GetDataMonth();
			captain.lastspotted.Day = GetDataDay()
			captain.lastspotted.Time = GetTime()
		}
	}
}
// <-JA

int GetSeaTime()
{
	ref pchar = GetMainCharacter();
	if(!CheckAttribute(pchar,"seatime")) return 0;
	return sti(pchar.seatime);
}

bool Ship_CheckMorale(int chridx, bool checkNow)
{
	ref rCharacter = GetCharacter(chridx);
	if(CheckAttribute(rCharacter,"surrendered")) return false; // NK surrender 05-04-20
	//if(IsCompanion(&rCharacter)) return false;  //Skip companions (and mainchar??)

	if (!CheckAttribute(rCharacter,"ship.type")) {
		trace("missing ship type for character: " + rCharacter.id + ", index: " + chridx);
		return false; //
	}
	if(rCharacter.ship.type == SHIP_FORT) return false;

	if (!checkNow) {
		//check occasionally
		int timecheck = CHANGE_MORALE_EVERY
		if(chridx == GetMainCharacterIndex()) {
			timecheck *= 50 //seem to get ~50 mainchar ticks per NPC tick
		}
		int timesincelast = 0;
		if(CheckAttribute(rCharacter,"seatime.timesincelast"))
		{	timesincelast = sti(rCharacter.seatime.timesincelast); }
		if(timesincelast < timecheck)
		{ //increment one and skip; this is to do this once every CHANGE_MORALE_EVERY seconds
			rCharacter.seatime.timesincelast = timesincelast+1;
			return false;
		}
		rCharacter.seatime.timesincelast = 0; //reset counter
	}
	//if (AISHIPDEBUG)	trace("************************************** CHECK MORALE FOR " + rCharacter.ship.name);

	//#20191022-02
	rCharacter.seatime.surmargin = SURR_MARGIN;
	if(!CheckAttribute(rCharacter,"Ship.Crew.Morale")) { rCharacter.Ship.Crew.Morale = 45; }
	if(!CheckAttribute(rCharacter,"seatime.basecrewmorale")) { rCharacter.seatime.basecrewmorale = rCharacter.Ship.Crew.Morale; }

	int CrewQuantity = GetCrewQuantity(rCharacter);
	if(!CheckAttribute(rCharacter,"seatime.basecrewquantity")) { rCharacter.seatime.basecrewquantity = CrewQuantity; }

	int basemorale = sti(rCharacter.seatime.basecrewmorale);

	float fMaxTempMorale = (MORALE_MAX + basemorale)/2.0;  //how much can morale increase if things are going well

	float tempmorale = stf(rCharacter.ship.crew.morale); if(CheckAttribute(rCharacter,"seatime.tempmorale")) tempmorale = stf(rCharacter.seatime.tempmorale); //i.e. a float morale
	rCharacter.seatime.tempmorale = tempmorale;

	float enemydistance = DIST_NOSHIP;
	//#20190703-01
	aref rSituation;
    makearef(rSituation, rCharacter.SeaAI.Update.Situation);

    int targetidx = sti(rSituation.MinEnemyIndex);
    enemydistance = stf(rSituation.MinEnemyDistance); // finds nearest hostile ship
    float fdist;
    int frndidx = sti(rSituation.MinFriendIndex);
    fdist = stf(rSituation.MinFriendDistance);

	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 (AISHIPDEBUG)	trace("Checking morale for the " + rCharacter.ship.name + ", time is " + currenttime + ", current morale is " + tempmorale + ", lht " + lasthit + ", lft " + lastfired);

	//Adjust Morale for dead crew
	if(CheckAttribute(rCharacter,"seatime.lastcrew"))
	{
		//Boyer fix for game crash after captain mission #20170318-44
		if (sti(rCharacter.seatime.basecrewquantity)  != 0) //Skip if basecrew zero
			tempmorale = tempmorale - (stf(rCharacter.seatime.lastcrew) - makefloat(CrewQuantity)) / stf(rCharacter.seatime.basecrewquantity) / CREW_PCT_FOR_MORALE;
		DeleteAttribute(rCharacter,"seatime.lastcrew");
		//if (AISHIPDEBUG)		trace("subtract morale for crew dmg, morale is: " + tempmorale);
	}
	//Adjust Morale for damaged ship
	if(CheckAttribute(rCharacter,"seatime.lastHP"))
	{
		//Boyer fix #20170318-44 for game crash after captain mission divide by zero error
		if (makefloat(GetCharacterShipHP(rCharacter)) != 0.0) //Skip if ShipHP is zero...would be dead anyway
			tempmorale = tempmorale - (stf(rCharacter.seatime.lastHP) - makefloat(rCharacter.Ship.HP)) / makefloat(GetCharacterShipHP(rCharacter)) / CREW_PCT_FOR_HPDAM;
		DeleteAttribute(rCharacter,"seatime.lastHP");
		//if (AISHIPDEBUG)		trace("subtract morale for ship dmg, morale is: " + tempmorale);
	}
	//Adjust Morale for being in combat
	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 (AISHIPDEBUG)	trace("Current combatlevel is " + combatlevel);

	if(combatlevel == COMBATLEVEL_NONE)
	{
		// return to baseline when not in combat
		if(makeint(tempmorale) != basemorale)
		{
			//if (AISHIPDEBUG)			trace("Out of combat, return morale to base");
			if(tempmorale > basemorale) tempmorale -= MORALE_AMT_COMEBACK;
			else tempmorale += MORALE_AMT_COMEBACK;
		}
	}
	else
	{
		//morale auto decreases to: ~80 (with ENEMYHERE), ~62 (with COMBATLEVEL_GUNS), ~16 when COMBATLEVEL_HIT.  More than that requires crew loss
		if(tempmorale > 5.0 / combatlevel)
		{
			//if (AISHIPDEBUG)			trace("In combat, subtract morale:" + retmin(COMBATLEVEL_HIT*CHANGE_MORALE_EVERY, combatlevel*2.5));
			tempmorale -= retmin(COMBATLEVEL_HIT*CHANGE_MORALE_EVERY, combatlevel*2.5);
		}
	}
	if(CheckAttribute(rCharacter,"seatime.enemydead"))
	{
		//if (AISHIPDEBUG)		trace("Adding Morale enemydead= " + rCharacter.seatime.enemydead);
		tempmorale += stf(rCharacter.seatime.enemydead);
		DeleteAttribute(rCharacter,"seatime.enemydead"); // 04-09-22 fix
	}
	//if a more powerful fleet gain morale (up to base)
	float strengthrat = FindPowerRatio(chridx);

	if (strengthrat < 0.6 && fdist < enemydistance && combatlevel != COMBATLEVEL_NONE)
	{ //stronger and with friends, so recover morale (probably winning)
		//if (AISHIPDEBUG)		trace("Adding Morale for " + rCharacter.ship.name + " being in a position of power");
		if(tempmorale < fMaxTempMorale) tempmorale += COMBATLEVEL_ENEMYHERE*2.5;
	}
	if(tempmorale > MORALE_MAX) tempmorale = MORALE_MAX;
	if(tempmorale < MORALE_MIN) tempmorale = MORALE_MIN;

	//if (AISHIPDEBUG)	trace("Morale for " + rCharacter.ship.name + " is " + tempmorale + ", change " + (tempmorale - makefloat(sti(rCharacter.seatime.tempmorale))));

	//rCharacter.ship.crew.morale = makeint(tempmorale);
	rCharacter.seatime.tempmorale = tempmorale;

	//Calculate Morale at which we consider surrender
	float surmorale;
	rCharacter.seatime.surmorale = 0.0;  //default, no surrender chance

	if(!CheckAttribute(InterfaceStates, "SurrenderEnabled") || sti(InterfaceStates.SurrenderEnabled) == 0 || IsCompanion(rCharacter) || targetidx == -1 || lasthit == -1 || CheckAttribute(rCharacter, "cannotsurrender")) {
		//No surrender
		//if (AISHIPDEBUG)		Trace("****** Surrender Disabled ******");
		return false;
	}
	float ftmult = 1.0;
	//#20191022-02
	float surrMarg = SURR_MARGIN;
	//#20200309-01
	/*
	if(CheckAttribute(rCharacter,"EncType"))
	{
		switch(rCharacter.EncType)
		{
			case "trade": ftmult = 1.5; surrMarg += 2.0; break; //ftmult was 2.0 and surrMarg was 2.5 //#20200307-03
			//#20191021-01
			case "pirate": ftmult = 0.85; surrMarg += 1.0; break; // ftmult was 0.75
		}
	}
	*/
	//Encounter type mod
	if(CheckAttribute(rCharacter,"EncType") && rCharacter.EncType == "trade") {
        ftmult *= 1.5; surrMarg += 2.0; //ftmult was 2.0 and surrMarg was 2.5 //#20200307-03
	}
	//Nation mod
	int nCharNat = sti(rCharacter.Nation);
	switch(nCharNat) {
        case PIRATE:
            int nOtherNation = sti(pchar.nation);
            int nLastBallChar = -1;
            if(CheckAttribute(rCharacter, "ship.LastBallCharacter"))
                nLastBallChar = sti(rCharacter.ship.LastBallCharacter);
            if(nLastBallChar > -1)
                nOtherNation = sti(Characters[nLastBallChar].nation);
            if(nOtherNation == PIRATE)
                ftmult *= 0.85; surrMarg *= 1.0; break;
            else
                ftmult *= 0.35; surrMarg *= 1.5; break;
        break;
        case SPAIN:
            ftmult *= 0.15;
        break;
        case HOLLAND:
            ftmult *= 0.85;
        break;
	}
	//Ship class mod
	int nShip = GetCharacterShipType(rCharacter);
	if (nShip != SHIP_NOTUSED) {
        int nClass = SHIP_CLASS_7;
        if(CheckAttribute(RealShips[nShip], "class"))
            nClass = sti(RealShips[nShip].Class);
        switch(nClass) {
            case SHIP_CLASS_5:
                ftmult *= 0.85; surrMarg *= 0.75; break;
            break;
            case SHIP_CLASS_4:
                ftmult *= 0.65; surrMarg *= 0.55; break;
            break;
            case SHIP_CLASS_3:
                ftmult *= 0.45; surrMarg *= 0.35; break;
            break;
            case SHIP_CLASS_2:
                ftmult *= 0.25; surrMarg *= 0.15; break;
            break;
            case SHIP_CLASS_1:
                ftmult = 0.0; surrMarg = 0.0; break;
            break;
        }
	}
	//NPC Rank mod
	int nRank = sti(pchar.rank);
	if (nRank > 10) {
        float fRnk = Bring2Range(1.0, 0.5, 10.0, 50.0, stf(pchar.rank));
        ftmult *= fRnk;
	}
	rCharacter.seatime.surmargin = surrMarg; //This only affects likely to flee
	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(sti(InterfaceStates.SurrenderEnabled) == 2) ftmult /= 2.75; //2.0; //rare surrender
	if(sti(InterfaceStates.SurrenderEnabled) == 3) ftmult /= 4.65; //4.0; //very rare surrender

	surmorale = SURR_MAX_MORALE * mrlmod * ftmult;

	//if (AISHIPDEBUG) Trace("SURR MORALE: base SurenderMorale = " + surmorale);

	if (enemydistance > 1600.0) {
        //#20200307-03
        DeleteAttribute(rCharacter, "seatime.thinkSurr");
        return false;				// LDH don't surrender if enemy not on radar
	}
	float HPp = GetHullPercent(rCharacter);

	//if(HPp > 40.0 && fdist <= enemydistance) return false; // if friend closer than enemy, no surrender.
	//#20191021-01
	//if (AISHIPDEBUG)	Trace("SURR MORALE: Check: HPp = " + HPp + ", fdist " + fdist + ", enemydist " + enemydistance + ", stren " + strengthrat);
    if (HPp > 40.0 && fdist <= enemydistance && strengthrat < 0.8) return false;

	if(HPp < 40.0)
	{
		//if (AISHIPDEBUG)		Trace("SURR: hull damage: HPp: " + HPp + ", Bring2Range(6.0, 1.0, 0.1, sqrt(40.0), sqrt(HPp)): " + Bring2Range(6.0, 1.0, 0.1, 6.325, sqrt(HPp)));
        //#20180921-01 remove sqrt(40.0) = 6.325
		surmorale *= Bring2Range(6.0, 1.0, 0.1, 6.325, sqrt(HPp)); //how broken are we? x6 change at really broken, x0.5 at 40%
		if (fdist < enemydistance - 200.0) {
			//if (AISHIPDEBUG)			Trace("SURR: friend close to help");
			surmorale *= 0.5; //is a friend close to help?
		}
	}
	//if (AISHIPDEBUG)	Trace("SURR MORALE: After Damage Mod: SurenderMorale = " + surmorale);

	//Whose got strong (local) power?
	if(strengthrat > 1.0) // they're stronger
	{
	    //if (AISHIPDEBUG)        Trace("SURR MORALE: Enemy Stronger strengthrat = " + strengthrat);
		float SP = GetSailPercent(rCharacter);
		//#20180921-01 remove sqrt(50.0) = 7.071
		if(SP < 40.0) surmorale *= Bring2Range(5.0, 1.0, 0.1, 7.071, sqrt(SP)); //panic if can't get away
	}
	else
	{ // we're stronger
		surmorale *= 0.3;
	}
	//if (AISHIPDEBUG)	Trace("SURR MORALE: After local Strength Mod: SurenderMorale = " + surmorale);

	//if (AISHIPDEBUG)	Trace("SURR: GetDistDeltaToNearestEnemy(chridx) " + GetDistDeltaToNearestEnemy(chridx));

	if (GetDistDeltaToNearestEnemy(chridx) > 1.0) {
		//seem to be escaping, so unlikely to surrender
		surmorale *= 0.1;
	}
	//if (AISHIPDEBUG)	Trace("SURR: surmorale after escaping modifier surmorale, tempmorale" + surmorale + ", " + tempmorale);

	rCharacter.seatime.surmorale = surmorale;

	if(tempmorale > surmorale)
	{
		// put ship back in group and do group's task.
		//ref rGroup = Group_FindOrCreateGroup(Ship_GetGroupID(rCharacter));
		//if(CheckAttribute(rGroup,"task") && rGroup.task != AITASK_RUNAWAY && CheckAttribute(rCharacter,"SeaAI.task") && rCharacter.SeaAI.task != rGroup.task) { Ship_SetTaskGroupTask(SECONDARY_TASK, chridx); }

		//if (AISHIPDEBUG)		trace("*** Ship_Checkmorale surmorale:" + surmorale + " is less than " + tempmorale + " so keep fighting");
        //#20200307-03
        if(CheckAttribute(rCharacter, "seatime.thinkSurr")) {
            int nSur = sti(rCharacter.seatime.thinkSurr);
            if(nSur <= 1)
                DeleteAttribute(rCharacter, "seatime.thinkSurr");
            else
                rCharacter.seatime.thinkSurr = nSur - 1;
        }
		return false;
	} //#20200307-03
	else {
        if(CheckAttribute(rCharacter, "seatime.thinkSurr"))
            rCharacter.seatime.thinkSurr = sti(rCharacter.seatime.thinkSurr) + 1;
        else
            rCharacter.seatime.thinkSurr = 1;
	}
	return true;  //chance to surrender
}

bool Ship_CheckMoraleFail(int chridx, float fSurrMult)
{
	// start surrender checking, also used for ransome checking
	ref rCharacter = GetCharacter(chridx);
	//if (AISHIPDEBUG)	trace("*** Ship_CheckMoraleFail chridx:" + rCharacter.ship.id + ", fSurrMult " + fSurrMult);

	if(!CheckAttribute(rCharacter,"seatime.tempmorale")) {
		//do a morale calc
		Ship_CheckMorale(chridx, true);
	}
	float tempmorale = rCharacter.seatime.tempmorale;

	if (!CheckAttribute(rCharacter, "seatime.surmorale")) {
		//if (AISHIPDEBUG)  trace("*** Ship_CheckMoraleFail missing rCharacter.seatime.surmorale");
		return false;
	}
	float surmorale = stf(rCharacter.seatime.surmorale) * fSurrMult;

	if(tempmorale < surmorale)
	{
		//if (AISHIPDEBUG)		trace("*** Ship_CheckMoraleFail tempmorale:" + tempmorale + " is less than adjusted surmorale:" + surmorale + " so failed morale check");
		return true;
	}
	//if (AISHIPDEBUG)	trace("*** Ship_CheckMoraleFail tempmorale:" + tempmorale + " is greater than adjusted surmorale:" + surmorale + " so morale ok");
	return false;
}

bool CheckCanSurrender(ref chr)
{
	if(CheckAttribute(chr, "cannotsurrender"))
	{
		//if (AISHIPDEBUG)  trace("CheckCanSurrender: No Surrender! chr.cannotsurrender exists");
		return false;
	}
	if(CheckAttribute(chr, "DontRansackCaptain"))
	{
		//if (AISHIPDEBUG)  trace("CheckCanSurrender: No Surrender! chr.DontRansackCaptain exists");
		return false;
	}
	if (!CheckAttribute(chr,"Ship.Type") || sti(chr.Ship.Type) < 0 || sti(chr.Ship.Type) ==SHIP_NOTUSED)
	{
		//if (AISHIPDEBUG)  trace("CheckCanSurrender: No Surrender! invalid ship type");
		return false;
	}
	if(!CheckAttribute(&InterfaceStates,"SurrenderEnabled") || sti(InterfaceStates.SurrenderEnabled) == 0)
	{
		//if (AISHIPDEBUG) trace("CheckCanSurrender: No Surrender!InterfaceStates.SurrenderEnabled == 0");
		return false;
	}
	//conditions when ship will never surrender
	if (GetPrisonerQty() > PRISONER_MAX)
	{
		//if (AISHIPDEBUG) trace("CheckCanSurrender: No Surrender!GetPrisonerQty() > PRISONER_MAX");
		return false;
	}
	if (sti(pchar.rank) < (sti(chr.rank) - MOD_SKILL_ENEMY_RATE / 2))
	{
		//if (AISHIPDEBUG) trace("CheckCanSurrender: No Surrender!sti(pchar.rank) < (sti(chr.rank) - MOD_SKILL_ENEMY_RATE / 2)");
		return false;
	}
	if (GetCharacterShipClass(chr) == SHIP_CLASS_1)
	{
		//if (AISHIPDEBUG) trace("CheckCanSurrender: No Surrender! Enemy ship is class 1, they don't surrender");
		return false;
	}
	return true;
}

bool Ship_Check_Surrender(int chridx, float fSurrMult)
{
	// start surrender checking, also used for ransom checking
	//#20200307-03
	int nSurr = sti(InterfaceStates.SurrenderEnabled);
	if(fSurrMult > 0.0) {
	    switch(nSurr) {
	        case 1: //occasional surrender
                fSurrMult -= frand(0.15);
                fSurrMult += frand(0.1);
            break;
            case 2: //rare surrender
                fSurrMult -= frand(0.25);
                fSurrMult += frand(0.15);
            break;
            case 3://very rare surrender
                fSurrMult -= frand(0.25);
                fSurrMult -= frand(0.25);
            break;
        }
	}
	//if (AISHIPDEBUG)	trace("*** Ship_Check_Surrender chridx:" + Characters[chridx].ship.name + ", fSurrMult " + fSurrMult);

	ref rCharacter = GetCharacter(chridx);

	if(!CheckAttribute(rCharacter,"seatime.tempmorale")) {
		//do a morale calc
		if (!Ship_CheckMorale(chridx, true)) return false;  //Boyer fix for when Ship_CheckMorale does not set .seatime.tempmorale #20170318-45
	}
	float tempmorale = rCharacter.seatime.tempmorale;
	//#20200307-03
	if(tempmorale < 0.05) {
	    switch(nSurr) {
	        case 1: //occasional surrender
                tempmorale += frand(0.15);
            break;
            case 2: //rare surrender
                tempmorale += frand(0.25);
                tempmorale += frand(0.25);
            break;
            case 3://very rare surrender
                tempmorale += frand(0.5);
                tempmorale += frand(0.5);
            break;
        }
	}
	if (!CheckAttribute(rCharacter, "seatime.surmorale") || stf(rCharacter.seatime.surmorale) < 0.01) {
		return false;
	}
	float surmorale = stf(rCharacter.seatime.surmorale) * fSurrMult;
    //#20200307-03
	if(surmorale < tempmorale) return false;

	int nThink = 25;
	if(CheckAttribute(rCharacter, "seatime.thinkSurr"))
        nThink = sti(rCharacter.seatime.thinkSurr);
    nThink = nThink % 50;
    switch(nSurr) {
        case 1: //occasional surrender
            if(rand(nThink) > 20) return false;
        break;
        case 2: //rare surrender
            if(rand(nThink) > 10) return false;
        break;
        case 3://very rare surrender
            if(rand(nThink) > 5) return false;
        break;
    }
	//if (AISHIPDEBUG)	trace("*** Ship_Checkmorale surmorale:" + surmorale + " is greater than " + tempmorale + " so maybe surrender");

	float surch;
	surch = Bring2Range(0.75, 0.01, 0.01, surmorale, tempmorale);

	//if (AISHIPDEBUG) Trace("SURR: Initial surch = " + surch);

	//ref echr = GetCharacter(targetidx);

	surch *= SURR_GLOBAL_SCL; // LDH

	//if (AISHIPDEBUG)	Trace("SURR: surch after global modifier = " + surch);

	if (surch < 0.1) return false;  //just don't surrender if less then 10% chance

	//use random surrender chance
	float fRoll = frnd();
	//if (AISHIPDEBUG)	Trace("SURR: surrender roll: fRoll=" + fRoll + ", surch=" + surch);
	if(fRoll < surch)
	{
		//if (AISHIPDEBUG)		Trace("Morale fail");
		return true;
	}
	return false;
}

void Ship_Surrender(int chridx)
{
	//if (AISHIPDEBUG)	trace("SURR: Ship_Surrender begin idx: " + chridx);
	ref chr = GetCharacter(chridx);
	int pchridx = GetMainCharacterIndex();
	string chid = chr.id;

	if (CheckAttribute(chr, "surrendered") || CheckAttribute(chr, "cannotsurrender")) {
		//if (AISHIPDEBUG)		trace("trying to surrender again.  But will only surrender once");
		return;
	}
	//if (AISHIPDEBUG)	trace("SURR: " + chid + " is surrendering.  Ship nation: " + sti(chr.nation) );

	int nRel = SeaAI_GetRelation(chridx, pchridx);
	//set surrendered
	chr.surrendered = true;
	chr.surrendered.seatime = GetSeaTime();

	// Set ship to take no actions
	Ship_SetTaskDrift(PRIMARY_TASK, chridx);
	Ship_SetTaskDrift(SECONDARY_TASK, chridx);

	//Raise White Flag
	Ship_FlagRefresh(chr);

	//set flag to increase morale for enemies of surrendered ships
	AddKillMorale(chr, chr);

	//forget that you were shot
	chr.Ship.LastBallCharacter = -1;

	// Alert that ship has surrendered
	if (nRel == RELATION_ENEMY) {
		Log_SetStringToLog(xiStr("MSG_sea_10") + chr.ship.name + xiStr("MSG_sea_11"));
		PlaySound("interface\notebook.wav");
		PlaySound("abordage\abordage_wining.wav");
	} else {
		Log_SetStringToLog(xiStr("MSG_sea_10") + chr.ship.name + xiStr("MSG_sea_12"));
	}
	Ship_Neutral(chridx, "SURR_GROUP");
	//if (AISHIPDEBUG)	Trace("SURR: Ship_Surrender complete");
}

void Ship_Neutral(int chridx, string sGroup)
{
	//if (AISHIPDEBUG) Trace("Ship_Neutral: Ship_Neutral begin idx: " + chridx);
	ref chr = GetCharacter(chridx);
	string chid = chr.id;

	//remember old relations
	if(CheckAttribute(chr,"relation")) {
		chr.oldrelation = "";
		aref arRel; makearef(arRel, chr.relation);
		aref arOldRel; makearef(arOldRel, chr.oldrelation);
		CopyAttributes(arOldRel, arRel);
	}
	//string sTemp1 = "" + iCharacterIndex2;
	//Characters[iCharacterIndex1].relation.(sTemp1) = iRelationType;

	//forget that you were shot
	chr.Ship.LastBallCharacter = -1;

	//*** Fix group membership as neutral ship changes group.
	int i;
	ref rGroup = Group_FindOrCreateGroup(sGroup);
	Group_SetType(sGroup, "trade");

	ref pchar = GetMainCharacter();
	int pchridx = GetMainCharacterIndex();
	ref schr;
	string ogroup = Ship_GetGroupID(&chr);

	int cmdridx = Group_GetGroupCommanderIndex(ogroup);
	if (cmdridx < 0) return;
	ref cmdr = GetCharacter(cmdridx);

	bool cmdrchange = cmdridx == chridx;

	ref rOGroup = Group_GetGroupByID(ogroup);
	bool firstchar = Group_GetCharacterIndexR(rOGroup, 0) == chridx;  // was this char the commander?
	//#20180914-03
	int og_ships = Group_GetLiveCharactersNum(ogroup); //Group_GetCharactersNumR(rOGroup);

	if (cmdrchange) {
		//set new commander if group commander surrenders
		if (og_ships > 1) {
			cmdridx = Group_GetCharacterIndexR(rOGroup, firstchar);  //if was the commander, get the second ship in group
			cmdr = GetCharacter(cmdridx);
			DeleteAttribute(cmdr,"relation.UseOtherCharacter");
			Group_SetGroupCommander(ogroup, cmdr.id); //set new commander
			Group_Refresh_Engine_relations(ogroup);
		} else {
		    Group_SetAddress(ogroup, "None", "", "");
			Group_DeleteGroup(ogroup); // KK
		}
	}
	//#20180914-03
    if(og_ships < 2 && ogroup == "PGGQuestE") {
        DeleteAttribute(PChar, "Quest.PGGQuest1_GroupDead.win_condition.l1.Group");
        PChar.Quest.PGGQuest1_GroupDead.win_condition.l1 = "Ship_capture";
        PChar.Quest.PGGQuest1_GroupDead.win_condition.l1.character = chridx;
    }
	//Remove ship from old group
	//if (AISHIPDEBUG) trace("Ship_Neutral: setting up character group: ogroup="+ ogroup);

	Group_DelCharacter(ogroup, chid);

	Event(SHIP_UPDATE_PARAMETERS, "lf", chridx, 1.0);		// Parameters

	DeleteAttribute(chr,"relation.UseOtherCharacter");
	Group_ChangeCharacter(sGroup, chid);
	Group_SetGroupCommander(sGroup, chid);
	ChangeCharacterShipGroup(chr, sGroup); //forces a engine group boss refresh for this char
	Group_DeleteAtEnd(sGroup);

	//if (AISHIPDEBUG) Trace("Ship_Neutral: group change done");
	//#20190728-01
    bool bHasOld = false;
    if(CheckAttribute(chr, "surrendered")) {
        bHasOld = true;
        int bOlSurr = sti(chr.surrendered);
        int nOldTime = sti(chr.surrendered.seatime);
        DeleteAttribute(chr, "surrendered");
    }
	//Set ship neutral to their enemies
	for (i = 0; i < iNumShips; i++) {
		//if (ships[i] == -1 || ships[i] == chridx) continue;
		if (ships[i] == -1) continue;
		schr = GetCharacter(ships[i]);
		//if (AISHIPDEBUG) trace("Ship_Neutral: chr.id: " + chr.id + ", schr.id " + schr.id);
		if (chr.id == schr.id) continue;

		if(GetRelation(chridx, ships[i]) == RELATION_ENEMY || GetNationRelation2Character(sti(schr.nation),chridx) == RELATION_ENEMY)
		{
			//if (AISHIPDEBUG) trace("Ship_Neutral: Set relation between: chr.id: " + chr.id + " and schr.id " + schr.id + " to neutral");
			SetCharacterRelationBoth(chridx, ships[i], RELATION_NEUTRAL);
		}
	}
	//set ships neutral to forts
	if (CheckAttribute(pchar, "seaAI.update.forts")) {
		aref fortattr; makearef(fortattr, PChar.seaAI.update.forts);
		int iFortidx;
		int num = GetAttributesNum(fortattr);
		for (i = 0; i < num; i++)
		{
		    string fortstr = "l" + i;
			iFortidx = sti(fortattr.(fortstr).idx);
			//#20181116-03
			if(GetRelation(nMainCharacterIndex, iFortidx) == RELATION_ENEMY) continue;
			if(GetRelation(chridx, iFortidx)  == RELATION_ENEMY)
			{
			    //if (AISHIPDEBUG) trace("Ship_Neutral: Set relation between: " + chridx + " and fort:" + iFortidx + " to neutral");
				SetCharacterRelationBoth(chridx, iFortidx, RELATION_NEUTRAL);
			}
		}
	}
	//#20190728-01
	if(bHasOld) {
        chr.surrendered = bOlSurr;
        chr.surrendered.seatime = nOldTime;
    }
	NationUpdate();
    DoQuestCheckDelay("NationUpdate", 0.9);

	//if (AISHIPDEBUG) Trace("Ship_Neutral: Ship_Neutral complete");
}

float FindPowerRatio(int idx)
{
    //if (AISHIPDEBUG) trace("FindPowerRatio(idx=" + idx + ")");
	ref chr = GetCharacter(idx);

	//to reduce load do this every three ticks, otherwise return last value.
	if (CheckAttribute(chr,"seatime.lastPowercheck.val"))
	{
		//we can return last val if needed
		int iPowercheckdelay = sti(chr.seatime.lastPowercheck);

		if(iPowercheckdelay < 3)
		{
			chr.seatime.lastPowercheck = iPowercheckdelay + 1;
			return stf(chr.seatime.lastPowercheck.val);
		}
	}
	//haven't got previous value, or time to recalc:
	chr.seatime.lastPowercheck = 0; //reset counter

	//#20190703-01
	//int sidx = sti(chr.curshipnum); // change to array 05-06-27

	float epower;
	float fpower;

    SendMessage(&AISea, "lale", AI_MESSAGE_GROUP_GET_POWERREL, chr, RELATION_ENEMY, &epower);
    SendMessage(&AISea, "lale", AI_MESSAGE_GROUP_GET_POWERREL, chr, RELATION_FRIEND, &fpower);

	if(epower < 0.5) epower = 1.0;
	if(fpower < 0.5) fpower = 1.0;

	float fResult = epower / fpower;
    //if (AISHIPDEBUG)    trace("FindPowerRatio " + chr.ship.name + ": " + fResult);

	chr.seatime.lastPowercheck.val = fResult; //Fix missing .val
	return fResult;
}

// 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)
{
    //#20190703-01
    //if (AISHIPDEBUG) trace("FindClosestShipOfGroup(idx=" + idx + ", ref retdist, groupid=" + groupid + ")");
    int sidx = -1;
    ref chr = GetCharacter(idx);

	SendMessage(&AISea, "lasee", AI_MESSAGE_GROUP_FIND_CLOSE, chr, groupid, &sidx, &retdist);

    //if (AISHIPDEBUG) trace("FindClosestShipOfGroup: retdist=" + retdist + ", idx = " + NearShips[sidx].(tstr).idx);

	return sidx;
}

float GetDistDeltaToNearestEnemy(int idx) {
	float fDistDelta = 0.0;
	ref chr = GetCharacter(idx);
	float fEnemyDistance = 0.0;
	//#20190703-01
	aref rSituation;
    makearef(rSituation, chr.SeaAI.Update.Situation);
	int iClosestIdx = sti(rSituation.MinEnemyIndex);
	fEnemyDistance = stf(rSituation.MinEnemyDistance);
	if (iClosestIdx == -1) { return 0; }
	float fNow = GetSeaTime();

	if (CheckAttribute(chr, "seatime.nearestenemy.id") && iClosestIdx == sti(chr.seatime.nearestenemy.id))
	{
		//same enemy is closest, return distance change.
		float fTimeDelta = fNow - stf(chr.seatime.nearestenemy.lastcheck)
//trace("fTimeDelta:" + fTimeDelta);
		if (fTimeDelta > 1)
		{
			//calc distance
			fDistDelta = (fEnemyDistance - stf(chr.seatime.nearestenemy.dist))/fTimeDelta;
		}
		else
		{
			//timespan too short
			return stf(chr.seatime.nearestenemy.LastDelta);
		}
	}
	chr.seatime.nearestenemy.id = iClosestIdx;
	chr.seatime.nearestenemy.dist = fEnemyDistance;
	chr.seatime.nearestenemy.lastcheck = GetSeaTime();
	chr.seatime.nearestenemy.LastDelta = fDistDelta;
	return fDistDelta;
}

void AddKillMorale(ref rKillerCharacter, ref rDead) {
	ref schr;
	int iDeadCharacterIndex = GetCharacterIndex(rDead.id);
	float tmped = 0;
	//if (AISHIPDEBUG) trace("AddKillMorale: rKillerCharacter.id" + rKillerCharacter.id + ", rDead.id" + rDead.id);
	if(rKillerCharacter.id != rDead.id) {
		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;
	}
	int iDeadChNation = sti(rDead.nation);
	for(int s = 0; s < iNumShips; s++)
	{
		if(Ships[s] < 0 || Ships[s] == iDeadCharacterIndex) continue; // KK
		//if (AISHIPDEBUG) trace("AddKillMorale: Ships[s]" + Ships[s]);
		schr = GetCharacter(Ships[s]);
		bool bOldEnemy = CheckAttribute(rDead, "surrendered") && GetNationRelation2Character(iDeadChNation,Ships[s]) == RELATION_ENEMY;
		if(SeaAI_GetRelation(iDeadCharacterIndex, Ships[s]) == RELATION_ENEMY || bOldEnemy)
		{
			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;
		}
	}
}

//#20180222-04 10 days adrift, then handle
void SetCompanionAdriftList(ref rDead)
{
    string sCID = rDead.id;
    pchar.quest.companionAdriftList.(sCID).id = rDead.id;
    pchar.quest.companionAdriftList.(sCID).month = GetDataMonth();
    pchar.quest.companionAdriftList.(sCID).day = GetDataDay();
    pchar.quest.companionAdriftList.(sCID).year = GetDataYear();
    SetTimerFunction("companionAdriftTerm", 0, 0, COMPANION_DAYS_ADRIFT);
    RemoveCharacterCompanion(pchar, rDead);
    Log_Info(GetFullName(rDead) + xiStr("MSG_AIShip_18"));
}
