diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | CHANGELOG.md | 19 | ||||
-rw-r--r-- | CHANGELOG.txt | 34 | ||||
-rw-r--r-- | LICENSE.txt (renamed from LICENSE) | 40 | ||||
-rw-r--r-- | assets/fonts/Vollkorn/OFL.txt | 93 | ||||
-rw-r--r-- | assets/fonts/Vollkorn/static/Vollkorn-Regular.ttf | bin | 0 -> 345232 bytes | |||
-rw-r--r-- | assets/map-new.png | bin | 0 -> 1467 bytes | |||
-rw-r--r-- | assets/sounds/CREDITS.txt | 9 | ||||
-rw-r--r-- | assets/sounds/popup-show.wav | bin | 0 -> 33122 bytes | |||
-rwxr-xr-x | build-linux.sh | 4 | ||||
-rw-r--r-- | eepers.adb | 391 | ||||
-rw-r--r-- | raylib.ads | 43 |
12 files changed, 492 insertions, 143 deletions
@@ -1,6 +1,6 @@ *.ali *.o -eepers +eepers-linux eepers.exe test test.exe diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index d5cff39..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,19 +0,0 @@ -# Eepers v1.3 - -- @Eropi4 - MacOS build - https://github.com/tsoding/eepers/pull/13 -- @cornishon - Sprint with Right Shift - https://github.com/tsoding/eepers/pull/18 -- Port the Source Code to Ada 2012 making it accessible to more setups. We were not using anything important from Ada 2022 anyway. -- ... - -# Eepers v1.2 - -- @LainLayer - Vary the turn duration based on how rapidly the player presses the keys - https://github.com/tsoding/eepers/pull/15 - -# Eepers v1.1 - -- @rexim - Fixed skipping keys when they are pressed in a rapid succession - https://github.com/tsoding/eepers/issues/4 -- @rexim - Fixed crash if you accidentally place several items into a single cell - https://github.com/tsoding/eepers/issues/8 - -# First Release of Eepers v1.0 - -This is a result of 20 days of Game Programming in Ada "Challenge". I don't know Ada, I'm not a Game Developer. So lower your expectations. But I hope this game will still provide some fun for you. Because it did for me. :) diff --git a/CHANGELOG.txt b/CHANGELOG.txt new file mode 100644 index 0000000..39acd8c --- /dev/null +++ b/CHANGELOG.txt @@ -0,0 +1,34 @@ +# Eepers v1.4
+
+- @rexim - Added items that extend the amount of bombs you can carry
+- @rexim - Do not restart on Any Key Press. Restart automatically after 2 seconds.
+- @ProgKea - Fix the Boss killing the Player on the same turn as it dies - https://github.com/tsoding/eepers/pull/24
+- @slukovic - Camera adjusts its zoom on bigger screen - https://github.com/tsoding/eepers/pull/27
+- ...
+
+# Eepers v1.3
+
+- @rexim - Implemented a Basic Tutorial that explains how to play the Game.
+- @cornishon - Sprint with Right Shift - https://github.com/tsoding/eepers/pull/18
+- @Eropi4 - MacOS build - https://github.com/tsoding/eepers/pull/13
+
+> Since I don't have a MacOS setup I'm not able to provide the MacOS binaries.
+> So if you want to play on MacOS you have to build the Game from the Source Code
+> yourself, sorry.
+> - @rexim
+
+- @rexim - Increased Path Finding Limit. Now Bosses give up on chasing the Player if the distance is over 10 turns instead of just 4. - https://github.com/tsoding/eepers/issues/16
+- @rexim - Custom font for "You Died!" sign (Vollkorn)
+
+# Eepers v1.2
+
+- @LainLayer - Vary the turn duration based on how rapidly the player presses the keys - https://github.com/tsoding/eepers/pull/15
+
+# Eepers v1.1
+
+- @rexim - Fixed skipping keys when they are pressed in a rapid succession - https://github.com/tsoding/eepers/issues/4
+- @rexim - Fixed crash if you accidentally place several items into a single cell - https://github.com/tsoding/eepers/issues/8
+
+# First Release of Eepers v1.0
+
+This is a result of 20 days of Game Programming in Ada "Challenge". I don't know Ada, I'm not a Game Developer. So lower your expectations. But I hope this game will still provide some fun for you. Because it did for me. :)
@@ -1,20 +1,20 @@ -Copyright 2024 Alexey Kutepov <reximkut@gmail.com> - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +Copyright 2024 Alexey Kutepov <reximkut@gmail.com> and Eeper Contributors
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/assets/fonts/Vollkorn/OFL.txt b/assets/fonts/Vollkorn/OFL.txt new file mode 100644 index 0000000..f72bad1 --- /dev/null +++ b/assets/fonts/Vollkorn/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2017 The Vollkorn Project Authors (https://github.com/FAlthausen/Vollkorn-Typeface)
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+https://openfontlicense.org
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/assets/fonts/Vollkorn/static/Vollkorn-Regular.ttf b/assets/fonts/Vollkorn/static/Vollkorn-Regular.ttf Binary files differnew file mode 100644 index 0000000..88b0657 --- /dev/null +++ b/assets/fonts/Vollkorn/static/Vollkorn-Regular.ttf diff --git a/assets/map-new.png b/assets/map-new.png Binary files differnew file mode 100644 index 0000000..3d01c7c --- /dev/null +++ b/assets/map-new.png diff --git a/assets/sounds/CREDITS.txt b/assets/sounds/CREDITS.txt new file mode 100644 index 0000000..527030c --- /dev/null +++ b/assets/sounds/CREDITS.txt @@ -0,0 +1,9 @@ +Used Sounds:
+ https://opengameart.org/content/magic-sfx-sample
+ https://opengameart.org/content/beep-tone-sound-sfx
+ https://opengameart.org/content/ambient-soundtrack
+ https://opengameart.org/content/pickupplastic-sound
+ https://opengameart.org/content/picked-coin-echo
+ https://opengameart.org/content/level-up-power-up-coin-get-13-sounds
+ https://opengameart.org/content/fire-whip-hit-yo-frankie
+ https://opengameart.org/content/ui-soundpack-by-m1chiboi-bleeps-and-clicks
diff --git a/assets/sounds/popup-show.wav b/assets/sounds/popup-show.wav Binary files differnew file mode 100644 index 0000000..22564e5 --- /dev/null +++ b/assets/sounds/popup-show.wav diff --git a/build-linux.sh b/build-linux.sh index 8d17811..12f57f5 100755 --- a/build-linux.sh +++ b/build-linux.sh @@ -2,8 +2,8 @@ set -xe -gnatmake -f -O3 -Wall -Wextra -gnat2012 eepers.adb -bargs -static -largs -L./raylib/raylib-5.0_linux_amd64/lib/ -l:libraylib.a -lm -pthread -./eepers +gnatmake -f -O3 -Wall -Wextra -gnat2012 -o eepers-linux eepers.adb -bargs -static -largs -L./raylib/raylib-5.0_linux_amd64/lib/ -l:libraylib.a -lm -pthread +./eepers-linux # gnatmake -f -Wall -Wextra -gnat2022 test.adb -largs -L./raylib/raylib-5.0_linux_amd64/lib/ -l:libraylib.a -lm # ./test @@ -33,7 +33,13 @@ procedure Eepers is Checkpoint_Sound: Sound; Plant_Bomb_Sound: Sound; Guard_Step_Sound: Sound; + Popup_Show_Sound: Sound; Ambient_Music: Music; + Tutorial_Font: Font; + Death_Font: Font; + + Tutorial_Font_Size: constant Int := 42; + Death_Font_Size: constant Int := 68; DEVELOPMENT : constant Boolean := False; @@ -66,6 +72,26 @@ procedure Eepers is return Color_From_HSV(H, S, V); end; + procedure Inc(X: in out Integer; Offset: Integer := 1) is + begin + X := X + Offset; + end; + + procedure Dec(X: in out Integer; Offset: Integer := 1) is + begin + X := X - Offset; + end; + + procedure Inc(X: in out Byte; Offset: Byte := 1) is + begin + X := X + Offset; + end; + + procedure Dec(X: in out Byte; Offset: Byte := 1) is + begin + X := X - Offset; + end; + Palette_RGB: array (Palette) of Color := (others => (A => 255, others => 0)); Palette_HSV: array (Palette) of HSV := (others => (others => 0)); @@ -91,7 +117,7 @@ procedure Eepers is begin Open(F, In_File, File_Name); while not End_Of_File(F) loop - Line_Number := Line_Number + 1; + Inc(Line_Number); declare Line: Unbounded_String := To_Unbounded_String(Get_Line(F)); @@ -142,16 +168,21 @@ procedure Eepers is Put_Line("WARNING: could not load colors from file " & File_Name & ": " & Exception_Message(E)); end; - BASE_TURN_DURATION_SECS : constant Float := 0.125; - TURN_DURATION_SECS : Float := BASE_TURN_DURATION_SECS; - GUARD_ATTACK_COOLDOWN : constant Integer := 10; - EEPER_EXPLOSION_DAMAGE : constant Float := 0.45; - GUARD_TURN_REGENERATION : constant Float := 0.01; - BOMB_GENERATOR_COOLDOWN : constant Integer := 10; - GUARD_STEPS_LIMIT : constant Integer := 4; - GUARD_STEP_LENGTH_LIMIT : constant Integer := 100; - EXPLOSION_LENGTH : constant Integer := 10; - EYES_ANGULAR_VELOCITY : constant Float := 10.0; + BASE_TURN_DURATION_SECS : constant Float := 0.125; + TURN_DURATION_SECS : Float := BASE_TURN_DURATION_SECS; + GUARD_ATTACK_COOLDOWN : constant Integer := 10; + EEPER_EXPLOSION_DAMAGE : constant Float := 0.45; + GUARD_TURN_REGENERATION : constant Float := 0.01; + BOMB_GENERATOR_COOLDOWN : constant Integer := 10; + GUARD_STEPS_LIMIT : constant Integer := 10; + GUARD_STEP_LENGTH_LIMIT : constant Integer := 100; + EXPLOSION_LENGTH : constant Integer := 10; + EYES_ANGULAR_VELOCITY : constant Float := 10.0; + TUTORIAL_MOVE_WAIT_TIME_SECS : constant C_Float := 5.0; + TUTORIAL_BOMB_WAIT_TIME_SECS : constant C_Float := 4.0; + TUTORIAL_SPRINT_WAIT_TIME_SECS : constant C_Float := 15.0; + POPUP_ANIMATION_DURATION : constant C_Float := 0.1; + RESTART_TIMEOUT_SECS : constant Double := 2.0; type IVector2 is record X, Y: Integer; @@ -209,7 +240,12 @@ procedure Eepers is return (A.X*S, A.Y*S); end; - type Item_Kind is (Item_None, Item_Key, Item_Bomb_Gen, Item_Checkpoint); + type Item_Kind is ( + Item_None, + Item_Key, + Item_Bomb_Refill, + Item_Checkpoint, + Item_Bomb_Slot); type Item is record Kind: Item_Kind := Item_None; Position: IVector2; @@ -267,6 +303,7 @@ procedure Eepers is Bombs: Integer := 0; Bomb_Slots: Integer := 1; Dead: Boolean := False; + Death_Time: Double; end record; type Eeper_Kind is (Eeper_Guard, Eeper_Mother, Eeper_Gnome, Eeper_Father); @@ -295,7 +332,7 @@ procedure Eepers is type Bomb_State_Array is array (1..10) of Bomb_State; - type Eeper_Index is range 1..15; + type Eeper_Index is range 1..30; type Eeper_Array is array (Eeper_Index) of Eeper_State; type Checkpoint_State is record @@ -309,6 +346,75 @@ procedure Eepers is Bombs: Bomb_State_Array; end record; + type Direction is (Left, Right, Up, Down); + + Direction_Vector: constant array (Direction) of IVector2 := ( + Left => (X => -1, Y => 0), + Right => (X => 1, Y => 0), + Up => (X => 0, Y => -1), + Down => (X => 0, Y => 1)); + + type Popup_State is record + Label: Char_Array(1..50); + Visible: Boolean := False; + Animation: C_Float := 0.0; + end record; + + procedure Show_Popup(Popup: in out Popup_State; Text: String) is + Ignore: Size_t; + begin + if not Popup.Visible then + Play_Sound(Popup_Show_Sound); + end if; + Popup.Visible := True; + To_C(Text, Popup.Label, Ignore); + end; + + procedure Hide_Popup(Popup: in out Popup_State) is + begin + Popup.Visible := False; + end; + + procedure Draw_Popup(Popup: in out Popup_State; Start, Size: Vector2) is + begin + if Popup.Visible then + if Popup.Animation < 1.0 then + Popup.Animation := (Popup.Animation*POPUP_ANIMATION_DURATION + Get_Frame_Time)/POPUP_ANIMATION_DURATION; + end if; + else + if Popup.Animation > 0.0 then + Popup.Animation := (Popup.Animation*POPUP_ANIMATION_DURATION - Get_Frame_Time)/POPUP_ANIMATION_DURATION; + end if; + end if; + + if Popup.Animation > 0.0 then + declare + Font_Size: constant C_Float := C_Float(Tutorial_Font_Size)*Popup.Animation; + Popup_Bottom_Margin: constant C_Float := Font_Size*0.05; + Label_Size: constant Vector2 := Measure_Text_Ex(Tutorial_Font, Popup.Label, Font_Size, 0.0); + Label_Position: constant Vector2 := Start + Size*(0.5, 0.0) - Label_Size*(0.5, 1.0) - (0.0, Popup_Bottom_Margin); + begin + Draw_Text_Ex(Tutorial_Font, Popup.Label, Label_Position + (-2.0, 2.0), Font_Size, 0.0, Palette_RGB(COLOR_WALL)); + Draw_Text_Ex(Tutorial_Font, Popup.Label, Label_Position, Font_Size, 0.0, Palette_RGB(COLOR_PLAYER)); + end; + end if; + end; + + type Tutorial_Phase is (Tutorial_Move, Tutorial_Place_Bombs, Tutorial_Waiting_For_Sprint, Tutorial_Sprint, Tutorial_Done); + type Tutorial_State is record + Phase: Tutorial_Phase := Tutorial_Move; + Waiting: C_Float := 0.0; + + Prev_Step_Timestamp: Double := 0.0; + Hurry_Count: Integer := 0; + + Popup: Popup_State; + + Knows_How_To_Move: Boolean := False; + Knows_How_To_Place_Bombs: Boolean := False; + Knows_How_To_Sprint: Boolean := False; + end record; + type Game_State is record Map: Map_Access := Null; Player: Player_State; @@ -318,8 +424,13 @@ procedure Eepers is Items: Item_Array; Bombs: Bomb_State_Array; - Camera_Position: Vector2 := (x => 0.0, y => 0.0); + Camera: Camera2D := ( + offset => (x => 0.0, y => 0.0), + target => (x => 0.0, y => 0.0), + rotation => 0.0, + zoom => 1.0); + Tutorial: Tutorial_State; Checkpoint: Checkpoint_State; Duration_Of_Last_Turn: Double; @@ -338,14 +449,6 @@ procedure Eepers is return M1; end; - type Direction is (Left, Right, Up, Down); - - Direction_Vector: constant array (Direction) of IVector2 := ( - Left => (X => -1, Y => 0), - Right => (X => 1, Y => 0), - Up => (X => 0, Y => -1), - Down => (X => 0, Y => 1)); - function Inside_Of_Rect(Start, Size, Point: in IVector2) return Boolean is begin return Start <= Point and then Point < Start + Size; @@ -572,25 +675,27 @@ procedure Eepers is Level_Wall, Level_Door, Level_Checkpoint, - Level_Bomb_Gen, + Level_Bomb_Refill, Level_Barricade, Level_Key, Level_Player, - Level_Father); + Level_Father, + Level_Bomb_Slot); Level_Cell_Color: constant array (Level_Cell) of Color := ( - Level_None => Get_Color(16#00000000#), - Level_Gnome => Get_Color(16#FF9600FF#), - Level_Mother => Get_Color(16#96FF00FF#), - Level_Guard => Get_Color(16#00FF00FF#), - Level_Floor => Get_Color(16#FFFFFFFF#), - Level_Wall => Get_Color(16#000000FF#), - Level_Door => Get_Color(16#00FFFFFF#), - Level_Checkpoint => Get_Color(16#FF00FFFF#), - Level_Bomb_Gen => Get_Color(16#FF0000FF#), - Level_Barricade => Get_Color(16#FF0096FF#), - Level_Key => Get_Color(16#FFFF00FF#), - Level_Player => Get_Color(16#0000FFFF#), - Level_Father => Get_Color(16#265FDAFF#)); + Level_None => Get_Color(16#00000000#), + Level_Gnome => Get_Color(16#FF9600FF#), + Level_Mother => Get_Color(16#96FF00FF#), + Level_Guard => Get_Color(16#00FF00FF#), + Level_Floor => Get_Color(16#FFFFFFFF#), + Level_Wall => Get_Color(16#000000FF#), + Level_Door => Get_Color(16#00FFFFFF#), + Level_Checkpoint => Get_Color(16#FF00FFFF#), + Level_Bomb_Refill => Get_Color(16#FF0000FF#), + Level_Barricade => Get_Color(16#FF0096FF#), + Level_Key => Get_Color(16#FFFF00FF#), + Level_Player => Get_Color(16#0000FFFF#), + Level_Father => Get_Color(16#265FDAFF#), + Level_Bomb_Slot => Get_Color(16#BC5353FF#)); function Cell_By_Color(Col: Color; Out_Cel: out Level_Cell) return Boolean is begin @@ -668,7 +773,8 @@ procedure Eepers is Game.Map(Row, Column) := Cell_Floor; when Level_Father => if Update_Camera then - Game.Camera_Position := Screen_Size*0.5 - (To_Vector2((Column, Row))*Cell_Size + To_Vector2((7, 7))*Cell_Size*0.5); + Game.Camera.target := (To_Vector2((Column, Row)) + To_Vector2((7, 7))*0.5) * Cell_Size; + Game.Camera.offset := Screen_Size*0.5 - Cell_Size*0.5; end if; Spawn_Father(Game, (Column, Row)); Game.Map(Row, Column) := Cell_Floor; @@ -678,9 +784,12 @@ procedure Eepers is when Level_Checkpoint => Game.Map(Row, Column) := Cell_Floor; Allocate_Item(Game, (Column, Row), Item_Checkpoint); - when Level_Bomb_Gen => + when Level_Bomb_Refill => Game.Map(Row, Column) := Cell_Floor; - Allocate_Item(Game, (Column, Row), Item_Bomb_Gen); + Allocate_Item(Game, (Column, Row), Item_Bomb_Refill); + when Level_Bomb_Slot => + Game.Map(Row, Column) := Cell_Floor; + Allocate_Item(Game, (Column, Row), Item_Bomb_Slot); when Level_Barricade => Game.Map(Row, Column) := Cell_Barricade; when Level_Key => @@ -760,13 +869,15 @@ procedure Eepers is begin Draw_Rectangle_V(To_Vector2(Item.Position)*Cell_Size + Cell_Size*0.5 - Checkpoint_Item_Size*0.5, Checkpoint_Item_Size, Palette_RGB(COLOR_CHECKPOINT)); end; - when Item_Bomb_Gen => + when Item_Bomb_Refill => if Item.Cooldown > 0 then Draw_Bomb(Item.Position, Color_Brightness(Palette_RGB(COLOR_BOMB), -0.5)); Draw_Number(Item.Position, Item.Cooldown, Palette_RGB(COLOR_LABEL)); else Draw_Bomb(Item.Position, Palette_RGB(COLOR_BOMB)); end if; + when Item_Bomb_Slot => + Draw_Bomb(Item.Position, Palette_RGB(COLOR_DOORKEY)); end case; end loop; end; @@ -825,17 +936,21 @@ procedure Eepers is case Item.Kind is when Item_None => null; when Item_Key => - Game.Player.Keys := Game.Player.Keys + 1; + Inc(Game.Player.Keys); Item.Kind := Item_None; Play_Sound(Key_Pickup_Sound); - when Item_Bomb_Gen => if + when Item_Bomb_Refill => if Game.Player.Bombs < Game.Player.Bomb_Slots and then Item.Cooldown <= 0 then - Game.Player.Bombs := Game.Player.Bombs + 1; + Inc(Game.Player.Bombs); Item.Cooldown := BOMB_GENERATOR_COOLDOWN; Play_Sound(Bomb_Pickup_Sound); end if; + when Item_Bomb_Slot => + Item.Kind := Item_None; + Inc(Game.Player.Bomb_Slots); + Game.Player.Bombs := Game.Player.Bomb_Slots; when Item_Checkpoint => Item.Kind := Item_None; Game.Player.Bombs := Game.Player.Bomb_Slots; @@ -846,7 +961,7 @@ procedure Eepers is end loop; when Cell_Door => if Game.Player.Keys > 0 then - Game.Player.Keys := Game.Player.Keys - 1; + Dec(Game.Player.Keys); Flood_Fill(Game, New_Position, Cell_Floor); Game.Player.Position := New_Position; Play_Sound(Open_Door_Sound); @@ -857,6 +972,12 @@ procedure Eepers is end; end; + procedure Kill_Player(Game: in out Game_State) is + begin + Game.Player.Dead := True; + Game.Player.Death_Time := Get_Time; + end; + procedure Explode(Game: in out Game_State; Position: in IVector2) is procedure Explode_Line(Dir: Direction) is New_Position: IVector2 := Position; @@ -871,7 +992,7 @@ procedure Eepers is Game.Map(New_Position.Y, New_Position.X) := Cell_Explosion; if New_Position = Game.Player.Position then - Game.Player.Dead := True; + Kill_Player(Game); end if; for Eeper of Game.Eepers loop @@ -904,20 +1025,17 @@ procedure Eepers is ); procedure Game_Update_Camera(Game: in out Game_State) is - Camera_Target: constant Vector2 := - Screen_Size*0.5 - To_Vector2(Game.Player.Position)*Cell_Size - Cell_Size*0.5; - Camera_Velocity: constant Vector2 := (Camera_Target - Game.Camera_Position)*2.0; - begin - Game.Camera_Position := Game.Camera_Position + Camera_Velocity*Get_Frame_Time; - end; - - function Game_Camera(Game: in Game_State) return Camera2D is + Camera_Target: constant Vector2 := To_Vector2(Game.Player.Position)*Cell_Size; + Camera_Offset: constant Vector2 := Screen_Size*0.5 - Cell_Size*0.5; + Camera_Velocity: constant Vector2 := (Camera_Target - Game.Camera.target)*2.0; begin - return ( - offset => Game.Camera_Position, - target => (x => 0.0, y => 0.0), - rotation => 0.0, - zoom => 1.0); + Game.Camera.offset := Camera_Offset; + Game.Camera.target := Game.Camera.target + Camera_Velocity*Get_Frame_Time; + -- TODO: animate zoom similarly to Game.Camera.target + -- So it looks cool when you resize the game in the window mode. + -- TODO: The tutorial signs look gross on bigger screens. + -- We need to do something with the fonts + Game.Camera.zoom := C_Float'Max(Screen_Size.x/1920.0, Screen_Size.y/1080.0); end; function Interpolate_Positions(IPrev_Position, IPosition: IVector2; T: Float) return Vector2 is @@ -946,7 +1064,7 @@ procedure Eepers is begin Q.Items((Q.Start + Q.Size) mod Command_Capacity) := C; if Q.Size < Command_Capacity then - Q.Size := Q.Size + 1; + Inc(Q.Size); else Q.Start := (Q.Start + 1) mod Command_Capacity; end if; @@ -958,18 +1076,18 @@ procedure Eepers is return False; end if; C := Q.Items(Q.Start); - Q.Size := Q.Size - 1; + Dec(Q.Size); Q.Start := (Q.Start + 1) mod Command_Capacity; return True; end; Command_Queue: Command_Queue_Record; - Any_Key_Pressed: Boolean := False; + Holding_Shift: Boolean := False; procedure Swallow_Player_Input is begin Command_Queue.Size := 0; - Any_Key_Pressed := False; + Holding_Shift := False; end; procedure Game_Bombs_Turn(Game: in out Game_State) is @@ -979,7 +1097,7 @@ procedure Eepers is end loop; for Bomb of Game.Bombs loop if Bomb.Countdown > 0 then - Bomb.Countdown := Bomb.Countdown - 1; + Dec(Bomb.Countdown); if Bomb.Countdown <= 0 then Play_Sound(Blast_Sound); Explode(Game, Bomb.Position); @@ -1059,7 +1177,7 @@ procedure Eepers is when Eeper_Guard | Eeper_Mother => Recompute_Path_For_Eeper(Game, Me, GUARD_STEPS_LIMIT, GUARD_STEP_LENGTH_LIMIT); if Eeper.Path(Eeper.Position.Y, Eeper.Position.X) = 0 then - Game.Player.Dead := True; + Kill_Player(Game); Eeper.Eyes := Eyes_Surprised; elsif Eeper.Path(Eeper.Position.Y, Eeper.Position.X) > 0 then if Eeper.Attack_Cooldown <= 0 then @@ -1076,7 +1194,7 @@ procedure Eepers is Position := Position + Direction_Vector(Dir); if Within_Map(Game, Position) and then Eeper.Path(Position.Y, Position.X) = Current - 1 then Available_Positions(Count) := Position; - Count := Count + 1; + Inc(Count); exit; end if; end loop; @@ -1089,7 +1207,7 @@ procedure Eepers is end; Eeper.Attack_Cooldown := GUARD_ATTACK_COOLDOWN; else - Eeper.Attack_Cooldown := Eeper.Attack_Cooldown - 1; + Dec(Eeper.Attack_Cooldown); end if; if Eeper.Path(Eeper.Position.Y, Eeper.Position.X) = 1 then @@ -1100,7 +1218,7 @@ procedure Eepers is Eeper.Eyes_Target := Game.Player.Position; if Inside_Of_Rect(Eeper.Position, Eeper.Size, Game.Player.Position) then - Game.Player.Dead := True; + Kill_Player(Game); end if; else Eeper.Eyes := Eyes_Closed; @@ -1130,7 +1248,7 @@ procedure Eepers is and then Eeper.Path(New_Position.Y, New_Position.X) > Eeper.Path(Position.Y, Position.X) then Available_Positions(Count) := New_Position; - Count := Count + 1; + Inc(Count); end if; end; end loop; @@ -1155,9 +1273,9 @@ procedure Eepers is procedure Game_Items_Turn(Game: in out Game_State) is begin for Item of Game.Items loop - if Item.Kind = Item_Bomb_Gen then + if Item.Kind = Item_Bomb_Refill then if Item.Cooldown > 0 then - Item.Cooldown := Item.Cooldown - 1; + Dec(Item.Cooldown); end if; end if; end loop; @@ -1222,6 +1340,53 @@ procedure Eepers is end loop; end; + procedure Game_Tutorial(Game: in out Game_State) is + begin + case Game.Tutorial.Phase is + when Tutorial_Move => + if Game.Tutorial.Knows_How_To_Move then + Game.Tutorial.Phase := Tutorial_Place_Bombs; + Game.Tutorial.Waiting := 0.0; + Hide_Popup(Game.Tutorial.Popup); + elsif Game.Tutorial.Waiting < TUTORIAL_MOVE_WAIT_TIME_SECS then + Game.Tutorial.Waiting := Game.Tutorial.Waiting + Get_Frame_Time; + else + Show_Popup(Game.Tutorial.Popup, "WASD to Move"); + end if; + when Tutorial_Place_Bombs => + if Game.Tutorial.Knows_How_To_Place_Bombs then + Game.Tutorial.Phase := Tutorial_Waiting_For_Sprint; + Hide_Popup(Game.Tutorial.Popup); + elsif Game.Player.Bombs > 0 then + if Game.Tutorial.Waiting < TUTORIAL_BOMB_WAIT_TIME_SECS then + Game.Tutorial.Waiting := Game.Tutorial.Waiting + Get_Frame_Time; + else + Show_Popup(Game.Tutorial.Popup, "SPACE to Place Bombs"); + end if; + end if; + when Tutorial_Waiting_For_Sprint => + if Game.Tutorial.Hurry_Count >= 10 then + Game.Tutorial.Phase := Tutorial_Sprint; + Game.Tutorial.Waiting := 0.0; + end if; + when Tutorial_Sprint => + if Game.Tutorial.Knows_How_To_Sprint then + Game.Tutorial.Phase := Tutorial_Done; + Hide_Popup(Game.Tutorial.Popup); + else + if Game.Tutorial.Waiting < TUTORIAL_SPRINT_WAIT_TIME_SECS then + Show_Popup(Game.Tutorial.Popup, "Hold SHIFT to Sprint"); + Game.Tutorial.Waiting := Game.Tutorial.Waiting + Get_Frame_Time; + else + Game.Tutorial.Phase := Tutorial_Done; + Hide_Popup(Game.Tutorial.Popup); + end if; + end if; + when Tutorial_Done => null; + end case; + Draw_Popup(Game.Tutorial.Popup, Screen_Player_Position(Game), Cell_Size); + end; + procedure Game_Player(Game: in out Game_State) is Eyes_Angular_Direction: constant Float := Delta_Angle(Game.Player.Eyes_Angle, Look_At(To_Vector2(Game.Player.Position)*Cell_Size + Cell_Size*0.5, To_Vector2(Game.Player.Eyes_Target)*Cell_Size + Cell_Size*0.5)); begin @@ -1232,7 +1397,7 @@ procedure Eepers is Draw_Eyes(Screen_Player_Position(Game), Cell_Size, Game.Player.Eyes_Angle, Game.Player.Prev_Eyes, Game.Player.Eyes, Game.Turn_Animation); end if; - if Any_Key_Pressed then + if (Get_Time - Game.Player.Death_Time) > RESTART_TIMEOUT_SECS then Game_Restore_Checkpoint(Game); Game.Player.Dead := False; end if; @@ -1256,12 +1421,31 @@ procedure Eepers is declare Start_Of_Turn: constant Double := Get_Time; begin + Game.Tutorial.Knows_How_To_Move := True; + if Holding_Shift then + Game.Tutorial.Knows_How_To_Sprint := True; + end if; + + if Game.Tutorial.Phase = Tutorial_Waiting_For_Sprint then + declare + Step_Timestamp: constant Double := Get_Time; + Delta_Timestamp: constant Double := Step_Timestamp - Game.Tutorial.Prev_Step_Timestamp; + begin + if Delta_Timestamp < 0.2 Then + Inc(Game.Tutorial.Hurry_Count); + elsif Game.Tutorial.Hurry_Count > 0 then + Dec(Game.Tutorial.Hurry_Count); + end if; + Game.Tutorial.Prev_Step_Timestamp := Step_Timestamp; + end; + end if; + Game.Turn_Animation := 1.0; Game_Explosions_Turn(Game); Game_Items_Turn(Game); Game_Player_Turn(Game, C.Dir); - Game_Eepers_Turn(Game); Game_Bombs_Turn(Game); + Game_Eepers_Turn(Game); Game.Duration_Of_Last_Turn := Get_Time - Start_Of_Turn; end; when Command_Plant => @@ -1269,6 +1453,8 @@ procedure Eepers is declare Start_Of_Turn: constant Double := Get_Time; begin + Game.Tutorial.Knows_How_To_Place_Bombs := True; + Game.Turn_Animation := 1.0; Game_Explosions_Turn(Game); Game_Items_Turn(Game); @@ -1277,8 +1463,8 @@ procedure Eepers is Game.Player.Prev_Eyes := Game.Player.Eyes; Game.Player.Prev_Position := Game.Player.Position; - Game_Eepers_Turn(Game); Game_Bombs_Turn(Game); + Game_Eepers_Turn(Game); if Game.Player.Bombs > 0 then for Bomb of Game.Bombs loop @@ -1288,7 +1474,7 @@ procedure Eepers is exit; end if; end loop; - Game.Player.Bombs := Game.Player.Bombs - 1; + Dec(Game.Player.Bombs); Play_Sound(Plant_Bomb_Sound); end if; @@ -1320,23 +1506,27 @@ procedure Eepers is end; end loop; - for Index in 1..Game.Player.Bombs loop + for Index in 1..Game.Player.Bomb_Slots loop declare - Position: constant Vector2 := (100.0 + C_float(Index - 1)*Cell_Size.X, 200.0); + Padding: constant C_Float := Cell_Size.X*0.5; + Position: constant Vector2 := (100.0 + C_float(Index - 1)*(Cell_Size.X + Padding), 200.0); begin - Draw_Circle_V(Position, Cell_Size.X*0.5, Palette_RGB(COLOR_BOMB)); + if Index <= Game.Player.Bombs then + Draw_Circle_V(Position, Cell_Size.X*0.5, Palette_RGB(COLOR_BOMB)); + else + Draw_Circle_V(Position, Cell_Size.X*0.5, Color_Brightness(Palette_RGB(COLOR_BOMB), -0.5)); + end if; end; end loop; if Game.Player.Dead then declare Label: constant Char_Array := To_C("You Died!"); - Label_Height: constant Integer := 48; - Label_Width: constant Integer := Integer(Measure_Text(Label, Int(Label_Height))); - Text_Size: constant Vector2 := To_Vector2((Label_Width, Label_Height)); + Text_Size: constant Vector2 := Measure_Text_Ex(Death_Font, Label, C_Float(Death_Font_Size), 0.0); Position: constant Vector2 := Screen_Size*0.5 - Text_Size*0.5; begin - Draw_Text(Label, Int(Position.X), Int(Position.Y), Int(Label_Height), Palette_RGB(COLOR_LABEL)); + Draw_Text_Ex(Death_Font, Label, Position + (-2.0, 2.0), C_Float(Death_Font_Size), 0.0, Palette_RGB(COLOR_WALL)); + Draw_Text_Ex(Death_Font, Label, Position, C_Float(Death_Font_Size), 0.0, Palette_RGB(COLOR_PLAYER)); end; end if; end; @@ -1404,7 +1594,7 @@ procedure Eepers is end; Game: Game_State; - Title: constant Char_Array := To_C("Eepers (v1.2)"); + Title: constant Char_Array := To_C("Eepers (v1.4)"); Palette_Editor: Boolean := False; Palette_Editor_Choice: Palette := Palette'First; @@ -1440,6 +1630,11 @@ begin Set_Sound_Pitch(Checkpoint_Sound, 0.8); Guard_Step_Sound := Load_Sound(To_C("assets/sounds/guard-step.ogg")); -- https://opengameart.org/content/fire-whip-hit-yo-frankie Plant_Bomb_Sound := Load_Sound(To_C("assets/sounds/plant-bomb.wav")); -- https://opengameart.org/content/ui-soundpack-by-m1chiboi-bleeps-and-clicks + Popup_Show_Sound := Load_Sound(To_C("assets/sounds/popup-show.wav")); -- https://opengameart.org/content/ui-soundpack-by-m1chiboi-bleeps-and-clicks + Tutorial_Font := Load_Font_Ex(To_C("assets/fonts/Vollkorn/static/Vollkorn-Regular.ttf"), Tutorial_Font_Size, 0, 0); + Gen_Texture_Mipmaps(Tutorial_Font.Texture'Access); + Death_Font := Load_Font_Ex(To_C("assets/fonts/Vollkorn/static/Vollkorn-Regular.ttf"), Death_Font_Size, 0, 0); + Gen_Texture_Mipmaps(Death_Font.Texture'Access); Random_Integer.Reset(Gen); Load_Colors("assets/colors.txt"); @@ -1454,10 +1649,11 @@ begin Begin_Drawing; Clear_Background(Palette_RGB(COLOR_BACKGROUND)); + Holding_Shift := Boolean(Is_Key_Down(KEY_LEFT_SHIFT)) or else Boolean(Is_Key_Down(KEY_RIGHT_SHIFT)); if Game.Player.Dead then Command_Queue.Size := 0; else - if (Boolean(Is_Key_Down(KEY_LEFT_SHIFT)) or else Boolean(Is_Key_Down(KEY_RIGHT_SHIFT))) and then Game.Turn_Animation <= 0.0 then + if Holding_Shift and then Game.Turn_Animation <= 0.0 then if Is_Key_Down(KEY_A) or else Is_Key_Down(KEY_LEFT) then Command_Queue.Size := 0; Command_Enqueue(Command_Queue, (Kind => Command_Step, Dir => Left)); @@ -1492,7 +1688,7 @@ begin Command_Enqueue(Command_Queue, (Kind => Command_Plant)); end if; end if; - if Boolean(Is_Key_Down(KEY_LEFT_SHIFT)) or else Boolean(Is_Key_Down(KEY_RIGHT_SHIFT)) then + if Holding_Shift then TURN_DURATION_SECS := BASE_TURN_DURATION_SECS * 0.8; else if Command_Queue.Size /= 0 then @@ -1502,11 +1698,6 @@ begin end if; end if; - Any_Key_Pressed := False; - while not Any_Key_Pressed and then Get_Key_Pressed /= KEY_NULL loop - Any_Key_Pressed := True; - end loop; - if DEVELOPMENT then if Is_Key_Pressed(KEY_R) then Load_Game_From_Image("assets/map.png", Game, Update_Player => False, Update_Camera => False); @@ -1538,12 +1729,12 @@ begin end if; if Is_Key_Down(Keys(Up)) then - Palette_HSV(Palette_Editor_Choice)(Palette_Editor_Component) := Palette_HSV(Palette_Editor_Choice)(Palette_Editor_Component) + 1; + Inc(Palette_HSV(Palette_Editor_Choice)(Palette_Editor_Component)); Palette_RGB(Palette_Editor_Choice) := HSV_To_RGB(Palette_HSV(Palette_Editor_Choice)); end if; if Is_Key_Down(Keys(Down)) then - Palette_HSV(Palette_Editor_Choice)(Palette_Editor_Component) := Palette_HSV(Palette_Editor_Choice)(Palette_Editor_Component) - 1; + Dec(Palette_HSV(Palette_Editor_Choice)(Palette_Editor_Component)); Palette_RGB(Palette_Editor_Choice) := HSV_To_RGB(Palette_HSV(Palette_Editor_Choice)); end if; else @@ -1577,12 +1768,13 @@ begin end if; Game_Update_Camera(Game); - Begin_Mode2D(Game_Camera(Game)); + Begin_Mode2D(Game.Camera); Game_Cells(Game); Game_Items(Game); Game_Player(Game); Game_Eepers(Game); Game_Bombs(Game); + Game_Tutorial(Game); if DEVELOPMENT then if Is_Key_Down(KEY_P) then for Row in Game.Map'Range(1) loop @@ -1641,6 +1833,12 @@ begin end; -- TODO: Items in HUD may sometimes blend with the background +-- TODO: Different palettes on each NG+ +-- TODO: NG+ must make the Game harder while retaining the collected bomb slots +-- TODO: The gnome blocking trick was never properly explained. +-- We should introduce an extra room that entirely relies on that mechanic, +-- so it does not feel out of place, when you discover it on Mother. +-- TODO: The puzzle with Gnome blocking Guard repeated twice (which sucks) -- TODO: Footstep variation for Mother/Guard bosses (depending on the distance traveled?) -- TODO: Footsteps for mother should be lower -- TODO: Restarting should be considered a turn @@ -1648,7 +1846,6 @@ end; -- Or maybe we should just save Path Maps too? -- TODO: If you are standing on the refilled bomb gen and place a bomb you should refill your bomb in that turn. -- TODO: Checkpoints should be circles (like all the items) --- TODO: Custom font -- TODO: Mother should require several attacks before being "split" -- TODO: Enemies should attack on zero just like a bomb. -- TODO: Properly disablable DEV features @@ -1658,17 +1855,11 @@ end; -- TODO: Visual Clue that the Eeper is about to kill the Player when Completely outside of the Screen -- - Cooldown ball is shaking -- TODO: Cool animation for New Game --- TODO: Tutorial --- - Sign that says "WASD" to move when you start the game for the first time. --- - And how to place the bomb on picking it up. --- - How to sprint after you blow up firt Barricade. -- TODO: Count the player's turns towards the final score of the game -- We can even collect different stats, like bombs collected, bombs used, -- times died etc. -- TODO: Animate key when you pick it up -- Smoothly move it into the HUD. --- TODO: Different palettes depending on the area --- Or maybe different palette for each NG+ -- TODO: Particles -- - Player Death animation -- - Eeper Death animation @@ -7,6 +7,8 @@ package Raylib is type Bool is new Boolean; pragma Convention (C, Bool); + type Addr is mod 2 ** Standard'Address_Size; + procedure Init_Window(Width, Height: int; Title: in char_array) with Import => True, @@ -164,6 +166,30 @@ package Raylib is Import => True, Convention => C, External_Name => "DrawText"; + type Texture2D is record + Id: unsigned; + Width: int; + Height: int; + Mipmaps: int; + Format: int; + end record + with Convention => C_Pass_By_Copy; + + type Font is record + Base_Size: int; + Glyph_Count: int; + Glyph_Padding: int; + Texture: aliased Texture2D; + Rects: Addr; + Glyph_Info: Addr; + end record + with Convention => C_Pass_By_Copy; + + procedure Draw_Text_Ex(F: Font; Text: Char_Array; Position: Vector2; Font_Size: C_Float; Spacing: C_Float; Tint: Color) + with + Import => True, + Convention => C, + External_Name => "DrawTextEx"; procedure Set_Target_FPS(Fps: int) with Import => True, @@ -199,7 +225,6 @@ package Raylib is Import => True, Convention => C, External_Name => "GetTime"; - type Addr is mod 2 ** Standard'Address_Size; type Image is record Data: Addr; Width: int; @@ -333,4 +358,20 @@ package Raylib is Import => True, Convention => C, External_Name => "SetWindowIcon"; + + function Load_Font_Ex(File_Name: char_array; Font_Size: int; Codepoints: Addr; Codepoint_Count: Integer) return Font + with + Import => True, + Convention => C, + External_Name => "LoadFontEx"; + function Measure_Text_Ex(F: Font; Text: Char_Array; Font_Size: C_Float; Spacing: C_Float) return Vector2 + with + Import => True, + Convention => C, + External_Name => "MeasureTextEx"; + procedure Gen_Texture_Mipmaps(T: access Texture2D) + with + Import => True, + Convention => C, + External_Name => "GenTextureMipmaps"; end Raylib; |