diff -r 31570b766315 -r ed5a6478e710 hedgewars/uGearsUtils.pas --- a/hedgewars/uGearsUtils.pas Tue Nov 10 18:16:35 2015 +0100 +++ b/hedgewars/uGearsUtils.pas Tue Nov 10 20:43:13 2015 +0100 @@ -1,6 +1,6 @@ (* * Hedgewars, a free turn based strategy game - * Copyright (c) 2004-2013 Andrey Korotaev + * Copyright (c) 2004-2015 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 @@ -13,7 +13,7 @@ * * 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 + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA *) {$INCLUDE "options.inc"} @@ -23,7 +23,9 @@ uses uTypes, uFloat; procedure doMakeExplosion(X, Y, Radius: LongInt; AttackingHog: PHedgehog; Mask: Longword); inline; -procedure doMakeExplosion(X, Y, Radius: LongInt; AttackingHog: PHedgehog; Mask: Longword; const Tint: LongWord); +procedure doMakeExplosion(X, Y, Radius: LongInt; AttackingHog: PHedgehog; Mask: Longword; const Tint: LongWord); +procedure AddSplashForGear(Gear: PGear; justSkipping: boolean); +procedure AddBounceEffectForGear(Gear: PGear); function ModifyDamage(dmg: Longword; Gear: PGear): Longword; procedure ApplyDamage(Gear: PGear; AttackerHog: PHedgehog; Damage: Longword; Source: TDamageSource); @@ -47,8 +49,8 @@ procedure ShotgunShot(Gear: PGear); procedure SetAllToActive; -procedure SetAllHHToActive; inline; procedure SetAllHHToActive(Ice: boolean); +procedure SetAllHHToActive(); inline; function GetAmmo(Hedgehog: PHedgehog): TAmmoType; function GetUtility(Hedgehog: PHedgehog): TAmmoType; @@ -64,7 +66,7 @@ implementation uses uSound, uCollisions, uUtils, uConsts, uVisualGears, uAIMisc, uVariables, uLandGraphics, uScript, uStats, uCaptions, uTeams, uStore, - uLocale, uTextures, uRenderUtils, uRandom, SDLh, uDebug, + uLocale, uTextures, uRenderUtils, uRandom, SDLh, uDebug, uGearsList, Math, uVisualGearsList, uGearsHandlersMess, uGearsHedgehog; @@ -79,6 +81,8 @@ fX, fY, tdX, tdY: hwFloat; vg: PVisualGear; i, cnt: LongInt; + wrap: boolean; + bubble: PVisualGear; begin if Radius > 4 then AddFileLog('Explosion: at (' + inttostr(x) + ',' + inttostr(y) + ')'); if Radius > 25 then KickFlakes(Radius, X, Y); @@ -86,7 +90,17 @@ if ((Mask and EXPLNoGfx) = 0) then begin vg:= nil; - if Radius > 50 then vg:= AddVisualGear(X, Y, vgtBigExplosion) + if CheckCoordInWater(X, Y - Radius) then + begin + cnt:= 2 * Radius; + for i:= (Radius * Radius) div 4 downto 0 do + begin + bubble := AddVisualGear(X - Radius + random(cnt), Y - Radius + random(cnt), vgtBubble); + if bubble <> nil then + bubble^.dY:= 0.1 + random(20)/10; + end + end + else 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; @@ -99,9 +113,15 @@ dmgRadius:= Radius; dmgBase:= dmgRadius + cHHRadius div 2;*) dmgBase:= Radius shl 1 + cHHRadius div 2; + +// we might have to run twice if weWrap is enabled +wrap:= false; +repeat + fX:= int2hwFloat(X); fY:= int2hwFloat(Y); Gear:= GearsList; + while Gear <> nil do begin dmg:= 0; @@ -118,6 +138,7 @@ gtClusterBomb, // gtCluster, too game breaking I think gtSMine, + gtAirMine, gtCase, gtTarget, gtFlame, @@ -135,7 +156,7 @@ //AddFileLog('Damage: ' + inttostr(dmg)); if (Mask and EXPLNoDamage) = 0 then begin - if Gear^.Hedgehog^.Effects[heInvulnerable] = 0 then + if (Gear^.Kind <> gtHedgehog) or (Gear^.Hedgehog^.Effects[heInvulnerable] = 0) then ApplyDamage(Gear, AttackingHog, dmg, dsExplosion) else Gear^.State:= Gear^.State or gstWinner; @@ -148,29 +169,30 @@ Gear^.State:= (Gear^.State or gstMoving) and (not gstLoser); if Gear^.Kind = gtKnife then Gear^.State:= Gear^.State and (not gstCollision); - if Gear^.Hedgehog^.Effects[heInvulnerable] = 0 then + if (Gear^.Kind = gtHedgehog) and (Gear^.Hedgehog^.Effects[heInvulnerable] = 0) 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 (Gear^.Hedgehog^.Effects[heInvulnerable] = 0) and (Gear^.State and gstHHDeath = 0) then - Gear^.Hedgehog^.Effects[hePoisoned] := 1; + Gear^.Hedgehog^.Effects[hePoisoned] := 5; end; end; - gtGrave: begin + gtGrave: if Mask and EXPLDoNotTouchAny = 0 then // Run the calcs only once we know we have a type that will need damage - tdX:= Gear^.X-fX; - tdY:= Gear^.Y-fY; - if LongInt(tdX.Round + tdY.Round + 2) < dmgBase then - dmg:= dmgBase - hwRound(Distance(tdX, tdY)); - if dmg > 1 then begin - dmg:= ModifyDamage(min(dmg div 2, Radius), Gear); - Gear^.dY:= - _0_004 * dmg; - Gear^.Active:= true - end - end; + tdX:= Gear^.X-fX; + tdY:= Gear^.Y-fY; + if LongInt(tdX.Round + tdY.Round + 2) < dmgBase then + dmg:= dmgBase - hwRound(Distance(tdX, tdY)); + 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 @@ -185,6 +207,27 @@ AddVisualGear(X, Y, vgtChunk) end; +if (WorldEdge = weWrap) then + begin + // already wrapped? let's not wrap again! + if wrap then + break; + + // Radius + 5 because that's the actual radius the explosion changes graphically + if X + (Radius + 5) > LongInt(rightX) then + begin + dec(X, playWidth); + wrap:= true; + end + else if X - (Radius + 5) < LongInt(leftX) then + begin + inc(X, playWidth); + wrap:= true; + end; + end; + +until (not wrap); + uAIMisc.AwareOfExplosion(0, 0, 0) end; @@ -198,10 +241,11 @@ i:= _1; if (CurrentHedgehog <> nil) and CurrentHedgehog^.King then i:= _1_5; -if (Gear^.Hedgehog <> nil) and (Gear^.Hedgehog^.King or (Gear^.Hedgehog^.Effects[heFrozen] > 0)) then +if (Gear^.Kind = gtHedgehog) and (Gear^.Hedgehog <> nil) and + (Gear^.Hedgehog^.King or (Gear^.Hedgehog^.Effects[heFrozen] > 0)) then ModifyDamage:= hwRound(cDamageModifier * dmg * i * cDamagePercent * _0_5 * _0_01) else - ModifyDamage:= hwRound(cDamageModifier * dmg * i * cDamagePercent * _0_01) + ModifyDamage:= hwRound(cDamageModifier * dmg * i * cDamagePercent * _0_01); end; procedure ApplyDamage(Gear: PGear; AttackerHog: PHedgehog; Damage: Longword; Source: TDamageSource); @@ -232,7 +276,7 @@ inc(CurrentHedgehog^.Gear^.Health,vampDmg); str(vampDmg, s); s:= '+' + s; - AddCaption(s, CurrentHedgehog^.Team^.Clan^.Color, capgrpAmmoinfo); + AddCaption(ansistring(s), CurrentHedgehog^.Team^.Clan^.Color, capgrpAmmoinfo); RenderHealth(CurrentHedgehog^); RecountTeamHealth(CurrentHedgehog^.Team); i:= 0; @@ -249,20 +293,41 @@ end; end end; - if (GameFlags and gfKarma <> 0) and (GameFlags and gfInvulnerable = 0) and + if (GameFlags and gfKarma <> 0) and (GameFlags and gfInvulnerable = 0) and (CurrentHedgehog^.Effects[heInvulnerable] = 0) 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); + uStats.HedgehogDamaged(Gear, AttackerHog, Damage, false); end; + + if AprilOne and (Gear^.Hedgehog^.Hat = 'fr_tomato') and (Damage > 2) then + for i := 0 to random(min(Damage,20))+5 do + begin + vg:= AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtStraightShot); + if vg <> nil then + with vg^ do + begin + dx:= 0.001 * (random(100)+10); + dy:= 0.001 * (random(100)+10); + tdy:= -cGravityf; + if random(2) = 0 then + dx := -dx; + //if random(2) = 0 then + // dy := -dy; + FrameTicks:= random(500) + 1000; + State:= ord(sprBubbles); + //Tint:= $bd2f03ff + Tint:= $ff0000ff + end + end end else //else if Gear^.Kind <> gtStructure then // not gtHedgehog nor gtStructure Gear^.Hedgehog:= AttackerHog; inc(Gear^.Damage, Damage); - + ScriptCall('onGearDamage', Gear^.UID, Damage); end; @@ -275,10 +340,11 @@ AllInactive:= false; HHGear^.Active:= true; end; - + procedure HHHurt(Hedgehog: PHedgehog; Source: TDamageSource); begin if Hedgehog^.Effects[heFrozen] <> 0 then exit; + if (Source = dsFall) or (Source = dsExplosion) then case random(3) of 0: PlaySoundV(sndOoff1, Hedgehog^.Team^.voicepack); @@ -300,8 +366,8 @@ end; procedure CheckHHDamage(Gear: PGear); -var - dmg: Longword; +var + dmg: LongInt; i: LongWord; particle: PVisualGear; begin @@ -314,7 +380,7 @@ if dmg < 1 then exit; - for i:= min(12, (3 + dmg div 10)) downto 0 do + 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 @@ -338,7 +404,7 @@ procedure CalcRotationDirAngle(Gear: PGear); -var +var dAngle: real; begin // Frac/Round to be kind to JS as of 2012-08-27 where there is yet no int64/uint64 @@ -355,18 +421,182 @@ Gear^.DirAngle := Gear^.DirAngle - 360 end; +procedure AddSplashForGear(Gear: PGear; justSkipping: boolean); +var x, y, i, distL, distR, distB, minDist, maxDrops: LongInt; + splash, particle: PVisualGear; + speed, hwTmp: hwFloat; + vi, vs, tmp: real; // impact speed and sideways speed + isImpactH, isImpactRight: boolean; +const dist2surf = 4; +begin +x:= hwRound(Gear^.X); +y:= hwRound(Gear^.Y); + +// find position for splash and impact speed + +distB:= cWaterline - y; + +if WorldEdge <> weSea then + minDist:= distB +else + begin + distL:= x - leftX; + distR:= rightX - x; + minDist:= min(distB, min(distL, distR)); + end; + +isImpactH:= (minDist <> distB); + +if not isImpactH then + begin + y:= cWaterline - dist2surf; + speed:= hwAbs(Gear^.dY); + end +else + begin + isImpactRight := minDist = distR; + if isImpactRight then + x:= rightX - dist2surf + else + x:= leftX + dist2surf; + speed:= hwAbs(Gear^.dX); + end; + +// splash sound + +if justSkipping then + PlaySound(sndSkip) +else + begin + // adjust water impact sound based on gear speed and density + hwTmp:= hwAbs(Gear^.Density * speed); + + if hwTmp > _1 then + PlaySound(sndSplash) + else if hwTmp > _0_5 then + PlaySound(sndSkip) + else + PlaySound(sndDroplet2); + end; + + +// splash visuals + +if ((cReducedQuality and rqPlainSplash) <> 0) then + exit; + +splash:= AddVisualGear(x, y, vgtSplash); +if splash = nil then + exit; + +if not isImpactH then + vs:= abs(hwFloat2Float(Gear^.dX)) +else + begin + if isImpactRight then + splash^.Angle:= -90 + else + splash^.Angle:= 90; + vs:= abs(hwFloat2Float(Gear^.dY)); + end; + + +vi:= hwFloat2Float(speed); + +with splash^ do + begin + Scale:= abs(hwFloat2Float(Gear^.Density / _3 * speed)); + if Scale > 1 then Scale:= power(Scale,0.3333) + else Scale:= Scale + ((1-Scale) / 2); + if Scale > 1 then Timer:= round(min(Scale*0.0005/cGravityf,4)) + else Timer:= 1; + // Low Gravity + FrameTicks:= FrameTicks*Timer; + end; + + +// eject water drops + +maxDrops := (hwRound(Gear^.Density) * 3) div 2 + round((vi + vs) * hwRound(Gear^.Density) * 6); +for i:= max(maxDrops div 3, min(32, Random(maxDrops))) downto 0 do + begin + if isImpactH then + particle := AddVisualGear(x, y - 3 + Random(7), vgtDroplet) + else + particle := AddVisualGear(x - 3 + Random(7), y, vgtDroplet); + + if particle <> nil then + with particle^ do + begin + // dX and dY were initialized to have a random value on creation (see uVisualGearsList) + if isImpactH then + begin + tmp:= dX; + if isImpactRight then + dX:= dY - vi / 5 + else + dX:= -dy + vi / 5; + dY:= tmp * (1 + vs / 10); + end + else + begin + dX:= dX * (1 + vs / 10); + dY:= dY - vi / 5; + end; + + if splash <> nil then + begin + if splash^.Scale > 1 then + begin + dX:= dX * power(splash^.Scale, 0.3333); // tone down the droplet height further + dY:= dY * power(splash^.Scale, 0.3333); + end + else + begin + dX:= dX * splash^.Scale; + dY:= dY * splash^.Scale; + end; + end; + end + end; + +end; + +procedure DrownGear(Gear: PGear); +begin +Gear^.doStep := @doStepDrowningGear; + +Gear^.Timer := 5000; // how long game should wait +end; + function CheckGearDrowning(var Gear: PGear): boolean; -var +var skipSpeed, skipAngle, skipDecay: hwFloat; - i, maxDrops, X, Y: LongInt; - vdX, vdY: real; - particle, splash: PVisualGear; - isSubmersible: boolean; + tmp, X, Y, dist2Water: LongInt; + isSubmersible, isDirH, isImpact, isSkip: boolean; + s: ansistring; begin // probably needs tweaking. might need to be in a case statement based upon gear type + X:= hwRound(Gear^.X); Y:= hwRound(Gear^.Y); - if cWaterLine < Y + Gear^.Radius then + + dist2Water:= cWaterLine - (Y + Gear^.Radius); + isDirH:= false; + + if WorldEdge = weSea then begin + tmp:= dist2Water; + dist2Water:= min(dist2Water, min(X - Gear^.Radius - LongInt(leftX), LongInt(rightX) - (X + Gear^.Radius))); + // if water on sides is closer than on bottom -> horizontal direction + isDirH:= tmp <> dist2Water; + end; + + isImpact:= false; + + if dist2Water < 0 then + begin + // invisible gears will just be deleted + // unless they are generic fallers, then they will be "respawned" if Gear^.State and gstInvisible <> 0 then begin if Gear^.Kind = gtGenericFaller then @@ -377,27 +607,41 @@ Gear^.dY:= _90-(GetRandomf*_360) end else DeleteGear(Gear); - exit + exit(true) end; isSubmersible:= ((Gear = CurrentHedgehog^.Gear) and (CurAmmoGear <> nil) and (CurAmmoGear^.State and gstSubmersible <> 0)) or (Gear^.State and gstSubmersible <> 0); + skipSpeed := _0_25; skipAngle := _1_9; skipDecay := _0_87; - X:= hwRound(Gear^.X); - vdX:= hwFloat2Float(Gear^.dX); - vdY:= hwFloat2Float(Gear^.dY); - // this could perhaps be a tiny bit higher. - if (cWaterLine + 64 + Gear^.Radius > Y) and (hwSqr(Gear^.dX) + hwSqr(Gear^.dY) > skipSpeed) - and (hwAbs(Gear^.dX) > skipAngle * hwAbs(Gear^.dY)) then + + + // skipping + + if (not isSubmersible) and (hwSqr(Gear^.dX) + hwSqr(Gear^.dY) > skipSpeed) + and ( ((not isDirH) and (hwAbs(Gear^.dX) > skipAngle * hwAbs(Gear^.dY))) + or (isDirH and (hwAbs(Gear^.dY) > skipAngle * hwAbs(Gear^.dX))) ) then begin - Gear^.dY.isNegative := true; + isSkip:= true; + // if skipping we move the gear out of water + if isDirH then + begin + Gear^.dX.isNegative := (not Gear^.dX.isNegative); + Gear^.X:= Gear^.X + Gear^.dX; + end + else + begin + Gear^.dY.isNegative := (not Gear^.dY.isNegative); + Gear^.Y:= Gear^.Y + Gear^.dY; + end; Gear^.dY := Gear^.dY * skipDecay; Gear^.dX := Gear^.dX * skipDecay; CheckGearDrowning := false; - PlaySound(sndSkip) end - else + else // not skipping begin + isImpact:= true; + isSkip:= false; if not isSubmersible then begin CheckGearDrowning := true; @@ -411,85 +655,66 @@ begin // Gear could become nil after this, just exit to skip splashes ResurrectHedgehog(Gear); - exit + exit(true) end else begin - Gear^.doStep := @doStepDrowningGear; + DrownGear(Gear); Gear^.State := Gear^.State and (not gstHHDriven); - AddCaption(Format(GetEventString(eidDrowned), Gear^.Hedgehog^.Name), cWhiteColor, capgrpMessage); + s:= ansistring(Gear^.Hedgehog^.Name); + AddCaption(FormatA(GetEventString(eidDrowned), s), cWhiteColor, capgrpMessage); end end else - Gear^.doStep := @doStepDrowningGear; - if Gear^.Kind = gtFlake then - exit // skip splashes + DrownGear(Gear); + if Gear^.Kind = gtFlake then + exit(true); // skip splashes end - else if (Y > cWaterLine + cVisibleWater*4) and - ((Gear <> CurrentHedgehog^.Gear) or (CurAmmoGear = nil) or (CurAmmoGear^.State and gstSubmersible = 0)) then - Gear^.doStep:= @doStepDrowningGear; - if ((not isSubmersible) and (Y < cWaterLine + 64 + Gear^.Radius)) - or (isSubmersible and (Y < cWaterLine + 2 + Gear^.Radius) and (Gear = CurAmmoGear) and ((CurAmmoGear^.Pos = 0) - and (CurAmmoGear^.dY < _0_01))) then - if Gear^.Density * Gear^.dY > _1 then - PlaySound(sndSplash) - else if Gear^.Density * Gear^.dY > _0_5 then - PlaySound(sndSkip) - else - PlaySound(sndDroplet2); - end; - - if ((cReducedQuality and rqPlainSplash) = 0) - and (((not isSubmersible) and (Y < cWaterLine + 64 + Gear^.Radius)) - or (isSubmersible and (Y < cWaterLine + 2 + Gear^.Radius) and (Gear = CurAmmoGear) and ((CurAmmoGear^.Pos = 0) - and (CurAmmoGear^.dY < _0_01)))) then - begin - splash:= AddVisualGear(X, cWaterLine, vgtSplash); - if splash <> nil then - with splash^ do + else // submersible begin - Scale:= hwFloat2Float(Gear^.Density / _3 * Gear^.dY); - if Scale > 1 then Scale:= power(Scale,0.3333) - else Scale:= Scale + ((1-Scale) / 2); - if Scale > 1 then Timer:= round(min(Scale*0.0005/cGravityf,4)) - else Timer:= 1; - // Low Gravity - FrameTicks:= FrameTicks*Timer; - end; + // drown submersible grears if far below map + if (Y > cWaterLine + cVisibleWater*4) then + begin + DrownGear(Gear); + exit(true); // no splashes needed + end; + + CheckGearDrowning := false; - maxDrops := (hwRound(Gear^.Density) * 3) div 2 + round(vdX * hwRound(Gear^.Density) * 6) + round(vdY * hwRound(Gear^.Density) * 6); - for i:= max(maxDrops div 3, min(32, Random(maxDrops))) downto 0 do - begin - particle := AddVisualGear(X - 3 + Random(7), cWaterLine, vgtDroplet); - if particle <> nil then - with particle^ do + // check if surface was penetrated + + // no penetration if center's water distance not smaller than radius + if abs(dist2Water + Gear^.Radius) >= Gear^.Radius then + isImpact:= false + else + begin + // get distance to water of last tick + if isDirH then begin - dX := dX - vdX / 10; - dY := dY - vdY / 5; - if splash <> nil then - begin - if splash^.Scale > 1 then - begin - dX:= dX * power(splash^.Scale,0.3333); // tone down the droplet height further - dY:= dY * power(splash^.Scale, 0.3333) - end - else - begin - dX:= dX * splash^.Scale; - dY:= dY * splash^.Scale - end - end + tmp:= hwRound(Gear^.X - Gear^.dX); + tmp:= abs(min(tmp - leftX, rightX - tmp)); end - end - end; - if isSubmersible and (Gear = CurAmmoGear) and (CurAmmoGear^.Pos = 0) then - CurAmmoGear^.Pos := 1000 + else + begin + tmp:= hwRound(Gear^.Y - Gear^.dY); + tmp:= abs(cWaterLine - tmp); + end; + + // there was an impact if distance was >= radius + isImpact:= (tmp >= Gear^.Radius) + end; + end; // end of submersible + end; // end of not skipping + + // splash sound animation and droplets + if isImpact or isSkip then + addSplashForGear(Gear, isSkip); + + if isSkip then + ScriptCall('onGearWaterSkip', Gear^.uid); end else - begin - if not (Gear^.Kind in [gtJetpack, gtBee]) then Gear^.State:= Gear^.State and not gstSubmersible; // making it temporary for most gears is more attractive I think CheckGearDrowning := false - end end; @@ -510,11 +735,11 @@ gear^.Hedgehog^.Effects[hePoisoned] := 0; if (CurrentHedgehog^.Effects[heResurrectable] = 0) or ((CurrentHedgehog^.Effects[heResurrectable] <> 0) and (Gear^.Hedgehog^.Team^.Clan <> CurrentHedgehog^.Team^.Clan)) then - with CurrentHedgehog^ do + with CurrentHedgehog^ do begin inc(Team^.stats.AIKills); - FreeTexture(Team^.AIKillsTex); - Team^.AIKillsTex := RenderStringTex(inttostr(Team^.stats.AIKills), Team^.Clan^.Color, fnt16); + FreeAndNilTexture(Team^.AIKillsTex); + Team^.AIKillsTex := RenderStringTex(ansistring(inttostr(Team^.stats.AIKills)), Team^.Clan^.Color, fnt16); end; tempTeam := gear^.Hedgehog^.Team; DeleteCI(gear); @@ -527,7 +752,7 @@ sparkles^.Tint:= tempTeam^.Clan^.Color shl 8 or $FF; //sparkles^.Angle:= random(360); end; - FindPlace(gear, false, 0, LAND_WIDTH, true); + FindPlace(gear, false, 0, LAND_WIDTH, true); if gear <> nil then begin AddVisualGear(hwRound(gear^.X), hwRound(gear^.Y), vgtExplosion); @@ -544,7 +769,7 @@ 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 + for i:= max(x - r, 0) to min(x + r, LAND_WIDTH - 1) do if Land[y, i] and mask <> 0 then begin inc(count); @@ -557,6 +782,28 @@ CountNonZeroz:= count; end; +function isSteadyPosition(x, y, r, c: LongInt; mask: Longword): boolean; +var cnt, i: LongInt; +begin + cnt:= 0; + isSteadyPosition:= false; + + if ((y and LAND_HEIGHT_MASK) = 0) and (x - r >= 0) and (x + r < LAND_WIDTH) then + begin + for i:= r - c + 2 to r do + begin + if (Land[y, x - i] and mask <> 0) then inc(cnt); + if (Land[y, x + i] and mask <> 0) then inc(cnt); + + if cnt >= c then + begin + isSteadyPosition:= true; + exit + end; + end; + end; +end; + function NoGearsToAvoid(mX, mY: LongInt; rX, rY: LongInt): boolean; var t: PGear; @@ -582,9 +829,10 @@ procedure FindPlace(var Gear: PGear; withFall: boolean; Left, Right: LongInt; skipProximity: boolean); var x: LongInt; - y, sy: LongInt; + y, sy, dir: LongInt; ar: array[0..1023] of TPoint; ar2: array[0..2047] of TPoint; + temp: TPoint; cnt, cnt2: Longword; delta: LongInt; ignoreNearObjects, ignoreOverlap, tryAgain: boolean; @@ -592,9 +840,9 @@ ignoreNearObjects:= false; // try not skipping proximity at first ignoreOverlap:= false; // this not only skips proximity, but allows overlapping objects (barrels, mines, hogs, crates). Saving it for a 3rd pass. With this active, winning AI Survival goes back to virtual impossibility tryAgain:= true; -if WorldEdge <> weNone then +if WorldEdge <> weNone then begin - Left:= max(Left,leftX+Gear^.Radius); + Left:= max(Left, LongInt(leftX) + Gear^.Radius); Right:= min(Right,rightX-Gear^.Radius) end; while tryAgain do @@ -602,17 +850,18 @@ delta:= LAND_WIDTH div 16; cnt2:= 0; repeat - x:= Left + max(LAND_WIDTH div 2048, LongInt(GetRandom(Delta))); + if GetRandom(2) = 0 then dir:= -1 else dir:= 1; + x:= max(LAND_WIDTH div 2048, LongInt(GetRandom(Delta))); + if dir = 1 then x:= Left + x else x:= Right - x; repeat - inc(x, Delta); cnt:= 0; - y:= min(1024, topY) - 2 * Gear^.Radius; + y:= min(1024, topY) - Gear^.Radius shl 1; while y < cWaterLine do begin repeat inc(y, 2); until (y >= cWaterLine) or - (not ignoreOverlap and (CountNonZeroz(x, y, Gear^.Radius - 1, 1, $FFFF) = 0)) or + ((not ignoreOverlap) and (CountNonZeroz(x, y, Gear^.Radius - 1, 1, $FFFF) = 0)) or (ignoreOverlap and (CountNonZeroz(x, y, Gear^.Radius - 1, 1, lfLandMask) = 0)); sy:= y; @@ -620,19 +869,19 @@ repeat inc(y); until (y >= cWaterLine) or - (not ignoreOverlap and (CountNonZeroz(x, y, Gear^.Radius - 1, 1, $FFFF) <> 0)) or - (ignoreOverlap and (CountNonZeroz(x, y, Gear^.Radius - 1, 1, lfLandMask) <> 0)); + ((not ignoreOverlap) and (CountNonZeroz(x, y, Gear^.Radius - 1, 1, $FFFF) <> 0)) or + (ignoreOverlap and (CountNonZeroz(x, y, Gear^.Radius - 1, 1, lfLandMask) <> 0)); - if (y - sy > Gear^.Radius * 2) + if (y - sy > Gear^.Radius * 2) and (y < cWaterLine) and (((Gear^.Kind = gtExplosives) - and (y < cWaterLine) - and (ignoreNearObjects or NoGearsToAvoid(x, y - Gear^.Radius, 60, 60)) - and (CountNonZeroz(x, y+1, Gear^.Radius - 1, Gear^.Radius+1, $FFFF) > Gear^.Radius)) - or - ((Gear^.Kind <> gtExplosives) - and (y < cWaterLine) - and (ignoreNearObjects or NoGearsToAvoid(x, y - Gear^.Radius, 110, 110)) - )) then + and (ignoreNearObjects or NoGearsToAvoid(x, y - Gear^.Radius, 60, 60)) + and (isSteadyPosition(x, y+1, Gear^.Radius - 1, 3, $FFFF) + or (CountNonZeroz(x, y+1, Gear^.Radius - 1, Gear^.Radius+1, $FFFF) > Gear^.Radius) + )) + or + ((Gear^.Kind <> gtExplosives) + and (ignoreNearObjects or NoGearsToAvoid(x, y - Gear^.Radius, 110, 110)) + )) then begin ar[cnt].X:= x; if withFall then @@ -646,13 +895,17 @@ end; if cnt > 0 then - with ar[GetRandom(cnt)] do + begin + temp := ar[GetRandom(cnt)]; + with temp do begin ar2[cnt2].x:= x; ar2[cnt2].y:= y; inc(cnt2) - end - until (x + Delta > Right); + end; + end; + inc(x, Delta*dir) + until ((dir = 1) and (x > Right)) or ((dir = -1) and (x < Left)); dec(Delta, 60) until (cnt2 > 0) or (Delta < 70); @@ -665,12 +918,15 @@ end; if cnt2 > 0 then - with ar2[GetRandom(cnt2)] do + begin + temp := ar2[GetRandom(cnt2)]; + with temp do begin Gear^.X:= int2hwFloat(x); Gear^.Y:= int2hwFloat(y); AddFileLog('Assigned Gear coordinates (' + inttostr(x) + ',' + inttostr(y) + ')'); end + end else begin OutError('Can''t find place for Gear', false); @@ -716,7 +972,7 @@ if (TestCollisionX(Gear, hwSign(Gear^.dX)) <> 0) or (TestCollisionY(Gear, hwSign(Gear^.dY)) <> 0) then Gear^.State := Gear^.State or gstCollision - else + else Gear^.State := Gear^.State and (not gstCollision) end; @@ -822,7 +1078,7 @@ end; if dmg > 0 then begin - if t^.Hedgehog^.Effects[heInvulnerable] = 0 then + if (t^.Kind <> gtHedgehog) or (t^.Hedgehog^.Effects[heInvulnerable] = 0) then ApplyDamage(t, Gear^.Hedgehog, dmg, dsBullet) else Gear^.State:= Gear^.State or gstWinner; @@ -885,14 +1141,14 @@ begin dec(i); Gear:= t^.ar[i]; - if ((Ammo^.Kind = gtFlame) or (Ammo^.Kind = gtBlowTorch)) and + if ((Ammo^.Kind = gtFlame) or (Ammo^.Kind = gtBlowTorch)) and (Gear^.Kind = gtHedgehog) and (Gear^.Hedgehog^.Effects[heFrozen] > 255) then Gear^.Hedgehog^.Effects[heFrozen]:= max(255,Gear^.Hedgehog^.Effects[heFrozen]-10000); tmpDmg:= ModifyDamage(Damage, Gear); if (Gear^.State and gstNoDamage) = 0 then begin - if (Ammo^.Kind = gtDEagleShot) or (Ammo^.Kind = gtSniperRifleShot) then + if (Ammo^.Kind = gtDEagleShot) or (Ammo^.Kind = gtSniperRifleShot) then begin VGear := AddVisualGear(hwround(Ammo^.X), hwround(Ammo^.Y), vgtBulletHit); if VGear <> nil then @@ -918,7 +1174,7 @@ Ammo^.Timer:= 0; exit; end; - if Gear^.Hedgehog^.Effects[heInvulnerable] = 0 then + if (Gear^.Kind <> gtHedgehog) or (Gear^.Hedgehog^.Effects[heInvulnerable] = 0) then begin if (Ammo^.Kind = gtKnife) and (tmpDmg > 0) then for j:= 1 to max(1,min(3,tmpDmg div 5)) do @@ -943,7 +1199,7 @@ end else Gear^.State:= Gear^.State or gstWinner; - if (Gear^.Kind = gtExplosives) and (Ammo^.Kind = gtBlowtorch) then + 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); @@ -1054,9 +1310,9 @@ s:= 0; SetLength(GearsNearArray, s); t := GearsList; - while t <> nil do + while t <> nil do begin - if (t^.Kind = Kind) + if (t^.Kind = Kind) and ((X - t^.X)*(X - t^.X) + (Y - t^.Y)*(Y-t^.Y) < int2hwFloat(r)) then begin inc(s); @@ -1113,6 +1369,8 @@ FollowGear:= AddGear(0, 0, gtCase, 0, _0, _0, 0); FollowGear^.Health:= cHealthCaseAmount; FollowGear^.Pos:= posCaseHealth; + // health crate is smaller than the other crates + FollowGear^.Radius := cCaseHealthRadius; AddCaption(GetEventString(eidNewHealthPack), cWhiteColor, capgrpAmmoInfo); end else if (t rightX) then +if (hwRound(Gear^.X) < LongInt(leftX)) or + (hwRound(Gear^.X) > LongInt(rightX)) then begin if WorldEdge = weWrap then begin - if (hwRound(Gear^.X)-Gear^.Radius < leftX) then - Gear^.X:= int2hwfloat(rightX-Gear^.Radius) - else Gear^.X:= int2hwfloat(leftX+Gear^.Radius); + if (hwRound(Gear^.X) < LongInt(leftX)) then + Gear^.X:= Gear^.X + int2hwfloat(rightX - leftX) + else Gear^.X:= Gear^.X - int2hwfloat(rightX - leftX); LeftImpactTimer:= 150; RightImpactTimer:= 150 end else if WorldEdge = weBounce then begin - if (hwRound(Gear^.X)-Gear^.Radius < leftX) then + if (hwRound(Gear^.X) - Gear^.Radius < LongInt(leftX)) then begin LeftImpactTimer:= 333; Gear^.dX.isNegative:= false; - Gear^.X:= int2hwfloat(leftX+Gear^.Radius) + Gear^.X:= int2hwfloat(LongInt(leftX) + Gear^.Radius) end - else + else begin RightImpactTimer:= 333; Gear^.dX.isNegative:= true; Gear^.X:= int2hwfloat(rightX-Gear^.Radius) end; if (Gear^.Radius > 2) and (Gear^.dX.QWordValue > _0_001.QWordValue) then - PlaySound(sndMelonImpact) - end + AddBounceEffectForGear(Gear); + end{ else if WorldEdge = weSea then begin if (hwRound(Gear^.Y) > cWaterLine) and (Gear^.State and gstSubmersible <> 0) then - Gear^.State:= Gear^.State and not gstSubmersible + Gear^.State:= Gear^.State and (not gstSubmersible) else begin Gear^.State:= Gear^.State or gstSubmersible; @@ -1262,12 +1520,12 @@ Gear^.dY:= tdx; Gear^.dY.isNegative:= true end - end; + end}; (* * Window in the sky (Gear moved high into the sky, Y is used to determine X) [unfortunately, not a safe thing to do. shame, I thought aerial bombardment would be kinda neat This one would be really easy to freeze game unless it was flagged unfortunately. - else + else begin Gear^.X:= int2hwFloat(PlayWidth)*int2hwFloat(min(max(0,hwRound(Gear^.Y)),PlayHeight))/PlayHeight; Gear^.Y:= -_2048-_256-_256; @@ -1281,4 +1539,21 @@ end; end; +procedure AddBounceEffectForGear(Gear: PGear); +var boing: PVisualGear; +begin + boing:= AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtStraightShot, 0, false, 1); + if boing <> nil then + with boing^ do + begin + Angle:= random(360); + dx:= 0; + dy:= 0; + FrameTicks:= 200; + Scale:= hwFloat2Float(Gear^.Density * hwAbs(Gear^.dY) + hwAbs(Gear^.dX)) / 1.5; + State:= ord(sprBoing) + end; + PlaySound(sndMelonImpact, true) +end; + end.