(* * Hedgewars, a free turn based strategy game * Copyright (c) 2004-2011 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 HHHurt(Hedgehog: PHedgehog; Source: TDamageSource); begin if (Source = dsFall) or (Source = dsExplosion) then case random(3) of 0: PlaySound(sndOoff1, Hedgehog^.Team^.voicepack); 1: PlaySound(sndOoff2, Hedgehog^.Team^.voicepack); 2: PlaySound(sndOoff3, Hedgehog^.Team^.voicepack); end else if (Source = dsPoison) then case random(2) of 0: PlaySound(sndPoisonCough, Hedgehog^.Team^.voicepack); 1: PlaySound(sndPoisonMoan, Hedgehog^.Team^.voicepack); end else case random(4) of 0: PlaySound(sndOw1, Hedgehog^.Team^.voicepack); 1: PlaySound(sndOw2, Hedgehog^.Team^.voicepack); 2: PlaySound(sndOw3, Hedgehog^.Team^.voicepack); 3: PlaySound(sndOw4, Hedgehog^.Team^.voicepack); end end; // Shouldn't more of this ammo switching stuff be moved to uAmmos ? function ChangeAmmo(HHGear: PGear): boolean; var slot, i: Longword; ammoidx: LongInt; begin ChangeAmmo:= false; slot:= HHGear^.MsgParam; with HHGear^.Hedgehog^ do begin HHGear^.Message:= HHGear^.Message and not gmSlot; ammoidx:= 0; if ((HHGear^.State and (gstAttacking or gstAttacked)) <> 0) or ((MultiShootAttacks > 0) and ((Ammoz[CurAmmoType].Ammo.Propz and ammoprop_NoRoundEnd) = 0)) or ((HHGear^.State and gstHHDriven) = 0) then exit; ChangeAmmo:= true; while (ammoidx < cMaxSlotAmmoIndex) and (Ammo^[slot, ammoidx].AmmoType <> CurAmmoType) do inc(ammoidx); if ((Ammoz[CurAmmoType].Ammo.Propz and ammoprop_NoRoundEnd) <> 0) and (MultiShootAttacks > 0) then OnUsedAmmo(HHGear^.Hedgehog^); MultiShootAttacks:= 0; HHGear^.Message:= HHGear^.Message and not (gmLJump or gmHJump); if Ammoz[CurAmmoType].Slot = slot then begin i:= 0; repeat inc(ammoidx); if (ammoidx > cMaxSlotAmmoIndex) then begin inc(i); CurAmmoType:= amNothing; ammoidx:= -1; //TryDo(i < 2, 'Engine bug: no ammo in current slot', true) end; until (i = 1) or ((Ammo^[slot, ammoidx].Count > 0) and (Team^.Clan^.TurnNumber > Ammoz[Ammo^[slot, ammoidx].AmmoType].SkipTurns)) end else begin i:= 0; // check whether there is ammo in slot while (i <= cMaxSlotAmmoIndex) and ((Ammo^[slot, i].Count = 0) or (Team^.Clan^.TurnNumber <= Ammoz[Ammo^[slot, i].AmmoType].SkipTurns)) do inc(i); if i <= cMaxSlotAmmoIndex then ammoidx:= i else ammoidx:= -1 end; if ammoidx >= 0 then CurAmmoType:= Ammo^[slot, ammoidx].AmmoType; end end; procedure HHSetWeapon(HHGear: PGear); var t: LongInt; weap: TAmmoType; Hedgehog: PHedgehog; s: boolean; begin s:= false; weap:= TAmmoType(HHGear^.MsgParam); Hedgehog:= HHGear^.Hedgehog; if Hedgehog^.Team^.Clan^.TurnNumber <= Ammoz[weap].SkipTurns then exit; // weapon is not activated yet HHGear^.MsgParam:= Ammoz[weap].Slot; t:= cMaxSlotAmmoIndex; HHGear^.Message:= HHGear^.Message and not gmWeapon; with Hedgehog^ do while (CurAmmoType <> weap) and (t >= 0) do begin s:= ChangeAmmo(HHGear); dec(t) end; if s then ApplyAmmoChanges(HHGear^.Hedgehog^) end; procedure HHSetTimer(Gear: PGear); var CurWeapon: PAmmo; color: LongWord; begin Gear^.Message:= Gear^.Message and not gmTimer; CurWeapon:= GetAmmoEntry(Gear^.Hedgehog^); with Gear^.Hedgehog^ do if ((Gear^.Message and gmPrecise) <> 0) and ((CurWeapon^.Propz and ammoprop_SetBounce) <> 0) then begin color:= Gear^.Hedgehog^.Team^.Clan^.Color; case Gear^.MsgParam of 1: begin AddCaption(format(trmsg[sidBounce], trmsg[sidBounce1]), color, capgrpAmmostate); CurWeapon^.Bounciness:= 350; end; 2: begin AddCaption(format(trmsg[sidBounce], trmsg[sidBounce2]), color, capgrpAmmostate); CurWeapon^.Bounciness:= 700; end; 3: begin AddCaption(format(trmsg[sidBounce], trmsg[sidBounce3]), color, capgrpAmmostate); CurWeapon^.Bounciness:= 1000; end; 4: begin AddCaption(format(trmsg[sidBounce], trmsg[sidBounce4]), color, capgrpAmmostate); CurWeapon^.Bounciness:= 2000; end; 5: begin AddCaption(format(trmsg[sidBounce], trmsg[sidBounce5]), color, capgrpAmmostate); CurWeapon^.Bounciness:= 4000; end end end else if (CurWeapon^.Propz and ammoprop_Timerable) <> 0 then begin CurWeapon^.Timer:= 1000 * Gear^.MsgParam; with CurrentTeam^ do ApplyAmmoChanges(Hedgehogs[CurrHedgehog]); end; end; procedure Attack(Gear: PGear); var xx, yy, newDx, newDy, lx, ly: hwFloat; speech: PVisualGear; newGear: PGear; CurWeapon: PAmmo; altUse: boolean; elastic: hwFloat; begin newGear:= nil; bShowFinger:= false; CurWeapon:= GetAmmoEntry(Gear^.Hedgehog^); with Gear^, Gear^.Hedgehog^ do begin if ((State and gstHHDriven) <> 0)and ((State and (gstAttacked or gstHHChooseTarget)) = 0) and (((State and gstMoving) = 0) or (CurAmmoType = amTeleport) or // Allow attacks while moving on ammo with AltAttack ((CurAmmoGear <> nil) and ((Ammoz[CurAmmoGear^.AmmoType].Ammo.Propz and ammoprop_AltAttack) <> 0)) or ((Ammoz[CurAmmoType].Ammo.Propz and ammoprop_AttackInMove) <> 0)) and ((TargetPoint.X <> NoPointX) or ((Ammoz[CurAmmoType].Ammo.Propz and ammoprop_NeedTarget) = 0)) then begin State:= State or gstAttacking; if Power = cMaxPower then Message:= Message and not gmAttack else if (Ammoz[CurAmmoType].Ammo.Propz and ammoprop_Power) = 0 then Message:= Message and not gmAttack else begin if Power = 0 then begin AttackBar:= CurrentTeam^.AttackBar; PlaySound(sndThrowPowerUp) end; inc(Power) end; if ((Message and gmAttack) <> 0) then exit; if (Ammoz[CurAmmoType].Ammo.Propz and ammoprop_Power) <> 0 then begin StopSound(sndThrowPowerUp); PlaySound(sndThrowRelease); end; xx:= SignAs(AngleSin(Angle), dX); yy:= -AngleCos(Angle); lx:= X + int2hwfloat(round(GetLaunchX(CurAmmoType, hwSign(dX), Angle))); ly:= Y + int2hwfloat(round(GetLaunchY(CurAmmoType, Angle))); if ((Gear^.State and gstHHHJump) <> 0) and (not cArtillery) then xx:= - xx; if Ammoz[CurAmmoType].Ammo.AttackVoice <> sndNone then AddVoice(Ammoz[CurAmmoType].Ammo.AttackVoice, CurrentTeam^.voicepack); // Initiating alt attack if (CurAmmoGear <> nil) and ((Ammoz[CurAmmoGear^.AmmoType].Ammo.Propz and ammoprop_AltAttack) <> 0) and ((Gear^.Message and gmLJump) <> 0) and ((Ammoz[CurAmmoType].Ammo.Propz and ammoprop_AltUse) <> 0) then begin newDx:= dX / _2; newDy:= dY / _2; altUse:= true; end else begin newDx:= xx*Power/cPowerDivisor; newDy:= yy*Power/cPowerDivisor; altUse:= false end; case CurAmmoType of amGrenade: newGear:= AddGear(hwRound(lx), hwRound(ly), gtGrenade, 0, newDx, newDy, CurWeapon^.Timer); amMolotov: newGear:= AddGear(hwRound(lx), hwRound(ly), gtMolotov, 0, newDx, newDy, 0); amClusterBomb: newGear:= AddGear(hwRound(lx), hwRound(ly), gtClusterBomb, 0, newDx, newDy, CurWeapon^.Timer); amGasBomb: newGear:= AddGear(hwRound(lx), hwRound(ly), gtGasBomb, 0, newDx, newDy, CurWeapon^.Timer); amBazooka: newGear:= AddGear(hwRound(lx), hwRound(ly), gtShell, 0, newDx, newDy, 0); amSnowball: newGear:= AddGear(hwRound(lx), hwRound(ly), gtSnowball, 0, newDx, newDy, 0); amBee: newGear:= AddGear(hwRound(lx), hwRound(ly), gtBee, 0, newDx, newDy, 0); amShotgun: begin PlaySound(sndShotgunReload); newGear:= AddGear(hwRound(lx), hwRound(ly), gtShotgunShot, 0, xx * _0_5, yy * _0_5, 0); end; amPickHammer: newGear:= AddGear(hwRound(lx), hwRound(ly) + cHHRadius, gtPickHammer, 0, _0, _0, 0); amSkip: ParseCommand('/skip', true); amRope: newGear:= AddGear(hwRound(lx), hwRound(ly), gtRope, 0, xx, yy, 0); amMine: if altUse then newGear:= AddGear(hwRound(lx) + hwSign(dX) * 7, hwRound(ly), gtMine, gstWait, newDx, newDy, 3000) else newGear:= AddGear(hwRound(lx) + hwSign(dX) * 7, hwRound(ly), gtMine, gstWait, SignAs(_0_02, dX), _0, 3000); amSMine: newGear:= AddGear(hwRound(lx), hwRound(ly), gtSMine, 0, xx*Power/cPowerDivisor, yy*Power/cPowerDivisor, 0); amDEagle: newGear:= AddGear(hwRound(lx + xx * cHHRadius), hwRound(ly + yy * cHHRadius), gtDEagleShot, 0, xx * _0_5, yy * _0_5, 0); amSineGun: newGear:= AddGear(hwRound(lx + xx * cHHRadius), hwRound(ly + yy * cHHRadius), gtSineGunShot, 0, xx * _0_5, yy * _0_5, 0); amPortalGun: begin newGear:= AddGear(hwRound(lx + xx * cHHRadius), hwRound(ly + yy * cHHRadius), gtPortal, 0, xx * _0_6, yy * _0_6, // set selected color CurWeapon^.Pos); end; amSniperRifle: begin PlaySound(sndSniperReload); newGear:= AddGear(hwRound(lx + xx * cHHRadius), hwRound(ly + yy * cHHRadius), gtSniperRifleShot, 0, xx * _0_5, yy * _0_5, 0); end; amDynamite: newGear:= AddGear(hwRound(lx) + hwSign(dX) * 7, hwRound(ly), gtDynamite, 0, SignAs(_0_03, dX), _0, 5000); amFirePunch: newGear:= AddGear(hwRound(lx) + hwSign(dX) * 10, hwRound(ly), gtFirePunch, 0, xx, _0, 0); amWhip: begin newGear:= AddGear(hwRound(lx) + hwSign(dX) * 10, hwRound(ly), gtWhip, 0, SignAs(_1, dX), - _0_8, 0); PlaySound(sndWhipCrack) end; amHammer: begin newGear:= AddGear(hwRound(lx) + hwSign(dX) * 10, hwRound(ly), gtHammer, 0, SignAs(_1, dX), - _0_8, 0); PlaySound(sndWhack) end; amBaseballBat: begin newGear:= AddGear(hwRound(lx) + hwSign(dX) * 10, hwRound(ly), gtShover, gsttmpFlag, xx * _0_5, yy * _0_5, 0); PlaySound(sndBaseballBat) // TODO: Only play if something is hit? end; amParachute: begin newGear:= AddGear(hwRound(lx), hwRound(ly), gtParachute, 0, _0, _0, 0); PlaySound(sndParachute) end; // we save CurWeapon^.Pos (in this case: cursor direction) by using it as (otherwise irrelevant) X value of the new gear. amAirAttack: newGear:= AddGear(CurWeapon^.Pos, 0, gtAirAttack, 0, _0, _0, 0); amMineStrike: newGear:= AddGear(CurWeapon^.Pos, 0, gtAirAttack, 1, _0, _0, 0); amDrillStrike: newGear:= AddGear(CurWeapon^.Pos, 0, gtAirAttack, 3, _0, _0, CurWeapon^.Timer); amNapalm: newGear:= AddGear(CurWeapon^.Pos, 0, gtAirAttack, 2, _0, _0, 0); amBlowTorch: newGear:= AddGear(hwRound(lx), hwRound(ly), gtBlowTorch, 0, SignAs(_0_5, dX), _0, 0); amGirder: newGear:= AddGear(0, 0, gtGirder, CurWeapon^.Pos, _0, _0, 0); amTeleport: newGear:= AddGear(CurWeapon^.Pos, 0, gtTeleport, 0, _0, _0, 0); amSwitch: newGear:= AddGear(hwRound(lx), hwRound(ly), gtSwitcher, 0, _0, _0, 0); amMortar: begin playSound(sndMortar); newGear:= AddGear(hwRound(lx), hwRound(ly), gtMortar, 0, xx*cMaxPower/cPowerDivisor, yy*cMaxPower/cPowerDivisor, 0); end; amRCPlane: begin newGear:= AddGear(hwRound(lx), hwRound(ly), gtRCPlane, 0, xx * cMaxPower / cPowerDivisor / 4, yy * cMaxPower / cPowerDivisor / 4, 0); newGear^.SoundChannel:= LoopSound(sndRCPlane, nil) end; amKamikaze: newGear:= AddGear(hwRound(lx), hwRound(ly), gtKamikaze, 0, xx * _0_5, yy * _0_5, 0); amCake: newGear:= AddGear(hwRound(lx) + hwSign(dX) * 3, hwRound(ly), gtCake, 0, xx, _0, 0); amSeduction: newGear:= AddGear(hwRound(lx), hwRound(ly), gtSeduction, 0, _0, _0, 0); amWatermelon: newGear:= AddGear(hwRound(lx), hwRound(ly), gtWatermelon, 0, newDx, newDy, CurWeapon^.Timer); amHellishBomb: newGear:= AddGear(hwRound(lx), hwRound(ly), gtHellishBomb, 0, newDx, newDy, 0); amDrill: newGear:= AddGear(hwRound(lx), hwRound(ly), gtDrill, 0, newDx, newDy, 0); amBallgun: newGear:= AddGear(hwRound(X), hwRound(Y), gtBallgun, 0, xx * _0_5, yy * _0_5, 0); amJetpack: newGear:= AddGear(hwRound(lx), hwRound(ly), gtJetpack, 0, _0, _0, 0); amBirdy: begin PlaySound(sndWhistle); newGear:= AddGear(hwRound(lx), hwRound(ly) - 32, gtBirdy, 0, _0, _0, 0); end; amLowGravity: begin PlaySound(sndLowGravity); cGravity:= cMaxWindSpeed; cGravityf:= 0.00025 end; amExtraDamage:begin PlaySound(sndHellishImpact4); cDamageModifier:= _1_5 end; amInvulnerable: Invulnerable:= true; amExtraTime: begin PlaySound(sndSwitchHog); TurnTimeLeft:= TurnTimeLeft + 30000 end; amLaserSight: cLaserSighting:= true; amVampiric: begin PlaySound(sndOw1, Team^.voicepack); cVampiric:= true; end; amPiano: begin // Tuck the hedgehog away until the piano attack is completed Unplaced:= true; X:= _0; Y:= _0; newGear:= AddGear(TargetPoint.X, 0, gtPiano, 0, _0, _0, 0); PauseMusic end; amFlamethrower: newGear:= AddGear(hwRound(X), hwRound(Y), gtFlamethrower, 0, xx * _0_5, yy * _0_5, 0); amLandGun: newGear:= AddGear(hwRound(X), hwRound(Y), gtLandGun, 0, xx * _0_5, yy * _0_5, 0); amResurrector: begin newGear:= AddGear(hwRound(lx), hwRound(ly), gtResurrector, 0, _0, _0, 0); newGear^.SoundChannel := LoopSound(sndResurrector); end; //amMelonStrike: AddGear(CurWeapon^.Pos, 0, gtAirAttack, 4, _0, _0, 0); amStructure: newGear:= AddGear(hwRound(lx) + hwSign(dX) * 7, hwRound(ly), gtStructure, gstWait, SignAs(_0_02, dX), _0, 3000); amTardis: newGear:= AddGear(hwRound(X), hwRound(Y), gtTardis, 0, _0, _0, 5000); end; case CurAmmoType of amGrenade, amMolotov, amClusterBomb, amGasBomb, amBazooka, amSnowball, amBee, amSMine, amMortar, amWatermelon, amHellishBomb, amDrill, amPiano: FollowGear:= newGear; amShotgun, amPickHammer, amRope, amDEagle, amSineGun, amSniperRifle, amFirePunch, amWhip, amHammer, amBaseballBat, amParachute, amBlowTorch, amGirder, amTeleport, amSwitch, amRCPlane, amKamikaze, amCake, amSeduction, amBallgun, amJetpack, amBirdy, amFlamethrower, amLandGun, amResurrector, amStructure, amTardis: CurAmmoGear:= newGear; end; if Ammoz[CurAmmoType].Ammo.Propz and ammoprop_NeedTarget <> 0 then begin newGear^.Target.X:= TargetPoint.X; newGear^.Target.Y:= TargetPoint.Y end; // Clear FollowGear if using on a rope/parachute/saucer etc so focus stays with the hog's movement if altUse then FollowGear:= nil; if (newGear <> nil) and ((Ammoz[newGear^.AmmoType].Ammo.Propz and ammoprop_SetBounce) <> 0) then begin elastic:= int2hwfloat(CurWeapon^.Bounciness) / _1000; if elastic < _1 then newGear^.Elasticity:= newGear^.Elasticity * elastic else if elastic > _1 then newGear^.Elasticity:= _1 - ((_1-newGear^.Elasticity) / elastic); (* Experimented with friction modifier. Didn't seem helpful fric:= int2hwfloat(CurWeapon^.Bounciness) / _250; if fric < _1 then newGear^.Friction:= newGear^.Friction * fric else if fric > _1 then newGear^.Friction:= _1 - ((_1-newGear^.Friction) / fric)*) end; uStats.AmmoUsed(CurAmmoType); if not (SpeechText = '') then begin speech:= AddVisualGear(0, 0, vgtSpeechBubble); if speech <> nil then begin speech^.Text:= SpeechText; speech^.Hedgehog:= Gear^.Hedgehog; speech^.FrameTicks:= SpeechType; end; SpeechText:= '' end; Power:= 0; if (CurAmmoGear <> nil) and ((Ammoz[CurAmmoType].Ammo.Propz and ammoprop_AltUse) = 0){check for dropping ammo from rope} then begin Message:= Message or gmAttack; CurAmmoGear^.Message:= Message end else begin if not CurrentTeam^.ExtDriven and ((Ammoz[CurAmmoType].Ammo.Propz and ammoprop_Power) <> 0) then SendIPC('a'); AfterAttack; end end else Message:= Message and not gmAttack; end; TargetPoint.X := NoPointX; ScriptCall('onHogAttack'); end; procedure AfterAttack; var s: shortstring; a: TAmmoType; begin with CurrentHedgehog^.Gear^, CurrentHedgehog^ do begin a:= CurAmmoType; State:= State and not gstAttacking; if (Ammoz[a].Ammo.Propz and ammoprop_Effect) = 0 then begin Inc(MultiShootAttacks); if (Ammoz[a].Ammo.NumPerTurn >= MultiShootAttacks) then begin s:= inttostr(Ammoz[a].Ammo.NumPerTurn - MultiShootAttacks + 1); AddCaption(format(trmsg[sidRemaining], s), cWhiteColor, capgrpAmmostate); end; if (Ammoz[a].Ammo.NumPerTurn >= MultiShootAttacks) or ((GameFlags and gfMultiWeapon) <> 0) then begin isInMultiShoot:= true end else begin OnUsedAmmo(CurrentHedgehog^); if ((Ammoz[a].Ammo.Propz and ammoprop_NoRoundEnd) = 0) and (((GameFlags and gfInfAttack) = 0) or PlacingHogs) then begin if TagTurnTimeLeft = 0 then TagTurnTimeLeft:= TurnTimeLeft; TurnTimeLeft:=(Ammoz[a].TimeAfterTurn * cGetAwayTime) div 100; end; if ((Ammoz[a].Ammo.Propz and ammoprop_NoRoundEnd) = 0) then State:= State or gstAttacked; if (Ammoz[a].Ammo.Propz and ammoprop_NoRoundEnd) <> 0 then ApplyAmmoChanges(CurrentHedgehog^) end; end else begin OnUsedAmmo(CurrentHedgehog^); ApplyAmmoChanges(CurrentHedgehog^); end; AttackBar:= 0 end end; //////////////////////////////////////////////////////////////////////////////// procedure doStepHedgehogDead(Gear: PGear); const frametime = 200; timertime = frametime * 6; begin if Gear^.Hedgehog^.Unplaced then exit; if Gear^.Timer > 1 then begin AllInactive:= false; dec(Gear^.Timer); if (Gear^.Timer mod frametime) = 0 then inc(Gear^.Pos) end else if Gear^.Timer = 1 then begin Gear^.State:= Gear^.State or gstNoDamage; doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 30, CurrentHedgehog, EXPLAutoSound); AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtGrave, 0, _0, _0, 0)^.Hedgehog:= Gear^.Hedgehog; DeleteGear(Gear); SetAllToActive end else // Gear^.Timer = 0 begin AllInactive:= false; Gear^.Z:= cCurrHHZ; RemoveGearFromList(Gear); InsertGearToList(Gear); PlaySound(sndByeBye, Gear^.Hedgehog^.Team^.voicepack); Gear^.Pos:= 0; Gear^.Timer:= timertime end end; //////////////////////////////////////////////////////////////////////////////// procedure doStepHedgehogGone(Gear: PGear); const frametime = 65; timertime = frametime * 11; begin if Gear^.Hedgehog^.Unplaced then exit; if Gear^.Timer > 1 then begin AllInactive:= false; dec(Gear^.Timer); if (Gear^.Timer mod frametime) = 0 then inc(Gear^.Pos) end else if Gear^.Timer = 1 then begin DeleteGear(Gear); SetAllToActive end else // Gear^.Timer = 0 begin AllInactive:= false; Gear^.Z:= cCurrHHZ; RemoveGearFromList(Gear); InsertGearToList(Gear); PlaySound(sndByeBye, Gear^.Hedgehog^.Team^.voicepack); PlaySound(sndWarp); Gear^.Pos:= 0; Gear^.Timer:= timertime end end; //////////////////////////////////////////////////////////////////////////////// procedure PickUp(HH, Gear: PGear); var s: shortstring; a: TAmmoType; i: LongInt; vga: PVisualGear; begin Gear^.Message:= gmDestroy; PlaySound(sndShotgunReload); if (Gear^.Pos and posCaseExplode) <> 0 then if (Gear^.Pos and posCasePoison) <> 0 then doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 25, HH^.Hedgehog, EXPLAutoSound + EXPLPoisoned) else doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 25, HH^.Hedgehog, EXPLAutoSound) else if (Gear^.Pos and posCasePoison) <> 0 then doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 25, HH^.Hedgehog, EXPLAutoSound + EXPLPoisoned + EXPLNoDamage) else case Gear^.Pos of posCaseUtility, posCaseAmmo: begin if Gear^.AmmoType <> amNothing then a:= Gear^.AmmoType else begin for i:= 0 to GameTicks and $7F do GetRandom(2); // Burn some random numbers if Gear^.Pos = posCaseUtility then a:= GetUtility else a:= GetAmmo end; AddAmmo(HH^.Hedgehog^, a); // Possibly needs to check shared clan ammo game flag once added. // On the other hand, no obvious reason that clan members shouldn't know what ammo another clan member picked up if (not (HH^.Hedgehog^.Team^.ExtDriven or (HH^.Hedgehog^.BotLevel > 0))) or (HH^.Hedgehog^.Team^.Clan^.ClanIndex = LocalClan) or (GameType = gmtDemo) then begin s:= trammo[Ammoz[a].NameId] + ' (+' + IntToStr(Ammoz[a].NumberInCase) + ')'; AddCaption(s, HH^.Hedgehog^.Team^.Clan^.Color, capgrpAmmoinfo); // show ammo icon vga:= AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtAmmo); if vga <> nil then vga^.Frame:= Longword(a); end; end; posCaseHealth: begin inc(HH^.Health, Gear^.Health); HH^.Hedgehog^.Effects[hePoisoned] := false; str(Gear^.Health, s); s:= '+' + s; AddCaption(s, HH^.Hedgehog^.Team^.Clan^.Color, capgrpAmmoinfo); RenderHealth(HH^.Hedgehog^); RecountTeamHealth(HH^.Hedgehog^.Team); i:= 0; while i < Gear^.Health do begin vga:= AddVisualGear(hwRound(HH^.X), hwRound(HH^.Y), vgtStraightShot); if vga <> nil then with vga^ do begin Tint:= $00FF00FF; State:= ord(sprHealth) end; inc(i, 5); end; end; end end; const StepTicks: LongWord = 0; procedure HedgehogStep(Gear: PGear); var PrevdX: LongInt; CurWeapon: PAmmo; begin CurWeapon:= GetAmmoEntry(Gear^.Hedgehog^); if ((Gear^.State and (gstAttacking or gstMoving)) = 0) then begin if isCursorVisible then with Gear^.Hedgehog^ do with CurWeapon^ do begin if (Gear^.Message and gmLeft ) <> 0 then Pos:= (Pos - 1 + Ammoz[AmmoType].PosCount) mod Ammoz[AmmoType].PosCount else if (Gear^.Message and gmRight ) <> 0 then Pos:= (Pos + 1) mod Ammoz[AmmoType].PosCount else exit; StepTicks:= 200; exit end; if ((Gear^.Message and gmAnimate) <> 0) then begin Gear^.Message:= 0; Gear^.State:= Gear^.State or gstAnimation; Gear^.Tag:= Gear^.MsgParam; Gear^.Timer:= 0; Gear^.Pos:= 0 end; if ((Gear^.Message and gmLJump ) <> 0) then begin Gear^.Message:= Gear^.Message and not gmLJump; DeleteCI(Gear); if not TestCollisionYwithGear(Gear, -1) then if not TestCollisionXwithXYShift(Gear, _0, -2, hwSign(Gear^.dX)) then Gear^.Y:= Gear^.Y - _2 else if not TestCollisionXwithXYShift(Gear, _0, -1, hwSign(Gear^.dX)) then Gear^.Y:= Gear^.Y - _1; if not (TestCollisionXwithGear(Gear, hwSign(Gear^.dX)) or TestCollisionYwithGear(Gear, -1)) then begin Gear^.dY:= -_0_15; if not cArtillery then Gear^.dX:= SignAs(_0_15, Gear^.dX); Gear^.State:= Gear^.State or gstMoving or gstHHJumping; PlaySound(sndJump1, Gear^.Hedgehog^.Team^.voicepack); exit end; end; if ((Gear^.Message and gmHJump ) <> 0) then begin DeleteCI(Gear); Gear^.Message:= Gear^.Message and not gmHJump; Gear^.dY:= -_0_2; SetLittle(Gear^.dX); Gear^.State:= Gear^.State or gstMoving or gstHHJumping; PlaySound(sndJump3, Gear^.Hedgehog^.Team^.voicepack); exit end; PrevdX:= hwSign(Gear^.dX); if (Gear^.Message and gmLeft )<>0 then Gear^.dX:= -cLittle else if (Gear^.Message and gmRight )<>0 then Gear^.dX:= cLittle else exit; if (Gear^.Message and (gmLeft or gmRight)) <> 0 then begin StepSoundTimer:= cHHStepTicks; end; StepTicks:= cHHStepTicks; if PrevdX <> hwSign(Gear^.dX) then begin FollowGear:= Gear; exit end; DeleteCI(Gear); // must be after exit!! (see previous line) Gear^.Hedgehog^.visStepPos:= (Gear^.Hedgehog^.visStepPos + 1) and 7; if TestCollisionXwithGear(Gear, hwSign(Gear^.dX)) then begin if not (TestCollisionXwithXYShift(Gear, _0, -6, hwSign(Gear^.dX)) or TestCollisionYwithGear(Gear, -1)) then Gear^.Y:= Gear^.Y - _1; if not (TestCollisionXwithXYShift(Gear, _0, -5, hwSign(Gear^.dX)) or TestCollisionYwithGear(Gear, -1)) then Gear^.Y:= Gear^.Y - _1; if not (TestCollisionXwithXYShift(Gear, _0, -4, hwSign(Gear^.dX)) or TestCollisionYwithGear(Gear, -1)) then Gear^.Y:= Gear^.Y - _1; if not (TestCollisionXwithXYShift(Gear, _0, -3, hwSign(Gear^.dX)) or TestCollisionYwithGear(Gear, -1)) then Gear^.Y:= Gear^.Y - _1; if not (TestCollisionXwithXYShift(Gear, _0, -2, hwSign(Gear^.dX)) or TestCollisionYwithGear(Gear, -1)) then Gear^.Y:= Gear^.Y - _1; if not (TestCollisionXwithXYShift(Gear, _0, -1, hwSign(Gear^.dX)) or TestCollisionYwithGear(Gear, -1)) then Gear^.Y:= Gear^.Y - _1; end; if (not cArtillery) and ((Gear^.Message and gmPrecise) = 0) and (not TestCollisionXwithGear(Gear, hwSign(Gear^.dX))) then Gear^.X:= Gear^.X + SignAs(_1, Gear^.dX); SetAllHHToActive; if not TestCollisionYwithGear(Gear, 1) then begin Gear^.Y:= Gear^.Y + _1; if not TestCollisionYwithGear(Gear, 1) then begin Gear^.Y:= Gear^.Y + _1; if not TestCollisionYwithGear(Gear, 1) then begin Gear^.Y:= Gear^.Y + _1; if not TestCollisionYwithGear(Gear, 1) then begin Gear^.Y:= Gear^.Y + _1; if not TestCollisionYwithGear(Gear, 1) then begin Gear^.Y:= Gear^.Y + _1; if not TestCollisionYwithGear(Gear, 1) then begin Gear^.Y:= Gear^.Y + _1; if not TestCollisionYwithGear(Gear, 1) then begin Gear^.Y:= Gear^.Y - _6; Gear^.dY:= _0; Gear^.State:= Gear^.State or gstMoving; exit end; end end end end end end; AddGearCI(Gear) end end; procedure HedgehogChAngle(HHGear: PGear); var da: LongWord; begin with HHGear^.Hedgehog^ do if ((CurAmmoType = amRope) and ((HHGear^.State and (gstMoving or gstHHJumping)) = gstMoving)) or ((CurAmmoType = amPortalGun) and ((HHGear^.State and gstMoving) <> 0)) then da:= 2 else da:= 1; if (((HHGear^.Message and gmPrecise) = 0) or ((GameTicks mod 5) = 1)) then if ((HHGear^.Message and gmUp) <> 0) and (HHGear^.Angle >= CurMinAngle + da) then dec(HHGear^.Angle, da) else if ((HHGear^.Message and gmDown) <> 0) and (HHGear^.Angle + da <= CurMaxAngle) then inc(HHGear^.Angle, da) end; procedure doStepHedgehog(Gear: PGear); forward; //////////////////////////////////////////////////////////////////////////////// procedure doStepHedgehogMoving(Gear: PGear); var isFalling, isUnderwater: boolean; begin isUnderwater:= cWaterLine < hwRound(Gear^.Y) + Gear^.Radius; if Gear^.dX.QWordValue > 8160437862 then Gear^.dX.QWordValue:= 8160437862; if Gear^.dY.QWordValue > 8160437862 then Gear^.dY.QWordValue:= 8160437862; if Gear^.Hedgehog^.Unplaced then begin Gear^.dY:= _0; Gear^.dX:= _0; Gear^.State:= Gear^.State and not gstMoving; exit end; isFalling:= (Gear^.dY.isNegative) or not TestCollisionYKick(Gear, 1); if isFalling then begin if (Gear^.dY.isNegative) and TestCollisionYKick(Gear, -1) then Gear^.dY:= _0; Gear^.State:= Gear^.State or gstMoving; if isUnderwater then Gear^.dY:= Gear^.dY + cGravity / _2 else begin Gear^.dY:= Gear^.dY + cGravity; // this set of circumstances could be less complex if jumping was more clearly identified if ((GameFlags and gfMoreWind) <> 0) and (((Gear^.Damage <> 0) or ((CurAmmoGear <> nil) and ((CurAmmoGear^.AmmoType = amJetpack) or (CurAmmoGear^.AmmoType = amBirdy))) or ((Gear^.dY.QWordValue + Gear^.dX.QWordValue) > _0_55.QWordValue))) then Gear^.dX := Gear^.dX + cWindSpeed / Gear^.Density end end else begin if ((Gear^.dX.QWordValue + Gear^.dY.QWordValue) < _0_55.QWordValue) and ((Gear^.State and gstHHJumping) <> 0) then SetLittle(Gear^.dX); if not Gear^.dY.isNegative then begin CheckHHDamage(Gear); if ((Gear^.State and gstHHHJump) <> 0) and (not cArtillery) and (Gear^.dX.QWordValue < _0_02.QWordValue) then Gear^.dX.isNegative:= not Gear^.dX.isNegative; // landing after high jump Gear^.State:= Gear^.State and not (gstHHJumping or gstHHHJump); Gear^.dY:= _0; end else Gear^.dY:= Gear^.dY + cGravity; if ((Gear^.State and gstMoving) <> 0) then Gear^.dX:= Gear^.dX * Gear^.Friction end; if (Gear^.State <> 0) then DeleteCI(Gear); if isUnderwater then begin Gear^.dY:= Gear^.dY * _0_999; Gear^.dX:= Gear^.dX * _0_999; end; if (Gear^.State and gstMoving) <> 0 then if TestCollisionXKick(Gear, hwSign(Gear^.dX)) then if not isFalling then if hwAbs(Gear^.dX) > _0_01 then if not TestCollisionXwithXYShift(Gear, int2hwFloat(hwSign(Gear^.dX)) - Gear^.dX, -1, hwSign(Gear^.dX)) then begin Gear^.X:= Gear^.X + Gear^.dX; Gear^.dX:= Gear^.dX * _0_96; Gear^.Y:= Gear^.Y - _1 end else if not TestCollisionXwithXYShift(Gear, int2hwFloat(hwSign(Gear^.dX)) - Gear^.dX, -2, hwSign(Gear^.dX)) then begin Gear^.X:= Gear^.X + Gear^.dX; Gear^.dX:= Gear^.dX * _0_93; Gear^.Y:= Gear^.Y - _2 end else if not TestCollisionXwithXYShift(Gear, int2hwFloat(hwSign(Gear^.dX)) - Gear^.dX, -3, hwSign(Gear^.dX)) then begin Gear^.X:= Gear^.X + Gear^.dX; Gear^.dX:= Gear^.dX * _0_9 ; Gear^.Y:= Gear^.Y - _3 end else if not TestCollisionXwithXYShift(Gear, int2hwFloat(hwSign(Gear^.dX)) - Gear^.dX, -4, hwSign(Gear^.dX)) then begin Gear^.X:= Gear^.X + Gear^.dX; Gear^.dX:= Gear^.dX * _0_87; Gear^.Y:= Gear^.Y - _4 end else if not TestCollisionXwithXYShift(Gear, int2hwFloat(hwSign(Gear^.dX)) - Gear^.dX, -5, hwSign(Gear^.dX)) then begin Gear^.X:= Gear^.X + Gear^.dX; Gear^.dX:= Gear^.dX * _0_84; Gear^.Y:= Gear^.Y - _5 end else if hwAbs(Gear^.dX) > _0_02 then Gear^.dX:= -Gear^.Elasticity * Gear^.dX else begin Gear^.State:= Gear^.State and not gstMoving; SetLittle(Gear^.dX) end else begin Gear^.State:= Gear^.State and not gstMoving; SetLittle(Gear^.dX) end else if (hwAbs(Gear^.dX) > cLittle) and ((Gear^.State and gstHHJumping) = 0) then Gear^.dX:= -Gear^.Elasticity * Gear^.dX else SetLittle(Gear^.dX); if (not isFalling) and (hwAbs(Gear^.dX) + hwAbs(Gear^.dY) < _0_03) then begin Gear^.State:= Gear^.State and not gstWinner; Gear^.State:= Gear^.State and not gstMoving; SetLittle(Gear^.dX); Gear^.dY:= _0 end else Gear^.State:= Gear^.State or gstMoving; if (Gear^.State and gstMoving) <> 0 then begin Gear^.State:= Gear^.State and not gstAnimation; // ARTILLERY but not being moved by explosions Gear^.X:= Gear^.X + Gear^.dX; Gear^.Y:= Gear^.Y + Gear^.dY; if (not Gear^.dY.isNegative) and (not TestCollisionYKick(Gear, 1)) and TestCollisionYwithXYShift(Gear, 0, 1, 1) then begin CheckHHDamage(Gear); Gear^.dY:= _0; Gear^.Y:= Gear^.Y + _1 end; CheckGearDrowning(Gear); if (Gear^.State and gstDrowning) <> 0 then isCursorVisible:= false end; if (hwAbs(Gear^.dY) > _0) and (Gear^.FlightTime > 0) and ((GameFlags and gfLowGravity) = 0) then begin inc(Gear^.FlightTime, 1); if Gear^.FlightTime = 3000 then begin AddCaption(GetEventString(eidHomerun), cWhiteColor, capgrpMessage); PlaySound(sndHomerun) end; end else begin Gear^.FlightTime:= 0; end; end; procedure doStepHedgehogDriven(HHGear: PGear); var t: PGear; wasJumping: boolean; Hedgehog: PHedgehog; begin Hedgehog:= HHGear^.Hedgehog; if not isInMultiShoot then AllInactive:= false else HHGear^.Message:= 0; if (TurnTimeLeft = 0) or (HHGear^.Damage > 0) then begin if TagTurnTimeLeft = 0 then TagTurnTimeLeft:= TurnTimeLeft; TurnTimeLeft:= 0; isCursorVisible:= false; HHGear^.State:= HHGear^.State and not (gstHHDriven or gstAnimation or gstAttacking); AttackBar:= 0; if HHGear^.Damage > 0 then HHGear^.State:= HHGear^.State and not (gstHHJumping or gstHHHJump); exit end; if (HHGear^.State and gstAnimation) <> 0 then begin HHGear^.Message:= 0; if (HHGear^.Pos = Wavez[TWave(HHGear^.Tag)].VoiceDelay) and (HHGear^.Timer = 0) then PlaySound(Wavez[TWave(HHGear^.Tag)].Voice, Hedgehog^.Team^.voicepack); inc(HHGear^.Timer); if HHGear^.Timer = Wavez[TWave(HHGear^.Tag)].Interval then begin HHGear^.Timer:= 0; inc(HHGear^.Pos); if HHGear^.Pos = Wavez[TWave(HHGear^.Tag)].FramesCount then HHGear^.State:= HHGear^.State and not gstAnimation end; exit end; if ((HHGear^.State and gstMoving) <> 0) or (StepTicks = cHHStepTicks) or (CurAmmoGear <> nil) then // we are moving begin with Hedgehog^ do if (CurAmmoGear = nil) and (HHGear^.dY > _0_39) and (CurAmmoType = amParachute) then HHGear^.Message:= HHGear^.Message or gmAttack; // check for case with ammo t:= CheckGearNear(HHGear, gtCase, 36, 36); if t <> nil then PickUp(HHGear, t) end; if (CurAmmoGear = nil) then if (((HHGear^.Message and gmAttack) <> 0) or ((HHGear^.State and gstAttacking) <> 0)) then Attack(HHGear) // should be before others to avoid desync with '/put' msg and changing weapon msgs else else with Hedgehog^ do if ((Ammoz[CurAmmoGear^.AmmoType].Ammo.Propz and ammoprop_AltAttack) <> 0) and ((HHGear^.Message and gmLJump) <> 0) and ((Ammoz[CurAmmoType].Ammo.Propz and ammoprop_AltUse) <> 0) then begin Attack(HHGear); HHGear^.Message:= HHGear^.Message and not gmLJump end; if (CurAmmoGear = nil) or ((Ammoz[CurAmmoGear^.AmmoType].Ammo.Propz and ammoprop_AltAttack) <> 0) or ((Ammoz[CurAmmoGear^.AmmoType].Ammo.Propz and ammoprop_NoRoundEnd) <> 0) then begin if ((HHGear^.Message and gmSlot) <> 0) then if ChangeAmmo(HHGear) then ApplyAmmoChanges(Hedgehog^); if ((HHGear^.Message and gmWeapon) <> 0) then HHSetWeapon(HHGear); if ((HHGear^.Message and gmTimer) <> 0) then HHSetTimer(HHGear); end; if CurAmmoGear <> nil then begin CurAmmoGear^.Message:= HHGear^.Message; exit end; if not isInMultiShoot then HedgehogChAngle(HHGear); if (HHGear^.State and gstMoving) <> 0 then begin wasJumping:= ((HHGear^.State and gstHHJumping) <> 0); if ((HHGear^.Message and gmHJump) <> 0) and wasJumping and ((HHGear^.State and gstHHHJump) = 0) then if (not (hwAbs(HHGear^.dX) > cLittle)) and (HHGear^.dY < -_0_02) then begin HHGear^.State:= HHGear^.State or gstHHHJump; HHGear^.dY:= -_0_25; if not cArtillery then HHGear^.dX:= -SignAs(_0_02, HHGear^.dX); PlaySound(sndJump2, Hedgehog^.Team^.voicepack) end; HHGear^.Message:= HHGear^.Message and not (gmLJump or gmHJump); if (not cArtillery) and wasJumping and TestCollisionXwithGear(HHGear, hwSign(HHGear^.dX)) then SetLittle(HHGear^.dX); if Hedgehog^.Gear <> nil then doStepHedgehogMoving(HHGear); if ((HHGear^.State and (gstMoving or gstDrowning)) = 0) then begin AddGearCI(HHGear); if wasJumping then StepTicks:= 410 else StepTicks:= 95 end; exit end; if not isInMultiShoot and (Hedgehog^.Gear <> nil) then begin if StepTicks > 0 then dec(StepTicks); if (StepTicks = 0) then HedgehogStep(HHGear) end end; //////////////////////////////////////////////////////////////////////////////// procedure doStepHedgehogFree(Gear: PGear); var prevState: Longword; begin prevState:= Gear^.State; doStepHedgehogMoving(Gear); if (Gear^.State and (gstMoving or gstDrowning)) <> 0 then begin if Gear^.Damage > 0 then CalcRotationDirAngle(Gear); AllInactive:= false; exit end; if (Gear^.Health = 0) then begin if PrvInactive or ((GameFlags and gfInfAttack) <> 0) then begin Gear^.Timer:= 0; FollowGear:= Gear; PrvInactive:= false; AllInactive:= false; if (Gear^.State and gstHHGone) = 0 then begin Gear^.Hedgehog^.Effects[hePoisoned] := false; if Gear^.Hedgehog^.Effects[heResurrectable] then begin ResurrectHedgehog(Gear); end else begin Gear^.State:= Gear^.State or gstHHDeath; Gear^.doStep:= @doStepHedgehogDead; // Death message AddCaption(Format(GetEventString(eidDied), Gear^.Hedgehog^.Name), cWhiteColor, capgrpMessage); end; end else begin Gear^.doStep:= @doStepHedgehogGone; // Gone message AddCaption(Format(GetEventString(eidGone), Gear^.Hedgehog^.Name), cWhiteColor, capgrpMessage); end end; exit end; if ((Gear^.State and gstWait) = 0) and (prevState <> Gear^.State) then begin Gear^.State:= gstWait; Gear^.Timer:= 150 end else begin if Gear^.Timer = 0 then begin Gear^.State:= 0; Gear^.Active:= false; AddGearCI(Gear); exit end else dec(Gear^.Timer) end; AllInactive:= false end; //////////////////////////////////////////////////////////////////////////////// procedure doStepHedgehog(Gear: PGear); begin if (Gear^.Message and gmDestroy) <> 0 then begin DeleteGear(Gear); exit end; if (Gear^.State and gstHHDriven) = 0 then doStepHedgehogFree(Gear) else begin with Gear^.Hedgehog^ do if Team^.hasGone then TeamGoneEffect(Team^) else doStepHedgehogDriven(Gear) end; end;