(* * Hedgewars, a free turn based strategy game * Copyright (c) 2004-2011 Andrey Korotaev <unC0Rr@gmail.com> * * 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 *){$INCLUDE "options.inc"}unit uGears;(* * This unit defines the behavior of gears. * * Gears are "things"/"objects" that may be visible to the player or not, * but always have an effect on the course of the game. * * E.g.: weapons, hedgehogs, etc. * * Note: The visual appearance of gears is defined in the unit "uGearsRender". * * Note: Gears that do not have an effect on the game but are just visual * effects are called "Visual Gears" and defined in the respective unit! *)interfaceuses SDLh, uConsts, uFloat, uTypes;procedure initModule;procedure freeModule;function AddGear(X, Y: LongInt; Kind: TGearType; State: Longword; dX, dY: hwFloat; Timer: LongWord): PGear;function SpawnCustomCrateAt(x, y: LongInt; crate: TCrateType; content: Longword ): PGear;function SpawnFakeCrateAt(x, y: LongInt; crate: TCrateType; explode: boolean; poison: boolean ): PGear;function GetAmmo: TAmmoType;function GetUtility: TAmmoType;procedure ResurrectHedgehog(gear: PGear);procedure HideHog(HH: PHedgehog);procedure RestoreHog(HH: PHedgehog);procedure ProcessGears;procedure EndTurnCleanup;procedure ApplyDamage(Gear: PGear; AttackerHog: PHedgehog; Damage: Longword; Source: TDamageSource);procedure SetAllToActive;procedure SetAllHHToActive;procedure DrawGears;procedure FreeGearsList;procedure AddMiscGears;procedure AssignHHCoords;function GearByUID(uid : Longword) : PGear;procedure InsertGearToList(Gear: PGear);procedure RemoveGearFromList(Gear: PGear);function ModifyDamage(dmg: Longword; Gear: PGear): Longword;procedure FindPlace(var Gear: PGear; withFall: boolean; Left, Right: LongInt; skipProximity: boolean = false);procedure DeleteGear(Gear: PGear); implementationuses uStore, uSound, uTeams, uRandom, uCollisions, uIO, uLandGraphics, uAIMisc, uLocale, uAI, uAmmos, uStats, uVisualGears, uScript, GLunit, uMobile, uVariables, uCommands, uUtils, uTextures, uRenderUtils, uGearsRender, uCaptions, uDebug, uLandTexture;procedure doMakeExplosion(X, Y, Radius: LongInt; AttackingHog: PHedgehog; Mask: Longword; const Tint: LongWord = $FFFFFFFF); forward;procedure AmmoShove(Ammo: PGear; Damage, Power: LongInt); forward;//procedure AmmoFlameWork(Ammo: PGear); forward;function GearsNear(X, Y: hwFloat; Kind: TGearType; r: LongInt): TPGearArray; forward;function CheckGearNear(Gear: PGear; Kind: TGearType; rX, rY: LongInt): PGear; forward;procedure SpawnBoxOfSmth; forward;procedure AfterAttack; forward;procedure HedgehogStep(Gear: PGear); forward;procedure doStepHedgehogMoving(Gear: PGear); forward;procedure HedgehogChAngle(HHGear: PGear); forward;procedure ShotgunShot(Gear: PGear); forward;procedure PickUp(HH, Gear: PGear); forward;procedure HHSetWeapon(HHGear: PGear); forward;procedure doStepCase(Gear: PGear); forward;// For better maintainability the step handlers of gears are stored in// separate files.// Note: step handlers of gears that are hedgehogs are in a different file// than the handlers for all other gears.{$INCLUDE "GSHandlers.inc"}{$INCLUDE "HHHandlers.inc"}const doStepHandlers: array[TGearType] of TGearStepProcedure = ( @doStepBomb, @doStepHedgehog, @doStepShell, @doStepGrave, @doStepBee, @doStepShotgunShot, @doStepPickHammer, @doStepRope, @doStepMine, @doStepCase, @doStepDEagleShot, @doStepDynamite, @doStepBomb, @doStepCluster, @doStepShover, @doStepFlame, @doStepFirePunch, @doStepActionTimer, @doStepActionTimer, @doStepParachute, @doStepAirAttack, @doStepAirBomb, @doStepBlowTorch, @doStepGirder, @doStepTeleport, @doStepSwitcher, @doStepTarget, @doStepMortar, @doStepWhip, @doStepKamikaze, @doStepCake, @doStepSeduction, @doStepBomb, @doStepCluster, @doStepBomb, @doStepWaterUp, @doStepDrill, @doStepBallgun, @doStepBomb, @doStepRCPlane, @doStepSniperRifleShot, @doStepJetpack, @doStepMolotov, @doStepCase, @doStepBirdy, @doStepEggWork, @doStepPortalShot, @doStepPiano, @doStepBomb, @doStepSineGunShot, @doStepFlamethrower, @doStepSMine, @doStepPoisonCloud, @doStepHammer, @doStepHammerHit, @doStepResurrector, @doStepNapalmBomb, @doStepSnowball, @doStepSnowflake, @doStepStructure, @doStepLandGun, @doStepTardis);procedure InsertGearToList(Gear: PGear);var tmp, ptmp: PGear;begin tmp:= GearsList; ptmp:= GearsList; while (tmp <> nil) and (tmp^.Z <= Gear^.Z) do begin ptmp:= tmp; tmp:= tmp^.NextGear end; if ptmp <> tmp then begin Gear^.NextGear:= ptmp^.NextGear; Gear^.PrevGear:= ptmp; if ptmp^.NextGear <> nil then ptmp^.NextGear^.PrevGear:= Gear; ptmp^.NextGear:= Gear end else begin Gear^.NextGear:= GearsList; if Gear^.NextGear <> nil then Gear^.NextGear^.PrevGear:= Gear; GearsList:= Gear; end;end;procedure RemoveGearFromList(Gear: PGear);beginif Gear^.NextGear <> nil then Gear^.NextGear^.PrevGear:= Gear^.PrevGear;if Gear^.PrevGear <> nil then Gear^.PrevGear^.NextGear:= Gear^.NextGearelse GearsList:= Gear^.NextGearend;procedure spawnHealthTagForHH(HHGear: PGear; dmg: Longword);var tag: PVisualGear;begintag:= AddVisualGear(hwRound(HHGear^.X), hwRound(HHGear^.Y), vgtHealthTag, dmg);if (tag <> nil) then tag^.Hedgehog:= HHGear^.Hedgehog; // the tag needs the tag to determine the text colorAllInactive:= false;HHGear^.Active:= true;end;function AddGear(X, Y: LongInt; Kind: TGearType; State: Longword; dX, dY: hwFloat; Timer: LongWord): PGear;const Counter: Longword = 0;var gear: PGear;begininc(Counter);AddFileLog('AddGear: #' + inttostr(Counter) + ' (' + inttostr(x) + ',' + inttostr(y) + '), d(' + floattostr(dX) + ',' + floattostr(dY) + ') type = ' + EnumToStr(Kind));New(gear);FillChar(gear^, sizeof(TGear), 0);gear^.X:= int2hwFloat(X);gear^.Y:= int2hwFloat(Y);gear^.Target.X:= NoPointX;gear^.Kind := Kind;gear^.State:= State;gear^.Active:= true;gear^.dX:= dX;gear^.dY:= dY;gear^.doStep:= doStepHandlers[Kind];gear^.CollisionIndex:= -1;gear^.Timer:= Timer;gear^.FlightTime:= 0;gear^.uid:= Counter;gear^.SoundChannel:= -1;gear^.ImpactSound:= sndNone;gear^.nImpactSounds:= 0;gear^.Density:= _1;// Define ammo association, if any.gear^.AmmoType:= GearKindAmmoTypeMap[Kind];if Ammoz[Gear^.AmmoType].Ammo.Propz and ammoprop_NeedTarget <> 0 then gear^.Z:= cHHZ+1else gear^.Z:= cUsualZ;if CurrentHedgehog <> nil then begin gear^.Hedgehog:= CurrentHedgehog; gear^.IntersectGear:= CurrentHedgehog^.Gear end;case Kind of gtGrenade, gtClusterBomb, gtGasBomb: begin gear^.ImpactSound:= sndGrenadeImpact; gear^.nImpactSounds:= 1; gear^.AdvBounce:= 1; gear^.Radius:= 5; gear^.Elasticity:= _0_8; gear^.Friction:= _0_8; gear^.Density:= _1_5; gear^.RenderTimer:= true; if gear^.Timer = 0 then gear^.Timer:= 3000 end; gtWatermelon: begin gear^.ImpactSound:= sndMelonImpact; gear^.nImpactSounds:= 1; gear^.AdvBounce:= 1; gear^.Radius:= 6; gear^.Elasticity:= _0_8; gear^.Friction:= _0_995; gear^.Density:= _2; gear^.RenderTimer:= true; if gear^.Timer = 0 then gear^.Timer:= 3000 end; gtMelonPiece: begin gear^.Density:= _2; end; gtHedgehog: begin gear^.AdvBounce:= 1; gear^.Radius:= cHHRadius; gear^.Elasticity:= _0_35; gear^.Friction:= _0_999; gear^.Angle:= cMaxAngle div 2; gear^.Density:= _3; gear^.Z:= cHHZ; if (GameFlags and gfAISurvival) <> 0 then if gear^.Hedgehog^.BotLevel > 0 then gear^.Hedgehog^.Effects[heResurrectable] := true; end; gtShell: begin gear^.Radius:= 4; gear^.Density:= _1; end; gtSnowball: begin gear^.ImpactSound:= sndMudballImpact; gear^.nImpactSounds:= 1; gear^.Radius:= 4; gear^.Elasticity:= _1; gear^.Friction:= _1; gear^.Density:= _0_5; end; gtFlake: begin with Gear^ do begin Pos:= 0; Radius:= 1; DirAngle:= random * 360; dx.isNegative:= GetRandom(2) = 0; dx.QWordValue:= GetRandom(100000000); dy.isNegative:= false; dy.QWordValue:= GetRandom(70000000); State:= State or gstInvisible; if GetRandom(2) = 0 then dx := -dx; Health:= random(vobFrameTicks); Timer:= random(vobFramesCount); Angle:= (random(2) * 2 - 1) * (1 + random(10000)) * vobVelocity end end; gtGrave: begin gear^.ImpactSound:= sndGraveImpact; gear^.nImpactSounds:= 1; gear^.Radius:= 10; gear^.Elasticity:= _0_6; end; gtBee: begin gear^.Radius:= 5; gear^.Timer:= 500; gear^.RenderTimer:= true; gear^.Elasticity:= _0_9; gear^.Tag:= getRandom(32); end; gtSeduction: begin gear^.Radius:= 250; end; gtShotgunShot: begin gear^.Timer:= 900; gear^.Radius:= 2 end; gtPickHammer: begin gear^.Radius:= 10; gear^.Timer:= 4000 end; gtHammerHit: begin gear^.Radius:= 8; gear^.Timer:= 125 end; gtRope: begin gear^.Radius:= 3; gear^.Friction:= _450 * _0_01 * cRopePercent; RopePoints.Count:= 0; end; gtMine: begin gear^.ImpactSound:= sndMineImpact; gear^.nImpactSounds:= 1; gear^.Health:= 10; gear^.State:= gear^.State or gstMoving; gear^.Radius:= 2; gear^.Elasticity:= _0_55; gear^.Friction:= _0_995; gear^.Density:= _0_9; if cMinesTime < 0 then gear^.Timer:= getrandom(51)*100 else gear^.Timer:= cMinesTime; end; gtSMine: begin gear^.Health:= 10; gear^.State:= gear^.State or gstMoving; gear^.Radius:= 2; gear^.Elasticity:= _0_55; gear^.Friction:= _0_995; gear^.Density:= _0_9; gear^.Timer:= 500; end; gtCase: begin gear^.ImpactSound:= sndGraveImpact; gear^.nImpactSounds:= 1; gear^.Radius:= 16; gear^.Elasticity:= _0_3 end; gtExplosives: begin gear^.ImpactSound:= sndGrenadeImpact; gear^.nImpactSounds:= 1; gear^.Radius:= 16; gear^.Elasticity:= _0_4; gear^.Friction:= _0_995; gear^.Density:= _6; gear^.Health:= cBarrelHealth; gear^.Z:= cHHZ-1 end; gtDEagleShot: begin gear^.Radius:= 1; gear^.Health:= 50 end; gtSniperRifleShot: begin gear^.Radius:= 1; gear^.Health:= 50 end; gtDynamite: begin gear^.Radius:= 3; gear^.Elasticity:= _0_55; gear^.Friction:= _0_03; gear^.Density:= _2; gear^.Timer:= 5000; end; gtCluster: begin gear^.Radius:= 2; gear^.Density:= _1_5; gear^.RenderTimer:= true end; gtShover: gear^.Radius:= 20; gtFlame: begin gear^.Tag:= GetRandom(32); gear^.Radius:= 1; gear^.Health:= 5; gear^.Density:= _1; if (gear^.dY.QWordValue = 0) and (gear^.dX.QWordValue = 0) then begin gear^.dY:= (getrandom - _0_8) * _0_03; gear^.dX:= (getrandom - _0_5) * _0_4 end end; gtFirePunch: begin gear^.Radius:= 15; gear^.Tag:= Y end; gtAirBomb: begin gear^.Radius:= 5; gear^.Density:= _2; end; gtBlowTorch: begin gear^.Radius:= cHHRadius + cBlowTorchC; gear^.Timer:= 7500 end; gtSwitcher: begin gear^.Z:= cCurrHHZ end; gtTarget: begin gear^.ImpactSound:= sndGrenadeImpact; gear^.nImpactSounds:= 1; gear^.Radius:= 10; gear^.Elasticity:= _0_3; gear^.Timer:= 0 end; gtTardis: begin gear^.Timer:= 0; gear^.Pos:= 1; gear^.Z:= cCurrHHZ+1; end; gtMortar: begin gear^.Radius:= 4; gear^.Elasticity:= _0_2; gear^.Friction:= _0_08; gear^.Density:= _1; end; gtWhip: gear^.Radius:= 20; gtHammer: gear^.Radius:= 20; gtKamikaze: begin gear^.Health:= 2048; gear^.Radius:= 20 end; gtCake: begin gear^.Health:= 2048; gear^.Radius:= 7; gear^.Z:= cOnHHZ; gear^.RenderTimer:= true; gear^.DirAngle:= -90 * hwSign(Gear^.dX); if not dX.isNegative then gear^.Angle:= 1 else gear^.Angle:= 3 end; gtHellishBomb: begin gear^.ImpactSound:= sndHellishImpact1; gear^.nImpactSounds:= 4; gear^.AdvBounce:= 1; gear^.Radius:= 4; gear^.Elasticity:= _0_5; gear^.Friction:= _0_96; gear^.Density:= _1_5; gear^.RenderTimer:= true; gear^.Timer:= 5000 end; gtDrill: begin if gear^.Timer = 0 then gear^.Timer:= 5000; // Tag for drill strike. if 1 then first impact occured already gear^.Tag := 0; gear^.Radius:= 4; gear^.Density:= _1; end; gtBall: begin gear^.ImpactSound:= sndGrenadeImpact; gear^.nImpactSounds:= 1; gear^.AdvBounce:= 1; gear^.Radius:= 5; gear^.Tag:= random(8); gear^.Timer:= 5000; gear^.Elasticity:= _0_7; gear^.Friction:= _0_995; gear^.Density:= _1_5; end; gtBallgun: begin gear^.Timer:= 5001; end; gtRCPlane: begin gear^.Timer:= 15000; gear^.Health:= 3; gear^.Radius:= 8 end; gtJetpack: begin gear^.Health:= 2000; gear^.Damage:= 100 end; gtMolotov: begin gear^.Radius:= 6; gear^.Density:= _2; end; gtBirdy: begin gear^.Radius:= 16; // todo: check gear^.Timer:= 0; gear^.Health := 2000; gear^.FlightTime := 2; end; gtEgg: begin gear^.Radius:= 4; gear^.Elasticity:= _0_6; gear^.Friction:= _0_96; gear^.Density:= _1; if gear^.Timer = 0 then gear^.Timer:= 3000 end; gtPortal: begin gear^.ImpactSound:= sndMelonImpact; gear^.nImpactSounds:= 1; gear^.AdvBounce:= 0; gear^.Radius:= 17; // set color gear^.Tag:= 2 * gear^.Timer; gear^.Timer:= 15000; gear^.RenderTimer:= false; gear^.Health:= 100; end; gtPiano: begin gear^.Radius:= 32; gear^.Density:= _50; end; gtSineGunShot: begin gear^.Radius:= 5; gear^.Health:= 6000; end;gtFlamethrower: begin gear^.Tag:= 10; gear^.Timer:= 10; gear^.Health:= 500; gear^.Damage:= 100; end; gtLandGun: begin gear^.Tag:= 10; gear^.Timer:= 10; gear^.Health:= 1000; gear^.Damage:= 100; end; gtPoisonCloud: begin gear^.Timer:= 5000; gear^.dY:= int2hwfloat(-4 + longint(getRandom(8))) / 1000; end; gtResurrector: begin gear^.Radius := 100; gear^.Tag := 0 end; gtWaterUp: begin gear^.Tag := 47; end; gtNapalmBomb: begin gear^.Timer:= 1000; gear^.Radius:= 5; gear^.Density:= _1_5; end; gtStructure: begin gear^.Elasticity:= _0_55; gear^.Friction:= _0_995; gear^.Density:= _0_9; gear^.Radius:= 13; gear^.Health:= 200; gear^.Tag:= 3; end; end;InsertGearToList(gear);AddGear:= gear;ScriptCall('onGearAdd', gear^.uid);end;procedure DeleteGear(Gear: PGear);var team: PTeam; t,i: Longword; k: boolean;beginScriptCall('onGearDelete', gear^.uid);DeleteCI(Gear);if Gear^.Tex <> nil then begin FreeTexture(Gear^.Tex); Gear^.Tex:= nil end;// make sure that portals have their link removed before deletionif (Gear^.Kind = gtPortal) then begin if (Gear^.IntersectGear <> nil) then if (Gear^.IntersectGear^.IntersectGear = Gear) then Gear^.IntersectGear^.IntersectGear:= nil; endelse if Gear^.Kind = gtHedgehog then (* This behaviour dates back to revision 4, and I accidentally encountered it with TARDIS. I don't think it must apply to any modern weapon, since if it was actually hit, the best the gear could do would be to destroy itself immediately, and you'd still end up with two graves. I believe it should be removed if (CurAmmoGear <> nil) and (CurrentHedgehog^.Gear = Gear) then begin AttackBar:= 0; Gear^.Message:= gmDestroy; CurAmmoGear^.Message:= gmDestroy; exit end else*) begin if (hwRound(Gear^.Y) >= cWaterLine) then begin t:= max(Gear^.Damage, Gear^.Health); Gear^.Damage:= t; if ((not SuddenDeathDmg and (cWaterOpacity < $FF)) or (SuddenDeathDmg and (cWaterOpacity < $FF))) and (hwRound(Gear^.Y) < cWaterLine + 256) then spawnHealthTagForHH(Gear, t); end; team:= Gear^.Hedgehog^.Team; if CurrentHedgehog^.Gear = Gear then begin AttackBar:= 0; FreeActionsList; // to avoid ThinkThread on drawned gear if ((Ammoz[CurrentHedgehog^.CurAmmoType].Ammo.Propz and ammoprop_NoRoundEnd) <> 0) and (CurrentHedgehog^.MultiShootAttacks > 0) then OnUsedAmmo(CurrentHedgehog^); end; Gear^.Hedgehog^.Gear:= nil; if Gear^.Hedgehog^.King then begin // are there any other kings left? Just doing nil check. Presumably a mortally wounded king will get reaped soon enough k:= false; for i:= 0 to Pred(team^.Clan^.TeamsNumber) do if (team^.Clan^.Teams[i]^.Hedgehogs[0].Gear <> nil) then k:= true; if not k then for i:= 0 to Pred(team^.Clan^.TeamsNumber) do begin team^.Clan^.Teams[i]^.hasGone:= true; TeamGoneEffect(team^.Clan^.Teams[i]^) end end; // should be not CurrentHedgehog, but hedgehog of the last gear which caused damage to this hog // same stand for CheckHHDamage if (Gear^.LastDamage <> nil) then uStats.HedgehogDamaged(Gear, Gear^.LastDamage, 0, true) else uStats.HedgehogDamaged(Gear, CurrentHedgehog, 0, true); inc(KilledHHs); RecountTeamHealth(team); if (CurrentHedgehog <> nil) and CurrentHedgehog^.Effects[heResurrectable] and not Gear^.Hedgehog^.Effects[heResurrectable] then with CurrentHedgehog^ do begin inc(Team^.stats.AIKills); if Team^.AIKillsTex <> nil then FreeTexture(Team^.AIKillsTex); Team^.AIKillsTex := RenderStringTex(inttostr(Team^.stats.AIKills), Team^.Clan^.Color, fnt16); end end;with Gear^ do AddFileLog('Delete: #' + inttostr(uid) + ' (' + inttostr(hwRound(x)) + ',' + inttostr(hwRound(y)) + '), d(' + floattostr(dX) + ',' + floattostr(dY) + ') type = ' + EnumToStr(Kind));if CurAmmoGear = Gear then CurAmmoGear:= nil;if FollowGear = Gear then FollowGear:= nil;if lastGearByUID = Gear then lastGearByUID := nil;RemoveGearFromList(Gear);Dispose(Gear)end;function CheckNoDamage: boolean; // returns TRUE in case of no damaged hhsvar Gear: PGear; dmg: LongInt;beginCheckNoDamage:= true;Gear:= GearsList;while Gear <> nil do begin if (Gear^.Kind = gtHedgehog) and (((GameFlags and gfInfAttack) = 0) or ((Gear^.dX.QWordValue < _0_000004.QWordValue) and (Gear^.dY.QWordValue < _0_000004.QWordValue))) then begin if (not isInMultiShoot) then inc(Gear^.Damage, Gear^.Karma); if (Gear^.Damage <> 0) and (not Gear^.Invulnerable) then begin CheckNoDamage:= false; dmg:= Gear^.Damage; if Gear^.Health < dmg then begin Gear^.Active:= true; Gear^.Health:= 0 end else dec(Gear^.Health, dmg); if (Gear^.Hedgehog^.Team = CurrentTeam) and (Gear^.Damage <> Gear^.Karma) and not Gear^.Hedgehog^.King and not Gear^.Hedgehog^.Effects[hePoisoned] and not SuddenDeathDmg then Gear^.State:= Gear^.State or gstLoser; spawnHealthTagForHH(Gear, dmg); RenderHealth(Gear^.Hedgehog^); RecountTeamHealth(Gear^.Hedgehog^.Team); end; if (not isInMultiShoot) then Gear^.Karma:= 0; Gear^.Damage:= 0 end; Gear:= Gear^.NextGear end;end;procedure HealthMachine;var Gear: PGear; team: PTeam; i: LongWord; flag: Boolean; tmp: LongWord;begin Gear:= GearsList; while Gear <> nil do begin if Gear^.Kind = gtHedgehog then begin tmp:= 0; if Gear^.Hedgehog^.Effects[hePoisoned] then begin inc(tmp, ModifyDamage(5, Gear)); if (GameFlags and gfResetHealth) <> 0 then dec(Gear^.Hedgehog^.InitialHealth) // does not need a minimum check since <= 1 basically disables it end; if (TotalRounds > cSuddenDTurns - 1) then begin inc(tmp, cHealthDecrease); if (GameFlags and gfResetHealth) <> 0 then dec(Gear^.Hedgehog^.InitialHealth, cHealthDecrease) end; if Gear^.Hedgehog^.King then begin flag:= false; team:= Gear^.Hedgehog^.Team; for i:= 0 to Pred(team^.HedgehogsNumber) do if (team^.Hedgehogs[i].Gear <> nil) and (not team^.Hedgehogs[i].King) and (team^.Hedgehogs[i].Gear^.Health > team^.Hedgehogs[i].Gear^.Damage) then flag:= true; if not flag then begin inc(tmp, 5); if (GameFlags and gfResetHealth) <> 0 then dec(Gear^.Hedgehog^.InitialHealth, 5) end end; if tmp > 0 then begin inc(Gear^.Damage, min(tmp, max(0,Gear^.Health - 1 - Gear^.Damage))); HHHurt(Gear^.Hedgehog, dsPoison); end end; Gear:= Gear^.NextGear end;end;procedure ProcessGears;const delay: LongWord = 0; delay2: LongWord = 0; step: (stDelay, stChDmg, stSweep, stTurnReact, stAfterDelay, stChWin, stWater, stChWin2, stHealth, stSpawn, stNTurn) = stDelay;var Gear, t: PGear; i, AliveCount: LongInt; s: shortstring;beginPrvInactive:= AllInactive;AllInactive:= true;if (StepSoundTimer > 0) and (StepSoundChannel < 0) then StepSoundChannel:= LoopSound(sndSteps)else if (StepSoundTimer = 0) and (StepSoundChannel > -1) then begin StopSound(StepSoundChannel); StepSoundChannel:= -1 end;if StepSoundTimer > 0 then dec(StepSoundTimer, 1);t:= GearsList;while t <> nil do begin Gear:= t; t:= Gear^.NextGear; if Gear^.Active then begin if Gear^.RenderTimer and (Gear^.Timer > 500) and ((Gear^.Timer mod 1000) = 0) then begin if Gear^.Tex <> nil then FreeTexture(Gear^.Tex); Gear^.Tex:= RenderStringTex(inttostr(Gear^.Timer div 1000), cWhiteColor, fntSmall); end; Gear^.doStep(Gear); // might be useful later //ScriptCall('onGearStep', Gear^.uid); end end;if AllInactive thencase step of stDelay: begin if delay = 0 then delay:= cInactDelay else dec(delay); if delay = 0 then inc(step) end; stChDmg: if CheckNoDamage then inc(step) else step:= stDelay; stSweep: if SweepDirty then begin SetAllToActive; step:= stChDmg end else inc(step); stTurnReact: begin if (not bBetweenTurns) and (not isInMultiShoot) then begin uStats.TurnReaction; inc(step) end else inc(step, 2); end; stAfterDelay: begin if delay = 0 then delay:= cInactDelay else dec(delay); if delay = 0 then inc(step) end; stChWin: begin CheckForWin; inc(step) end; stWater: if (not bBetweenTurns) and (not isInMultiShoot) then begin if TotalRounds = cSuddenDTurns + 1 then bWaterRising:= true; if bWaterRising and (cWaterRise > 0) then AddGear(0, 0, gtWaterUp, 0, _0, _0, 0)^.Tag:= cWaterRise; inc(step) end else inc(step); stChWin2: begin CheckForWin; inc(step) end; stHealth: begin if (cWaterRise <> 0) or (cHealthDecrease <> 0) then begin if (TotalRounds = cSuddenDTurns) and not SuddenDeath and not isInMultiShoot then begin SuddenDeath:= true; if cHealthDecrease <> 0 then begin SuddenDeathDmg:= true; // flash ScreenFade:= sfFromWhite; ScreenFadeValue:= sfMax; ScreenFadeSpeed:= 1; ChangeToSDClouds; ChangeToSDFlakes; glClearColor(SDSkyColor.r * (SDTint/255) / 255, SDSkyColor.g * (SDTint/255) / 255, SDSkyColor.b * (SDTint/255) / 255, 0.99); end; AddCaption(trmsg[sidSuddenDeath], cWhiteColor, capgrpGameState); playSound(sndSuddenDeath); MusicFN:= SDMusic; ChangeMusic end else if (TotalRounds < cSuddenDTurns) and not isInMultiShoot then begin i:= cSuddenDTurns - TotalRounds; s:= inttostr(i); if i = 1 then AddCaption(trmsg[sidRoundSD], cWhiteColor, capgrpGameState) else if i in [2, 5, 10, 15, 20, 25, 50, 100] then AddCaption(Format(trmsg[sidRoundsSD], s), cWhiteColor, capgrpGameState); end; end; if bBetweenTurns or isInMultiShoot or (TotalRounds = -1) then inc(step) else begin bBetweenTurns:= true; HealthMachine; step:= stChDmg end end; stSpawn: begin if not isInMultiShoot then SpawnBoxOfSmth; inc(step) end; stNTurn: begin if isInMultiShoot then isInMultiShoot:= false else begin // delayed till after 0.9.12 // reset to default zoom //ZoomValue:= ZoomDefault; with CurrentHedgehog^ do if (Gear <> nil) and ((Gear^.State and gstAttacked) = 0) and (MultiShootAttacks > 0) then OnUsedAmmo(CurrentHedgehog^); EndTurnCleanup; FreeActionsList; // could send -left, -right and similar commands, so should be called before /nextturn ParseCommand('/nextturn', true); SwitchHedgehog; AfterSwitchHedgehog; bBetweenTurns:= false end; step:= Low(step) end; endelse if ((GameFlags and gfInfAttack) <> 0) then begin if delay2 = 0 then delay2:= cInactDelay * 50 else begin dec(delay2); if ((delay2 mod cInactDelay) = 0) and (CurrentHedgehog <> nil) and (CurrentHedgehog^.Gear <> nil) and not CurrentHedgehog^.Unplaced then begin if (CurrentHedgehog^.Gear^.State and gstAttacked <> 0) and (Ammoz[CurrentHedgehog^.CurAmmoType].Ammo.Propz and ammoprop_NeedTarget <> 0) then begin CurrentHedgehog^.Gear^.State:= CurrentHedgehog^.Gear^.State or gstHHChooseTarget; isCursorVisible := true end; CurrentHedgehog^.Gear^.State:= CurrentHedgehog^.Gear^.State and not gstAttacked; end; if delay2 = 0 then begin if (CurrentHedgehog^.Gear <> nil) and (CurrentHedgehog^.Gear^.State and gstAttacked = 0) and (CurAmmoGear = nil) then SweepDirty; CheckNoDamage; AliveCount:= 0; // shorter version of check for win to allow typical step activity to proceed for i:= 0 to Pred(ClansCount) do if ClansArray[i]^.ClanHealth > 0 then inc(AliveCount); if (AliveCount <= 1) and ((GameFlags and gfOneClanMode) = 0) then begin step:= stChDmg; if TagTurnTimeLeft = 0 then TagTurnTimeLeft:= TurnTimeLeft; TurnTimeLeft:= 0 end end end end;if TurnTimeLeft > 0 then if CurrentHedgehog^.Gear <> nil then if ((CurrentHedgehog^.Gear^.State and gstAttacking) = 0) and not isInMultiShoot then begin if (TurnTimeLeft = 5000) and (cHedgehogTurnTime >= 10000) and (not PlacingHogs) and (CurrentHedgehog^.Gear <> nil) and ((CurrentHedgehog^.Gear^.State and gstAttacked) = 0) then AddVoice(sndHurry, CurrentTeam^.voicepack); if ReadyTimeLeft > 0 then begin if ReadyTimeLeft = 2000 then AddVoice(sndComeonthen, CurrentTeam^.voicepack); dec(ReadyTimeLeft) end else dec(TurnTimeLeft) end;if skipFlag then begin if TagTurnTimeLeft = 0 then TagTurnTimeLeft:= TurnTimeLeft; TurnTimeLeft:= 0; skipFlag:= false; inc(CurrentHedgehog^.Team^.stats.TurnSkips); end;if ((GameTicks and $FFFF) = $FFFF) then begin if (not CurrentTeam^.ExtDriven) then begin SendIPC('#'); AddFileLog('hiTicks increment message sent') end; if (not CurrentTeam^.ExtDriven) or CurrentTeam^.hasGone then inc(hiTicks) // we do not recieve a message for this end;ScriptCall('onGameTick');inc(GameTicks)end;//Purpose, to reset all transient attributes toggled by a utility and clean up various gears and effects at end of turn//If any of these are set as permanent toggles in the frontend, that needs to be checked and skipped here.procedure EndTurnCleanup;var i: LongInt; t: PGear;begin SpeechText:= ''; // in case it has not been consumed if (GameFlags and gfLowGravity) = 0 then begin cGravity:= cMaxWindSpeed * 2; cGravityf:= 0.00025 * 2 end; if (GameFlags and gfVampiric) = 0 then cVampiric:= false; cDamageModifier:= _1; if (GameFlags and gfLaserSight) = 0 then cLaserSighting:= false; if (GameFlags and gfArtillery) = 0 then cArtillery:= false; // have to sweep *all* current team hedgehogs since it is theoretically possible if you have enough invulnerabilities and switch turns to make your entire team invulnerable if (CurrentTeam <> nil) then with CurrentTeam^ do for i:= 0 to cMaxHHIndex do with Hedgehogs[i] do begin(* if (SpeechGear <> nil) then begin DeleteVisualGear(SpeechGear); // remove to restore persisting beyond end of turn. Tiy says was too much of a gameplay issue SpeechGear:= nil end;*) if (Gear <> nil) then begin if (GameFlags and gfInvulnerable) = 0 then Gear^.Invulnerable:= false; end; end; t:= GearsList; while t <> nil do begin t^.PortalCounter:= 0; if ((GameFlags and gfResetHealth) <> 0) and (t^.Kind = gtHedgehog) and (t^.Health < t^.Hedgehog^.InitialHealth) then begin t^.Health:= t^.Hedgehog^.InitialHealth; RenderHealth(t^.Hedgehog^); end; t:= t^.NextGear end; if ((GameFlags and gfResetWeps) <> 0) and not PlacingHogs then ResetWeapons; if (GameFlags and gfResetHealth) <> 0 then for i:= 0 to Pred(TeamsCount) do RecountTeamHealth(TeamsArray[i])end;procedure ApplyDamage(Gear: PGear; AttackerHog: PHedgehog; Damage: Longword; Source: TDamageSource);var s: shortstring; vampDmg, tmpDmg, i: Longword; vg: PVisualGear;begin if Damage = 0 then exit; // nothing to apply if (Gear^.Kind = gtHedgehog) then begin Gear^.LastDamage := AttackerHog; Gear^.Hedgehog^.Team^.Clan^.Flawless:= false; HHHurt(Gear^.Hedgehog, Source); AddDamageTag(hwRound(Gear^.X), hwRound(Gear^.Y), Damage, Gear^.Hedgehog^.Team^.Clan^.Color); tmpDmg:= min(Damage, max(0,Gear^.Health-Gear^.Damage)); if (Gear <> CurrentHedgehog^.Gear) and (CurrentHedgehog^.Gear <> nil) and (tmpDmg >= 1) then begin if cVampiric then begin vampDmg:= hwRound(int2hwFloat(tmpDmg)*_0_8); if vampDmg >= 1 then begin // was considering pulsing on attack, Tiy thinks it should be permanent while in play //CurrentHedgehog^.Gear^.State:= CurrentHedgehog^.Gear^.State or gstVampiric; inc(CurrentHedgehog^.Gear^.Health,vampDmg); str(vampDmg, s); s:= '+' + s; AddCaption(s, CurrentHedgehog^.Team^.Clan^.Color, capgrpAmmoinfo); RenderHealth(CurrentHedgehog^); RecountTeamHealth(CurrentHedgehog^.Team); i:= 0; while i < vampDmg do begin vg:= AddVisualGear(hwRound(CurrentHedgehog^.Gear^.X), hwRound(CurrentHedgehog^.Gear^.Y), vgtStraightShot); if vg <> nil then with vg^ do begin Tint:= $FF0000FF; State:= ord(sprHealth) end; inc(i, 5); end; end end; if ((GameFlags and gfKarma) <> 0) and ((GameFlags and gfInvulnerable) = 0) and not CurrentHedgehog^.Gear^.Invulnerable then begin // this cannot just use Damage or it interrupts shotgun and gets you called stupid inc(CurrentHedgehog^.Gear^.Karma, tmpDmg); CurrentHedgehog^.Gear^.LastDamage := CurrentHedgehog; spawnHealthTagForHH(CurrentHedgehog^.Gear, tmpDmg); end; uStats.HedgehogDamaged(Gear, AttackerHog, Damage, false); end; end else if Gear^.Kind <> gtStructure then // not gtHedgehog nor gtStructure begin Gear^.Hedgehog:= AttackerHog; end; inc(Gear^.Damage, Damage); ScriptCall('onGearDamage', Gear^.UID, Damage);end;procedure SetAllToActive;var t: PGear;beginAllInactive:= false;t:= GearsList;while t <> nil do begin t^.Active:= true; t:= t^.NextGear endend;procedure SetAllHHToActive;var t: PGear;beginAllInactive:= false;t:= GearsList;while t <> nil do begin if (t^.Kind = gtHedgehog) or (t^.Kind = gtExplosives) then t^.Active:= true; t:= t^.NextGear endend;procedure DrawGears;var Gear: PGear; x, y: LongInt;beginGear:= GearsList;while Gear <> nil do begin if Gear^.State and gstInvisible = 0 then begin x:= hwRound(Gear^.X) + WorldDx; y:= hwRound(Gear^.Y) + WorldDy; RenderGear(Gear, x, y); end; Gear:= Gear^.NextGear end;end;procedure FreeGearsList;var t, tt: PGear;begin tt:= GearsList; GearsList:= nil; while tt <> nil do begin t:= tt; tt:= tt^.NextGear; Dispose(t) end;end;procedure AddMiscGears;var i: Longword; Gear: PGear;beginAddGear(0, 0, gtATStartGame, 0, _0, _0, 2000);i:= 0;Gear:= PGear(1);while (i < cLandMines) {and (Gear <> nil)} do // disable this check until better solution found begin Gear:= AddGear(0, 0, gtMine, 0, _0, _0, 0); FindPlace(Gear, false, 0, LAND_WIDTH); inc(i) end;i:= 0;Gear:= PGear(1);while (i < cExplosives){ and (Gear <> nil)} do begin Gear:= AddGear(0, 0, gtExplosives, 0, _0, _0, 0); FindPlace(Gear, false, 0, LAND_WIDTH); inc(i) end;if (GameFlags and gfLowGravity) <> 0 then begin cGravity:= cMaxWindSpeed; cGravityf:= 0.00025 end;if (GameFlags and gfVampiric) <> 0 then cVampiric:= true;Gear:= GearsList;if (GameFlags and gfInvulnerable) <> 0 then while Gear <> nil do begin Gear^.Invulnerable:= true; // this is only checked on hogs right now, so no need for gear type check Gear:= Gear^.NextGear end;if (GameFlags and gfLaserSight) <> 0 then cLaserSighting:= true;if (GameFlags and gfArtillery) <> 0 then cArtillery:= true;if not hasBorder and ((Theme = 'Snow') or (Theme = 'Christmas')) then begin for i:= 0 to Pred(vobCount*2) do AddGear(GetRandom(LAND_WIDTH+1024)-512, LAND_HEIGHT - GetRandom(LAND_HEIGHT div 2), gtFlake, 0, _0, _0, 0); //disableLandBack:= true endend;procedure doMakeExplosion(X, Y, Radius: LongInt; AttackingHog: PHedgehog; Mask: Longword; const Tint: LongWord);var Gear: PGear; dmg, dmgRadius, dmgBase: LongInt; fX, fY: hwFloat; vg: PVisualGear; i, cnt: LongInt;beginif Radius > 4 then AddFileLog('Explosion: at (' + inttostr(x) + ',' + inttostr(y) + ')');if Radius > 25 then KickFlakes(Radius, X, Y);if ((Mask and EXPLNoGfx) = 0) then begin vg:= nil; if Radius > 50 then vg:= AddVisualGear(X, Y, vgtBigExplosion) else if Radius > 10 then vg:= AddVisualGear(X, Y, vgtExplosion); if vg <> nil then vg^.Tint:= Tint; end;if (Mask and EXPLAutoSound) <> 0 then PlaySound(sndExplosion);if (Mask and EXPLAllDamageInRadius) = 0 then dmgRadius:= Radius shl 1else dmgRadius:= Radius;dmgBase:= dmgRadius + cHHRadius div 2;fX:= int2hwFloat(X);fY:= int2hwFloat(Y);Gear:= GearsList;while Gear <> nil do begin dmg:= 0; //dmg:= dmgRadius + cHHRadius div 2 - hwRound(Distance(Gear^.X - int2hwFloat(X), Gear^.Y - int2hwFloat(Y))); //if (dmg > 1) and if (Gear^.State and gstNoDamage) = 0 then begin case Gear^.Kind of gtHedgehog, gtMine, gtSMine, gtCase, gtTarget, gtFlame, gtExplosives, gtStructure: begin// Run the calcs only once we know we have a type that will need damage if hwRound(hwAbs(Gear^.X-fX)+hwAbs(Gear^.Y-fY)) < dmgBase then dmg:= dmgBase - max(hwRound(Distance(Gear^.X - fX, Gear^.Y - fY)),Gear^.Radius); if dmg > 1 then begin dmg:= ModifyDamage(min(dmg div 2, Radius), Gear); //AddFileLog('Damage: ' + inttostr(dmg)); if (Mask and EXPLNoDamage) = 0 then begin if not Gear^.Invulnerable then ApplyDamage(Gear, AttackingHog, dmg, dsExplosion) else Gear^.State:= Gear^.State or gstWinner; end; if ((Mask and EXPLDoNotTouchAny) = 0) and (((Mask and EXPLDoNotTouchHH) = 0) or (Gear^.Kind <> gtHedgehog)) then begin DeleteCI(Gear); Gear^.dX:= Gear^.dX + SignAs(_0_005 * dmg + cHHKick, Gear^.X - fX); Gear^.dY:= Gear^.dY + SignAs(_0_005 * dmg + cHHKick, Gear^.Y - fY); Gear^.State:= (Gear^.State or gstMoving) and (not gstLoser); if not Gear^.Invulnerable then Gear^.State:= (Gear^.State or gstMoving) and (not gstWinner); Gear^.Active:= true; if Gear^.Kind <> gtFlame then FollowGear:= Gear end; if ((Mask and EXPLPoisoned) <> 0) and (Gear^.Kind = gtHedgehog) and not Gear^.Invulnerable then Gear^.Hedgehog^.Effects[hePoisoned] := true; end; end; gtGrave: begin// Run the calcs only once we know we have a type that will need damage if hwRound(hwAbs(Gear^.X-fX)+hwAbs(Gear^.Y-fY)) < dmgBase then dmg:= dmgBase - hwRound(Distance(Gear^.X - fX, Gear^.Y - fY)); if dmg > 1 then begin dmg:= ModifyDamage(min(dmg div 2, Radius), Gear); Gear^.dY:= - _0_004 * dmg; Gear^.Active:= true end end; end; end; Gear:= Gear^.NextGear end;if (Mask and EXPLDontDraw) = 0 then if (GameFlags and gfSolidLand) = 0 then begin cnt:= DrawExplosion(X, Y, Radius) div 1608; // approx 2 16x16 circles to erase per chunk if (cnt > 0) and (SpritesData[sprChunk].Texture <> nil) then for i:= 0 to cnt do AddVisualGear(X, Y, vgtChunk) end;uAIMisc.AwareOfExplosion(0, 0, 0)end;procedure ShotgunShot(Gear: PGear);var t: PGear; dmg, r, dist: LongInt; dx, dy: hwFloat;beginGear^.Radius:= cShotgunRadius;t:= GearsList;while t <> nil do begin case t^.Kind of gtHedgehog, gtMine, gtSMine, gtCase, gtTarget, gtExplosives, gtStructure: begin//addFileLog('ShotgunShot radius: ' + inttostr(Gear^.Radius) + ', t^.Radius = ' + inttostr(t^.Radius) + ', distance = ' + inttostr(dist) + ', dmg = ' + inttostr(dmg)); dmg:= 0; r:= Gear^.Radius + t^.Radius; dx:= Gear^.X-t^.X; dx.isNegative:= false; dy:= Gear^.Y-t^.Y; dy.isNegative:= false; if r-hwRound(dx+dy) > 0 then begin dist:= hwRound(Distance(dx, dy)); dmg:= ModifyDamage(min(r - dist, 25), t); end; if dmg > 0 then begin if (not t^.Invulnerable) then ApplyDamage(t, Gear^.Hedgehog, dmg, dsBullet) else Gear^.State:= Gear^.State or gstWinner; DeleteCI(t); t^.dX:= t^.dX + Gear^.dX * dmg * _0_01 + SignAs(cHHKick, Gear^.dX); t^.dY:= t^.dY + Gear^.dY * dmg * _0_01; t^.State:= t^.State or gstMoving; t^.Active:= true; FollowGear:= t end end; gtGrave: begin dmg:= 0; r:= Gear^.Radius + t^.Radius; dx:= Gear^.X-t^.X; dx.isNegative:= false; dy:= Gear^.Y-t^.Y; dy.isNegative:= false; if r-hwRound(dx+dy) > 0 then begin dist:= hwRound(Distance(dx, dy)); dmg:= ModifyDamage(min(r - dist, 25), t); end; if dmg > 0 then begin t^.dY:= - _0_1; t^.Active:= true end end; end; t:= t^.NextGear end;if (GameFlags and gfSolidLand) = 0 then DrawExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), cShotgunRadius)end;procedure AmmoShove(Ammo: PGear; Damage, Power: LongInt);var t: PGearArray; Gear: PGear; i, tmpDmg: LongInt; VGear: PVisualGear;begint:= CheckGearsCollision(Ammo);// Just to avoid hogs on rope dodging fire.if (CurAmmoGear <> nil) and ((CurAmmoGear^.Kind = gtRope) or (CurAmmoGear^.Kind = gtJetpack) or (CurAmmoGear^.Kind = gtBirdy)) and (CurrentHedgehog^.Gear <> nil) and (CurrentHedgehog^.Gear^.CollisionIndex = -1) and (sqr(hwRound(Ammo^.X) - hwRound(CurrentHedgehog^.Gear^.X)) + sqr(hwRound(Ammo^.Y) - hwRound(CurrentHedgehog^.Gear^.Y)) <= sqr(cHHRadius + Ammo^.Radius)) then begin t^.ar[t^.Count]:= CurrentHedgehog^.Gear; inc(t^.Count) end;i:= t^.Count;if (Ammo^.Kind = gtFlame) and (i > 0) then Ammo^.Health:= 0;while i > 0 do begin dec(i); Gear:= t^.ar[i]; tmpDmg:= ModifyDamage(Damage, Gear); if (Gear^.State and gstNoDamage) = 0 then begin if (Ammo^.Kind = gtDEagleShot) or (Ammo^.Kind = gtSniperRifleShot) then begin VGear := AddVisualGear(hwround(Ammo^.X), hwround(Ammo^.Y), vgtBulletHit); if VGear <> nil then VGear^.Angle := DxDy2Angle(-Ammo^.dX, Ammo^.dY); end; if (Gear^.Kind = gtHedgehog) and (Ammo^.State and gsttmpFlag <> 0) and (Ammo^.Kind = gtShover) then Gear^.FlightTime:= 1; case Gear^.Kind of gtHedgehog, gtMine, gtSMine, gtTarget, gtCase, gtExplosives, gtStructure: begin if (Ammo^.Kind = gtDrill) then begin Ammo^.Timer:= 0; exit; end; if (not Gear^.Invulnerable) then ApplyDamage(Gear, Ammo^.Hedgehog, tmpDmg, dsShove) else Gear^.State:= Gear^.State or gstWinner; if (Gear^.Kind = gtExplosives) and (Ammo^.Kind = gtBlowtorch) then begin if (Ammo^.Hedgehog^.Gear <> nil) then Ammo^.Hedgehog^.Gear^.State:= Ammo^.Hedgehog^.Gear^.State and not gstNotKickable; ApplyDamage(Gear, Ammo^.Hedgehog, tmpDmg * 100, dsUnknown); // crank up damage for explosives + blowtorch end; DeleteCI(Gear); if (Gear^.Kind = gtHedgehog) and Gear^.Hedgehog^.King then begin Gear^.dX:= Ammo^.dX * Power * _0_005; Gear^.dY:= Ammo^.dY * Power * _0_005 end else begin Gear^.dX:= Ammo^.dX * Power * _0_01; Gear^.dY:= Ammo^.dY * Power * _0_01 end; Gear^.Active:= true; Gear^.State:= Gear^.State or gstMoving; if TestCollisionXwithGear(Gear, hwSign(Gear^.dX)) then begin 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 (Ammo^.Kind <> gtFlame) or ((Ammo^.State and gsttmpFlag) = 0) then FollowGear:= Gear end; end end; end;if i <> 0 then SetAllToActiveend;procedure AssignHHCoords;var i, t, p, j: LongInt; ar: array[0..Pred(cMaxHHs)] of PHedgehog; Count: Longword;beginif (GameFlags and gfPlaceHog) <> 0 then PlacingHogs:= true;if (GameFlags and gfDivideTeams) <> 0 then begin t:= 0; TryDo(ClansCount = 2, 'More or less than 2 clans on map in divided teams mode!', true); for p:= 0 to 1 do begin with ClansArray[p]^ do for j:= 0 to Pred(TeamsNumber) do with Teams[j]^ do for i:= 0 to cMaxHHIndex do with Hedgehogs[i] do if (Gear <> nil) and (Gear^.X.QWordValue = 0) then begin if PlacingHogs then Unplaced:= true else FindPlace(Gear, false, t, t + LAND_WIDTH div 2);// could make Gear == nil; if Gear <> nil then begin Gear^.Pos:= GetRandom(49); Gear^.dX.isNegative:= p = 1; end end; t:= LAND_WIDTH div 2 end end else // mix hedgehogs begin Count:= 0; for p:= 0 to Pred(TeamsCount) do with TeamsArray[p]^ do begin for i:= 0 to cMaxHHIndex do with Hedgehogs[i] do if (Gear <> nil) and (Gear^.X.QWordValue = 0) then begin ar[Count]:= @Hedgehogs[i]; inc(Count) end; end; // unC0Rr, while it is true user can watch value on map screen, IMO this (and check above) should be enforced in UI // - is there a good place to put values for the different widgets to check? Right now they are kind of disconnected. //it would be nice if divide teams, forts mode and hh per map could all be checked by the team widget, or maybe disable start button TryDo(Count <= MaxHedgehogs, 'Too many hedgehogs for this map! (max # is ' + inttostr(MaxHedgehogs) + ')', true); while (Count > 0) do begin i:= GetRandom(Count); if PlacingHogs then ar[i]^.Unplaced:= true else FindPlace(ar[i]^.Gear, false, 0, LAND_WIDTH); if ar[i]^.Gear <> nil then begin ar[i]^.Gear^.dX.isNegative:= hwRound(ar[i]^.Gear^.X) > LAND_WIDTH div 2; ar[i]^.Gear^.Pos:= GetRandom(19) end; ar[i]:= ar[Count - 1]; dec(Count) end endend;function GearsNear(X, Y: hwFloat; Kind: TGearType; r: LongInt): TPGearArray;var t: PGear; l: Longword;begin r:= r*r; GearsNear := nil; t := GearsList; while t <> nil do begin if (t^.Kind = Kind) and ((X - t^.X)*(X - t^.X) + (Y - t^.Y)*(Y-t^.Y) < int2hwFloat(r)) then begin l:= Length(GearsNear); SetLength(GearsNear, l + 1); GearsNear[l] := t; end; t := t^.NextGear; end;end;function CheckGearNear(Gear: PGear; Kind: TGearType; rX, rY: LongInt): PGear;var t: PGear;begint:= GearsList;rX:= sqr(rX);rY:= sqr(rY);while t <> nil do begin if (t <> Gear) and (t^.Kind = Kind) then if not((hwSqr(Gear^.X - t^.X) / rX + hwSqr(Gear^.Y - t^.Y) / rY) > _1) then exit(t); t:= t^.NextGear end;CheckGearNear:= nilend;{procedure AmmoFlameWork(Ammo: PGear);var t: PGear;begint:= GearsList;while t <> nil do begin if (t^.Kind = gtHedgehog) and (t^.Y < Ammo^.Y) then if not (hwSqr(Ammo^.X - t^.X) + hwSqr(Ammo^.Y - t^.Y - int2hwFloat(cHHRadius)) * 2 > _2) then begin ApplyDamage(t, 5); t^.dX:= t^.dX + (t^.X - Ammo^.X) * _0_02; t^.dY:= - _0_25; t^.Active:= true; DeleteCI(t); FollowGear:= t end; t:= t^.NextGear end;end;}function CheckGearsNear(mX, mY: LongInt; Kind: TGearsType; rX, rY: LongInt): PGear;var t: PGear;begint:= GearsList;rX:= sqr(rX);rY:= sqr(rY);while t <> nil do begin if t^.Kind in Kind then if not (hwSqr(int2hwFloat(mX) - t^.X) / rX + hwSqr(int2hwFloat(mY) - t^.Y) / rY > _1) then exit(t); t:= t^.NextGear end;CheckGearsNear:= nilend;function CountGears(Kind: TGearType): Longword;var t: PGear; count: Longword = 0;begint:= GearsList;while t <> nil do begin if t^.Kind = Kind then inc(count); t:= t^.NextGear end;CountGears:= count;end;procedure ResurrectHedgehog(gear: PGear);var tempTeam : PTeam;begin AttackBar:= 0; gear^.dX := _0; gear^.dY := _0; gear^.Damage := 0; gear^.Health := gear^.Hedgehog^.InitialHealth; gear^.Hedgehog^.Effects[hePoisoned] := false; if not CurrentHedgehog^.Effects[heResurrectable] then with CurrentHedgehog^ do begin inc(Team^.stats.AIKills); if Team^.AIKillsTex <> nil then FreeTexture(Team^.AIKillsTex); Team^.AIKillsTex := RenderStringTex(inttostr(Team^.stats.AIKills), Team^.Clan^.Color, fnt16); end; tempTeam := gear^.Hedgehog^.Team; DeleteCI(gear); FindPlace(gear, false, 0, LAND_WIDTH, true); if gear <> nil then begin RenderHealth(gear^.Hedgehog^); ScriptCall('onGearResurrect', gear^.uid); gear^.State := gstWait; end; RecountTeamHealth(tempTeam);end;function SpawnCustomCrateAt(x, y: LongInt; crate: TCrateType; content: Longword): PGear;begin FollowGear := AddGear(x, y, gtCase, 0, _0, _0, 0); cCaseFactor := 0; if (crate <> HealthCrate) and (content > ord(High(TAmmoType))) then content := ord(High(TAmmoType)); case crate of HealthCrate: begin FollowGear^.Pos := posCaseHealth; FollowGear^.Health := content; AddCaption(GetEventString(eidNewHealthPack), cWhiteColor, capgrpAmmoInfo); end; AmmoCrate: begin FollowGear^.Pos := posCaseAmmo; FollowGear^.AmmoType := TAmmoType(content); AddCaption(GetEventString(eidNewAmmoPack), cWhiteColor, capgrpAmmoInfo); end; UtilityCrate: begin FollowGear^.Pos := posCaseUtility; FollowGear^.AmmoType := TAmmoType(content); AddCaption(GetEventString(eidNewUtilityPack), cWhiteColor, capgrpAmmoInfo); end; end; if ( (x = 0) and (y = 0) ) then FindPlace(FollowGear, true, 0, LAND_WIDTH); SpawnCustomCrateAt := FollowGear;end;function SpawnFakeCrateAt(x, y: LongInt; crate: TCrateType; explode: boolean; poison: boolean): PGear;begin FollowGear := AddGear(x, y, gtCase, 0, _0, _0, 0); cCaseFactor := 0; FollowGear^.Pos := posCaseDummy; if explode then FollowGear^.Pos := FollowGear^.Pos + posCaseExplode; if poison then FollowGear^.Pos := FollowGear^.Pos + posCasePoison; case crate of HealthCrate: begin FollowGear^.Pos := FollowGear^.Pos + posCaseHealth; AddCaption(GetEventString(eidNewHealthPack), cWhiteColor, capgrpAmmoInfo); end; AmmoCrate: begin FollowGear^.Pos := FollowGear^.Pos + posCaseAmmo; AddCaption(GetEventString(eidNewAmmoPack), cWhiteColor, capgrpAmmoInfo); end; UtilityCrate: begin FollowGear^.Pos := FollowGear^.Pos + posCaseUtility; AddCaption(GetEventString(eidNewUtilityPack), cWhiteColor, capgrpAmmoInfo); end; end; if ( (x = 0) and (y = 0) ) then FindPlace(FollowGear, true, 0, LAND_WIDTH); SpawnFakeCrateAt := FollowGear;end;function GetAmmo: TAmmoType;var t, aTot: LongInt; i: TAmmoType;beginaTot:= 0;for i:= Low(TAmmoType) to High(TAmmoType) do if (Ammoz[i].Ammo.Propz and ammoprop_Utility) = 0 then inc(aTot, Ammoz[i].Probability);t:= aTot;i:= Low(TAmmoType);if (t > 0) then begin t:= GetRandom(t); while t >= 0 do begin inc(i); if (Ammoz[i].Ammo.Propz and ammoprop_Utility) = 0 then dec(t, Ammoz[i].Probability) end end;GetAmmo:= iend;function GetUtility: TAmmoType;var t, uTot: LongInt; i: TAmmoType;beginuTot:= 0;for i:= Low(TAmmoType) to High(TAmmoType) do if (Ammoz[i].Ammo.Propz and ammoprop_Utility) <> 0 then inc(uTot, Ammoz[i].Probability);t:= uTot;i:= Low(TAmmoType);if (t > 0) then begin t:= GetRandom(t); while t >= 0 do begin inc(i); if (Ammoz[i].Ammo.Propz and ammoprop_Utility) <> 0 then dec(t, Ammoz[i].Probability) end end;GetUtility:= iend;procedure SpawnBoxOfSmth;var t, aTot, uTot, a, h: LongInt; i: TAmmoType;beginif (PlacingHogs) or (cCaseFactor = 0) or (CountGears(gtCase) >= 5) or (GetRandom(cCaseFactor) <> 0) then exit;FollowGear:= nil;aTot:= 0;uTot:= 0;for i:= Low(TAmmoType) to High(TAmmoType) do if (Ammoz[i].Ammo.Propz and ammoprop_Utility) = 0 then inc(aTot, Ammoz[i].Probability) else inc(uTot, Ammoz[i].Probability);t:=0;a:=aTot;h:= 1;if (aTot+uTot) <> 0 then if ((GameFlags and gfInvulnerable) = 0) then begin h:= cHealthCaseProb * 100; t:= GetRandom(10000); a:= (10000-h)*aTot div (aTot+uTot) end else begin t:= GetRandom(aTot+uTot); h:= 0 end;if t<h then begin FollowGear:= AddGear(0, 0, gtCase, 0, _0, _0, 0); FollowGear^.Health:= cHealthCaseAmount; FollowGear^.Pos:= posCaseHealth; AddCaption(GetEventString(eidNewHealthPack), cWhiteColor, capgrpAmmoInfo); endelse if (t<a+h) then begin t:= aTot; if (t > 0) then begin FollowGear:= AddGear(0, 0, gtCase, 0, _0, _0, 0); t:= GetRandom(t); i:= Low(TAmmoType); FollowGear^.Pos:= posCaseAmmo; FollowGear^.AmmoType:= i; AddCaption(GetEventString(eidNewAmmoPack), cWhiteColor, capgrpAmmoInfo); end endelse begin t:= uTot; if (t > 0) then begin FollowGear:= AddGear(0, 0, gtCase, 0, _0, _0, 0); t:= GetRandom(t); i:= Low(TAmmoType); FollowGear^.Pos:= posCaseUtility; FollowGear^.AmmoType:= i; AddCaption(GetEventString(eidNewUtilityPack), cWhiteColor, capgrpAmmoInfo); end end;// handles case of no ammo or utility crates - considered also placing booleans in uAmmos and altering probabilitiesif (FollowGear <> nil) then begin FindPlace(FollowGear, true, 0, LAND_WIDTH); if (FollowGear <> nil) then AddVoice(sndReinforce, CurrentTeam^.voicepack) endend;procedure FindPlace(var Gear: PGear; withFall: boolean; Left, Right: LongInt; skipProximity: boolean = false); function CountNonZeroz(x, y, r, c: LongInt): LongInt; var i: LongInt; count: LongInt = 0; begin if (y and LAND_HEIGHT_MASK) = 0 then for i:= max(x - r, 0) to min(x + r, LAND_WIDTH - 4) do if Land[y, i] <> 0 then begin inc(count); if count = c then exit(count) end; CountNonZeroz:= count; end;var x: LongInt; y, sy: LongInt; ar: array[0..511] of TPoint; ar2: array[0..1023] of TPoint; cnt, cnt2: Longword; delta: LongInt; reallySkip, tryAgain: boolean;beginreallySkip:= false; // try not skipping proximity at firsttryAgain:= true;while tryAgain do begin delta:= 250; cnt2:= 0; repeat x:= Left + LongInt(GetRandom(Delta)); repeat inc(x, Delta); cnt:= 0; y:= min(1024, topY) - 2 * Gear^.Radius; while y < cWaterLine do begin repeat inc(y, 2); until (y >= cWaterLine) or (CountNonZeroz(x, y, Gear^.Radius - 1, 1) = 0); sy:= y; repeat inc(y); until (y >= cWaterLine) or (CountNonZeroz(x, y, Gear^.Radius - 1, 1) <> 0); if (y - sy > Gear^.Radius * 2) and (((Gear^.Kind = gtExplosives) and (y < cWaterLine) and (reallySkip or (CheckGearsNear(x, y - Gear^.Radius, [gtFlame, gtHedgehog, gtMine, gtCase, gtExplosives], 60, 60) = nil)) and (CountNonZeroz(x, y+1, Gear^.Radius - 1, Gear^.Radius+1) > Gear^.Radius)) or ((Gear^.Kind <> gtExplosives) and (y < cWaterLine) and (reallySkip or (CheckGearsNear(x, y - Gear^.Radius, [gtFlame, gtHedgehog, gtMine, gtCase, gtExplosives], 110, 110) = nil)))) then begin ar[cnt].X:= x; if withFall then ar[cnt].Y:= sy + Gear^.Radius else ar[cnt].Y:= y - Gear^.Radius; inc(cnt) end; inc(y, 45) end; if cnt > 0 then with ar[GetRandom(cnt)] do begin ar2[cnt2].x:= x; ar2[cnt2].y:= y; inc(cnt2) end until (x + Delta > Right); dec(Delta, 60) until (cnt2 > 0) or (Delta < 70); if (cnt2 = 0) and skipProximity and not reallySkip then tryAgain:= true else tryAgain:= false; reallySkip:= true; end;if cnt2 > 0 then with ar2[GetRandom(cnt2)] do begin Gear^.X:= int2hwFloat(x); Gear^.Y:= int2hwFloat(y); AddFileLog('Assigned Gear coordinates (' + inttostr(x) + ',' + inttostr(y) + ')'); end else begin OutError('Can''t find place for Gear', false); if Gear^.Kind = gtHedgehog then Gear^.Hedgehog^.Effects[heResurrectable] := false; DeleteGear(Gear); Gear:= nil endend;function ModifyDamage(dmg: Longword; Gear: PGear): Longword;var i: hwFloat;begin(* Invulnerability cannot be placed in here due to still needing kicks Not without a new damage machine. King check should be in here instead of ApplyDamage since Tiy wants them kicked less*)i:= _1;if (CurrentHedgehog <> nil) and CurrentHedgehog^.King then i:= _1_5;if (Gear^.Hedgehog <> nil) and (Gear^.Hedgehog^.King) then ModifyDamage:= hwRound(_0_01 * cDamageModifier * dmg * i * cDamagePercent * _0_5)else ModifyDamage:= hwRound(_0_01 * cDamageModifier * dmg * i * cDamagePercent)end;function GearByUID(uid : Longword) : PGear;var gear: PGear;beginGearByUID:= nil;if uid = 0 then exit;if (lastGearByUID <> nil) and (lastGearByUID^.uid = uid) then begin GearByUID:= lastGearByUID; exit end;gear:= GearsList;while gear <> nil do begin if gear^.uid = uid then begin lastGearByUID:= gear; GearByUID:= gear; exit end; gear:= gear^.NextGear endend;procedure chSkip(var s: shortstring);begins:= s; // avoid compiler hintif not CurrentTeam^.ExtDriven then SendIPC(',');uStats.Skipped;skipFlag:= trueend;procedure chHogSay(var s: shortstring);var Gear: PVisualGear; text: shortstring; hh: PHedgehog; i, x, t, h: byte; c, j: LongInt;begin hh:= nil; i:= 0; t:= 0; x:= byte(s[1]); // speech type if x < 4 then begin t:= byte(s[2]); // team if Length(s) > 2 then h:= byte(s[3]) // target hog end; // allow targetting a hog by specifying a number as the first portion of the text if (x < 4) and (h > byte('0')) and (h < byte('9')) then i:= h - 48; if i <> 0 then text:= copy(s, 4, Length(s) - 1) else if x < 4 then text:= copy(s, 3, Length(s) - 1) else text:= copy(s, 2, Length(s) - 1); (* if CheckNoTeamOrHH then begin ParseCommand('say ' + text, true); exit end; *) if (x < 4) and (TeamsArray[t] <> nil) then begin // if team matches current hedgehog team, default to current hedgehog if (i = 0) and (CurrentHedgehog <> nil) and (CurrentHedgehog^.Team = TeamsArray[t]) then hh:= CurrentHedgehog else begin // otherwise use the first living hog or the hog amongs the remaining ones indicated by i j:= 0; c:= 0; while (j <= cMaxHHIndex) and (hh = nil) do begin if (TeamsArray[t]^.Hedgehogs[j].Gear <> nil) then begin inc(c); if (i=0) or (i=c) then hh:= @TeamsArray[t]^.Hedgehogs[j] end; inc(j) end end; if hh <> nil then begin Gear:= AddVisualGear(0, 0, vgtSpeechBubble); if Gear <> nil then begin Gear^.Hedgehog:= hh; Gear^.Text:= text; Gear^.FrameTicks:= x end end //else ParseCommand('say ' + text, true) end else if (x >= 4) then begin SpeechType:= x-3; SpeechText:= text end;end;procedure initModule;begin RegisterVariable('skip', vtCommand, @chSkip, false); RegisterVariable('hogsay', vtCommand, @chHogSay, true ); CurAmmoGear:= nil; GearsList:= nil; KilledHHs:= 0; SuddenDeath:= false; SuddenDeathDmg:= false; SpeechType:= 1; skipFlag:= false; AllInactive:= false; PrvInactive:= false;end;procedure freeModule;begin FreeGearsList();end;end.