--- a/hedgewars/uGearsUtils.pas Wed May 16 18:22:28 2018 +0200
+++ b/hedgewars/uGearsUtils.pas Wed Jul 31 23:14:27 2019 +0200
@@ -25,14 +25,16 @@
procedure doMakeExplosion(X, Y, Radius: LongInt; AttackingHog: PHedgehog; Mask: Longword); inline;
procedure doMakeExplosion(X, Y, Radius: LongInt; AttackingHog: PHedgehog; Mask: Longword; const Tint: LongWord);
procedure AddSplashForGear(Gear: PGear; justSkipping: boolean);
+procedure AddBounceEffectForGear(Gear: PGear; imageScale: Single);
procedure AddBounceEffectForGear(Gear: PGear);
function ModifyDamage(dmg: Longword; Gear: PGear): Longword;
procedure ApplyDamage(Gear: PGear; AttackerHog: PHedgehog; Damage: Longword; Source: TDamageSource);
procedure spawnHealthTagForHH(HHGear: PGear; dmg: Longword);
-procedure HHHurt(Hedgehog: PHedgehog; Source: TDamageSource);
-procedure HHHeal(Hedgehog: PHedgehog; healthBoost: Longword; showMessage: boolean; vgTint: Longword);
-procedure HHHeal(Hedgehog: PHedgehog; healthBoost: Longword; showMessage: boolean);
+procedure HHHurt(Hedgehog: PHedgehog; Source: TDamageSource; Damage: Longword);
+procedure HHHeal(Hedgehog: PHedgehog; healthBoost: LongInt; showMessage: boolean; vgTint: Longword);
+procedure HHHeal(Hedgehog: PHedgehog; healthBoost: LongInt; showMessage: boolean);
+function IncHogHealth(Hedgehog: PHedgehog; healthBoost: LongInt): LongInt;
procedure CheckHHDamage(Gear: PGear);
procedure CalcRotationDirAngle(Gear: PGear);
procedure ResurrectHedgehog(var gear: PGear);
@@ -40,15 +42,18 @@
procedure FindPlace(var Gear: PGear; withFall: boolean; Left, Right: LongInt); inline;
procedure FindPlace(var Gear: PGear; withFall: boolean; Left, Right: LongInt; skipProximity: boolean);
+function CheckGearNear(Kind: TGearType; X, Y: hwFloat; rX, rY: LongInt): PGear;
function CheckGearNear(Gear: PGear; Kind: TGearType; rX, rY: LongInt): PGear;
function CheckGearDrowning(var Gear: PGear): boolean;
procedure CheckCollision(Gear: PGear); inline;
procedure CheckCollisionWithLand(Gear: PGear); inline;
procedure AmmoShove(Ammo: PGear; Damage, Power: LongInt);
+procedure AmmoShoveCache(Ammo: PGear; Damage, Power: LongInt);
procedure AmmoShoveLine(Ammo: PGear; Damage, Power: LongInt; oX, oY, tX, tY: hwFloat);
function GearsNear(X, Y: hwFloat; Kind: TGearType; r: LongInt): PGearArrayS;
-procedure SpawnBoxOfSmth;
+function SpawnBoxOfSmth: PGear;
+procedure PlayBoxSpawnTaunt(Gear: PGear);
procedure ShotgunShot(Gear: PGear);
function CanUseTardis(HHGear: PGear): boolean;
@@ -60,7 +65,9 @@
function GetUtility(Hedgehog: PHedgehog): TAmmoType;
function WorldWrap(var Gear: PGear): boolean;
+function HomingWrap(var Gear: PGear): boolean;
+function IsHogFacingLeft(Gear: PGear): boolean;
function IsHogLocal(HH: PHedgehog): boolean;
@@ -113,11 +120,6 @@
end;
if (Mask and EXPLAutoSound) <> 0 then PlaySound(sndExplosion);
-(*if (Mask and EXPLAllDamageInRadius) = 0 then
- dmgRadius:= Radius shl 1
-else
- dmgRadius:= Radius;
-dmgBase:= dmgRadius + cHHRadius div 2;*)
dmgBase:= Radius shl 1 + cHHRadius div 2;
// we might have to run twice if weWrap is enabled
@@ -131,8 +133,6 @@
while Gear <> nil do
begin
dmg:= 0;
- //dmg:= dmgRadius + cHHRadius div 2 - hwRound(Distance(Gear^.X - int2hwFloat(X), Gear^.Y - int2hwFloat(Y)));
- //if (dmg > 1) and
if (Gear^.State and gstNoDamage) = 0 then
begin
case Gear^.Kind of
@@ -142,15 +142,13 @@
gtMelonPiece,
gtGrenade,
gtClusterBomb,
- // gtCluster, too game breaking I think
gtSMine,
gtAirMine,
gtCase,
gtTarget,
gtFlame,
gtKnife,
- gtExplosives: begin //,
- //gtStructure: begin
+ gtExplosives: begin
// Run the calcs only once we know we have a type that will need damage
tdX:= Gear^.X-fX;
tdY:= Gear^.Y-fY;
@@ -176,9 +174,18 @@
Gear^.State:= (Gear^.State or gstMoving) and (not gstLoser);
if Gear^.Kind = gtKnife then Gear^.State:= Gear^.State and (not gstCollision);
if (Gear^.Kind = gtHedgehog) and (Gear^.Hedgehog^.Effects[heInvulnerable] = 0) then
- Gear^.State:= (Gear^.State or gstMoving) and (not gstWinner);
+ begin
+ Gear^.State:= (Gear^.State or gstMoving) and (not (gstHHJumping or gstHHHJump));
+ if (not GameOver) then
+ Gear^.State:= (Gear^.State and (not gstWinner));
+ end;
Gear^.Active:= true;
- if Gear^.Kind <> gtFlame then FollowGear:= Gear
+ if Gear^.Kind <> gtFlame then FollowGear:= Gear;
+ if Gear^.Kind = gtAirMine then
+ begin
+ Gear^.Tag:= 1;
+ Gear^.FlightTime:= 5000;
+ end
end;
if ((Mask and EXPLPoisoned) <> 0) and (Gear^.Kind = gtHedgehog) and
(Gear^.Hedgehog^.Effects[heInvulnerable] = 0) and (Gear^.Hedgehog^.Effects[heFrozen] = 0) and
@@ -187,7 +194,7 @@
if Gear^.Hedgehog^.Effects[hePoisoned] = 0 then
begin
s:= ansistring(Gear^.Hedgehog^.Name);
- AddCaption(FormatA(GetEventString(eidPoisoned), s), cWhiteColor, capgrpMessage);
+ AddCaption(FormatA(GetEventString(eidPoisoned), s), capcolDefault, capgrpMessage);
uStats.HedgehogPoisoned(Gear, AttackingHog)
end;
Gear^.Hedgehog^.Effects[hePoisoned] := 5;
@@ -215,7 +222,7 @@
end;
if (Mask and EXPLDontDraw) = 0 then
- if (GameFlags and gfSolidLand) = 0 then
+ if ((GameFlags and gfSolidLand) = 0) or ((Mask and EXPLForceDraw) <> 0) then
begin
cnt:= DrawExplosion(X, Y, Radius) div 1608; // approx 2 16x16 circles to erase per chunk
if (cnt > 0) and (SpritesData[sprChunk].Texture <> nil) then
@@ -230,12 +237,12 @@
break;
// Radius + 5 because that's the actual radius the explosion changes graphically
- if X + (Radius + 5) > LongInt(rightX) then
+ if X + (Radius + 5) > rightX then
begin
dec(X, playWidth);
wrap:= true;
end
- else if X - (Radius + 5) < LongInt(leftX) then
+ else if X - (Radius + 5) < leftX then
begin
inc(X, playWidth);
wrap:= true;
@@ -276,8 +283,16 @@
Gear^.LastDamage := AttackerHog;
Gear^.Hedgehog^.Team^.Clan^.Flawless:= false;
- HHHurt(Gear^.Hedgehog, Source);
- AddDamageTag(hwRound(Gear^.X), hwRound(Gear^.Y), Damage, Gear^.Hedgehog^.Team^.Clan^.Color);
+
+ if (Gear^.State and gstHHDeath) <> 0 then
+ // If hog took damage while dying, explode hog instantly (see doStepHedgehogDead)
+ Gear^.Timer:= 1
+ else
+ begin
+ HHHurt(Gear^.Hedgehog, Source, Damage);
+ AddDamageTag(hwRound(Gear^.X), hwRound(Gear^.Y), Damage, Gear^.Hedgehog^.Team^.Clan^.Color);
+ end;
+
tmpDmg:= min(Damage, max(0,Gear^.Health-Gear^.Damage));
if (Gear <> CurrentHedgehog^.Gear) and (CurrentHedgehog^.Gear <> nil) and (tmpDmg >= 1) then
begin
@@ -288,7 +303,7 @@
begin
// was considering pulsing on attack, Tiy thinks it should be permanent while in play
//CurrentHedgehog^.Gear^.State:= CurrentHedgehog^.Gear^.State or gstVampiric;
- inc(CurrentHedgehog^.Gear^.Health,vampDmg);
+ vampDmg:= IncHogHealth(CurrentHedgehog, vampDmg);
RenderHealth(CurrentHedgehog^);
RecountTeamHealth(CurrentHedgehog^.Team);
HHHeal(CurrentHedgehog, vampDmg, true, $FF0000FF);
@@ -305,28 +320,24 @@
uStats.HedgehogDamaged(Gear, AttackerHog, Damage, false);
- if AprilOne and (Gear^.Hedgehog^.Hat = 'fr_tomato') and (Damage > 2) then
- for i := 0 to random(min(Damage,20))+5 do
- begin
- vg:= AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtStraightShot);
- if vg <> nil then
- with vg^ do
- begin
- dx:= 0.001 * (random(100)+10);
- dy:= 0.001 * (random(100)+10);
- tdy:= -cGravityf;
- if random(2) = 0 then
- dx := -dx;
- //if random(2) = 0 then
- // dy := -dy;
- FrameTicks:= random(500) + 1000;
- State:= ord(sprBubbles);
- //Tint:= $bd2f03ff
- Tint:= $ff0000ff
- end
- end
+ if AprilOne and (Gear^.Hedgehog^.Hat = 'fr_tomato') and (Damage > 2) then
+ for i := 0 to random(min(Damage,20))+5 do
+ begin
+ vg:= AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtStraightShot);
+ if vg <> nil then
+ with vg^ do
+ begin
+ dx:= 0.001 * (random(100)+10);
+ dy:= 0.001 * (random(100)+10);
+ tdy:= -cGravityf;
+ if random(2) = 0 then
+ dx := -dx;
+ FrameTicks:= random(500) + 1000;
+ State:= ord(sprBubbles);
+ Tint:= $ff0000ff
+ end
+ end
end else
- //else if Gear^.Kind <> gtStructure then // not gtHedgehog nor gtStructure
Gear^.Hedgehog:= AttackerHog;
inc(Gear^.Damage, Damage);
@@ -344,11 +355,17 @@
end;
// Play effects for hurt hedgehog
-procedure HHHurt(Hedgehog: PHedgehog; Source: TDamageSource);
+procedure HHHurt(Hedgehog: PHedgehog; Source: TDamageSource; Damage: Longword);
begin
if Hedgehog^.Effects[heFrozen] <> 0 then exit;
-if (Source = dsFall) or (Source = dsExplosion) then
+if (Damage >= ouchDmg) and (OuchTauntTimer = 0) and ((Source = dsFall) or (Source = dsBullet) or (Source = dsShove) or (Source = dsHammer)) then
+ begin
+ PlaySoundV(sndOuch, Hedgehog^.Team^.voicepack);
+ // Prevent sndOuch from being played too often in short time
+ OuchTauntTimer:= 1250;
+ end
+else if (Source = dsFall) or (Source = dsExplosion) then
case random(3) of
0: PlaySoundV(sndOoff1, Hedgehog^.Team^.voicepack);
1: PlaySoundV(sndOoff2, Hedgehog^.Team^.voicepack);
@@ -373,9 +390,9 @@
Hedgehog: Hedgehog which gets the health boost
healthBoost: Amount of added health added
showMessage: Whether to show announcer message
-vgTint: Tint of heal particle
+vgTint: Tint of heal particle (if 0, don't render particles)
-}
-procedure HHHeal(Hedgehog: PHedgehog; healthBoost: Longword; showMessage: boolean; vgTint: Longword);
+procedure HHHeal(Hedgehog: PHedgehog; healthBoost: LongInt; showMessage: boolean; vgTint: Longword);
var i: LongInt;
vg: PVisualGear;
s: ansistring;
@@ -391,25 +408,45 @@
i:= 0;
// One particle for every 5 HP. Max. 200 particles
- while (i < healthBoost) and (i < 1000) do
- begin
- vg:= AddVisualGear(hwRound(Hedgehog^.Gear^.X), hwRound(Hedgehog^.Gear^.Y), vgtStraightShot);
- if vg <> nil then
- with vg^ do
- begin
- Tint:= vgTint;
- State:= ord(sprHealth)
- end;
- inc(i, 5)
- end;
+ if (vgTint <> 0) then
+ while (i < healthBoost) and (i < 1000) do
+ begin
+ vg:= AddVisualGear(hwRound(Hedgehog^.Gear^.X), hwRound(Hedgehog^.Gear^.Y), vgtStraightShot);
+ if vg <> nil then
+ with vg^ do
+ begin
+ Tint:= vgTint;
+ State:= ord(sprHealth)
+ end;
+ inc(i, 5)
+ end;
end;
// Shorthand for the same above, but with tint implied
-procedure HHHeal(Hedgehog: PHedgehog; healthBoost: Longword; showMessage: boolean);
+procedure HHHeal(Hedgehog: PHedgehog; healthBoost: LongInt; showMessage: boolean);
begin
HHHeal(Hedgehog, healthBoost, showMessage, $00FF00FF);
end;
+// Increase hog health by healthBoost (at least 1).
+// Resulting health is capped at cMaxHogHealth.
+// Returns actual amount healed.
+function IncHogHealth(Hedgehog: PHedgehog; healthBoost: LongInt): LongInt;
+var oldHealth: LongInt;
+begin
+ if healthBoost < 1 then
+ begin
+ IncHogHealth:= 0;
+ exit;
+ end;
+ oldHealth:= Hedgehog^.Gear^.Health;
+ inc(Hedgehog^.Gear^.Health, healthBoost);
+ // Prevent overflow
+ if (Hedgehog^.Gear^.Health < 1) or (Hedgehog^.Gear^.Health > cMaxHogHealth) then
+ Hedgehog^.Gear^.Health:= cMaxHogHealth;
+ IncHogHealth:= Hedgehog^.Gear^.Health - oldHealth;
+end;
+
procedure CheckHHDamage(Gear: PGear);
var
dmg: LongInt;
@@ -435,11 +472,6 @@
if ((Gear^.Hedgehog^.Effects[heInvulnerable] <> 0)) then
exit;
- //if _0_6 < Gear^.dY then
- // PlaySound(sndOw4, Gear^.Hedgehog^.Team^.voicepack)
- //else
- // PlaySound(sndOw1, Gear^.Hedgehog^.Team^.voicepack);
-
if Gear^.LastDamage <> nil then
ApplyDamage(Gear, Gear^.LastDamage, dmg, dsFall)
else
@@ -453,7 +485,6 @@
dAngle: real;
begin
// Frac/Round to be kind to JS as of 2012-08-27 where there is yet no int64/uint64
- //dAngle := (Gear^.dX.QWordValue + Gear^.dY.QWordValue) / $80000000;
dAngle := (Gear^.dX.Round + Gear^.dY.Round) / 2 + (Gear^.dX.Frac/$100000000+Gear^.dY.Frac/$100000000);
if not Gear^.dX.isNegative then
Gear^.DirAngle := Gear^.DirAngle + dAngle
@@ -555,6 +586,13 @@
else Scale:= Scale + ((1-Scale) / 2);
if Scale > 1 then Timer:= round(min(Scale*0.0005/cGravityf,4))
else Timer:= 1;
+ if Scale > 1 then
+ if (not isImpactH) then
+ Y:= Y + 10
+ else if isImpactRight then
+ X:= X + 10
+ else
+ X:= X - 10;
// Low Gravity
FrameTicks:= FrameTicks*Timer;
end;
@@ -631,7 +669,7 @@
if WorldEdge = weSea then
begin
tmp:= dist2Water;
- dist2Water:= min(dist2Water, min(X - Gear^.Radius - LongInt(leftX), LongInt(rightX) - (X + Gear^.Radius)));
+ dist2Water:= min(dist2Water, min(X - Gear^.Radius - leftX, rightX - (X + Gear^.Radius)));
// if water on sides is closer than on bottom -> horizontal direction
isDirH:= tmp <> dist2Water;
end;
@@ -711,9 +749,9 @@
Gear^.State := Gear^.State and (not gstHHDriven);
s:= ansistring(Gear^.Hedgehog^.Name);
if Gear^.Hedgehog^.King then
- AddCaption(FormatA(GetEventString(eidKingDied), s), cWhiteColor, capgrpMessage)
+ AddCaption(FormatA(GetEventString(eidKingDied), s), capcolDefault, capgrpMessage)
else
- AddCaption(FormatA(GetEventString(eidDrowned), s), cWhiteColor, capgrpMessage);
+ AddCaption(FormatA(GetEventString(eidDrowned), s), capcolDefault, capgrpMessage);
end
end
else
@@ -723,7 +761,7 @@
end
else // submersible
begin
- // drown submersible grears if far below map
+ // drown submersible gears if far below map
if (Y > cWaterLine + cVisibleWater*4) then
begin
DrownGear(Gear);
@@ -762,7 +800,8 @@
// splash sound animation and droplets
if isImpact or isSkip then
- addSplashForGear(Gear, isSkip);
+ if (not (((dist2Water + Gear^.Radius div 2) < 0) or (abs(dist2Water + Gear^.Radius) >= Gear^.Radius))) then
+ addSplashForGear(Gear, isSkip);
if isSkip then
ScriptCall('onGearWaterSkip', Gear^.uid);
@@ -774,13 +813,14 @@
procedure ResurrectHedgehog(var gear: PGear);
var tempTeam : PTeam;
- sparkles: PVisualGear;
+ sparkles, expl: PVisualGear;
gX, gY: LongInt;
begin
if (Gear^.LastDamage <> nil) then
uStats.HedgehogDamaged(Gear, Gear^.LastDamage, 0, true)
else
uStats.HedgehogDamaged(Gear, CurrentHedgehog, 0, true);
+ // Reset gear state
AttackBar:= 0;
gear^.dX := _0;
gear^.dY := _0;
@@ -799,20 +839,25 @@
DeleteCI(gear);
gX := hwRound(gear^.X);
gY := hwRound(gear^.Y);
- // might need more sparkles for a column
+ // Spawn a few sparkles at death position.
+ // Might need more sparkles for a column.
sparkles:= AddVisualGear(gX, gY, vgtDust, 1);
if sparkles <> nil then
begin
sparkles^.Tint:= tempTeam^.Clan^.Color shl 8 or $FF;
- //sparkles^.Angle:= random(360);
end;
+ // Set new position of gear (might fail)
FindPlace(gear, false, 0, LAND_WIDTH, true);
if gear <> nil then
begin
- AddVisualGear(hwRound(gear^.X), hwRound(gear^.Y), vgtExplosion);
+ // Visual effect at position of resurrection
+ expl:= AddVisualGear(hwRound(gear^.X), hwRound(gear^.Y), vgtExplosion);
PlaySound(sndWarp);
RenderHealth(gear^.Hedgehog^);
- ScriptCall('onGearResurrect', gear^.uid);
+ if expl <> nil then
+ ScriptCall('onGearResurrect', gear^.uid, expl^.uid)
+ else
+ ScriptCall('onGearResurrect', gear^.uid);
gear^.State := gstWait;
end;
RecountTeamHealth(tempTeam);
@@ -896,7 +941,7 @@
tryAgain:= true;
if WorldEdge <> weNone then
begin
- Left:= max(Left, LongInt(leftX) + Gear^.Radius);
+ Left:= max(Left, leftX + Gear^.Radius);
Right:= min(Right,rightX-Gear^.Radius)
end;
while tryAgain do
@@ -1025,28 +1070,92 @@
end
end;
-function CheckGearNear(Gear: PGear; Kind: TGearType; rX, rY: LongInt): PGear;
+function CheckGearNearImpl(Kind: TGearType; X, Y: hwFloat; rX, rY: LongInt; exclude: PGear): PGear;
var t: PGear;
+ width, bound, dX, dY: hwFloat;
+ isHit: Boolean;
+ i, j: LongWord;
begin
-t:= GearsList;
-rX:= sqr(rX);
-rY:= sqr(rY);
+ bound:= _1_5 * int2hwFloat(max(rX, rY));
+ rX:= sqr(rX);
+ rY:= sqr(rY);
+ width:= int2hwFloat(RightX - LeftX);
+ if (Kind = gtHedgehog) then
+ begin
+ for j:= 0 to Pred(TeamsCount) do
+ if TeamsArray[j]^.TeamHealth > 0 then // it's impossible for a team to have hogs in game and zero health right?
+ with TeamsArray[j]^ do
+ for i:= 0 to cMaxHHIndex do
+ with Hedgehogs[i] do
+ if (Gear <> nil) and (Gear <> exclude) then
+ begin
+ // code duplication - could throw into an inline function I guess
+ dX := X - Gear^.X;
+ dY := Y - Gear^.Y;
+ isHit := (hwAbs(dX) + hwAbs(dY) < bound)
+ and (not ((hwSqr(dX) / rX + hwSqr(dY) / rY) > _1));
+
+ if (not isHit) and (WorldEdge = weWrap) then
+ begin
+ if (hwAbs(dX - width) + hwAbs(dY) < bound)
+ and (not ((hwSqr(dX - width) / rX + hwSqr(dY) / rY) > _1)) then
+ isHit := true
+ else if (hwAbs(dX + width) + hwAbs(dY) < bound)
+ and (not ((hwSqr(dX + width) / rX + hwSqr(dY) / rY) > _1)) then
+ isHit := true
+ end;
-while t <> nil do
- begin
- if (t <> Gear) and (t^.Kind = Kind) then
- if (not ((hwSqr(Gear^.X - t^.X) / rX + hwSqr(Gear^.Y - t^.Y) / rY) > _1)) or
- ((WorldEdge = weWrap) and (
- (not ((hwSqr(Gear^.X - int2hwFloat(RightX-LeftX) - t^.X) / rX + hwSqr(Gear^.Y - t^.Y) / rY) > _1)) or
- (not ((hwSqr(Gear^.X + int2hwFloat(RightX-LeftX) - t^.X) / rX + hwSqr(Gear^.Y - t^.Y) / rY) > _1)))) then
+ if isHit then
+ begin
+ CheckGearNearImpl:= Gear;
+ exit;
+ end
+ end;
+ end
+ else
begin
- CheckGearNear:= t;
- exit;
- end;
- t:= t^.NextGear
+ t:= GearsList;
+
+ while t <> nil do
+ begin
+ if (t <> exclude) and (t^.Kind = Kind) then
+ begin
+ dX := X - t^.X;
+ dY := Y - t^.Y;
+ isHit := (hwAbs(dX) + hwAbs(dY) < bound)
+ and (not ((hwSqr(dX) / rX + hwSqr(dY) / rY) > _1));
+
+ if (not isHit) and (WorldEdge = weWrap) then
+ begin
+ if (hwAbs(dX - width) + hwAbs(dY) < bound)
+ and (not ((hwSqr(dX - width) / rX + hwSqr(dY) / rY) > _1)) then
+ isHit := true
+ else if (hwAbs(dX + width) + hwAbs(dY) < bound)
+ and (not ((hwSqr(dX + width) / rX + hwSqr(dY) / rY) > _1)) then
+ isHit := true
+ end;
+
+ if isHit then
+ begin
+ CheckGearNearImpl:= t;
+ exit;
+ end;
+ end;
+ t:= t^.NextGear
+ end
end;
-CheckGearNear:= nil
+ CheckGearNearImpl:= nil
+end;
+
+function CheckGearNear(Kind: TGearType; X, Y: hwFloat; rX, rY: LongInt): PGear;
+begin
+ CheckGearNear := CheckGearNearImpl(Kind, X, Y, rX, rY, nil);
+end;
+
+function CheckGearNear(Gear: PGear; Kind: TGearType; rX, rY: LongInt): PGear;
+begin
+ CheckGearNear := CheckGearNearImpl(Kind, Gear^.X, Gear^.Y, rX, rY, Gear);
end;
procedure CheckCollision(Gear: PGear); inline;
@@ -1150,11 +1259,11 @@
gtHedgehog,
gtMine,
gtSMine,
+ gtAirMine,
gtKnife,
gtCase,
gtTarget,
- gtExplosives: begin//,
-// gtStructure: begin
+ gtExplosives: begin
//addFileLog('ShotgunShot radius: ' + inttostr(Gear^.Radius) + ', t^.Radius = ' + inttostr(t^.Radius) + ', distance = ' + inttostr(dist) + ', dmg = ' + inttostr(dmg));
dmg:= 0;
r:= Gear^.Radius + t^.Radius;
@@ -1180,7 +1289,13 @@
t^.State:= t^.State or gstMoving;
if t^.Kind = gtKnife then t^.State:= t^.State and (not gstCollision);
t^.Active:= true;
- FollowGear:= t
+ FollowGear:= t;
+
+ if t^.Kind = gtAirmine then
+ begin
+ t^.Tag:= 1;
+ t^.FlightTime:= 5000;
+ end
end
end;
gtGrave: begin
@@ -1262,8 +1377,10 @@
begin
dec(i);
Gear:= t^.ar[i];
- if (Ammo^.Data <> nil) and (Ammo^.Kind in [gtDEagleShot, gtSniperRifleShot, gtMinigunBullet]) and (PGear(Ammo^.Data) = Gear)
- or ((Ammo^.Kind = gtMinigunBullet) and (not UpdateHitOrder(Gear, Ammo^.WDTimer))) then
+ if (Ammo^.Kind in [gtDEagleShot, gtSniperRifleShot, gtMinigunBullet,
+ gtFirePunch, gtKamikaze, gtWhip, gtShover])
+ and (((Ammo^.Data <> nil) and (PGear(Ammo^.Data) = Gear))
+ or (not UpdateHitOrder(Gear, Ammo^.WDTimer))) then
continue;
if ((Ammo^.Kind = gtFlame) or (Ammo^.Kind = gtBlowTorch)) and
@@ -1273,30 +1390,28 @@
if (Gear^.State and gstNoDamage) = 0 then
begin
- if (Gear^.Kind <> gtMinigun) and
- ((Ammo^.Kind = gtDEagleShot)
- or (Ammo^.Kind = gtSniperRifleShot)
- or (Ammo^.Kind = gtMinigunBullet)) then
- begin
- VGear := AddVisualGear(t^.cX[i], t^.cY[i], vgtBulletHit);
- if VGear <> nil then
- VGear^.Angle := DxDy2Angle(-Ammo^.dX, Ammo^.dY);
- end;
-
if (Gear^.Kind = gtHedgehog) and (Ammo^.State and gsttmpFlag <> 0) and (Ammo^.Kind = gtShover) then
Gear^.FlightTime:= 1;
-
case Gear^.Kind of
gtHedgehog,
gtMine,
+ gtAirMine,
gtSMine,
gtKnife,
gtTarget,
gtCase,
- gtExplosives: //,
- //gtStructure:
+ gtExplosives:
begin
+ if (Ammo^.Kind in [gtFirePunch, gtKamikaze]) and (Gear^.Kind <> gtSMine) then
+ PlaySound(sndFirePunchHit);
+
+ if Ammo^.Kind in [gtDEagleShot, gtSniperRifleShot, gtMinigunBullet] then
+ begin
+ VGear := AddVisualGear(t^.cX[i], t^.cY[i], vgtBulletHit);
+ if VGear <> nil then
+ VGear^.Angle := DxDy2Angle(-Ammo^.dX, Ammo^.dY);
+ end;
if (Ammo^.Kind = gtDrill) then
begin
Ammo^.Timer:= 0;
@@ -1326,7 +1441,13 @@
State:= ord(sprStar)
end
end;
- ApplyDamage(Gear, Ammo^.Hedgehog, tmpDmg, dsShove)
+ ApplyDamage(Gear, Ammo^.Hedgehog, tmpDmg, dsShove);
+
+ if Gear^.Kind = gtAirmine then
+ begin
+ Gear^.Tag:= 1;
+ Gear^.FlightTime:= 5000;
+ end
end
else
Gear^.State:= Gear^.State or gstWinner;
@@ -1334,18 +1455,18 @@
begin
if (Ammo^.Hedgehog^.Gear <> nil) then
Ammo^.Hedgehog^.Gear^.State:= Ammo^.Hedgehog^.Gear^.State and (not gstNotKickable);
- ApplyDamage(Gear, Ammo^.Hedgehog, tmpDmg * 100, dsUnknown); // crank up damage for explosives + blowtorch
+ ApplyDamage(Gear, Ammo^.Hedgehog, tmpDmg * 100, dsExplosion); // crank up damage for explosives + blowtorch
end;
if (Gear^.Kind = gtHedgehog) and (Gear^.Hedgehog^.King or (Gear^.Hedgehog^.Effects[heFrozen] > 0)) then
begin
- Gear^.dX:= Gear^.dX + Ammo^.dX * Power * _0_005;
- Gear^.dY:= Gear^.dY + Ammo^.dY * Power * _0_005
+ Gear^.dX:= Ammo^.dX * Power * _0_005;
+ Gear^.dY:= Ammo^.dY * Power * _0_005
end
else if ((Ammo^.Kind <> gtFlame) or (Gear^.Kind = gtHedgehog)) and (Power <> 0) then
begin
- Gear^.dX:= Gear^.dX + Ammo^.dX * Power * _0_01;
- Gear^.dY:= Gear^.dY + Ammo^.dY * Power * _0_01
+ Gear^.dX:= Ammo^.dX * Power * _0_01;
+ Gear^.dY:= Ammo^.dY * Power * _0_01
end;
if (not isZero(Gear^.dX)) or (not isZero(Gear^.dY)) then
@@ -1383,10 +1504,7 @@
procedure AmmoShoveLine(Ammo: PGear; Damage, Power: LongInt; oX, oY, tX, tY: hwFloat);
var t: PGearArray;
begin
- if Ammo^.Kind = gtMinigunBullet then
- t:= CheckAllGearsLineCollision(Ammo, oX, oY, tX, tY)
- else
- t:= CheckGearsLineCollision(Ammo, oX, oY, tX, tY);
+ t:= CheckAllGearsLineCollision(Ammo, oX, oY, tX, tY);
AmmoShoveImpl(Ammo, Damage, Power, t);
end;
@@ -1396,6 +1514,11 @@
CheckGearsCollision(Ammo));
end;
+procedure AmmoShoveCache(Ammo: PGear; Damage, Power: LongInt);
+begin
+ AmmoShoveImpl(Ammo, Damage, Power,
+ CheckCacheCollision(Ammo));
+end;
function CountGears(Kind: TGearType): Longword;
var t: PGear;
@@ -1481,14 +1604,14 @@
GearsNear.ar:= @GearsNearArray
end;
-
-procedure SpawnBoxOfSmth;
+function SpawnBoxOfSmth: PGear;
var t, aTot, uTot, a, h: LongInt;
i: TAmmoType;
begin
-if (PlacingHogs) or
+SpawnBoxOfSmth:= nil;
+if (PlacingHogs) or (PlacingKings) or
(cCaseFactor = 0)
- or (CountGears(gtCase) >= 5)
+ or (CountGears(gtCase) >= cMaxCaseDrops)
or (GetRandom(cCaseFactor) <> 0) then
exit;
@@ -1522,11 +1645,12 @@
if t<h then
begin
FollowGear:= AddGear(0, 0, gtCase, 0, _0, _0, 0);
+ FollowGear^.RenderHealth:= true;
FollowGear^.Health:= cHealthCaseAmount;
FollowGear^.Pos:= posCaseHealth;
// health crate is smaller than the other crates
FollowGear^.Radius := cCaseHealthRadius;
- AddCaption(GetEventString(eidNewHealthPack), cWhiteColor, capgrpAmmoInfo);
+ AddCaption(GetEventString(eidNewHealthPack), capcolDefault, capgrpAmmoInfo);
end
else if (t<a+h) then
begin
@@ -1538,7 +1662,7 @@
i:= Low(TAmmoType);
FollowGear^.Pos:= posCaseAmmo;
FollowGear^.AmmoType:= i;
- AddCaption(GetEventString(eidNewAmmoPack), cWhiteColor, capgrpAmmoInfo);
+ AddCaption(GetEventString(eidNewAmmoPack), capcolDefault, capgrpAmmoInfo);
end
end
else
@@ -1551,7 +1675,7 @@
i:= Low(TAmmoType);
FollowGear^.Pos:= posCaseUtility;
FollowGear^.AmmoType:= i;
- AddCaption(GetEventString(eidNewUtilityPack), cWhiteColor, capgrpAmmoInfo);
+ AddCaption(GetEventString(eidNewUtilityPack), capcolDefault, capgrpAmmoInfo);
end
end;
@@ -1559,10 +1683,51 @@
if (FollowGear <> nil) then
begin
FindPlace(FollowGear, true, 0, LAND_WIDTH);
+ PlayBoxSpawnTaunt(FollowGear);
+ SpawnBoxOfSmth:= FollowGear;
+ end
+end;
- if (FollowGear <> nil) then
- AddVoice(sndReinforce, CurrentTeam^.voicepack)
- end
+procedure PlayBoxSpawnTaunt(Gear: PGear);
+const
+ // Max. distance between hog and crate for sndThisOneIsMine taunt
+ ThisOneIsMineDistance : LongInt = 130;
+var d, minD: LongInt;
+ gi, closestHog: PGear;
+begin
+ // Taunt
+ if (Gear <> nil) then
+ begin
+ // Look for hog closest to the crate (on the X axis)
+ gi := GearsList;
+ minD := LAND_WIDTH + ThisOneIsMineDistance + 1;
+ closestHog:= nil;
+ while gi <> nil do
+ begin
+ if (gi^.Kind = gtHedgehog) then
+ begin
+ // Y axis is ignored to simplify calculations
+ d := hwRound(hwAbs(gi^.X - Gear^.X));
+ if d < minD then
+ begin
+ minD := d;
+ closestHog:= gi;
+ end;
+ end;
+ gi := gi^.NextGear;
+ end;
+
+ // Is closest hog close enough to the crate (on the X axis)?
+ if (closestHog <> nil) and (closestHog^.Hedgehog <> nil) and (minD <= ThisOneIsMineDistance) then
+ // If so, there's a chance for a special taunt
+ if random(3) > 0 then
+ AddVoice(sndThisOneIsMine, closestHog^.Hedgehog^.Team^.voicepack)
+ else
+ AddVoice(sndReinforce, CurrentTeam^.voicepack)
+ else
+ // Default crate drop taunt
+ AddVoice(sndReinforce, CurrentTeam^.voicepack);
+ end;
end;
@@ -1629,74 +1794,117 @@
Trying to make the checks a little broader than on first pass to catch things that don't move normally.
*)
function WorldWrap(var Gear: PGear): boolean;
-//var tdx: hwFloat;
+var bounced: boolean;
begin
WorldWrap:= false;
if WorldEdge = weNone then exit(false);
-if (hwRound(Gear^.X) < LongInt(leftX)) or
- (hwRound(Gear^.X) > LongInt(rightX)) then
+if (hwRound(Gear^.X) < leftX) or
+ (hwRound(Gear^.X) > rightX) then
begin
if WorldEdge = weWrap then
begin
- if (hwRound(Gear^.X) < LongInt(leftX)) then
+ if (hwRound(Gear^.X) < leftX) then
Gear^.X:= Gear^.X + int2hwfloat(rightX - leftX)
else Gear^.X:= Gear^.X - int2hwfloat(rightX - leftX);
LeftImpactTimer:= 150;
- RightImpactTimer:= 150
+ RightImpactTimer:= 150;
+ WorldWrap:= true;
end
else if WorldEdge = weBounce then
begin
- if (hwRound(Gear^.X) - Gear^.Radius < LongInt(leftX)) then
+ bounced:= false;
+ // Bounce left
+ if (hwRound(Gear^.X) - Gear^.Radius < leftX) and (((hwSign(Gear^.dX) = -1) and (not isZero(Gear^.dX))) or (Gear^.Kind = gtHedgehog)) then
begin
LeftImpactTimer:= 333;
+ // Set X coordinate to bounce edge, unless the gear spawned inside the bounce edge before
+ if (Gear^.State and gstInBounceEdge) = 0 then
+ Gear^.X:= int2hwfloat(leftX + Gear^.Radius);
+ // Invert horizontal speed
Gear^.dX.isNegative:= false;
- Gear^.X:= int2hwfloat(LongInt(leftX) + Gear^.Radius)
+ bounced:= true;
end
- else
+ // Bounce right
+ else if (hwRound(Gear^.X) + Gear^.Radius > rightX) and (((hwSign(Gear^.dX) = 1) and (not isZero(Gear^.dX))) or (Gear^.Kind = gtHedgehog)) then
begin
RightImpactTimer:= 333;
+ // Set X coordinate to bounce edge, unless the gear spawned inside the bounce edge before
+ if (Gear^.State and gstInBounceEdge) = 0 then
+ Gear^.X:= int2hwfloat(rightX - Gear^.Radius);
+ // Invert horizontal speed
Gear^.dX.isNegative:= true;
- Gear^.X:= int2hwfloat(rightX-Gear^.Radius)
+ bounced:= true;
end;
- if (Gear^.Radius > 2) and (Gear^.dX.QWordValue > _0_001.QWordValue) then
- AddBounceEffectForGear(Gear);
- end{
- else if WorldEdge = weSea then
- begin
- if (hwRound(Gear^.Y) > cWaterLine) and (Gear^.State and gstSubmersible <> 0) then
- Gear^.State:= Gear^.State and (not gstSubmersible)
- else
+ // Clear gstInBounceEdge when gear is no longer inside a bounce edge area
+ if ((Gear^.State and gstInBounceEdge) <> 0) and (hwRound(Gear^.X) - Gear^.Radius >= leftX) and (hwRound(Gear^.X) + Gear^.Radius <= rightX) then
+ Gear^.State:= Gear^.State and (not gstInBounceEdge);
+ if (bounced) then
begin
- Gear^.State:= Gear^.State or gstSubmersible;
- Gear^.X:= int2hwFloat(PlayWidth)*int2hwFloat(min(max(0,hwRound(Gear^.Y)),PlayHeight))/PlayHeight;
- Gear^.Y:= int2hwFloat(cWaterLine+cVisibleWater+Gear^.Radius*2);
- tdx:= Gear^.dX;
- Gear^.dX:= -Gear^.dY;
- Gear^.dY:= tdx;
- Gear^.dY.isNegative:= true
- end
- end};
-(*
-* Window in the sky (Gear moved high into the sky, Y is used to determine X) [unfortunately, not a safe thing to do. shame, I thought aerial bombardment would be kinda neat
-This one would be really easy to freeze game unless it was flagged unfortunately.
-
+ WorldWrap:= true;
+ if (Gear^.dX.QWordValue > _0_001.QWordValue) then
+ AddBounceEffectForGear(Gear);
+ end;
+ end
else
- begin
- Gear^.X:= int2hwFloat(PlayWidth)*int2hwFloat(min(max(0,hwRound(Gear^.Y)),PlayHeight))/PlayHeight;
- Gear^.Y:= -_2048-_256-_256;
- tdx:= Gear^.dX;
- Gear^.dX:= Gear^.dY;
- Gear^.dY:= tdx;
- Gear^.dY.isNegative:= false
- end
-*)
- WorldWrap:= true
+ WorldWrap:= true;
end;
end;
+(*
+Applies wrap-around logic for the target of homing gears.
+
+In wrap-around world edge, the shortest way may to the target might
+be across the border, so the X value of the target would lead the
+gear to the wrong direction across the whole map. This procedure
+changes the target X in this case.
+This function must be called after the gear passed through
+the wrap-around world edge (WorldWrap returned true).
+
+No-op for other world edges.
+
+Returns true if target has been changed.
+*)
+function HomingWrap(var Gear: PGear): boolean;
+var dist_center, dist_right, dist_left: hwFloat;
+begin
+ if WorldEdge = weWrap then
+ begin
+ HomingWrap:= false;
+ // We just check the same target 3 times:
+ // 1) in current section (no change)
+ // 2) clone in the right section
+ // 3) clone in the left section
+ // The gear will go for the target with the shortest distance to the gear.
+ // For simplicity, we only check distance on the X axis.
+ dist_center:= hwAbs(Gear^.X - int2hwFloat(Gear^.Target.X));
+ dist_right:= hwAbs(Gear^.X - int2hwFloat(Gear^.Target.X + (RightX-LeftX)));
+ dist_left:= hwAbs(Gear^.X - int2hwFloat(Gear^.Target.X - (RightX-LeftX)));
+ if (dist_left < dist_right) and (dist_left < dist_center) then
+ begin
+ dec(Gear^.Target.X, RightX-LeftX);
+ HomingWrap:= true;
+ end
+ else if (dist_right < dist_left) and (dist_right < dist_center) then
+ begin
+ inc(Gear^.Target.X, RightX-LeftX);
+ HomingWrap:= true;
+ end;
+ end;
+end;
+
+// Add an audiovisual bounce effect for gear after it bounced from bouncy material.
+// Graphical effect is based on speed.
procedure AddBounceEffectForGear(Gear: PGear);
+begin
+ AddBounceEffectForGear(Gear, hwFloat2Float(Gear^.Density * hwAbs(Gear^.dY) + hwAbs(Gear^.dX)) / 1.5);
+end;
+
+// Same as above, but can specify the size of bounce image with imageScale manually.
+procedure AddBounceEffectForGear(Gear: PGear; imageScale: Single);
var boing: PVisualGear;
begin
+ if (Gear^.Density < _0_01) or (Gear^.Radius < 2) then
+ exit;
boing:= AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtStraightShot, 0, false, 1);
if boing <> nil then
with boing^ do
@@ -1705,18 +1913,27 @@
dx:= 0;
dy:= 0;
FrameTicks:= 200;
- Scale:= hwFloat2Float(Gear^.Density * hwAbs(Gear^.dY) + hwAbs(Gear^.dX)) / 1.5;
+ Scale:= imageScale;
State:= ord(sprBoing)
end;
- if Gear^.Kind = gtDuck then
- PlaySound(sndDuckDrop, true)
+ PlaySound(sndMelonImpact, true)
+end;
+
+function IsHogFacingLeft(Gear: PGear): boolean;
+var sign: LongInt;
+begin
+ sign:= hwSign(Gear^.dX);
+ if (CurAmmoGear <> nil) and (CurAmmoGear^.Kind = gtParachute) then
+ IsHogFacingLeft:= CurAmmoGear^.Tag = -1
+ else if ((Gear^.State and gstHHHJump) <> 0) and (Gear^.Hedgehog^.Effects[heArtillery] = 0) then
+ IsHogFacingLeft:= sign > 0
else
- PlaySound(sndMelonImpact, true)
+ IsHogFacingLeft:= sign < 0;
end;
function IsHogLocal(HH: PHedgehog): boolean;
begin
- IsHogLocal:= (not (HH^.Team^.ExtDriven or (HH^.BotLevel > 0))) or (HH^.Team^.Clan^.ClanIndex = LocalClan) or (GameType = gmtDemo);
+ IsHogLocal:= (not (HH^.Team^.ExtDriven or (HH^.BotLevel > 0))) or (HH^.Team^.Clan^.LocalOrAlly) or (GameType = gmtDemo);
end;
end.