(* * Hedgewars, a free turn based strategy game * Copyright (c) 2004-2010 Andrey Korotaev * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA *) procedure makeHogsWorry(x, y: hwFloat; r: LongInt); var gi: PGear; d: LongInt; begin gi:= GearsList; while gi <> nil do begin if (gi^.Kind = gtHedgehog) then begin d:= r - hwRound(Distance(gi^.X - x, gi^.Y - y)); if (d > 1) and not gi^.Invulnerable and (GetRandom(2) = 0) then begin if (CurrentHedgehog^.Gear = gi) then PlaySound(sndOops, PHedgehog(gi^.Hedgehog)^.Team^.voicepack) else begin if (gi^.State and gstMoving) = 0 then gi^.State:= gi^.State or gstLoser; if d > r div 2 then PlaySound(sndNooo, PHedgehog(gi^.Hedgehog)^.Team^.voicepack) else PlaySound(sndUhOh, PHedgehog(gi^.Hedgehog)^.Team^.voicepack); end; end; end; gi:= gi^.NextGear end; end; //////////////////////////////////////////////////////////////////////////////// procedure doStepDrowningGear(Gear: PGear); forward; function CheckGearDrowning(Gear: PGear): boolean; var skipSpeed, skipAngle, skipDecay: hwFloat; i, maxDrops: LongInt; particle: PVisualGear; begin // probably needs tweaking. might need to be in a case statement based upon gear type if cWaterLine < hwRound(Gear^.Y) + Gear^.Radius then begin skipSpeed:= _0_25; skipAngle:= _1_9; skipDecay:= _0_87; // this could perhaps be a tiny bit higher. if (hwSqr(Gear^.dX) + hwSqr(Gear^.dY) > skipSpeed) and (hwAbs(Gear^.dX) > skipAngle * hwAbs(Gear^.dY)) then begin Gear^.dY.isNegative:= true; Gear^.dY:= Gear^.dY * skipDecay; Gear^.dX:= Gear^.dX * skipDecay; CheckGearDrowning:= false; PlaySound(sndSkip) end else begin CheckGearDrowning:= true; Gear^.State:= gstDrowning; Gear^.RenderTimer:= false; if (Gear^.Kind <> gtSniperRifleShot) and (Gear^.Kind <> gtShotgunShot) and (Gear^.Kind <> gtDEagleShot) and (Gear^.Kind <> gtSineGunShot) then Gear^.doStep:= @doStepDrowningGear; if Gear^.Kind = gtHedgehog then begin Gear^.State:= Gear^.State and (not gstHHDriven); AddCaption(Format(GetEventString(eidDrowned), PHedgehog(Gear^.Hedgehog)^.Name), cWhiteColor, capgrpMessage); end; if hwRound(Gear^.Y) < cWaterLine + 64 + Gear^.Radius then // don't play splash if they are already way past the surface PlaySound(sndSplash) end; if not cReducedQuality and (hwRound(Gear^.Y) < cWaterLine + 64 + Gear^.Radius) then begin AddVisualGear(hwRound(Gear^.X), cWaterLine, vgtSplash); maxDrops := (Gear^.Radius div 2) + hwRound(Gear^.dX * Gear^.Radius * 2) + hwRound(Gear^.dY * Gear^.Radius * 2); for i:= max(maxDrops div 3, min(32, Random(maxDrops))) downto 0 do begin particle := AddVisualGear(hwRound(Gear^.X) - 3 + Random(6), cWaterLine, vgtDroplet); if particle <> nil then begin particle^.dX := particle^.dX - (Gear^.dX / 10); particle^.dY := particle^.dY - (Gear^.dY / 5) end end end; end else CheckGearDrowning:= false end; procedure CheckCollision(Gear: PGear); begin if TestCollisionXwithGear(Gear, hwSign(Gear^.X)) or TestCollisionYwithGear(Gear, hwSign(Gear^.Y)) then Gear^.State:= Gear^.State or gstCollision else Gear^.State:= Gear^.State and not gstCollision end; procedure CheckHHDamage(Gear: PGear); var dmg: Longword; i: LongInt; particle: PVisualGear; begin if _0_4 < Gear^.dY then begin dmg:= ModifyDamage(1 + hwRound((hwAbs(Gear^.dY) - _0_4) * 70), Gear); if dmg < 1 then exit; for i:= min(12, (3 + dmg div 10)) downto 0 do begin particle := AddVisualGear(hwRound(Gear^.X) - 5 + Random(10), hwRound(Gear^.Y) + 12, vgtDust); if particle <> nil then particle^.dX := particle^.dX + (Gear^.dX / 5); end; if(Gear^.Invulnerable) then exit; if _0_6 < Gear^.dY then PlaySound(sndOw4, PHedgehog(Gear^.Hedgehog)^.Team^.voicepack) else PlaySound(sndOw1, PHedgehog(Gear^.Hedgehog)^.Team^.voicepack); ApplyDamage(Gear, dmg); end end; //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// procedure CalcRotationDirAngle(Gear: PGear); var dAngle: real; begin dAngle:= (Gear^.dX.QWordValue + Gear^.dY.QWordValue) / $80000000; if not Gear^.dX.isNegative then Gear^.DirAngle:= Gear^.DirAngle + dAngle else Gear^.DirAngle:= Gear^.DirAngle - dAngle; if Gear^.DirAngle < 0 then Gear^.DirAngle:= Gear^.DirAngle + 360 else if 360 < Gear^.DirAngle then Gear^.DirAngle:= Gear^.DirAngle - 360 end; //////////////////////////////////////////////////////////////////////////////// procedure doStepDrowningGear(Gear: PGear); begin AllInactive:= false; Gear^.Y:= Gear^.Y + cDrownSpeed; Gear^.X:= Gear^.X + Gear^.dX * cDrownSpeed; if (cWaterOpacity > $FE) or (hwRound(Gear^.Y) > Gear^.Radius + cWaterLine + cVisibleWater) then DeleteGear(Gear); // Create some bubbles (0.5% might be better but causes too few bubbles sometimes) if (cWaterOpacity < $FF) and ((GameTicks and $1F) = 0) then if (Gear^.Kind = gtHedgehog) and (Random(4) = 0) then AddVisualGear(hwRound(Gear^.X) - Gear^.Radius, hwRound(Gear^.Y) - Gear^.Radius, vgtBubble) else if Random(12) = 0 then AddVisualGear(hwRound(Gear^.X) - Gear^.Radius, hwRound(Gear^.Y) - Gear^.Radius, vgtBubble) end; //////////////////////////////////////////////////////////////////////////////// procedure doStepFallingGear(Gear: PGear); var isFalling: boolean; //tmp: QWord; tdX, tdY: hwFloat; collV, collH: LongInt; begin if Gear^.dX > _0_995 then Gear^.dX:= _0_995; if Gear^.dY > _0_995 then Gear^.dY:= _0_995; Gear^.State:= Gear^.State and not gstCollision; collV:= 0; collH:= 0; tdX:= Gear^.dX; tdY:= Gear^.dY; // might need some testing/adjustments - just to avoid projectiles to fly forever (accelerated by wind/skips) if (hwRound(Gear^.X) < LAND_WIDTH div -2) or (hwRound(Gear^.X) > LAND_WIDTH * 3 div 2) then begin Gear^.State:= Gear^.State or gstCollision; exit end; if Gear^.dY.isNegative then begin isFalling:= true; if TestCollisionYwithGear(Gear, -1) then begin collV:= -1; Gear^.dX:= Gear^.dX * Gear^.Friction; Gear^.dY:= - Gear^.dY * Gear^.Elasticity; Gear^.State:= Gear^.State or gstCollision end else if (Gear^.AdvBounce=1) and TestCollisionYwithGear(Gear, 1) then collV:= 1; end else if TestCollisionYwithGear(Gear, 1) then begin collV:= 1; isFalling:= false; Gear^.dX:= Gear^.dX * Gear^.Friction; Gear^.dY:= - Gear^.dY * Gear^.Elasticity; Gear^.State:= Gear^.State or gstCollision end else begin isFalling:= true; if (Gear^.AdvBounce=1) and not Gear^.dY.isNegative and TestCollisionYwithGear(Gear, -1) then collV:= -1; end; if TestCollisionXwithGear(Gear, hwSign(Gear^.dX)) then begin collH:= hwSign(Gear^.dX); Gear^.dX:= - Gear^.dX * Gear^.Elasticity; Gear^.dY:= Gear^.dY * Gear^.Elasticity; Gear^.State:= Gear^.State or gstCollision end else if (Gear^.AdvBounce=1) and TestCollisionXwithGear(Gear, -hwSign(Gear^.dX)) then collH:= -hwSign(Gear^.dX); //if Gear^.AdvBounce and (collV <>0) and (collH <> 0) and (hwSqr(tdX) + hwSqr(tdY) > _0_08) then if (Gear^.AdvBounce=1) and (collV <>0) and (collH <> 0) and ((collV=-1) or ((tdX.QWordValue + tdY.QWordValue) > _0_2.QWordValue)) then begin Gear^.dX:= tdY*Gear^.Elasticity*Gear^.Friction; Gear^.dY:= tdX*Gear^.Elasticity;//*Gear^.Friction; Gear^.dY.isNegative:= not tdY.isNegative; isFalling:= false; Gear^.AdvBounce:= 10; end; if Gear^.AdvBounce > 1 then dec(Gear^.AdvBounce); if isFalling then Gear^.dY:= Gear^.dY + cGravity; Gear^.X:= Gear^.X + Gear^.dX; Gear^.Y:= Gear^.Y + Gear^.dY; CheckGearDrowning(Gear); //if (hwSqr(Gear^.dX) + hwSqr(Gear^.dY) < _0_0002) and if ((Gear^.dX.QWordValue + Gear^.dY.QWordValue) < _0_02.QWordValue) and (not isFalling) then Gear^.State:= Gear^.State and not gstMoving else Gear^.State:= Gear^.State or gstMoving; if (Gear^.nImpactSounds > 0) then if ((Gear^.Damage <> 0) or ((Gear^.State and (gstCollision or gstMoving)) = (gstCollision or gstMoving))) and ((Gear^.dX.QWordValue > _0_1.QWordValue) or (Gear^.dY.QWordValue > _0_1.QWordValue)) then PlaySound(TSound(ord(Gear^.ImpactSound) + LongInt(GetRandom(Gear^.nImpactSounds))), true); end; //////////////////////////////////////////////////////////////////////////////// procedure doStepBomb(Gear: PGear); var i, x, y: LongInt; dX, dY: hwFloat; Fire: PGear; begin AllInactive:= false; doStepFallingGear(Gear); dec(Gear^.Timer); if Gear^.Timer = 1000 then // might need adjustments case Gear^.Kind of gtAmmo_Bomb: makeHogsWorry(Gear^.X, Gear^.Y, 50); gtClusterBomb: makeHogsWorry(Gear^.X, Gear^.Y, 20); gtWatermelon: makeHogsWorry(Gear^.X, Gear^.Y, 75); gtHellishBomb: makeHogsWorry(Gear^.X, Gear^.Y, 90); gtGasBomb: makeHogsWorry(Gear^.X, Gear^.Y, 50); end; if (Gear^.Kind = gtBall) and ((Gear^.State and gstTmpFlag) <> 0) then begin CheckCollision(Gear); if (Gear^.State and gstCollision) <> 0 then doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 20, EXPLDontDraw or EXPLNoGfx); end; if Gear^.Timer = 0 then begin case Gear^.Kind of gtAmmo_Bomb: doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 50, EXPLAutoSound); gtBall: doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 40, EXPLAutoSound); gtClusterBomb: begin x:= hwRound(Gear^.X); y:= hwRound(Gear^.Y); doMakeExplosion(x, y, 20, EXPLAutoSound); for i:= 0 to 4 do begin dX:= rndSign(GetRandom * _0_1); dY:= (GetRandom - _3) * _0_08; AddGear(x, y, gtCluster, 0, dX, dY, 25); end end; gtWatermelon: begin x:= hwRound(Gear^.X); y:= hwRound(Gear^.Y); doMakeExplosion(x, y, 75, EXPLAutoSound); for i:= 0 to 5 do begin dX:= rndSign(GetRandom * _0_1); dY:= (GetRandom - _1_5) * _0_3; AddGear(x, y, gtMelonPiece, 0, dX, dY, 75)^.DirAngle:= i * 60; end end; gtHellishBomb: begin x:= hwRound(Gear^.X); y:= hwRound(Gear^.Y); doMakeExplosion(x, y, 90, EXPLAutoSound); for i:= 0 to 127 do begin dX:= AngleCos(i * 16) * _0_5 * (GetRandom + _1); dY:= AngleSin(i * 16) * _0_5 * (GetRandom + _1); Fire:= AddGear(x, y, gtFlame, 0, dX, dY, 0); if i mod 2 = 0 then Fire^.State:= Fire^.State or gsttmpFlag; Fire:= AddGear(x, y, gtFlame, 0, dX, -dY, 0); if i mod 2 <> 0 then Fire^.State:= Fire^.State or gsttmpFlag; end end; gtGasBomb: doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 50, EXPLAutoSound or EXPLPoisoned); end; DeleteGear(Gear); exit end; CalcRotationDirAngle(Gear); if Gear^.Kind = gtHellishBomb then begin if Gear^.Timer = 3000 then begin Gear^.nImpactSounds:= 0; PlaySound(sndHellish); end; if (GameTicks and $3F) = 0 then if (Gear^.State and gstCollision) = 0 then AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtEvilTrace, 0, _0, _0, 0); end; end; //////////////////////////////////////////////////////////////////////////////// procedure doStepMolotov(Gear: PGear); var i, gX, gY: LongInt; dX, dY: hwFloat; Fire: PGear; begin AllInactive:= false; doStepFallingGear(Gear); CalcRotationDirAngle(Gear); if (Gear^.State and gstCollision) <> 0 then begin PlaySound(sndMolotov); gX:= hwRound(Gear^.X); gY:= hwRound(Gear^.Y); //doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 5, EXPLAutoSound); for i:= 0 to 20 do begin dX:= AngleCos(i * 2) * ((_0_1*(i div 5))) * (GetRandom + _1); dY:= AngleSin(i * 8) * _0_5 * (GetRandom + _1); Fire:= AddGear(gX, gY, gtFlame, 0, dX, dY, 0); Fire^.State:= Fire^.State or gsttmpFlag; Fire:= AddGear(gX, gY, gtFlame, 0, dX, -dY, 0); Fire^.State:= Fire^.State or gsttmpFlag; Fire:= AddGear(gX, gY, gtFlame, 0, -dX, dY, 0); Fire^.State:= Fire^.State or gsttmpFlag; Fire:= AddGear(gX, gY, gtFlame, 0, -dX, -dY, 0); Fire^.State:= Fire^.State or gsttmpFlag; end; DeleteGear(Gear); exit end; end; procedure doStepWatermelon(Gear: PGear); begin AllInactive:= false; Gear^.doStep:= @doStepBomb end; procedure doStepCluster(Gear: PGear); begin AllInactive:= false; doStepFallingGear(Gear); if (Gear^.State and gstCollision) <> 0 then begin doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), Gear^.Timer, EXPLAutoSound); DeleteGear(Gear); exit end; if (Gear^.Kind = gtMelonPiece) or (Gear^.Kind = gtBall) then CalcRotationDirAngle(Gear) else if (GameTicks and $1F) = 0 then AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtSmokeTrace, 0, _0, _0, 0) end; //////////////////////////////////////////////////////////////////////////////// procedure doStepGrenade(Gear: PGear); begin AllInactive:= false; Gear^.dX:= Gear^.dX + cWindSpeed; doStepFallingGear(Gear); if (Gear^.State and gstCollision) <> 0 then begin doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 50, EXPLAutoSound); DeleteGear(Gear); exit end; if (GameTicks and $3F) = 0 then AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtSmokeTrace, 0, _0, _0, 0) end; //////////////////////////////////////////////////////////////////////////////// procedure doStepHealthTagWork(Gear: PGear); begin if Gear^.Kind = gtHealthTag then AllInactive:= false; dec(Gear^.Timer); Gear^.Y:= Gear^.Y + Gear^.dY; if Gear^.Timer = 0 then begin if (Gear^.Kind = gtHealthTag) and (PHedgehog(Gear^.Hedgehog)^.Gear <> nil) then PHedgehog(Gear^.Hedgehog)^.Gear^.Active:= true; // to let current hh die DeleteGear(Gear) end end; procedure doStepHealthTagWorkUnderWater(Gear: PGear); begin AllInactive:= false; Gear^.Y:= Gear^.Y - _0_08; if hwRound(Gear^.Y) < cWaterLine + 10 then DeleteGear(Gear) end; procedure doStepHealthTag(Gear: PGear); var s: shortstring; begin AllInactive:= false; Gear^.dY:= -_0_08; str(Gear^.State, s); Gear^.Tex:= RenderStringTex(s, PHedgehog(Gear^.Hedgehog)^.Team^.Clan^.Color, fnt16); if hwRound(Gear^.Y) < cWaterLine then Gear^.doStep:= @doStepHealthTagWork else Gear^.doStep:= @doStepHealthTagWorkUnderWater; Gear^.Y:= Gear^.Y - int2hwFloat(Gear^.Tex^.h) end; //////////////////////////////////////////////////////////////////////////////// procedure doStepGrave(Gear: PGear); begin AllInactive:= false; if Gear^.dY.isNegative then if TestCollisionY(Gear, -1) then Gear^.dY:= _0; if not Gear^.dY.isNegative then if TestCollisionY(Gear, 1) then begin Gear^.dY:= - Gear^.dY * Gear^.Elasticity; if Gear^.dY > - _1div1024 then begin Gear^.Active:= false; exit end else if Gear^.dY < - _0_03 then PlaySound(Gear^.ImpactSound) end; Gear^.Y:= Gear^.Y + Gear^.dY; CheckGearDrowning(Gear); Gear^.dY:= Gear^.dY + cGravity end; //////////////////////////////////////////////////////////////////////////////// procedure doStepBeeWork(Gear: PGear); var t: hwFloat; gX,gY: LongInt; nuw: boolean; const uw: boolean = false; begin AllInactive:= false; gX:= hwRound(Gear^.X); gY:= hwRound(Gear^.Y); nuw:= (cWaterLine < hwRound(Gear^.Y) + Gear^.Radius); if nuw and not uw then begin AddVisualGear(gX, cWaterLine, vgtSplash); AddVisualGear(gX - 3 + Random(6), cWaterLine, vgtDroplet); AddVisualGear(gX - 3 + Random(6), cWaterLine, vgtDroplet); AddVisualGear(gX - 3 + Random(6), cWaterLine, vgtDroplet); AddVisualGear(gX - 3 + Random(6), cWaterLine, vgtDroplet); StopSound(Gear^.SoundChannel); Gear^.SoundChannel:= LoopSound(sndBeeWater); uw:= nuw end else if not nuw and uw then begin AddVisualGear(gX, cWaterLine, vgtSplash); StopSound(Gear^.SoundChannel); Gear^.SoundChannel:= LoopSound(sndBee); uw:= nuw end; t:= Distance(Gear^.dX, Gear^.dY); Gear^.dX:= Gear^.Elasticity * (Gear^.dX + _0_000004 * (TargetPoint.X - gX)); Gear^.dY:= Gear^.Elasticity * (Gear^.dY + _0_000004 * (TargetPoint.Y - gY)); t:= t / Distance(Gear^.dX, Gear^.dY); Gear^.dX:= Gear^.dX * t; Gear^.dY:= Gear^.dY * t; Gear^.X:= Gear^.X + Gear^.dX; Gear^.Y:= Gear^.Y + Gear^.dY; if (GameTicks and $3F) = 0 then begin AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtBeeTrace); end; CheckCollision(Gear); dec(Gear^.Timer); if ((Gear^.State and gstCollision) <> 0) or (Gear^.Timer = 0) then begin StopSound(Gear^.SoundChannel); doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 50, EXPLAutoSound); DeleteGear(Gear); end; end; procedure doStepBee(Gear: PGear); begin AllInactive:= false; Gear^.X:= Gear^.X + Gear^.dX; Gear^.Y:= Gear^.Y + Gear^.dY; Gear^.dY:= Gear^.dY + cGravity; CheckCollision(Gear); if (Gear^.State and gstCollision) <> 0 then begin doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 50, EXPLAutoSound); DeleteGear(Gear); exit end; dec(Gear^.Timer); if Gear^.Timer = 0 then begin Gear^.SoundChannel:= LoopSound(sndBee); Gear^.Timer:= 5000; Gear^.doStep:= @doStepBeeWork end; end; //////////////////////////////////////////////////////////////////////////////// procedure doStepShotIdle(Gear: PGear); begin AllInactive:= false; inc(Gear^.Timer); if Gear^.Timer > 75 then begin DeleteGear(Gear); AfterAttack end end; procedure doStepShotgunShot(Gear: PGear); var i: LongWord; shell: PVisualGear; begin AllInactive:= false; if ((Gear^.State and gstAnimation) = 0) then begin dec(Gear^.Timer); if Gear^.Timer = 0 then begin PlaySound(sndShotgunFire); shell:= AddVisualGear(hwRound(Gear^.x), hwRound(Gear^.y), vgtShell); if shell <> nil then begin shell^.dX:= gear^.dX / -4; shell^.dY:= gear^.dY / -4; shell^.Frame:= 0 end; Gear^.State:= Gear^.State or gstAnimation end; exit end else inc(Gear^.Timer); i:= 200; repeat Gear^.X:= Gear^.X + Gear^.dX; Gear^.Y:= Gear^.Y + Gear^.dY; CheckCollision(Gear); if (Gear^.State and gstCollision) <> 0 then begin Gear^.X:= Gear^.X + Gear^.dX * 8; Gear^.Y:= Gear^.Y + Gear^.dY * 8; ShotgunShot(Gear); Gear^.doStep:= @doStepShotIdle; exit end; CheckGearDrowning(Gear); if (Gear^.State and gstDrowning) <> 0 then begin Gear^.doStep:= @doStepShotIdle; exit end; dec(i) until i = 0; if (hwRound(Gear^.X) and LAND_WIDTH_MASK <> 0) or (hwRound(Gear^.Y) and LAND_HEIGHT_MASK <> 0) then Gear^.doStep:= @doStepShotIdle end; //////////////////////////////////////////////////////////////////////////////// procedure doStepBulletWork(Gear: PGear); var i, x, y: LongWord; oX, oY: hwFloat; begin AllInactive:= false; inc(Gear^.Timer); i:= 80; oX:= Gear^.X; oY:= Gear^.Y; repeat Gear^.X:= Gear^.X + Gear^.dX; Gear^.Y:= Gear^.Y + Gear^.dY; x:= hwRound(Gear^.X); y:= hwRound(Gear^.Y); if ((y and LAND_HEIGHT_MASK) = 0) and ((x and LAND_WIDTH_MASK) = 0) and (Land[y, x] <> 0) then inc(Gear^.Damage); if Gear^.Damage > 5 then if Gear^.Ammo^.AmmoType = amDEagle then AmmoShove(Gear, 7, 20) else AmmoShove(Gear, Gear^.Timer, 20); CheckGearDrowning(Gear); dec(i) until (i = 0) or (Gear^.Damage > Gear^.Health) or ((Gear^.State and gstDrowning) <> 0); if Gear^.Damage > 0 then begin DrawTunnel(oX, oY, Gear^.dX, Gear^.dY, 82 - i, 1); dec(Gear^.Health, Gear^.Damage); Gear^.Damage:= 0 end; if ((Gear^.State and gstDrowning) <> 0) and (Gear^.Damage < Gear^.Health) and (cWaterOpacity < $FF) then begin for i:=(Gear^.Health - Gear^.Damage) * 4 downto 0 do begin if Random(6) = 0 then AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtBubble); Gear^.X:= Gear^.X + Gear^.dX; Gear^.Y:= Gear^.Y + Gear^.dY; end; end; if (Gear^.Health <= 0) or (hwRound(Gear^.X) and LAND_WIDTH_MASK <> 0) or (hwRound(Gear^.Y) and LAND_HEIGHT_MASK <> 0) then begin if (Gear^.Kind = gtSniperRifleShot) and ((GameFlags and gfLaserSight) = 0) then cLaserSighting:= false; if (Gear^.Ammo^.NumPerTurn <= CurrentHedgehog^.MultiShootAttacks) and ((GameFlags and gfArtillery) = 0) then cArtillery:= false; Gear^.doStep:= @doStepShotIdle end; end; procedure doStepDEagleShot(Gear: PGear); begin PlaySound(sndGun); Gear^.doStep:= @doStepBulletWork end; procedure doStepSniperRifleShot(Gear: PGear); var HHGear: PGear; shell: PVisualGear; begin cArtillery:= true; HHGear:=PHedgehog(Gear^.Hedgehog)^.Gear; HHGear^.State:= HHGear^.State or gstNotKickable; HedgehogChAngle(HHGear); if not cLaserSighting then // game does not have default laser sight. turn it on and give them a chance to aim begin cLaserSighting:= true; HHGear^.Message:= 0; if(HHGear^.Angle - 32 >= 0) then dec(HHGear^.Angle,32) end; if (HHGear^.Message and gm_Attack) <> 0 then begin shell:= AddVisualGear(hwRound(Gear^.x), hwRound(Gear^.y), vgtShell); if shell <> nil then begin shell^.dX:= gear^.dX / -2; shell^.dY:= gear^.dY / -2; shell^.Frame:= 1 end; Gear^.State:= Gear^.State or gstAnimation; Gear^.dX:= SignAs(AngleSin(HHGear^.Angle), HHGear^.dX) * _0_5; Gear^.dY:= -AngleCos(HHGear^.Angle) * _0_5; PlaySound(sndGun); Gear^.doStep:= @doStepBulletWork; end else if (GameTicks mod 32) = 0 then if (GameTicks mod 4096) < 2048 then begin if(HHGear^.Angle + 1 <= cMaxAngle) then inc(HHGear^.Angle) end else if(HHGear^.Angle - 1 >= 0) then dec(HHGear^.Angle); if (TurnTimeLeft > 0) then dec(TurnTimeLeft) else begin DeleteGear(Gear); AfterAttack end; end; //////////////////////////////////////////////////////////////////////////////// procedure doStepActionTimer(Gear: PGear); begin dec(Gear^.Timer); case Gear^.Kind of gtATStartGame: begin AllInactive:= false; if Gear^.Timer = 0 then begin AddCaption(trmsg[sidStartFight], cWhiteColor, capgrpGameState); end end; gtATSmoothWindCh: begin if Gear^.Timer = 0 then begin if WindBarWidth < Gear^.Tag then inc(WindBarWidth) else if WindBarWidth > Gear^.Tag then dec(WindBarWidth); if WindBarWidth <> Gear^.Tag then Gear^.Timer:= 10; end end; gtATFinishGame: begin AllInactive:= false; if Gear^.Timer = 1000 then begin ScreenFade:= sfToBlack; ScreenFadeValue:= 0; ScreenFadeSpeed:= 1; end; if Gear^.Timer = 0 then begin SendIPC('N'); SendIPC('q'); GameState:= gsExit end end; end; if Gear^.Timer = 0 then DeleteGear(Gear) end; //////////////////////////////////////////////////////////////////////////////// procedure doStepPickHammerWork(Gear: PGear); var i, ei: LongInt; HHGear: PGear; begin AllInactive:= false; HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear; dec(Gear^.Timer); if (Gear^.Timer = 0)or((Gear^.Message and gm_Destroy) <> 0)or((HHGear^.State and gstHHDriven) = 0) then begin StopSound(Gear^.SoundChannel); DeleteGear(Gear); AfterAttack; exit end; if (Gear^.Timer mod 33) = 0 then begin HHGear^.State:= HHGear^.State or gstNoDamage; doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y) + 7, 6, EXPLDontDraw); HHGear^.State:= HHGear^.State and not gstNoDamage end; if (Gear^.Timer mod 47) = 0 then begin i:= hwRound(Gear^.X) - Gear^.Radius - LongInt(GetRandom(2)); ei:= hwRound(Gear^.X) + Gear^.Radius + LongInt(GetRandom(2)); while i <= ei do begin DrawExplosion(i, hwRound(Gear^.Y) + 3, 3); inc(i, 1) end; if CheckLandValue(hwRound(Gear^.X + Gear^.dX + SignAs(_6,Gear^.dX)), hwRound(Gear^.Y + _1_9), COLOR_INDESTRUCTIBLE) then begin Gear^.X:= Gear^.X + Gear^.dX; Gear^.Y:= Gear^.Y + _1_9; end; SetAllHHToActive; end; if TestCollisionYwithGear(Gear, 1) then begin Gear^.dY:= _0; SetLittle(HHGear^.dX); HHGear^.dY:= _0; end else begin Gear^.dY:= Gear^.dY + cGravity; Gear^.Y:= Gear^.Y + Gear^.dY; if hwRound(Gear^.Y) > cWaterLine then Gear^.Timer:= 1 end; Gear^.X:= Gear^.X + HHGear^.dX; HHGear^.X:= Gear^.X; HHGear^.Y:= Gear^.Y - int2hwFloat(cHHRadius); if (Gear^.Message and gm_Attack) <> 0 then if (Gear^.State and gsttmpFlag) <> 0 then Gear^.Timer:= 1 else else if (Gear^.State and gsttmpFlag) = 0 then Gear^.State:= Gear^.State or gsttmpFlag; if ((Gear^.Message and gm_Left) <> 0) then Gear^.dX:= - _0_3 else if ((Gear^.Message and gm_Right) <> 0) then Gear^.dX:= _0_3 else Gear^.dX:= _0; end; procedure doStepPickHammer(Gear: PGear); var i, y: LongInt; ar: TRangeArray; HHGear: PGear; begin i:= 0; HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear; y:= hwRound(Gear^.Y) - cHHRadius * 2; while y < hwRound(Gear^.Y) do begin ar[i].Left := hwRound(Gear^.X) - Gear^.Radius - LongInt(GetRandom(2)); ar[i].Right:= hwRound(Gear^.X) + Gear^.Radius + LongInt(GetRandom(2)); inc(y, 2); inc(i) end; DrawHLinesExplosions(@ar, 3, hwRound(Gear^.Y) - cHHRadius * 2, 2, Pred(i)); Gear^.dY:= HHGear^.dY; DeleteCI(HHGear); Gear^.SoundChannel:= LoopSound(sndPickhammer); doStepPickHammerWork(Gear); Gear^.doStep:= @doStepPickHammerWork end; //////////////////////////////////////////////////////////////////////////////// var BTPrevAngle, BTSteps: LongInt; procedure doStepBlowTorchWork(Gear: PGear); var HHGear: PGear; b: boolean; prevX: LongInt; begin AllInactive:= false; dec(Gear^.Timer); HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear; HedgehogChAngle(HHGear); b:= false; if abs(LongInt(HHGear^.Angle) - BTPrevAngle) > 7 then begin Gear^.dX:= SignAs(AngleSin(HHGear^.Angle) * _0_5, HHGear^.dX); Gear^.dY:= AngleCos(HHGear^.Angle) * ( - _0_5); BTPrevAngle:= HHGear^.Angle; b:= true end; if ((HHGear^.State and gstMoving) <> 0) then begin doStepHedgehogMoving(HHGear); if (HHGear^.State and gstHHDriven) = 0 then Gear^.Timer:= 0 end; if Gear^.Timer mod cHHStepTicks = 0 then begin b:= true; if Gear^.dX.isNegative then HHGear^.Message:= (HHGear^.Message and (gm_Attack or gm_Up or gm_Down)) or gm_Left else HHGear^.Message:= (HHGear^.Message and (gm_Attack or gm_Up or gm_Down)) or gm_Right; if ((HHGear^.State and gstMoving) = 0) then begin HHGear^.State:= HHGear^.State and not gstAttacking; prevX:= hwRound(HHGear^.X); // why the call to HedgehogStep then a further increment of X? if (prevX = hwRound(HHGear^.X)) and CheckLandValue(hwRound(HHGear^.X + SignAs(_6, HHGear^.dX)), hwRound(HHGear^.Y), COLOR_INDESTRUCTIBLE) then HedgehogStep(HHGear); if (prevX = hwRound(HHGear^.X)) and CheckLandValue(hwRound(HHGear^.X + SignAs(_6, HHGear^.dX)), hwRound(HHGear^.Y), COLOR_INDESTRUCTIBLE) then HHGear^.X:= HHGear^.X + SignAs(_1, HHGear^.dX); HHGear^.State:= HHGear^.State or gstAttacking end; inc(BTSteps); if BTSteps = 7 then begin BTSteps:= 0; if CheckLandValue(hwRound(HHGear^.X + Gear^.dX * (cHHRadius + cBlowTorchC) + SignAs(_6,Gear^.dX)), hwRound(HHGear^.Y + Gear^.dY * (cHHRadius + cBlowTorchC)), COLOR_INDESTRUCTIBLE) then begin Gear^.X:= HHGear^.X + Gear^.dX * (cHHRadius + cBlowTorchC); Gear^.Y:= HHGear^.Y + Gear^.dY * (cHHRadius + cBlowTorchC); end; HHGear^.State:= HHGear^.State or gstNoDamage; AmmoShove(Gear, 2, 15); HHGear^.State:= HHGear^.State and not gstNoDamage end; end; if b then DrawTunnel(HHGear^.X - Gear^.dX * cHHRadius, HHGear^.Y - _4 - Gear^.dY * cHHRadius + hwAbs(Gear^.dY) * 7, Gear^.dX, Gear^.dY, cHHRadius * 5, cHHRadius * 2 + 7); if (Gear^.Timer = 0) or ((HHGear^.Message and gm_Attack) <> 0) then begin HHGear^.Message:= 0; HHGear^.State:= HHGear^.State and (not gstNotKickable); DeleteGear(Gear); AfterAttack end end; procedure doStepBlowTorch(Gear: PGear); var HHGear: PGear; begin BTPrevAngle:= High(LongInt); BTSteps:= 0; HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear; HHGear^.Message:= 0; HHGear^.State:= HHGear^.State or gstNotKickable; Gear^.doStep:= @doStepBlowTorchWork end; //////////////////////////////////////////////////////////////////////////////// procedure doStepRope(Gear: PGear); forward; procedure doStepRopeAfterAttack(Gear: PGear); var HHGear: PGear; begin HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear; if ((HHGear^.State and gstHHDriven) = 0) or (CheckGearDrowning(HHGear)) or TestCollisionYwithGear(HHGear, 1) then begin DeleteGear(Gear); isCursorVisible:= false; ApplyAmmoChanges(PHedgehog(HHGear^.Hedgehog)^); exit end; HedgehogChAngle(HHGear); if TestCollisionXwithGear(HHGear, hwSign(HHGear^.dX)) then SetLittle(HHGear^.dX); if HHGear^.dY.isNegative and TestCollisionYwithGear(HHGear, -1) then HHGear^.dY:= _0; HHGear^.X:= HHGear^.X + HHGear^.dX; HHGear^.Y:= HHGear^.Y + HHGear^.dY; HHGear^.dY:= HHGear^.dY + cGravity; if (Gear^.Message and gm_Attack) <> 0 then begin Gear^.X:= HHGear^.X; Gear^.Y:= HHGear^.Y; ApplyAngleBounds(PHedgehog(Gear^.Hedgehog)^, amRope); Gear^.dX:= SignAs(AngleSin(HHGear^.Angle), HHGear^.dX); Gear^.dY:= -AngleCos(HHGear^.Angle); Gear^.Friction:= _450; Gear^.Elasticity:= _0; Gear^.State:= Gear^.State and not gsttmpflag; Gear^.doStep:= @doStepRope; end end; procedure doStepRopeWork(Gear: PGear); var HHGear: PGear; len, tx, ty, nx, ny, ropeDx, ropeDy, mdX, mdY: hwFloat; lx, ly: LongInt; haveCollision, haveDivided: boolean; procedure DeleteMe; begin with HHGear^ do begin Message:= Message and not gm_Attack; State:= (State or gstMoving) and not gstWinner; end; DeleteGear(Gear) end; procedure WaitCollision; begin with HHGear^ do begin Message:= Message and not gm_Attack; State:= State or gstMoving; end; RopePoints.Count:= 0; Gear^.Elasticity:= _0; Gear^.doStep:= @doStepRopeAfterAttack end; begin HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear; if ((HHGear^.State and gstHHDriven) = 0) or (CheckGearDrowning(HHGear)) then begin PlaySound(sndRopeRelease); DeleteMe; exit end; if (Gear^.Message and gm_Left <> 0) then HHGear^.dX:= HHGear^.dX - _0_0002 else if (Gear^.Message and gm_Right <> 0) then HHGear^.dX:= HHGear^.dX + _0_0002; if not TestCollisionYwithGear(HHGear, 1) then HHGear^.dY:= HHGear^.dY + cGravity; ropeDx:= HHGear^.X - Gear^.X; // vector between hedgehog and rope attaching point ropeDy:= HHGear^.Y - Gear^.Y; mdX:= ropeDx + HHGear^.dX; mdY:= ropeDy + HHGear^.dY; len:= _1 / Distance(mdX, mdY); mdX:= mdX * len; // rope vector plus hedgehog direction vector normalized mdY:= mdY * len; Gear^.dX:= mdX; // for visual purposes only Gear^.dY:= mdY; ///// tx:= HHGear^.X; ty:= HHGear^.Y; if ((Gear^.Message and gm_Down) <> 0) and (Gear^.Elasticity < Gear^.Friction) then if not (TestCollisionXwithGear(HHGear, hwSign(ropeDx)) or TestCollisionYwithGear(HHGear, hwSign(ropeDy))) then Gear^.Elasticity:= Gear^.Elasticity + _0_3; if ((Gear^.Message and gm_Up) <> 0) and (Gear^.Elasticity > _30) then if not (TestCollisionXwithGear(HHGear, -hwSign(ropeDx)) or TestCollisionYwithGear(HHGear, -hwSign(ropeDy))) then Gear^.Elasticity:= Gear^.Elasticity - _0_3; HHGear^.X:= Gear^.X + mdX * Gear^.Elasticity; HHGear^.Y:= Gear^.Y + mdY * Gear^.Elasticity; HHGear^.dX:= HHGear^.X - tx; HHGear^.dY:= HHGear^.Y - ty; //// haveDivided:= false; // check whether rope needs dividing len:= _1 / Distance(ropeDx, ropeDy); // old rope pos nx:= ropeDx * len; ny:= ropeDy * len; len:= Gear^.Elasticity - _5; while len > _3 do begin lx:= hwRound(Gear^.X + mdX * len); ly:= hwRound(Gear^.Y + mdY * len); if ((ly and LAND_HEIGHT_MASK) = 0) and ((lx and LAND_WIDTH_MASK) = 0) and (Land[ly, lx] <> 0) then begin with RopePoints.ar[RopePoints.Count] do begin X:= Gear^.X; Y:= Gear^.Y; if RopePoints.Count = 0 then RopePoints.HookAngle:= DxDy2Angle(Gear^.dY, Gear^.dX); b:= (nx * HHGear^.dY) > (ny * HHGear^.dX); dLen:= len end; with RopePoints.rounded[RopePoints.Count] do begin X:= hwRound(Gear^.X); Y:= hwRound(Gear^.Y); end; Gear^.X:= Gear^.X + nx * len; Gear^.Y:= Gear^.Y + ny * len; inc(RopePoints.Count); TryDo(RopePoints.Count <= MAXROPEPOINTS, 'Rope points overflow', true); Gear^.Elasticity:= Gear^.Elasticity - len; Gear^.Friction:= Gear^.Friction - len; haveDivided:= true; break end; len:= len - _0_3 // should be the same as increase step end; if not haveDivided then if RopePoints.Count > 0 then // check whether the last dividing point could be removed begin tx:= RopePoints.ar[Pred(RopePoints.Count)].X; ty:= RopePoints.ar[Pred(RopePoints.Count)].Y; mdX:= tx - Gear^.X; mdY:= ty - Gear^.Y; if RopePoints.ar[Pred(RopePoints.Count)].b xor (mdX * (ty - HHGear^.Y) > (tx - HHGear^.X) * mdY) then begin dec(RopePoints.Count); Gear^.X:= RopePoints.ar[RopePoints.Count].X; Gear^.Y:= RopePoints.ar[RopePoints.Count].Y; Gear^.Elasticity:= Gear^.Elasticity + RopePoints.ar[RopePoints.Count].dLen; Gear^.Friction:= Gear^.Friction + RopePoints.ar[RopePoints.Count].dLen; // restore hog position len:= _1 / Distance(mdX, mdY); mdX:= mdX * len; mdY:= mdY * len; HHGear^.X:= Gear^.X - mdX * Gear^.Elasticity; HHGear^.Y:= Gear^.Y - mdY * Gear^.Elasticity; end end; haveCollision:= false; if TestCollisionXwithGear(HHGear, hwSign(HHGear^.dX)) then begin HHGear^.dX:= -_0_6 * HHGear^.dX; haveCollision:= true end; if TestCollisionYwithGear(HHGear, hwSign(HHGear^.dY)) then begin HHGear^.dY:= -_0_6 * HHGear^.dY; haveCollision:= true end; if haveCollision and (Gear^.Message and (gm_Left or gm_Right) <> 0) and (Gear^.Message and (gm_Up or gm_Down) <> 0) then begin HHGear^.dX:= SignAs(hwAbs(HHGear^.dX) + _0_2, HHGear^.dX); HHGear^.dY:= SignAs(hwAbs(HHGear^.dY) + _0_2, HHGear^.dY) end; len:= Distance(HHGear^.dX, HHGear^.dY); if len > _0_8 then begin len:= _0_8 / len; HHGear^.dX:= HHGear^.dX * len; HHGear^.dY:= HHGear^.dY * len; end; if (Gear^.Message and gm_Attack) <> 0 then if (Gear^.State and gsttmpFlag) <> 0 then with PHedgehog(Gear^.Hedgehog)^ do begin PlaySound(sndRopeRelease); if Ammo^[CurSlot, CurAmmo].AmmoType <> amParachute then WaitCollision else DeleteMe end else else if (Gear^.State and gsttmpFlag) = 0 then Gear^.State:= Gear^.State or gsttmpFlag; end; procedure doStepRopeAttach(Gear: PGear); var HHGear: PGear; tx, ty, tt: hwFloat; procedure RemoveFromAmmo; begin if (Gear^.State and gstAttacked) = 0 then begin OnUsedAmmo(PHedgehog(HHGear^.Hedgehog)^); Gear^.State:= Gear^.State or gstAttacked end; ApplyAmmoChanges(PHedgehog(HHGear^.Hedgehog)^) end; begin Gear^.X:= Gear^.X - Gear^.dX; Gear^.Y:= Gear^.Y - Gear^.dY; Gear^.Elasticity:= Gear^.Elasticity + _1; HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear; DeleteCI(HHGear); if (HHGear^.State and gstMoving) <> 0 then begin if TestCollisionXwithGear(HHGear, hwSign(HHGear^.dX)) then SetLittle(HHGear^.dX); if HHGear^.dY.isNegative and TestCollisionYwithGear(HHGear, -1) then HHGear^.dY:= _0; HHGear^.X:= HHGear^.X + HHGear^.dX; Gear^.X:= Gear^.X + HHGear^.dX; if TestCollisionYwithGear(HHGear, 1) then begin CheckHHDamage(HHGear); HHGear^.dY:= _0; //HHGear^.State:= HHGear^.State and not (gstHHJumping or gstHHHJump); end else begin HHGear^.Y:= HHGear^.Y + HHGear^.dY; Gear^.Y:= Gear^.Y + HHGear^.dY; HHGear^.dY:= HHGear^.dY + cGravity; end; tt:= Gear^.Elasticity; tx:= _0; ty:= _0; while tt > _20 do begin if TestCollisionXwithXYShift(Gear, tx, hwRound(ty), -hwSign(Gear^.dX)) or TestCollisionYwithXYShift(Gear, hwRound(tx), hwRound(ty), -hwSign(Gear^.dY)) then begin Gear^.X:= Gear^.X + tx; Gear^.Y:= Gear^.Y + ty; Gear^.Elasticity:= tt; Gear^.doStep:= @doStepRopeWork; PlaySound(sndRopeAttach); with HHGear^ do State:= State and not (gstAttacking or gstHHJumping or gstHHHJump); RemoveFromAmmo; tt:= _0; exit end; tx:= tx + Gear^.dX + Gear^.dX; ty:= ty + Gear^.dY + Gear^.dY; tt:= tt - _2; end; end; CheckCollision(Gear); if (Gear^.State and gstCollision) <> 0 then if Gear^.Elasticity < _10 then Gear^.Elasticity:= _10000 else begin Gear^.doStep:= @doStepRopeWork; PlaySound(sndRopeAttach); with HHGear^ do State:= State and not (gstAttacking or gstHHJumping or gstHHHJump); RemoveFromAmmo; exit end; if (Gear^.Elasticity > Gear^.Friction) or ((Gear^.Message and gm_Attack) = 0) or ((HHGear^.State and gstHHDriven) = 0) or (HHGear^.Damage > 0) then begin with PHedgehog(Gear^.Hedgehog)^.Gear^ do begin State:= State and not gstAttacking; Message:= Message and not gm_Attack end; DeleteGear(Gear) end end; procedure doStepRope(Gear: PGear); begin Gear^.dX:= - Gear^.dX; Gear^.dY:= - Gear^.dY; Gear^.doStep:= @doStepRopeAttach; PlaySound(sndRopeShot) end; //////////////////////////////////////////////////////////////////////////////// procedure doStepSmokeTrace(Gear: PGear); begin inc(Gear^.Timer); if Gear^.Timer > 64 then begin Gear^.Timer:= 0; dec(Gear^.State) end; Gear^.dX:= Gear^.dX + cWindSpeed; Gear^.X:= Gear^.X + Gear^.dX; if Gear^.State = 0 then DeleteGear(Gear) end; //////////////////////////////////////////////////////////////////////////////// procedure doStepExplosionWork(Gear: PGear); begin inc(Gear^.Timer); if Gear^.Timer > 75 then begin inc(Gear^.State); Gear^.Timer:= 0; if Gear^.State > 5 then DeleteGear(Gear) end; end; procedure doStepExplosion(Gear: PGear); var i: LongWord; begin for i:= 0 to 31 do AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtFire); for i:= 0 to 8 do AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtExplPart); for i:= 0 to 8 do AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtExplPart2); Gear^.doStep:= @doStepExplosionWork end; //////////////////////////////////////////////////////////////////////////////// procedure doStepMine(Gear: PGear); begin if (Gear^.State and gstMoving) <> 0 then begin DeleteCI(Gear); doStepFallingGear(Gear); if (Gear^.State and gstMoving) = 0 then begin AddGearCI(Gear); Gear^.dX:= _0; Gear^.dY:= _0 end; CalcRotationDirAngle(Gear); AllInactive:= false end else if ((GameTicks and $3F) = 25) then doStepFallingGear(Gear); if ((Gear^.State and gsttmpFlag) <> 0) and (Gear^.Health <> 0) then if ((Gear^.State and gstAttacking) = 0) then begin if ((GameTicks and $1F) = 0) then if CheckGearNear(Gear, gtHedgehog, 46, 32) <> nil then Gear^.State:= Gear^.State or gstAttacking end else // gstAttacking <> 0 begin AllInactive:= false; if (Gear^.Timer and $FF) = 0 then PlaySound(sndMineTick); if Gear^.Timer = 0 then begin if ((Gear^.State and gstWait) <> 0) or (cMineDudPercent = 0) or (getRandom(100) > cMineDudPercent) then begin doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 50, EXPLAutoSound); DeleteGear(Gear) end else begin AddVisualGear(hwRound(Gear^.X) - 4 + Random(8), hwRound(Gear^.Y) - 4 - Random(4), vgtSmoke); PlaySound(sndVaporize); Gear^.Health:= 0; end; exit end; dec(Gear^.Timer); end else // gsttmpFlag = 0 if TurnTimeLeft = 0 then Gear^.State:= Gear^.State or gsttmpFlag; end; //////////////////////////////////////////////////////////////////////////////// procedure doStepDynamite(Gear: PGear); begin doStepFallingGear(Gear); AllInactive:= false; if Gear^.Timer mod 166 = 0 then inc(Gear^.Tag); if Gear^.Timer = 1000 then // might need better timing makeHogsWorry(Gear^.X, Gear^.Y, 75); if Gear^.Timer = 0 then begin doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 75, EXPLAutoSound); DeleteGear(Gear); exit end; dec(Gear^.Timer); end; /////////////////////////////////////////////////////////////////////////////// (* TODO Increase damage as barrel smokes? Try tweaking friction some more *) procedure doStepRollingBarrel(Gear: PGear); var i: LongInt; particle: PVisualGear; begin Gear^.State:= Gear^.State or gstAnimation; if ((Gear^.dX.QWordValue <> 0) or (Gear^.dY.QWordValue <> 0)) then begin DeleteCI(Gear); AllInactive:= false; if not Gear^.dY.isNegative and (Gear^.dY > _0_03) and TestCollisionYwithGear(Gear, 1) then begin Gear^.State:= Gear^.State or gsttmpFlag; inc(Gear^.Damage, hwRound(Gear^.dY * _50)); for i:= min(12, hwRound(Gear^.dY*_10)) downto 0 do begin particle:= AddVisualGear(hwRound(Gear^.X) - 5 + Random(10), hwRound(Gear^.Y) + 12, vgtDust); if particle <> nil then particle^.dX := particle^.dX + (Gear^.dX / 5) end end else if not Gear^.dX.isNegative and (Gear^.dX > _0_03) and TestCollisionXwithGear(Gear, 1) then inc(Gear^.Damage, hwRound(Gear^.dX * _50)) else if Gear^.dY.isNegative and (Gear^.dY < -_0_03) and TestCollisionYwithGear(Gear, -1) then inc(Gear^.Damage, hwRound(Gear^.dY * -_50)) else if Gear^.dX.isNegative and (Gear^.dX < -_0_03) and TestCollisionXwithGear(Gear, -1) then inc(Gear^.Damage, hwRound(Gear^.dX * -_50)); doStepFallingGear(Gear); CalcRotationDirAngle(Gear); //CheckGearDrowning(Gear) end else begin Gear^.State:= Gear^.State or gsttmpFlag; AddGearCI(Gear) end; (* Attempt to make a barrel knock itself over an edge. Would need more checks to avoid issues like burn damage begin x:= hwRound(Gear^.X); y:= hwRound(Gear^.Y); if (((y+1) and LAND_HEIGHT_MASK) = 0) and ((x and LAND_WIDTH_MASK) = 0) then if (Land[y+1, x] = 0) then begin if (((y+1) and LAND_HEIGHT_MASK) = 0) and (((x+Gear^.Radius-2) and LAND_WIDTH_MASK) = 0) and (Land[y+1, x+Gear^.Radius-2] = 0) then Gear^.dX:= -_0_08 else if (((y+1 and LAND_HEIGHT_MASK)) = 0) and (((x-(Gear^.Radius-2)) and LAND_WIDTH_MASK) = 0) and (Land[y+1, x-(Gear^.Radius-2)] = 0) then Gear^.dX:= _0_08; end; if Gear^.dX.QWordValue = 0 then AddGearCI(Gear) end; *) if not Gear^.dY.isNegative and (Gear^.dY < _0_001) and TestCollisionYwithGear(Gear, 1) then Gear^.dY:= _0; if hwAbs(Gear^.dX) < _0_001 then Gear^.dX:= _0; if ((Gear^.Health * 100 div cBarrelHealth) < random(90)) and ((GameTicks and $FF) = 0) then if (cBarrelHealth div Gear^.Health) > 2 then AddVisualGear(hwRound(Gear^.X) - 16 + Random(32), hwRound(Gear^.Y) - 2, vgtSmoke) else AddVisualGear(hwRound(Gear^.X) - 16 + Random(32), hwRound(Gear^.Y) - 2, vgtSmokeWhite); dec(Gear^.Health, Gear^.Damage); Gear^.Damage:= 0; if Gear^.Health <= 0 then Gear^.doStep:= @doStepCase; // Hand off to doStepCase for the explosion end; procedure doStepCase(Gear: PGear); var i, x, y: LongInt; k: TGearType; exBoom: boolean; dX, dY: HWFloat; begin k:= Gear^.Kind; exBoom:= false; if (Gear^.Message and gm_Destroy) > 0 then begin DeleteGear(Gear); FreeActionsList; SetAllToActive; // something (hh, mine, etc...) could be on top of the case with CurrentHedgehog^ do if Gear <> nil then Gear^.Message:= Gear^.Message and not (gm_LJump or gm_HJump); exit end; if k = gtExplosives then begin //if V > _0_03 then Gear^.State:= Gear^.State or gstAnimation; if (hwAbs(Gear^.dX) > _0_15) or ((hwAbs(Gear^.dY) > _0_15) and (hwAbs(Gear^.dX) > _0_02)) then Gear^.doStep:= @doStepRollingBarrel; if ((Gear^.Health * 100 div cBarrelHealth) < random(90)) and ((GameTicks and $FF) = 0) then if (cBarrelHealth div Gear^.Health) > 2 then AddVisualGear(hwRound(Gear^.X) - 16 + Random(32), hwRound(Gear^.Y) - 2, vgtSmoke) else AddVisualGear(hwRound(Gear^.X) - 16 + Random(32), hwRound(Gear^.Y) - 2, vgtSmokeWhite); dec(Gear^.Health, Gear^.Damage); Gear^.Damage:= 0; if Gear^.Health <= 0 then exBoom:= true; end; if (Gear^.Damage > 0) or exBoom then begin x:= hwRound(Gear^.X); y:= hwRound(Gear^.Y); DeleteGear(Gear); // <-- delete gear! if k = gtCase then begin doMakeExplosion(x, y, 25, EXPLAutoSound); for i:= 0 to 63 do AddGear(x, y, gtFlame, 0, _0, _0, 0); end else if k = gtExplosives then begin doMakeExplosion(x, y, 75, EXPLAutoSound); for i:= 0 to 31 do begin dX:= AngleCos(i * 64) * _0_5 * (getrandom + _1); dY:= AngleSin(i * 64) * _0_5 * (getrandom + _1); AddGear(x, y, gtFlame, 0, dX, dY, 0); AddGear(x, y, gtFlame, 0, -dX, -dY, 0)^.State:= gsttmpFlag; end end; exit end; if (Gear^.dY.QWordValue <> 0) or (not TestCollisionYwithGear(Gear, 1)) then begin AllInactive:= false; Gear^.dY:= Gear^.dY + cGravity; Gear^.Y:= Gear^.Y + Gear^.dY; if (not Gear^.dY.isNegative) and (Gear^.dY > _0_001) then SetAllHHToActive; if (Gear^.dY.isNegative) and TestCollisionYwithGear(Gear, -1) then Gear^.dY:= _0; if (not Gear^.dY.isNegative) and TestCollisionYwithGear(Gear, 1) then begin if (Gear^.dY > _0_02) and (k = gtExplosives) then inc(Gear^.Damage, hwRound(Gear^.dY * _40)); if Gear^.dY > _0_2 then for i:= min(12, hwRound(Gear^.dY*_10)) downto 0 do AddVisualGear(hwRound(Gear^.X) - 5 + Random(10), hwRound(Gear^.Y) + 12, vgtDust); Gear^.dY:= - Gear^.dY * Gear^.Elasticity; if Gear^.dY > - _0_001 then Gear^.dY:= _0 else if Gear^.dY < - _0_03 then PlaySound(Gear^.ImpactSound); end; //if Gear^.dY > - _0_001 then Gear^.dY:= _0 CheckGearDrowning(Gear); end; if (Gear^.dY.QWordValue = 0) then AddGearCI(Gear) else if (Gear^.dY.QWordValue <> 0) then DeleteCI(Gear) end; //////////////////////////////////////////////////////////////////////////////// procedure doStepTarget(Gear: PGear); begin if (Gear^.Timer = 0) and (Gear^.Tag = 0) then PlaySound(sndWarp); if (Gear^.Tag = 0) and (Gear^.Timer < 1000) then inc(Gear^.Timer) else if Gear^.Tag = 1 then begin Gear^.Tag:= 2; if (TrainingFlags and tfTimeTrial) <> 0 then begin inc(TurnTimeLeft, TrainingTimeInc); if TrainingTimeInc > TrainingTimeInM then dec(TrainingTimeInc, TrainingTimeInD); if TurnTimeLeft > TrainingTimeMax then TurnTimeLeft:= TrainingTimeMax; end; end else if Gear^.Tag = 2 then if Gear^.Timer > 0 then dec(Gear^.Timer) else begin if (TrainingFlags and tfTargetRespawn) <> 0 then begin TrainingTargetGear:= AddGear(0, 0, gtTarget, 0, _0, _0, 0); FindPlace(TrainingTargetGear, false, 0, LAND_WIDTH); end; DeleteGear(Gear); exit; end; doStepCase(Gear) end; //////////////////////////////////////////////////////////////////////////////// procedure doStepIdle(Gear: PGear); begin AllInactive:= false; dec(Gear^.Timer); if Gear^.Timer = 0 then begin DeleteGear(Gear); AfterAttack end end; procedure doStepShover(Gear: PGear); var HHGear: PGear; begin HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear; HHGear^.State:= HHGear^.State or gstNoDamage; DeleteCI(HHGear); AmmoShove(Gear, 30, 115); HHGear^.State:= HHGear^.State and not gstNoDamage; Gear^.Timer:= 250; Gear^.doStep:= @doStepIdle end; //////////////////////////////////////////////////////////////////////////////// procedure doStepWhip(Gear: PGear); var HHGear: PGear; i: LongInt; begin HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear; HHGear^.State:= HHGear^.State or gstNoDamage; DeleteCI(HHGear); for i:= 0 to 3 do begin AmmoShove(Gear, 30, 25); Gear^.X:= Gear^.X + Gear^.dX * 5 end; HHGear^.State:= HHGear^.State and not gstNoDamage; Gear^.Timer:= 250; Gear^.doStep:= @doStepIdle end; //////////////////////////////////////////////////////////////////////////////// procedure doStepFlame(Gear: PGear); var gX,gY,i: LongInt; begin if (Gear^.State and gsttmpFlag) = 0 then AllInactive:= false; if not TestCollisionYwithGear(Gear, 1) then begin AllInactive:= false; if Gear^.dX.QWordValue > _0_01.QWordValue then Gear^.dX:= Gear^.dX * _0_995; Gear^.dY:= Gear^.dY + cGravity; if (Gear^.State and gsttmpFlag) <> 0 then Gear^.dY:= Gear^.dY + cGravity; if Gear^.dY.QWordValue > _0_2.QWordValue then Gear^.dY:= Gear^.dY * _0_995; if (Gear^.State and gsttmpFlag) <> 0 then Gear^.X:= Gear^.X + Gear^.dX else Gear^.X:= Gear^.X + Gear^.dX + cWindSpeed * 640; Gear^.Y:= Gear^.Y + Gear^.dY; if (hwRound(Gear^.Y) > cWaterLine) then begin gX:= hwRound(Gear^.X); for i:= 0 to 3 do AddVisualGear(gX - 16 + Random(32), cWaterLine - 16 + Random(16), vgtSteam); PlaySound(sndVaporize); DeleteGear(Gear); exit end end else begin if (Gear^.State and gsttmpFlag) <> 0 then begin Gear^.Radius:= 9; AmmoShove(Gear, 2, 30); Gear^.Radius:= 1 end; if Gear^.Timer > 0 then begin dec(Gear^.Timer); inc(Gear^.Damage) end else begin // Standard fire if (Gear^.State and gsttmpFlag) = 0 then begin Gear^.Radius:= 9; AmmoShove(Gear, 4, 100); gX:= hwRound(Gear^.X); gY:= hwRound(Gear^.Y); Gear^.Radius:= 1; doMakeExplosion(gX, gY, 4, EXPLNoDamage); if ((GameTicks and $7) = 0) and (Random(2) = 0) then for i:= 1 to Random(2)+1 do AddVisualGear(gX - 3 + Random(6), gY - 2, vgtSmoke); if Gear^.Health > 0 then dec(Gear^.Health); Gear^.Timer:= 450 - Gear^.Tag * 8 end else begin // Modified fire if ((GameTicks and $7FF) = 0) and ((GameFlags and gfSolidLand) = 0) then begin DrawExplosion(gX, gY, 4); for i:= 0 to Random(3) do AddVisualGear(gX - 3 + Random(6), gY - 2, vgtSmoke); end; // This one is interesting. I think I understand the purpose, but I wonder if a bit more fuzzy of kicking could be done with getrandom. Gear^.Timer:= 100 - Gear^.Tag * 3; if (Gear^.Damage > 3000+Gear^.Tag*1500) then Gear^.Health:= 0 end end end; if Gear^.Health = 0 then begin gX:= hwRound(Gear^.X); gY:= hwRound(Gear^.Y); if (Gear^.State and gsttmpFlag) = 0 then begin if ((GameTicks and $3) = 0) and (Random(1) = 0) then begin for i:= 1 to Random(2)+1 do begin AddVisualGear(gX - 3 + Random(6), gY - 2, vgtSmoke); end; end; end else begin for i:= 0 to Random(3) do begin AddVisualGear(gX - 3 + Random(6), gY - 2, vgtSmoke); end; end; DeleteGear(Gear) end; end; //////////////////////////////////////////////////////////////////////////////// procedure doStepFirePunchWork(Gear: PGear); var HHGear: PGear; begin AllInactive:= false; if ((Gear^.Message and gm_Destroy) <> 0) then begin DeleteGear(Gear); AfterAttack; exit end; HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear; if hwRound(HHGear^.Y) <= Gear^.Tag - 2 then begin Gear^.Tag:= hwRound(HHGear^.Y); DrawTunnel(HHGear^.X - int2hwFloat(cHHRadius), HHGear^.Y - _1, _0_5, _0, cHHRadius * 4, 2); HHGear^.State:= HHGear^.State or gstNoDamage; Gear^.Y:= HHGear^.Y; AmmoShove(Gear, 30, 40); HHGear^.State:= HHGear^.State and not gstNoDamage end; HHGear^.dY:= HHGear^.dY + cGravity; if not (HHGear^.dY.isNegative) then begin HHGear^.State:= HHGear^.State or gstMoving; DeleteGear(Gear); AfterAttack; exit end; if CheckLandValue(hwRound(HHGear^.X), hwRound(HHGear^.Y + HHGear^.dY + SignAs(_6,Gear^.dY)), COLOR_INDESTRUCTIBLE) then HHGear^.Y:= HHGear^.Y + HHGear^.dY end; procedure doStepFirePunch(Gear: PGear); var HHGear: PGear; begin AllInactive:= false; HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear; DeleteCI(HHGear); HHGear^.X:= int2hwFloat(hwRound(HHGear^.X)) - _0_5; HHGear^.dX:= SignAs(cLittle, Gear^.dX); HHGear^.dY:= - _0_3; Gear^.X:= HHGear^.X; Gear^.dX:= SignAs(_0_45, Gear^.dX); Gear^.dY:= - _0_9; Gear^.doStep:= @doStepFirePunchWork; DrawTunnel(HHGear^.X - int2hwFloat(cHHRadius), HHGear^.Y + _1, _0_5, _0, cHHRadius * 4, 5); PlaySound(TSound(ord(sndFirePunch1) + GetRandom(6)), PHedgehog(HHGear^.Hedgehog)^.Team^.voicepack) end; //////////////////////////////////////////////////////////////////////////////// procedure doStepParachuteWork(Gear: PGear); var HHGear: PGear; begin HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear; inc(Gear^.Timer); if TestCollisionYwithGear(HHGear, 1) or ((HHGear^.State and gstHHDriven) = 0) or CheckGearDrowning(HHGear) or ((Gear^.Message and gm_Attack) <> 0) then begin with HHGear^ do begin Message:= 0; SetLittle(dX); dY:= _0; State:= State or gstMoving; end; DeleteGear(Gear); isCursorVisible:= false; ApplyAmmoChanges(PHedgehog(HHGear^.Hedgehog)^); exit end; if not TestCollisionXwithGear(HHGear, hwSign(HHGear^.dX)) then HHGear^.X:= HHGear^.X + cWindSpeed * 200; if (Gear^.Message and gm_Left) <> 0 then HHGear^.X:= HHGear^.X - cMaxWindSpeed * 80 else if (Gear^.Message and gm_Right) <> 0 then HHGear^.X:= HHGear^.X + cMaxWindSpeed * 80; if (Gear^.Message and gm_Up) <> 0 then HHGear^.Y:= HHGear^.Y - cGravity * 40 else if (Gear^.Message and gm_Down) <> 0 then HHGear^.Y:= HHGear^.Y + cGravity * 40; HHGear^.Y:= HHGear^.Y + cGravity * 100; Gear^.X:= HHGear^.X; Gear^.Y:= HHGear^.Y end; procedure doStepParachute(Gear: PGear); var HHGear: PGear; begin HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear; DeleteCI(HHGear); AfterAttack; HHGear^.State:= HHGear^.State and not (gstAttacking or gstAttacked or gstMoving); HHGear^.Message:= HHGear^.Message and not gm_Attack; Gear^.doStep:= @doStepParachuteWork; Gear^.Message:= HHGear^.Message; doStepParachuteWork(Gear) end; //////////////////////////////////////////////////////////////////////////////// procedure doStepAirAttackWork(Gear: PGear); var i: Longint; begin AllInactive:= false; Gear^.X:= Gear^.X + cAirPlaneSpeed * Gear^.Tag; if (Gear^.Health > 0)and(not (Gear^.X < Gear^.dX))and(Gear^.X < Gear^.dX + cAirPlaneSpeed) then begin dec(Gear^.Health); case Gear^.State of 0: FollowGear:= AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtAirBomb, 0, cBombsSpeed * Gear^.Tag, _0, 0); 1: FollowGear:= AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtMine, 0, cBombsSpeed * Gear^.Tag, _0, 0); 2: for i:= -19 to 19 do FollowGear:= AddGear(hwRound(Gear^.X) + i div 3, hwRound(Gear^.Y), gtFlame, 0, _0_001 * i, _0, 0); end; Gear^.dX:= Gear^.dX + int2hwFloat(30 * Gear^.Tag) end; if (GameTicks and $3F) = 0 then AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtSmokeTrace, 0, _0, _0, 0); if (hwRound(Gear^.X) > (LAND_WIDTH+1024)) or (hwRound(Gear^.X) < -1024) then DeleteGear(Gear) end; procedure doStepAirAttack(Gear: PGear); begin AllInactive:= false; if Gear^.X.QWordValue = 0 then begin Gear^.Tag:= 1; Gear^.X:= -_1024; end else begin Gear^.Tag:= -1; Gear^.X:= int2hwFloat(LAND_WIDTH + 1024); end; Gear^.Y:= int2hwFloat(topY-300); Gear^.dX:= int2hwFloat(TargetPoint.X - 5 * Gear^.Tag * 15); if (int2hwFloat(TargetPoint.Y) - Gear^.Y > _0) and (Gear^.State <> 2) then Gear^.dX:= Gear^.dX - cBombsSpeed * hwSqrt((int2hwFloat(TargetPoint.Y) - Gear^.Y) * 2 / cGravity) * Gear^.Tag; Gear^.Health:= 6; Gear^.doStep:= @doStepAirAttackWork; end; //////////////////////////////////////////////////////////////////////////////// procedure doStepAirBomb(Gear: PGear); begin AllInactive:= false; doStepFallingGear(Gear); if (Gear^.State and gstCollision) <> 0 then begin doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 30, EXPLAutoSound); DeleteGear(Gear); exit end; if (GameTicks and $3F) = 0 then AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtSmokeTrace, 0, _0, _0, 0) end; //////////////////////////////////////////////////////////////////////////////// procedure doStepGirder(Gear: PGear); var HHGear: PGear; x, y, tx, ty: hwFloat; begin AllInactive:= false; HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear; tx:= int2hwFloat(TargetPoint.X); ty:= int2hwFloat(TargetPoint.Y); x:= HHGear^.X; y:= HHGear^.Y; if (Distance(tx - x, ty - y) > _256) or not TryPlaceOnLand(TargetPoint.X - SpritesData[sprAmGirder].Width div 2, TargetPoint.Y - SpritesData[sprAmGirder].Height div 2, sprAmGirder, Gear^.State, true) then begin PlaySound(sndDenied); HHGear^.Message:= HHGear^.Message and not gm_Attack; HHGear^.State:= HHGear^.State and not gstAttacking; HHGear^.State:= HHGear^.State or gstHHChooseTarget; isCursorVisible:= true; DeleteGear(Gear) end else begin PlaySound(sndPlaced); DeleteGear(Gear); AfterAttack; end; HHGear^.State:= HHGear^.State and not (gstAttacking or gstAttacked); HHGear^.Message:= HHGear^.Message and not gm_Attack; TargetPoint.X:= NoPointX end; //////////////////////////////////////////////////////////////////////////////// procedure doStepTeleportAfter(Gear: PGear); var HHGear: PGear; begin PHedgehog(Gear^.Hedgehog)^.Unplaced:= false; HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear; HHGear^.Y:= HHGear^.Y + HHGear^.dY; // hedgehog falling to collect cases HHGear^.dY:= HHGear^.dY + cGravity; if TestCollisionYwithGear(HHGear, 1) or CheckGearDrowning(HHGear) then begin DeleteGear(Gear); AfterAttack end end; procedure doStepTeleportAnim(Gear: PGear); begin inc(Gear^.Timer); if Gear^.Timer = 65 then begin Gear^.Timer:= 0; inc(Gear^.Pos); if Gear^.Pos = 11 then Gear^.doStep:= @doStepTeleportAfter end; end; procedure doStepTeleport(Gear: PGear); var HHGear: PGear; begin AllInactive:= false; HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear; if not TryPlaceOnLand(TargetPoint.X - SpritesData[sprHHTelepMask].Width div 2, TargetPoint.Y - SpritesData[sprHHTelepMask].Height div 2, sprHHTelepMask, 0, false) then begin HHGear^.Message:= HHGear^.Message and not gm_Attack; HHGear^.State:= HHGear^.State and not gstAttacking; HHGear^.State:= HHGear^.State or gstHHChooseTarget; DeleteGear(Gear); isCursorVisible:= true; PlaySound(sndDenied) end else begin DeleteCI(HHGear); SetAllHHToActive; Gear^.doStep:= @doStepTeleportAnim; // copy old HH position and direction to Gear (because we need them for drawing the vanishing hog) Gear^.dX:= HHGear^.dX; // retrieve the cursor direction (it was previously copied to X so it doesn't get lost) HHGear^.dX.isNegative := (Gear^.X.QWordValue <> 0); Gear^.X:= HHGear^.X; Gear^.Y:= HHGear^.Y; HHGear^.X:= int2hwFloat(TargetPoint.X); HHGear^.Y:= int2hwFloat(TargetPoint.Y); HHGear^.State:= HHGear^.State or gstMoving; playSound(sndWarp) end; TargetPoint.X:= NoPointX; end; //////////////////////////////////////////////////////////////////////////////// procedure doStepSwitcherWork(Gear: PGear); var HHGear: PGear; Msg, State: Longword; begin AllInactive:= false; if ((Gear^.Message and not gm_Switch) <> 0) or (TurnTimeLeft = 0) then begin HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear; Msg:= Gear^.Message and not gm_Switch; DeleteGear(Gear); AfterAttack; HHGear:= CurrentHedgehog^.Gear; ApplyAmmoChanges(PHedgehog(HHGear^.Hedgehog)^); HHGear^.Message:= Msg; exit end; if (Gear^.Message and gm_Switch) <> 0 then begin HHGear:= CurrentHedgehog^.Gear; HHGear^.Message:= HHGear^.Message and not gm_Switch; Gear^.Message:= Gear^.Message and not gm_Switch; State:= HHGear^.State; HHGear^.State:= 0; HHGear^.Active:= false; HHGear^.Z:= cHHZ; RemoveGearFromList(HHGear); InsertGearToList(HHGear); PlaySound(sndSwitchHog); repeat CurrentTeam^.CurrHedgehog:= Succ(CurrentTeam^.CurrHedgehog) mod (CurrentTeam^.HedgehogsNumber); until (CurrentTeam^.Hedgehogs[CurrentTeam^.CurrHedgehog].Gear <> nil); CurrentHedgehog:= @CurrentTeam^.Hedgehogs[CurrentTeam^.CurrHedgehog]; HHGear:= CurrentHedgehog^.Gear; HHGear^.State:= State; HHGear^.Active:= true; FollowGear:= HHGear; HHGear^.Z:= cCurrHHZ; RemoveGearFromList(HHGear); InsertGearToList(HHGear); Gear^.X:= HHGear^.X; Gear^.Y:= HHGear^.Y end; end; procedure doStepSwitcher(Gear: PGear); var HHGear: PGear; begin Gear^.doStep:= @doStepSwitcherWork; HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear; with HHGear^ do begin State:= State and not gstAttacking; Message:= Message and not gm_Attack end end; //////////////////////////////////////////////////////////////////////////////// procedure doStepMortar(Gear: PGear); var dX, dY: hwFloat; i: LongInt; dxn, dyn: boolean; begin AllInactive:= false; dxn:= Gear^.dX.isNegative; dyn:= Gear^.dY.isNegative; doStepFallingGear(Gear); if (Gear^.State and gstCollision) <> 0 then begin doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 20, EXPLAutoSound); Gear^.dX.isNegative:= not dxn; Gear^.dY.isNegative:= not dyn; for i:= 0 to 4 do begin dX:= Gear^.dX + (GetRandom - _0_5) * _0_03; dY:= Gear^.dY + (GetRandom - _0_5) * _0_03; AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtCluster, 0, dX, dY, 25); end; DeleteGear(Gear); exit end; if (GameTicks and $3F) = 0 then AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtSmokeTrace, 0, _0, _0, 0) end; //////////////////////////////////////////////////////////////////////////////// procedure doStepKamikazeWork(Gear: PGear); const upd: Longword = 0; var i: LongWord; HHGear: PGear; begin AllInactive:= false; HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear; HHGear^.State:= HHGear^.State or gstNoDamage; DeleteCI(HHGear); i:= 2; repeat Gear^.X:= Gear^.X + HHGear^.dX; Gear^.Y:= Gear^.Y + HHGear^.dY; HHGear^.X:= Gear^.X; HHGear^.Y:= Gear^.Y; inc(Gear^.Damage, 2); // if TestCollisionXwithGear(HHGear, hwSign(Gear^.dX)) // or TestCollisionYwithGear(HHGear, hwSign(Gear^.dY)) then inc(Gear^.Damage, 3); dec(i) until (i = 0) or (Gear^.Damage > Gear^.Health); inc(upd); if upd > 3 then begin if Gear^.Health < 1500 then Gear^.Pos:= 2; AmmoShove(Gear, 30, 40); DrawTunnel(HHGear^.X - HHGear^.dX * 10, HHGear^.Y - _2 - HHGear^.dY * 10 + hwAbs(HHGear^.dY) * 2, HHGear^.dX, HHGear^.dY, 20 + cHHRadius * 2, cHHRadius * 2 + 6); upd:= 0 end; if Gear^.Health < Gear^.Damage then begin doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 30, EXPLAutoSound); AfterAttack; DeleteGear(Gear); DeleteGear(HHGear); end else begin dec(Gear^.Health, Gear^.Damage); Gear^.Damage:= 0 end end; procedure doStepKamikazeIdle(Gear: PGear); begin AllInactive:= false; dec(Gear^.Timer); if Gear^.Timer = 0 then begin Gear^.Pos:= 1; PlaySound(sndKamikaze, PHedgehog(Gear^.Hedgehog)^.Team^.voicepack); Gear^.doStep:= @doStepKamikazeWork end end; procedure doStepKamikaze(Gear: PGear); var HHGear: PGear; begin AllInactive:= false; HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear; HHGear^.dX:= Gear^.dX; HHGear^.dY:= Gear^.dY; Gear^.dX:= SignAs(_0_45, Gear^.dX); Gear^.dY:= - _0_9; Gear^.Timer:= 550; Gear^.doStep:= @doStepKamikazeIdle end; //////////////////////////////////////////////////////////////////////////////// const cakeh = 27; cakeDmg = 75; var CakePoints: array[0..Pred(cakeh)] of record x, y: hwFloat; end; CakeI: Longword; procedure doStepCakeExpl(Gear: PGear); begin AllInactive:= false; inc(Gear^.Tag); if Gear^.Tag < 2250 then exit; doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), cakeDmg, EXPLAutoSound); AfterAttack; DeleteGear(Gear) end; procedure doStepCakeDown(Gear: PGear); var gi: PGear; dmg: LongInt; begin AllInactive:= false; inc(Gear^.Tag); if Gear^.Tag < 100 then exit; Gear^.Tag:= 0; if Gear^.Pos = 0 then begin gi:= GearsList; while gi <> nil do begin dmg:= cakeDmg * 2 - hwRound(Distance(gi^.X - Gear^.X, gi^.Y - Gear^.Y)); if (dmg > 1) and (gi^.Kind = gtHedgehog) then if (CurrentHedgehog^.Gear = gi) and (not gi^.Invulnerable) then gi^.State:= gi^.State or gstLoser else gi^.State:= gi^.State or gstWinner; gi:= gi^.NextGear end; Gear^.doStep:= @doStepCakeExpl; PlaySound(sndCake) end else dec(Gear^.Pos) end; procedure doStepCakeWork(Gear: PGear); const dirs: array[0..3] of TPoint = ((x: 0; y: -1), (x: 1; y: 0),(x: 0; y: 1),(x: -1; y: 0)); var xx, yy, xxn, yyn: LongInt; da: LongInt; tdx, tdy: hwFloat; procedure PrevAngle; begin Gear^.Angle:= (LongInt(Gear^.Angle) + 4 - dA) mod 4 end; procedure NextAngle; begin Gear^.Angle:= (LongInt(Gear^.Angle) + 4 + dA) mod 4 end; begin AllInactive:= false; inc(Gear^.Tag); if Gear^.Tag < 7 then exit; dA:= hwSign(Gear^.dX); xx:= dirs[Gear^.Angle].x; yy:= dirs[Gear^.Angle].y; xxn:= dirs[(LongInt(Gear^.Angle) + 4 + dA) mod 4].x; yyn:= dirs[(LongInt(Gear^.Angle) + 4 + dA) mod 4].y; if (xx = 0) then if TestCollisionYwithGear(Gear, yy) then PrevAngle else begin Gear^.Tag:= 0; Gear^.Y:= Gear^.Y + int2hwFloat(yy); if not TestCollisionXwithGear(Gear, xxn) then begin Gear^.X:= Gear^.X + int2hwFloat(xxn); NextAngle end; end; if (yy = 0) then if TestCollisionXwithGear(Gear, xx) then PrevAngle else begin Gear^.Tag:= 0; Gear^.X:= Gear^.X + int2hwFloat(xx); if not TestCollisionYwithGear(Gear, yyn) then begin Gear^.Y:= Gear^.Y + int2hwFloat(yyn); NextAngle end; end; if Gear^.Tag = 0 then begin CakeI:= (CakeI + 1) mod cakeh; tdx:= CakePoints[CakeI].x - Gear^.X; tdy:= - CakePoints[CakeI].y + Gear^.Y; CakePoints[CakeI].x:= Gear^.X; CakePoints[CakeI].y:= Gear^.Y; Gear^.DirAngle:= DxDy2Angle(tdx, tdy); end; dec(Gear^.Health); Gear^.Timer:= Gear^.Health*10; // This is not seconds, but at least it is *some* feedback if (Gear^.Health = 0) or ((Gear^.Message and gm_Attack) <> 0) then begin FollowGear:= Gear; Gear^.RenderTimer:= false; Gear^.doStep:= @doStepCakeDown end end; procedure doStepCakeUp(Gear: PGear); var i: Longword; begin AllInactive:= false; inc(Gear^.Tag); if Gear^.Tag < 100 then exit; Gear^.Tag:= 0; if Gear^.Pos = 6 then begin for i:= 0 to Pred(cakeh) do begin CakePoints[i].x:= Gear^.X; CakePoints[i].y:= Gear^.Y end; CakeI:= 0; Gear^.doStep:= @doStepCakeWork end else inc(Gear^.Pos) end; procedure doStepCakeFall(Gear: PGear); begin AllInactive:= false; Gear^.dY:= Gear^.dY + cGravity; if TestCollisionYwithGear(Gear, 1) then Gear^.doStep:= @doStepCakeUp else begin Gear^.Y:= Gear^.Y + Gear^.dY; if CheckGearDrowning(Gear) then AfterAttack end end; procedure doStepCake(Gear: PGear); var HHGear: PGear; begin AllInactive:= false; HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear; HHGear^.Message:= HHGear^.Message and (not gm_Attack); DeleteCI(HHGear); FollowGear:= Gear; Gear^.doStep:= @doStepCakeFall end; //////////////////////////////////////////////////////////////////////////////// procedure doStepSeductionWork(Gear: PGear); var x, y: LongInt; begin AllInactive:= false; Gear^.X:= Gear^.X + Gear^.dX; Gear^.Y:= Gear^.Y + Gear^.dY; x:= hwRound(Gear^.X); y:= hwRound(Gear^.Y); if ((y and LAND_HEIGHT_MASK) = 0) and ((x and LAND_WIDTH_MASK) = 0) then if (Land[y, x] <> 0) then begin Gear^.dX.isNegative:= not Gear^.dX.isNegative; Gear^.dY.isNegative:= not Gear^.dY.isNegative; Gear^.dX:= Gear^.dX * _1_5; Gear^.dY:= Gear^.dY * _1_5 - _0_3; AmmoShove(Gear, 0, 40); AfterAttack; DeleteGear(Gear) end else else begin AfterAttack; DeleteGear(Gear) end end; procedure doStepSeductionWear(Gear: PGear); begin AllInactive:= false; inc(Gear^.Timer); if Gear^.Timer > 250 then begin Gear^.Timer:= 0; inc(Gear^.Pos); if Gear^.Pos = 5 then PlaySound(sndYoohoo, PHedgehog(Gear^.Hedgehog)^.Team^.voicepack) end; if Gear^.Pos = 14 then Gear^.doStep:= @doStepSeductionWork end; procedure doStepSeduction(Gear: PGear); begin AllInactive:= false; DeleteCI(PHedgehog(Gear^.Hedgehog)^.Gear); Gear^.doStep:= @doStepSeductionWear end; //////////////////////////////////////////////////////////////////////////////// procedure doStepWaterUp(Gear: PGear); var i: LongWord; begin AllInactive:= false; inc(Gear^.Timer); if Gear^.Timer = 17 then Gear^.Timer:= 0 else exit; if cWaterLine > 0 then begin dec(cWaterLine); for i:= 0 to LAND_WIDTH - 1 do Land[cWaterLine, i]:= 0; SetAllToActive end; inc(Gear^.Tag); if (Gear^.Tag = 47) or (cWaterLine = 0) then DeleteGear(Gear) end; //////////////////////////////////////////////////////////////////////////////// procedure doStepDrillDrilling(Gear: PGear); var t: PGearArray; ox, oy: hwFloat; begin AllInactive:= false; if (Gear^.Timer > 0) and ((Gear^.Timer mod 10) = 0) then begin ox:= Gear^.X; oy:= Gear^.Y; Gear^.X:= Gear^.X + Gear^.dX; Gear^.Y:= Gear^.Y + Gear^.dY; DrawTunnel(oX, oY, Gear^.dX, Gear^.dY, 2, 6); if(CheckGearDrowning(Gear)) then begin StopSound(Gear^.SoundChannel); exit end end; t:= CheckGearsCollision(Gear); //fixes drill not exploding when touching HH bug if (Gear^.Timer = 0) or (t^.Count <> 0) or (not TestCollisionYWithGear(Gear, hwSign(Gear^.dY)) and not TestCollisionXWithGear(Gear, hwSign(Gear^.dX))) or (Land[hwRound(Gear^.Y), hwRound(Gear^.X)] = COLOR_INDESTRUCTIBLE) then begin //out of time or exited ground StopSound(Gear^.SoundChannel); doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 50, EXPLAutoSound); DeleteGear(Gear); exit end; dec(Gear^.Timer); end; procedure doStepDrill(Gear: PGear); var t: PGearArray; oldDx, oldDy: hwFloat; t2: hwFloat; begin AllInactive:= false; Gear^.dX:= Gear^.dX + cWindSpeed; oldDx:= Gear^.dX; oldDy:= Gear^.dY; doStepFallingGear(Gear); if (GameTicks and $3F) = 0 then AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtSmokeTrace, 0, _0, _0, 0); if ((Gear^.State and gstCollision) <> 0) then begin //hit Gear^.dX:= oldDx; Gear^.dY:= oldDy; t:= CheckGearsCollision(Gear); if (t^.Count = 0) then begin //hit the ground not the HH t2 := _0_5 / Distance(Gear^.dX, Gear^.dY); Gear^.dX:= Gear^.dX * t2; Gear^.dY:= Gear^.dY * t2; end else begin //explode right on contact with HH doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 50, EXPLAutoSound); DeleteGear(Gear); exit; end; Gear^.SoundChannel:= LoopSound(sndDrillRocket); Gear^.doStep:= @doStepDrillDrilling; dec(Gear^.Timer) end end; //////////////////////////////////////////////////////////////////////////////// procedure doStepBallgunWork(Gear: PGear); var HHGear: PGear; rx, ry: hwFloat; gX, gY: LongInt; begin AllInactive:= false; dec(Gear^.Timer); HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear; HedgehogChAngle(HHGear); gX:= hwRound(Gear^.X); gY:= hwRound(Gear^.Y); if (Gear^.Timer mod 100) = 0 then begin rx:= rndSign(getRandom * _0_1); ry:= rndSign(getRandom * _0_1); AddGear(gx, gy, gtBall, 0, SignAs(AngleSin(HHGear^.Angle) * _0_8, HHGear^.dX) + rx, AngleCos(HHGear^.Angle) * ( - _0_8) + ry, 0); PlaySound(sndGun); end; if (Gear^.Timer = 0) or (HHGear^.Damage <> 0) then begin DeleteGear(Gear); AfterAttack end end; procedure doStepBallgun(Gear: PGear); var HHGear: PGear; begin HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear; HHGear^.Message:= HHGear^.Message and not (gm_Up or gm_Down); HHGear^.State:= HHGear^.State or gstNotKickable; Gear^.doStep:= @doStepBallgunWork end; //////////////////////////////////////////////////////////////////////////////// procedure doStepRCPlaneWork(Gear: PGear); const cAngleSpeed = 3; var HHGear: PGear; i: LongInt; dX, dY: hwFloat; fChanged: boolean; trueAngle: Longword; t: PGear; begin AllInactive:= false; if ((TrainingFlags and tfRCPlane) = 0) and (Gear^.Timer > 0) then dec(Gear^.Timer); if ((TrainingFlags and tfRCPlane) <> 0) and ((TrainingFlags and tfTimeTrial) <> 0 ) and (TimeTrialStartTime = 0) then TimeTrialStartTime:= RealTicks; HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear; FollowGear:= Gear; fChanged:= false; if ((HHGear^.State and gstHHDriven) = 0) or (Gear^.Timer = 0) then begin fChanged:= true; if Gear^.Angle > 2048 then dec(Gear^.Angle) else if Gear^.Angle < 2048 then inc(Gear^.Angle) else fChanged:= false end else begin if ((Gear^.Message and gm_Left) <> 0) then begin fChanged:= true; Gear^.Angle:= (Gear^.Angle + (4096 - cAngleSpeed)) mod 4096 end; if ((Gear^.Message and gm_Right) <> 0) then begin fChanged:= true; Gear^.Angle:= (Gear^.Angle + cAngleSpeed) mod 4096 end end; if fChanged then begin Gear^.dX.isNegative:= (Gear^.Angle > 2048); if Gear^.dX.isNegative then trueAngle:= 4096 - Gear^.Angle else trueAngle:= Gear^.Angle; Gear^.dX:= SignAs(AngleSin(trueAngle), Gear^.dX) * _0_25; Gear^.dY:= AngleCos(trueAngle) * -_0_25; end; Gear^.X:= Gear^.X + Gear^.dX; Gear^.Y:= Gear^.Y + Gear^.dY; if (TrainingFlags and tfRCPlane) = 0 then begin if (GameTicks and $FF) = 0 then if Gear^.Timer < 3500 then AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtEvilTrace, 0, _0, _0, 0) else AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtSmokeTrace, 0, _0, _0, 0); if ((HHGear^.Message and gm_Attack) <> 0) and (Gear^.Health <> 0) then begin HHGear^.Message := HHGear^.Message and not gm_Attack; AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtAirBomb, 0, Gear^.dX * _0_5, Gear^.dY * _0_5, 0); dec(Gear^.Health) end; if ((HHGear^.Message and gm_LJump) <> 0) and ((Gear^.State and gsttmpFlag) = 0) then begin Gear^.State:= Gear^.State or gsttmpFlag; PauseMusic; playSound(sndRideOfTheValkyries); end; // pickup bonuses t:= CheckGearNear(Gear, gtCase, 36, 36); if t <> nil then PickUp(HHGear, t); end else begin if (GameTicks and $FF) = 0 then AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtSmokeTrace, 0, _0, _0, 0); // pickup targets t:= CheckGearNear(Gear, gtTarget, 36, 36); if t <> nil then begin if t^.Tag <> 0 then // collect it only once exit; PlaySound(sndShotgunReload); t^.Tag:= 1; TrainingTargetGear:= nil; // remove target cursor exit; end; if (TurnTimeLeft > 0) then dec(TurnTimeLeft) end; CheckCollision(Gear); if ((Gear^.State and gstCollision) <> 0) or (((TrainingFlags and tfRCPlane) <> 0) and (TurnTimeLeft = 0)) or CheckGearDrowning(Gear) then begin if ((TrainingFlags and tfRCPlane) <> 0) and ((TrainingFlags and tfTimeTrial) <> 0 ) and (TimeTrialStopTime = 0) then TimeTrialStopTime:= RealTicks; StopSound(Gear^.SoundChannel); StopSound(sndRideOfTheValkyries); ResumeMusic; if ((Gear^.State and gstCollision) <> 0) or (((TrainingFlags and tfRCPlane) <> 0) and (TurnTimeLeft = 0)) then begin doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 25, EXPLAutoSound); for i:= 0 to 32 do begin dX:= AngleCos(i * 64) * _0_5 * (GetRandom + _1); dY:= AngleSin(i * 64) * _0_5 * (GetRandom + _1); AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtFlame, 0, dX, dY, 0); AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtFlame, 0, dX, -dY, 0); end; DeleteGear(Gear) end; AfterAttack; CurAmmoGear:= nil; TurnTimeLeft:= 14 * 125; if (TrainingFlags and tfRCPlane) <> 0 then TurnTimeLeft:= 0; // HACK: RCPlane training allows unlimited plane starts in last 2 seconds HHGear^.Message:= 0; ParseCommand('/taunt '#1, true) end end; procedure doStepRCPlane(Gear: PGear); var HHGear: PGear; begin HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear; HHGear^.Message:= 0; HHGear^.State:= HHGear^.State or gstNotKickable; Gear^.Angle:= HHGear^.Angle; Gear^.Tag:= hwSign(HHGear^.dX); if HHGear^.dX.isNegative then Gear^.Angle:= 4096 - Gear^.Angle; Gear^.doStep:= @doStepRCPlaneWork end; procedure doStepJetpackWork(Gear: PGear); var HHGear: PGear; fuel: LongInt; move: hwFloat; begin AllInactive:= false; HHGear:=PHedgehog(Gear^.Hedgehog)^.Gear; //dec(Gear^.Timer); move:= _0_1; fuel:= 50; (*if (HHGear^.Message and gm_Precise) <> 0 then begin move:= _0_02; fuel:= 5; end;*) if (HHGear^.Message and gm_Up) <> 0 then begin if (not HHGear^.dY.isNegative) or (HHGear^.Y > -_256) then HHGear^.dY:= HHGear^.dY - move; HHGear^.dY:= HHGear^.dY - move; dec(Gear^.Health, fuel); Gear^.MsgParam:= Gear^.MsgParam or gm_Up; Gear^.Timer:= GameTicks end; if (HHGear^.Message and gm_Left) <> 0 then move.isNegative:= true; if (HHGear^.Message and (gm_Left or gm_Right)) <> 0 then begin HHGear^.dX:= HHGear^.dX + (move * _0_2); dec(Gear^.Health, fuel div 5); Gear^.MsgParam:= Gear^.MsgParam or (HHGear^.Message and (gm_Left or gm_Right)); Gear^.Timer:= GameTicks end; // erases them all at once :-/ if (Gear^.Timer <> 0) and (GameTicks - Gear^.Timer > 250) then begin Gear^.Timer:= 0; Gear^.MsgParam:= 0 end; if Gear^.Health < 0 then Gear^.Health:= 0; if (GameTicks and $3F) = 0 then begin //AddCaption('Fuel: '+inttostr(round(Gear^.Health/20))+'%', cWhiteColor, capgrpAmmostate); if Gear^.Tex <> nil then FreeTexture(Gear^.Tex); Gear^.Tex:= RenderStringTex(trmsg[sidFuel] + ': ' + inttostr(round(Gear^.Health / 20)) + '%', cWhiteColor, fntSmall) end; if HHGear^.Message and (gm_Attack or gm_Up or gm_Precise or gm_Left or gm_Right) <> 0 then Gear^.State:= Gear^.State and not gsttmpFlag; HHGear^.Message:= HHGear^.Message and not (gm_Up or gm_Precise or gm_Left or gm_Right); HHGear^.State:= HHGear^.State or gstMoving; Gear^.X:= HHGear^.X; Gear^.Y:= HHGear^.Y; // For some reason I need to reapply followgear here, something else grabs it otherwise. if not bShowAmmoMenu then FollowGear:= HHGear; if ((Gear^.State and gsttmpFlag) = 0) or (HHGear^.dY < _0) then doStepHedgehogMoving(HHGear); if (Gear^.Health = 0) or (HHGear^.Damage <> 0) or CheckGearDrowning(HHGear) or (TurnTimeLeft = 0) // allow brief ground touches - to be fair on this, might need another counter or (((GameTicks and $1FF) = 0) and (not HHGear^.dY.isNegative) and TestCollisionYwithGear(HHGear, 1)) or ((Gear^.Message and gm_Attack) <> 0) then begin with HHGear^ do begin Message:= 0; Active:= true; State:= State or gstMoving end; DeleteGear(Gear); isCursorVisible:= false; ApplyAmmoChanges(PHedgehog(HHGear^.Hedgehog)^); // if Gear^.Tex <> nil then FreeTexture(Gear^.Tex); // Gear^.Tex:= RenderStringTex(trmsg[sidFuel] + ': ' + inttostr(round(Gear^.Health / 20)) + '%', cWhiteColor, fntSmall) //AddCaption(trmsg[sidFuel]+': '+inttostr(round(Gear^.Health/20))+'%', cWhiteColor, capgrpAmmostate); end end; //////////////////////////////////////////////////////////////////////////////// procedure doStepJetpack(Gear: PGear); var HHGear: PGear; begin Gear^.doStep:= @doStepJetpackWork; HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear; FollowGear:= HHGear; AfterAttack; with HHGear^ do begin State:= State and not gstAttacking; Message:= Message and not (gm_Attack or gm_Up or gm_Precise or gm_Left or gm_Right); if (dY < _0_1) and (dY > -_0_1) then begin Gear^.State:= Gear^.State or gsttmpFlag; dY:= dY - _0_2 end end end; //////////////////////////////////////////////////////////////////////////////// procedure doStepBirdyDisappear(Gear: PGear); begin AllInactive:= false; Gear^.Pos:= 0; if Gear^.Timer < 2000 then inc(Gear^.Timer, 1) else begin DeleteGear(Gear); end; end; //////////////////////////////////////////////////////////////////////////////// procedure doStepBirdyFly(Gear: PGear); var HHGear: PGear; fuel, i: LongInt; move: hwFloat; begin HHGear:= CurrentHedgehog^.Gear; move:= _0_1; fuel:= 50; if Gear^.Pos > 0 then dec(Gear^.Pos, 1) else if (HHGear^.Message and (gm_Left or gm_Right or gm_Up)) <> 0 then Gear^.Pos:= 500; if HHGear^.dX.isNegative then Gear^.Tag:= -1 else Gear^.Tag:= 1; if (HHGear^.Message and gm_Up) <> 0 then begin if (not HHGear^.dY.isNegative) or (HHGear^.Y > -_256) then HHGear^.dY:= HHGear^.dY - move; HHGear^.dY:= HHGear^.dY - move; dec(Gear^.Health, fuel); Gear^.MsgParam:= Gear^.MsgParam or gm_Up; end; if (HHGear^.Message and gm_Left) <> 0 then move.isNegative:= true; if (HHGear^.Message and (gm_Left or gm_Right)) <> 0 then begin HHGear^.dX:= HHGear^.dX + (move * _0_2); dec(Gear^.Health, fuel div 5); Gear^.MsgParam:= Gear^.MsgParam or (HHGear^.Message and (gm_Left or gm_Right)); end; if Gear^.Health < 0 then Gear^.Health:= 0; if ((GameTicks and $FF) = 0) and (Gear^.Health < 500) then for i:= ((500-Gear^.Health) div 250) downto 0 do AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtFeather); if (HHGear^.Message and gm_Attack <> 0) then begin HHGear^.Message := HHGear^.Message and not gm_Attack; if Gear^.FlightTime > 0 then begin AddGear(hwRound(Gear^.X), hwRound(Gear^.Y) + 32, gtEgg, 0, Gear^.dX * _0_5, Gear^.dY, 0); PlaySound(sndBirdyLay); dec(Gear^.FlightTime) end; end; if HHGear^.Message and (gm_Up or gm_Precise or gm_Left or gm_Right) <> 0 then Gear^.State:= Gear^.State and not gsttmpFlag; HHGear^.Message:= HHGear^.Message and not (gm_Up or gm_Precise or gm_Left or gm_Right); HHGear^.State:= HHGear^.State or gstMoving; Gear^.X:= HHGear^.X; Gear^.Y:= HHGear^.Y - int2hwFloat(32); // For some reason I need to reapply followgear here, something else grabs it otherwise. if not bShowAmmoMenu then FollowGear:= HHGear; if ((Gear^.State and gsttmpFlag) = 0) or (HHGear^.dY < _0) then doStepHedgehogMoving(HHGear); if (Gear^.Health = 0) or (HHGear^.Damage <> 0) or CheckGearDrowning(HHGear) or (TurnTimeLeft = 0) // allow brief ground touches - to be fair on this, might need another counter or (((GameTicks and $1FF) = 0) and (not HHGear^.dY.isNegative) and TestCollisionYwithGear(HHGear, 1)) or ((Gear^.Message and gm_Attack) <> 0) then begin with HHGear^ do begin Message:= 0; Active:= true; State:= State or gstMoving end; Gear^.State:= Gear^.State or gstAnimation or gstTmpFlag; if HHGear^.dY < _0 then begin Gear^.dX:= HHGear^.dX; Gear^.dY:= HHGear^.dY; end; Gear^.Timer:= 0; Gear^.doStep:= @doStepBirdyDisappear; CurAmmoGear:= nil; isCursorVisible:= false; AfterAttack; end end; //////////////////////////////////////////////////////////////////////////////// procedure doStepBirdyDescend(Gear: PGear); var HHGear: PGear; begin if Gear^.Timer > 0 then dec(Gear^.Timer, 1) else if CurrentHedgehog = nil then begin DeleteGear(Gear); AfterAttack; exit end; HHGear:= CurrentHedgehog^.Gear; HHGear^.Message:= HHGear^.Message and not (gm_Up or gm_Precise or gm_Left or gm_Right); if abs(hwRound(HHGear^.Y - Gear^.Y)) > 32 then begin if Gear^.Timer = 0 then Gear^.Y:= Gear^.Y + _0_1 end else if Gear^.Timer = 0 then begin Gear^.doStep:= @doStepBirdyFly; HHGear^.dY:= -_0_2 end end; procedure doStepBirdyAppear(Gear: PGear); begin Gear^.Pos:= 0; if Gear^.Timer < 2000 then inc(Gear^.Timer, 1) else begin Gear^.Timer:= 500; Gear^.dX:= _0; Gear^.dY:= _0; Gear^.State:= Gear^.State and not gstAnimation; Gear^.doStep:= @doStepBirdyDescend; end end; //////////////////////////////////////////////////////////////////////////////// procedure doStepBirdy(Gear: PGear); var HHGear: PGear; begin gear^.State:= gear^.State or gstAnimation and not gstTmpFlag; Gear^.doStep:= @doStepBirdyAppear; if CurrentHedgehog = nil then begin DeleteGear(Gear); exit end; HHGear:= CurrentHedgehog^.Gear; if HHGear^.dX.isNegative then Gear^.Tag:= -1 else Gear^.Tag:= 1; Gear^.Pos:= 0; AllInactive:= false; FollowGear:= HHGear; with HHGear^ do begin State:= State and not gstAttacking; Message:= Message and not (gm_Attack or gm_Up or gm_Precise or gm_Left or gm_Right) end end; //////////////////////////////////////////////////////////////////////////////// procedure doStepBigExplosionWork(Gear: PGear); var maxMovement: LongInt; begin inc(Gear^.Timer); if (Gear^.Timer and 5) = 0 then begin maxMovement := max(1, 13 - ((Gear^.Timer * 15) div 250)); ShakeCamera(maxMovement); end; if Gear^.Timer > 250 then DeleteGear(Gear); end; procedure doStepBigExplosion(Gear: PGear); var i: LongWord; gX,gY: LongInt; begin gX:= hwRound(Gear^.X); gY:= hwRound(Gear^.Y); AddVisualGear(gX, gY, vgtSmokeRing); for i:= 0 to 46 do AddVisualGear(gX, gY, vgtFire); for i:= 0 to 15 do AddVisualGear(gX, gY, vgtExplPart); for i:= 0 to 15 do AddVisualGear(gX, gY, vgtExplPart2); Gear^.doStep:= @doStepBigExplosionWork end; //////////////////////////////////////////////////////////////////////////////// procedure doStepEggWork(Gear: PGear); var vg: PVisualGear; i: LongInt; begin AllInactive:= false; Gear^.dX:= Gear^.dX; doStepFallingGear(Gear); // CheckGearDrowning(Gear); // already checked for in doStepFallingGear CalcRotationDirAngle(Gear); if (Gear^.State and gstCollision) <> 0 then begin doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 10, EXPLPoisoned or EXPLNoGfx); doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 10, EXPLPoisoned or EXPLNoGfx); PlaySound(sndEggBreak); AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtEgg); vg:= AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtEgg); if vg <> nil then vg^.Frame:= 2; for i:= 10 downto 0 do begin vg := AddVisualGear(hwRound(Gear^.X) - 3 + Random(6), hwRound(Gear^.Y) - 3 + Random(6), vgtDust); if vg <> nil then vg^.dX := vg^.dX + (Gear^.dX / 5); end; DeleteGear(Gear); exit end; end; procedure doStepPortal(Gear: PGear); var tmpGear, iterator: PGear; begin //if not AllInactive and (Gear^.IntersectGear <> nil) then if (Gear^.IntersectGear <> nil) then begin iterator:= GearsList; while iterator <> nil do begin if iterator^.Active and (iterator^.Kind <> gtPortal) and (hwRound(hwAbs(Gear^.X-iterator^.X)+hwAbs(Gear^.Y-iterator^.Y)) < Gear^.Radius) and ((hwAbs(Gear^.X-(iterator^.X+iterator^.dX))+hwAbs(Gear^.Y-(iterator^.Y+iterator^.dY))).QWordValue < (hwAbs(Gear^.X-iterator^.X)+hwAbs(Gear^.Y-iterator^.Y)).QWordValue) and (hwRound(Distance(Gear^.X-iterator^.X,Gear^.Y-iterator^.Y)) < Gear^.Radius) then // Let's check this one more closely begin iterator^.X:=Gear^.IntersectGear^.X+_128; iterator^.Y:=Gear^.IntersectGear^.Y+_128; iterator^.dX.isNegative:= not iterator^.dX.isNegative; end; iterator:= iterator^.NextGear; end; // do portal stuff end (* 2) From then on, if doStepPortal is called and a gear of a radius less than or equal to the portal is within X pixels of the portal (we could also check on moving toward the portal I guess, depends how accurate this needs to be) the portal will then locate the first other portal of the opposite type (there should only be one other one), and move that gear's X/Y to that other portal's location, and modify dX/dY to be relative to that other portal's orientation relative to this portal's orientation. This might require some tweaking with offsets of a few pixels to avoid getting gears stuck in land. *) end; procedure doStepMovingPortal(Gear: PGear); var x, y: LongInt;//, tx, ty, bx, by, tangle: LongInt; begin Gear^.X:= Gear^.X + Gear^.dX; Gear^.Y:= Gear^.Y + Gear^.dY; x:= hwRound(Gear^.X); y:= hwRound(Gear^.Y); if ((y and LAND_HEIGHT_MASK) = 0) and ((x and LAND_WIDTH_MASK) = 0) and ((Land[y, x] and $FF00) <> 0) then begin (* This is not quite doing what I want, but basically hoping to avoid portals just sitting out in midair Works ok for right angles, aaaand that's about it. The opposite approach could be taken, we could determine the angle of the land using sheepluva's code and snap the Angle/DirAngle to it. tangle:= Gear^.Angle+1024; if tangle > 2048 then dec(tangle,2048); tx:= hwRound(Gear^.X+SignAs(AngleSin(tangle), Gear^.dX)*_6); ty:= hwRound(Gear^.Y-AngleCos(tangle)*_6); bx:= hwRound(Gear^.X-SignAs(AngleSin(tangle), Gear^.dX)*_6); by:= hwRound(Gear^.Y+AngleCos(tangle)*_6); *) if ((Gear^.IntersectGear <> nil) and (hwRound(Distance(Gear^.X - Gear^.IntersectGear^.X,Gear^.Y-Gear^.IntersectGear^.Y)) < Gear^.Radius*2)) (*or (((ty and LAND_HEIGHT_MASK) = 0) and ((tx and LAND_WIDTH_MASK) = 0) and ((Land[ty, tx] and $FF00) = 0)) or (((by and LAND_HEIGHT_MASK) = 0) and ((bx and LAND_WIDTH_MASK) = 0) and ((Land[by, bx] and $FF00) = 0))*) then begin if CurrentHedgehog <> nil then if Gear^.IntersectGear = nil then CurrentHedgehog^.Ammo^[CurrentHedgehog^.CurSlot, CurrentHedgehog^.CurAmmo].Timer:= 1 else if Gear^.Tag = 2 then CurrentHedgehog^.Ammo^[CurrentHedgehog^.CurSlot, CurrentHedgehog^.CurAmmo].Timer:= 2 else CurrentHedgehog^.Ammo^[CurrentHedgehog^.CurSlot, CurrentHedgehog^.CurAmmo].Timer:= 1; DeleteGear(Gear) end else begin if CurrentHedgehog <> nil then if Gear^.Tag = 2 then CurrentHedgehog^.Ammo^[CurrentHedgehog^.CurSlot, CurrentHedgehog^.CurAmmo].Timer:= 1 else CurrentHedgehog^.Ammo^[CurrentHedgehog^.CurSlot, CurrentHedgehog^.CurAmmo].Timer:= 2; inc(Gear^.Tag); Gear^.doStep:= @doStepPortal; if Gear^.IntersectGear <> nil then begin Gear^.IntersectGear^.IntersectGear:= Gear; SetAllToActive end end end else if (y > cWaterLine + cVisibleWater + Gear^.Radius) or (y < -LAND_WIDTH) or (x > LAND_WIDTH + LAND_WIDTH) or (x < -LAND_WIDTH) then begin if CurrentHedgehog <> nil then if Gear^.IntersectGear = nil then CurrentHedgehog^.Ammo^[CurrentHedgehog^.CurSlot, CurrentHedgehog^.CurAmmo].Timer:= 1 else if Gear^.Tag = 2 then CurrentHedgehog^.Ammo^[CurrentHedgehog^.CurSlot, CurrentHedgehog^.CurAmmo].Timer:= 2 else CurrentHedgehog^.Ammo^[CurrentHedgehog^.CurSlot, CurrentHedgehog^.CurAmmo].Timer:= 1; DeleteGear(Gear); end; end; procedure doStepPiano(Gear: PGear); var r0, r1: LongInt; begin AllInactive:= false; if (CurrentHedgehog <> nil) and (CurrentHedgehog^.Gear <> nil) and ((CurrentHedgehog^.Gear^.Message and gm_Slot) <> 0) then begin case CurrentHedgehog^.Gear^.MsgParam of 0: PlaySound(sndPiano0); 1: PlaySound(sndPiano1); 2: PlaySound(sndPiano2); 3: PlaySound(sndPiano3); 4: PlaySound(sndPiano4); 5: PlaySound(sndPiano5); 6: PlaySound(sndPiano6); 7: PlaySound(sndPiano7); else PlaySound(sndPiano8); end; CurrentHedgehog^.Gear^.MsgParam:= 0; CurrentHedgehog^.Gear^.Message:= CurrentHedgehog^.Gear^.Message and not gm_Slot; end; if ((Gear^.Pos = 3) and ((GameFlags and gfSolidLand) <> 0)) or (Gear^.Pos = 20) then // bounce up to 20 times (3 times on gameflagged solid land) before dropping past landscape begin Gear^.dY:= Gear^.dY + cGravity * 3; Gear^.Y:= Gear^.Y + Gear^.dY; CheckGearDrowning(Gear); exit end; doStepFallingGear(Gear); if (Gear^.State and gstDrowning) <> 0 then begin if CurrentHedgehog^.Gear <> nil then begin // Drown the hedgehog. Could also just delete it, but hey, this gets a caption CurrentHedgehog^.Gear^.Active:= true; CurrentHedgehog^.Gear^.X:= Gear^.X; CurrentHedgehog^.Gear^.Y:=int2hwFloat(cWaterLine+cVisibleWater)+_128; CurrentHedgehog^.Unplaced:= false end; ResumeMusic end else if (Gear^.State and gstCollision) <> 0 then begin r0:= GetRandom(21); r1:= GetRandom(21); doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 80 + r0, EXPLAutoSound); doMakeExplosion(hwRound(Gear^.X) - 30 - r0, hwRound(Gear^.Y) + 40, 40 + r1, EXPLAutoSound); doMakeExplosion(hwRound(Gear^.X) + 30 + r1, hwRound(Gear^.Y) + 40, 40 + r0, EXPLAutoSound); Gear^.dY:= -_1; Gear^.Pos:= Gear^.Pos + 1; end else Gear^.dY:= Gear^.dY + cGravity * 2; // let it fall faster so itdoesn't take too long for the whole attack end; //////////////////////////////////////////////////////////////////////////////// procedure doStepSineGunShotWork(Gear: PGear); var x, y, rX, rY, t, tmp, initHealth: LongWord; oX, oY, ldX, ldY, sdX, sdY, sine, lx, ly, amp: hwFloat; justCollided: boolean; begin AllInactive:= false; initHealth:= Gear^.Health; lX:= Gear^.X; lY:= Gear^.Y; ldX:= Gear^.dX; ldY:= Gear^.dY; sdy:= _0_5/Distance(Gear^.dX,Gear^.dY); ldX:= ldX * sdy; ldY:= ldY * sdy; sdY:= hwAbs(ldX) + hwAbs(ldY); sdX:= _1 - hwAbs(ldX/sdY); sdY:= _1 - hwAbs(ldY/sdY); if (ldX.isNegative = ldY.isNegative) then sdY:= -sdY; // initial angle depends on current GameTicks t:= GameTicks mod 4096; // used for a work-around detection of area that is within land array, but outside borders justCollided:= false; repeat lX:= lX + ldX; lY:= lY + ldY; oX:= Gear^.X; oY:= Gear^.Y; rX:= hwRound(oX); rY:= hwRound(oY); tmp:= t mod 4096; amp:= _128 * (_1 - hwSqr(int2hwFloat(Gear^.Health)/initHealth)); sine:= amp * AngleSin(tmp mod 2048); sine.isNegative:= (tmp < 2048); inc(t,Gear^.Health div 313); Gear^.X:= lX + (sine * sdX); Gear^.Y:= ly + (sine * sdY); Gear^.dX:= Gear^.X - oX; Gear^.dY:= Gear^.Y - oY; x:= hwRound(Gear^.X); y:= hwRound(Gear^.Y); // if borders are on, stop outside land array if hasBorder and (((x and LAND_WIDTH_MASK) <> 0) or ((y and LAND_HEIGHT_MASK) <> 0)) then begin Gear^.Damage:= 0; Gear^.Health:= 0; end else begin if (rY <= cWaterLine) or (y <= cWaterLine) then begin if ((y and LAND_HEIGHT_MASK) = 0) and ((x and LAND_WIDTH_MASK) = 0) and (Land[y, x] <> 0) then begin if justCollided then begin Gear^.Damage:= 0; Gear^.Health:= 0; end else begin inc(Gear^.Damage,3); justCollided:= true; end; end else justCollided:= false; // kick nearby hogs, dig tunnel and add some fire // if at least 5 collisions occured if Gear^.Damage > 0 then begin DrawExplosion(rX,rY,Gear^.Radius); // kick nearby hogs AmmoShove(Gear, 35, 50); dec(Gear^.Health, Gear^.Damage); Gear^.Damage:= 0; // add some fire to the tunnel if getRandom(6) = 0 then AddGear(x-Gear^.Radius+getRandom(2*Gear^.Radius), y-getRandom(Gear^.Radius+1), gtFlame, gsttmpFlag, _0, _0, 0); end; if getRandom(100) = 0 then AddGear(x, y, gtSmokeTrace, 0, _0, _0, 0); end // if underwater get additional damage else dec(Gear^.Health, 5); end; dec(Gear^.Health); // decrease bullet size towards the end if (Gear^.Radius > 4) then begin if (Gear^.Health <= (initHealth div 3)) then dec(Gear^.Radius) end else if (Gear^.Radius > 3) then begin if (Gear^.Health <= (initHealth div 4)) then dec(Gear^.Radius) end else if (Gear^.Radius > 2) then begin if (Gear^.Health <= (initHealth div 5)) then dec(Gear^.Radius) end else if (Gear^.Radius > 1) then begin if (Gear^.Health <= (initHealth div 6)) then dec(Gear^.Radius) end; until (Gear^.Health <= 0); DeleteGear(Gear); AfterAttack; end; procedure doStepSineGunShot(Gear: PGear); var HHGear: PGear; begin PlaySound(sndSineGun); // push the shooting Hedgehog back HHGear:= CurrentHedgehog^.Gear; Gear^.dX.isNegative:= not Gear^.dX.isNegative; Gear^.dY.isNegative:= not Gear^.dY.isNegative; HHGear^.dX:= Gear^.dX; HHGear^.dY:= Gear^.dY; AmmoShove(Gear, 0, 80); Gear^.dX.isNegative:= not Gear^.dX.isNegative; Gear^.dY.isNegative:= not Gear^.dY.isNegative; Gear^.doStep:= @doStepSineGunShotWork end;