Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion MANUAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions ai_c/ai_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
41 changes: 41 additions & 0 deletions client/ugame.pas
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions server/uai_types.pas
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions server/uatomic_server.pas
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
11 changes: 8 additions & 3 deletions units/uatomic_common.pas
Original file line number Diff line number Diff line change
Expand Up @@ -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%}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;

Expand Down
97 changes: 77 additions & 20 deletions units/uatomic_field.pas
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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 ! *)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand All @@ -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
Expand Down