diff --git a/MANUAL.md b/MANUAL.md index ecf078b..46aacf4 100644 --- a/MANUAL.md +++ b/MANUAL.md @@ -47,9 +47,11 @@ The key binding can be changed in the options dialog ### First action - place bomb +- Drop carried bomb at current position, resuming its timer (when carrying a bomb with the blue glove powerup) ### First action (double pressed) -- Grab and throw bomb (if powerup was collected) +- Carry bomb: pick up a bomb at the same tile and hold it (timer frozen) — blue glove powerup required. While moving the bomb follows the player. +- Throw carried bomb in the current walking direction (when already carrying a bomb) - Spooge all available bombs (if powerup was collected) ### Second action diff --git a/ai_c/ai_types.h b/ai_c/ai_types.h index c7cc292..39c0485 100644 --- a/ai_c/ai_types.h +++ b/ai_c/ai_types.h @@ -130,6 +130,7 @@ typedef struct { bool Jelly; // If true, bomb will bounce back on walls. bool DudBomb; // If true, the dud bomb animation is showing (means the bomb will not explode for an unknown amount of time). int LifeTime; // Time in ms since when the bomb is laid down! Attention! if this bomb is dud or ManualTriggered, this time can be "resetted" to 0 when Dud is finished, or Manual Trigger is timed out. + bool Held; // If true, the bomb is currently being carried by a player (timer is frozen until dropped or thrown). } TAiBombInfo_t; typedef struct { diff --git a/client/ugame.pas b/client/ugame.pas index 71a086e..a806aee 100644 --- a/client/ugame.pas +++ b/client/ugame.pas @@ -133,6 +133,7 @@ Procedure RenderPlayerbyInfo(Const Info: TAtomicInfo; Edge: Boolean); Procedure RenderFieldHeader(); Procedure RenderBombs(); + Procedure RenderHeldBombs(); Procedure Connection_Connect(aSocket: TLSocket); Procedure Connection_Disconnect(aSocket: TLSocket); @@ -1482,6 +1483,7 @@ stream.Read(fBombs[i].Position, SizeOf(fBombs[i].Position)); stream.Read(fBombs[i].Animation, SizeOf(fBombs[i].Animation)); stream.Read(fBombs[i].AnimationOffset, SizeOf(fBombs[i].AnimationOffset)); + stream.Read(fBombs[i].IsHeld, SizeOf(fBombs[i].IsHeld)); End; End; @@ -1859,6 +1861,7 @@ glEnable(GL_ALPHA_TEST); glTranslatef(0, 0, atomic_Bomb_Layer); For i := 0 To fBombCount - 1 Do Begin + If fBombs[i].IsHeld Then Continue; // Getragene Bomben werden über dem Spieler gerendert glPushMatrix; glTranslatef(Fieldxoff + fBombs[i].Position.x * FieldBlockWidth, FieldyOff + fBombs[i].Position.y * FieldBlockHeight, 0); ani.ani := Nil; @@ -1880,6 +1883,42 @@ glPopMatrix; End; +Procedure TGame.RenderHeldBombs; +Const + // Offset in Pixel, um die Bombe über dem Kopf des Spielers zu rendern + HeldBombYOffset = FieldBlockHeight * 2; +Var + i: Integer; + ani: TAnimation; +Begin + glPushMatrix; + glColor4f(1, 1, 1, 1); + glAlphaFunc(GL_LESS, 0.5); + glEnable(GL_ALPHA_TEST); + glTranslatef(0, 0, atomic_Layer + atomic_EPSILON); + For i := 0 To fBombCount - 1 Do Begin + If Not fBombs[i].IsHeld Then Continue; + glPushMatrix; + glTranslatef(FieldxOff + fBombs[i].Position.x * FieldBlockWidth, FieldyOff + fBombs[i].Position.y * FieldBlockHeight - HeldBombYOffset, 0); + ani.ani := Nil; + Case fBombs[i].Animation Of + baNormal: ani := fAtomics[fBombs[i].ColorIndex].Bomb; + baTimeTriggered: ani := fAtomics[fBombs[i].ColorIndex].Bomb_trigger; + baDud: ani := fAtomics[fBombs[i].ColorIndex].Bomb_dud; + baWobble: ani := fAtomics[fBombs[i].ColorIndex].Bomb_Wobble; + End; + If Not assigned(ani.ani) Then Begin + LogShow('Error: TGame.RenderHeldBombs: no Animation found.', llFatal); + End; + ani.ani.AnimationOffset := fBombs[i].AnimationOffset; + glTranslatef(ani.OffsetX, ani.OffsetY, 0); + ani.ani.Render(0); + glPopMatrix; + End; + gldisable(GL_ALPHA_TEST); + glPopMatrix; +End; + Procedure TGame.PingForOpenGames; Var N: TNetworkAdapterList; @@ -2415,6 +2454,8 @@ fPlayer[i].edge := false; End; End; + // Getragene Bomben über dem Spieler rendern + RenderHeldBombs; If fPause Then Begin glPushMatrix(); glTranslatef(0, 0, atomic_dialog_Layer + atomic_EPSILON); diff --git a/server/uai_types.pas b/server/uai_types.pas index f012d7a..60cf70b 100644 --- a/server/uai_types.pas +++ b/server/uai_types.pas @@ -146,6 +146,7 @@ Jelly: cBool; // If true, bomb will bounce back on walls. DudBomb: cBool; // If true, the dud bomb animation is showing (means the bomb will not explode for an unknown amount of time). LifeTime: cint; // Time in ms since when the bomb is layed down ! Attention ! if this bomb is dud or ManualTriggered, this time can be "resetted" to 0 when Dud is finished, or Manual Trigger is timed out + Held: cBool; // If true, the bomb is currently being carried by a player (timer is frozen until dropped or thrown). End; TAiInfo = Record diff --git a/server/uatomic_server.pas b/server/uatomic_server.pas index 3c0c924..a020b2d 100644 --- a/server/uatomic_server.pas +++ b/server/uatomic_server.pas @@ -1058,6 +1058,7 @@ fPLayer[i].Disease := []; fPLayer[i].DiseaseCounter := 0; fPLayer[i].IdleTimer := 0; + fPLayer[i].HeldBombIndex := -1; fPLayer[i].Info.Dying := false; // Wir Sind alle wieder am Leben ;) // fPLayer[i].Team := fSettings.Scheme.PlayerStartPositions[i].Team; -- Wurde schon gemacht in HandleLoadSettings fPLayer[i].Powers := PowersFromScheme(fSettings.Scheme); diff --git a/units/uatomic_common.pas b/units/uatomic_common.pas index 8b7fd7d..f80877c 100644 --- a/units/uatomic_common.pas +++ b/units/uatomic_common.pas @@ -124,10 +124,11 @@ * ADD: implement missing Brick spawning for haunted house field. * 0.13001 = ADD: Disable hohles in Hurry mode, see https://bomberman.fandom.com/wiki/The_Coal_Mine * ADD: Disable trampolins in Hurry mode, see https://bomberman.fandom.com/wiki/Deep_Forest_Green + * 0.13002 = ADD: Carry Bomb feature - player can pick up and hold a bomb (timer frozen), then drop or throw it *) - ProtocollVersion: uint32 = 13; // ACHTUNG die Versionsnummer mus hier und in der Zeile darunter angepasst werden - Version = '0.13001'; + ProtocollVersion: uint32 = 14; // ACHTUNG die Versionsnummer mus hier und in der Zeile darunter angepasst werden + Version = '0.13002'; defCaption = 'FPC Atomic ver. ' + Version // ACHTUNG die Versionsnummer mus hier und in der Zeile darüber angepasst werden {$IFDEF DebuggMode} + ' build: ' + {$I %DATE%} + ' ' + {$I %TIME%} @@ -307,13 +308,16 @@ baWobble // Prallt eine Bombe ab, ist sie ab dann im Wobble Mode -> nur bei Jelly Bombem ); - TBombMoveDir = (bmNone, bmUp, bmDown, bmLeft, bmRight, bmFly); + TBombMoveDir = (bmNone, bmUp, bmDown, bmLeft, bmRight, bmFly, bmHeld); TBombInfo = Record ColorIndex: integer; // 0..9 Farbe in der der Client die Bombe Rendern soll Position: TVector2; // in Field Coordinaten, können aber auch "Verrückt" sein, wenn die Bombe wild rum fliegt.. Animation: TBombAnimation; // Die Jeweilige Animation AnimationOffset: uint16; // Damit nicht alle "Gleich" aussehen +{$IFDEF Client} + IsHeld: Boolean; // True, wenn die Bombe gerade von einem Spieler getragen wird +{$ENDIF} {$IFDEF Server} FlyStart, FlyTarget: TVector2; FlyTime: integer; // Zeit In ms Seit derer die Bombe Fliegt @@ -547,6 +551,7 @@ Action: TAtomicAction; Powers: TAtomicPowers; PowerUpCounter: Array[TPowerUps] Of Integer; // Zähler, welche Powerups der Spieler wie oft aufgenommen hat, wenn er stirbt werden diese wieder "Verteilt" + HeldBombIndex: Integer; // Index in fBombs[] des gerade getragenen Bombs, -1 = kein Bomb {$ENDIF} End; diff --git a/units/uatomic_field.pas b/units/uatomic_field.pas index 5f336c6..4ee4af1 100644 --- a/units/uatomic_field.pas +++ b/units/uatomic_field.pas @@ -602,7 +602,8 @@ For i := 0 To fBombCount - 1 Do Begin If (x = trunc(fBombs[i].Position.x)) And (y = trunc(fBombs[i].Position.y)) And - (fBombs[i].MoveDir <> bmFly) Then Begin + (fBombs[i].MoveDir <> bmFly) And + (fBombs[i].MoveDir <> bmHeld) Then Begin result := false; exit; End; @@ -785,6 +786,8 @@ stream.Write(fBombs[i].Position, SizeOf(fBombs[i].Position)); stream.Write(fBombs[i].Animation, SizeOf(fBombs[i].Animation)); stream.Write(fBombs[i].AnimationOffset, SizeOf(fBombs[i].AnimationOffset)); + b := fBombs[i].MoveDir = bmHeld; + stream.Write(b, SizeOf(b)); End; {$ENDIF} End; @@ -799,7 +802,8 @@ For i := 0 To fBombCount - 1 Do Begin If (trunc(fBombs[i].Position.x) = x) And (trunc(fBombs[i].Position.y) = y) And - (fBombs[i].MoveDir <> bmFly) Then Begin // Fliegende Bomben haben keine Kollisionen ! + (fBombs[i].MoveDir <> bmFly) And + (fBombs[i].MoveDir <> bmHeld) Then Begin // Fliegende und getragene Bomben haben keine Kollisionen ! result := i; exit; End; @@ -1176,6 +1180,39 @@ handled: Boolean; Begin If Player.Flying Then exit; // Im Flug darf der Spieler natürlich nichts machen ;) + // Wenn der Spieler eine Bombe trägt, hat er andere Aktionen zur Verfügung + If Player.HeldBombIndex >= 0 Then Begin + If Player.Action = aaFirst Then Begin + // Bombe fallen lassen (Timer läuft weiter) + fBombs[Player.HeldBombIndex].MoveDir := bmNone; + fBombs[Player.HeldBombIndex].Position.x := trunc(Player.Info.Position.x) + 0.5; + fBombs[Player.HeldBombIndex].Position.y := trunc(Player.Info.Position.y) + 0.5; + fPlaySoundEffect(PlayerIndex, seBombDrop); + Player.HeldBombIndex := -1; + End + Else If Player.Action = aaFirstDouble Then Begin + // Bombe werfen + bx := trunc(fBombs[Player.HeldBombIndex].Position.x); + by := trunc(fBombs[Player.HeldBombIndex].Position.y); + dx := 0; + dy := 0; + Case (trunc(Player.Info.Direction) Div 90) Of + 0: dx := 1; + 1: dy := -1; + 2: dx := -1; + 3: dy := 1; + End; + fBombs[Player.HeldBombIndex].FlyStart := v2(bx + 0.5, by + 0.5); + fBombs[Player.HeldBombIndex].FlyTarget := v2(bx + 0.5 + 3 * dx, by + 0.5 + 3 * dy); + fBombs[Player.HeldBombIndex].MoveDir := bmFly; + fBombs[Player.HeldBombIndex].FlyTime := 0; + fBombs[Player.HeldBombIndex].FlyFinTime := AtomicBombBigFlyTime; + fPlaySoundEffect(PlayerIndex, seBombGrab); + fPlayerStatistics.UpdatePlayerID(PlayerIndex, pssThrownBombs); + Player.HeldBombIndex := -1; + End; + exit; + End; Case player.Action Of aaFirstDouble: Begin (* Jeder Doppelt action geht eine Einfach Aktion vorraus ! *) @@ -1210,29 +1247,19 @@ End; End; If Player.Powers.CanGrabBombs Then Begin - // Gibt es eine Bombe zum Graben ? + // Gibt es eine Bombe zum Greifen? x := trunc(player.Info.Position.x); y := trunc(player.Info.Position.y); - For i := 0 To high(fBombs) Do Begin + For i := 0 To fBombCount - 1 Do Begin If fBombs[i].MoveDir = bmFly Then Continue; // Fliegende Bomben dürfen nicht gegriffen werden. + If fBombs[i].MoveDir = bmHeld Then Continue; // Bereits getragene Bomben können nicht noch einmal gegriffen werden. bx := trunc(fBombs[i].Position.x); by := trunc(fBombs[i].Position.y); If (x = bx) And (y = by) Then Begin - dx := 0; - dy := 0; - Case (trunc(player.info.Direction) Div 90) Of - 0: dx := 1; - 1: dy := -1; - 2: dx := -1; - 3: dy := 1; - End; - fBombs[i].FlyStart := v2(bx + 0.5, by + 0.5); - fBombs[i].FlyTarget := v2(bx + 0.5 + 3 * dx, by + 0.5 + 3 * dy); - fBombs[i].MoveDir := bmFly; - fBombs[i].FlyTime := 0; - fBombs[i].FlyFinTime := AtomicBombBigFlyTime; + // Bombe aufheben: Timer einfrieren, Spieler trägt die Bombe + fBombs[i].MoveDir := bmHeld; + Player.HeldBombIndex := i; fPlaySoundEffect(PlayerIndex, seBombGrab); - fPlayerStatistics.UpdatePlayerID(PlayerIndex, pssThrownBombs); Player.Info.Animation := raPup; Player.Info.Counter := 0; break; @@ -1263,6 +1290,7 @@ End; For i := 0 To high(fBombs) Do Begin If fBombs[i].MoveDir = bmFly Then Continue; // Fliegende Bomben dürfen nicht gepuncht werden. + If fBombs[i].MoveDir = bmHeld Then Continue; // Getragene Bomben dürfen nicht gepuncht werden. bx := trunc(fBombs[i].Position.x); by := trunc(fBombs[i].Position.y); If (x + dx = bx) And (y + dy = by) Then Begin @@ -1286,7 +1314,7 @@ * Zünden der eigenen Time Triggered Bomben, das geht irgendwie immer ... *) For i := 0 To fBombCount - 1 Do Begin - If (fBombs[i].PlayerIndex = PlayerIndex) And (fBombs[i].Animation = baTimeTriggered) And (fBombs[i].MoveDir <> bmFly) Then Begin + If (fBombs[i].PlayerIndex = PlayerIndex) And (fBombs[i].Animation = baTimeTriggered) And (fBombs[i].MoveDir <> bmFly) And (fBombs[i].MoveDir <> bmHeld) Then Begin fBombs[i].Animation := baNormal; fBombs[i].Lifetime := AtomicBombDetonateTime; fPlayerStatistics.UpdatePlayerID(PlayerIndex, pssTriggeredBombs); @@ -1369,6 +1397,7 @@ BombExplodeSound: Array[0..Length(PlayerColors) - 1] Of Boolean; dx, dy, s: Single; mdir: TBombMoveDir; + k: Integer; Begin If Not fBombsEnabled Then exit; // Bomben dürfen nicht mehr gezündet werden ! fBombDetonateFifo.Clear; @@ -1498,6 +1527,19 @@ End; bmNone: Begin // Nix zu tun End; + bmHeld: Begin + fBombs[i].Lifetime := fBombs[i].Lifetime - FrameRate; // Countdown bis zur Detonation wieder Rückgängig machen (Bombe wird getragen, Timer einfrieren) + // Position der Bombe an Spielerposition anpassen (nur wenn Spieler nicht fliegt) + For k := 0 To high(Players) Do Begin + If Players[k].HeldBombIndex = i Then Begin + If Not Players[k].Flying Then Begin + fBombs[i].Position.x := trunc(Players[k].Info.Position.x) + 0.5; + fBombs[i].Position.y := trunc(Players[k].Info.Position.y) + 0.5; + End; + break; + End; + End; + End; bmRight: Begin fBombs[i].Position.x := fBombs[i].Position.x + rSpeed; commapartx := (fBombs[i].Position.x - trunc(fBombs[i].Position.x)); @@ -1623,7 +1665,7 @@ // (dass kann nur bei sich bewegenden Bomben passieren, da die Liegenden in Detonate schon berücksichtigt werden.) x := trunc(fBombs[i].Position.x); y := trunc(fBombs[i].Position.y); - If (y > 0) And (fBombs[i].MoveDir <> bmFly) Then Begin // Eine Fliegende Bombe kann Negative Koordinaten kriegen + If (y > 0) And (fBombs[i].MoveDir <> bmFly) And (fBombs[i].MoveDir <> bmHeld) Then Begin // Eine Fliegende oder getragene Bombe kann ungültige Koordinaten haben If (fField[x, y].Flame <> []) Then Begin fBombs[i].Position.x := x + 0.5; fBombs[i].Position.y := y + 0.5; @@ -1716,6 +1758,15 @@ For i := fBombCount - 1 Downto 0 Do Begin If fBombs[i].Detonated Then Begin BombExplodeSound[fBombs[i].PlayerIndex] := true; + // HeldBombIndex aller Spieler aktualisieren, da sich die Array-Indices verschieben + For k := 0 To high(Players) Do Begin + If Players[k].HeldBombIndex = i Then Begin + Players[k].HeldBombIndex := -1; // Getragene Bombe explodiert + End + Else If Players[k].HeldBombIndex > i Then Begin + Players[k].HeldBombIndex := Players[k].HeldBombIndex - 1; // Index anpassen + End; + End; For j := i To fBombCount - 2 Do Begin fBombs[j] := fBombs[j + 1]; End; @@ -2046,6 +2097,7 @@ result.Bombs[i].Jelly := fBombs[i].Jelly; result.Bombs[i].DudBomb := fBombs[i].Animation = baDud; result.Bombs[i].Lifetime := fBombs[i].Lifetime; + result.Bombs[i].Held := fBombs[i].MoveDir = bmHeld; End; End; @@ -2070,6 +2122,11 @@ Procedure TAtomicField.KillPlayer(Var Players: TPlayers; Index: integer); Begin + // Wenn der Spieler gerade eine Bombe trägt, diese fallen lassen (Timer läuft weiter) + If Players[Index].HeldBombIndex >= 0 Then Begin + fBombs[Players[Index].HeldBombIndex].MoveDir := bmNone; + Players[Index].HeldBombIndex := -1; + End; Players[Index].MoveState := msStill; // Zum Sterben halten wir an ;) Players[Index].Info.Animation := raDie; Players[Index].Info.Value := Random(65536); // Eine Zufällige Todesanimation wählen