void Ship_HullHitEvent()
{
float fHP;
int iBallType = sti(AIBalls.CurrentBallType);
aref rShipObject = GetEventData();
int iBallCharacterIndex = GetEventData();
int iOurCharacterIndex = GetEventData();
ref rBallCharacter = GetCharacter(iBallCharacterIndex);
ref rOurCharacter = GetCharacter(iOurCharacterIndex);
rOurCharacter.Ship.LastBallCharacter = iBallCharacterIndex;
rBallCharacter.Ship.lasttgt = iOurCharacterIndex; // NK 04-09-16 to see who AI is attacking
int iCompanion = GetCompanionNumber(rBallCharacter, iOurCharacterIndex);
float x = GetEventData();
float y = GetEventData();
float z = GetEventData();
int iFirePlaceIndex = GetEventData();
float fFirePlaceDistance = GetEventData();
bool bDead = LAi_IsDead(rOurCharacter);
bool bSeriousBoom = false;
bool bInflame = false;
// NK 05-04-18 to handle relation change for REAL_CANNONS
if(iBallCharacterIndex == GetMainCharacterIndex() && USE_REAL_CANNONS && EXTRA_HIT_CHECK && rOurCharacter.ship.type != SHIP_FORT_NAME)
{
int currel = SeaAI_GetRelation(iOurCharacterIndex, iBallCharacterIndex) ;
if(currel != RELATION_ENEMY && !CheckAttribute(rOurCharacter,"surrendered")) // NK surrender 05-04-20
{
aref aShips; makearef(aShips, rBallCharacter.SeaAI.Update.Ships);
int iShipsNum = GetAttributesNum(aShips);
int s;
bool enemy = false;
for (s=0; s<iShipsNum; s++)
{
aref aShip = GetAttributeN(aShips, s);
if (sti(aShip.relation) == RELATION_ENEMY) { enemy = true; break; }
}
ref cmdr = Group_GetGroupCommander(GetGroupIDFromCharacter(&rOurCharacter));
if (!enemy) {
Event(SHIP_FIRE_ACTION, "iil", &rOurCharacter, &cmdr, currel); // if there's no enemy, then you have no excuse.
} else {
if (!CheckAttribute(rOurCharacter, "numhits")) {
rOurCharacter.numhits = 1;
} else {
int curhits = sti(rOurCharacter.numhits); // max of cqty / 2 hits.
if (curhits > GetCannonMaxQuantity(&rBallCharacter) / 2) {
Event(SHIP_FIRE_ACTION, "iil", &rOurCharacter, &cmdr, currel);
} else {
curhits++;
rOurCharacter.numhits = curhits;
}
}
}
}
}
if(iBallCharacterIndex != GetMainCharacterIndex() && CheckAttribute(rOurCharacter,"surrendered")) return; // NK because AI doesn't respect surrender. 05-04-23
// NK <--
// Cannon damage multiply
ref rCannon = GetCannonByType(sti(rBallCharacter.Ship.Cannons.Type));
float maxqty, fCannonDamageMultiply;
// THIS IS NOT NEEDED ANY MORE. But the comment is left and the code commented. NK 04-09-15 to simulate fewer crew manning fewer guns. Hence reload rate will now go back to constant.
// KNB 05-02-04 changed this a bit: there is no penalty for >60% crew and the minimum damage is 10%
// now use cannon qty rather than crew mult. 05-04-18
// KK -->
fCannonDamageMultiply = stf(rCannon.DamageMultiply);
if(USE_REAL_CANNONS)
{
// KNB 04-01-17 distance/aspect based damage multipliers -->
float fShotDistance = Ship_GetDistance2D(rBallCharacter, rOurCharacter);
if(fShotDistance < 0) fShotDistance = 500; // NK for when dist < 0 05-04-15
if (rOurCharacter.Ship.Type != SHIP_FORT_NAME && rBallCharacter.Ship.Type != SHIP_FORT_NAME)
{
float fShotSpeed = rCannon.SpeedV0 * stf(Goods[iBallType].SpeedV0);
float fBallAngle = 0;
float fOurAngle = 0;
fCannonDamageMultiply *= 2 * pow(1-1/(2.25*fShotSpeed),fShotDistance); // 2x at point blank, decreasing exponentially with range
if (iBallType != GOOD_GRAPES)
{
if(CheckAttribute(rOurCharacter,"ship.ang.y")) // NK for checking atttributes first before we need CheckAttribute(rBallCharacter,"ship.ang.y") && to
{
// NK now get angle from AIBalls, this requires reversing of the B2R.
fBallAngle = clampangle(stf(AIBalls.Dir)+PI); fOurAngle = stf(rOurCharacter.Ship.Ang.y);
fCannonDamageMultiply *= Bring2Range(0.50, 2.0, 0.0, 1.0, abs(GetDotProduct(fBallAngle,fOurAngle))); //50%-200% depending on angle
}
}
}
}
// <-- KNB
// <-- KK
if (sti(rBallCharacter.TmpPerks.CriticalShoot) && rand(19)==10) { bSeriousBoom = true; } // +5%
if (sti(rBallCharacter.TmpPerks.CannonProfessional) && rand(9)==4) { bSeriousBoom = true; } // +10%
ref rBall = GetGoodByType(iBallType);
switch (iBallType)
{
// TIH --> this section adjusted to be a bit more understandable in setting, and reasonable in graphics Nov13'06
// Removal of "CreateBlastM()" function due to that function causing a CTD in battles Nov12'06
// NK add USE_PARTICLES switch // TIH changed to own switch SHIPHIT_PARTICLES 7-7-06
// Ball_impact and splinters by MM
// By MM, Flames and Critical hits only present on bombs now. // changed to add a rare chance (otherwise what's the POINT of the PERKS?)
case GOOD_BALLS:
if (rand(1)) { bSeriousBoom = false; }// pref toggle and 50% chance a possible critical hit is allowed
if (rand(32) == 1) { bInflame = true; }// pref toggle and 3% chance ship catches on fire from hit
Play3DSound("ball2bort", x, y, z);
switch(SHIPHIT_PARTICLES)
{
case 0: CreateParticleSystem("blast", x, y, z, 0.0, 0.0, 0.0, 0); break; // one orange puff // stock behavior
case 1:
CreateParticleSystem("ball_impact", x, y, z, 0.0, 0.0, 0.0, 0); // two small gray puffs
break;
case 2:
CreateParticleSystem("ball_impact_enhanced", x, y, z, 0.0, 0.0, 0.0, 0); // wood dust
CreateParticleSystem("splinters_enhanced", x, y, z, 0.0, 0.0, 0.0, 0); // twenty-four small splinters
CreateParticleSystem("splinters2_enhanced", x, y, z, 0.0, 0.0, 0.0, 0); // twenty-four medium splinters
break;
case 3:
CreateBlast(x,y,z); // excessive plume of planks and barrels, causing big water splashes
CreateParticleSystem("ball_impact", x, y, z, 0.0, 0.0, 0.0, 0); // two small gray puffs
CreateParticleSystem("splinters2", x, y, z, 0.0, 0.0, 0.0, 0); // six medium splinters
CreateParticleSystem("splinters3", x, y, z, 0.0, 0.0, 0.0, 0); // five large splinters
CreateParticleSystem("flyers", x, y, z, 0.0, 0.0, 0.0, 0);
CreateParticleSystem("flyers2", x, y, z, 0.0, 0.0, 0.0, 0);
break;
}
break;
case GOOD_GRAPES:
bSeriousBoom = false;
bInflame = false;
Play3DSound("grapes2bort", x, y, z);
switch(SHIPHIT_PARTICLES)
{
case 0: CreateParticleSystem("blast", x, y, z, 0.0, 0.0, 0.0, 0); break; // one orange puff // stock behavior
case 1:
CreateParticleSystem("ball_impact", x, y, z, 0.0, 0.0, 0.0, 0);
break;
case 2:
CreateParticleSystem("ball_impact", x, y, z, 0.0, 0.0, 0.0, 0);
CreateParticleSystem("splinters", x, y, z, 0.0, 0.0, 0.0, 0);
break;
case 3:
CreateBlast(x,y,z);
CreateParticleSystem("ball_impact", x, y, z, 0.0, 0.0, 0.0, 0);
break;
}
break;
case GOOD_KNIPPELS:
bSeriousBoom = false;
bInflame = false;
Play3DSound("knippel2bort", x, y, z);
switch(SHIPHIT_PARTICLES)
{
case 0: CreateParticleSystem("blast", x, y, z, 0.0, 0.0, 0.0, 0); break; // one orange puff // stock behavior
case 1:
CreateParticleSystem("ball_impact", x, y, z, 0.0, 0.0, 0.0, 0);
break;
case 2:
CreateParticleSystem("ball_impact", x, y, z, 0.0, 0.0, 0.0, 0);
CreateParticleSystem("splinters", x, y, z, 0.0, 0.0, 0.0, 0);
CreateParticleSystem("splinters2", x, y, z, 0.0, 0.0, 0.0, 0);
break;
case 3:
CreateBlast(x,y,z);
CreateParticleSystem("ball_impact", x, y, z, 0.0, 0.0, 0.0, 0);
CreateParticleSystem("flyers", x, y, z, 0.0, 0.0, 0.0, 0);
CreateParticleSystem("flyers2", x, y, z, 0.0, 0.0, 0.0, 0);
break;
}
break;
case GOOD_BOMBS:
if (rand(20) == 10) { bSeriousBoom = true; }
if (rand(2) == 1) { bInflame = true; }
Play3DSound("bomb2bort", x, y, z);
switch(SHIPHIT_PARTICLES)
{
case 0: CreateParticleSystem("blast", x, y, z, 0.0, 0.0, 0.0, 0); break; // one orange puff // stock behavior
case 1:
CreateParticleSystem("blast", x, y, z, 0.0, 0.0, 0.0, 0);
break;
case 2:
CreateBlast(x,y,z);
CreateParticleSystem("blast", x, y, z, 0.0, 0.0, 0.0, 0);
CreateParticleSystem("flyers", x, y, z, 0.0, 0.0, 0.0, 0);
CreateParticleSystem("flyers2", x, y, z, 0.0, 0.0, 0.0, 0);
break;
case 3:
CreateBlast(x,y,z);
CreateParticleSystem("blast", x, y, z, 0.0, 0.0, 0.0, 0);
CreateParticleSystem("blast_inv", x, y, z, 0.0, 0.0, 0.0, 0);
CreateParticleSystem("splinters2", x, y, z, 0.0, 0.0, 0.0, 0);
CreateParticleSystem("splinters3", x, y, z, 0.0, 0.0, 0.0, 0);
CreateParticleSystem("flyers", x, y, z, 0.0, 0.0, 0.0, 0);
CreateParticleSystem("flyers2", x, y, z, 0.0, 0.0, 0.0, 0);
break;
}
break;
// TIH <-- end adjustment
}
// PB: Queen Anne's Revenge -->
if(sti(GetAttribute(AIBalls, "GreekFire")))
{
bInflame = true;
LogIt("Greek Fire hits the " + rOurCharacter.ship.name + "!")
}
// PB: Queen Anne's Revenge <--
if (sti(rOurCharacter.TmpPerks.ShipDefenceProfessional) && frnd() < PROSHIPDEF_NOCRITCH) { bSeriousBoom = false; } // no seriouse boom, NK 05-04-19 add setting
if (!bDead)
{
float fCrewDamage;
if (USE_REAL_CANNONS)
fCrewDamage = stf(rBall.DamageCrew) * fCannonDamageMultiply * AIShip_isPerksUse(rBallCharacter.TmpPerks.CrewDamageUp, 1.0, 1.10); // 1.15 KNB;
else
fCrewDamage = stf(rBall.DamageCrew) * fCannonDamageMultiply * AIShip_isPerksUse(rBallCharacter.TmpPerks.CrewDamageUp, 1.0, 1.15);
// KK -->
//trace("fCrewDamage="+fCrewDamage);
fCrewDamage = FRAND(fCrewDamage);
int iRelation = SeaAI_GetRelation(iOurCharacterIndex, GetMainCharacterIndex());
if (IsMainCharacter(rBallCharacter))
{
if (iRelation != RELATION_ENEMY)
{
AttackFriendlyShip(rOurCharacter, iRelation, false); // PB: General function also used by Ship_FireAction
}
// PB: Warning Note -->
if (iRelation == RELATION_ENEMY && GetFlagRMRelation(sti(rOurCharacter.nation)) != RELATION_ENEMY)
{
if (sti(rOurCharacter.nation) != PIRATE && !CheckAttribute(rBallCharacter, "false_flag_note"))
{
LogIt("Captain, we are under a flag friendly to the ship we're attacking. We may be branded a pirate if we don't hoist our true colours!");
PlaySound("INTERFACE\notebook.wav");
rBallCharacter.false_flag_note = true;
}
}
// PB: Warning Note <--
}
if (LAi_IsImmortal(rOurCharacter) || LAi_IsCrewImmortal(rOurCharacter)) fCrewDamage = 0.0; // KK
rOurCharacter.cannonhit = true; rOurCharacter.cannonhit.x = x; rOurCharacter.cannonhit.y = y; rOurCharacter.cannonhit.z = z; // NK 05-04-20 so cannon losing has pos for FX
if (bSeriousBoom)
{
//fCrewDamage = fCrewDamage * 2.0; // stock
//fHP = fCannonDamageMultiply * stf(rBall.DamageHull) * (4.0 + frnd() * 2.0); // stock
//fCrewDamage = fCrewDamage * 5.0; // excessive
//fHP = fCannonDamageMultiply * stf(rBall.DamageHull) * (80.0 + frnd() * 40.0); // excessive
fHP = fCannonDamageMultiply * stf(rBall.DamageHull) * (2.0 + frnd());
rOurCharacter.cannonhit.critchance = true; // NK 05-04-20 higher chance for loss of cannon on crit // TIH attribute renamed to critchance Nov13'06
if (!LAi_IsImmortal(rOurCharacter)) Ship_ApplyHullHitpoints(rOurCharacter, fHP, KILL_BY_BALL, iBallCharacterIndex); // KK
if (!LAi_IsImmortal(rOurCharacter) && !LAi_IsCrewImmortal(rOurCharacter)) Ship_ApplyCrewHitpoints(rOurCharacter, fCrewDamage * (2.0 + frnd()) ); // KK
//Ship_ApplyCrewHitpoints(rOurCharacter, fCrewDamage * 1.5); // stock
if (iCompanion == -1)
{
if(AUTO_SKILL_SYSTEM)
{
// LDH divide experience between cannons and accuracy - 27Dec08
Ship_AddCharacterExpChar(rBallCharacter, "Cannons", 100);
Ship_AddCharacterExpChar(rBallCharacter, "Accuracy", 100);
}
else
{
Ship_AddCharacterExp(rBallCharacter, 150);
}
}
if (iBallCharacterIndex == GetMainCharacterIndex())
{
Log_SetStringToLog(LanguageConvertString(iSeaSectionLang, "Ship_critical"));
}
if(AUTO_SKILL_SYSTEM)
{
// LDH divide experience between cannons and accuracy - 27Dec08
Ship_AddCharacterExpChar(GetCharacter(iBallCharacterIndex), "Cannons", 500);
Ship_AddCharacterExpChar(GetCharacter(iBallCharacterIndex), "Accuracy", 500);
}
}
else
{
fHP = fCannonDamageMultiply * stf(rBall.DamageHull);
if (!LAi_IsImmortal(rOurCharacter)) Ship_ApplyHullHitpoints(rOurCharacter, fHP, KILL_BY_BALL, iBallCharacterIndex); // KK
if (!LAi_IsImmortal(rOurCharacter) && !LAi_IsCrewImmortal(rOurCharacter)) Ship_ApplyCrewHitpoints(rOurCharacter, fCrewDamage); // moved from below, so not applied twice during crits // KK
if (iCompanion == - 1)
{
if(AUTO_SKILL_SYSTEM)
{
Ship_AddCharacterExpChar(rBallCharacter, "Cannons", 20);
}
else
{
Ship_AddCharacterExp(rBallCharacter, 20);
}
}
if(AUTO_SKILL_SYSTEM)
{
// LDH divide experience between cannons and accuracy - 27Dec08
Ship_AddCharacterExpChar(GetCharacter(iBallCharacterIndex), "Cannons", 125);
Ship_AddCharacterExpChar(GetCharacter(iBallCharacterIndex), "Accuracy", 125);
}
}
DeleteAttribute(&rOurCharacter, "cannonhit"); // NK 05-04-20 cannon lost FX
//Ship_ApplyCrewHitpoints(rOurCharacter, fCrewDamage); // moved up, so not applied twice during crits
}
if (bInflame == true && fFirePlaceDistance < 4.0 && iFirePlaceIndex >= 0)
{
int iRandStartTime = rand(1000);
float fTotalFireTime = Ship_GetTotalFireTime(rOurCharacter);
PostEvent(SHIP_ACTIVATE_FIRE_PLACE, iRandStartTime, "ialsf", rShipObject, rOurCharacter, iFirePlaceIndex, "ship_onfire", fTotalFireTime);
PostEvent(SHIP_FIRE_DAMAGE, iRandStartTime, "lllf", iOurCharacterIndex, iBallCharacterIndex, iFirePlaceIndex, fTotalFireTime);
}
if (bSeriousBoom == true) //{ Ship_Serious_Boom(x, y, z); } // NK modify coords some 05-04-18 -->
{
if(SHIPHIT_PARTICLES==3) { Ship_Detonate(rOurCharacter, false, false); }//{ Ship_Serious_Boom(x-1+frnd()*2, y-1+frnd()*2, z-1+frnd()*2); }
else { Ship_Serious_Boom(x, y, z); }
}
}